0%

趣消除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协议里完成
1
2
3
4
5
6
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
1
2
3
4
5
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
#!/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

1
2
3
4
5
6
7
8
9
一劳永逸
一掷千金
一曝十寒
一石二鸟
一筹莫展
一落千丈
一衣带水
一语破的
...

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

参考

楼主的趣消除App系列文章

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