diff --git a/app.py b/app.py index 02abf42..b45be10 100644 --- a/app.py +++ b/app.py @@ -13,17 +13,18 @@ app = Flask(__name__) -with open('config.yaml', 'r', encoding='utf8') as fp: +with open("config.yaml", "r", encoding="utf8") as fp: CONFIG = yaml.load(fp, yaml.FullLoader) -ENABLE_CACHE = CONFIG['enable_cache'] +ENABLE_CACHE = CONFIG["enable_cache"] + class CacheDAO: - def __init__(self, file: str='cache.json'): + def __init__(self, file: str = "cache.json"): self.cacheFile = Path(file) if not self.cacheFile.is_file(): - self.cacheFile.open('w').write('{}') - self.fp = self.cacheFile.open('r+', encoding='utf8') + self.cacheFile.open("w").write("{}") + self.fp = self.cacheFile.open("r+", encoding="utf8") def getCache(self, question: str) -> Optional[str]: self.fp.seek(0) @@ -38,26 +39,28 @@ def addCache(self, question: str, answer: str): self.fp.seek(0) json.dump(data, self.fp, ensure_ascii=False, indent=4) + cache = CacheDAO() -xxy = XxyWxAPI(open_id=CONFIG['xxy_open_id']) +xxy = XxyWxAPI(open_id=CONFIG["xxy_open_id"]) + def searchXuexiaoyi(question: str) -> str: xxy.search(question) - q_options, answer_plain_text = xxy.get() # 选项, 正确答案 + q_options, answer_plain_text = xxy.get() # 选项, 正确答案 # 处理答案是字母的情况 - if re.search(r'^[ ABCDEF]+$', answer_plain_text): + if re.search(r"^[ ABCDEF]+$", answer_plain_text): answer_text = [] for option in answer_plain_text: # 遍历并搜索选项 temp1 = q_options.split(option)[1] # 切分选项以提取正确答案 - for alpha in 'ABCDEF': - if (len(temp2 := temp1.rsplit(f'{alpha} ')) > 1) | (alpha == 'F'): - answer_text.append(temp2[0].strip('..、 ')) + for alpha in "ABCDEF": + if (len(temp2 := temp1.rsplit(f"{alpha} ")) > 1) | (alpha == "F"): + answer_text.append(temp2[0].strip("..、 ")) break # 多选题情况 选项之间补 '#' if len(answer_text) >= 1: - answer_text = '#'.join(answer_text) + answer_text = "#".join(answer_text) # 单选题情况 else: answer_text = answer_text[0] @@ -66,34 +69,36 @@ def searchXuexiaoyi(question: str) -> str: answer_text = answer_plain_text # 处理和替换答案文本 - answer_text = (answer_text - .replace('正确答案:', '') - .replace('参考答案:', '') - .replace('答案:', '') - .replace('参考', '') - .replace('

', '') - .replace('

', '') - .replace(' ', '') - .replace('\n', '') - .replace('\r', '') - .strip(';;') - .replace('√', '正确') - .replace('×', '错误') + answer_text = ( + answer_text.replace("正确答案:", "") + .replace("参考答案:", "") + .replace("答案:", "") + .replace("参考", "") + .replace("

", "") + .replace("

