mirror of
https://github.com/janeczku/calibre-web
synced 2024-11-28 04:19:59 +00:00
Merge branch 'Develop' into feature/OPDS-access
# Conflicts: # test/Calibre-Web TestSummary_Linux.html
This commit is contained in:
commit
e99be72ff7
@ -186,7 +186,6 @@ def create_app():
|
||||
services.ldap.init_app(app, config)
|
||||
if services.goodreads_support:
|
||||
services.goodreads_support.connect(config.config_goodreads_api_key,
|
||||
config.config_goodreads_api_secret_e,
|
||||
config.config_use_goodreads)
|
||||
config.store_calibre_uuid(calibre_db, db.Library_Id)
|
||||
# Configure rate limiter
|
||||
|
@ -1809,11 +1809,8 @@ def _configuration_update_helper():
|
||||
# Goodreads configuration
|
||||
_config_checkbox(to_save, "config_use_goodreads")
|
||||
_config_string(to_save, "config_goodreads_api_key")
|
||||
if to_save.get("config_goodreads_api_secret_e", ""):
|
||||
_config_string(to_save, "config_goodreads_api_secret_e")
|
||||
if services.goodreads_support:
|
||||
services.goodreads_support.connect(config.config_goodreads_api_key,
|
||||
config.config_goodreads_api_secret_e,
|
||||
config.config_use_goodreads)
|
||||
|
||||
_config_int(to_save, "config_updatechannel")
|
||||
|
53
cps/clean_html.py
Normal file
53
cps/clean_html.py
Normal file
@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
|
||||
# Copyright (C) 2018-2019 OzzieIsaacs
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from . import logger
|
||||
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:
|
||||
try:
|
||||
BLEACH = False
|
||||
from nh3 import clean as clean_html
|
||||
except ImportError:
|
||||
try:
|
||||
BLEACH = False
|
||||
from lxml.html.clean import clean_html
|
||||
except ImportError:
|
||||
clean_html = None
|
||||
|
||||
|
||||
log = logger.create()
|
||||
|
||||
|
||||
def clean_string(unsafe_text, book_id=0):
|
||||
try:
|
||||
if BLEACH:
|
||||
safe_text = clean_html(unsafe_text, tags=set(), attributes=set())
|
||||
else:
|
||||
safe_text = clean_html(unsafe_text)
|
||||
except ParserError as e:
|
||||
log.error("Comments of book {} are corrupted: {}".format(book_id, e))
|
||||
safe_text = ""
|
||||
except TypeError as e:
|
||||
log.error("Comments can't be parsed, maybe 'lxml' is too new, try installing 'bleach': {}".format(e))
|
||||
safe_text = ""
|
||||
return safe_text
|
@ -102,7 +102,7 @@ def _extract_cover_from_archive(original_file_extension, tmp_file_name, rar_exec
|
||||
extension = ext[1].lower()
|
||||
if extension in cover.COVER_EXTENSIONS:
|
||||
try:
|
||||
cover_data = cf.read(name)[name].read()
|
||||
cover_data = cf.read([name])[name].read()
|
||||
except (py7zr.Bad7zFile, OSError) as ex:
|
||||
log.error('7Zip file failed with error: {}'.format(ex))
|
||||
break
|
||||
|
@ -114,8 +114,6 @@ class _Settings(_Base):
|
||||
|
||||
config_use_goodreads = Column(Boolean, default=False)
|
||||
config_goodreads_api_key = Column(String)
|
||||
config_goodreads_api_secret_e = Column(String)
|
||||
config_goodreads_api_secret = Column(String)
|
||||
config_register_email = Column(Boolean, default=False)
|
||||
config_login_type = Column(Integer, default=0)
|
||||
|
||||
@ -422,19 +420,13 @@ def _encrypt_fields(session, secret_key):
|
||||
except OperationalError:
|
||||
with session.bind.connect() as conn:
|
||||
conn.execute(text("ALTER TABLE settings ADD column 'mail_password_e' String"))
|
||||
conn.execute(text("ALTER TABLE settings ADD column 'config_goodreads_api_secret_e' String"))
|
||||
conn.execute(text("ALTER TABLE settings ADD column 'config_ldap_serv_password_e' String"))
|
||||
session.commit()
|
||||
crypter = Fernet(secret_key)
|
||||
settings = session.query(_Settings.mail_password, _Settings.config_goodreads_api_secret,
|
||||
_Settings.config_ldap_serv_password).first()
|
||||
settings = session.query(_Settings.mail_password, _Settings.config_ldap_serv_password).first()
|
||||
if settings.mail_password:
|
||||
session.query(_Settings).update(
|
||||
{_Settings.mail_password_e: crypter.encrypt(settings.mail_password.encode())})
|
||||
if settings.config_goodreads_api_secret:
|
||||
session.query(_Settings).update(
|
||||
{_Settings.config_goodreads_api_secret_e:
|
||||
crypter.encrypt(settings.config_goodreads_api_secret.encode())})
|
||||
if settings.config_ldap_serv_password:
|
||||
session.query(_Settings).update(
|
||||
{_Settings.config_ldap_serv_password_e:
|
||||
|
@ -27,22 +27,22 @@ 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
|
||||
# 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:
|
||||
try:
|
||||
BLEACH = False
|
||||
from nh3 import clean as clean_html
|
||||
except ImportError:
|
||||
try:
|
||||
BLEACH = False
|
||||
from lxml.html.clean import clean_html
|
||||
except ImportError:
|
||||
clean_html = None
|
||||
#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:
|
||||
# try:
|
||||
# BLEACH = False
|
||||
# from nh3 import clean as clean_html
|
||||
# except ImportError:
|
||||
# try:
|
||||
# BLEACH = False
|
||||
# from lxml.html.clean import clean_html
|
||||
# except ImportError:
|
||||
# clean_html = None
|
||||
|
||||
from flask import Blueprint, request, flash, redirect, url_for, abort, Response
|
||||
from flask_babel import gettext as _
|
||||
@ -54,6 +54,7 @@ from sqlalchemy.orm.exc import StaleDataError
|
||||
from sqlalchemy.sql.expression import func
|
||||
|
||||
from . import constants, logger, isoLanguages, gdriveutils, uploader, helper, kobo_sync_status
|
||||
from .clean_html import clean_string
|
||||
from . import config, ub, db, calibre_db
|
||||
from .services.worker import WorkerThread
|
||||
from .tasks.upload import TaskUpload
|
||||
@ -1004,17 +1005,18 @@ 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=set(), attributes=set())
|
||||
else:
|
||||
comments = clean_html(comments)
|
||||
except ParserError as e:
|
||||
log.error("Comments of book {} are corrupted: {}".format(book.id, e))
|
||||
comments = ""
|
||||
except TypeError as e:
|
||||
log.error("Comments can't be parsed, maybe 'lxml' is too new, try installing 'bleach': {}".format(e))
|
||||
comments = ""
|
||||
comments = clean_string(comments, book.id)
|
||||
#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 = ""
|
||||
#except TypeError as e:
|
||||
# log.error("Comments can't be parsed, maybe 'lxml' is too new, try installing 'bleach': {}".format(e))
|
||||
# comments = ""
|
||||
if len(book.comments):
|
||||
if book.comments[0].text != comments:
|
||||
book.comments[0].text = comments
|
||||
@ -1072,18 +1074,19 @@ def edit_cc_data_value(book_id, book, c, to_save, cc_db_value, cc_string):
|
||||
elif c.datatype == 'comments':
|
||||
to_save[cc_string] = Markup(to_save[cc_string]).unescape()
|
||||
if to_save[cc_string]:
|
||||
try:
|
||||
if BLEACH:
|
||||
to_save[cc_string] = clean_html(to_save[cc_string], tags=set(), attributes=set())
|
||||
else:
|
||||
to_save[cc_string] = clean_html(to_save[cc_string])
|
||||
except ParserError as e:
|
||||
log.error("Customs Comments of book {} are corrupted: {}".format(book_id, e))
|
||||
to_save[cc_string] = ""
|
||||
except TypeError as e:
|
||||
to_save[cc_string] = ""
|
||||
log.error("Customs Comments can't be parsed, maybe 'lxml' is too new, "
|
||||
"try installing 'bleach': {}".format(e))
|
||||
to_save[cc_string] = clean_string(to_save[cc_string], book_id)
|
||||
#try:
|
||||
# if BLEACH:
|
||||
# to_save[cc_string] = clean_html(to_save[cc_string], tags=set(), attributes=set())
|
||||
# else:
|
||||
# to_save[cc_string] = clean_html(to_save[cc_string])
|
||||
#except ParserError as e:
|
||||
# log.error("Customs Comments of book {} are corrupted: {}".format(book_id, e))
|
||||
# to_save[cc_string] = ""
|
||||
#except TypeError as e:
|
||||
# to_save[cc_string] = ""
|
||||
# log.error("Customs Comments can't be parsed, maybe 'lxml' is too new, "
|
||||
# "try installing 'bleach': {}".format(e))
|
||||
elif c.datatype == 'datetime':
|
||||
try:
|
||||
to_save[cc_string] = datetime.strptime(to_save[cc_string], "%Y-%m-%d")
|
||||
|
@ -18,16 +18,49 @@
|
||||
|
||||
import time
|
||||
from functools import reduce
|
||||
import requests
|
||||
import xmltodict
|
||||
|
||||
from goodreads.client import GoodreadsClient
|
||||
from goodreads.request import GoodreadsRequest
|
||||
|
||||
try:
|
||||
from goodreads.client import GoodreadsClient
|
||||
import Levenshtein
|
||||
except ImportError:
|
||||
from betterreads.client import GoodreadsClient
|
||||
|
||||
try: import Levenshtein
|
||||
except ImportError: Levenshtein = False
|
||||
Levenshtein = False
|
||||
|
||||
from .. import logger
|
||||
from ..clean_html import clean_string
|
||||
|
||||
class my_GoodreadsClient(GoodreadsClient):
|
||||
|
||||
def request(self, *args, **kwargs):
|
||||
"""Create a GoodreadsRequest object and make that request"""
|
||||
req = my_GoodreadsRequest(self, *args, **kwargs)
|
||||
return req.request()
|
||||
|
||||
class GoodreadsRequestException(Exception):
|
||||
def __init__(self, error_msg, url):
|
||||
self.error_msg = error_msg
|
||||
self.url = url
|
||||
|
||||
def __str__(self):
|
||||
return self.url, ':', self.error_msg
|
||||
|
||||
|
||||
class my_GoodreadsRequest(GoodreadsRequest):
|
||||
|
||||
def request(self):
|
||||
resp = requests.get(self.host+self.path, params=self.params,
|
||||
headers={"User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:125.0) "
|
||||
"Gecko/20100101 Firefox/125.0"})
|
||||
if resp.status_code != 200:
|
||||
raise GoodreadsRequestException(resp.reason, self.path)
|
||||
if self.req_format == 'xml':
|
||||
data_dict = xmltodict.parse(resp.content)
|
||||
return data_dict['GoodreadsResponse']
|
||||
else:
|
||||
raise Exception("Invalid format")
|
||||
|
||||
|
||||
log = logger.create()
|
||||
@ -38,20 +71,20 @@ _CACHE_TIMEOUT = 23 * 60 * 60 # 23 hours (in seconds)
|
||||
_AUTHORS_CACHE = {}
|
||||
|
||||
|
||||
def connect(key=None, secret=None, enabled=True):
|
||||
def connect(key=None, enabled=True):
|
||||
global _client
|
||||
|
||||
if not enabled or not key or not secret:
|
||||
if not enabled or not key:
|
||||
_client = None
|
||||
return
|
||||
|
||||
if _client:
|
||||
# make sure the configuration has not changed since last we used the client
|
||||
if _client.client_key != key or _client.client_secret != secret:
|
||||
if _client.client_key != key:
|
||||
_client = None
|
||||
|
||||
if not _client:
|
||||
_client = GoodreadsClient(key, secret)
|
||||
_client = my_GoodreadsClient(key, None)
|
||||
|
||||
|
||||
def get_author_info(author_name):
|
||||
@ -76,6 +109,7 @@ def get_author_info(author_name):
|
||||
|
||||
if author_info:
|
||||
author_info._timestamp = now
|
||||
author_info.safe_about = clean_string(author_info.about)
|
||||
_AUTHORS_CACHE[author_name] = author_info
|
||||
return author_info
|
||||
|
||||
|
@ -8,8 +8,8 @@
|
||||
<img title="{{author.name}}" src="{{author.image_url}}" alt="{{author.name}}" class="author-photo pull-left">
|
||||
{% endif %}
|
||||
|
||||
{%if author.about is not none %}
|
||||
<p>{{author.about}}</p>
|
||||
{%if author.safe_about is not none %}
|
||||
<p>{{author.safe_about|safe}}</p>
|
||||
{% endif %}
|
||||
|
||||
- {{_("via")}} <a href="{{author.link}}" class="author-link" target="_blank" rel="noopener">Goodreads</a>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<h2>{{title}}</h2>
|
||||
<form role="form" method="POST" autocomplete="off">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="panel-group col-md-10 col-lg-8">
|
||||
<div class="panel-group col-md-11 col-lg-8">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
@ -155,17 +155,12 @@
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="config_use_goodreads" name="config_use_goodreads" data-control="goodreads-settings" {% if config.config_use_goodreads %}checked{% endif %}>
|
||||
<label for="config_use_goodreads">{{_('Use Goodreads')}}</label>
|
||||
<a href="https://www.goodreads.com/api/keys" target="_blank" style="margin-left: 5px">{{_('Create an API Key')}}</a>
|
||||
</div>
|
||||
<div data-related="goodreads-settings">
|
||||
<div class="form-group">
|
||||
<label for="config_goodreads_api_key">{{_('Goodreads API Key')}}</label>
|
||||
<input type="text" class="form-control" id="config_goodreads_api_key" name="config_goodreads_api_key" value="{% if config.config_goodreads_api_key != None %}{{ config.config_goodreads_api_key }}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="config_goodreads_api_secret_e">{{_('Goodreads API Secret')}}</label>
|
||||
<input type="password" class="form-control" id="config_goodreads_api_secret_e" name="config_goodreads_api_secret_e" value="" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-group">
|
||||
|
Loading…
Reference in New Issue
Block a user