diff --git a/cps/__init__.py b/cps/__init__.py
index 621705fd..9601c63b 100755
--- a/cps/__init__.py
+++ b/cps/__init__.py
@@ -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
diff --git a/cps/admin.py b/cps/admin.py
index 86e59317..3382e566 100755
--- a/cps/admin.py
+++ b/cps/admin.py
@@ -1631,7 +1631,10 @@ def import_ldap_users():
imported = 0
for username in new_users:
- user = username.decode('utf-8')
+ if isinstance(username, bytes):
+ user = username.decode('utf-8')
+ else:
+ user = username
if '=' in user:
# if member object field is empty take user object as filter
if config.config_ldap_member_user_object:
@@ -1806,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")
diff --git a/cps/clean_html.py b/cps/clean_html.py
new file mode 100644
index 00000000..19e87599
--- /dev/null
+++ b/cps/clean_html.py
@@ -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 .
+
+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
diff --git a/cps/config_sql.py b/cps/config_sql.py
index a781f2c5..4ea4a9e0 100644
--- a/cps/config_sql.py
+++ b/cps/config_sql.py
@@ -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:
diff --git a/cps/editbooks.py b/cps/editbooks.py
index 030fbf90..43309a14 100644
--- a/cps/editbooks.py
+++ b/cps/editbooks.py
@@ -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")
diff --git a/cps/services/goodreads_support.py b/cps/services/goodreads_support.py
index 74e6eba9..29c5c9f9 100644
--- a/cps/services/goodreads_support.py
+++ b/cps/services/goodreads_support.py
@@ -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
diff --git a/cps/templates/author.html b/cps/templates/author.html
index 3e82161c..f7314586 100644
--- a/cps/templates/author.html
+++ b/cps/templates/author.html
@@ -8,8 +8,8 @@
{% endif %}
- {%if author.about is not none %}
-
{{author.about}}
+ {%if author.safe_about is not none %}
+ {{author.safe_about|safe}}
{% endif %}
- {{_("via")}} Goodreads
diff --git a/cps/templates/config_edit.html b/cps/templates/config_edit.html
index 8035d03f..0d0a695f 100755
--- a/cps/templates/config_edit.html
+++ b/cps/templates/config_edit.html
@@ -9,7 +9,7 @@
{{title}}