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

[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%能找到全部答案

参考

  • WebSocket 教程
  • https://pypi.org/project/websockets/

楼主的趣消除App系列文章

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

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