", "") + .replace(" ", "") + .replace("\n", "") + .replace("\r", "") + .strip(";;") + .replace("√", "正确") + .replace("×", "错误") ) return answer_text -@app.route('/v1/cx', methods=('POST',)) + +@app.route("/v1/cx", methods=("POST",)) def searchView(): try: # 过滤请求问题 - question = (request.form.get('question') - .replace('题型说明:请输入题型说明', '') - .replace('【单选题】', '') - .replace('【判断题】', '') - .strip('\x0a\x09') - .strip('') + question = ( + request.form.get("question") + .replace("题型说明:请输入题型说明", "") + .replace("【单选题】", "") + .replace("【判断题】", "") + .strip("\x0a\x09") + .strip("") ) - + # 题库缓存处理 hit = False if ENABLE_CACHE: @@ -106,33 +111,34 @@ def searchView(): else: answer = searchXuexiaoyi(question) # 进行搜题 - print(f'{Fore.BLUE}题目: {question}{Fore.RESET}') - print(f"{Fore.GREEN + '命中答案' if hit else Fore.YELLOW + '搜索答案'}: {answer}{Fore.RESET}") - + print(f"{Fore.BLUE}题目: {question}{Fore.RESET}") + print( + f"{Fore.GREEN + '命中答案' if hit else Fore.YELLOW + '搜索答案'}: {answer}{Fore.RESET}" + ) + return { - 'code': 1, - 'messsage': '', - 'data': answer, - 'hit': hit, + "code": 1, + "messsage": "", + "data": answer, + "hit": hit, } except Exception as err: traceback.print_exc() - return { - 'code': -1, - 'messsage': err.__str__(), - 'data': '服务器酱被玩坏了耶!' - } + return {"code": -1, "messsage": err.__str__(), "data": "服务器酱被玩坏了耶!"} + -@app.route('/decrypt', methods=('POST',)) +@app.route("/decrypt", methods=("POST",)) def decryptView(): args = json.loads(request.data) - key_font_b64 = args['secFont'] - dst_text = args['dstText'] - font_hashmap = cxsecret_font.font2map(key_font_b64) # 创建加密字体hash map - src_text = cxsecret_font.decrypt(font_hashmap, dst_text) # 解密目标文本 - print(f'{Fore.GREEN}解密成功{Fore.RESET}: {Fore.YELLOW}{dst_text}{Fore.RESET} -> {Fore.GREEN}{src_text}{Fore.RESET}') - return { - 'srcText': src_text - } - -app.run(CONFIG['host'], CONFIG['port']) + key_font_b64 = args["secFont"] + dst_text = args["dstText"] + font_hashmap = cxsecret_font.font2map(key_font_b64) # 创建加密字体hash map + src_text = cxsecret_font.decrypt(font_hashmap, dst_text) # 解密目标文本 + print( + f"{Fore.GREEN}解密成功{Fore.RESET}: {Fore.YELLOW}{dst_text}{Fore.RESET} -> {Fore.GREEN}{src_text}{Fore.RESET}" + ) + return {"srcText": src_text} + + +if __name__ == '__main__': + app.run(CONFIG["host"], CONFIG["port"]) diff --git a/cxsecret_font.py b/cxsecret_font.py index e0a305c..fa60fe2 100644 --- a/cxsecret_font.py +++ b/cxsecret_font.py @@ -5,7 +5,7 @@ from pathlib import Path from typing import IO, Union, Dict -from fontTools.ttLib.tables._g_l_y_f import Glyph +from fontTools.ttLib.tables._g_l_y_f import Glyph, table__g_l_y_f from fontTools.ttLib.ttFont import TTFont # 康熙部首替换表 @@ -13,55 +13,61 @@ # 康熙部首 "⼀⼁⼂⼃⼄⼅⼆⼇⼈⼉⼊⼋⼌⼍⼎⼏⼐⼑⼒⼓⼔⼕⼖⼗⼘⼙⼚⼛⼜⼝⼞⼟⼠⼡⼢⼣⼤⼥⼦⼧⼨⼩⼪⼫⼬⼭⼮⼯⼰⼱⼲⼳⼴⼵⼶⼷⼸⼹⼺⼻⼼⼽⼾⼿⽀⽁⽂⽃⽄⽅⽆⽇⽈⽉⽊⽋⽌⽍⽎⽏⽐⽑⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜⽝⽞⽟⽠⽡⽢⽣⽤⽥⽦⽧⽨⽩⽪⽫⽬⽭⽮⽯⽰⽱⽲⽳⽴⽵⽶⽷⽸⽹⽺⽻⽼⽽⽾⽿⾀⾁⾂⾃⾄⾅⾆⾇⾈⾉⾊⾋⾌⾍⾎⾏⾐⾑⾒⾓⾔⾕⾖⾗⾘⾙⾚⾛⾜⾝⾞⾟⾠⾡⾢⾣⾤⾥⾦⾧⾨⾩⾪⾫⾬⾭⾮⾯⾰⾱⾲⾳⾴⾵⾶⾷⾸⾹⾺⾻⾼髙⾽⾾⾿⿀⿁⿂⿃⿄⿅⿆⿇⿈⿉⿊⿋⿌⿍⿎⿏⿐⿑⿒⿓⿔⿕⺠⻬⻩⻢⻜⻅⺟⻓", # 对应汉字 - "一丨丶丿乙亅二亠人儿入八冂冖冫几凵刀力勹匕匚匸十卜卩厂厶又口囗土士夂夊夕大女子宀寸小尢尸屮山巛工己巾干幺广廴廾弋弓彐彡彳心戈戶手支攴文斗斤方无日曰月木欠止歹殳毋比毛氏气水火爪父爻爿片牙牛犬玄玉瓜瓦甘生用田疋疒癶白皮皿目矛矢石示禸禾穴立竹米糸缶网羊羽老而耒耳聿肉臣自至臼舌舛舟艮色艸虍虫血行衣襾見角言谷豆豕豸貝赤走足身車辛辰辵邑酉采里金長門阜隶隹雨青非面革韋韭音頁風飛食首香馬骨高高髟鬥鬯鬲鬼魚鳥鹵鹿麥麻黃黍黑黹黽鼎鼓鼠鼻齊齒龍龜龠民齐黄马飞见母长" + "一丨丶丿乙亅二亠人儿入八冂冖冫几凵刀力勹匕匚匸十卜卩厂厶又口囗土士夂夊夕大女子宀寸小尢尸屮山巛工己巾干幺广廴廾弋弓彐彡彳心戈戶手支攴文斗斤方无日曰月木欠止歹殳毋比毛氏气水火爪父爻爿片牙牛犬玄玉瓜瓦甘生用田疋疒癶白皮皿目矛矢石示禸禾穴立竹米糸缶网羊羽老而耒耳聿肉臣自至臼舌舛舟艮色艸虍虫血行衣襾見角言谷豆豕豸貝赤走足身車辛辰辵邑酉采里金長門阜隶隹雨青非面革韋韭音頁風飛食首香馬骨高高髟鬥鬯鬲鬼魚鳥鹵鹿麥麻黃黍黑黹黽鼎鼓鼠鼻齊齒龍龜龠民齐黄马飞见母长", ) + class FontHashDAO: - '原始字体hashmap DAO' - char_map: Dict[str, str] # unicode -> hsah - hash_map: Dict[str, str] # hash -> unicode - - def __init__(self, file: str='font_map.json'): - with open(file, 'r') as fp: + """原始字体hashmap DAO""" + char_map: Dict[str, str] # unicode -> hsah + hash_map: Dict[str, str] # hash -> unicode + + def __init__(self, file: str = "font_map.json"): + with open(file, "r") as fp: _map: dict = json.load(fp) self.char_map = _map self.hash_map = dict(zip(_map.values(), _map.keys())) def find_char(self, font_hash: str) -> str: - '以hash查内码' + """以hash查内码""" return self.hash_map.get(font_hash) - + def find_hash(self, char: str) -> str: - '以内码查hash' + """以内码查hash""" return self.char_map.get(char) + fonthash_dao = FontHashDAO() + def hash_glyph(glyph: Glyph) -> str: - 'ttf字形曲线转hash算法实现' - pos_bin = '' + """ttf字形曲线转hash算法实现""" + pos_bin = "" last = 0 for i in range(glyph.numberOfContours): for j in range(last, glyph.endPtsOfContours[i] + 1): - pos_bin += f'{glyph.coordinates[j][0]}{glyph.coordinates[j][1]}{glyph.flags[j] & 0x01}' + pos_bin += f"{glyph.coordinates[j][0]}{glyph.coordinates[j][1]}{glyph.flags[j] & 0x01}" last = glyph.endPtsOfContours[i] + 1 return hashlib.md5(pos_bin.encode()).hexdigest() + def font2map(file: Union[IO, Path, str]) -> Dict[str, str]: - '以加密字体计算hashMap' + """以加密字体计算hashMap""" font_hashmap = {} if isinstance(file, str): file = BytesIO(base64.b64decode(file[47:])) - with TTFont(file) as fontFile: - for code, glyph in dict(fontFile.getGlyphSet()).items(): - font_hashmap[code] = hash_glyph(glyph._glyph) + with TTFont(file, lazy=False) as fontFile: + table: table__g_l_y_f = fontFile["glyf"] + for name in table.glyphOrder: + font_hashmap[name] = hash_glyph(table.glyphs[name]) return font_hashmap + def decrypt(dststr_fontmap: Dict[str, str], dst_str: str) -> str: - '解码字体解密' - ori_str = '' + """解码字体解密""" + ori_str = "" for char in dst_str: - if (dstchar_hash := dststr_fontmap.get(f'uni{ord(char):X}')): + if dstchar_hash := dststr_fontmap.get(f"uni{ord(char):X}"): # 存在于“密钥”字体,解密 orichar_hash = fonthash_dao.find_char(dstchar_hash) if orichar_hash is not None: diff --git a/xuexiaoyi_API.py b/xuexiaoyi_API.py index 6f6429f..2fbb57d 100644 --- a/xuexiaoyi_API.py +++ b/xuexiaoyi_API.py @@ -2,83 +2,82 @@ import requests -WXAPI_SEARCH = 'https://xxy.51xuexiaoyi.com/el/wx/sou/search' -WXAPI_TOKEN = 'https://xxy.51xuexiaoyi.com/el/wx/app/code2session' -WXAPI_UA = 'Mozilla/5.0 (Linux; Android 12; M2102K1C Build/SKQ1.211006.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/86.0.4240.99 XWEB/4317 MMWEBSDK/20220903 Mobile Safari/537.36 MMWEBID/6294 MicroMessenger/8.0.28.2240(0x28001C35) WeChat/arm64 Weixin NetType/5G Language/zh_CN ABI/arm64 MiniProgramEnv/android' +WXAPI_SEARCH = "https://xxy.51xuexiaoyi.com/el/wx/sou/search" +WXAPI_TOKEN = "https://xxy.51xuexiaoyi.com/el/wx/app/code2session" +WXAPI_UA = "Mozilla/5.0 (Linux; Android 12; M2102K1C Build/SKQ1.211006.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/86.0.4240.99 XWEB/4317 MMWEBSDK/20220903 Mobile Safari/537.36 MMWEBID/6294 MicroMessenger/8.0.28.2240(0x28001C35) WeChat/arm64 Weixin NetType/5G Language/zh_CN ABI/arm64 MiniProgramEnv/android" + class APIError(Exception): def __init__(self, code, msg) -> None: self.code = code self.msg = msg super().__init__() - + def __str__(self) -> str: - return f'{self.code}:{self.msg}' + return f"{self.code}:{self.msg}" + class XxyWxAPI: - '学小易-微信小程序API调用' + """学小易-微信小程序API调用""" session: requests.Session items: List[dict] open_id: str - - def __init__(self, open_id: str='') -> None: + + def __init__(self, open_id: str = "") -> None: self.session = requests.Session() self.open_id = open_id - self.session.headers.update({ - 'User-Agent': WXAPI_UA, - 'Referer': 'https://servicewechat.com/wx7436885f6e1ba040/6/page-frame.html' - }) - - def code2session(self, code: str) -> bool: - '获取/刷新session' - resp = self.session.get(WXAPI_TOKEN, - params={ - 'mp_id': 1, - 'js_code': code - }, - headers={ - 'wx-open-id': self.open_id, - 'Content-Type': 'application/json' + self.session.headers.update( + { + "User-Agent": WXAPI_UA, + "Referer": "https://servicewechat.com/wx7436885f6e1ba040/6/page-frame.html", } ) + + def code2session(self, code: str) -> bool: + """获取/刷新session""" + resp = self.session.get( + WXAPI_TOKEN, + params={"mp_id": 1, "js_code": code}, + headers={"wx-open-id": self.open_id, "Content-Type": "application/json"}, + ) resp.raise_for_status() resp_json = resp.json() - code = resp_json['err_no'] + code = resp_json["err_no"] if code != 0: - raise APIError(code, resp_json['err_tips']) - if d := resp_json.get('data'): - self.open_id = d['open_id'] + raise APIError(code, resp_json["err_tips"]) + if d := resp_json.get("data"): + self.open_id = d["open_id"] return True return False - + def search(self, question: str) -> bool: - '搜题' - resp = self.session.post(WXAPI_SEARCH, - headers={ - 'wx-open-id': self.open_id - }, - json={ - 'query': question, - 'channel': 1 - } + """搜题""" + resp = self.session.post( + WXAPI_SEARCH, + headers={"wx-open-id": self.open_id}, + json={"query": question, "channel": 1}, ) resp.raise_for_status() resp_json = resp.json() - code = resp_json['err_no'] + code = resp_json["err_no"] if code != 0: - raise APIError(code, resp_json['err_tips']) - self.items = resp_json['data']['result']['items'] + raise APIError(code, resp_json["err_tips"]) + self.items = resp_json["data"]["result"]["items"] return len(self.items) >= 1 - def get(self, index: int=0) -> tuple[str, str]: - '获取搜题结果' + def get(self, index: int = 0) -> tuple[str, str]: + """获取搜题结果""" question_info = self.items[index] - return question_info['question_answer']['question_plain_text'], question_info['question_answer']['answer_plain_text'] - -if __name__ == '__main__': - patten = '国防是阶级斗争的产物,它伴随着()的形成而产生。' - xxy = XxyWxAPI('oKtmq5YGlp26rm6eL-aRKew1ZRHs') + return ( + question_info["question_answer"]["question_plain_text"], + question_info["question_answer"]["answer_plain_text"], + ) + + +if __name__ == "__main__": + patten = "国防是阶级斗争的产物,它伴随着()的形成而产生。" + xxy = XxyWxAPI("oKtmq5YGlp26rm6eL-aRKew1ZRHs") xxy.search(patten) q, a = xxy.get(0) - print('题 --- ', q) - print('答 --- ', a) \ No newline at end of file + print("题 --- ", q) + print("答 --- ", a)