mirror of
https://github.com/janeczku/calibre-web
synced 2025-08-03 12:35:28 +00:00
Merge branch 'master' into Develop
This commit is contained in:
commit
d08acdc3cc
@ -58,8 +58,8 @@ mimetypes.add_type('application/epub+zip', '.kepub')
|
|||||||
mimetypes.add_type('application/fb2+zip', '.fb2')
|
mimetypes.add_type('application/fb2+zip', '.fb2')
|
||||||
mimetypes.add_type('application/x-mobipocket-ebook', '.mobi')
|
mimetypes.add_type('application/x-mobipocket-ebook', '.mobi')
|
||||||
mimetypes.add_type('application/octet-stream', '.prc')
|
mimetypes.add_type('application/octet-stream', '.prc')
|
||||||
mimetypes.add_type('application/vnd.amazon.ebook', '.azw')
|
mimetypes.add_type('application/x-mobipocket-ebook', '.azw')
|
||||||
mimetypes.add_type('application/x-mobi8-ebook', '.azw3')
|
mimetypes.add_type('application/x-mobipocket-ebook', '.azw3')
|
||||||
mimetypes.add_type('application/x-cbr', '.cbr')
|
mimetypes.add_type('application/x-cbr', '.cbr')
|
||||||
mimetypes.add_type('application/x-cbz', '.cbz')
|
mimetypes.add_type('application/x-cbz', '.cbz')
|
||||||
mimetypes.add_type('application/x-tar', '.cbt')
|
mimetypes.add_type('application/x-tar', '.cbt')
|
||||||
@ -77,7 +77,7 @@ mimetypes.add_type('audio/ogg', '.ogg')
|
|||||||
mimetypes.add_type('application/ogg', '.oga')
|
mimetypes.add_type('application/ogg', '.oga')
|
||||||
mimetypes.add_type('text/css', '.css')
|
mimetypes.add_type('text/css', '.css')
|
||||||
mimetypes.add_type('application/x-ms-reader', '.lit')
|
mimetypes.add_type('application/x-ms-reader', '.lit')
|
||||||
mimetypes.add_type('text/javascript; charset=UTF-8', '.js')
|
mimetypes.add_type('text/javascript', '.js')
|
||||||
mimetypes.add_type('text/rtf', '.rtf')
|
mimetypes.add_type('text/rtf', '.rtf')
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
@ -425,7 +425,7 @@ def table_get_locale():
|
|||||||
current_locale = get_locale()
|
current_locale = get_locale()
|
||||||
for loc in locale:
|
for loc in locale:
|
||||||
ret.append({'value': str(loc), 'text': loc.get_language_name(current_locale)})
|
ret.append({'value': str(loc), 'text': loc.get_language_name(current_locale)})
|
||||||
return json.dumps(ret)
|
return json.dumps(sorted(ret, key=lambda x: x['text']))
|
||||||
|
|
||||||
|
|
||||||
@admi.route("/ajax/getdefaultlanguage")
|
@admi.route("/ajax/getdefaultlanguage")
|
||||||
@ -437,7 +437,7 @@ def table_get_default_lang():
|
|||||||
ret.append({'value': 'all', 'text': _('Show All')})
|
ret.append({'value': 'all', 'text': _('Show All')})
|
||||||
for lang in languages:
|
for lang in languages:
|
||||||
ret.append({'value': lang.lang_code, 'text': lang.name})
|
ret.append({'value': lang.lang_code, 'text': lang.name})
|
||||||
return json.dumps(ret)
|
return json.dumps(sorted(ret, key=lambda x: x['text']))
|
||||||
|
|
||||||
|
|
||||||
@admi.route("/ajax/editlistusers/<param>", methods=['POST'])
|
@admi.route("/ajax/editlistusers/<param>", methods=['POST'])
|
||||||
|
@ -36,7 +36,7 @@ def cover_processing(tmp_file_path, img, extension):
|
|||||||
if use_IM:
|
if use_IM:
|
||||||
with Image(blob=img) as imgc:
|
with Image(blob=img) as imgc:
|
||||||
imgc.format = 'jpeg'
|
imgc.format = 'jpeg'
|
||||||
imgc.transform_colorspace('rgb')
|
imgc.transform_colorspace('srgb')
|
||||||
imgc.save(filename=tmp_cover_name)
|
imgc.save(filename=tmp_cover_name)
|
||||||
return tmp_cover_name
|
return tmp_cover_name
|
||||||
else:
|
else:
|
||||||
|
@ -34,7 +34,7 @@ def get_user_locale_language(user_language):
|
|||||||
|
|
||||||
|
|
||||||
def get_available_locale():
|
def get_available_locale():
|
||||||
return [Locale('en')] + babel.list_translations()
|
return sorted(babel.list_translations(), key=lambda x: x.display_name.lower())
|
||||||
|
|
||||||
|
|
||||||
def get_available_translations():
|
def get_available_translations():
|
||||||
|
@ -56,7 +56,7 @@ def get_epub_layout(book, book_data):
|
|||||||
p = tree.xpath('/pkg:package/pkg:metadata', namespaces=default_ns)[0]
|
p = tree.xpath('/pkg:package/pkg:metadata', namespaces=default_ns)[0]
|
||||||
|
|
||||||
layout = p.xpath('pkg:meta[@property="rendition:layout"]/text()', namespaces=default_ns)
|
layout = p.xpath('pkg:meta[@property="rendition:layout"]/text()', namespaces=default_ns)
|
||||||
except (etree.XMLSyntaxError, KeyError, IndexError, OSError) as e:
|
except (etree.XMLSyntaxError, KeyError, IndexError, OSError, UnicodeDecodeError) as e:
|
||||||
log.error("Could not parse epub metadata of book {} during kobo sync: {}".format(book.id, e))
|
log.error("Could not parse epub metadata of book {} during kobo sync: {}".format(book.id, e))
|
||||||
layout = []
|
layout = []
|
||||||
|
|
||||||
|
@ -237,7 +237,7 @@ def send_mail(book_id, book_format, convert, ereader_mail, calibrepath, user_id)
|
|||||||
return _("The requested file could not be read. Maybe wrong permissions?")
|
return _("The requested file could not be read. Maybe wrong permissions?")
|
||||||
|
|
||||||
|
|
||||||
def get_valid_filename(value, replace_whitespace=True, chars=128):
|
def get_valid_filename(value, replace_whitespace=True, chars=128, force_unidecode=False):
|
||||||
"""
|
"""
|
||||||
Returns the given string converted to a string that can be used for a clean
|
Returns the given string converted to a string that can be used for a clean
|
||||||
filename. Limits num characters to 128 max.
|
filename. Limits num characters to 128 max.
|
||||||
@ -245,7 +245,7 @@ def get_valid_filename(value, replace_whitespace=True, chars=128):
|
|||||||
if value[-1:] == '.':
|
if value[-1:] == '.':
|
||||||
value = value[:-1]+'_'
|
value = value[:-1]+'_'
|
||||||
value = value.replace("/", "_").replace(":", "_").strip('\0')
|
value = value.replace("/", "_").replace(":", "_").strip('\0')
|
||||||
if config.config_unicode_filename:
|
if config.config_unicode_filename or force_unidecode:
|
||||||
value = (unidecode.unidecode(value))
|
value = (unidecode.unidecode(value))
|
||||||
if replace_whitespace:
|
if replace_whitespace:
|
||||||
# *+:\"/<>? are replaced by _
|
# *+:\"/<>? are replaced by _
|
||||||
@ -891,7 +891,7 @@ def save_cover(img, book_path):
|
|||||||
else:
|
else:
|
||||||
imgc = Image(blob=io.BytesIO(img.content))
|
imgc = Image(blob=io.BytesIO(img.content))
|
||||||
imgc.format = 'jpeg'
|
imgc.format = 'jpeg'
|
||||||
imgc.transform_colorspace("rgb")
|
imgc.transform_colorspace("srgb")
|
||||||
img = imgc
|
img = imgc
|
||||||
except (BlobError, MissingDelegateError):
|
except (BlobError, MissingDelegateError):
|
||||||
log.error("Invalid cover file content")
|
log.error("Invalid cover file content")
|
||||||
@ -1091,11 +1091,14 @@ def get_download_link(book_id, book_format, client):
|
|||||||
file_name = book.title
|
file_name = book.title
|
||||||
if len(book.authors) > 0:
|
if len(book.authors) > 0:
|
||||||
file_name = file_name + ' - ' + book.authors[0].name
|
file_name = file_name + ' - ' + book.authors[0].name
|
||||||
file_name = get_valid_filename(file_name, replace_whitespace=False)
|
if client == "kindle":
|
||||||
|
file_name = get_valid_filename(file_name, replace_whitespace=False, force_unidecode=True)
|
||||||
|
else:
|
||||||
|
file_name = quote(get_valid_filename(file_name, replace_whitespace=False))
|
||||||
headers = Headers()
|
headers = Headers()
|
||||||
headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream")
|
headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream")
|
||||||
headers["Content-Disposition"] = "attachment; filename=%s.%s; filename*=UTF-8''%s.%s" % (
|
headers["Content-Disposition"] = ('attachment; filename="{}.{}"; filename*=UTF-8\'\'{}.{}').format(
|
||||||
quote(file_name), book_format, quote(file_name), book_format)
|
file_name, book_format, file_name, book_format)
|
||||||
return do_download_file(book, book_format, client, data1, headers)
|
return do_download_file(book, book_format, client, data1, headers)
|
||||||
else:
|
else:
|
||||||
log.error("Book id {} not found for downloading".format(book_id))
|
log.error("Book id {} not found for downloading".format(book_id))
|
||||||
|
37
cps/kobo.py
37
cps/kobo.py
@ -106,24 +106,29 @@ def make_request_to_kobo_store(sync_token=None):
|
|||||||
return store_response
|
return store_response
|
||||||
|
|
||||||
|
|
||||||
def redirect_or_proxy_request():
|
def redirect_or_proxy_request(auth=False):
|
||||||
if config.config_kobo_proxy:
|
if config.config_kobo_proxy:
|
||||||
if request.method == "GET":
|
try:
|
||||||
return redirect(get_store_url_for_current_request(), 307)
|
if request.method == "GET":
|
||||||
else:
|
alfa = redirect(get_store_url_for_current_request(), 307)
|
||||||
# The Kobo device turns other request types into GET requests on redirects,
|
return alfa
|
||||||
# so we instead proxy to the Kobo store ourselves.
|
else:
|
||||||
store_response = make_request_to_kobo_store()
|
# The Kobo device turns other request types into GET requests on redirects,
|
||||||
|
# so we instead proxy to the Kobo store ourselves.
|
||||||
|
store_response = make_request_to_kobo_store()
|
||||||
|
|
||||||
response_headers = store_response.headers
|
response_headers = store_response.headers
|
||||||
for header_key in CONNECTION_SPECIFIC_HEADERS:
|
for header_key in CONNECTION_SPECIFIC_HEADERS:
|
||||||
response_headers.pop(header_key, default=None)
|
response_headers.pop(header_key, default=None)
|
||||||
|
|
||||||
return make_response(
|
return make_response(
|
||||||
store_response.content, store_response.status_code, response_headers.items()
|
store_response.content, store_response.status_code, response_headers.items()
|
||||||
)
|
)
|
||||||
else:
|
except Exception as e:
|
||||||
return make_response(jsonify({}))
|
log.error("Failed to receive or parse response from Kobo's endpoint: {}".format(e))
|
||||||
|
if auth:
|
||||||
|
return make_calibre_web_auth_response()
|
||||||
|
return make_response(jsonify({}))
|
||||||
|
|
||||||
|
|
||||||
def convert_to_kobo_timestamp_string(timestamp):
|
def convert_to_kobo_timestamp_string(timestamp):
|
||||||
@ -1042,7 +1047,7 @@ def HandleAuthRequest():
|
|||||||
log.debug('Kobo Auth request')
|
log.debug('Kobo Auth request')
|
||||||
if config.config_kobo_proxy:
|
if config.config_kobo_proxy:
|
||||||
try:
|
try:
|
||||||
return redirect_or_proxy_request()
|
return redirect_or_proxy_request(auth=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
log.error("Failed to receive or parse response from Kobo's auth endpoint. Falling back to un-proxied mode.")
|
log.error("Failed to receive or parse response from Kobo's auth endpoint. Falling back to un-proxied mode.")
|
||||||
return make_calibre_web_auth_response()
|
return make_calibre_web_auth_response()
|
||||||
|
@ -29,7 +29,7 @@ from .constants import CONFIG_DIR as _CONFIG_DIR
|
|||||||
ACCESS_FORMATTER_GEVENT = Formatter("%(message)s")
|
ACCESS_FORMATTER_GEVENT = Formatter("%(message)s")
|
||||||
ACCESS_FORMATTER_TORNADO = Formatter("[%(asctime)s] %(message)s")
|
ACCESS_FORMATTER_TORNADO = Formatter("[%(asctime)s] %(message)s")
|
||||||
|
|
||||||
FORMATTER = Formatter("[%(asctime)s] %(levelname)5s {%(name)s:%(lineno)d} %(message)s")
|
FORMATTER = Formatter("[%(asctime)s] %(levelname)5s {%(filename)s:%(lineno)d} %(message)s")
|
||||||
DEFAULT_LOG_LEVEL = logging.INFO
|
DEFAULT_LOG_LEVEL = logging.INFO
|
||||||
DEFAULT_LOG_FILE = os.path.join(_CONFIG_DIR, "calibre-web.log")
|
DEFAULT_LOG_FILE = os.path.join(_CONFIG_DIR, "calibre-web.log")
|
||||||
DEFAULT_ACCESS_LOG = os.path.join(_CONFIG_DIR, "access.log")
|
DEFAULT_ACCESS_LOG = os.path.join(_CONFIG_DIR, "access.log")
|
||||||
@ -42,18 +42,12 @@ logging.addLevelName(logging.CRITICAL, "CRIT")
|
|||||||
|
|
||||||
class _Logger(logging.Logger):
|
class _Logger(logging.Logger):
|
||||||
|
|
||||||
def error_or_exception(self, message, stacklevel=2, *args, **kwargs):
|
def error_or_exception(self, message, stacklevel=1, *args, **kwargs):
|
||||||
is_debug = self.getEffectiveLevel() <= logging.DEBUG
|
is_debug = self.getEffectiveLevel() <= logging.DEBUG
|
||||||
if sys.version_info > (3, 7):
|
if not is_debug:
|
||||||
if is_debug:
|
self.exception(message, stacklevel=stacklevel, *args, **kwargs)
|
||||||
self.exception(message, stacklevel=stacklevel, *args, **kwargs)
|
|
||||||
else:
|
|
||||||
self.error(message, stacklevel=stacklevel, *args, **kwargs)
|
|
||||||
else:
|
else:
|
||||||
if is_debug:
|
self.error(message, stacklevel=stacklevel, *args, **kwargs)
|
||||||
self.exception(message, stack_info=True, *args, **kwargs)
|
|
||||||
else:
|
|
||||||
self.error(message, *args, **kwargs)
|
|
||||||
|
|
||||||
def debug_no_auth(self, message, *args, **kwargs):
|
def debug_no_auth(self, message, *args, **kwargs):
|
||||||
message = message.strip("\r\n")
|
message = message.strip("\r\n")
|
||||||
|
@ -7153,12 +7153,11 @@ body.edituser.admin > div.container-fluid > div.row-fluid > div.col-sm-10 > div.
|
|||||||
}
|
}
|
||||||
|
|
||||||
body.editbook > div.container-fluid > div.row-fluid > div.col-sm-10 > div.col-sm-3, body.upload > div.container-fluid > div.row-fluid > div.col-sm-10 > div.col-sm-3 {
|
body.editbook > div.container-fluid > div.row-fluid > div.col-sm-10 > div.col-sm-3, body.upload > div.container-fluid > div.row-fluid > div.col-sm-10 > div.col-sm-3 {
|
||||||
max-width: 130px;
|
position: relative;
|
||||||
width: 130px;
|
max-width: unset;
|
||||||
height: 180px;
|
width: 100%;
|
||||||
margin: 0;
|
height: unset;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
position: absolute
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body.editbook > div.container-fluid > div.row-fluid > div.col-sm-10 > form > div.col-sm-9, body.upload > div.container-fluid > div.row-fluid > div.col-sm-10 > form > div.col-sm-9 {
|
body.editbook > div.container-fluid > div.row-fluid > div.col-sm-10 > form > div.col-sm-9, body.upload > div.container-fluid > div.row-fluid > div.col-sm-10 > form > div.col-sm-9 {
|
||||||
@ -7167,10 +7166,6 @@ body.edituser.admin > div.container-fluid > div.row-fluid > div.col-sm-10 > div.
|
|||||||
width: 100%
|
width: 100%
|
||||||
}
|
}
|
||||||
|
|
||||||
body.editbook > div.container-fluid > div.row-fluid > div.col-sm-10 > form > div.col-sm-9 > .form-group:nth-child(1), body.editbook > div.container-fluid > div.row-fluid > div.col-sm-10 > form > div.col-sm-9 > .form-group:nth-child(2), body.upload > div.container-fluid > div.row-fluid > div.col-sm-10 > form > div.col-sm-9 > .form-group:nth-child(1), body.upload > div.container-fluid > div.row-fluid > div.col-sm-10 > form > div.col-sm-9 > .form-group:nth-child(2) {
|
|
||||||
padding-left: 120px
|
|
||||||
}
|
|
||||||
|
|
||||||
#deleteButton, body.editbook > div.container-fluid > div.row-fluid > div.col-sm-10 > div.col-sm-3 > div.text-center > #delete, body.upload > div.container-fluid > div.row-fluid > div.col-sm-10 > div.col-sm-3 > div.text-center > #delete {
|
#deleteButton, body.editbook > div.container-fluid > div.row-fluid > div.col-sm-10 > div.col-sm-3 > div.text-center > #delete, body.upload > div.container-fluid > div.row-fluid > div.col-sm-10 > div.col-sm-3 > div.text-center > #delete {
|
||||||
top: 48px;
|
top: 48px;
|
||||||
height: 42px
|
height: 42px
|
||||||
|
@ -16667,7 +16667,7 @@ const PDFWorkerUtil = {
|
|||||||
{
|
{
|
||||||
if (isNodeJS) {
|
if (isNodeJS) {
|
||||||
PDFWorkerUtil.isWorkerDisabled = true;
|
PDFWorkerUtil.isWorkerDisabled = true;
|
||||||
GlobalWorkerOptions.workerSrc ||= "./pdf.worker.mjs";
|
GlobalWorkerOptions.workerSrc ||= "./pdf.worker.js";
|
||||||
}
|
}
|
||||||
PDFWorkerUtil.isSameOrigin = function (baseUrl, otherUrl) {
|
PDFWorkerUtil.isSameOrigin = function (baseUrl, otherUrl) {
|
||||||
let base;
|
let base;
|
||||||
@ -24745,4 +24745,4 @@ var __webpack_exports__shadow = __webpack_exports__.shadow;
|
|||||||
var __webpack_exports__version = __webpack_exports__.version;
|
var __webpack_exports__version = __webpack_exports__.version;
|
||||||
export { __webpack_exports__AbortException as AbortException, __webpack_exports__AnnotationEditorLayer as AnnotationEditorLayer, __webpack_exports__AnnotationEditorParamsType as AnnotationEditorParamsType, __webpack_exports__AnnotationEditorType as AnnotationEditorType, __webpack_exports__AnnotationEditorUIManager as AnnotationEditorUIManager, __webpack_exports__AnnotationLayer as AnnotationLayer, __webpack_exports__AnnotationMode as AnnotationMode, __webpack_exports__CMapCompressionType as CMapCompressionType, __webpack_exports__ColorPicker as ColorPicker, __webpack_exports__DOMSVGFactory as DOMSVGFactory, __webpack_exports__DrawLayer as DrawLayer, __webpack_exports__FeatureTest as FeatureTest, __webpack_exports__GlobalWorkerOptions as GlobalWorkerOptions, __webpack_exports__ImageKind as ImageKind, __webpack_exports__InvalidPDFException as InvalidPDFException, __webpack_exports__MissingPDFException as MissingPDFException, __webpack_exports__OPS as OPS, __webpack_exports__PDFDataRangeTransport as PDFDataRangeTransport, __webpack_exports__PDFDateString as PDFDateString, __webpack_exports__PDFWorker as PDFWorker, __webpack_exports__PasswordResponses as PasswordResponses, __webpack_exports__PermissionFlag as PermissionFlag, __webpack_exports__PixelsPerInch as PixelsPerInch, __webpack_exports__RenderingCancelledException as RenderingCancelledException, __webpack_exports__TextLayer as TextLayer, __webpack_exports__UnexpectedResponseException as UnexpectedResponseException, __webpack_exports__Util as Util, __webpack_exports__VerbosityLevel as VerbosityLevel, __webpack_exports__XfaLayer as XfaLayer, __webpack_exports__build as build, __webpack_exports__createValidAbsoluteUrl as createValidAbsoluteUrl, __webpack_exports__fetchData as fetchData, __webpack_exports__getDocument as getDocument, __webpack_exports__getFilenameFromUrl as getFilenameFromUrl, __webpack_exports__getPdfFilenameFromUrl as getPdfFilenameFromUrl, __webpack_exports__getXfaPageViewport as getXfaPageViewport, __webpack_exports__isDataScheme as isDataScheme, __webpack_exports__isPdfFile as isPdfFile, __webpack_exports__noContextMenu as noContextMenu, __webpack_exports__normalizeUnicode as normalizeUnicode, __webpack_exports__setLayerDimensions as setLayerDimensions, __webpack_exports__shadow as shadow, __webpack_exports__version as version };
|
export { __webpack_exports__AbortException as AbortException, __webpack_exports__AnnotationEditorLayer as AnnotationEditorLayer, __webpack_exports__AnnotationEditorParamsType as AnnotationEditorParamsType, __webpack_exports__AnnotationEditorType as AnnotationEditorType, __webpack_exports__AnnotationEditorUIManager as AnnotationEditorUIManager, __webpack_exports__AnnotationLayer as AnnotationLayer, __webpack_exports__AnnotationMode as AnnotationMode, __webpack_exports__CMapCompressionType as CMapCompressionType, __webpack_exports__ColorPicker as ColorPicker, __webpack_exports__DOMSVGFactory as DOMSVGFactory, __webpack_exports__DrawLayer as DrawLayer, __webpack_exports__FeatureTest as FeatureTest, __webpack_exports__GlobalWorkerOptions as GlobalWorkerOptions, __webpack_exports__ImageKind as ImageKind, __webpack_exports__InvalidPDFException as InvalidPDFException, __webpack_exports__MissingPDFException as MissingPDFException, __webpack_exports__OPS as OPS, __webpack_exports__PDFDataRangeTransport as PDFDataRangeTransport, __webpack_exports__PDFDateString as PDFDateString, __webpack_exports__PDFWorker as PDFWorker, __webpack_exports__PasswordResponses as PasswordResponses, __webpack_exports__PermissionFlag as PermissionFlag, __webpack_exports__PixelsPerInch as PixelsPerInch, __webpack_exports__RenderingCancelledException as RenderingCancelledException, __webpack_exports__TextLayer as TextLayer, __webpack_exports__UnexpectedResponseException as UnexpectedResponseException, __webpack_exports__Util as Util, __webpack_exports__VerbosityLevel as VerbosityLevel, __webpack_exports__XfaLayer as XfaLayer, __webpack_exports__build as build, __webpack_exports__createValidAbsoluteUrl as createValidAbsoluteUrl, __webpack_exports__fetchData as fetchData, __webpack_exports__getDocument as getDocument, __webpack_exports__getFilenameFromUrl as getFilenameFromUrl, __webpack_exports__getPdfFilenameFromUrl as getPdfFilenameFromUrl, __webpack_exports__getXfaPageViewport as getXfaPageViewport, __webpack_exports__isDataScheme as isDataScheme, __webpack_exports__isPdfFile as isPdfFile, __webpack_exports__noContextMenu as noContextMenu, __webpack_exports__normalizeUnicode as normalizeUnicode, __webpack_exports__setLayerDimensions as setLayerDimensions, __webpack_exports__shadow as shadow, __webpack_exports__version as version };
|
||||||
|
|
||||||
//# sourceMappingURL=pdf.mjs.map
|
//# sourceMappingURL=pdf.mjs.map
|
@ -828,7 +828,7 @@ const defaultOptions = {
|
|||||||
kind: OptionKind.WORKER
|
kind: OptionKind.WORKER
|
||||||
},
|
},
|
||||||
workerSrc: {
|
workerSrc: {
|
||||||
value: "../build/pdf.worker.mjs",
|
value: "../build/pdf.worker.js",
|
||||||
kind: OptionKind.WORKER
|
kind: OptionKind.WORKER
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -25,7 +25,7 @@ import mimetypes
|
|||||||
|
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from email.message import EmailMessage
|
from email.message import EmailMessage
|
||||||
from email.utils import formatdate, parseaddr
|
from email.utils import formatdate, parseaddr, make_msgid
|
||||||
from email.generator import Generator
|
from email.generator import Generator
|
||||||
from flask_babel import lazy_gettext as N_
|
from flask_babel import lazy_gettext as N_
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ from cps.embed_helper import do_calibre_export
|
|||||||
from cps import logger, config
|
from cps import logger, config
|
||||||
from cps import gdriveutils
|
from cps import gdriveutils
|
||||||
from cps.string_helper import strip_whitespaces
|
from cps.string_helper import strip_whitespaces
|
||||||
import uuid
|
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ class EmailBase:
|
|||||||
|
|
||||||
def send(self, strg):
|
def send(self, strg):
|
||||||
"""Send `strg' to the server."""
|
"""Send `strg' to the server."""
|
||||||
log.debug_no_auth('send: {}'.format(strg[:300]))
|
log.debug_no_auth('send: {}'.format(strg[:300]), stacklevel=2)
|
||||||
if hasattr(self, 'sock') and self.sock:
|
if hasattr(self, 'sock') and self.sock:
|
||||||
try:
|
try:
|
||||||
if self.transferSize:
|
if self.transferSize:
|
||||||
@ -142,7 +142,7 @@ class TaskEmail(CalibreTask):
|
|||||||
message['To'] = self.recipient
|
message['To'] = self.recipient
|
||||||
message['Subject'] = self.subject
|
message['Subject'] = self.subject
|
||||||
message['Date'] = formatdate(localtime=True)
|
message['Date'] = formatdate(localtime=True)
|
||||||
message['Message-Id'] = "{}@{}".format(uuid.uuid4(), self.get_msgid_domain())
|
message['Message-ID'] = make_msgid(domain=self.get_msgid_domain())
|
||||||
message.set_content(self.text.encode('UTF-8'), "text", "plain")
|
message.set_content(self.text.encode('UTF-8'), "text", "plain")
|
||||||
if self.attachment:
|
if self.attachment:
|
||||||
data = self._get_attachment(self.filepath, self.attachment)
|
data = self._get_attachment(self.filepath, self.attachment)
|
||||||
@ -169,10 +169,14 @@ class TaskEmail(CalibreTask):
|
|||||||
else:
|
else:
|
||||||
self.send_gmail_email(msg)
|
self.send_gmail_email(msg)
|
||||||
except MemoryError as e:
|
except MemoryError as e:
|
||||||
log.error_or_exception(e, stacklevel=3)
|
log.error_or_exception(e, stacklevel=2)
|
||||||
self._handleError('MemoryError sending e-mail: {}'.format(str(e)))
|
self._handleError('MemoryError sending e-mail: {}'.format(str(e)))
|
||||||
|
except (smtplib.SMTPRecipientsRefused) as e:
|
||||||
|
log.error_or_exception(e, stacklevel=2)
|
||||||
|
self._handleError('Smtplib Error sending e-mail: {}'.format(
|
||||||
|
(list(e.args[0].values())[0][1]).decode('utf-8)').replace("\n", '. ')))
|
||||||
except (smtplib.SMTPException, smtplib.SMTPAuthenticationError) as e:
|
except (smtplib.SMTPException, smtplib.SMTPAuthenticationError) as e:
|
||||||
log.error_or_exception(e, stacklevel=3)
|
log.error_or_exception(e, stacklevel=2)
|
||||||
if hasattr(e, "smtp_error"):
|
if hasattr(e, "smtp_error"):
|
||||||
text = e.smtp_error.decode('utf-8').replace("\n", '. ')
|
text = e.smtp_error.decode('utf-8').replace("\n", '. ')
|
||||||
elif hasattr(e, "message"):
|
elif hasattr(e, "message"):
|
||||||
@ -183,10 +187,10 @@ class TaskEmail(CalibreTask):
|
|||||||
text = ''
|
text = ''
|
||||||
self._handleError('Smtplib Error sending e-mail: {}'.format(text))
|
self._handleError('Smtplib Error sending e-mail: {}'.format(text))
|
||||||
except (socket.error) as e:
|
except (socket.error) as e:
|
||||||
log.error_or_exception(e, stacklevel=3)
|
log.error_or_exception(e, stacklevel=2)
|
||||||
self._handleError('Socket Error sending e-mail: {}'.format(e.strerror))
|
self._handleError('Socket Error sending e-mail: {}'.format(e.strerror))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
log.error_or_exception(ex, stacklevel=3)
|
log.error_or_exception(ex, stacklevel=2)
|
||||||
self._handleError('Error sending e-mail: {}'.format(ex))
|
self._handleError('Error sending e-mail: {}'.format(ex))
|
||||||
|
|
||||||
def send_standard_email(self, msg):
|
def send_standard_email(self, msg):
|
||||||
@ -269,7 +273,7 @@ class TaskEmail(CalibreTask):
|
|||||||
if config.config_binariesdir and config.config_embed_metadata:
|
if config.config_binariesdir and config.config_embed_metadata:
|
||||||
os.remove(datafile)
|
os.remove(datafile)
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
log.error_or_exception(e, stacklevel=3)
|
log.error_or_exception(e, stacklevel=2)
|
||||||
log.error('The requested file could not be read. Maybe wrong permissions?')
|
log.error('The requested file could not be read. Maybe wrong permissions?')
|
||||||
return None
|
return None
|
||||||
return data
|
return data
|
||||||
|
@ -62,7 +62,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div id="db_submit" name="submit" class="btn btn-default">{{_('Save')}}</div>
|
<button id="db_submit" type="submit" name="submit" class="btn btn-default">{{_('Save')}}</button>
|
||||||
<a href="{{ url_for('admin.admin') }}" id="config_back" class="btn btn-default">{{_('Cancel')}}</a>
|
<a href="{{ url_for('admin.admin') }}" id="config_back" class="btn btn-default">{{_('Cancel')}}</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -35,7 +35,7 @@ See https://github.com/adobe-type-tools/cmap-resources
|
|||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/libs/viewer.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/libs/viewer.css') }}">
|
||||||
<!-- This snippet is used in production (included from viewer.html) -->
|
<!-- This snippet is used in production (included from viewer.html) -->
|
||||||
<link rel="resource" type="application/l10n" href="{{ url_for('static', filename='locale/locale.json') }}">
|
<link rel="resource" type="application/l10n" href="{{ url_for('static', filename='locale/locale.json') }}">
|
||||||
<script src="{{ url_for('static', filename='js/libs/pdf.mjs') }}" type="module"></script>
|
<script src="{{ url_for('static', filename='js/libs/pdf.js') }}" type="module"></script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.addEventListener('webviewerloaded', function() {
|
window.addEventListener('webviewerloaded', function() {
|
||||||
@ -46,12 +46,12 @@ See https://github.com/adobe-type-tools/cmap-resources
|
|||||||
PDFViewerApplicationOptions.set('cMapUrl', "{{ url_for('static', filename='cmaps/') }}");
|
PDFViewerApplicationOptions.set('cMapUrl', "{{ url_for('static', filename='cmaps/') }}");
|
||||||
PDFViewerApplicationOptions.set('sidebarViewOnLoad', 0);
|
PDFViewerApplicationOptions.set('sidebarViewOnLoad', 0);
|
||||||
PDFViewerApplicationOptions.set('imageResourcesPath', "{{ url_for('static', filename='css/images/') }}");
|
PDFViewerApplicationOptions.set('imageResourcesPath', "{{ url_for('static', filename='css/images/') }}");
|
||||||
PDFViewerApplicationOptions.set('workerSrc', "{{ url_for('static', filename='js/libs/pdf.worker.mjs') }}");
|
PDFViewerApplicationOptions.set('workerSrc', "{{ url_for('static', filename='js/libs/pdf.worker.js') }}");
|
||||||
PDFViewerApplicationOptions.set('defaultUrl',"{{ url_for('web.serve_book', book_id=pdffile, book_format='pdf') }}")
|
PDFViewerApplicationOptions.set('defaultUrl',"{{ url_for('web.serve_book', book_id=pdffile, book_format='pdf') }}")
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="{{ url_for('static', filename='js/libs/viewer.mjs') }}" type="module">></script>
|
<script src="{{ url_for('static', filename='js/libs/viewer.js') }}" type="module">></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ from .services.worker import WorkerThread
|
|||||||
from .tasks_status import render_task_status
|
from .tasks_status import render_task_status
|
||||||
from .usermanagement import user_login_required
|
from .usermanagement import user_login_required
|
||||||
from .string_helper import strip_whitespaces
|
from .string_helper import strip_whitespaces
|
||||||
|
import traceback
|
||||||
|
|
||||||
feature_support = {
|
feature_support = {
|
||||||
'ldap': bool(services.ldap),
|
'ldap': bool(services.ldap),
|
||||||
@ -1242,7 +1242,12 @@ def serve_book(book_id, book_format, anyname):
|
|||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
@download_required
|
@download_required
|
||||||
def download_link(book_id, book_format, anyname):
|
def download_link(book_id, book_format, anyname):
|
||||||
client = "kobo" if "Kobo" in request.headers.get('User-Agent') else ""
|
if "kindle" in request.headers.get('User-Agent').lower():
|
||||||
|
client = "kindle"
|
||||||
|
elif "Kobo" in request.headers.get('User-Agent').lower():
|
||||||
|
client = "kobo"
|
||||||
|
else:
|
||||||
|
client = ""
|
||||||
return get_download_link(book_id, book_format, client)
|
return get_download_link(book_id, book_format, client)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
# GDrive Integration
|
# GDrive Integration
|
||||||
google-api-python-client>=1.7.11,<2.200.0
|
google-api-python-client>=1.7.11,<2.200.0
|
||||||
gevent>20.6.0,<24.3.0
|
gevent>20.6.0,<24.12.0
|
||||||
greenlet>=0.4.17,<3.2.0
|
greenlet>=0.4.17,<3.2.0
|
||||||
httplib2>=0.9.2,<0.23.0
|
httplib2>=0.9.2,<0.23.0
|
||||||
oauth2client>=4.0.0,<4.1.4
|
oauth2client>=4.0.0,<4.1.4
|
||||||
uritemplate>=3.0.0,<4.2.0
|
uritemplate>=3.0.0,<4.2.0
|
||||||
pyasn1-modules>=0.0.8,<0.5.0
|
pyasn1-modules>=0.0.8,<0.7.0
|
||||||
pyasn1>=0.1.9,<0.7.0
|
pyasn1>=0.1.9,<0.7.0
|
||||||
PyDrive2>=1.3.1,<1.22.0
|
PyDrive2>=1.3.1,<1.22.0
|
||||||
PyYAML>=3.12,<6.1
|
PyYAML>=3.12,<6.1
|
||||||
@ -17,23 +17,23 @@ google-api-python-client>=1.7.11,<2.200.0
|
|||||||
|
|
||||||
# goodreads
|
# goodreads
|
||||||
goodreads>=0.3.2,<0.4.0
|
goodreads>=0.3.2,<0.4.0
|
||||||
python-Levenshtein>=0.12.0,<0.27.0
|
python-Levenshtein>=0.12.0,<0.28.0
|
||||||
|
|
||||||
# ldap login
|
# ldap login
|
||||||
python-ldap>=3.0.0,<3.5.0
|
python-ldap>=3.0.0,<3.5.0
|
||||||
Flask-SimpleLDAP>=1.4.0,<2.1.0
|
Flask-SimpleLDAP>=1.4.0,<2.1.0
|
||||||
|
|
||||||
# oauth
|
# oauth
|
||||||
Flask-Dance>=2.0.0,<7.1.0
|
Flask-Dance>=2.0.0,<7.2.0
|
||||||
SQLAlchemy-Utils>=0.33.5,<0.42.0
|
SQLAlchemy-Utils>=0.33.5,<0.42.0
|
||||||
|
|
||||||
# metadata extraction
|
# metadata extraction
|
||||||
rarfile>=3.2,<5.0
|
rarfile>=3.2,<5.0
|
||||||
scholarly>=1.2.0,<1.8
|
scholarly>=1.2.0,<1.8
|
||||||
markdown2>=2.0.0,<2.5.0
|
markdown2>=2.0.0,<2.6.0
|
||||||
html2text>=2020.1.16,<2024.2.26
|
html2text>=2020.1.16,<2025.2.26
|
||||||
python-dateutil>=2.1,<2.10.0
|
python-dateutil>=2.1,<2.10.0
|
||||||
beautifulsoup4>=4.0.1,<4.13.0
|
beautifulsoup4>=4.0.1,<4.14.0
|
||||||
faust-cchardet>=2.1.18,<2.1.20
|
faust-cchardet>=2.1.18,<2.1.20
|
||||||
py7zr>=0.15.0,<0.21.0
|
py7zr>=0.15.0,<0.21.0
|
||||||
mutagen>=1.40.0,<1.50.0
|
mutagen>=1.40.0,<1.50.0
|
||||||
|
@ -12,12 +12,11 @@ classifiers = [
|
|||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
"License :: OSI Approved :: GNU Affero General Public License v3",
|
"License :: OSI Approved :: GNU Affero General Public License v3",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.6",
|
|
||||||
"Programming Language :: Python :: 3.7",
|
|
||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.8",
|
||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.9",
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
]
|
]
|
||||||
keywords = [
|
keywords = [
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
APScheduler>=3.6.3,<3.11.0
|
APScheduler>=3.6.3,<3.12.0
|
||||||
Babel>=1.3,<3.0
|
Babel>=1.3,<3.0
|
||||||
Flask-Babel>=0.11.1,<4.1.0
|
Flask-Babel>=3.0.0,<4.1.0
|
||||||
Flask-Principal>=0.3.2,<0.5.1
|
Flask-Principal>=0.3.2,<0.5.1
|
||||||
Flask>=1.0.2,<3.1.0
|
Flask>=1.0.2,<3.2.0
|
||||||
iso-639>=0.4.5,<0.5.0;python_version<'3.12'
|
iso-639>=0.4.5,<0.5.0;python_version<'3.12'
|
||||||
pycountry>=20.0.0,<25.0.0;python_version>='3.12'
|
pycountry>=20.0.0,<25.0.0;python_version>='3.12'
|
||||||
PyPDF>=3.15.6,<5.1.0
|
PyPDF>=3.15.6,<5.5.0
|
||||||
pytz>=2016.10
|
pytz>=2016.10
|
||||||
requests>=2.32.0,<2.33.0
|
requests>=2.32.0,<2.33.0
|
||||||
SQLAlchemy>=1.3.0,<2.1.0
|
SQLAlchemy>=1.3.0,<2.1.0
|
||||||
tornado>=6.3,<6.5
|
tornado>=6.4.2,<6.6
|
||||||
Wand>=0.4.4,<0.7.0
|
Wand>=0.4.4,<0.7.0
|
||||||
unidecode>=0.04.19,<1.4.0
|
unidecode>=0.04.19,<1.4.0
|
||||||
lxml>=4.9.1,<5.3.0
|
lxml>=4.9.1,<5.4.0
|
||||||
flask-wtf>=0.14.2,<1.3.0
|
flask-wtf>=0.14.2,<1.3.0
|
||||||
chardet>=3.0.0,<5.3.0
|
chardet>=3.0.0,<5.3.0
|
||||||
netifaces-plus>=0.12.0,<0.13.0
|
netifaces-plus>=0.12.0,<0.13.0
|
||||||
urllib3>=1.22,<3.0
|
urllib3>=1.22,<3.0
|
||||||
Flask-Limiter>=2.3.0,<3.9.0
|
Flask-Limiter>=2.3.0,<3.13.0
|
||||||
regex>=2022.3.2,<2024.6.25
|
regex>=2022.3.2,<2025.3.20
|
||||||
bleach>=6.0.0,<6.2.0
|
bleach>=6.0.0,<6.2.0
|
||||||
python-magic>=0.4.27,<0.5.0
|
python-magic>=0.4.27,<0.5.0
|
||||||
python-magic-bin>=0.4.0,<0.5.0;sys_platform=='win32'
|
python-magic-bin>=0.4.0,<0.5.0;sys_platform=='win32'
|
||||||
flask-httpAuth>=4.4.0,<5.0.0
|
flask-httpAuth>=4.4.0,<5.0.0
|
||||||
cryptography>=30.0.0,<44.0.0
|
cryptography>=43.0.4,<45.0.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user