forked from fzls/djc_helper
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdjc_helper.py
12320 lines (10325 loc) · 518 KB
/
djc_helper.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
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
from __future__ import annotations
import datetime
import functools
import json
import math
import os
import random
import re
import string
import time
import uuid
from multiprocessing import Pool
from typing import Any, Callable
from urllib import parse
from urllib.parse import parse_qsl, quote, quote_plus, unquote_plus, urlparse
import requests
import json_parser
from black_list import check_in_black_list
from config import AccountConfig, CommonConfig, XinYueOperationConfig, config, load_config
from const import appVersion, cached_dir, guanjia_skey_version, sVersionName, vscode_online_url
from dao import (
XIN_YUE_MIN_LEVEL,
AmesvrCommonModRet,
AmesvrQueryFriendsInfo,
AmesvrQueryRole,
AmesvrUserBindInfo,
BuyInfo,
ColgBattlePassInfo,
ColgYearlySigninInfo,
DnfChronicleMatchServerAddUserRequest,
DnfChronicleMatchServerCommonResponse,
DnfChronicleMatchServerRequestUserRequest,
DnfChronicleMatchServerRequestUserResponse,
DnfCollectionInfo,
DnfHeiyaInfo,
DnfHelperChronicleBasicAwardInfo,
DnfHelperChronicleBasicAwardList,
DnfHelperChronicleBindInfo,
DnfHelperChronicleExchangeGiftInfo,
DnfHelperChronicleExchangeList,
DnfHelperChronicleLotteryList,
DnfHelperChronicleSignGiftInfo,
DnfHelperChronicleSignList,
DnfHelperChronicleUserActivityTopInfo,
DnfHelperChronicleUserTaskList,
DnfRoleInfo,
GameRoleInfo,
GoodsInfo,
GuanjiaNewLotteryResult,
GuanjiaNewQueryLotteryInfo,
GuanjiaNewRequest,
HuyaActTaskInfo,
HuyaUserTaskInfo,
IdeActInfo,
LuckyUserInfo,
LuckyUserTaskConf,
MobileGameGiftInfo,
MoJieRenInfo,
MyHomeFarmInfo,
MyHomeFriendDetail,
MyHomeFriendList,
MyHomeGift,
MyHomeGiftList,
MyHomeInfo,
MyHomeValueGift,
NewArkLotteryAgreeRequestCardResult,
NewArkLotteryCardCountInfo,
NewArkLotteryLotteryCountInfo,
NewArkLotteryRequestCardResult,
NewArkLotterySendCardResult,
RankUserInfo,
RoleInfo,
SailiyamWorkInfo,
SpringFuDaiInfo,
TemporaryChangeBindRoleInfo,
VoteEndWorkInfo,
VoteEndWorkList,
XiaojiangyouInfo,
XiaojiangyouPackageInfo,
XinyueCatInfo,
XinyueCatInfoFromApp,
XinyueCatMatchResult,
XinyueCatUserInfo,
XinyueFinancingInfo,
XinYueInfo,
XinYueMatchServerAddTeamRequest,
XinYueMatchServerCommonResponse,
XinYueMatchServerRequestTeamRequest,
XinYueMatchServerRequestTeamResponse,
XinYueTeamAwardInfo,
XinYueTeamGroupInfo,
XinYueTeamInfo,
XinYueTeamMember,
XinyueWeeklyGiftInfo,
XinyueWeeklyGPointsInfo,
parse_amesvr_common_info,
)
from data_struct import to_raw_type
from db import (
CacheInfo,
DianzanDB,
DnfHelperChronicleExchangeListDB,
DnfHelperChronicleUserActivityTopInfoDB,
FireCrackersDB,
WelfareDB,
)
from encrypt import make_dnf_helper_signature, make_dnf_helper_signature_data
from exceptions_def import (
ArkLotteryTargetQQSendByRequestReachMaxCount,
DnfHelperChronicleTokenExpiredOrWrongException,
GithubActionLoginException,
SameAccountTryLoginAtMultipleThreadsException,
)
from first_run import is_daily_first_run, is_first_run, is_monthly_first_run, is_weekly_first_run, reset_first_run
from game_info import get_game_info, get_game_info_by_bizcode
from log import color, logger
from network import Network, extract_qq_video_message, jsonp_callback_flag
from qq_login import LoginResult, QQLogin
from qzone_activity import QzoneActivity
from server import get_match_server_api
from setting import dnf_server_id_to_area_info, dnf_server_id_to_name, parse_card_group_info_map, zzconfig
from sign import getACSRFTokenForAMS, getMillSecondsUnix
from urls import (
Urls,
get_act_url,
get_ams_act,
get_ams_act_desc,
get_ide_act,
get_ide_act_desc,
get_not_ams_act,
get_not_ams_act_desc,
not_know_end_time____,
search_act,
)
from usage_count import increase_counter
from util import (
async_message_box,
base64_str,
double_quote,
extract_between,
filter_unused_params_catch_exception,
format_now,
format_time,
get_last_week_monday_datetime,
get_logger_func,
get_meaningful_call_point_for_log,
get_month,
get_now,
get_now_unix,
get_this_week_monday_datetime,
get_today,
get_week,
is_act_expired,
json_compact,
md5,
message_box,
now_after,
now_in_range,
padLeftRight,
parse_time,
parse_url_param,
pause,
pause_and_exit,
post_json_to_data,
range_from_one,
remove_suffix,
show_end_time,
show_head_line,
show_quick_edit_mode_tip,
start_and_end_date_of_a_month,
tableify,
triple_quote,
try_except,
uin2qq,
use_by_myself,
utf8len,
wait_for,
will_act_expired_in,
with_cache,
with_retry,
)
# DNF蚊子腿小助手
class DjcHelper:
local_saved_skey_file = os.path.join(cached_dir, ".saved_skey.{}.json")
local_saved_pskey_file = os.path.join(cached_dir, ".saved_pskey.{}.json")
local_saved_guanjia_openid_file = os.path.join(cached_dir, ".saved_guanjia_openid.{}.json")
local_saved_teamid_file = os.path.join(cached_dir, ".teamid_new.{}.json")
def __init__(self, account_config, common_config, user_buy_info: BuyInfo | None = None):
self.cfg: AccountConfig = account_config
self.common_cfg: CommonConfig = common_config
self.zzconfig = zzconfig()
# 初始化部分字段
self.lr: LoginResult | None = None
# 配置加载后,尝试读取本地缓存的skey
self.local_load_uin_skey()
# 初始化网络相关设置
self.init_network()
# 相关链接
self.urls = Urls()
self.user_buy_info = user_buy_info
# --------------------------------------------一些辅助函数--------------------------------------------
def init_network(self):
self.network = Network(self.cfg.sDeviceID, self.uin(), self.cfg.account_info.skey, self.common_cfg)
def check_skey_expired(self, window_index=1):
# 这个是查询心悦战场信息的接口,应该比较稳定
# 未过期: {"flowRet": ..., "ret": "0", "msg": ""}
# 已过期: {"ret": "101", "msg": "非常抱歉,请先登录!", "flowRet": ...}
xinyue_info_result = self.xinyue_battle_ground_op("判断skey是否过期", "767160", print_res=False)
if str(xinyue_info_result["ret"]) == "0":
# skey尚未过期,则重新刷一遍,主要用于从qq空间获取的情况
account_info = self.cfg.account_info
self.save_uin_skey(account_info.uin, account_info.skey, self.get_vuserid())
else:
# 已过期,更新skey
logger.info("")
logger.warning(f"账号({self.cfg.name})的skey已过期,即将尝试更新skey")
self.update_skey(window_index=window_index)
# skey获取完毕后,检查是否在黑名单内
check_in_black_list(self.uin())
def update_skey(self, window_index=1):
if self.cfg.function_switches.disable_login_mode_normal:
logger.warning("禁用了普通登录模式,将不会尝试更新skey")
return
login_mode_dict: dict[str, Callable[[int], None]] = {
"by_hand": self.update_skey_by_hand,
"qr_login": self.update_skey_qr_login,
"auto_login": self.update_skey_auto_login,
}
login_mode_dict[self.cfg.login_mode](window_index)
def update_skey_by_hand(self, window_index=1):
js_code = """cookies=Object.fromEntries(document.cookie.split(/; */).map(cookie => cookie.split('=', 2)));console.log("uin="+cookies.uin+"\\nskey="+cookies.skey+"\\n");"""
fallback_js_code = """document.cookie.split(/; */);"""
logger.error(
"skey过期,请按下列步骤获取最新skey并更新到配置中\n"
"1. 在本脚本自动打开的活动网页中使用通用登录组件完成登录操作\n"
" 1.1 指点击(亲爱的玩家,请【登录】)中的登录按钮,并完成后续登录操作\n"
"2. 点击F12,将默认打开DevTools(开发者工具界面)的Console界面\n"
" 如果默认不是该界面,则点击上方第二个tab(Console)(中文版这个tab的名称可能是命令行?)\n"
"3. 在下方输入区输入下列内容来从cookie中获取uin和skey(或者直接粘贴,默认已复制到系统剪贴板里了)\n"
f" {js_code}\n"
"-- 如果上述代码执行报错,可能是因为浏览器不支持,这时候可以复制下面的代码进行上述操作\n"
" 执行后,应该会显示一个可点开的内容,戳一下会显示各个cookie的内容,然后手动在里面查找uin和skey即可\n"
f" {fallback_js_code}\n"
"3. 将uin/skey的值分别填写到config.toml中对应配置的值中即可\n"
"4. 填写dnf的区服和手游的区服信息到config.toml中\n"
"5. 正常使用还需要填写完成后再次运行脚本,获得角色相关信息,并将信息填入到config.toml中\n"
"\n"
f"具体信息为可见前面的日志"
)
# 打开配置界面
cfgFile = "./config.toml"
localCfgFile = "./config.toml.local"
if os.path.isfile(localCfgFile):
cfgFile = localCfgFile
async_message_box(f"请使用网页版vscode或者下载个本地版的vscode打开【{cfgFile}】文件 第53行 来自行修改~", "提示", open_url=vscode_online_url)
# # 复制js代码到剪贴板,方便复制
# pyperclip.copy(js_code)
# 打开活动界面
os.popen("start https://dnf.qq.com/lbact/a20200716wgmhz/index.html?wg_ad_from=loginfloatad")
# 提示
input("\n完成上述操作后点击回车键即可退出程序,重新运行即可...")
pause_and_exit(-1)
def update_skey_qr_login(self, window_index=1):
qqLogin = QQLogin(self.common_cfg, window_index=window_index)
loginResult = qqLogin.qr_login(
QQLogin.login_mode_normal, name=self.cfg.name, account=self.cfg.account_info.account
)
self.save_uin_skey(loginResult.uin, loginResult.skey, loginResult.vuserid)
def update_skey_auto_login(self, window_index=1):
qqLogin = QQLogin(self.common_cfg, window_index=window_index)
ai = self.cfg.account_info
loginResult = qqLogin.login(ai.account, ai.password, QQLogin.login_mode_normal, name=self.cfg.name)
self.save_uin_skey(loginResult.uin, loginResult.skey, loginResult.vuserid)
def save_uin_skey(self, uin, skey, vuserid):
self.memory_save_uin_skey(uin, skey)
self.local_save_uin_skey(uin, skey, vuserid)
def local_save_uin_skey(self, uin, skey, vuserid):
# 本地缓存
self.set_vuserid(vuserid)
with open(self.get_local_saved_skey_file(), "w", encoding="utf-8") as sf:
loginResult = {
"uin": str(uin),
"skey": str(skey),
"vuserid": str(vuserid),
}
json.dump(loginResult, sf)
logger.debug(f"本地保存skey信息,具体内容如下:{loginResult}")
logger.debug("同时由于在pskey的缓存中也有一份skey数据, 去读取过来更新这部分字段,确保两边最终一致")
cached_pskey = self.load_uin_pskey()
if cached_pskey is not None:
self.save_uin_pskey(
cached_pskey["p_uin"],
cached_pskey["p_skey"],
skey,
vuserid,
)
def local_load_uin_skey(self):
# 仅二维码登录和自动登录模式需要尝试在本地获取缓存的信息
if self.cfg.login_mode not in ["qr_login", "auto_login"]:
return
# 若未有缓存文件,则跳过
if not os.path.isfile(self.get_local_saved_skey_file()):
return
with open(self.get_local_saved_skey_file(), encoding="utf-8") as f:
try:
loginResult = json.load(f)
except json.decoder.JSONDecodeError:
logger.error(f"账号 {self.cfg.name} 的skey缓存已损坏,将视为已过期")
return
self.memory_save_uin_skey(loginResult["uin"], loginResult["skey"])
self.set_vuserid(loginResult.get("vuserid", ""))
logger.debug(f"读取本地缓存的skey信息,具体内容如下:{loginResult}")
def get_local_saved_skey_file(self):
return self.local_saved_skey_file.format(self.cfg.name)
def memory_save_uin_skey(self, uin, skey):
# 保存到内存中
self.cfg.updateUinSkey(uin, skey)
# uin, skey更新后重新初始化网络相关
self.init_network()
def set_vuserid(self, vuserid: str):
self.vuserid = vuserid
def get_vuserid(self) -> str:
return getattr(self, "vuserid", "")
# --------------------------------------------获取角色信息和游戏信息--------------------------------------------
@with_retry(max_retry_count=3)
def get_bind_role_list(self, print_warning=True):
self.fetch_djc_login_info("获取绑定角色列表", print_warning)
# 查询全部绑定角色信息
res = self.get(
"获取道聚城各游戏的绑定角色列表", self.urls.query_bind_role_list, print_res=False, use_this_cookies=self.djc_custom_cookies
)
roleinfo_list = res.get("data", [])
db = CacheInfo()
db.with_context(f"绑定角色缓存/{self.cfg.get_account_cache_key()}").load()
if len(roleinfo_list) != 0:
# 成功请求时,保存一份数据到本地
db.value = roleinfo_list
db.save()
else:
get_logger_func(print_warning)("获取绑定角色失败了,尝试使用本地缓存的角色信息")
logger.debug(f"缓存的信息为 {db.value}")
roleinfo_list = db.value or []
self.bizcode_2_bind_role_map = {}
for roleinfo_dict in roleinfo_list:
role_info = GameRoleInfo().auto_update_config(roleinfo_dict)
self.bizcode_2_bind_role_map[role_info.sBizCode] = role_info
def get_dnf_bind_role_copy(self) -> RoleInfo:
return self.get_dnf_bind_role().clone()
def get_dnf_bind_role(self) -> RoleInfo | None:
roleinfo = None
if self.cfg.bind_role.has_config():
# 如果配置了绑定角色,则优先使用这个
server_id, role_id = self.cfg.bind_role.dnf_server_id, self.cfg.bind_role.dnf_role_id
role_info_from_web = self.query_dnf_role_info_by_serverid_and_roleid(server_id, role_id)
server_name = dnf_server_id_to_name(server_id)
area_info = dnf_server_id_to_area_info(server_id)
# 构造一份道聚城绑定角色信息,简化改动
djc_role_info = RoleInfo()
djc_role_info.roleCode = role_id
djc_role_info.roleName = role_info_from_web.rolename
djc_role_info.serviceID = server_id
djc_role_info.serviceName = server_name
djc_role_info.areaID = area_info.v
djc_role_info.areaName = area_info.t
logger.debug("使用本地配置的角色来领奖")
roleinfo = djc_role_info
else:
# 否则尝试使用道聚城绑定的角色信息
game_name = "dnf"
if game_name in self.bizcode_2_bind_role_map:
roleinfo = self.bizcode_2_bind_role_map[game_name].sRoleInfo
return roleinfo
def get_mobile_game_info(self):
# 如果游戏名称设置为【任意手游】,则从绑定的手游中随便挑一个
if self.cfg.mobile_game_role_info.use_any_binded_mobile_game():
found_binded_game = False
for _bizcode, bind_role_info in self.bizcode_2_bind_role_map.items():
if bind_role_info.is_mobile_game():
self.cfg.mobile_game_role_info.game_name = bind_role_info.sRoleInfo.gameName
found_binded_game = True
logger.warning(f"当前手游名称配置为任意手游,将从道聚城已绑定的手游中随便选一个,挑选为:{self.cfg.mobile_game_role_info.game_name}")
break
if not found_binded_game:
return None
return get_game_info(self.cfg.mobile_game_role_info.game_name)
# --------------------------------------------各种操作--------------------------------------------
def run(self, user_buy_info: BuyInfo):
self.normal_run(user_buy_info)
# 预处理阶段
def check_djc_role_binding(self) -> bool:
# 指引获取uin/skey/角色信息等
self.check_skey_expired()
# 尝试获取绑定的角色信息
self.get_bind_role_list()
# 检查绑定信息
binded = True
if self.cfg.function_switches.get_djc:
# 检查道聚城是否已绑定dnf角色信息,若未绑定则警告(这里不停止运行是因为可以不配置领取dnf的道具)
if not self.cfg.cannot_bind_dnf_v2 and "dnf" not in self.bizcode_2_bind_role_map:
logger.warning(color("fg_bold_yellow") + "未在道聚城绑定【地下城与勇士】的角色信息,请前往道聚城app进行绑定")
binded = False
# if self.cfg.mobile_game_role_info.enabled() and not self.check_mobile_game_bind():
# logger.warning(color("fg_bold_green") + "!!!请注意,我说的是手游,不是DNF!!!")
# logger.warning(color("fg_bold_green") + "如果不需要做道聚城的手游任务和许愿任务(不做会少豆子),可以在配置工具里将手游名称设为无")
# binded = False
if binded:
if self.cfg.function_switches.get_djc:
# 打印dnf和手游的绑定角色信息
logger.info("已获取道聚城目前绑定的角色信息如下")
games = []
if "dnf" in self.bizcode_2_bind_role_map:
games.append("dnf")
# if self.cfg.mobile_game_role_info.enabled():
# games.append(self.get_mobile_game_info().bizCode)
for bizcode in games:
roleinfo = self.bizcode_2_bind_role_map[bizcode].sRoleInfo
logger.info(
f"{roleinfo.gameName}: ({roleinfo.serviceName}-{roleinfo.roleName}-{roleinfo.roleCode})"
)
else:
logger.warning("当前账号未启用道聚城相关功能")
if self.cfg.bind_role.has_config():
# 若本地配置了领奖角色,则强制认为已绑定
binded = True
return binded
def check_mobile_game_bind(self):
# 检查配置的手游是否有效
gameinfo = self.get_mobile_game_info()
if gameinfo is None:
logger.warning(color("fg_bold_yellow") + "当前手游名称配置为【任意手游】,但未在道聚城找到任何绑定的手游,请前往道聚城绑定任意一个手游,如王者荣耀")
return False
# 检查道聚城是否已绑定该手游的角色,若未绑定则警告并停止运行
bizcode = gameinfo.bizCode
if bizcode not in self.bizcode_2_bind_role_map:
logger.warning(
color("fg_bold_yellow") + f"未在道聚城绑定手游【{get_game_info_by_bizcode(bizcode).bizName}】的角色信息,请前往道聚城app进行绑定。"
)
logger.warning(
color("fg_bold_cyan") + "若想绑定其他手游则调整【配置工具】配置的手游名称," + color("fg_bold_blue") + "若不启用则将手游名称调整为无"
)
return False
# 检查这个游戏是否是手游
role_info = self.bizcode_2_bind_role_map[bizcode]
if not role_info.is_mobile_game():
logger.warning(color("fg_bold_yellow") + f"【{get_game_info_by_bizcode(bizcode).bizName}】是端游,不是手游。")
logger.warning(
color("fg_bold_cyan") + "若想绑定其他手游则调整【配置工具】配置的手游名称," + color("fg_bold_blue") + "若不启用则将手游名称调整为无"
)
return False
return True
# 正式运行阶段
def normal_run(self, user_buy_info: BuyInfo):
# 检查skey是否过期
self.check_skey_expired()
# 获取dnf和手游的绑定信息
self.get_bind_role_list()
# 运行活动
activity_funcs_to_run = self.get_activity_funcs_to_run(user_buy_info)
for _act_name, activity_func in activity_funcs_to_run:
activity_func()
# # 以下为并行执行各个活动的调用方式
# # 由于下列原因,该方式基本确定不会再使用
# # 1. amesvr活动服务器会限制调用频率,如果短时间内请求过快,会返回401,并提示请求过快
# # 而多进程处理活动的时候,会非常频繁的触发这种情况,感觉收益不大。另外频繁触发这个警报,感觉也有可能会被腾讯风控,到时候就得不偿失了
# # 2. python的multiprocessing.pool.Pool不支持在子进程中再创建新的子进程
# # 因此在不同账号已经在不同的进程下运行的前提下,子进程下不能再创建新的子进程了
# async_run_all_act(self.cfg, self.common_cfg, activity_funcs_to_run)
def get_activity_funcs_to_run(self, user_buy_info: BuyInfo) -> list[tuple[str, Callable]]:
activity_funcs_to_run = []
activity_funcs_to_run.extend(self.free_activities())
if user_buy_info.is_active():
# 付费期间将付费活动也加入到执行列表中
activity_funcs_to_run.extend(self.payed_activities())
return activity_funcs_to_run
@try_except(show_exception_info=False)
def show_activities_summary(self, user_buy_info: BuyInfo):
# 需要运行的活动
free_activities = self.free_activities()
paied_activities = self.payed_activities()
# 展示活动的信息
def get_activities_summary(categray: str, activities: list) -> str:
activities_summary = ""
if len(activities) != 0:
activities_summary += f"\n目前的{categray}活动如下:"
heads = ["序号", "活动名称", "结束于", "剩余天数", "活动链接为"]
colSizes = [4, 24, 12, 8, 50]
activities_summary += "\n" + color("bold_green") + tableify(heads, colSizes)
for idx, name_and_func in enumerate(activities):
act_name, act_func = name_and_func
op_func_name = act_func.__name__ + "_op"
end_time = parse_time(not_know_end_time____)
# 可能是非ams活动
act_info = None
try:
act_info = get_not_ams_act(act_name)
if act_info is None and hasattr(self, op_func_name):
# 可能是ams或ide活动
act_info = getattr(self, op_func_name)("获取活动信息", "", get_act_info_only=True)
except Exception as e:
logger.debug(f"请求{act_name} 出错了", exc_info=e)
if act_info is not None:
end_time = parse_time(act_info.get_endtime())
line_color = "bold_green"
if is_act_expired(format_time(end_time)):
line_color = "bold_black"
end_time_str = format_time(end_time, "%Y-%m-%d")
remaining_days = (end_time - get_now()).days
print_act_name = padLeftRight(act_name, colSizes[1], mode="left", need_truncate=True)
act_url = padLeftRight(get_act_url(act_name), colSizes[-1], mode="left")
# activities_summary += with_color(line_color, f'\n {idx + 1:2d}. {print_act_name} 将结束于{end_time_str}(剩余 {remaining_days:3d} 天),活动链接为: {act_url}')
activities_summary += (
"\n"
+ color(line_color)
+ tableify(
[idx + 1, print_act_name, end_time_str, remaining_days, act_url],
colSizes,
need_truncate=False,
)
)
else:
activities_summary += f"\n目前尚无{categray}活动,当新的{categray}活动出现时会及时加入~"
return activities_summary
# 提示如何复制
if self.common_cfg.disable_cmd_quick_edit:
show_quick_edit_mode_tip()
# 免费活动信息
free_activities_summary = get_activities_summary("长期免费", free_activities)
show_head_line("以下为免费的长期活动", msg_color=color("bold_cyan"))
logger.info(free_activities_summary)
# 付费活动信息
paied_activities_summary = get_activities_summary("短期付费", paied_activities)
show_head_line("以下为付费期间才会运行的短期活动", msg_color=color("bold_cyan"))
if not user_buy_info.is_active():
if user_buy_info.total_buy_month != 0:
msg = f"账号{user_buy_info.qq}的付费内容已到期,到期时间点为{user_buy_info.expire_at}。"
else:
msg = f"账号{user_buy_info.qq}未购买付费内容。"
msg += "\n因此2021-02-06之后添加的短期新活动将被跳过,如果想要启用该部分内容,可查看目录中的【付费指引/付费指引.docx】,目前定价为5元每月。"
msg += "\n2021-02-06之前添加的所有活动不受影响,仍可继续使用。"
msg += "\n具体受影响的活动内容如下"
logger.warning(color("bold_yellow") + msg)
logger.info(paied_activities_summary)
def free_activities(self) -> list[tuple[str, Callable]]:
return [
("道聚城", self.djc_operations),
("DNF地下城与勇士心悦特权专区", self.xinyue_battle_ground),
("心悦app", self.xinyue_app_operations),
("黑钻礼包", self.get_heizuan_gift),
("腾讯游戏信用礼包", self.get_credit_xinyue_gift),
("心悦app理财礼卡", self.xinyue_financing),
("dnf论坛签到", self.dnf_bbs),
("小酱油周礼包和生日礼包", self.xiaojiangyou),
]
def payed_activities(self) -> list[tuple[str, Callable]]:
# re: 更新新的活动时记得更新urls.py的not_ams_activities
# ? NOTE: 同时顺带更新 配置工具功能开关列表 act_category_to_act_desc_switch_list
return [
("DNF助手编年史", self.dnf_helper_chronicle),
("DNF福利中心兑换", self.dnf_welfare),
("DNF心悦wpe", self.dnf_xinyue_wpe),
("DNF落地页活动", self.dnf_luodiye),
("colg每日签到", self.colg_signin),
("巴卡尔对战地图", self.dnf_bakaer_map_ide),
("dnf助手活动", self.dnf_helper),
("冒险的起点", self.maoxian_start),
("colg年终盛典签到", self.colg_yearly_signin),
]
def expired_activities(self) -> list[tuple[str, Callable]]:
# re: 记得过期活动全部添加完后,一个个确认下确实过期了
return [
("巴卡尔大作战", self.dnf_bakaer_fight),
("魔界人探险记", self.mojieren),
("qq视频蚊子腿-爱玩", self.qq_video_iwan),
("WeGame活动", self.dnf_wegame),
("DNF集合站", self.dnf_collection),
("超级会员", self.dnf_super_vip),
("集卡", self.dnf_ark_lottery),
("dnf助手活动Dup", self.dnf_helper_dup),
("DNF马杰洛的规划", self.majieluo),
("心悦app周礼包", self.xinyue_weekly_gift),
("DNF闪光杯", self.dnf_shanguang),
("DNF娱乐赛", self.dnf_game),
("DNF冒险家之路", self.dnf_maoxian_road),
("超享玩", self.super_core),
("我的小屋", self.dnf_my_home),
("勇士的冒险补给", self.maoxian),
("DNF集合站_ide", self.dnf_collection_ide),
("幸运勇士", self.dnf_lucky_user),
("会员关怀", self.dnf_vip_mentor),
("KOL", self.dnf_kol),
("黄钻", self.dnf_yellow_diamond),
("心悦猫咪", self.xinyue_cat),
("DNF心悦", self.dnf_xinyue),
("DNF周年庆登录活动", self.dnf_anniversary),
("DNF互动站", self.dnf_interactive),
("DNF格斗大赛", self.dnf_pk),
("DNF共创投票", self.dnf_dianzan),
("DNF漫画预约活动", self.dnf_comic),
("翻牌活动", self.dnf_card_flip),
("hello语音(皮皮蟹)网页礼包兑换", self.hello_voice),
("管家蚊子腿", self.guanjia_new),
("组队拜年", self.team_happy_new_year),
("新职业预约活动", self.dnf_reserve),
("WeGame活动_新版", self.wegame_new),
("DNF公会活动", self.dnf_gonghui),
("关怀活动", self.dnf_guanhuai),
("DNF记忆", self.dnf_memory),
("DNF预约", self.dnf_reservation),
("DNF名人堂", self.dnf_vote),
("qq视频蚊子腿", self.qq_video),
("WeGameDup", self.dnf_wegame_dup),
("轻松之路", self.dnf_relax_road),
("命运的抉择挑战赛", self.dnf_mingyun_jueze),
("管家蚊子腿", self.guanjia_new_dup),
("虎牙", self.huya),
("wegame国庆活动【秋风送爽关怀常伴】", self.wegame_guoqing),
("微信签到", self.wx_checkin),
("10月女法师三觉", self.dnf_female_mage_awaken),
("dnf助手排行榜", self.dnf_rank),
("2020DNF嘉年华页面主页面签到", self.dnf_carnival),
("DNF进击吧赛利亚", self.xinyue_sailiyam),
("阿拉德勇士征集令", self.dnf_warriors_call),
("dnf漂流瓶", self.dnf_drift),
("暖冬好礼活动", self.warm_winter),
("史诗之路来袭活动合集", self.dnf_1224),
("新春福袋大作战", self.spring_fudai),
("燃放爆竹活动", self.firecrackers),
("DNF福签大作战", self.dnf_fuqian),
("会员关怀", self.vip_mentor),
("DNF强者之路", self.dnf_strong),
("管家蚊子腿", self.guanjia),
("DNF十三周年庆活动", self.dnf_13),
("DNF奥兹玛竞速", self.dnf_ozma),
("我的dnf13周年活动", self.dnf_my_story),
("集卡_旧版", self.ark_lottery),
("qq视频-AME活动", self.qq_video_amesvr),
("qq会员杯", self.dnf_club_vip),
]
# --------------------------------------------道聚城--------------------------------------------
@try_except()
def djc_operations(self):
show_head_line("开始道聚城相关操作")
self.show_not_ams_act_info("道聚城")
if not self.cfg.function_switches.get_djc:
logger.warning("未启用领取道聚城功能,将跳过")
return
self.fetch_djc_login_info("获取道聚城登录信息")
# ------------------------------初始工作------------------------------
old_info = self.query_balance("1. 操作前:查询余额")["data"]
old_allin, old_balance = int(old_info["allin"]), int(old_info["balance"])
# self.query_money_flow("1.1 操作前:查一遍流水")
# ------------------------------核心逻辑------------------------------
# 自动签到
self.sign_in_and_take_awards()
# 完成任务
self.complete_tasks()
# 领取奖励并兑换道具
self.take_task_awards_and_exchange_items()
# ------------------------------清理工作------------------------------
new_info = self.query_balance("5. 操作全部完成后:查询余额")["data"]
new_allin, new_balance = int(new_info["allin"]), int(new_info["balance"])
# self.query_money_flow("5.1 操作全部完成后:查一遍流水")
delta = new_allin - old_allin
logger.warning(
color("fg_bold_yellow")
+ f"账号 {self.cfg.name} 本次道聚城操作共获得 {delta} 个豆子(历史总获取: {old_allin} -> {new_allin} 余额: {old_balance} -> {new_balance} )"
)
def query_balance(self, ctx, print_res=True):
return self.get(ctx, self.urls.balance, print_res=print_res, use_this_cookies=self.djc_custom_cookies)
def query_money_flow(self, ctx):
return self.get(ctx, self.urls.money_flow)
# urls.sign签到接口偶尔会报 401 Unauthorized,因此需要加一层保护,确保不影响其他流程
@try_except()
def sign_in_and_take_awards(self):
self.get("2.1.2 发送app登录事件", self.urls.user_login_event, use_this_cookies=self.djc_custom_cookies)
total_try = self.common_cfg.retry.max_retry_count
for try_idx in range_from_one(total_try):
try:
# 签到
self.post("2.2 签到", self.urls.sign, self.sign_flow_data("911887"))
self.post("2.2 签到补签", self.urls.sign, self.sign_flow_data("912938"))
self.post("2.3 领取签到赠送的聚豆", self.urls.sign, self.sign_flow_data("324410"))
# 尝试领取自动签到的奖励
# 查询本月签到的日期列表
res = self.post("查询签到", self.urls.sign, self.sign_flow_data("321961"), print_res=False)
month_total_signed_days = int(res["modRet"]["jData"]["monthNum"])
logger.info(f"本月签到次数为 {month_total_signed_days}")
# 根据本月已签到数,领取符合条件的每月签到若干日的奖励(也就是聚豆页面最上面的那个横条)
# ("累计3天领取5聚豆", "322021")
# ("累计7天领取15聚豆", "322036")
# ("累计10天领取20聚豆", "322037")
# ("累计15天领取25聚豆", "322038")
# ("累计20天领取30聚豆", "322039")
# ("累计25天领取50聚豆", "322040")
for sign_reward_rule in self.get("2.3.2 查询连续签到奖励规则", self.urls.sign_reward_rule, print_res=False)[
"data"
]:
if sign_reward_rule["iCanUse"] == 1 and month_total_signed_days >= int(sign_reward_rule["iDays"]):
ctx = f"2.3.3 领取连续签到{sign_reward_rule['iDays']}天奖励"
self.post(ctx, self.urls.sign, self.sign_flow_data(str(sign_reward_rule["iFlowId"])))
# 最短的月为28天,做个保底
if month_total_signed_days >= 28:
self.post("2.3.4 累计签到整月-全勤奖", self.urls.sign, self.sign_flow_data("881740"))
break
except json.decoder.JSONDecodeError as e:
logger.error(f"第 {try_idx}/{total_try} 次尝试道聚城签到相关操作失败了,等待一会重试", exc_info=e)
if try_idx != total_try:
wait_for("道聚城签到操作失败", self.common_cfg.retry.retry_wait_time)
def sign_flow_data(self, iFlowId):
return self.format(self.urls.sign_raw_data, iFlowId=iFlowId)
def djc_operations_op(self, ctx, iFlowId, print_res=True, **extra_params):
iActivityId = self.urls.iActivityId_djc_operations
return self.amesvr_request(
ctx,
"comm.ams.game.qq.com",
"djc",
"dj",
iActivityId,
iFlowId,
print_res,
"",
**extra_params,
)
def complete_tasks(self):
# 完成《绝不错亿》
self.get(
"3.1 模拟点开活动中心", self.urls.task_report, task_type="activity_center", use_this_cookies=self.djc_custom_cookies
)
if self.cfg.mobile_game_role_info.enabled():
# 完成《礼包达人》
self.take_mobile_game_gift()
else:
async_message_box(
f"账号 {self.cfg.name} 未启用自动完成《礼包达人》任务功能,如需启用,请配置道聚城的手游名称。不配置,则每日任务的豆子会领不全", "道聚城参数未配置", show_once=True
)
if self.cfg.function_switches.make_wish:
# 完成《有理想》
self.make_wish()
else:
async_message_box(
f"账号 {self.cfg.name} 未启用自动完成《有理想》任务功能,如需启用,请打开道聚城许愿功能。不配置,则每日任务的豆子会领不全", "道聚城参数未配置", show_once=True
)
@try_except()
def take_mobile_game_gift(self):
game_info = self.get_mobile_game_info()
role_info = self.bizcode_2_bind_role_map[game_info.bizCode].sRoleInfo
giftInfos = self.get_mobile_game_gifts()
if len(giftInfos) == 0:
logger.warning(f"未找到手游【{game_info.bizName}】的有效七日签到配置,请换个手游,比如王者荣耀")
return
dayIndex = datetime.datetime.now().weekday() # 0-周一...6-周日,恰好跟下标对应
giftInfo = giftInfos[dayIndex]
self.get(
f"3.2 一键领取{role_info.gameName}日常礼包-{giftInfo.sTask}",
self.urls.receive_game_gift,
bizcode=game_info.bizCode,
iruleId=giftInfo.iRuleId,
systemID=role_info.systemID,
sPartition=role_info.areaID,
channelID=role_info.channelID,
channelKey=role_info.channelKey,
roleCode=role_info.roleCode,
sRoleName=quote_plus(role_info.roleName),
use_this_cookies=self.djc_custom_cookies,
)
@try_except()
def make_wish(self):
bizCode = "yxzj"
if bizCode not in self.bizcode_2_bind_role_map:
logger.warning(color("fg_bold_cyan") + "未在道聚城绑定王者荣耀,将跳过许愿功能。建议使用安卓模拟器下载道聚城,在上面绑定王者荣耀")
return
roleModel = self.bizcode_2_bind_role_map[bizCode].sRoleInfo
if "苹果" in roleModel.channelKey:
logger.warning(color("fg_bold_cyan") + f"ios端不能许愿手游,建议使用安卓模拟器下载道聚城,在上面绑定王者荣耀。roleModel={roleModel}")
return
# 查询许愿道具信息
query_wish_item_list_res = self.get(
"3.3.0 查询许愿道具",
self.urls.query_wish_goods_list,
plat=roleModel.systemID,
biz=roleModel.bizCode,
print_res=False,
)
if "data" not in query_wish_item_list_res or len(query_wish_item_list_res["data"]) == 0:
logger.warning(
f"在{roleModel.systemKey}上游戏【{roleModel.gameName}】暂不支持许愿,query_wish_item_list_res={query_wish_item_list_res}"
)
return
propModel = GoodsInfo().auto_update_config(query_wish_item_list_res["data"]["goods"][0])
# 查询许愿列表
wish_list_res = self.get(
"3.3.1 查询许愿列表", self.urls.query_wish, appUid=self.qq(), use_this_cookies=self.djc_custom_cookies
)
# 删除已经许愿的列表,确保许愿成功
for wish_info in wish_list_res["data"]["list"]:
ctx = f"3.3.2 删除已有许愿-{wish_info['bizName']}-{wish_info['sGoodsName']}"
self.get(ctx, self.urls.delete_wish, sKeyId=wish_info["sKeyId"])
# 许愿
param = {
"iActionId": propModel.type,
"iGoodsId": propModel.valiDate[0].code,
"sBizCode": roleModel.bizCode,
}
if roleModel.type == "0":
# 端游
if roleModel.serviceID != "":
param["iZoneId"] = roleModel.serviceID
else:
param["iZoneId"] = roleModel.areaID
param["sZoneDesc"] = quote_plus(roleModel.serviceName)
else:
# 手游
if roleModel.serviceID != "" and roleModel.serviceID != "0":
param["partition"] = roleModel.serviceID
elif roleModel.areaID != "" and roleModel.areaID != "0":
param["partition"] = roleModel.areaID
param["iZoneId"] = roleModel.channelID
if int(roleModel.systemID) < 0:
param["platid"] = 0
else:
param["platid"] = roleModel.systemID
param["sZoneDesc"] = quote_plus(roleModel.serviceName)
if roleModel.bizCode == "lol" and roleModel.accountId != "":
param["sRoleId"] = roleModel.accountId
else:
param["sRoleId"] = roleModel.roleCode
param["sRoleName"] = quote_plus(roleModel.roleName)
param["sGetterDream"] = quote_plus("不要888!不要488!9.98带回家")
wish_res = self.get("3.3.3 完成许愿任务", self.urls.make_wish, **param, use_this_cookies=self.djc_custom_cookies)
# 检查是否不支持许愿
# {"ret": "-8735", "msg": "该业务暂未开放许愿", "sandbox": false, "serverTime": 1601375249, "event_id": "DJC-DJ-0929182729-P8DDy9-3-534144", "data": []}
if wish_res["ret"] == "-8735":
logger.warning(f"游戏【{roleModel.gameName}】暂未开放许愿,请换个道聚城许愿界面中支持的游戏来进行许愿哦,比如王者荣耀~")
def take_task_awards_and_exchange_items(self):
# 领取奖励
# 领取《礼包达人》
self.take_task_award("4.1.1", "100066", "礼包达人")
# 领取《绝不错亿》
self.take_task_award("4.1.2", "100040", "绝不错亿")
# 领取《有理想》
self.take_task_award("4.1.3", "302124", "有理想")
# 领取《活跃度银宝箱》
self.take_task_award("4.1.4", "100001", "活跃度银宝箱")
# 领取《活跃度金宝箱》
self.take_task_award("4.1.5", "100002", "活跃度金宝箱")
# 兑换所需道具
self.exchange_items()
# 领取《兑换有礼》
self.take_task_award("4.3.1", "327091", "兑换有礼")
def take_task_award(self, prefix, iRuleId, taskName=""):
ctx = f"{prefix} 查询当前任务状态"
taskinfo = self.get(ctx, self.urls.usertask, print_res=False)
if self.can_take_task_award(taskinfo, iRuleId):
ctx = f"{prefix} 领取任务-{taskName}-奖励"
self.get(ctx, self.urls.take_task_reward, iruleId=iRuleId)
# 尝试领取每日任务奖励
def can_take_task_award(self, taskinfo, iRuleId):
opt_tasks = taskinfo["data"]["list"]["day"].copy()
for _id, task in taskinfo["data"]["chest_list"].items():
opt_tasks.append(task)
for tinfo in opt_tasks:
if int(iRuleId) == int(tinfo["iruleId"]):
return int(tinfo["iCurrentNum"]) >= int(tinfo["iCompleteNum"])
return False