Skip to content
This repository was archived by the owner on Sep 1, 2023. It is now read-only.

Commit

Permalink
🐛 fix fonttools problem
Browse files Browse the repository at this point in the history
  • Loading branch information
RF-Tar-Railt committed Nov 5, 2022
1 parent f7cc54f commit 9ac2fc6
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 126 deletions.
118 changes: 62 additions & 56 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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]
Expand All @@ -66,34 +69,36 @@ def searchXuexiaoyi(question: str) -> str:
answer_text = answer_plain_text

# 处理和替换答案文本
answer_text = (answer_text
.replace('正确答案:', '')
.replace('参考答案:', '')
.replace('答案:', '')
.replace('参考', '')
.replace('</p>', '')
.replace('<p>', '')
.replace('&nbsp;', '')
.replace('\n', '')
.replace('\r', '')
.strip(';;')
.replace('√', '正确')
.replace('×', '错误')
answer_text = (
answer_text.replace("正确答案:", "")
.replace("参考答案:", "")
.replace("答案:", "")
.replace("参考", "")
.replace("</p>", "")
.replace("<p>", "")
.replace("&nbsp;", "")
.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:
Expand All @@ -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"])
48 changes: 27 additions & 21 deletions cxsecret_font.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,63 +5,69 @@
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

# 康熙部首替换表
KX_RADICALS_TAB = str.maketrans(
# 康熙部首
"⼀⼁⼂⼃⼄⼅⼆⼇⼈⼉⼊⼋⼌⼍⼎⼏⼐⼑⼒⼓⼔⼕⼖⼗⼘⼙⼚⼛⼜⼝⼞⼟⼠⼡⼢⼣⼤⼥⼦⼧⼨⼩⼪⼫⼬⼭⼮⼯⼰⼱⼲⼳⼴⼵⼶⼷⼸⼹⼺⼻⼼⼽⼾⼿⽀⽁⽂⽃⽄⽅⽆⽇⽈⽉⽊⽋⽌⽍⽎⽏⽐⽑⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜⽝⽞⽟⽠⽡⽢⽣⽤⽥⽦⽧⽨⽩⽪⽫⽬⽭⽮⽯⽰⽱⽲⽳⽴⽵⽶⽷⽸⽹⽺⽻⽼⽽⽾⽿⾀⾁⾂⾃⾄⾅⾆⾇⾈⾉⾊⾋⾌⾍⾎⾏⾐⾑⾒⾓⾔⾕⾖⾗⾘⾙⾚⾛⾜⾝⾞⾟⾠⾡⾢⾣⾤⾥⾦⾧⾨⾩⾪⾫⾬⾭⾮⾯⾰⾱⾲⾳⾴⾵⾶⾷⾸⾹⾺⾻⾼髙⾽⾾⾿⿀⿁⿂⿃⿄⿅⿆⿇⿈⿉⿊⿋⿌⿍⿎⿏⿐⿑⿒⿓⿔⿕⺠⻬⻩⻢⻜⻅⺟⻓",
# 对应汉字
"一丨丶丿乙亅二亠人儿入八冂冖冫几凵刀力勹匕匚匸十卜卩厂厶又口囗土士夂夊夕大女子宀寸小尢尸屮山巛工己巾干幺广廴廾弋弓彐彡彳心戈戶手支攴文斗斤方无日曰月木欠止歹殳毋比毛氏气水火爪父爻爿片牙牛犬玄玉瓜瓦甘生用田疋疒癶白皮皿目矛矢石示禸禾穴立竹米糸缶网羊羽老而耒耳聿肉臣自至臼舌舛舟艮色艸虍虫血行衣襾見角言谷豆豕豸貝赤走足身車辛辰辵邑酉采里金長門阜隶隹雨青非面革韋韭音頁風飛食首香馬骨高高髟鬥鬯鬲鬼魚鳥鹵鹿麥麻黃黍黑黹黽鼎鼓鼠鼻齊齒龍龜龠民齐黄马飞见母长"
"一丨丶丿乙亅二亠人儿入八冂冖冫几凵刀力勹匕匚匸十卜卩厂厶又口囗土士夂夊夕大女子宀寸小尢尸屮山巛工己巾干幺广廴廾弋弓彐彡彳心戈戶手支攴文斗斤方无日曰月木欠止歹殳毋比毛氏气水火爪父爻爿片牙牛犬玄玉瓜瓦甘生用田疋疒癶白皮皿目矛矢石示禸禾穴立竹米糸缶网羊羽老而耒耳聿肉臣自至臼舌舛舟艮色艸虍虫血行衣襾見角言谷豆豕豸貝赤走足身車辛辰辵邑酉采里金長門阜隶隹雨青非面革韋韭音頁風飛食首香馬骨高高髟鬥鬯鬲鬼魚鳥鹵鹿麥麻黃黍黑黹黽鼎鼓鼠鼻齊齒龍龜龠民齐黄马飞见母长",
)


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:
Expand Down
97 changes: 48 additions & 49 deletions xuexiaoyi_API.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
print("题 --- ", q)
print("答 --- ", a)

0 comments on commit 9ac2fc6

Please sign in to comment.