From b00ae596ef12d03abf72d35ff76ca6dadb5a1595 Mon Sep 17 00:00:00 2001 From: John Smith Date: Mon, 29 Jul 2024 22:40:41 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E8=85=BE=E8=AE=AF=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E7=99=BD=E5=AB=96=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 12 ++- data/config.example.ini | 18 +--- services/translate.py | 216 +--------------------------------------- 3 files changed, 10 insertions(+), 236 deletions(-) diff --git a/config.py b/config.py index 817f4ea..9f7b4c5 100644 --- a/config.py +++ b/config.py @@ -146,11 +146,13 @@ class AppConfig: 'type': type_, 'query_interval': section.getfloat('query_interval'), } - if type_ == 'TencentTranslateFree': - translator_config['source_language'] = section['source_language'] - translator_config['target_language'] = section['target_language'] - elif type_ == 'BilibiliTranslateFree': - pass + if type_ in ('TencentTranslateFree', 'BilibiliTranslateFree'): + doc_url = ( + 'https://github.com/xfgryujk/blivechat/wiki/%E9%85%8D%E7%BD%AE%E5%AE%98%E6%96%B9' + '%E7%BF%BB%E8%AF%91%E6%8E%A5%E5%8F%A3' + ) + logger.warning('%s is deprecated, please see %s', type_, doc_url) + continue elif type_ == 'TencentTranslate': translator_config['source_language'] = section['source_language'] translator_config['target_language'] = section['target_language'] diff --git a/data/config.example.ini b/data/config.example.ini index aa55142..9e9cd46 100644 --- a/data/config.example.ini +++ b/data/config.example.ini @@ -76,22 +76,8 @@ open_live_app_id = 0 # 翻译器配置,索引到下面的配置节。可以以逗号分隔配置多个翻译器,翻译时会自动负载均衡 # 配置多个翻译器可以增加额度、增加QPS、容灾 # 不同配置可以使用同一个类型,但要使用不同的账号,否则还是会遇到额度、调用频率限制 -translator_configs = tencent_translate_free - - -[tencent_translate_free] -# 类型:腾讯翻译白嫖版。使用了网页版的接口,**将来可能失效** -type = TencentTranslateFree - -# 请求间隔时间(秒),等于 1 / QPS -query_interval = 1 - -# 自动:auto;中文:zh;日语:jp;英语:en;韩语:kr -# 完整语言列表见文档:https://cloud.tencent.com/document/product/551/15619 -# 源语言 -source_language = zh -# 目标语言 -target_language = jp +# Example: translator_configs = tencent_translate,baidu_translate +translator_configs = [tencent_translate] diff --git a/services/translate.py b/services/translate.py index a7ce041..bce0d0c 100644 --- a/services/translate.py +++ b/services/translate.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- import asyncio -import base64 import dataclasses import datetime import enum @@ -10,7 +9,6 @@ import hmac import json import logging import random -import re from typing import * import Crypto.Cipher.AES as cry_aes # noqa @@ -71,11 +69,7 @@ async def _do_init(): def create_translate_provider(cfg): type_ = cfg['type'] - if type_ == 'TencentTranslateFree': - return TencentTranslateFree( - cfg['query_interval'], cfg['source_language'], cfg['target_language'] - ) - elif type_ == 'TencentTranslate': + if type_ == 'TencentTranslate': return TencentTranslate( cfg['query_interval'], cfg['source_language'], cfg['target_language'], cfg['secret_id'], cfg['secret_key'], cfg['region'] @@ -301,214 +295,6 @@ class TranslateProvider: raise NotImplementedError -class TencentTranslateFree(TranslateProvider): - def __init__(self, query_interval, source_language, target_language): - super().__init__(query_interval) - self._be_available_event.clear() # _do_init之后才可用 - self._source_language = source_language - self._target_language = target_language - - self._server_time_delta = 0 - self._uc_key = self._uc_iv = '' - self._qtv = self._qtk = '' - self._reinit_future = None - - # 连续失败的次数 - self._fail_count = 0 - - async def init(self): - if not await super().init(): - return False - self._reinit_future = asyncio.create_task(self._reinit_coroutine()) - return True - - async def _do_init(self): - try: - async with utils.request.http_session.get('https://fanyi.qq.com/') as r: - if r.status != 200: - logger.warning('TencentTranslateFree init request failed: status=%d %s', r.status, r.reason) - return False - html = await r.text() - - 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.ClientError, 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 utils.request.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', - reauthuri, r.status, r.reason) - return False - data = await r.json() - except (aiohttp.ClientError, asyncio.TimeoutError): - logger.exception('TencentTranslateFree init error:') - return False - - qtv = data.get('qtv', None) - if qtv is None: - logger.warning('TencentTranslateFree init failed: qtv not found') - return False - qtk = data.get('qtk', None) - if qtk is None: - 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 - - self._on_availability_change() - return True - - async def _reinit_coroutine(self): - while True: - logger.debug('TencentTranslateFree reinit') - start_time = datetime.datetime.now() - try: - await self._do_init() - except Exception: # noqa - pass - cost_time = (datetime.datetime.now() - start_time).total_seconds() - - await asyncio.sleep(30 - cost_time) - - @property - def is_available(self): - return '' not in (self._uc_key, self._uc_iv, self._qtv, self._qtk) and super().is_available - - async def _translate_wrapper(self, task: TranslateTask) -> Optional[str]: - res = await super()._translate_wrapper(task) - if res is not None: - self._fail_count = 0 - else: - self._on_fail() - return res - - async def _do_translate(self, text) -> Optional[str]: - try: - async with utils.request.http_session.post( - 'https://fanyi.qq.com/api/translate', - headers={ - 'Referer': 'https://fanyi.qq.com/', - 'uc': self._get_uc() - }, - data={ - 'source': self._source_language, - 'target': self._target_language, - 'sourceText': text, - 'qtv': self._qtv, - 'qtk': self._qtk - } - ) as r: - 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.ClientError, asyncio.TimeoutError): - return None - if data['errCode'] != 0: - logger.warning('TencentTranslateFree failed: %d %s', data['errCode'], data['errMsg']) - return None - res = ''.join(record['targetText'] for record in data['translate']['records']) - if res == '' and text.strip() != '': - # qtv、qtk过期 - logger.info('TencentTranslateFree result is empty %s', data) - 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 - # 为了可靠性,连续失败5次时冷却直到下次重新init - if self._fail_count >= 5: - 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 - - self._on_availability_change() - - class TencentTranslate(TranslateProvider): def __init__(self, query_interval, source_language, target_language, secret_id, secret_key, region):