Merge branch 'master' into Develop

This commit is contained in:
Ozzie Isaacs
2025-12-06 11:50:00 +01:00
75 changed files with 16225 additions and 14520 deletions
+3
View File
@@ -1838,6 +1838,9 @@ def _configuration_update_helper():
services.goodreads_support.connect(config.config_goodreads_api_key, services.goodreads_support.connect(config.config_goodreads_api_key,
config.config_use_goodreads) config.config_use_goodreads)
# Google Books API configuration
reboot_required |=_config_string(to_save, "config_googlebooks_api_key")
_config_int(to_save, "config_updatechannel") _config_int(to_save, "config_updatechannel")
# Reverse proxy login configuration # Reverse proxy login configuration
+4 -4
View File
@@ -65,7 +65,7 @@ def _extract_cover_from_archive(original_file_extension, tmp_file_name, rar_exec
cover_data = extension = None cover_data = extension = None
if original_file_extension.upper() == '.CBZ': if original_file_extension.upper() == '.CBZ':
cf = zipfile.ZipFile(tmp_file_name) cf = zipfile.ZipFile(tmp_file_name)
for name in cf.namelist(): for name in sorted(cf.namelist()):
ext = os.path.splitext(name) ext = os.path.splitext(name)
if len(ext) > 1: if len(ext) > 1:
extension = ext[1].lower() extension = ext[1].lower()
@@ -74,7 +74,7 @@ def _extract_cover_from_archive(original_file_extension, tmp_file_name, rar_exec
break break
elif original_file_extension.upper() == '.CBT': elif original_file_extension.upper() == '.CBT':
cf = tarfile.TarFile(tmp_file_name) cf = tarfile.TarFile(tmp_file_name)
for name in cf.getnames(): for name in sorted(cf.getnames()):
ext = os.path.splitext(name) ext = os.path.splitext(name)
if len(ext) > 1: if len(ext) > 1:
extension = ext[1].lower() extension = ext[1].lower()
@@ -85,7 +85,7 @@ def _extract_cover_from_archive(original_file_extension, tmp_file_name, rar_exec
try: try:
rarfile.UNRAR_TOOL = rar_executable rarfile.UNRAR_TOOL = rar_executable
cf = rarfile.RarFile(tmp_file_name) cf = rarfile.RarFile(tmp_file_name)
for name in cf.namelist(): for name in sorted(cf.namelist()):
ext = os.path.splitext(name) ext = os.path.splitext(name)
if len(ext) > 1: if len(ext) > 1:
extension = ext[1].lower() extension = ext[1].lower()
@@ -96,7 +96,7 @@ def _extract_cover_from_archive(original_file_extension, tmp_file_name, rar_exec
log.error('Rarfile failed with error: {}'.format(ex)) log.error('Rarfile failed with error: {}'.format(ex))
elif original_file_extension.upper() == '.CB7' and use_7zip: elif original_file_extension.upper() == '.CB7' and use_7zip:
cf = py7zr.SevenZipFile(tmp_file_name) cf = py7zr.SevenZipFile(tmp_file_name)
for name in cf.getnames(): for name in sorted(cf.getnames()):
ext = os.path.splitext(name) ext = os.path.splitext(name)
if len(ext) > 1: if len(ext) > 1:
extension = ext[1].lower() extension = ext[1].lower()
+2 -1
View File
@@ -117,6 +117,7 @@ class _Settings(_Base):
config_use_goodreads = Column(Boolean, default=False) config_use_goodreads = Column(Boolean, default=False)
config_goodreads_api_key = Column(String) config_goodreads_api_key = Column(String)
config_googlebooks_api_key = Column(String, default='')
config_register_email = Column(Boolean, default=False) config_register_email = Column(Boolean, default=False)
config_login_type = Column(Integer, default=0) config_login_type = Column(Integer, default=0)
@@ -325,7 +326,7 @@ class ConfigSQL(object):
def to_dict(self): def to_dict(self):
storage = {} storage = {}
for k, v in self.__dict__.items(): for k, v in self.__dict__.items():
if k[0] != '_' and not k.endswith("_e") and not k == "cli": if k[0] != '_' and not k.endswith("_e") and not k == "cli" and 'api' not in k.lower():
storage[k] = v storage[k] = v
return storage return storage
+1 -1
View File
@@ -406,7 +406,7 @@ def edit_book_param(param, vals, multi=False):
kobo_sync_status.remove_synced_book(book.id) kobo_sync_status.remove_synced_book(book.id)
continue continue
elif param == 'read_status': elif param == 'read_status':
error = helper.edit_book_read_status(book.id, vals['value'] == "True", True) error = helper.edit_book_read_status(book.id, vals['value'] == "True")
if error: if error:
if multi: if multi:
out.append({"success":False, "msg":error}) out.append({"success":False, "msg":error})
+2 -2
View File
@@ -303,7 +303,7 @@ def get_sorted_author(value):
return value2 return value2
def edit_book_read_status(book_id, read_status=None, archived=False): def edit_book_read_status(book_id, read_status=None):
if not config.config_read_column: if not config.config_read_column:
book = ub.session.query(ub.ReadBook).filter(and_(ub.ReadBook.user_id == int(current_user.id), book = ub.session.query(ub.ReadBook).filter(and_(ub.ReadBook.user_id == int(current_user.id),
ub.ReadBook.book_id == book_id)).first() ub.ReadBook.book_id == book_id)).first()
@@ -327,7 +327,7 @@ def edit_book_read_status(book_id, read_status=None, archived=False):
else: else:
try: try:
calibre_db.create_functions(config) calibre_db.create_functions(config)
book = calibre_db.get_filtered_book(book_id, archived) book = calibre_db.get_filtered_book(book_id, True)
book_read_status = getattr(book, 'custom_column_' + str(config.config_read_column)) book_read_status = getattr(book, 'custom_column_' + str(config.config_read_column))
if len(book_read_status): if len(book_read_status):
if read_status is None: if read_status is None:
+5
View File
@@ -1014,6 +1014,8 @@ def handle_getests():
@kobo.route("/v1/products/dailydeal", methods=["GET", "POST"]) @kobo.route("/v1/products/dailydeal", methods=["GET", "POST"])
@kobo.route("/v1/products/deals", methods=["GET", "POST"]) @kobo.route("/v1/products/deals", methods=["GET", "POST"])
@kobo.route("/v1/products", methods=["GET", "POST"]) @kobo.route("/v1/products", methods=["GET", "POST"])
@kobo.route("/v1/products/<path:dummy>", methods=["GET", "POST"])
@kobo.route("/v1/products/<path:dummy>/", methods=["GET", "POST"])
@kobo.route("/v1/affiliate", methods=["GET", "POST"]) @kobo.route("/v1/affiliate", methods=["GET", "POST"])
@kobo.route("/v1/deals", methods=["GET", "POST"]) @kobo.route("/v1/deals", methods=["GET", "POST"])
def HandleProductsRequest(dummy=None): def HandleProductsRequest(dummy=None):
@@ -1201,6 +1203,9 @@ def NATIVE_KOBO_RESOURCES():
"image_host": "//cdn.kobo.com/book-images/", "image_host": "//cdn.kobo.com/book-images/",
"image_url_quality_template": "https://cdn.kobo.com/book-images/{ImageId}/{Width}/{Height}/{Quality}/{IsGreyscale}/image.jpg", "image_url_quality_template": "https://cdn.kobo.com/book-images/{ImageId}/{Width}/{Height}/{Quality}/{IsGreyscale}/image.jpg",
"image_url_template": "https://cdn.kobo.com/book-images/{ImageId}/{Width}/{Height}/false/image.jpg", "image_url_template": "https://cdn.kobo.com/book-images/{ImageId}/{Width}/{Height}/false/image.jpg",
"instapaper_enabled": "True",
"instapaper_env_url": "https://www.instapaper.com/api/kobo",
"instapaper_link_account_start": "https://authorize.kobo.com/{region}/{language}/linkinstapaper",
"kobo_audiobooks_credit_redemption": "False", "kobo_audiobooks_credit_redemption": "False",
"kobo_audiobooks_enabled": "True", "kobo_audiobooks_enabled": "True",
"kobo_audiobooks_orange_deal_enabled": "False", "kobo_audiobooks_orange_deal_enabled": "False",
+3 -2
View File
@@ -23,7 +23,7 @@ from datetime import datetime
import requests import requests
from cps import logger from cps import logger, config
from cps.isoLanguages import get_lang3, get_language_name from cps.isoLanguages import get_lang3, get_language_name
from cps.services.Metadata import MetaRecord, MetaSourceInfo, Metadata from cps.services.Metadata import MetaRecord, MetaSourceInfo, Metadata
@@ -38,6 +38,7 @@ class Google(Metadata):
BOOK_URL = "https://books.google.com/books?id=" BOOK_URL = "https://books.google.com/books?id="
SEARCH_URL = "https://www.googleapis.com/books/v1/volumes?q=" SEARCH_URL = "https://www.googleapis.com/books/v1/volumes?q="
ISBN_TYPE = "ISBN_13" ISBN_TYPE = "ISBN_13"
API_KEY = "&key=" + config.config_googlebooks_api_key
def search( def search(
self, query: str, generic_cover: str = "", locale: str = "en" self, query: str, generic_cover: str = "", locale: str = "en"
@@ -50,7 +51,7 @@ class Google(Metadata):
tokens = [quote(t.encode("utf-8")) for t in title_tokens] tokens = [quote(t.encode("utf-8")) for t in title_tokens]
query = "+".join(tokens) query = "+".join(tokens)
try: try:
results = requests.get(Google.SEARCH_URL + query) results = requests.get(Google.SEARCH_URL + query + Google.API_KEY)
results.raise_for_status() results.raise_for_status()
except Exception as e: except Exception as e:
log.warning(e) log.warning(e)
+45 -23
View File
@@ -88,8 +88,8 @@ def feed_letter_books(book_id):
letter, letter,
[db.Books.sort], [db.Books.sort],
True, config.config_read_column) True, config.config_read_column)
cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', entries=entries, pagination=pagination) return render_xml_template('feed.xml', entries=entries, pagination=pagination, cc=cc)
@opds.route("/opds/new") @opds.route("/opds/new")
@@ -101,7 +101,8 @@ def feed_new():
entries, __, pagination = calibre_db.fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1), 0, entries, __, pagination = calibre_db.fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1), 0,
db.Books, True, [db.Books.timestamp.desc()], db.Books, True, [db.Books.timestamp.desc()],
True, config.config_read_column) True, config.config_read_column)
return render_xml_template('feed.xml', entries=entries, pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', entries=entries, pagination=pagination, cc=cc)
@opds.route("/opds/discover") @opds.route("/opds/discover")
@@ -112,7 +113,8 @@ def feed_discover():
query = calibre_db.generate_linked_query(config.config_read_column, db.Books) query = calibre_db.generate_linked_query(config.config_read_column, db.Books)
entries = query.filter(calibre_db.common_filters()).order_by(func.random()).limit(config.config_books_per_page) entries = query.filter(calibre_db.common_filters()).order_by(func.random()).limit(config.config_books_per_page)
pagination = Pagination(1, config.config_books_per_page, int(config.config_books_per_page)) pagination = Pagination(1, config.config_books_per_page, int(config.config_books_per_page))
return render_xml_template('feed.xml', entries=entries, pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', entries=entries, pagination=pagination, cc=cc)
@opds.route("/opds/rated") @opds.route("/opds/rated")
@@ -125,7 +127,8 @@ def feed_best_rated():
db.Books, db.Books.ratings.any(db.Ratings.rating > 9), db.Books, db.Books.ratings.any(db.Ratings.rating > 9),
[db.Books.timestamp.desc()], [db.Books.timestamp.desc()],
True, config.config_read_column) True, config.config_read_column)
return render_xml_template('feed.xml', entries=entries, pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', entries=entries, pagination=pagination, cc=cc)
@opds.route("/opds/hot") @opds.route("/opds/hot")
@@ -149,7 +152,8 @@ def feed_hot():
num_books = entries.__len__() num_books = entries.__len__()
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1),
config.config_books_per_page, num_books) config.config_books_per_page, num_books)
return render_xml_template('feed.xml', entries=entries, pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', entries=entries, pagination=pagination, cc=cc)
@opds.route("/opds/author") @opds.route("/opds/author")
@@ -174,7 +178,8 @@ def feed_letter_author(book_id):
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
entries.count()) entries.count())
entries = entries.limit(config.config_books_per_page).offset(off).all() entries = entries.limit(config.config_books_per_page).offset(off).all()
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_author', pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_author', pagination=pagination, cc=cc)
@opds.route("/opds/author/<int:book_id>") @opds.route("/opds/author/<int:book_id>")
@@ -197,7 +202,8 @@ def feed_publisherindex():
.limit(config.config_books_per_page).offset(off) .limit(config.config_books_per_page).offset(off)
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
len(calibre_db.session.query(db.Publishers).all())) len(calibre_db.session.query(db.Publishers).all()))
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_publisher', pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_publisher', pagination=pagination, cc=cc)
@opds.route("/opds/publisher/<int:book_id>") @opds.route("/opds/publisher/<int:book_id>")
@@ -230,7 +236,8 @@ def feed_letter_category(book_id):
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
entries.count()) entries.count())
entries = entries.offset(off).limit(config.config_books_per_page).all() entries = entries.offset(off).limit(config.config_books_per_page).all()
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_category', pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_category', pagination=pagination, cc=cc)
@opds.route("/opds/category/<int:book_id>") @opds.route("/opds/category/<int:book_id>")
@@ -263,7 +270,8 @@ def feed_letter_series(book_id):
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
entries.count()) entries.count())
entries = entries.offset(off).limit(config.config_books_per_page).all() entries = entries.offset(off).limit(config.config_books_per_page).all()
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_series', pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_series', pagination=pagination, cc=cc)
@opds.route("/opds/series/<int:book_id>") @opds.route("/opds/series/<int:book_id>")
@@ -275,7 +283,8 @@ def feed_series(book_id):
db.Books.series.any(db.Series.id == book_id), db.Books.series.any(db.Series.id == book_id),
[db.Books.series_index], [db.Books.series_index],
True, config.config_read_column) True, config.config_read_column)
return render_xml_template('feed.xml', entries=entries, pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', entries=entries, pagination=pagination, cc=cc)
@opds.route("/opds/ratings") @opds.route("/opds/ratings")
@@ -297,7 +306,8 @@ def feed_ratingindex():
element = list() element = list()
for entry in entries: for entry in entries:
element.append(FeedObject(entry[0].id, _("{} Stars").format(entry.name))) element.append(FeedObject(entry[0].id, _("{} Stars").format(entry.name)))
return render_xml_template('feed.xml', listelements=element, folder='opds.feed_ratings', pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', listelements=element, folder='opds.feed_ratings', pagination=pagination, cc=cc)
@opds.route("/opds/ratings/<book_id>") @opds.route("/opds/ratings/<book_id>")
@@ -321,7 +331,8 @@ def feed_formatindex():
element = list() element = list()
for entry in entries: for entry in entries:
element.append(FeedObject(entry.format, entry.format)) element.append(FeedObject(entry.format, entry.format))
return render_xml_template('feed.xml', listelements=element, folder='opds.feed_format', pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', listelements=element, folder='opds.feed_format', pagination=pagination, cc=cc)
@opds.route("/opds/formats/<book_id>") @opds.route("/opds/formats/<book_id>")
@@ -333,7 +344,8 @@ def feed_format(book_id):
db.Books.data.any(db.Data.format == book_id.upper()), db.Books.data.any(db.Data.format == book_id.upper()),
[db.Books.timestamp.desc()], [db.Books.timestamp.desc()],
True, config.config_read_column) True, config.config_read_column)
return render_xml_template('feed.xml', entries=entries, pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', entries=entries, pagination=pagination, cc=cc)
@opds.route("/opds/language") @opds.route("/opds/language")
@@ -351,7 +363,8 @@ def feed_languagesindex():
languages[0].name = isoLanguages.get_language_name(get_locale(), languages[0].lang_code) languages[0].name = isoLanguages.get_language_name(get_locale(), languages[0].lang_code)
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
len(languages)) len(languages))
return render_xml_template('feed.xml', listelements=languages, folder='opds.feed_languages', pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', listelements=languages, folder='opds.feed_languages', pagination=pagination, cc=cc)
@opds.route("/opds/language/<int:book_id>") @opds.route("/opds/language/<int:book_id>")
@@ -363,7 +376,8 @@ def feed_languages(book_id):
db.Books.languages.any(db.Languages.id == book_id), db.Books.languages.any(db.Languages.id == book_id),
[db.Books.timestamp.desc()], [db.Books.timestamp.desc()],
True, config.config_read_column) True, config.config_read_column)
return render_xml_template('feed.xml', entries=entries, pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', entries=entries, pagination=pagination, cc=cc)
@opds.route("/opds/shelfindex") @opds.route("/opds/shelfindex")
@@ -377,7 +391,8 @@ def feed_shelfindex():
number = len(shelf) number = len(shelf)
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
number) number)
return render_xml_template('feed.xml', listelements=shelf, folder='opds.feed_shelf', pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', listelements=shelf, folder='opds.feed_shelf', pagination=pagination, cc=cc)
@opds.route("/opds/shelf/<int:book_id>") @opds.route("/opds/shelf/<int:book_id>")
@@ -417,7 +432,8 @@ def feed_shelf(book_id):
except (OperationalError, InvalidRequestError) as e: except (OperationalError, InvalidRequestError) as e:
ub.session.rollback() ub.session.rollback()
log.error_or_exception("Settings Database error: {}".format(e)) log.error_or_exception("Settings Database error: {}".format(e))
return render_xml_template('feed.xml', entries=result, pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', entries=result, pagination=pagination, cc=cc)
@opds.route("/opds/download/<book_id>/<book_format>/") @opds.route("/opds/download/<book_id>/<book_format>/")
@@ -470,7 +486,8 @@ def feed_read_books():
return abort(403) return abort(403)
off = request.args.get("offset") or 0 off = request.args.get("offset") or 0
result, pagination = render_read_books(int(off) / (int(config.config_books_per_page)) + 1, True, True) result, pagination = render_read_books(int(off) / (int(config.config_books_per_page)) + 1, True, True)
return render_xml_template('feed.xml', entries=result, pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', entries=result, pagination=pagination, cc=cc)
@opds.route("/opds/unreadbooks") @opds.route("/opds/unreadbooks")
@@ -480,7 +497,8 @@ def feed_unread_books():
return abort(403) return abort(403)
off = request.args.get("offset") or 0 off = request.args.get("offset") or 0
result, pagination = render_read_books(int(off) / (int(config.config_books_per_page)) + 1, False, True) result, pagination = render_read_books(int(off) / (int(config.config_books_per_page)) + 1, False, True)
return render_xml_template('feed.xml', entries=result, pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', entries=result, pagination=pagination, cc=cc)
class FeedObject: class FeedObject:
@@ -502,7 +520,8 @@ def feed_search(term):
entries, __, ___ = calibre_db.get_search_results(term, config=config) entries, __, ___ = calibre_db.get_search_results(term, config=config)
entries_count = len(entries) if len(entries) > 0 else 1 entries_count = len(entries) if len(entries) > 0 else 1
pagination = Pagination(1, entries_count, entries_count) pagination = Pagination(1, entries_count, entries_count)
return render_xml_template('feed.xml', searchterm=term, entries=entries, pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', searchterm=term, entries=entries, pagination=pagination, cc=cc)
else: else:
return render_xml_template('feed.xml', searchterm="") return render_xml_template('feed.xml', searchterm="")
@@ -524,7 +543,8 @@ def render_xml_dataset(data_table, book_id):
getattr(db.Books, data_table.__tablename__).any(data_table.id == book_id), getattr(db.Books, data_table.__tablename__).any(data_table.id == book_id),
[db.Books.timestamp.desc()], [db.Books.timestamp.desc()],
True, config.config_read_column) True, config.config_read_column)
return render_xml_template('feed.xml', entries=entries, pagination=pagination) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', entries=entries, pagination=pagination, cc=cc)
def render_element_index(database_column, linked_table, folder): def render_element_index(database_column, linked_table, folder):
@@ -545,7 +565,9 @@ def render_element_index(database_column, linked_table, folder):
elements.append({'id': entry.id, 'name': entry.id}) elements.append({'id': entry.id, 'name': entry.id})
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
len(entries) + 1) len(entries) + 1)
cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
return render_xml_template('feed.xml', return render_xml_template('feed.xml',
letterelements=elements, letterelements=elements,
folder=folder, folder=folder,
pagination=pagination) pagination=pagination,
cc=cc)
+1 -1
View File
@@ -29,7 +29,7 @@
from urllib.parse import urlparse, urljoin from urllib.parse import urlparse, urljoin
from flask import request, url_for, redirect, current_app from flask import request, url_for, current_app
def is_safe_url(target): def is_safe_url(target):
+37 -2
View File
@@ -22,7 +22,7 @@ import requests
from goodreads.client import GoodreadsClient from goodreads.client import GoodreadsClient
from goodreads.request import GoodreadsRequest from goodreads.request import GoodreadsRequest
import xmltodict from lxml import etree
try: try:
import Levenshtein import Levenshtein
@@ -33,6 +33,39 @@ from .. import logger
from ..clean_html import clean_string from ..clean_html import clean_string
def etree_to_dict(t):
"""
Convert lxml ElementTree to a nested dict (similar to xmltodict).
"""
d = {t.tag: {} if t.attrib else None}
children = list(t)
if children:
dd = {}
for dc in map(etree_to_dict, children):
for k, v in dc.items():
if k in dd:
if not isinstance(dd[k], list):
dd[k] = [dd[k]]
dd[k].append(v)
else:
dd[k] = v
d = {t.tag: dd}
if t.attrib:
d[t.tag].update(('@' + k, v) for k, v in t.attrib.items())
text = (t.text or '').strip()
if text:
if children or t.attrib:
d[t.tag]['#text'] = text
else:
d[t.tag] = text
return d
class my_GoodreadsClient(GoodreadsClient): class my_GoodreadsClient(GoodreadsClient):
def request(self, *args, **kwargs): def request(self, *args, **kwargs):
@@ -59,7 +92,9 @@ class my_GoodreadsRequest(GoodreadsRequest):
if resp.status_code != 200: if resp.status_code != 200:
raise GoodreadsRequestException(resp.reason, self.path) raise GoodreadsRequestException(resp.reason, self.path)
if self.req_format == 'xml': if self.req_format == 'xml':
data_dict = xmltodict.parse(resp.content) root = etree.fromstring(resp.content)
data_dict = etree_to_dict(root)
return data_dict['GoodreadsResponse'] return data_dict['GoodreadsResponse']
else: else:
raise Exception("Invalid format") raise Exception("Invalid format")
+1 -1
View File
@@ -235,7 +235,7 @@ class CalibreTask:
@property @property
def dead(self): def dead(self):
"""Determines whether this task can be garbage collected """Determines whether or not this task can be garbage collected
We have a separate dictating this because there may be certain tasks that want to override this We have a separate dictating this because there may be certain tasks that want to override this
""" """
@@ -1,21 +0,0 @@
// register new event emitter locationchange that fires on urlchange
// source: https://stackoverflow.com/a/52809105/21941129
(() => {
let oldPushState = history.pushState;
history.pushState = function pushState() {
let ret = oldPushState.apply(this, arguments);
window.dispatchEvent(new Event('locationchange'));
return ret;
};
let oldReplaceState = history.replaceState;
history.replaceState = function replaceState() {
let ret = oldReplaceState.apply(this, arguments);
window.dispatchEvent(new Event('locationchange'));
return ret;
};
window.addEventListener('popstate', () => {
window.dispatchEvent(new Event('locationchange'));
});
})();
+4
View File
@@ -163,6 +163,10 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
<div class="form-group">
<label for="config_googlebooks_api_key">{{_('Google Books API Key')}}</label>
<input type="text" class="form-control" id="config_googlebooks_api_key" name="config_googlebooks_api_key" value="{% if config.config_googlebooks_api_key != '' %}{{ config.config_googlebooks_api_key }}{% endif %}" autocomplete="off">
</div>
<div class="form-group"> <div class="form-group">
<input type="checkbox" id="config_allow_reverse_proxy_header_login" name="config_allow_reverse_proxy_header_login" data-control="reverse-proxy-login-settings" {% if config.config_allow_reverse_proxy_header_login %}checked{% endif %}> <input type="checkbox" id="config_allow_reverse_proxy_header_login" name="config_allow_reverse_proxy_header_login" data-control="reverse-proxy-login-settings" {% if config.config_allow_reverse_proxy_header_login %}checked{% endif %}>
<label for="config_allow_reverse_proxy_header_login">{{_('Allow Reverse Proxy Authentication')}}</label> <label for="config_allow_reverse_proxy_header_login">{{_('Allow Reverse Proxy Authentication')}}</label>
+51 -1
View File
@@ -63,7 +63,57 @@
term="{{tag.name}}" term="{{tag.name}}"
label="{{tag.name}}"/> label="{{tag.name}}"/>
{% endfor %} {% endfor %}
{% if entry.Books.comments[0] %}<summary>{{entry.Books.comments[0].text|striptags}}</summary>{% endif %} <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">
{% if entry.Books.ratings.__len__() > 0 %}
RATING: {% for number in range((entry.Books.ratings[0].rating/2)|int(2)) %}★{% endfor %}<br/>
{% endif %}
{% if entry.Books.tags|length > 0 %}
TAGS: {% for tag in entry.Books.tags %}{{tag.name}}{{ ", " if not loop.last else "" }}{% endfor %}<br/>
{% endif %}
{% if entry.Books.series.__len__() > 0 %}
SERIES: {{entry.Books.series[0].name}} [{{entry.Books.series_index|formatfloat(2)}}]<br/>
{% endif %}
{% if cc|length > 0 %}
{% for c in cc %}
{% if entry.Books['custom_column_' ~ c.id]|length > 0 %}
{{ c.name }}:
{% for column in entry.Books['custom_column_' ~ c.id] %}
{% if c.datatype == 'rating' %}
{{ (column.value / 2)|formatfloat }}
{% else %}
{% if c.datatype == 'bool' %}
{% if column.value == true %}
✓
{% else %}
✕
{% endif %}
{% else %}
{% if c.datatype == 'float' %}
{{ column.value|formatfloat(2) }}
{% elif c.datatype == 'datetime' %}
{{ column.value|formatdate }}
{% elif c.datatype == 'comments' %}
{{ column.value|safe }}
{% elif c.datatype == 'series' %}
{{ '%s [%s]' % (column.value, column.extra|formatfloat(2)) }}
{% elif c.datatype == 'text' %}
{{ column.value.strip() }}{% if not loop.last %}, {% endif %}
{% else %}
{{ column.value }}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
<br/>
{% endif %}
{% endfor %}
{% endif %}
{% if entry.Books.comments[0] %}
<p>{{entry.Books.comments[0].text}}</p>
{% endif %}
</div></content>
{% if entry.Books.has_cover %} {% if entry.Books.has_cover %}
<link type="image/jpeg" href="{{url_for('opds.feed_get_cover', book_id=entry.Books.id)}}" rel="http://opds-spec.org/image"/> <link type="image/jpeg" href="{{url_for('opds.feed_get_cover', book_id=entry.Books.id)}}" rel="http://opds-spec.org/image"/>
<link type="image/jpeg" href="{{url_for('opds.feed_get_cover', book_id=entry.Books.id)}}" rel="http://opds-spec.org/image/thumbnail"/> <link type="image/jpeg" href="{{url_for('opds.feed_get_cover', book_id=entry.Books.id)}}" rel="http://opds-spec.org/image/thumbnail"/>
+1 -2
View File
@@ -41,8 +41,7 @@
<div class="plexBack"><a href="{{url_for('web.index')}}"></a></div> <div class="plexBack"><a href="{{url_for('web.index')}}"></a></div>
{% endif %} {% endif %}
{% if current_user.is_authenticated or g.allow_anonymous %} {% if current_user.is_authenticated or g.allow_anonymous %}
<!--# margin 0, padding 15, background color--> <form class="navbar-form navbar-left" role="search" action="{{url_for('search.simple_search')}}" method="GET">
<form class="navbar-form navbar-left" role="search" action="{{url_for('search.simple_search')}}" method="GET">
<div class="form-group input-group input-group-sm"> <div class="form-group input-group input-group-sm">
<label for="query" class="sr-only">{{_('Search')}}</label> <label for="query" class="sr-only">{{_('Search')}}</label>
<input type="text" class="form-control" id="query" name="query" placeholder="{{_('Search Library')}}" value="{{searchterm}}"> <input type="text" class="form-control" id="query" name="query" placeholder="{{_('Search Library')}}" value="{{searchterm}}">
+1 -2
View File
@@ -48,8 +48,7 @@
<div class="modal-body text-center"> <div class="modal-body text-center">
<p> <p>
<span class="hidden" id="book_format">{{_('This book format will be permanently erased from database')}}</span> <span class="hidden" id="book_format">{{_('This book format will be permanently erased from database')}}</span>
<span class="hidden" id="book_complete">{{_('This book will be permanently erased from database')}}</span> <span class="hidden" id="book_complete">{{_('This book will be permanently erased from database and hard disk')}}</span>
<span>{{_('and hard disk')}}</span>
</p> </p>
{% if config.config_kobo_sync %} {% if config.config_kobo_sync %}
<p> <p>
-1
View File
@@ -216,6 +216,5 @@
<script src="{{ url_for('static', filename='js/libs/screenfull.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/screenfull.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/reader.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/reader.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/reading/epub.js') }}"></script> <script src="{{ url_for('static', filename='js/reading/epub.js') }}"></script>
<!--script src="{{ url_for('static', filename='js/reading/locationchange-polyfill.js') }}"></script-->
</body> </body>
</html> </html>
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
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
+9 -4
View File
@@ -812,14 +812,19 @@ def index(page):
return render_books_list("newest", sort_param, 1, page) return render_books_list("newest", sort_param, 1, page)
@web.route('/<data>/<sort_param>', defaults={'page': 1, 'book_id': 1})
@web.route('/<data>/<sort_param>/', defaults={'page': 1, 'book_id': 1})
@web.route('/<data>/<sort_param>/<book_id>', defaults={'page': 1})
@web.route('/<data>/<sort_param>/<book_id>/<int:page>')
@login_required_if_no_ano @login_required_if_no_ano
def books_list(data, sort_param, book_id, page): def books_list(data, sort_param, book_id, page):
return render_books_list(data, sort_param, book_id, page) return render_books_list(data, sort_param, book_id, page)
# Limit number of routes to avoid redirects
data =["rated", "discover", "unread", "read", "hot", "download", "author", "publisher", "series", "ratings", "formats",
"category", "language", "archived", "search", "advsearch", "newest"]
for d in data:
web.add_url_rule('/{}/<sort_param>'.format(d), view_func=books_list, defaults={'page': 1, 'book_id': 1, "data": d})
web.add_url_rule('/{}/<sort_param>/'.format(d), view_func=books_list, defaults={'page': 1, 'book_id': 1, "data": d})
web.add_url_rule('/{}/<sort_param>/<book_id>'.format(d), view_func=books_list, defaults={'page': 1, "data": d})
web.add_url_rule('/{}/<sort_param>/<book_id>/<int:page>'.format(d), defaults={"data": d}, view_func=books_list)
@web.route("/table") @web.route("/table")
@user_login_required @user_login_required
+474 -368
View File
File diff suppressed because it is too large Load Diff