Compare commits

...

4 Commits

Author SHA1 Message Date
John Smith
2a22617c1a 更新版本号v1.5.3 2021-07-18 14:55:13 +08:00
John Smith
6b77a8a17f 修复腾讯翻译白嫖版 2021-07-18 14:53:36 +08:00
John Smith
8780655341 配置文件兼容带BOM的UTF-8 2021-07-17 13:17:40 +08:00
John Smith
6a4faa83a4 修复一些PyCharm警告 2021-07-17 13:03:45 +08:00
9 changed files with 114 additions and 26 deletions

View File

@@ -7,6 +7,10 @@ import tornado.web
# noinspection PyAbstractClass
class ApiHandler(tornado.web.RequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.json_args = None
def set_default_headers(self):
# 跨域测试用
if not self.application.settings['debug']:
@@ -18,13 +22,12 @@ class ApiHandler(tornado.web.RequestHandler):
self.request.headers['Access-Control-Request-Headers'])
def prepare(self):
if self.request.headers.get('Content-Type', '').startswith('application/json'):
try:
self.json_args = json.loads(self.request.body)
except json.JSONDecodeError:
self.json_args = None
else:
self.json_args = None
if not self.request.headers.get('Content-Type', '').startswith('application/json'):
return
try:
self.json_args = json.loads(self.request.body)
except json.JSONDecodeError:
pass
async def options(self, *_args, **_kwargs):
# 跨域测试用

View File

@@ -62,7 +62,7 @@ class AppConfig:
def load(self, path):
try:
config = configparser.ConfigParser()
config.read(path, 'utf-8')
config.read(path, 'utf-8-sig')
self._load_app_config(config)
self._load_translator_configs(config)
@@ -121,7 +121,7 @@ class AppConfig:
self.translator_configs = translator_configs
def _str_to_list(value, item_type: Type=str, container_type: Type=list):
def _str_to_list(value, item_type: Type = str, container_type: Type = list):
value = value.strip()
if value == '':
return container_type()

View File

@@ -9,7 +9,7 @@
</router-link>
</div>
<div class="version">
v1.5.2
v1.5.3
</div>
<sidebar></sidebar>
</el-aside>

View File

