趣消除App自动化 - 成语消消乐-半自动化

[TOC]

目标

做为一个程序员,已成中年油腻大叔了?那怎么行,来趣消除App扶我起来学数学App成语消消乐游戏battle下

这里没有作弊一说,只有发挥自己的特长。做为已毕业十几年的人,肯定不是成语容量,而是工作专业技能 -- 上代码

测试环境

App: 趣消除AppiOS版本、扶我起来学数学App版本
工具: mitmproxy、python、Charles
背景知识:mitmproxy、python、抓包

解决

分析

游戏界面上呈现的:


趣消除App自动化 - 成语消消乐-半自动化_第1张图片
image

网络呈现:
游戏是通过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.pyasking消息里解析出ask_stringchengyu.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所在路径

运行效果

趣消除App自动化 - 成语消消乐-半自动化_第2张图片
image

参考

  • 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

你可能感兴趣的:(趣消除App自动化 - 成语消消乐-半自动化)