美文网首页工作生活
趣消除App自动化 - 成语消消乐-全自动化

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

作者: 一吻江山 | 来源:发表于2019-07-02 09:38 被阅读0次

    [TOC]

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

    目标

    趣消除App自动化 - 成语消消乐-半自动化
    这篇文章实现了成语消消乐的半自动化:

    • 用户点击开始一局游戏
    • 代码自动答题
    • 对代码没有找到的成语,用户自己点击成语赢得游戏

    这篇文章的目的是全自动化:

    • 代码自动开始一局游戏
    • 代码自动答题
    • 对没有全部找到的,放弃这局:等待对方赢得游戏;
    • 开始下一局游戏

    写在前面:

    • 自己的文章从不介绍背景知识,直接上代码;因为定位实战,非教程
    • 看了些评论,多是不明觉厉,希望你可以评主题相关的讨论或感谢
    • 这篇文章威力巨大[呵呵],所以不要做恶;不要做恶;不要做恶

    不要做恶:
    比如游戏有12个成语要找,但代码只答对了11个,还有1个成语4个字,共有4*3*2*1=24种排列组合,请读者不要发24个请求来找到这最后一个成语,这是楼主认为的'做恶',也是对自动化:对没有全部找到的,放弃这局:等待对方赢得游戏做出的取舍;你可以用游戏里的认输提示

    写这篇文章与代码的目的:

    • 虚荣:有读者阅读、评论
    • 金钱:赢得游戏有几分钱
    • 时间:游戏里插了很广告,跳过广告;自动化节约自己时间
    • 能力:要写能用的代码,一定要学点什么,比如学习了websocket库
    • 爱惜:自己的手机用了3年了,移植到电脑上来执行,可以让手机再战一年啊
    • 成就:代码和文章等作品;不同维度地'虐人'的快感[鄙视]

    好了,希望你找到了学习的兴趣与动力,上代码

    测试环境

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

    解决

    分析

    成语消消乐有2个接口:

    1. https://king.hddgood.com/king_api/v1/game/join_game[http]
    2. wss://king.hddgood.com/websock_m/websock_message?uid={}&gameid={}&token={}[websocket]
    • game/join_game接口会返回websock_m/websock_message接口需要的gameidgameid每局都不同
    • uid对每个账号是固定
    • token对一次登入是固定,每局游戏都一样;
    • 游戏的消息来回传递都在websock_m/websock_message接口websocket协议里完成
    POST /king_api/v1/game/join_game HTTP/1.1
    Host: king.hddgood.com
    A-Token-Header: PTtWUFdWUkBFHEVZCVcNdUtVWwdc=
    Cookie: UM_distinctid=16b27e625da1ef-038c4847dc733-336d7451-4a640-16b27e625dd490; cn_1276022107_dplus=%7B%22distinct_id%22%3A%20%2216b27e625da1ef-038c4847dc733-336d7451-4a640-16b27e625dd490%22%2C%22%24_sessionid%22%3A%20104%2C%22%24_sessionTime%22%3A%201561087099%2C%22%24dp%22%3A%200%2C%22%24_sessionPVTime%22%3A%201561087099%2C%22initial_view_time%22%3A%20%221559738991%22%2C%22initial_referrer%22%3A%20%22%24direct%22%2C%22initial_referrer_domain%22%3A%20%22%24direct%22%2C%22%24recent_outside_referrer%22%3A%20%22%24direct%22%7D; CNZZDATA1276022107=326225286-1559738991-%7C1561086230
    
    uid=1457362&rank=11&type=G
    
    HTTP/1.1 200 
    Content-Type: application/json;charset=UTF-8
    Connection: close
    
    {"success":true,"msg":"操作成功","code":"200","codemsg":"操作成功","result":{"gameid":"G11-810737","dup":0,"starter":472251}}
    

    工作原理

    写2个文件:chengyu-auto.py[代码文件]、chengyu.text[数据文件]

    • chengyu-auto.pyasking消息里解析出ask_stringchengyu.text文件里查找是否包含相应的成语
    • 自动提交成语答案
    • chengyu.text文件刚开始是空的;在每局游戏结束时,游戏都会发送game_result消息给我们,里面有这局游戏的答案成语,把这些成语写到文件中
    • 玩的局数越多,chengyu.text文件包含的成语越多,查找到答案的可能性越大

    代码

    需要安装第三方python库:websockets
    chengyu-auto.py

    #!/usr/bin/env python3
    # coding=utf-8
    
    '''
    # 趣消除App-成语消消乐全自动化;
    # App版本:1.1.2
    # App地址:https://itunes.apple.com/cn/app/id1449545954
    提现非常迅速
    '''
    
    import re
    import time
    import datetime
    import random
    import json
    import sys
    import logging
    import collections
    import pathlib
    
    import requests
    
    
    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,
    }
    
    
    # 这些变量的值可以通过像Charles抓包软件获得
    # 账号变量
    # ------------------------------------------------
    # A_Token_Header的一些结论:
    # 1.每个账号不同;
    # 2.同一个账号每次登录时也是不一样的
    # 3.同一个账号,退出时,只要不登录,上次的A-Token-Header的值还有效,只有再登录时,上次的token值才失败
    A_Token_Header_zxg = 'PTtWUFdWUkBFHEVZCVcNdUtVWwdc'
    
    
    # Cookie的一些结论:
    # 1.同一个账号,退出或再登录,都不用修改,一直有效
    # 2.值为空也可以
    Cookie_zxg = ''
    
    # UUID的一些结论:
    # 1.固定不变
    UUID_zxg = '1457362'
    # ------------------------------------------------
    
    api_ = 'https://king.hddgood.com/king_api/v1/'
    
    
    class QuXiaoChuUser():
        headers = {
            'Host': 'king.hddgood.com',
            'Accept': 'application/json, text/plain, */*',
            'Accept-Language': 'zh-cn',
            'Origin': 'https://king.hddgood.com',
            'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16D57/; quxiaochu/ios v1.1.2',
            'Referer': 'https://king.hddgood.com/'
        }
    
        data = {
            'uid': '',
            'channel': '',
            'version': '1.1.2',
            'os': 'ios',
            'web_ver': '20190261'
        }
    
        SLEEP = 0.5
    
        def __init__(self, uid, token_header, cookie):
            self.uid = uid
            self.headers = dict(QuXiaoChuUser.headers)
            self.headers['A-Token-Header'] = token_header
            self.token_header = token_header
            self.headers['Cookie'] = cookie
    
        def game_chengyu_join_game(self, rank):
            '''
            成语消消乐-获取游戏id
            https://king.hddgood.com/king_api/v1/game/join_game
            {"success":true,"msg":"操作成功","code":"200","codemsg":"操作成功","result":{"gameid":"G15-3232777","dup":0,"starter":531492}}
            '''
            print("成语消消乐-获取游戏id {}".format(self.uid))
    
            data = self._uid_data()
            # 1:书童;2:儒生;15:殿阁大学士
            data['rank'] = str(rank) 
            data['type'] = 'G'
    
            api = self._genapi('game/join_game')
            result = self._post(api, self.headers, data) 
            return json.loads(result)       
    
        def _uid_data(self):
            return {'uid': self.uid}
    
        @staticmethod
        def _genapi(path):
            return 'https://king.hddgood.com/king_api/v1/' + path
    
        @staticmethod
        def _post(api, headers, data, p=logging.warning):
            time.sleep(QuXiaoChuUser.SLEEP)
    
            res = requests.post(api, headers=headers, data=data, verify=False)
            print(res.url)
            result = res.text
            print(result)
            print('')
            return result
    
    
    class Chengyu(object):
        def __init__(self):
            path = pathlib.PurePath(__file__)
            path = path.parent.joinpath('chengyu.text')
            self.dictpath = str(path) 
            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_answers = list()
            self.ack_true_answers = list()
    
            # 找到的的成语中各异字符为2个的答案数量:如 [真真假假] 
            self.answer_2chars_count = 0
    
            # {'中流砥柱':[1,9,21,25]}
            self.answer_indexs_dict = dict()
    
            # {'中流砥柱':set('中流砥柱')}
            self.answer_charset_dict = dict()
    
            # 查找到的错误答案
            self.error_answers = []
    
        # ---------------------------------------------
        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 ) * 1.5         
            for item in self.chengyu:
                item_set = self.answer_charset_dict.setdefault(item, set(item))
                if not (item_set - ask_set):
                    self.answers.append(item)
                    if len(item_set)<4:
                        self.answer_2chars_count += 1
                    if len(self.answers) - self.answer_2chars_count >= max_count :
                        self.count = len(self.answers)
                        return
            self.count = len(self.answers)
    
        async def auto_answer(self, flow):
            if len(self.answers):
                item = self.answers[0]
                answer_index = []
                counter = collections.Counter(item)
    
                for char, count in counter.items():
                    if self.char_indexs_dict[char]:
                        if len(self.char_indexs_dict[char]) < count:
                            self.error_answers.append(item)
                            self.answers.remove(item)
                            return
                    else:
                        pass
    
                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:
                        pass
                  
    
                if len(set(answer_index)) < 4:
                    print('算法有错误:{} 小于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
                # 向服务器发送消息
                self.auto_send_answers.append(item)
                self.answers.remove(item)
                await flow.send(mm)
                # time.sleep(0.5)
    
    
        def add_new_worlds_to_memory(self, m):
            '''
                把答案增加到内存字典中
            '''
            if len(self.ack_true_answers) < len(m['all_answer']):
                for answer in m['all_answer']:
                    self.chengyu.add(answer['phrase'])
    
            print('\033[1;31m 共收录{}个成语 \033[0m'.format(len(self.chengyu)))
    
        def add_new_worlds_to_file(self, m):
            '''
                把答案增加到文件中
            '''
            if len(self.ack_true_answers) < len(m['all_answer']):
                with open(self.dictpath, 'wt') as f:
                    l = list(self.chengyu)
                    l.sort()
                    for item in l:
                        f.write(item)
                        f.write('\n')
    
        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(len(self.auto_send_answers),self.auto_send_answers))
            self.print_color('已确认 {} 个提交:{}'.format(len(self.ack_true_answers),self.ack_true_answers))
            self.print_color('问题 {}'.format(self.ask_string))
            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.answer_2chars_count = 0
    
            self.answer_indexs_dict.clear()
            self.char_indexs_dict.clear()
            self.error_answers.clear()
            self.ack_true_answers.clear()
            self.auto_send_answers.clear()
    
    
    def chengyu_auto_answer(user: QuXiaoChuUser):
        '''
        成语消消乐自动答题
        wss://king.hddgood.com/websock_m/websock_message?uid=472251&gameid=G15-3232777&token=JSdLVVRRV0ZCH0INUlYNchcDUlc=
        '''
    
        result = user.game_chengyu_join_game(g_rank)
        if result['success']:
            gameid = result['result']['gameid']
            url = 'wss://king.hddgood.com/websock_m/websock_message?uid={}&gameid={}&token={}'
            url = url.format(user.uid, gameid, user.token_header)
            print(url)
    
            import asyncio
            import websockets
    
            async def chengyu():
                async with websockets.connect(url) as websocket:
                    print('连接成功')
                    global chengyu
                    live = True
                    count = 0
                    while live:
    
                        if count % 10 == 0:
                            keeplive = json.dumps({"type":"keepalive"})
                            await websocket.send(keeplive)
                            print('send keeplive')
    
                        # await asyncio.sleep(0.5)                    
                        count += 1
    
                        m = await websocket.recv()
                        print(f"\n{m}\n")
    
                        m = json.loads(m)
                        message_type = m['type']
                        if m.get('ask_string'):
                            chengyu.ask_string = m['ask_string']        
                            # 计算答案
                            chengyu.find_answers_v2(chengyu.ask_string)
    
                        if message_type == 'answer':
                            chengyu.answer_indexs_dict[m['answer']] = m['answer_index']
    
    
                        # 删除已回答正确的答案
                        if m.get('ack') == 1:
    
                            answer = m['answer']
                            chengyu.ack_true_answers.append(answer)
                            answer_index = chengyu.answer_indexs_dict.get(answer,[])
                            for i in answer_index:
                                chengyu.index_char_dict[int(i)] = '  '
                            try:
                                chengyu.answers.remove(m['answer'])
                            except:
                                pass
    
                        # 自动答题
                        await chengyu.auto_answer(websocket)
    
                        # 显示答案
                        if len(chengyu.ask_string):
                            chengyu.print_answers()
    
                        
                        if message_type == 'game_result':
                            live = False
                            # 把答案增加到内存字典中
                            chengyu.add_new_worlds_to_memory(m)
    
                            chengyu.add_new_worlds_to_file(m) 
    
                            chengyu.reset_data_to_init()
    
    
                            # 其它解析
                            for item in m['scores']:
                                if str(item['uid']) == user.uid:
                                    global g_rank
                                    g_rank = item['rank'] 
    
                            print('\033[1;31m 获得金币: {} Rank: {}\033[0m'.format(m['coin'], g_rank))
    
                    print('\033[1;31m 游戏结束 \033[0m')            
    
            asyncio.get_event_loop().run_until_complete(chengyu())
    
    
    def genUsers():
        yield QuXiaoChuUser(UUID_zxg, A_Token_Header_zxg, Cookie_zxg)
    
    g_rank = 15
    chengyu = Chengyu()
    
    if __name__ == "__main__":
    
        for user in genUsers():
    
            start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
            for _ in range(20):   
                chengyu_auto_answer(user)
                time.sleep(1)
            print('开始时间 ', start_time)
            print('结束时间 ', time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))      
    
    

    chengyu.text

    一劳永逸
    一掷千金
    一曝十寒
    一石二鸟
    一筹莫展
    一落千丈
    一衣带水
    一语破的
    ...
    

    注意:
    chengyu.textchengyu-auto.py放在同一目录下
    chengyu.text收集约1926个成语,98%能找到全部答案

    参考

    楼主的趣消除App系列文章

    1. 趣消除App自动化 - 签到and作战休息区
    2. 趣消除App自动化 - 成语消消乐-半自动化
    3. 趣消除App自动化 - 成语消消乐-全自动化

    相关文章

      网友评论

        本文标题:趣消除App自动化 - 成语消消乐-全自动化

        本文链接:https://www.haomeiwen.com/subject/vpbacctx.html