[TOC]
目标
做为一个程序员,已成中年油腻大叔
了?那怎么行,来趣消除App
、扶我起来学数学App
的成语消消乐
游戏battle下
这里没有作弊一说,只有发挥自己的特长。做为已毕业十几年的人,肯定不是成语容量,而是工作专业技能 -- 上代码
测试环境
App: 趣消除App
iOS版本、扶我起来学数学App
版本
工具: mitmproxy、python、Charles
背景知识:mitmproxy、python、抓包
解决
分析
游戏界面上呈现的:
网络呈现:
游戏是通过websocket协议来传输json格式的字符串,举asking
消息如下:
{
"ask_string": "步不之来疏笔口平去重学青字斟浅之论暗明尊易伐道不云句来刊酌诛才师",
"type": "asking",
"scores": [
{
"nick": "吕耀辉",
....
},
{
"nick": "xxxx",
......
}
]
}
成语答案:answer
消息
{
"answer": "口诛笔伐",
"answer_index": [
"6",
"29",
"5",
"21"
],
"type": "answer"
}
目的是把上面的"ask_string": "步不之来疏笔口平去重学青字斟浅之论暗明尊易伐道不云句来刊酌诛才师"
解析为一个个成语
工作原理
写2个文件:chengyu_mitm.py
[代码文件]、chengyu.text
[数据文件]
-
chengyu_mitm.py
从asking
消息里解析出ask_string
到chengyu.text
文件里查找是否包含相应的成语 - 如果包含成语,图形化、格式化显示结果
-
chengyu.text
文件刚开始是空的;在每局游戏结束时,游戏都会发送game_result
消息给我们,里面有这局游戏的答案成语,把这些成语写到文件中 - 玩的局数越多,
chengyu.text
文件包含的成语越多,查找到答案的可能性越大
所以我们只要关注:asking
消息、game_result
消息
如果要程序回复答案,可以关注下answer
消息[客户端发给服务器的]
代码
chengyu_mitm.py
:
import json
import time
from mitmproxy import ctx
'''
> mitmdump -s chengyu_mitm.py '~u websock_m/websock_message'
'''
Red = '\033[0;31m'
Green = '\033[0;32m'
Yellow = '\033[0;33m'
Blue = '\033[0;34m'
Purple = '\033[0;35m'
Cyan = '\033[0;36m'
White = '\033[0;37m'
colors = {
0:Red,
1:Purple,
2:Yellow,
3:Blue,
4:White,
}
class Chengyu(object):
def __init__(self):
self.dictpath = '/Users/zhoujie/chengyu.text'
self.chengyu = set()
with open(self.dictpath, 'rt') as f:
for line in f.readlines():
self.chengyu.add(line.strip())
self.answers = list()
self.ask_string = ''
# {'和':[1,8], '我':[11]}
self.char_indexs_dict = dict()
# {1:'和', 8:'和', 11:'我'}
self.index_char_dict = dict()
self.count = 0
# 自动提交答案的网络发送次数
self.auto_send_count = 0
# 找到的的成语中各异字符为2个的答案数量:如 [真真假假]
self.answer_2chars_count = 0
# {'中流砥柱':[1,9,21,25]}
self.answer_indexs_dict = dict()
#
self.error_answers = []
# 玩了多少局
self.play_times = 0
# General lifecycle
def load(self, loader):
ctx.log.info('\033[1;31mevent: load\033[0m')
def configure(self, updated):
ctx.log.info('\033[1;31mevent: configure\033[0m')
# Websocket lifecycle
def websocket_message(self, flow):
"""
Called when a WebSocket message is received from the client or
server. The most recent message will be flow.messages[-1]. The
message is user-modifiable. Currently there are two types of
messages, corresponding to the BINARY and TEXT frame types.
"""
ctx.log.info('\033[1;31m websocket_message \033[0m')
# get the latest message
message = flow.messages[-1]
# simply print the content of the message
ctx.log.info('')
ctx.log.info(message.content)
ctx.log.info('')
m = json.loads(message.content)
t = m['type']
if m.get('ask_string'):
ask_string = m['ask_string']
self.ask_string = ask_string
# 计算答案
self.find_answers_v2(ask_string)
self.play_times += 1
if m['type'] == 'answer':
self.answer_indexs_dict[m['answer']] = m['answer_index']
# 删除已回答正确的答案
if m.get('ack') == 1:
answer = m['answer']
answer_index = self.answer_indexs_dict.get(answer,[])
for i in answer_index:
self.index_char_dict[int(i)] = ' '
try:
self.answers.remove(m['answer'])
except:
pass
# 自动答题
self.auto_answer(flow)
# 显示答案
self.print_answers()
if m['type'] == 'game_result':
# 把答案增加到内存字典中
self.__add_new_worlds_to_memory(m)
self.reset_data_to_init()
def websocket_end(self, flow):
"""
A websocket connection has ended.
"""
ctx.log.info('\033[1;31m websocket_end \033[0m')
self.reset_data_to_init()
if self.play_times % 5 == 0:
with open(self.dictpath, 'wt') as f:
l = list(self.chengyu)
l.sort()
for item in l:
f.write(item)
f.write('\n')
# ---------------------------------------------
def find_answers_v2(self, ask_string):
'''
在内存成语字典查找答案
'''
ask_set = set(ask_string)
for i, c in enumerate(ask_string):
self.char_indexs_dict.setdefault(c, []).append(i)
self.index_char_dict = dict( zip(range(len(ask_string)), ask_string))
max_count = len(ask_string) / 4
for item in self.chengyu:
item_set = set(item)
if not (item_set - ask_set):
self.answers.append(item)
if len(self.answers) - self.answer_2chars_count >= max_count :
self.count = len(self.answers)
return
self.count = len(self.answers)
def auto_answer(self, flow):
if len(self.answers):
item = self.answers[0]
answer_index = []
for c in item:
if self.char_indexs_dict[c]:
index = self.char_indexs_dict[c][0]
answer_index.append( str(index) )
del self.char_indexs_dict[c][0]
else:
'''
这个答案是错误的
'''
self.error_answers.append(item)
self.answers.remove(item)
return
ask_string = self.ask_string
if len(set(answer_index)) < 4:
ctx.log.error('算法有错误:{} 小于4'.format(answer_index))
send_message = {
'answer': item,
'answer_index': answer_index,
'type': 'answer'
}
mm = json.dumps(send_message)
# -----------------------
print(mm)
# -----------------------
self.answer_indexs_dict[item] = answer_index
# 向服务器发送消息
if not flow.ended and not flow.error:
self.auto_send_count += 1
self.answers.remove(item)
flow.inject_message(flow.server_conn, mm)
time.sleep(0.5)
def __add_new_worlds_to_memory(self, m):
'''
把答案增加到内存字典中
'''
for answer in m['all_answer']:
self.chengyu.add(answer['phrase'])
ctx.log.info('\033[1;31m 共收录{}个成语 \033[0m'.format(len(self.chengyu)))
def print_answers(self):
'''
图形化、色彩化显示答案
'''
self.print_color('共找到 {}/{} 个成语'.format(self.count, len(self.ask_string)//4))
self.print_color('错误成语 {}'.format(self.error_answers))
self.print_color('共自动 {} 次提交'.format(self.auto_send_count))
for item in self.answers:
self.print_color(item)
self.print_matrix(item)
if (not self.answers) and self.index_char_dict:
self.print_matrix()
def print_matrix(self, item = []):
chars_in_line = 6
length = len(self.ask_string)
lines = (length + chars_in_line - 1) // chars_in_line
PADDING = ' '*(lines * chars_in_line - length)
is_need_padding = len(PADDING) != 0
self.print_color('--'*chars_in_line)
global colors, White
for i, c in self.index_char_dict.items():
end = ''
if (i+1) % chars_in_line == 0 or (i+1) == length:
end = '\n'
color = White
if c in item:
color = colors[item.index(c)]
line, first = divmod(i, chars_in_line)
if is_need_padding and first == 0 and (line + 1 == lines):
c = PADDING + c
self.print_color(c, end=end, color=color)
self.print_color('--'*chars_in_line)
def print_color(self, message, end='\n', color=Red):
print('{}{}\033[0m'.format(color, message), end=end)
def reset_data_to_init(self):
self.ask_string = ''
self.answers.clear()
self.index_char_dict.clear()
self.count = 0
self.auto_send_count = 0
self.answer_2chars_count = 0
self.answer_indexs_dict.clear()
self.char_indexs_dict.clear()
self.error_answers.clear()
addons = [
Chengyu()
]
# if __name__ == "__main__":
# c = Chengyu()
# ask_string = '腊见家义降德若功赎仁判悲生升道肘两身乐极尽立罪春命明回人捉襟性暗'
# c.ask_string = ask_string
# c.find_answers_v2(ask_string)
# c.print_answers()
chengyu.text
一劳永逸
一掷千金
一曝十寒
一石二鸟
一筹莫展
一落千丈
一衣带水
一语破的
注意:self.dictpath = '/Users/xxx/chengyu.text'
一定要修改成你自己的chengyu.text所在路径
运行效果
参考
- WebSocket 教程
- https://kaazing.com/demos/
- https://github.com/mitmproxy/mitmproxy/blob/master/examples/simple/websocket_messages.py
- https://github.com/mitmproxy/mitmproxy/blob/master/examples/complex/websocket_inject_message.py