趣消除App自動化 - 成語消消樂-全自動化

[TOC]

趣消除App自動化 - 成語消消樂-全自動化

目標(biāo)

趣消除App自動化 - 成語消消樂-半自動化
這篇文章實(shí)現(xiàn)了成語消消樂的半自動化:

  • 用戶點(diǎn)擊開始一局游戲
  • 代碼自動答題
  • 對代碼沒有找到的成語,用戶自己點(diǎn)擊成語贏得游戲

這篇文章的目的是全自動化:

  • 代碼自動開始一局游戲
  • 代碼自動答題
  • 對沒有全部找到的,放棄這局:等待對方贏得游戲;
  • 開始下一局游戲

寫在前面:

  • 自己的文章從不介紹背景知識,直接上代碼;因?yàn)槎ㄎ粚?shí)戰(zhàn),非教程
  • 看了些評論,多是不明覺厲,希望你可以評主題相關(guān)的討論或感謝
  • 這篇文章威力巨大[呵呵],所以不要做惡;不要做惡;不要做惡

不要做惡:
比如游戲有12個(gè)成語要找,但代碼只答對了11個(gè),還有1個(gè)成語4個(gè)字,共有4*3*2*1=24種排列組合,請讀者不要發(fā)24個(gè)請求來找到這最后一個(gè)成語,這是樓主認(rèn)為的'做惡',也是對自動化:對沒有全部找到的,放棄這局:等待對方贏得游戲做出的取舍;你可以用游戲里的認(rèn)輸提示

寫這篇文章與代碼的目的:

  • 虛榮:有讀者閱讀、評論
  • 金錢:贏得游戲有幾分錢
  • 時(shí)間:游戲里插了很廣告,跳過廣告;自動化節(jié)約自己時(shí)間
  • 能力:要寫能用的代碼,一定要學(xué)點(diǎn)什么,比如學(xué)習(xí)了websocket庫
  • 愛惜:自己的手機(jī)用了3年了,移植到電腦上來執(zhí)行,可以讓手機(jī)再戰(zhàn)一年啊
  • 成就:代碼和文章等作品;不同維度地'虐人'的快感[鄙視]

好了,希望你找到了學(xué)習(xí)的興趣與動力,上代碼

測試環(huán)境

App: 趣消除AppiOS版本、扶我起來學(xué)數(shù)學(xué)AppiOS版本
工具: python、Charles、python第三方庫websocket
背景知識:python、抓包、websocket

解決

分析

成語消消樂有2個(gè)接口:

  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接口需要的gameid;gameid每局都不同
  • uid對每個(gè)賬號是固定
  • token對一次登入是固定,每局游戲都一樣;
  • 游戲的消息來回傳遞都在websock_m/websock_message接口websocket協(xié)議里完成
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個(gè)文件:chengyu-auto.py[代碼文件]、chengyu.text[數(shù)據(jù)文件]

  • chengyu-auto.pyasking消息里解析出ask_stringchengyu.text文件里查找是否包含相應(yīng)的成語
  • 自動提交成語答案
  • chengyu.text文件剛開始是空的;在每局游戲結(jié)束時(shí),游戲都會發(fā)送game_result消息給我們,里面有這局游戲的答案成語,把這些成語寫到文件中
  • 玩的局?jǐn)?shù)越多,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
提現(xiàn)非常迅速
'''

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的一些結(jié)論:
# 1.每個(gè)賬號不同;
# 2.同一個(gè)賬號每次登錄時(shí)也是不一樣的
# 3.同一個(gè)賬號,退出時(shí),只要不登錄,上次的A-Token-Header的值還有效,只有再登錄時(shí),上次的token值才失敗
A_Token_Header_zxg = 'PTtWUFdWUkBFHEVZCVcNdUtVWwdc'


# Cookie的一些結(jié)論:
# 1.同一個(gè)賬號,退出或再登錄,都不用修改,一直有效
# 2.值為空也可以
Cookie_zxg = ''

# UUID的一些結(jié)論:
# 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:殿閣大學(xué)士
        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

        # 自動提交答案的網(wǎng)絡(luò)發(fā)送次數(shù)
        self.auto_send_answers = list()
        self.ack_true_answers = list()

        # 找到的的成語中各異字符為2個(gè)的答案數(shù)量:如 [真真假假] 
        self.answer_2chars_count = 0

        # {'中流砥柱':[1,9,21,25]}
        self.answer_indexs_dict = dict()

        # {'中流砥柱':set('中流砥柱')}
        self.answer_charset_dict = dict()

        # 查找到的錯(cuò)誤答案
        self.error_answers = []

    # ---------------------------------------------
    def find_answers_v2(self, ask_string):
        '''
            在內(nèi)存成語字典查找答案
        '''      
        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('算法有錯(cuò)誤:{} 小于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
            # 向服務(wù)器發(fā)送消息
            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):
        '''
            把答案增加到內(nèi)存字典中
        '''
        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 共收錄{}個(gè)成語 \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('共找到 {}/{} 個(gè)成語'.format(self.count, len(self.ask_string)//4))
        self.print_color('錯(cuò)誤成語 {}'.format(self.error_answers))
        self.print_color('共自動 {} 次提交:{}'.format(len(self.auto_send_answers),self.auto_send_answers))
        self.print_color('已確認(rèn) {} 個(gè)提交:{}'.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']        
                        # 計(jì)算答案
                        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
                        # 把答案增加到內(nèi)存字典中
                        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 游戲結(jié)束 \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('開始時(shí)間 ', start_time)
        print('結(jié)束時(shí)間 ', time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))      

chengyu.text

一勞永逸
一擲千金
一曝十寒
一石二鳥
一籌莫展
一落千丈
一衣帶水
一語破的
...

注意:
chengyu.textchengyu-auto.py放在同一目錄下
chengyu.text收集約1926個(gè)成語,98%能找到全部答案

參考

樓主的趣消除App系列文章

  1. 趣消除App自動化 - 簽到and作戰(zhàn)休息區(qū)
  2. 趣消除App自動化 - 成語消消樂-半自動化
  3. 趣消除App自動化 - 成語消消樂-全自動化
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容