From 94070ab96d60da270d4dffa6c6cf6aefcf747ef6 Mon Sep 17 00:00:00 2001 From: cdhigh Date: Thu, 21 Nov 2024 10:38:15 -0300 Subject: [PATCH] 3.2.1 1. Add proxy feature 2. Add dsl dictionary feature --- application/back_end/db_models.py | 4 +- application/back_end/send_mail_adpt.py | 4 +- application/back_end/task_queue_celery.py | 2 +- application/{utils.py => ke_utils.py} | 0 application/lib/build_ebook.py | 2 +- .../ebooks/conversion/plugins/recipe_input.py | 2 +- .../lib/calibre/ebooks/conversion/plumber.py | 2 +- application/lib/calibre/web/feeds/news.py | 2 +- application/lib/calibre/web/fetch/simple.py | 2 +- application/lib/dictionary/__init__.py | 3 +- application/lib/dictionary/lingvo/__init__.py | 3 + .../lib/dictionary/lingvo/dsl_reader.py | 156 ++++++++++++++++++ .../lib/dictionary/lingvo/lingvo_dict.py | 53 ++++++ application/lib/dictionary/mdict/mdict.py | 31 ++-- .../lib/dictionary/stardict/__init__.py | 3 + .../dictionary/{ => stardict}/pystardict.py | 12 +- .../lib/dictionary/{ => stardict}/stardict.py | 26 ++- .../lib/ebook_summarizer/html_summarizer.py | 2 +- .../lib/ebook_translator/html_translator.py | 2 +- application/lib/filesystem_dict.py | 4 +- application/lib/urlopener.py | 32 +++- application/static/base.js | 18 +- application/templates/adv_base.html | 17 ++ application/templates/adv_proxy.html | 43 +++++ application/translations/messages.pot | 89 +++++----- .../tr_TR/LC_MESSAGES/messages.mo | Bin 30768 -> 30831 bytes .../tr_TR/LC_MESSAGES/messages.po | 90 +++++----- .../translations/zh/LC_MESSAGES/messages.mo | Bin 28935 -> 29005 bytes .../translations/zh/LC_MESSAGES/messages.po | 90 +++++----- application/view/admin.py | 2 +- application/view/adv.py | 49 +++++- application/view/extension.py | 2 +- application/view/library.py | 2 +- application/view/library_offical.py | 4 +- application/view/login.py | 2 +- application/view/logs.py | 4 +- application/view/reader.py | 14 +- application/view/settings.py | 2 +- application/view/share.py | 2 +- application/view/subscribe.py | 2 +- application/view/translator.py | 2 +- application/work/url2book.py | 7 +- application/work/worker.py | 10 +- docs/Chinese/reader.md | 5 +- docs/English/reader.md | 5 +- main.py | 2 +- requirements.txt | 3 +- tools/update_req.py | 36 ++-- 48 files changed, 647 insertions(+), 202 deletions(-) rename application/{utils.py => ke_utils.py} (100%) create mode 100644 application/lib/dictionary/lingvo/__init__.py create mode 100644 application/lib/dictionary/lingvo/dsl_reader.py create mode 100644 application/lib/dictionary/lingvo/lingvo_dict.py create mode 100644 application/lib/dictionary/stardict/__init__.py rename application/lib/dictionary/{ => stardict}/pystardict.py (98%) rename application/lib/dictionary/{ => stardict}/stardict.py (64%) create mode 100644 application/templates/adv_proxy.html diff --git a/application/back_end/db_models.py b/application/back_end/db_models.py index fad41fef..dfc79772 100644 --- a/application/back_end/db_models.py +++ b/application/back_end/db_models.py @@ -5,7 +5,7 @@ #Author: cdhigh import os, random, datetime from operator import attrgetter -from ..utils import PasswordManager, ke_encrypt, ke_decrypt, utcnow, compare_version +from ..ke_utils import PasswordManager, ke_encrypt, ke_decrypt, utcnow, compare_version if os.getenv('DATABASE_URL', '').startswith(("datastore", "mongodb", "redis", "pickle")): from .db_models_nosql import * @@ -46,7 +46,7 @@ def cfg(self, item, default=None): return {'email': '', 'kindle_email': '', 'secret_key': '', 'timezone': 0, 'inbound_email': 'save,forward', 'keep_in_email_days': 1, 'delivery_mode': 'email,local', 'webshelf_days': 7, - 'reader_params': {}}.get(item, value) + 'reader_params': {}, 'proxy': ''}.get(item, value) else: return value def set_cfg(self, item, value): diff --git a/application/back_end/send_mail_adpt.py b/application/back_end/send_mail_adpt.py index 639f9943..f6c98135 100644 --- a/application/back_end/send_mail_adpt.py +++ b/application/back_end/send_mail_adpt.py @@ -6,7 +6,7 @@ #https://cloud.google.com/appengine/docs/standard/python3/reference/services/bundled/google/appengine/api/mail #https://cloud.google.com/appengine/docs/standard/python3/services/mail import os, datetime, zipfile, base64 -from ..utils import str_to_bool, sanitize_filename +from ..ke_utils import str_to_bool, sanitize_filename from ..base_handler import save_delivery_log #google.appengine will apply patch for os.env module @@ -207,7 +207,7 @@ def mailjet_send_mail(apikey, secret_key, sender, to, subject, body, html=None, def save_mail_to_local(dest_dir, subject, body, attachments=None, html=None, **kwargs): attachments = attachments or [] mailDir = os.path.join(appDir, dest_dir) - if not os.path.exists(mailDir): + if not os.path.isdir(mailDir): os.makedirs(mailDir) now = str(datetime.datetime.now().strftime('%H-%M-%S')) diff --git a/application/back_end/task_queue_celery.py b/application/back_end/task_queue_celery.py index c0c88aeb..54509910 100644 --- a/application/back_end/task_queue_celery.py +++ b/application/back_end/task_queue_celery.py @@ -34,7 +34,7 @@ def __call__(self, *args, **kwargs): transport_opts = {'data_folder_in': dir_in, 'data_folder_out': dir_out, 'processed_folder': dir_procsed, 'store_processed': True} for d in [dir_, dir_in, dir_out, dir_procsed]: - if not os.path.exists(d): + if not os.path.isdir(d): os.makedirs(d) broker_url = 'filesystem://' diff --git a/application/utils.py b/application/ke_utils.py similarity index 100% rename from application/utils.py rename to application/ke_utils.py diff --git a/application/lib/build_ebook.py b/application/lib/build_ebook.py index f9a5032d..3de394e4 100644 --- a/application/lib/build_ebook.py +++ b/application/lib/build_ebook.py @@ -9,7 +9,7 @@ from calibre.web.feeds.recipes import compile_recipe from recipe_helper import GenerateRecipeSource from urlopener import UrlOpener -from application.utils import loc_exc_pos +from application.ke_utils import loc_exc_pos #从输入格式生成对应的输出格式 #input_: 如果是recipe,为编译后的recipe(或列表),或者是一个输入文件名,或一个BytesIO diff --git a/application/lib/calibre/ebooks/conversion/plugins/recipe_input.py b/application/lib/calibre/ebooks/conversion/plugins/recipe_input.py index f7d69b94..1e3bc2d9 100644 --- a/application/lib/calibre/ebooks/conversion/plugins/recipe_input.py +++ b/application/lib/calibre/ebooks/conversion/plugins/recipe_input.py @@ -14,7 +14,7 @@ from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata.opf2 import OPFCreator from calibre.ebooks.metadata.toc import TOC -from application.utils import loc_exc_pos +from application.ke_utils import loc_exc_pos class RecipeDisabled(Exception): pass diff --git a/application/lib/calibre/ebooks/conversion/plumber.py b/application/lib/calibre/ebooks/conversion/plumber.py index d88a9913..6a0f7c70 100644 --- a/application/lib/calibre/ebooks/conversion/plumber.py +++ b/application/lib/calibre/ebooks/conversion/plumber.py @@ -25,7 +25,7 @@ from polyglot.builtins import string_or_bytes from filesystem_dict import FsDictStub -from application.utils import get_directory_size, loc_exc_pos +from application.ke_utils import get_directory_size, loc_exc_pos from application.base_handler import save_delivery_log DEBUG_README=b''' diff --git a/application/lib/calibre/web/feeds/news.py b/application/lib/calibre/web/feeds/news.py index 88f07d9c..48d144e2 100644 --- a/application/lib/calibre/web/feeds/news.py +++ b/application/lib/calibre/web/feeds/news.py @@ -34,7 +34,7 @@ from requests_file import LocalFileAdapter from filesystem_dict import FsDictStub from application.back_end.db_models import LastDelivered -from application.utils import loc_exc_pos +from application.ke_utils import loc_exc_pos MASTHEAD_SIZE = (600, 60) DEFAULT_MASTHEAD_IMAGE = 'mastheadImage.gif' diff --git a/application/lib/calibre/web/fetch/simple.py b/application/lib/calibre/web/fetch/simple.py index 16574347..bad8f78d 100644 --- a/application/lib/calibre/web/fetch/simple.py +++ b/application/lib/calibre/web/fetch/simple.py @@ -32,7 +32,7 @@ URLError, quote, url2pathname, urljoin, urlparse, urlsplit, urlunparse, urlunsplit, urlopen ) -from application.utils import loc_exc_pos +from application.ke_utils import loc_exc_pos class AbortArticle(Exception): pass diff --git a/application/lib/dictionary/__init__.py b/application/lib/dictionary/__init__.py index fd115bad..44d5461f 100644 --- a/application/lib/dictionary/__init__.py +++ b/application/lib/dictionary/__init__.py @@ -8,10 +8,11 @@ from .oxford_learners import OxfordLearners from .stardict import StarDict from .mdict import MDict +from .lingvo import LingvoDict all_dict_engines = {DictOrg.name: DictOrg, DictCn.name: DictCn, DictCc.name: DictCc, MerriamWebster.name: MerriamWebster, OxfordLearners.name: OxfordLearners, - StarDict.name: StarDict, MDict.name: MDict} + StarDict.name: StarDict, MDict.name: MDict, LingvoDict.name: LingvoDict} #创建一个词典实例 def CreateDictInst(engine, database, host=None): diff --git a/application/lib/dictionary/lingvo/__init__.py b/application/lib/dictionary/lingvo/__init__.py new file mode 100644 index 00000000..e2c905cb --- /dev/null +++ b/application/lib/dictionary/lingvo/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- +from .lingvo_dict import LingvoDict diff --git a/application/lib/dictionary/lingvo/dsl_reader.py b/application/lib/dictionary/lingvo/dsl_reader.py new file mode 100644 index 00000000..7a1bbd46 --- /dev/null +++ b/application/lib/dictionary/lingvo/dsl_reader.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- +#dsl离线词典支持,不支持dsl.dz,即使使用indexed_gzip还是慢,建议先解压为dsl再使用 +#Author: cdhigh +import os, re, logging, io +import chardet + +try: + import marisa_trie +except: + marisa_trie = None + +#外部接口 +class DslReader: + TRIE_FMT = '>LH' #释义开始位置,释义块字数 + + def __init__(self, fileName): + self.log = logging.getLogger() + self.fileName = fileName + self.encoding = None + firstPart = os.path.splitext(fileName)[0] + self.trieFileName = firstPart + '.trie' + self.encFileName = firstPart + '.enc' + self.trie = None + + if os.path.isfile(self.encFileName): + with open(self.encFileName, 'r', encoding='utf-8') as f: + self.encoding = f.read().strip() + + if os.path.isfile(self.trieFileName): + try: + self.trie = marisa_trie.RecordTrie(self.TRIE_FMT) #type:ignore + self.trie.load(self.trieFileName) + except Exception as e: + self.trie = None + self.log.warning(f'Failed to load dsldict trie data: {fileName}: {e}') + + if self.trie: + return + + #分析索引数据,构建前缀树 + self.log.info(f"Building trie for {fileName}") + self.buildTrie() + + #分析索引数据,构建前缀树 + #代码简单点,全部读入内存 + def buildTrie(self): + f = self.openDslFile() + encoding = self.encoding + records = [] + currWord = '' + meanStart = None + meanWordCnt = 0 + while True: + line = f.readline() + if line.startswith(('#', r'{{', '\n', '\r')): + meanWordCnt += len(line) + continue + + if not line: #文件结束 + if currWord and meanStart is not None: + records.append((currWord, (meanStart, min(meanWordCnt, 65000)))) + break + + #开始一个词条 + if not line.startswith((' ', '\t')): + if currWord and meanStart is not None: + #保存前词条的偏移位置 + records.append((currWord, (meanStart, min(meanWordCnt, 65000)))) + meanStart = None + + currWord = line.strip() + if meanStart is None: + meanStart = f.tell() #f.tell()特别慢,要等到需要的时候才调用 + meanWordCnt = 0 + else: #有缩进,是释义块 + meanWordCnt += len(line) + + f.close() + self.trie = marisa_trie.RecordTrie(self.TRIE_FMT, records) #type:ignore + self.trie.save(self.trieFileName) + del records + del self.trie + self.trie = marisa_trie.RecordTrie(self.TRIE_FMT) #type:ignore + self.trie.load(self.trieFileName) + + #打开文件,返回文件实例 + def openDslFile(self): + if not self.encoding: #检测编码,因为很多词典不按官方的要求使用unicode + import chardet + with open(self.fileName, 'rb') as f: + data = f.read(10000) + ret = chardet.detect(data) + encoding = ret['encoding'] if ret['confidence'] >= 0.8 else None + + #逐一测试 + if not encoding: + for enc in ['utf-16', 'utf-16-le', 'windows-1252']: + try: + with open(self.fileName, 'r', encoding=enc) as f: + f.readline() + encoding = enc + break + except UnicodeError: + pass + + self.encoding = (encoding or 'utf-16').lower() + with open(self.encFileName, 'w', encoding='utf-8') as fEnc: + fEnc.write(self.encoding) + + return open(self.fileName, 'r', encoding=self.encoding) + + #查词接口 + def get(self, word, default=''): #type:ignore + for wd in [word, word.lower(), word.capitalize()]: + if wd in self.trie: + break + else: + return default + + start, size = self.trie[wd][0] + lines = [] + with self.openDslFile() as f: + f.seek(start) + lines = f.read(size).splitlines() + mean = '\n'.join([line for line in lines if line.startswith((' ', '\t'))]) + return self.dslMeanToHtml(mean) + + #将原始释义转换为合法的html文本 + def dslMeanToHtml(self, mean): + simpleTags = {"[']": '', "[/']": '', '[b]': '', '[/b]': '', '[i]': '', + '[/i]': '', '[u]': '', '[/u]': '', '[sub]': '', '[/sub]': '', + '[sup]': '', '[/sup]': '', '[/c]': '', '@': '
', '\t': '', + '[*]': '', '[/*]': '', '\\[': '[', '\\]': ']', '\n': '
', + '[ex]': '', '[/ex]': '', + '[p]': '', '[/p]': '', + '[url]': '', '[/url]': '', + '[ref]': '', '[/ref]': '',} + removeTags = ['[/m]', '[com]', '[/com]', '[trn]', '[/trn]', '[trs]', + '[/trs]', '[!trn]', '[/!trn]', '[!trs]', '[/!trs]', '[/lang]'] + + #print(mean) #TODO + for tag, repl in simpleTags.items(): + mean = mean.replace(tag, repl) + for tag in removeTags: + mean = mean.replace(tag, '') + + # 替换[m],根据匹配内容生成相应数量的空格 + mean = re.sub(r'\[m\d+?\]', lambda match: ' ' * int(match.group(0)[2:-1]), mean) + mean = re.sub(r'\[c.*?\]', '', mean) + #浏览器不支持 entry:// 协议,会直接拦截导致无法跳转, + mean = re.sub(r'\[lang.*?\]', '', mean) + mean = re.sub(r'\[s\].*?\[/s\]', '', mean) + mean = re.sub(r'<<(.*?)>>', r'\1', mean) + #print(mean) #TODO + return mean diff --git a/application/lib/dictionary/lingvo/lingvo_dict.py b/application/lib/dictionary/lingvo/lingvo_dict.py new file mode 100644 index 00000000..93b1d80a --- /dev/null +++ b/application/lib/dictionary/lingvo/lingvo_dict.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- +#lingvo dsl 离线词典支持 +#Author: cdhigh +import os, re +from application.ke_utils import loc_exc_pos +from .dsl_reader import DslReader + +#获取本地的dsl文件列表,只有列表,没有校验是否有效 +def getDslDictList(): + dictDir = os.environ.get('DICTIONARY_DIR') + if not dictDir or not os.path.isdir(dictDir): + return {} + + ret = {} + for dirPath, _, fileNames in os.walk(dictDir): + for fileName in fileNames: + if fileName.lower().endswith('.dsl'): + dictName = os.path.splitext(fileName)[0] + #为了界面显示和其他dict的一致,键为词典全路径名,值为词典名字 + ret[os.path.join(dirPath, fileName)] = dictName + return ret + +class LingvoDict: + name = "lingvo" + #词典列表,键为词典缩写,值为词典描述 + databases = getDslDictList() + + #更新词典列表 + @classmethod + def refresh(cls): + cls.databases = getDslDictList() + + def __init__(self, database='', host=None): + self.database = database + self.dictionary = None + self.initError = None + if database in self.databases: + try: + self.dictionary = DslReader(database) + except: + self.initError = loc_exc_pos(f'Init LingvoDict failed: {self.databases[database]}') + default_log.warning(self.initError) + else: + self.initError = f'Dict not found: {self.databases[database]}' + default_log.warning(self.initError) + + #返回当前使用的词典名字 + def __repr__(self): + return '{} [{}]'.format(self.name, self.databases.get(self.database, '')) + + def definition(self, word, language=''): + return self.initError if self.initError else self.dictionary.get(word) diff --git a/application/lib/dictionary/mdict/mdict.py b/application/lib/dictionary/mdict/mdict.py index 18ca0cb0..fc43ed67 100644 --- a/application/lib/dictionary/mdict/mdict.py +++ b/application/lib/dictionary/mdict/mdict.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 # -*- coding:utf-8 -*- #mdx离线词典接口 +#Author: cdhigh import os from bs4 import BeautifulSoup -from application.utils import xml_escape +from application.ke_utils import xml_escape, loc_exc_pos from .readmdict import MDX try: import marisa_trie @@ -38,20 +39,25 @@ def refresh(cls): def __init__(self, database='', host=None): self.database = database self.dictionary = None + self.initError = None if database in self.databases: try: self.dictionary = IndexedMdx(database) - except Exception as e: - default_log.warning(f'Instantiate mdict failed: {self.databases[database]}: {e}') + except: + self.initError = loc_exc_pos(f'Init mdict failed: {self.databases[database]}') + default_log.warning(self.initError) else: - default_log.warning(f'dict not found: {self.databases[database]}') + self.initError = f'Dict not found: {self.databases[database]}' + default_log.warning(self.initError) #返回当前使用的词典名字 def __repr__(self): return 'mdict [{}]'.format(self.databases.get(self.database, '')) def definition(self, word, language=''): - return self.dictionary.get(word) if self.dictionary else '' + if self.initError: + return self.initError + return self.dictionary.get(word) #经过词典树缓存的Mdx class IndexedMdx: @@ -94,18 +100,23 @@ def __init__(self, fname, encoding="", substyle=False, passcode=None): def get(self, word): if not self.trie: return '' - word = word.lower().strip() + #和mdict官方应用一样,输入:about返回词典基本信息 if word == ':about': return self.dict_html_info() - indexes = self.trie[word] if word in self.trie else None + for wd in [word, word.lower(), word.capitalize()]: + if wd in self.trie: + indexes = self.trie[word] + break + else: + return '' + ret = self.get_content_by_Index(indexes) if ret.startswith('@@@LINK='): word = ret[8:].strip() - if word: - indexes = self.trie[word] if word in self.trie else None - ret = self.get_content_by_Index(indexes) + if word and word in self.trie: + ret = self.get_content_by_Index(self.trie[word]) return ret def __contains__(self, word) -> bool: diff --git a/application/lib/dictionary/stardict/__init__.py b/application/lib/dictionary/stardict/__init__.py new file mode 100644 index 00000000..534e23c1 --- /dev/null +++ b/application/lib/dictionary/stardict/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- +from .stardict import StarDict diff --git a/application/lib/dictionary/pystardict.py b/application/lib/dictionary/stardict/pystardict.py similarity index 98% rename from application/lib/dictionary/pystardict.py rename to application/lib/dictionary/stardict/pystardict.py index b2208984..e2d25b41 100644 --- a/application/lib/dictionary/pystardict.py +++ b/application/lib/dictionary/stardict/pystardict.py @@ -58,7 +58,11 @@ def __repr__(self): return f'{self.__class__} {self.ifo.bookname}' def get(self, word, default=''): #type:ignore - return self[word] if word in self.idx else default + for wd in [word, word.lower(), word.capitalize()]: + if wd in self.idx: + return self[wd] + else: + return default def has_key(self, k): return k in self @@ -178,7 +182,7 @@ def __init__(self, dict_prefix, container): bytes_size = int(container.ifo.idxoffsetbits / 8) offset_format = 'L' if bytes_size == 4 else 'Q' trie_fmt = f">{offset_format}L" - if os.path.exists(trie_filename): + if os.path.isfile(trie_filename): try: self.trie = marisa_trie.RecordTrie(trie_fmt) #type:ignore self.trie.load(trie_filename) @@ -451,7 +455,7 @@ def open_file(regular, gz): Open regular file if it exists, gz file otherwise. If no file exists, raise ValueError. """ - if os.path.exists(regular): + if os.path.isfile(regular): try: return open(regular, 'rb') except Exception as e: @@ -460,7 +464,7 @@ def open_file(regular, gz): #压缩索引文件后缀一般是gz,使用gzip压缩, #压缩数据文件后缀一般是dz,使用dictzip压缩,dictzip使用与 gzip 相同的压缩算法和文件格式, #但是它提供一个表可以用来在文件中随机访问压缩块。 - if os.path.exists(gz): + if os.path.isfile(gz): try: return igzip.IndexedGzipFile(gz) if igzip else gzip.open(gz, 'rb') #type:ignore except Exception as e: diff --git a/application/lib/dictionary/stardict.py b/application/lib/dictionary/stardict/stardict.py similarity index 64% rename from application/lib/dictionary/stardict.py rename to application/lib/dictionary/stardict/stardict.py index f1c5b792..3fb2da2c 100644 --- a/application/lib/dictionary/stardict.py +++ b/application/lib/dictionary/stardict/stardict.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 # -*- coding:utf-8 -*- #stardict离线词典支持 -import os +#Author: cdhigh +import os, re +from application.ke_utils import loc_exc_pos from .pystardict import PyStarDict #获取本地的stardict文件列表,只有列表,没有校验是否有效 def getStarDictList(): dictDir = os.environ.get('DICTIONARY_DIR') - if not dictDir or not os.path.exists(dictDir): + if not dictDir or not os.path.isdir(dictDir): return {} ret = {} @@ -32,23 +34,33 @@ def refresh(cls): def __init__(self, database='', host=None): self.database = database self.dictionary = None + self.initError = None if database in self.databases: try: self.dictionary = PyStarDict(database) except Exception as e: - default_log.warning(f'Instantiate stardict failed: {self.databases[database]}: {e}') + self.initError = loc_exc_pos(f'Init stardict failed: {self.databases[database]}') + default_log.warning(self.initError) + else: + self.initError = f'Dict not found: {self.databases[database]}' + default_log.warning(self.initError) #返回当前使用的词典名字 def __repr__(self): return 'stardict [{}]'.format(self.databases.get(self.database, '')) def definition(self, word, language=''): - word = word.lower().strip() - ret = self.dictionary.get(word) if self.dictionary else '' + if self.initError: + return self.initError + + ret = self.dictionary.get(word) if isinstance(ret, bytes): ret = ret.decode('utf-8') + #每条释义的前面添加一个换行 + ret = re.sub(r'(\s*\d+)', r'
\1', ret, flags=re.IGNORECASE) lines = [line.strip() for line in str(ret).split('\n') if line.strip()] - if lines and lines[0] in (word, f'{word}'): + if lines and lines[0] in (word, f'{word}'): #去掉开头的词条 lines = lines[1:] - return '\n'.join(lines) + + return '
'.join(lines) diff --git a/application/lib/ebook_summarizer/html_summarizer.py b/application/lib/ebook_summarizer/html_summarizer.py index 155ebff8..4dc3fbf1 100644 --- a/application/lib/ebook_summarizer/html_summarizer.py +++ b/application/lib/ebook_summarizer/html_summarizer.py @@ -4,7 +4,7 @@ #Author: cdhigh import re, time import simple_ai_provider -from application.utils import loc_exc_pos +from application.ke_utils import loc_exc_pos def get_summarizer_engines(): return simple_ai_provider._PROV_AI_LIST diff --git a/application/lib/ebook_translator/html_translator.py b/application/lib/ebook_translator/html_translator.py index 51013d3d..c7423003 100644 --- a/application/lib/ebook_translator/html_translator.py +++ b/application/lib/ebook_translator/html_translator.py @@ -4,7 +4,7 @@ import re, time, copy from bs4 import BeautifulSoup, NavigableString, Tag from ebook_translator.engines import * -from application.utils import loc_exc_pos +from application.ke_utils import loc_exc_pos #生成一个当前所有支持的翻译引擎的字典,在网页内使用 def get_trans_engines(): diff --git a/application/lib/filesystem_dict.py b/application/lib/filesystem_dict.py index 43f389e1..ccf890c7 100644 --- a/application/lib/filesystem_dict.py +++ b/application/lib/filesystem_dict.py @@ -70,7 +70,7 @@ def dump(self, path: str): file = key[1:] if key.startswith('/') else key file_path = os.path.join(path, file) file_dir = os.path.dirname(file_path) - if not os.path.exists(file_dir): + if not os.path.isdir(file_dir): os.makedirs(file_dir) with open(file_path, 'wb') as f: f.write(content) @@ -145,7 +145,7 @@ def write(self, path, data, mode='wb'): self.fs_dict[path] = data else: dir_ = os.path.dirname(path) - if not os.path.exists(dir_): + if not os.path.isdir(dir_): os.makedirs(dir_) with open(path, mode) as f: f.write(data) diff --git a/application/lib/urlopener.py b/application/lib/urlopener.py index b7f2dbe8..8b374b49 100644 --- a/application/lib/urlopener.py +++ b/application/lib/urlopener.py @@ -40,6 +40,8 @@ def wrapper(*args, **kwargs): return decorator class UrlOpener: + _proxies = {} + #headers 可以传入一个字典或元祖列表 #file_stub 用于本地文件读取,如果为None,则使用python的open()函数 def __init__(self, *, host=None, timeout=60, headers=None, file_stub=None, user_agent=None, **kwargs): @@ -62,6 +64,8 @@ def __init__(self, *, host=None, timeout=60, headers=None, file_stub=None, user_ self.fs = file_stub #FsDictStub self.session = requests.session() + if self._proxies: + self.session.proxies.update(self._proxies) self.prevRespRef = None #对Response实例的一个弱引用 self.form = None self.soup = None @@ -102,6 +106,7 @@ def post(self, *args, **kwargs): return self.open(*args, **kwargs) #远程连接互联网的url + #正常情况不会抛出异常,可以通过传入 throw_exception=True 抛出异常 @UrlRetry(max_retries=2, delay=2, backoff=2) def open_remote_url(self, url, data, headers, timeout, method, **kwargs): timeout = timeout if timeout else self.timeout @@ -115,14 +120,24 @@ def open_remote_url(self, url, data, headers, timeout, method, **kwargs): else: req_func = self.session.post #type:ignore + throwException = kwargs.pop('throw_exception', None) + kwargs.setdefault('proxies', self._proxies) + try: resp = req_func(url, data=data, headers=headers, timeout=timeout, allow_redirects=True, verify=False, **kwargs) - except: - resp = requests.models.Response() - resp.status_code = 555 - default_log.warning(f"open_remote_url: {method} {url} failed: {traceback.format_exc()}") - + except Exception as e: + if throwException: + raise + else: + resp = requests.models.Response() + resp.status_code = 555 + resp.reason = str(e) + default_log.warning(f"open_remote_url: {method} {url} failed: {traceback.format_exc()}") + + if throwException: + resp.raise_for_status() + #有些网页头部没有编码信息,则使用chardet检测编码,否则requests会认为text类型的编码为"ISO-8859-1" contentType = resp.headers.get("Content-Type", "").lower() if 'text/html' in contentType and "charset" not in contentType: @@ -367,6 +382,13 @@ def reload(self): def response(self): return self.prevRespRef() if self.prevRespRef else None + @classmethod + def set_proxy(cls, url): + if url and url.startswith(('http', 'socks')): + cls._proxies = {'http': url, 'https': url} + else: + cls._proxies = {} + @classmethod def CodeMap(cls, errCode): return '{} {}'.format(errCode, cls._codeMapDict.get(errCode, '')) diff --git a/application/static/base.js b/application/static/base.js index f186dab1..eaa161c1 100644 --- a/application/static/base.js +++ b/application/static/base.js @@ -1739,4 +1739,20 @@ function DictEngineFieldChanged(idx) { } } -///[end] adv_dict.html \ No newline at end of file +///[end] adv_dict.html + +///[start] adv_proxy.html +//测试代理服务器文本框有变化后,将网址更新到测试按钮 +function UpdateProxyTestHref(url) { + $('#btn_test_proxy').attr('href', '/fwd?url=' + encodeURIComponent(url)); +} + +//按下回车键触发 "测试" 链接按钮 +function TriggerBtnTestProxy(event) { + if (event.key === "Enter") { + console.log('enter'); + event.preventDefault(); + window.open($('#btn_test_proxy').attr("href"), "_blank"); + } +} +///[end] adv_proxy.html \ No newline at end of file diff --git a/application/templates/adv_base.html b/application/templates/adv_base.html index 644e391b..89f8f8a7 100644 --- a/application/templates/adv_base.html +++ b/application/templates/adv_base.html @@ -70,6 +70,23 @@ {{_("Dictionary")}} {% endif -%} + {% if advCurr=='proxy' -%} +
  • + {{_("Proxy")}} + {%if hasProxy -%} + + {% endif -%} + +
  • + {% else -%} +
  • + {{_("Proxy")}} + {%if hasProxy -%} + + {% endif -%} + +
  • + {% endif -%} {% if advCurr=='import' -%}
  • {{_("Import Feeds")}} diff --git a/application/templates/adv_proxy.html b/application/templates/adv_proxy.html new file mode 100644 index 00000000..4372cc4c --- /dev/null +++ b/application/templates/adv_proxy.html @@ -0,0 +1,43 @@ +{% extends "adv_base.html" %} +{% block titleTag -%} +{{ _("Proxy") }} - KindleEar +{% endblock -%} + +{% block advcontent -%} +
    +
    + {% if tips -%} +
    {{tips}}
    + {% endif -%} + {{_("Proxy")}} +

    {{_("Supports")}}: http, https, socks4, socks4a, socks5, socks5h

    +
    + +
    +
    + +
    +
    +
    +
    +
    + {{_("Test")}} +
    +
    + + +
    +
    +
    +
    +{% endblock -%} +{% block js -%} + +{% endblock -%} diff --git a/application/translations/messages.pot b/application/translations/messages.pot index fc5d8c26..ae527889 100644 --- a/application/translations/messages.pot +++ b/application/translations/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-11-18 13:20-0300\n" +"POT-Creation-Date: 2024-11-21 10:11-0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -28,6 +28,7 @@ msgstr "" #: application/templates/admin.html:19 #: application/templates/adv_calibre_options.html:18 +#: application/templates/adv_proxy.html:18 msgid "Save" msgstr "" @@ -269,6 +270,13 @@ msgstr "" #: application/templates/adv_base.html:102 #: application/templates/adv_base.html:106 +#: application/templates/adv_proxy.html:3 +#: application/templates/adv_proxy.html:12 +msgid "Proxy" +msgstr "" + +#: application/templates/adv_base.html:111 +#: application/templates/adv_base.html:115 #: application/templates/adv_calibre_options.html:12 msgid "Calibre Options" msgstr "" @@ -436,6 +444,18 @@ msgstr "" msgid "Please input mail address" msgstr "" +#: application/templates/adv_proxy.html:13 +msgid "Supports" +msgstr "" + +#: application/templates/adv_proxy.html:24 +#: application/templates/adv_proxy.html:29 +#: application/templates/book_audiolator.html:132 +#: application/templates/book_summarizer.html:105 +#: application/templates/book_translator.html:97 +msgid "Test" +msgstr "" + #: application/templates/adv_uploadcover.html:3 msgid "Cover image" msgstr "" @@ -1126,12 +1146,6 @@ msgstr "" msgid "Your browser does not support the audio element." msgstr "" -#: application/templates/book_audiolator.html:132 -#: application/templates/book_summarizer.html:105 -#: application/templates/book_translator.html:97 -msgid "Test" -msgstr "" - #: application/templates/book_summarizer.html:18 #: application/templates/book_translator.html:21 msgid "Enable" @@ -1670,9 +1684,10 @@ msgstr "" msgid "Word" msgstr "" -#: application/view/admin.py:48 application/view/adv.py:441 -#: application/view/settings.py:66 application/view/translator.py:87 -#: application/view/translator.py:171 application/view/translator.py:253 +#: application/view/admin.py:48 application/view/adv.py:431 +#: application/view/adv.py:483 application/view/settings.py:66 +#: application/view/translator.py:87 application/view/translator.py:171 +#: application/view/translator.py:253 msgid "Settings Saved!" msgstr "" @@ -1725,93 +1740,93 @@ msgstr "" msgid "Changes saved successfully." msgstr "" -#: application/view/adv.py:100 application/view/adv.py:101 -#: application/view/adv.py:102 application/view/adv.py:103 -#: application/view/adv.py:104 application/view/adv.py:105 -#: application/view/adv.py:106 application/view/adv.py:107 -#: application/view/adv.py:108 application/view/adv.py:109 +#: application/view/adv.py:101 application/view/adv.py:102 +#: application/view/adv.py:103 application/view/adv.py:104 +#: application/view/adv.py:105 application/view/adv.py:106 +#: application/view/adv.py:107 application/view/adv.py:108 +#: application/view/adv.py:109 application/view/adv.py:110 msgid "Append hyperlink '{}' to article" msgstr "" -#: application/view/adv.py:100 application/view/adv.py:101 -#: application/view/adv.py:102 application/view/adv.py:103 -#: application/view/adv.py:104 +#: application/view/adv.py:101 application/view/adv.py:102 +#: application/view/adv.py:103 application/view/adv.py:104 +#: application/view/adv.py:105 msgid "Save to {}" msgstr "" -#: application/view/adv.py:100 +#: application/view/adv.py:101 msgid "evernote" msgstr "" -#: application/view/adv.py:101 +#: application/view/adv.py:102 msgid "wiz" msgstr "" -#: application/view/adv.py:102 +#: application/view/adv.py:103 msgid "pocket" msgstr "" -#: application/view/adv.py:103 +#: application/view/adv.py:104 msgid "instapaper" msgstr "" -#: application/view/adv.py:104 +#: application/view/adv.py:105 msgid "wallabag" msgstr "" -#: application/view/adv.py:105 application/view/adv.py:106 -#: application/view/adv.py:107 application/view/adv.py:108 +#: application/view/adv.py:106 application/view/adv.py:107 +#: application/view/adv.py:108 application/view/adv.py:109 msgid "Share on {}" msgstr "" -#: application/view/adv.py:105 +#: application/view/adv.py:106 msgid "weibo" msgstr "" -#: application/view/adv.py:106 +#: application/view/adv.py:107 msgid "facebook" msgstr "" -#: application/view/adv.py:108 +#: application/view/adv.py:109 msgid "tumblr" msgstr "" -#: application/view/adv.py:109 +#: application/view/adv.py:110 msgid "Open in browser" msgstr "" -#: application/view/adv.py:110 +#: application/view/adv.py:111 msgid "Append qrcode of url to article" msgstr "" -#: application/view/adv.py:374 application/view/share.py:54 +#: application/view/adv.py:375 application/view/share.py:54 #: application/view/subscribe.py:250 msgid "Unknown command: {}" msgstr "" -#: application/view/adv.py:443 +#: application/view/adv.py:433 application/view/adv.py:485 msgid "The format is invalid." msgstr "" -#: application/view/adv.py:475 +#: application/view/adv.py:517 msgid "Authorization Error!
    {}" msgstr "" -#: application/view/adv.py:496 +#: application/view/adv.py:538 msgid "Success authorized by Pocket!" msgstr "" -#: application/view/adv.py:502 +#: application/view/adv.py:544 msgid "" "Failed to request authorization of Pocket!
    See details " "below:

    {}" msgstr "" -#: application/view/adv.py:523 +#: application/view/adv.py:565 msgid "The Instapaper service encountered an error. Please try again later." msgstr "" -#: application/view/adv.py:536 +#: application/view/adv.py:578 msgid "Request type [{}] unsupported" msgstr "" diff --git a/application/translations/tr_TR/LC_MESSAGES/messages.mo b/application/translations/tr_TR/LC_MESSAGES/messages.mo index 65a87c6efd60d881e5932c455d7665cc71f66e17..440b077d15690bd7b3f54c7f10d7ca1f78ecf5bc 100644 GIT binary patch delta 8066 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 Nb1SN9(`Q|r{ZGLDlM(;` delta 7990 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\n" "Language: tr_TR\n" @@ -29,6 +29,7 @@ msgstr "Kayıt Ayarları" #: application/templates/admin.html:19 #: application/templates/adv_calibre_options.html:18 +#: application/templates/adv_proxy.html:18 msgid "Save" msgstr "Kaydet" @@ -270,6 +271,13 @@ msgstr "Stylesheet" #: application/templates/adv_base.html:102 #: application/templates/adv_base.html:106 +#: application/templates/adv_proxy.html:3 +#: application/templates/adv_proxy.html:12 +msgid "Proxy" +msgstr "Proxy" + +#: application/templates/adv_base.html:111 +#: application/templates/adv_base.html:115 #: application/templates/adv_calibre_options.html:12 msgid "Calibre Options" msgstr "Calibre seçenekleri" @@ -441,6 +449,18 @@ msgstr "Örnek" msgid "Please input mail address" msgstr "Lütfen e-posta adresini giriniz" +#: application/templates/adv_proxy.html:13 +msgid "Supports" +msgstr "Destekler" + +#: application/templates/adv_proxy.html:24 +#: application/templates/adv_proxy.html:29 +#: application/templates/book_audiolator.html:132 +#: application/templates/book_summarizer.html:105 +#: application/templates/book_translator.html:97 +msgid "Test" +msgstr "Test" + #: application/templates/adv_uploadcover.html:3 msgid "Cover image" msgstr "Kapak resmi" @@ -1144,12 +1164,6 @@ msgstr "Metin" msgid "Your browser does not support the audio element." msgstr "Tarayıcınız ses öğesini desteklemiyor." -#: application/templates/book_audiolator.html:132 -#: application/templates/book_summarizer.html:105 -#: application/templates/book_translator.html:97 -msgid "Test" -msgstr "Test" - #: application/templates/book_summarizer.html:18 #: application/templates/book_translator.html:21 msgid "Enable" @@ -1704,9 +1718,10 @@ msgstr "Ek olarak ilet" msgid "Word" msgstr "Kelime" -#: application/view/admin.py:48 application/view/adv.py:441 -#: application/view/settings.py:66 application/view/translator.py:87 -#: application/view/translator.py:171 application/view/translator.py:253 +#: application/view/admin.py:48 application/view/adv.py:431 +#: application/view/adv.py:483 application/view/settings.py:66 +#: application/view/translator.py:87 application/view/translator.py:171 +#: application/view/translator.py:253 msgid "Settings Saved!" msgstr "Ayarlar Kaydedildi!" @@ -1759,83 +1774,83 @@ msgstr "Eski şifre yanlış." msgid "Changes saved successfully." msgstr "Değişiklikler başarıyla kaydedildi." -#: application/view/adv.py:100 application/view/adv.py:101 -#: application/view/adv.py:102 application/view/adv.py:103 -#: application/view/adv.py:104 application/view/adv.py:105 -#: application/view/adv.py:106 application/view/adv.py:107 -#: application/view/adv.py:108 application/view/adv.py:109 +#: application/view/adv.py:101 application/view/adv.py:102 +#: application/view/adv.py:103 application/view/adv.py:104 +#: application/view/adv.py:105 application/view/adv.py:106 +#: application/view/adv.py:107 application/view/adv.py:108 +#: application/view/adv.py:109 application/view/adv.py:110 msgid "Append hyperlink '{}' to article" msgstr "'{}' linkini makaleye ekle" -#: application/view/adv.py:100 application/view/adv.py:101 -#: application/view/adv.py:102 application/view/adv.py:103 -#: application/view/adv.py:104 +#: application/view/adv.py:101 application/view/adv.py:102 +#: application/view/adv.py:103 application/view/adv.py:104 +#: application/view/adv.py:105 msgid "Save to {}" msgstr "{} kaydedildi" -#: application/view/adv.py:100 +#: application/view/adv.py:101 msgid "evernote" msgstr "evernote" -#: application/view/adv.py:101 +#: application/view/adv.py:102 msgid "wiz" msgstr "wiz" -#: application/view/adv.py:102 +#: application/view/adv.py:103 msgid "pocket" msgstr "pocket" -#: application/view/adv.py:103 +#: application/view/adv.py:104 msgid "instapaper" msgstr "instapaper" -#: application/view/adv.py:104 +#: application/view/adv.py:105 msgid "wallabag" msgstr "wallabag" -#: application/view/adv.py:105 application/view/adv.py:106 -#: application/view/adv.py:107 application/view/adv.py:108 +#: application/view/adv.py:106 application/view/adv.py:107 +#: application/view/adv.py:108 application/view/adv.py:109 msgid "Share on {}" msgstr "{} üzerinde paylaş" -#: application/view/adv.py:105 +#: application/view/adv.py:106 msgid "weibo" msgstr "weibo" -#: application/view/adv.py:106 +#: application/view/adv.py:107 msgid "facebook" msgstr "facebook" -#: application/view/adv.py:108 +#: application/view/adv.py:109 msgid "tumblr" msgstr "tumblr" -#: application/view/adv.py:109 +#: application/view/adv.py:110 msgid "Open in browser" msgstr "Tarayıcıda aç" -#: application/view/adv.py:110 +#: application/view/adv.py:111 msgid "Append qrcode of url to article" msgstr "Makaleye URL'nin QR kodunu ekle" -#: application/view/adv.py:374 application/view/share.py:54 +#: application/view/adv.py:375 application/view/share.py:54 #: application/view/subscribe.py:250 msgid "Unknown command: {}" msgstr "Bilinmeyen komut: {}" -#: application/view/adv.py:443 +#: application/view/adv.py:433 application/view/adv.py:485 msgid "The format is invalid." msgstr "Format geçersiz." -#: application/view/adv.py:475 +#: application/view/adv.py:517 msgid "Authorization Error!
    {}" msgstr "Yetkilendirme Hatası!
    {}" -#: application/view/adv.py:496 +#: application/view/adv.py:538 msgid "Success authorized by Pocket!" msgstr "Pocket tarafından yetkilendirilen başarı!" -#: application/view/adv.py:502 +#: application/view/adv.py:544 msgid "" "Failed to request authorization of Pocket!


    See details " "below:

    {}" @@ -1843,13 +1858,13 @@ msgstr "" "Pocket yetkilendirme isteği başarısız oldu!
    Aşağıdaki ayrıntılara " "bakın:

    {}" -#: application/view/adv.py:523 +#: application/view/adv.py:565 msgid "The Instapaper service encountered an error. Please try again later." msgstr "" "Instapaper servisi bir hata ile karşılaştı. Lütfen daha sonra tekrar " "deneyin." -#: application/view/adv.py:536 +#: application/view/adv.py:578 msgid "Request type [{}] unsupported" msgstr "İstek türü [{}] desteklenmiyor" @@ -2256,3 +2271,4 @@ msgstr "Bu tarifin giriş bilgileri kaydedildi." msgid "The api key is required." msgstr "API anahtarı gereklidir." + diff --git a/application/translations/zh/LC_MESSAGES/messages.mo b/application/translations/zh/LC_MESSAGES/messages.mo index f586b3f1d9089fd52da41e17cf087944fc73b221..1989e5a58235cf3a9d8370c859a4659e62085f60 100644 GIT binary patch delta 8073 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 Spr8dCe$0QVaAS7g3jYJqERrDr delta 7991 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 NJ`ueA=J;U|{{wwFe2@SD diff --git a/application/translations/zh/LC_MESSAGES/messages.po b/application/translations/zh/LC_MESSAGES/messages.po index 9f0f0946..2e80037a 100644 --- a/application/translations/zh/LC_MESSAGES/messages.po +++ b/application/translations/zh/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: KindleEar v3.0.0\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-11-18 13:20-0300\n" +"POT-Creation-Date: 2024-11-21 10:11-0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh\n" @@ -29,6 +29,7 @@ msgstr "注册设置" #: application/templates/admin.html:19 #: application/templates/adv_calibre_options.html:18 +#: application/templates/adv_proxy.html:18 msgid "Save" msgstr "保存" @@ -270,6 +271,13 @@ msgstr "样式表" #: application/templates/adv_base.html:102 #: application/templates/adv_base.html:106 +#: application/templates/adv_proxy.html:3 +#: application/templates/adv_proxy.html:12 +msgid "Proxy" +msgstr "代理服务器" + +#: application/templates/adv_base.html:111 +#: application/templates/adv_base.html:115 #: application/templates/adv_calibre_options.html:12 msgid "Calibre Options" msgstr "Calibre配置" @@ -437,6 +445,18 @@ msgstr "例子" msgid "Please input mail address" msgstr "请输入邮件地址" +#: application/templates/adv_proxy.html:13 +msgid "Supports" +msgstr "支持" + +#: application/templates/adv_proxy.html:24 +#: application/templates/adv_proxy.html:29 +#: application/templates/book_audiolator.html:132 +#: application/templates/book_summarizer.html:105 +#: application/templates/book_translator.html:97 +msgid "Test" +msgstr "测试" + #: application/templates/adv_uploadcover.html:3 msgid "Cover image" msgstr "封面图像" @@ -1127,12 +1147,6 @@ msgstr "原文" msgid "Your browser does not support the audio element." msgstr "您的浏览器不支持audio标签。" -#: application/templates/book_audiolator.html:132 -#: application/templates/book_summarizer.html:105 -#: application/templates/book_translator.html:97 -msgid "Test" -msgstr "测试" - #: application/templates/book_summarizer.html:18 #: application/templates/book_translator.html:21 msgid "Enable" @@ -1673,9 +1687,10 @@ msgstr "作为附件转发" msgid "Word" msgstr "单词" -#: application/view/admin.py:48 application/view/adv.py:441 -#: application/view/settings.py:66 application/view/translator.py:87 -#: application/view/translator.py:171 application/view/translator.py:253 +#: application/view/admin.py:48 application/view/adv.py:431 +#: application/view/adv.py:483 application/view/settings.py:66 +#: application/view/translator.py:87 application/view/translator.py:171 +#: application/view/translator.py:253 msgid "Settings Saved!" msgstr "设置已保存!" @@ -1728,93 +1743,93 @@ msgstr "原密码错误。" msgid "Changes saved successfully." msgstr "账号设置已更新。" -#: application/view/adv.py:100 application/view/adv.py:101 -#: application/view/adv.py:102 application/view/adv.py:103 -#: application/view/adv.py:104 application/view/adv.py:105 -#: application/view/adv.py:106 application/view/adv.py:107 -#: application/view/adv.py:108 application/view/adv.py:109 +#: application/view/adv.py:101 application/view/adv.py:102 +#: application/view/adv.py:103 application/view/adv.py:104 +#: application/view/adv.py:105 application/view/adv.py:106 +#: application/view/adv.py:107 application/view/adv.py:108 +#: application/view/adv.py:109 application/view/adv.py:110 msgid "Append hyperlink '{}' to article" msgstr "在每篇文章后附加 '{}' 超链接" -#: application/view/adv.py:100 application/view/adv.py:101 -#: application/view/adv.py:102 application/view/adv.py:103 -#: application/view/adv.py:104 +#: application/view/adv.py:101 application/view/adv.py:102 +#: application/view/adv.py:103 application/view/adv.py:104 +#: application/view/adv.py:105 msgid "Save to {}" msgstr "保存到 {}" -#: application/view/adv.py:100 +#: application/view/adv.py:101 msgid "evernote" msgstr "evernote" -#: application/view/adv.py:101 +#: application/view/adv.py:102 msgid "wiz" msgstr "为知笔记" -#: application/view/adv.py:102 +#: application/view/adv.py:103 msgid "pocket" msgstr "pocket" -#: application/view/adv.py:103 +#: application/view/adv.py:104 msgid "instapaper" msgstr "instapaper" -#: application/view/adv.py:104 +#: application/view/adv.py:105 msgid "wallabag" msgstr "wallabag" -#: application/view/adv.py:105 application/view/adv.py:106 -#: application/view/adv.py:107 application/view/adv.py:108 +#: application/view/adv.py:106 application/view/adv.py:107 +#: application/view/adv.py:108 application/view/adv.py:109 msgid "Share on {}" msgstr "分享到 {}" -#: application/view/adv.py:105 +#: application/view/adv.py:106 msgid "weibo" msgstr "微博" -#: application/view/adv.py:106 +#: application/view/adv.py:107 msgid "facebook" msgstr "facebook" -#: application/view/adv.py:108 +#: application/view/adv.py:109 msgid "tumblr" msgstr "tumblr" -#: application/view/adv.py:109 +#: application/view/adv.py:110 msgid "Open in browser" msgstr "在浏览器打开" -#: application/view/adv.py:110 +#: application/view/adv.py:111 msgid "Append qrcode of url to article" msgstr "在每篇文章后附加文章链接的二维码" -#: application/view/adv.py:374 application/view/share.py:54 +#: application/view/adv.py:375 application/view/share.py:54 #: application/view/subscribe.py:250 msgid "Unknown command: {}" msgstr "未知命令:{}" -#: application/view/adv.py:443 +#: application/view/adv.py:433 application/view/adv.py:485 msgid "The format is invalid." msgstr "格式非法。" -#: application/view/adv.py:475 +#: application/view/adv.py:517 msgid "Authorization Error!
    {}" msgstr "申请授权过程失败!
    {}" -#: application/view/adv.py:496 +#: application/view/adv.py:538 msgid "Success authorized by Pocket!" msgstr "已经成功获得Pocket的授权!" -#: application/view/adv.py:502 +#: application/view/adv.py:544 msgid "" "Failed to request authorization of Pocket!
    See details " "below:

    {}" msgstr "申请Pocket授权失败!
    错误信息参考如下:

    {}" -#: application/view/adv.py:523 +#: application/view/adv.py:565 msgid "The Instapaper service encountered an error. Please try again later." msgstr "Instapaper服务器异常,请稍候再试。" -#: application/view/adv.py:536 +#: application/view/adv.py:578 msgid "Request type [{}] unsupported" msgstr "不支持你请求的命令类型 [{}]" @@ -2210,4 +2225,3 @@ msgstr "此Recipe的网站登录信息已经保存。" #: application/view/translator.py:81 application/view/translator.py:164 msgid "The api key is required." msgstr "需要填写api key." - diff --git a/application/view/admin.py b/application/view/admin.py index 6f9fcef5..c13b2a96 100644 --- a/application/view/admin.py +++ b/application/view/admin.py @@ -8,7 +8,7 @@ from flask_babel import gettext as _ from ..base_handler import * from ..back_end.db_models import * -from ..utils import str_to_int, utcnow +from ..ke_utils import str_to_int, utcnow from .login import CreateAccountIfNotExist bpAdmin = Blueprint('bpAdmin', __name__) diff --git a/application/view/adv.py b/application/view/adv.py index 923fd671..517d0933 100644 --- a/application/view/adv.py +++ b/application/view/adv.py @@ -6,12 +6,13 @@ from urllib.parse import unquote, urljoin, urlparse from bs4 import BeautifulSoup from html import escape -from flask import Blueprint, url_for, render_template, redirect, session, send_file, abort, current_app as app +from flask import (Blueprint, url_for, render_template, redirect, session, send_file, + abort, Response, current_app as app) from flask_babel import gettext as _ from PIL import Image from ..base_handler import * from ..back_end.db_models import * -from ..utils import ke_decrypt, str_to_int, str_to_bool, safe_eval, xml_escape, xml_unescape +from ..ke_utils import ke_decrypt, str_to_int, str_to_bool, safe_eval, xml_escape, xml_unescape from ..lib.pocket import Pocket from ..lib.wallabag import WallaBag from ..lib.urlopener import UrlOpener @@ -20,9 +21,12 @@ bpAdv = Blueprint('bpAdv', __name__) def adv_render_template(tpl, advCurr, **kwargs): + user = kwargs.get('user', None) kwargs.setdefault('tab', 'advset') kwargs.setdefault('tips', '') kwargs.setdefault('adminName', app.config['ADMIN_NAME']) + if user: + kwargs.setdefault('hasProxy', bool(user.cfg('proxy'))) #print(kwargs.get('tips')) return render_template(tpl, advCurr=advCurr, **kwargs) @@ -412,6 +416,47 @@ def AdvDictPost(user: KeUser): user.save() return redirect(url_for('bpAdv.AdvDict')) +#打开代理设置页面 +@bpAdv.route("/adv/proxy", endpoint='AdvProxy') +@login_required() +def AdvProxy(user: KeUser): + proxy = user.cfg('proxy') + return adv_render_template('adv_proxy.html', 'proxy', proxy=proxy, user=user, tips='') + +#设置代理地址 +@bpAdv.post("/adv/proxy", endpoint='AdvProxyPost') +@login_required() +def AdvProxyPost(user: KeUser): + proxy = request.form.get('proxy', '').lower() + if not proxy or proxy.startswith(('http', 'socks')): + user.set_cfg('proxy', proxy) + user.save() + tips = _("Settings Saved!") + else: + tips = _("The format is invalid.") + return adv_render_template('adv_proxy.html', 'proxy', proxy=proxy, user=user, tips=tips) + +#测试代理是否正常 +@bpAdv.route("/fwd", endpoint='AdvFwdRoute') +@login_required() +def AdvFwdRoute(user: KeUser): + UrlOpener.set_proxy(user.cfg('proxy')) + url = request.args.get('url') + if url: + url = unquote(url) + if not url.startswith('http'): + url = 'https://' + url + try: + resp = UrlOpener().open(url) + headers = dict(resp.headers) + headers.pop('Transfer-Encoding', None) + headers.pop('Content-Encoding', None) + return Response(resp.content, status=resp.status_code, headers=headers) + except Exception as e: + return str(e) + else: + return 'param "url" is empty' + #设置calibre的参数 @bpAdv.route("/adv/calibre", endpoint='AdvCalibreOptions') @login_required() diff --git a/application/view/extension.py b/application/view/extension.py index e1fb1399..984e2825 100644 --- a/application/view/extension.py +++ b/application/view/extension.py @@ -10,7 +10,7 @@ from flask_babel import gettext as _ from calibre.web.feeds.news import get_tags_from_rules from ..base_handler import * -from ..utils import xml_escape +from ..ke_utils import xml_escape from ..back_end.db_models import * from urlopener import UrlOpener diff --git a/application/view/library.py b/application/view/library.py index 252d988a..db7448bc 100644 --- a/application/view/library.py +++ b/application/view/library.py @@ -6,7 +6,7 @@ from flask import Blueprint, render_template, request, current_app as app from flask_babel import gettext as _ from ..base_handler import * -from ..utils import str_to_bool, url_validator +from ..ke_utils import str_to_bool, url_validator from ..back_end.db_models import * from ..lib.urlopener import UrlOpener from .library_offical import * diff --git a/application/view/library_offical.py b/application/view/library_offical.py index 0853b0b7..0428f439 100644 --- a/application/view/library_offical.py +++ b/application/view/library_offical.py @@ -7,7 +7,7 @@ from flask import Blueprint, request, Response, current_app as app from flask_babel import gettext as _ from ..base_handler import * -from ..utils import str_to_bool, str_to_int, utcnow +from ..ke_utils import str_to_bool, str_to_int, utcnow from ..back_end.db_models import * #几个"官方"服务的地址 @@ -75,8 +75,6 @@ def SharedLibraryOfficalAjax(): return respDict #将贡献者的网址加密 - #from apps.utils import hide_website - #creator = hide_website(creator) creator = hashlib.md5(creator.encode('utf-8')).hexdigest() #判断是否存在,如果存在,则更新分类或必要的信息,同时返回成功 diff --git a/application/view/login.py b/application/view/login.py index bab9946f..0b970150 100644 --- a/application/view/login.py +++ b/application/view/login.py @@ -9,7 +9,7 @@ from ..base_handler import * from ..back_end.db_models import * from ..back_end.send_mail_adpt import send_html_mail -from ..utils import new_secret_key, hide_email, PasswordManager, utcnow +from ..ke_utils import new_secret_key, hide_email, PasswordManager, utcnow bpLogin = Blueprint('bpLogin', __name__) diff --git a/application/view/logs.py b/application/view/logs.py index a1d75bc5..d0ae1e55 100644 --- a/application/view/logs.py +++ b/application/view/logs.py @@ -8,7 +8,7 @@ from flask_babel import gettext as _ from ..base_handler import * from ..back_end.db_models import * -from ..utils import utcnow +from ..ke_utils import utcnow bpLogs = Blueprint('bpLogs', __name__) @@ -51,7 +51,7 @@ def RemoveLogs(): #清理临时目录 tmpDir = os.environ.get('KE_TEMP_DIR') - if tmpDir and os.path.exists(tmpDir): + if tmpDir and os.path.isdir(tmpDir): ret.append(DeleteOldFiles(tmpDir, 1)) #清理30天之前的推送记录 diff --git a/application/view/reader.py b/application/view/reader.py index acf216b9..4f2bd3a5 100644 --- a/application/view/reader.py +++ b/application/view/reader.py @@ -11,7 +11,7 @@ from flask_babel import gettext as _ from build_ebook import html_to_book from ..base_handler import * -from ..utils import xml_escape, xml_unescape, str_to_int, str_to_float, str_to_bool +from ..ke_utils import xml_escape, xml_unescape, str_to_int, str_to_float, str_to_bool from ..back_end.db_models import * from ..back_end.send_mail_adpt import send_to_kindle from .settings import get_locale, LangMap @@ -130,7 +130,7 @@ def ReaderDeletePost(user: KeUser, userDir: str): continue bkDir = os.path.join(userDir, book) dateDir = os.path.dirname(bkDir) - if os.path.exists(bkDir): + if os.path.isdir(bkDir): try: shutil.rmtree(bkDir) except Exception as e: @@ -253,7 +253,7 @@ def ReaderDictPost(user: KeUser, userDir: str): @login_required() def ReaderDictCssRoute(path: str, user: KeUser): dictDir = app.config['DICTIONARY_DIR'] - return send_from_directory(dictDir, path) if dictDir and os.path.exists(dictDir) else '' + return send_from_directory(dictDir, path) if dictDir and os.path.isdir(dictDir) else '' #构建Hunspell实例 #language: 语种代码,只有前两个字母 @@ -269,7 +269,7 @@ def InitHunspell(language): dictDir = app.config['DICTIONARY_DIR'] or '' morphDir = os.path.join(dictDir, 'morphology') if dictDir else '' dics = [] - if morphDir and os.path.exists(morphDir): + if morphDir and os.path.isdir(morphDir): dics.extend([os.path.splitext(e)[0] for e in os.listdir(morphDir) if e.endswith('.dic') and e.startswith(language)]) if dics: @@ -380,7 +380,7 @@ def PushSingleArticle(src: str, title: str, user: KeUser, userDir: str, language #获取当前用户保存的所有电子书,返回一个列表[{date:, books: [{title:, articles:[{title:, src:}],},...]}, ] def GetSavedOebList(userDir: str) -> list: - if not os.path.exists(userDir): + if not os.path.isdir(userDir): return [] ret = [] @@ -405,7 +405,7 @@ def GetSavedOebList(userDir: str) -> list: #从content.opf里面提取文章元信息,返回一个字典 {title:,language:,} def ExtractBookMeta(opfFile: str) -> dict: - if not os.path.exists(opfFile): + if not os.path.isfile(opfFile): return {} try: @@ -427,7 +427,7 @@ def ExtractBookMeta(opfFile: str) -> dict: #从toc.ncx里面提取文章列表,返回一个字典列表 [{title:,'src':,}] def ExtractArticleList(ncxFile: str, prefix: str) -> list: - if not os.path.exists(ncxFile): + if not os.path.isfile(ncxFile): return [] try: diff --git a/application/view/settings.py b/application/view/settings.py index 7a5671af..ae7da6db 100644 --- a/application/view/settings.py +++ b/application/view/settings.py @@ -8,7 +8,7 @@ from flask_babel import gettext as _ from calibre.customize.profiles import output_profiles from ..base_handler import * -from ..utils import str_to_bool, str_to_int +from ..ke_utils import str_to_bool, str_to_int from ..back_end.db_models import * from ..back_end.send_mail_adpt import avaliable_sm_services, send_mail from .subscribe import UpdateBookedCustomRss diff --git a/application/view/share.py b/application/view/share.py index 260ed337..eb822420 100644 --- a/application/view/share.py +++ b/application/view/share.py @@ -11,7 +11,7 @@ from ..base_handler import * from ..back_end.db_models import * from ..back_end.send_mail_adpt import send_html_mail -from ..utils import hide_email, ke_decrypt +from ..ke_utils import hide_email, ke_decrypt from pocket import Pocket from wallabag import WallaBag from urlopener import UrlOpener diff --git a/application/view/subscribe.py b/application/view/subscribe.py index 7083bb44..f0d9367c 100644 --- a/application/view/subscribe.py +++ b/application/view/subscribe.py @@ -9,7 +9,7 @@ from ..base_handler import * from ..back_end.db_models import * from ..back_end.task_queue_adpt import create_notifynewsubs_task -from ..utils import str_to_bool, xml_escape, utcnow +from ..ke_utils import str_to_bool, xml_escape, utcnow from ..lib.urlopener import UrlOpener from ..lib.recipe_helper import GetBuiltinRecipeInfo, GetBuiltinRecipeSource from .library import LIBRARY_MGR, SUBSCRIBED_FROM_LIBRARY, LIBRARY_GETSRC, buildKeUrl diff --git a/application/view/translator.py b/application/view/translator.py index 7463a1b7..f17a4364 100644 --- a/application/view/translator.py +++ b/application/view/translator.py @@ -6,7 +6,7 @@ from functools import wraps from flask import Blueprint, render_template, request, url_for from flask_babel import gettext as _ -from ..utils import str_to_bool, str_to_int +from ..ke_utils import str_to_bool, str_to_int from ..base_handler import * from ..back_end.db_models import * from ..lib.recipe_helper import GetBuiltinRecipeInfo diff --git a/application/work/url2book.py b/application/work/url2book.py index 54badbec..e4b6bb92 100644 --- a/application/work/url2book.py +++ b/application/work/url2book.py @@ -7,7 +7,7 @@ from bs4 import BeautifulSoup from calibre.web.feeds.news import recursive_fetch_url from ..base_handler import * -from ..utils import filesizeformat, hide_email +from ..ke_utils import filesizeformat, hide_email from ..back_end.db_models import * from ..back_end.send_mail_adpt import send_to_kindle, send_mail from urlopener import UrlOpener @@ -44,7 +44,10 @@ def Url2BookImpl(userName, urls, title, key, action='', text='', language=''): return "The user does not exist." urls = urls.split('|') if urls else [] - + + #设置全局代理 + UrlOpener.set_proxy(user.cfg('proxy')) + if urls and action == 'download': #直接下载电子书并推送 return u2lDownloadFile(user, urls, title) elif action == 'debug': #调试目的,将链接直接下载,发送到管理员邮箱 diff --git a/application/work/worker.py b/application/work/worker.py index 1181399e..6666786b 100644 --- a/application/work/worker.py +++ b/application/work/worker.py @@ -7,9 +7,10 @@ from collections import defaultdict from flask import Blueprint, request, current_app as app from ..base_handler import * -from ..utils import filesizeformat +from ..ke_utils import filesizeformat from ..back_end.send_mail_adpt import send_to_kindle from ..back_end.db_models import * +from urlopener import UrlOpener from calibre.web.feeds.recipes import compile_recipe from recipe_helper import * from build_ebook import convert_book @@ -69,6 +70,9 @@ def WorkerImpl(userName: str, recipeId: Union[list,str,None]=None, reason='cron' startTime = time.time() + #设置全局代理 + UrlOpener.set_proxy(user.cfg('proxy')) + #编译recipe srcDict = GetAllRecipeSrc(user, recipeId) #返回一个字典,键名为title,元素为 [BookedRecipe, Recipe, src] recipes = defaultdict(list) #用于保存编译好的recipe代码对象 @@ -239,7 +243,7 @@ def MergeAudioSegment(roList): except Exception as e: default_log.warning('Failed to merge mp3 by pymp3cat: {e}') - if mergedFiles and os.path.exists(outputFile): + if mergedFiles and os.path.isfile(outputFile): chapters.append(outputFile) @@ -268,7 +272,7 @@ def MergeAudioSegment(roList): except Exception as e: info = 'Failed merge mp3 by pymp3cat: {e}' - if not info and mergedFiles and os.path.exists(outputFile): + if not info and mergedFiles and os.path.isfile(outputFile): try: with open(outputFile, 'rb') as f: data = f.read() diff --git a/docs/Chinese/reader.md b/docs/Chinese/reader.md index 0e24f623..ef27ddf5 100644 --- a/docs/Chinese/reader.md +++ b/docs/Chinese/reader.md @@ -45,7 +45,10 @@ KindleEar支持邮件推送和在线阅读,内置一个为电子墨水屏进 ### 安装词典 1. KindleEar支持在线词典 [dict.org](https://dict.org/), [dict.cc](https://www.dict.cc/), [dict.cn](http://dict.cn/), [韦氏词典](https://www.merriam-webster.com/),[牛津词典](https://www.oxfordlearnersdictionaries.com/), 这几个词典不需要安装,开箱即用。 -2. 在线词典很方便,但是避免有时候因为网络原因不是太稳定,所以如果要稳定使用,最好还是使用离线词典,为此,KindleEar同时支持 mdict/stardict 格式词典,下载对应的词典后,解压到 `data/dict` 目录(可以使用子目录整理不同的词典)。 +2. 在线词典很方便,但是避免有时候因为网络原因不是太稳定,所以如果要稳定使用,最好还是使用离线词典,为此,KindleEar同时支持 mdict/stardict/lingvo 格式词典,下载对应的词典后,解压到 `data/dict` 目录(可以使用子目录整理不同的词典)。 + * mdict: 只需要 mdx 文件,如果有css,则需要位于同样目录 + * stardict: 需要 ifo, idx, dict 或 dict.dz + * lingvo: 只需要 dsl 文件,不支持 dsl.dz,需要将 dsl.dz 解压缩为 dsl 3. 离线词典第一次查词会比较慢,因为要创建索引文件(后缀为trie),之后就很快了。 如果要使用大型词典(比如几百兆以上),在生成索引的过程中会消耗比较多的内存,如你的服务器内存比较小,可能会创建索引失败,你可以在你的本地机器先使用对应词典查一次单词,待本地生成trie文件后,拷贝到服务器对应目录即可。 4. 已经默认支持美式英语的构词法规则,可以查询单词时态语态复数等变形,如果需要支持其他语种的构词法,请下载对应的hunspell格式的文件(.dic/.aff),然后拷贝到 `data/dict/morphology` (请直接创建此目录) ,注意不要存放到子目录下,KindleEar会自动使用和书本语言相匹配的构词法规则。 diff --git a/docs/English/reader.md b/docs/English/reader.md index d6f2e5f5..00500876 100644 --- a/docs/English/reader.md +++ b/docs/English/reader.md @@ -50,7 +50,10 @@ The extracted word is sent to your deployed KindleEar site for translation, and ### Installing Dictionaries 1. KindleEar supports online dictionaries such as [dict.org](https://dict.org/), [dict.cc](https://www.dict.cc/), [dict.cn](http://dict.cn/), [Merriam-Webster](https://www.merriam-webster.com/), [Oxford](https://www.oxfordlearnersdictionaries.com/). These dictionaries require no installation and are ready to use out of the box. -2. KindleEar also supports offline dictionaries in the stardict format. After downloading the corresponding dictionary, unzip it into the `data/dict` directory. You can organize different dictionaries into subdirectories. Then, restart the KindleEar service to refresh the dictionary list. +2. KindleEar also supports offline dictionaries in the mdict/stardict/lingvo format. After downloading the corresponding dictionary, unzip it into the `data/dict` directory. You can organize different dictionaries into subdirectories. Then, restart the KindleEar service to refresh the dictionary list. + * mdict: Requires `.mdx` file only. If there is a `.css`, it must be in the same directory. + * stardict: Requires `.ifo`, `.idx`, and `.dict` (or `.dict.dz`). + * lingvo: Only the `.dsl`. requires extracting it into `dsl` if you have `dsl.dz`. 3. The first time you look up a word in the offline dictionary, it may be slow because it needs to create an index file (suffix: trie), After that, it will be much faster. If you are using a large dictionary (for example, above several hundred megabytes), the indexing process will consume a significant amount of memory. If the server has limited memory, the indexing might fail. You can first use the dictionary on your local machine to look up a word and generate the "trie" file, then copy it to the corresponding directory on the server. diff --git a/main.py b/main.py index 45a323b3..3646e27d 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,7 @@ # Visit for the latest version # Author: cdhigh -__Version__ = '3.2' +__Version__ = '3.2.1' import os, sys, builtins, logging from application.lib import clogging diff --git a/requirements.txt b/requirements.txt index 5dabee73..126e4c43 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,9 @@ requests~=2.32.0 +PySocks~=1.7.1 chardet~=5.2.0 pillow~=10.3.0 lxml~=5.2.0 -lxml_html_clean~=0.1.1 +lxml_html_clean~=0.4.0 sendgrid~=6.11.0 mailjet_rest~=1.3.4 python-dateutil~=2.9.0 diff --git a/tools/update_req.py b/tools/update_req.py index a3e52c3c..245bee9c 100644 --- a/tools/update_req.py +++ b/tools/update_req.py @@ -12,35 +12,37 @@ def new_secret_key(length=12): allchars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXZYabcdefghijklmnopqrstuvwxyz' return ''.join([secrets.choice(allchars) for i in range(length)]) -REQ_COMM = [('requests', '~=2.32.0'), +REQ_COMM = [ + ('requests', '~=2.32.0'), + ('PySocks', '~=1.7.1'), ('chardet', '~=5.2.0'), - ('pillow', '~=10.3.0'), - ('lxml', '~=5.2.0'), - ('lxml_html_clean', '~=0.1.1'), + ('pillow', '~=10.4.0'), + ('lxml', '~=5.3.0'), + ('lxml-html-clean', '~=0.4.1'), ('sendgrid', '~=6.11.0'), - ('mailjet_rest', '~=1.3.4'), + ('mailjet-rest', '~=1.3.4'), ('python-dateutil', '~=2.9.0'), - ('css_parser', '~=1.0.10'), - ('beautifulsoup4', '~=4.12.2'), + ('css-parser', '~=1.0.10'), + ('beautifulsoup4', '~=4.12.3'), ('html2text', '~=2024.2.26'), ('html5lib', '~=1.1'), ('#html5-parser', '~=0.4.0'), ('gunicorn', '~=22.0.0'), - ('Flask', '~=3.0.3'), + ('Flask', '~=3.1.0'), ('flask-babel', '~=4.0.0'), ('six', '~=1.16.0'), ('feedparser', '~=6.0.11'), ('qrcode', '~=7.4.2'), - ('gtts', '~=2.5.1'), - ('edge-tts', '~=6.1.11'), + ('gtts', '~=2.5.4'), + ('edge-tts', '~=6.1.18'), ('justext', '~=3.0.1'), ] REQ_DB = { - 'sqlite': [('peewee', '~=3.17.1'),], - 'mysql': [('peewee', '~=3.17.1'), ('pymysql', '~=1.1.0'),], - 'postgresql': [('peewee', '~=3.17.1'), ('psycopg2-binary', '~=2.9.9'),], - 'cockroachdb': [('peewee', '~=3.17.1'), ('psycopg2-binary', '~=2.9.9'),], + 'sqlite': [('peewee', '~=3.17.8'),], + 'mysql': [('peewee', '~=3.17.8'), ('pymysql', '~=1.1.1'),], + 'postgresql': [('peewee', '~=3.17.8'), ('psycopg2-binary', '~=2.9.9'),], + 'cockroachdb': [('peewee', '~=3.17.8'), ('psycopg2-binary', '~=2.9.9'),], 'datastore': [('weedata', '>=0.2.7,<1.0.0'), ('google-cloud-datastore', '~=2.19.0'),], 'mongodb': [('weedata', '>=0.2.7,<1.0.0'), ('pymongo', '~=4.6.3'),], 'redis': [('weedata', '>=0.2.7,<1.0.0'), ('redis', '~=5.0.3'),], @@ -56,9 +58,9 @@ def new_secret_key(length=12): REQ_PLAT = {'gae': [('appengine-python-standard', '~=1.1.6'), ('google-cloud-texttospeech', '~=2.16.3')], - 'docker': [('chunspell', '~=2.0.4'), ('marisa_trie', '~=1.2.0'), ('indexed-gzip', '~=1.8.7')], #docker/amd64 basic libs - 'dockerArm': [('chunspell', '~=2.0.4'), ('marisa_trie', '~=1.2.0'), ('indexed-gzip', '~=1.8.7')], #docker/arm64 basic libs - 'dockerAll': [('weedata', '>=0.2.7,<1.0.0'),('pymysql', '~=1.1.0'), #docker[all] install all libs + 'docker': [('chunspell', '~=2.0.4'), ('marisa-trie', '~=1.2.1'), ('indexed-gzip', '~=1.9.1')], #docker/amd64 basic libs + 'dockerArm': [('chunspell', '~=2.0.4'), ('marisa-trie', '~=1.2.1'), ('indexed-gzip', '~=1.9.1')], #docker/arm64 basic libs + 'dockerAll': [('weedata', '>=0.2.7,<1.0.0'),('pymysql', '~=1.1.1'), #docker[all] install all libs ('psycopg2-binary', '~=2.9.9'),('pymongo', '~=4.6.3'),('redis', '~=5.0.3'), ('celery', '~=5.3.6'),('flask-rq2', '~=18.3'),('sqlalchemy', '~=2.0.29')], }