Merge branch 'master' into Develop

This commit is contained in:
Ozzie Isaacs 2024-01-07 08:03:40 +01:00
commit 902fa254b0
62 changed files with 5674 additions and 5426 deletions

View File

@ -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

View File

@ -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:
try:
if BLEACH:
comments = clean_html(comments, tags=None, attributes=None)
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

View File

@ -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'

View File

@ -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:

View File

@ -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)

View File

@ -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
}

View File

@ -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")

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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')

File diff suppressed because it is too large Load Diff

View File

@ -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