@@ -60,6 +60,7 @@ def init_logging(debug):
file_handler = logging.handlers.TimedRotatingFileHandler(
LOG_FILE_NAME, encoding='utf-8', when='midnight', backupCount=7, delay=True
)
# noinspection PyArgumentList
logging.basicConfig(
format='{asctime} {levelname} [{name}]: {message}',
datefmt='%Y-%m-%d %H:%M:%S',

View File

@@ -25,7 +25,7 @@ _avatar_url_cache: Dict[int, str] = {}
# 正在获取头像的Futureuser_id -> Future
_uid_fetch_future_map: Dict[int, asyncio.Future] = {}
# 正在获取头像的user_id队列
_uid_queue_to_fetch = None
_uid_queue_to_fetch: Optional[asyncio.Queue] = None
# 上次被B站ban时间
_last_fetch_banned_time: Optional[datetime.datetime] = None

View File

@@ -13,7 +13,7 @@ engine = None
DbSession: Optional[Type[sqlalchemy.orm.Session]] = None
def init(debug):
def init(_debug):
cfg = config.get_config()
global engine, DbSession
# engine = sqlalchemy.create_engine(cfg.database_url, echo=debug)
@@ -28,7 +28,7 @@ def get_session():
session = DbSession()
try:
yield session
except:
except BaseException:
session.rollback()
raise
finally:

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import asyncio
import base64
import datetime
import functools
import hashlib
@@ -11,6 +12,8 @@ import random
import re
from typing import *
import Crypto.Cipher.AES as cry_aes
import Crypto.Util.Padding as cry_pad
import aiohttp
import config
@@ -23,7 +26,7 @@ NO_TRANSLATE_TEXTS = {
}
_main_event_loop = asyncio.get_event_loop()
_http_session = None
_http_session: Optional[aiohttp.ClientSession] = None
_translate_providers: List['TranslateProvider'] = []
# text -> res
_translate_cache: Dict[str, str] = {}
@@ -217,9 +220,11 @@ class TencentTranslateFree(FlowControlTranslateProvider):
self._source_language = source_language
self._target_language = target_language
self._qtv = ''
self._qtk = ''
self._server_time_delta = 0
self._uc_key = self._uc_iv = ''
self._qtv = self._qtk = ''
self._reinit_future = None
# 连续失败的次数
self._fail_count = 0
@@ -239,12 +244,42 @@ class TencentTranslateFree(FlowControlTranslateProvider):
return False
html = await r.text()
m = re.search(r"""\breauthuri\s*=\s*['"](.+?)['"]""", html)
if m is None:
logger.exception('TencentTranslateFree init failed: reauthuri not found')
return False
reauthuri = m[1]
try:
server_time = r.headers['Date']
server_time = datetime.datetime.strptime(server_time, '%a, %d %b %Y %H:%M:%S GMT')
server_time = server_time.replace(tzinfo=datetime.timezone.utc).timestamp()
self._server_time_delta = int((datetime.datetime.now().timestamp() - server_time) * 1000)
except (KeyError, ValueError):
self._server_time_delta = 0
except (aiohttp.ClientConnectionError, asyncio.TimeoutError):
logger.exception('TencentTranslateFree init error:')
return False
# 获取token URL
m = re.search(r"""\breauthuri\s*=\s*['"](.+?)['"]""", html)
if m is None:
logger.exception('TencentTranslateFree init failed: reauthuri not found')
return False
reauthuri = m[1]
# 获取验证用的key、iv
m = re.search(r"""\s*=\s*['"]((?:\w+\|\w+-)+\w+\|\w+)['"]""", html)
if m is None:
logger.exception('TencentTranslateFree init failed: initial global variables not found')
return False
uc_key = None
uc_iv = None
for item in m[1].split('-'):
key, _, value = item.partition('|')
if key == 'a137':
uc_key = value
elif key == 'E74':
uc_iv = value
if uc_key is not None and uc_iv is not None:
break
# 获取token
try:
async with _http_session.post('https://fanyi.qq.com/api/' + reauthuri) as r:
if r.status != 200:
logger.warning('TencentTranslateFree init request failed: reauthuri=%s, status=%d %s',
@@ -264,6 +299,8 @@ class TencentTranslateFree(FlowControlTranslateProvider):
logger.warning('TencentTranslateFree init failed: qtk not found')
return False
self._uc_key = uc_key
self._uc_iv = uc_iv
self._qtv = qtv
self._qtk = qtk
return True
@@ -279,7 +316,7 @@ class TencentTranslateFree(FlowControlTranslateProvider):
@property
def is_available(self):
return self._qtv != '' and self._qtk != '' and super().is_available
return '' not in (self._uc_key, self._uc_iv, self._qtv, self._qtk) and super().is_available
async def _translate_coroutine(self, text, future):
try:
@@ -299,7 +336,8 @@ class TencentTranslateFree(FlowControlTranslateProvider):
async with _http_session.post(
'https://fanyi.qq.com/api/translate',
headers={
'Referer': 'https://fanyi.qq.com/'
'Referer': 'https://fanyi.qq.com/',
'uc': self._get_uc()
},
data={
'source': self._source_language,
@@ -312,6 +350,7 @@ class TencentTranslateFree(FlowControlTranslateProvider):
if r.status != 200:
logger.warning('TencentTranslateFree request failed: status=%d %s', r.status, r.reason)
return None
self._update_uc_key(r)
data = await r.json()
except (aiohttp.ClientConnectionError, asyncio.TimeoutError):
return None
@@ -325,15 +364,59 @@ class TencentTranslateFree(FlowControlTranslateProvider):
return None
return res
def _get_uc(self):
user_actions = self._gen_user_actions()
cur_timestamp = str(int(datetime.datetime.now().timestamp() * 1000))
server_time_delta = str(self._server_time_delta)
uc = '|'.join([user_actions, cur_timestamp, server_time_delta])
aes = cry_aes.new(self._uc_key.encode('utf-8'), cry_aes.MODE_CBC, self._uc_iv.encode('utf-8'))
uc = cry_pad.pad(uc.encode('utf-8'), aes.block_size, 'pkcs7')
uc = aes.encrypt(uc)
uc = base64.b64encode(uc).decode('utf-8')
return uc
@staticmethod
def _gen_user_actions():
# 1点击翻译2源输入框聚焦或失去焦点3点击源语言列表4点击交换语言5点击目标语言列表6源输入框输入、粘贴
user_actions = []
if random.randint(1, 5) == 1:
for i in range(random.randint(1, 2)):
user_actions.append('2')
user_actions.append('6')
for i in range(random.randint(0, 6)):
user_actions.append(random.choice('26'))
if random.randint(1, 5) == 1:
user_actions.append('1')
return ''.join(user_actions)
def _update_uc_key(self, r):
try:
hf_f = r.headers['f']
hf_ts = int(r.headers['ts'])
except (KeyError, ValueError):
return
cur_timestamp = int(datetime.datetime.now().timestamp() * 1000)
hf_f = base64.b64decode(hf_f.encode('utf-8')).decode('utf-8')
pos = int(hf_f[72: 72 + 4])
uc_key = hf_f[pos: pos + 16]
uc_iv = hf_f[pos + 16: pos + 16 + 16]
self._server_time_delta = cur_timestamp - hf_ts
self._uc_key = uc_key
self._uc_iv = uc_iv
def _on_fail(self):
self._fail_count += 1
# 目前没有测试出被ban的情况为了可靠性,连续失败20次时冷却直到下次重新init
if self._fail_count >= 20:
# 为了可靠性,连续失败10次时冷却直到下次重新init
if self._fail_count >= 10:
self._cool_down()
def _cool_down(self):
logger.info('TencentTranslateFree is cooling down')
# 下次_do_init后恢复
self._uc_key = self._uc_iv = ''
self._qtv = self._qtk = ''
self._fail_count = 0

View File

@@ -1,3 +1,4 @@
aiohttp==3.7.4
pycryptodome==3.10.1
sqlalchemy==1.3.13
tornado==6.0.2

View File

@@ -4,7 +4,7 @@ import asyncio
import aiohttp
VERSION = 'v1.5.2'
VERSION = 'v1.5.3'
def check_update():