Merge branch 'master' into Develop
This commit is contained in:
commit
902fa254b0
|
@ -65,7 +65,7 @@ Calibre-Web is a web app that offers a clean and intuitive interface for browsin
|
|||
|
||||
*Note: Raspberry Pi OS users may encounter issues during installation. If so, please update pip (`./venv/bin/python3 -m pip install --upgrade pip`) and/or install cargo (`sudo apt install cargo`) before retrying the installation.*
|
||||
|
||||
Refer to the Wiki for additional installation examples: [manual installation](https://github.com/janeczku/calibre-web/wiki/Manual-installation), [Linux Mint](https://github.com/janeczku/calibre-web/wiki/How-To:Install-Calibre-Web-in-Linux-Mint-19-or-20), [Cloud Provider](https://github.com/janeczku/calibre-web/wiki/How-To:-Install-Calibre-Web-on-a-Cloud-Provider).
|
||||
Refer to the Wiki for additional installation examples: [manual installation](https://github.com/janeczku/calibre-web/wiki/Manual-installation), [Linux Mint](https://github.com/janeczku/calibre-web/wiki/How-To:-Install-Calibre-Web-in-Linux-Mint-19-or-20), [Cloud Provider](https://github.com/janeczku/calibre-web/wiki/How-To:-Install-Calibre-Web-on-a-Cloud-Provider).
|
||||
|
||||
## Quick Start
|
||||
|
||||
|
|
|
@ -27,8 +27,10 @@ from shutil import copyfile
|
|||
from uuid import uuid4
|
||||
from markupsafe import escape, Markup # dependency of flask
|
||||
from functools import wraps
|
||||
from lxml.etree import ParserError
|
||||
|
||||
try:
|
||||
# at least bleach 6.0 is needed -> incomplatible change from list arguments to set arguments
|
||||
from bleach import clean_text as clean_html
|
||||
BLEACH = True
|
||||
except ImportError:
|
||||
|
@ -1001,10 +1003,14 @@ def edit_book_series_index(series_index, book):
|
|||
def edit_book_comments(comments, book):
|
||||
modify_date = False
|
||||
if comments:
|
||||
if BLEACH:
|
||||
comments = clean_html(comments, tags=None, attributes=None)
|
||||
else:
|
||||
comments = clean_html(comments)
|
||||
try:
|
||||
if BLEACH:
|
||||
comments = clean_html(comments, tags=set(), attributes=set())
|
||||
else:
|
||||
comments = clean_html(comments)
|
||||
except ParserError as e:
|
||||
log.error("Comments of book {} are corrupted: {}".format(book.id, e))
|
||||
comments = ""
|
||||
if len(book.comments):
|
||||
if book.comments[0].text != comments:
|
||||
book.comments[0].text = comments
|
||||
|
|
|
@ -103,7 +103,7 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension):
|
|||
elif s == 'date':
|
||||
epub_metadata[s] = tmp[0][:10]
|
||||
else:
|
||||
epub_metadata[s] = tmp[0]
|
||||
epub_metadata[s] = tmp[0].strip()
|
||||
else:
|
||||
epub_metadata[s] = 'Unknown'
|
||||
|
||||
|
|
|
@ -137,10 +137,13 @@ def convert_to_kobo_timestamp_string(timestamp):
|
|||
|
||||
@kobo.route("/v1/library/sync")
|
||||
@requires_kobo_auth
|
||||
@download_required
|
||||
# @download_required
|
||||
def HandleSyncRequest():
|
||||
if not current_user.role_download():
|
||||
log.info("Users need download permissions for syncing library to Kobo reader")
|
||||
return abort(403)
|
||||
sync_token = SyncToken.SyncToken.from_headers(request.headers)
|
||||
log.info("Kobo library sync request received.")
|
||||
log.info("Kobo library sync request received")
|
||||
log.debug("SyncToken: {}".format(sync_token))
|
||||
log.debug("Download link format {}".format(get_download_url_for_book('[bookid]','[bookformat]')))
|
||||
if not current_app.wsgi_app.is_proxied:
|
||||
|
|
|
@ -21,6 +21,7 @@ import os
|
|||
import errno
|
||||
import signal
|
||||
import socket
|
||||
import asyncio
|
||||
|
||||
try:
|
||||
from gevent.pywsgi import WSGIServer
|
||||
|
@ -326,4 +327,5 @@ class WebServer(object):
|
|||
if restart:
|
||||
self.wsgiserver.call_later(1.0, self.wsgiserver.stop)
|
||||
else:
|
||||
self.wsgiserver.add_callback_from_signal(self.wsgiserver.stop)
|
||||
self.wsgiserver.asyncio_loop.call_soon_threadsafe(self.wsgiserver.stop)
|
||||
|
||||
|
|
|
@ -3296,6 +3296,7 @@ div.btn-group[role=group][aria-label="Download, send to Kindle, reading"] .dropd
|
|||
left: 0 !important;
|
||||
}
|
||||
#add-to-shelves {
|
||||
min-height: 48px;
|
||||
max-height: calc(100% - 120px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
@ -4812,8 +4813,14 @@ body.advsearch:not(.blur) > div.container-fluid > div.row-fluid > div.col-sm-10
|
|||
z-index: 999999999999999999999999999999999999
|
||||
}
|
||||
|
||||
.search #shelf-actions, body.login .home-btn {
|
||||
display: none
|
||||
body.search #shelf-actions button#add-to-shelf {
|
||||
height: 40px;
|
||||
}
|
||||
@media screen and (max-width: 767px) {
|
||||
body.search .discover, body.advsearch .discover {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
body.read:not(.blur) a[href*=readbooks] {
|
||||
|
@ -5134,7 +5141,7 @@ body.login > div.navbar.navbar-default.navbar-static-top > div > div.navbar-head
|
|||
right: 5px
|
||||
}
|
||||
|
||||
#shelf-actions > .btn-group.open, .downloadBtn.open, .profileDrop[aria-expanded=true] {
|
||||
body:not(.search) #shelf-actions > .btn-group.open, .downloadBtn.open, .profileDrop[aria-expanded=true] {
|
||||
pointer-events: none
|
||||
}
|
||||
|
||||
|
@ -5151,7 +5158,7 @@ body.login > div.navbar.navbar-default.navbar-static-top > div > div.navbar-head
|
|||
color: var(--color-primary)
|
||||
}
|
||||
|
||||
#shelf-actions, #shelf-actions > .btn-group, #shelf-actions > .btn-group > .empty-ul {
|
||||
body:not(.search) #shelf-actions, body:not(.search) #shelf-actions > .btn-group, body:not(.search) #shelf-actions > .btn-group > .empty-ul {
|
||||
pointer-events: none
|
||||
}
|
||||
|
||||
|
|
|
@ -369,6 +369,13 @@ $("div.comments").readmore({
|
|||
// End of Global Work //
|
||||
///////////////////////////////
|
||||
|
||||
// Search Results
|
||||
if($("body.search").length > 0) {
|
||||
$('div[aria-label="Add to shelves"]').click(function () {
|
||||
$("#add-to-shelves").toggle();
|
||||
});
|
||||
}
|
||||
|
||||
// Advanced Search Results
|
||||
if($("body.advsearch").length > 0) {
|
||||
$("#loader + .container-fluid")
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
27
cps/web.py
27
cps/web.py
|
@ -1195,8 +1195,15 @@ def serve_book(book_id, book_format, anyname):
|
|||
rawdata = open(os.path.join(config.get_book_path(), book.path, data.name + "." + book_format),
|
||||
"rb").read()
|
||||
result = chardet.detect(rawdata)
|
||||
return make_response(
|
||||
rawdata.decode(result['encoding'], 'surrogatepass').encode('utf-8', 'surrogatepass'))
|
||||
try:
|
||||
text_data = rawdata.decode(result['encoding']).encode('utf-8')
|
||||
except UnicodeDecodeError as e:
|
||||
log.error("Encoding error in text file {}: {}".format(book.id, e))
|
||||
if "surrogate" in e.reason:
|
||||
text_data = rawdata.decode(result['encoding'], 'surrogatepass').encode('utf-8', 'surrogatepass')
|
||||
else:
|
||||
text_data = rawdata.decode(result['encoding'], 'ignore').encode('utf-8', 'ignore')
|
||||
return make_response(text_data)
|
||||
except FileNotFoundError:
|
||||
log.error("File Not Found")
|
||||
return "File Not Found"
|
||||
|
@ -1347,21 +1354,21 @@ def login():
|
|||
@limiter.limit("3/minute", key_func=lambda: request.form.get('username', "").strip().lower())
|
||||
def login_post():
|
||||
form = request.form.to_dict()
|
||||
username = form.get('username', "").strip().lower().replace("\n","\\n").replace("\r","")
|
||||
try:
|
||||
limiter.check()
|
||||
except RateLimitExceeded:
|
||||
flash(_(u"Please wait one minute before next login"), category="error")
|
||||
return render_login(form.get("username", ""), form.get("password", ""))
|
||||
return render_login(username, form.get("password", ""))
|
||||
if current_user is not None and current_user.is_authenticated:
|
||||
return redirect(url_for('web.index'))
|
||||
if config.config_login_type == constants.LOGIN_LDAP and not services.ldap:
|
||||
log.error(u"Cannot activate LDAP authentication")
|
||||
flash(_(u"Cannot activate LDAP authentication"), category="error")
|
||||
user = ub.session.query(ub.User).filter(func.lower(ub.User.name) == form.get('username', "").strip().lower()) \
|
||||
.first()
|
||||
user = ub.session.query(ub.User).filter(func.lower(ub.User.name) == username).first()
|
||||
remember_me = bool(form.get('remember_me'))
|
||||
if config.config_login_type == constants.LOGIN_LDAP and services.ldap and user and form['password'] != "":
|
||||
login_result, error = services.ldap.bind_user(form['username'], form['password'])
|
||||
login_result, error = services.ldap.bind_user(username, form['password'])
|
||||
if login_result:
|
||||
log.debug(u"You are now logged in as: '{}'".format(user.name))
|
||||
return handle_login_user(user,
|
||||
|
@ -1381,7 +1388,7 @@ def login_post():
|
|||
flash(_(u"Could not login: %(message)s", message=error), category="error")
|
||||
else:
|
||||
ip_address = request.headers.get('X-Forwarded-For', request.remote_addr)
|
||||
log.warning('LDAP Login failed for user "%s" IP-address: %s', form['username'], ip_address)
|
||||
log.warning('LDAP Login failed for user "%s" IP-address: %s', username, ip_address)
|
||||
flash(_(u"Wrong Username or Password"), category="error")
|
||||
else:
|
||||
ip_address = request.headers.get('X-Forwarded-For', request.remote_addr)
|
||||
|
@ -1390,7 +1397,7 @@ def login_post():
|
|||
ret, __ = reset_password(user.id)
|
||||
if ret == 1:
|
||||
flash(_(u"New Password was send to your email address"), category="info")
|
||||
log.info('Password reset for user "%s" IP-address: %s', form['username'], ip_address)
|
||||
log.info('Password reset for user "%s" IP-address: %s', username, ip_address)
|
||||
else:
|
||||
log.error(u"An unknown error occurred. Please try again later")
|
||||
flash(_(u"An unknown error occurred. Please try again later."), category="error")
|
||||
|
@ -1406,9 +1413,9 @@ def login_post():
|
|||
_(u"You are now logged in as: '%(nickname)s'", nickname=user.name),
|
||||
"success")
|
||||
else:
|
||||
log.warning('Login failed for user "{}" IP-address: {}'.format(form['username'], ip_address))
|
||||
log.warning('Login failed for user "{}" IP-address: {}'.format(username, ip_address))
|
||||
flash(_(u"Wrong Username or Password"), category="error")
|
||||
return render_login(form.get("username", ""), form.get("password", ""))
|
||||
return render_login(username, form.get("password", ""))
|
||||
|
||||
|
||||
@web.route('/logout')
|
||||
|
|
406
messages.pot
406
messages.pot
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,5 @@
|
|||
# GDrive Integration
|
||||
google-api-python-client>=1.7.11,<2.98.0
|
||||
google-api-python-client>=1.7.11,<2.108.0
|
||||
gevent>20.6.0,<24.0.0
|
||||
greenlet>=0.4.17,<2.1.0
|
||||
httplib2>=0.9.2,<0.23.0
|
||||
|
@ -13,7 +13,7 @@ rsa>=3.4.2,<4.10.0
|
|||
|
||||
# Gmail
|
||||
google-auth-oauthlib>=0.4.3,<1.1.0
|
||||
google-api-python-client>=1.7.11,<2.98.0
|
||||
google-api-python-client>=1.7.11,<2.108.0
|
||||
|
||||
# goodreads
|
||||
goodreads>=0.3.2,<0.4.0
|
||||
|
|
Loading…
Reference in New Issue
Block a user