forked from fzls/djc_helper
-
Notifications
You must be signed in to change notification settings - Fork 0
/
config.py
794 lines (659 loc) · 37.6 KB
/
config.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
import re
from multiprocessing import cpu_count
from typing import List, Dict
import toml
from const import *
from data_struct import ConfigInterface
from log import *
from sign import getACSRFTokenForAMS, getDjcSignParams
from util import *
encoding_error_str = "Found invalid character in key name: '#'. Try quoting the key name. (line 1 column 2 char 1)"
class AccountInfoConfig(ConfigInterface):
def __init__(self):
# 手动登录需要设置的信息
self.uin = "o123456789"
self.skey = "@a1b2c3d4e"
# 自动登录需要设置的信息
self.account = "123456789"
self.password = "使用账号密码自动登录有风险_请理解这个功能到底如何使用你的账号密码后再决定是否使用"
class MobileGameRoleInfoConfig(ConfigInterface):
def __init__(self):
# 手游名称: 无/任意手游/剑网3:指尖江湖/和平精英/王者荣耀/QQ飞车手游/天天酷跑/其他任意游戏,可参考djc_biz_list.json获取完整列表
self.game_name = "任意手游"
def enabled(self):
return self.game_name not in ["无", "none"]
def use_any_binded_mobile_game(self):
return self.game_name in ["任意手游"]
class ExchangeItemConfig(ConfigInterface):
def __init__(self):
self.iGoodsId = "753"
self.sGoodsName = "装备品级调整箱(5个)"
self.count = 0
class DnfHelperChronicleExchangeItemConfig(ConfigInterface):
def __init__(self):
self.sLbcode = "ex_0003"
self.sName = "装备提升礼盒*1"
self.count = 3
class FirecrackersExchangeItemConfig(ConfigInterface):
def __init__(self):
self.index = 6
self.name = "灿烂的徽章自选礼盒*1"
self.need_points = 120
self.count = 1
class XinYueOperationConfig(ConfigInterface):
def __init__(self):
self.iFlowId = "512411"
self.package_id = "" # 仅礼包兑换需要这个参数,如兑换【勇者宝库】的【装备提升礼盒】的package_id为702218
self.sFlowName = "输出我的任务积分"
self.count = 1
class XinYueAppOperationConfig(ConfigInterface):
def __init__(self):
# 操作名称
self.name = "兑换复活币"
# 抓包获取的加密http请求体。
# 获取方式,抓包获取http body。如fiddler,抓包,找到对应请求(body大小为150的请求),右侧点Inspector/HexView,选中Http Body部分的字节码(未标蓝部分),右击Copy/Copy as 0x##,然后粘贴出来,将其中的bytes复制到下列对应数组位置
self.encrypted_raw_http_body = [0x58, 0x59, 0x01, 0x00, 0x00, 0x01, 0x61, 0x0A, 0x01, 0x01, 0x2B, 0x10, 0x02, 0x3D, 0x00, 0x00, 0x10, 0xE5, 0xF7, 0x11, 0x0E, 0xF8, 0x2F, 0x1B, 0x13, 0x10, 0x6E, 0xA5, 0xF7, 0xE2, 0x7B, 0xD3, 0x58,
0x0B, 0x1D, 0x00, 0x01, 0x01, 0x41, 0x28, 0x52, 0x61, 0x09, 0x86, 0x2C, 0x45, 0x32, 0x20, 0x87, 0xBA, 0xAE, 0xDF, 0x03, 0x34, 0x24, 0x68, 0x75, 0x65, 0x58, 0xDF, 0xC1, 0x61, 0x95, 0x7F, 0xAD, 0x9D,
0xD3, 0x8E, 0x1E, 0x04, 0x5F, 0x68, 0xB2, 0xFA, 0x7A, 0x64, 0x77, 0x99, 0xCA, 0x36, 0x3D, 0xB9, 0x71, 0xF1, 0x80, 0x13, 0xAE, 0xCA, 0xBE, 0xF5, 0x26, 0x99, 0xB6, 0x6F, 0x93, 0xFD, 0xA0, 0x5C, 0x22,
0xF5, 0x11, 0x21, 0xD2, 0x11, 0xE6, 0x0B, 0x39, 0xE2, 0xB8, 0xB0, 0x05, 0x8A, 0xA7, 0x76, 0xD7, 0xF4, 0x22, 0xA4, 0x24, 0x0F, 0xB5, 0xD2, 0x12, 0xAF, 0x09, 0xD8, 0xA0, 0x1C, 0x23, 0x0D, 0x75, 0xF0,
0x68, 0x09, 0x6A, 0x2E, 0xEF, 0x6A, 0x76, 0x49, 0x5A, 0x6B, 0x78, 0xAA, 0xE2, 0x69, 0xE9, 0x31, 0x92, 0xB7, 0x21, 0x7C, 0xD9, 0x6E, 0x8C, 0x1E, 0x0D, 0xE6, 0xE0, 0xC0, 0x10, 0xDF, 0x95, 0x8E, 0x55,
0xFC, 0x64, 0x21, 0x27, 0xA7, 0x87, 0x1E, 0x2B, 0x58, 0xBD, 0x84, 0x4F, 0xE3, 0xC2, 0xC4, 0xB4, 0x23, 0x79, 0x45, 0x57, 0x94, 0xFD, 0x2D, 0xD3, 0xA1, 0x09, 0x04, 0x86, 0xB7, 0xAC, 0xC5, 0x56, 0xB4,
0xEF, 0xA2, 0x3A, 0xF2, 0x41, 0x16, 0x14, 0x02, 0xC4, 0xB2, 0x00, 0x5E, 0xD5, 0x0C, 0x9B, 0x5E, 0x0A, 0xFD, 0x1C, 0x75, 0xEC, 0xB1, 0x50, 0x7A, 0x4E, 0x6C, 0x78, 0xF9, 0xC4, 0x58, 0x7E, 0x73, 0xB6,
0xA8, 0xB0, 0x91, 0xCE, 0x0D, 0xBA, 0xF8, 0xFC, 0x82, 0x81, 0xE6, 0xA8, 0x97, 0x75, 0x5F, 0x8B, 0x5A, 0x4B, 0xEB, 0x59, 0x45, 0x7F, 0x26, 0x57, 0x16, 0x4C, 0x84, 0x6A, 0x50, 0xF8, 0x95, 0x8F, 0x4C,
0x85, 0x2D, 0xA1, 0x88, 0x81, 0xA8, 0xFF, 0x4D, 0x69, 0xEC, 0x6D, 0xC8, 0x05, 0xA0, 0xE0, 0x8F, 0x7D, 0x9C, 0x37, 0xCD, 0xB3, 0x0B, 0x05, 0xFD, 0xF0, 0x52, 0xB2, 0x86, 0x0D, 0x36, 0x27, 0x8B, 0xDF,
0x34, 0x01, 0xA2, 0xC2, 0x01, 0xA8, 0x7F, 0xC9, 0x2F, 0xFA, 0x44, 0x1F, 0xBA, 0x81, 0x73, 0xF6, 0xD0, 0xAC, 0x5D, 0xA4, 0xED, 0x1F, 0xDB, 0x1D, 0xF8, 0x10, 0x97, 0x7E, 0x3F, 0xC3, 0x21, 0x08, 0x8D,
0xB9, 0xCD, 0x82, 0x74, 0x1A, 0xE5, 0x8A, 0x39, 0x67, 0x3C, 0x26, 0x18, 0x53, 0xFC, 0xC4, 0x22, 0xAF, 0x83, 0x2F, 0x06, 0x13, 0xAB, 0xCF, 0x56, 0xF6, 0x42, 0x7D, 0x52, 0xD8, 0x62]
class WegameGuoqingExchangeItemConfig(ConfigInterface):
def __init__(self):
self.iFlowId = "703514"
self.sGoodsName = "强化器-4分"
self.count = 1
class ArkLotteryAwardConfig(ConfigInterface):
def __init__(self):
self.name = "勇士归来礼包"
self.ruleid = 25947
self.count = 1
def update(self, name, ruleid):
self.name = name
self.ruleid = ruleid
return self
class ArkLotteryConfig(ConfigInterface):
def __init__(self):
# 用于完成幸运勇士的区服ID和角色ID,若服务器ID留空,则使用道聚城绑定的dnf角色信息
self.lucky_dnf_server_id = "" # 区服id可查阅reference_data/dnf_server_list.js,具体值为每一个服务器配置中的v字段,如{t: "广东三区", v: "22"}表示广东三区的区服ID为"22"
self.lucky_dnf_role_id = "" # 角色ID,不知道时可以填写区服ID,该数值留空,这样处理到抽卡的时候会用黄色字体打印出来信息
# 是否领取礼包(建议仅大号开启这个功能)
self.need_take_awards = False
# 是否展示在概览界面
self.show_status = True
# 卡牌数目使用特定的颜色
self.show_color = ""
# 活动ID => 是否消耗所有卡牌来抽奖(建议在兑换完所有礼包后再开启这个功能)
self.act_id_to_cost_all_cards_and_do_lottery = {} # type: Dict[int, bool]
def fields_to_fill(self):
return [
('take_awards', ArkLotteryAwardConfig),
]
def on_config_update(self, raw_config: dict):
self.act_id_to_cost_all_cards_and_do_lottery = {int(k): bool(v) for k, v in self.act_id_to_cost_all_cards_and_do_lottery.items()}
class VipMentorConfig(ConfigInterface):
def __init__(self):
# 领取第几个关怀礼包,可填1/2/3,一般是第二个最好
self.take_index = 2
# 用于完成关怀礼包的区服ID和角色ID,若服务器ID留空,则使用道聚城绑定的dnf角色信息
self.guanhuai_dnf_server_id = "" # 区服id可查阅reference_data/dnf_server_list.js,具体值为每一个服务器配置中的v字段,如{t: "广东三区", v: "22"}表示广东三区的区服ID为"22"
self.guanhuai_dnf_role_id = "" # 角色ID,不知道时可以填写区服ID,该数值留空,这样处理到抽卡的时候会用黄色字体打印出来信息
class DnfHelperInfoConfig(ConfigInterface):
def __init__(self):
# userId/nickName的获取方式为,点开dnf助手中点开右下角的【我的】,然后点击右上角的【编辑】按钮,则社区ID即为userId,昵称即为nickname,如我的这俩值为504051073、风之凌殇
# 社区ID
self.userId = ""
# 昵称
self.nickName = ""
# 登录票据,目前需手动更新。
# 流程:
# 1. 打开dnf助手并确保已登录账户,点击活动,找到【艾丽丝的密室,塔罗牌游戏】并点开,点击右上角分享,选择QQ好友,发送给【我的电脑】。
# 2. 在我的电脑聊天框中的链接中找到请求中的token(形如&serverId=11&token=6C6bNrA4&isMainRole=0&subGameId=10014,因为&是参数分隔符,所以token部分为token=6C6bNrA4,所以token为6C6bNrA4, ps: 如果参数形如&serverId=&token=&isMainRole=&subGameId=,那么token部分参数为token=,说明这个活动助手没有把token放到链接里,需要尝试下一个),将其进行更新到配置文件中【dnf助手信息】配置中
#
# ps: 如果有多个账号需要领取这个,请不要在手机上依次登入登出执行上述步骤来获取token,因为你在登陆下一个账号的时候,之前的账号的token就因为登出而失效了
# 有这个需求的话,请使用安卓模拟器的多开功能来多开dnf助手去登陆各个账号。如果手机支持多开app,也可以使用对应功能。具体多开流程请自行百度搜索: 手机 app 多开
self.token = ""
# dnf助手编年史是否开启抽奖
self.chronicle_lottery = False
# dnf助手编年史兑换道具信息,其他奖励信息可查阅reference_data/dnf助手编年史活动_可兑换奖励列表.json
self.chronicle_exchange_items = [] # type: List[DnfHelperChronicleExchangeItemConfig]
def fields_to_fill(self):
return [
('chronicle_exchange_items', DnfHelperChronicleExchangeItemConfig),
]
class HelloVoiceInfoConfig(ConfigInterface):
def __init__(self):
# hello语音的用户ID
# 获取方式:打开hello语音,点击右下角【我的】tab,在最上方头像框的右侧,昵称下方,有形如【ID:XXXXXX】的字样,其中ID后面这串数字就是用户ID
self.hello_id = ""
class FirecrackersConfig(ConfigInterface):
def __init__(self):
# 是否开启抽奖,建议兑换完所有道具后再开启
self.enable_lottery = False
# 兑换道具信息
self.exchange_items = [] # type: List[FirecrackersExchangeItemConfig]
def fields_to_fill(self):
return [
('exchange_items', FirecrackersExchangeItemConfig),
]
class FunctionSwitchesConfig(ConfigInterface):
def __init__(self):
# ------------ 全局禁用开关 ------------
# 是否禁用各种活动,供小号使用,这样新增的各种活动都将被禁用
# 例外情况:道聚城、许愿、心悦特权专区、集卡这四个活动不受该配置项影响
# 如果想要单独设置各个活动的开关,请不要设置这个配置项,否则各个新活动都会被禁用
self.disable_most_activities = False
# ------------ 普通skey(需要登录 炎炎夏日 活动页面 获取) ------------
# 是否领取道聚城
self.get_djc = True
# 是否启用许愿功能,用于完成《有理想》。目前仅限安卓版本道聚城上绑定王者荣耀时可使用
self.make_wish = True
# 是否领取心悦特权专区
self.get_xinyue = True
# 是否领取腾讯游戏信用相关礼包
self.get_credit_xinyue_gift = True
# 是否领取每月黑钻等级礼包
self.get_heizuan_gift = True
# 是否领取DNF进击吧赛利亚活动
self.get_xinyue_sailiyam = True
# 是否领取wegame国庆活动
self.get_wegame_guoqing = True
# 是否领取史诗之路来袭活动合集活动
self.get_dnf_1224 = True
# 是否领取DNF闪光杯第三期活动
self.get_dnf_shanguang = True
# 是否领取qq视频活动
self.get_qq_video = True
# 是否领取10月女法师三觉活动
self.get_dnf_female_mage_awaken = True
# 是否领取DNF助手排行榜活动,额外需要助手userId和token
self.get_dnf_rank = True
# 是否领取dnf助手编年史活动,额外需要助手userId
self.get_dnf_helper_chronicle = True
# 是否启用hello语音奖励兑换功能,额外需要hello语音的用户ID
self.get_hello_voice = True
# 是否领取2020DNF嘉年华页面主页面签到活动
self.get_dnf_carnival = True
# 是否领取2020DNF嘉年华直播活动
self.get_dnf_carnival_live = True
# 是否DNF共创投票
self.get_dnf_dianzan = True
# 是否领取DNF福利中心兑换
self.get_dnf_welfare = True
# 是否领取心悦app理财礼卡
self.get_xinyue_financing = True
# 是否领取心悦猫咪
self.get_xinyue_cat = True
# 是否领取心悦app周礼包
self.get_xinyue_weekly_gift = True
# 是否领取dnf漂流瓶
self.get_dnf_drift = True
# 是否领取DNF马杰洛的规划
self.get_majieluo = True
# 是否领取dnf助手活动,额外需要助手userId和token
self.get_dnf_helper = True
# 是否领取暖冬好礼活动
self.get_warm_winter = True
# 是否领取qq视频-看江湖有翡
self.get_youfei = True
# 是否进行dnf论坛签到
self.get_dnf_bbs_signin = True
# 是否领取 DNF落地页 活动
self.get_dnf_luodiye = True
# 是否领取 WeGame 活动
self.get_dnf_wegame = True
# 是否领取 新春福袋大作战 活动
self.get_spring_fudai = True
# 是否领取 DNF福签大作战 活动
self.get_dnf_fuqian = True
# 是否领取 DNF集合站 活动
self.get_dnf_collection = True
# 是否领取 燃放爆竹 活动
self.get_firecrackers = True
# 是否领取 DNF黑鸦竞速 活动
self.get_dnf_heiya = True
# 是否自动进行colg每日签到和积分领取(其他需自行操作~)
self.get_colg_signin = True
# 是否进行 心悦app 相关操作
self.get_xinyue_app = True
# 是否领取 DNF格斗大赛 活动
self.get_dnf_pk = True
# 是否领取 心悦51 活动
self.get_dnf_xinyue_51 = True
# 是否领取 DNF强者之路 活动
self.get_dnf_strong = True
# ------------ QQ空间pskey(需要登录 QQ空间 获取) ------------
# 是否启用集卡功能
self.get_ark_lottery = True
# 是否启用阿拉德勇士征集令活动
self.get_dnf_warriors_call = True
# 是否启用会员关怀活动
self.get_vip_mentor = True
# ------------ 安全管家pskey(需要登录 安全管家 获取) ------------
# 是否领取管家蚊子腿活动
self.get_guanjia = True
class AccountConfig(ConfigInterface):
def __init__(self):
# 是否启用该账号
self.enable = True
# 是否在github action模式下启用
self.enable_in_github_action = True
# 是否处于安全模式,也就是登录的时候需要滑动验证码或者发送短信
self.in_safe_mode = False
# 账号名称,仅用于区分不同账号
self.name = "默认账号_请修改这个名称_并确保不同账号取不同名字"
# 登录模式
# by_hand: 手动登录,在skey无效的情况下会弹出活动界面,自行登录后将cookie中uin和skey提取到下面的配置处
# qr_login: 二维码登录,每次运行时若本地缓存的.skey文件中存储的skey过期了,则弹出登录页面,扫描二维码后将自动更新skey,进行后续操作
# auto_login: 自动登录,每次运行若本地缓存的.skey文件中存储的skey过期了,根据填写的账密信息,自动登录来获取uin和skey,无需手动操作
self.login_mode = "qr_login"
# 是否无法在道聚城绑定dnf,比如被封禁或者是朋友的QQ(主要用于小号,被风控不能注册dnf账号,但是不影响用来当抽卡等活动的工具人)
self.cannot_bind_dnf = False
# 漂流瓶每日邀请列表,最多可填8个(不会实际发消息)
self.drift_send_qq_list = [] # type: List[str]
# 新春福袋大作战邀请列表(会实际发消息)
self.spring_fudai_receiver_qq_list = [] # type: List[str]
# 燃放爆竹活动是否尝试邀请好友(不会实际发消息)
self.enable_firecrackers_invite_friend = False
# 马杰洛活动是否尝试黑钻送好友(不会实际发消息)
self.enable_majieluo_invite_friend = False
# dnf论坛签到formhash
self.dnf_bbs_formhash = ""
# dnf论坛cookie
self.dnf_bbs_cookie = ""
# colg cookie
self.colg_cookie = ""
# 腾讯系网页登录通用账号凭据与token
self.account_info = AccountInfoConfig()
# 各功能开关
self.function_switches = FunctionSwitchesConfig()
# 完成《礼包达人》任务所需的手游的名称信息
self.mobile_game_role_info = MobileGameRoleInfoConfig()
# 兑换道具信息
self.exchange_items = [] # type: List[ExchangeItemConfig]
# 心悦相关操作信息
self.xinyue_operations = [] # type: List[XinYueOperationConfig]
# 心悦app相关操作
self.xinyue_app_operations = [] # type: List[XinYueAppOperationConfig]
# 抽卡相关配置
self.ark_lottery = ArkLotteryConfig()
# 会员关怀相关配置
self.vip_mentor = VipMentorConfig()
# wegame国庆活动兑换道具,具体道具的iFlowId和描述可参考reference_data/wegame国庆活动.json
self.wegame_guoqing_exchange_items = [] # type: List[WegameGuoqingExchangeItemConfig]
# dnf助手信息
self.dnf_helper_info = DnfHelperInfoConfig()
# hello语音相关信息
self.hello_voice = HelloVoiceInfoConfig()
# 燃放爆竹相关配置
self.firecrackers = FirecrackersConfig()
def fields_to_fill(self):
return [
('exchange_items', ExchangeItemConfig),
('xinyue_operations', XinYueOperationConfig),
('xinyue_app_operations', XinYueAppOperationConfig),
('wegame_guoqing_exchange_items', WegameGuoqingExchangeItemConfig),
]
def is_enabled(self):
if self.in_safe_mode and not config().common.enable_in_safe_mode_accounts:
# 若账号处于安全模式,且当前不启用处于安全模式的账号,则视为未启用当前账号
return False
if is_run_in_github_action() and not self.enable_in_github_action:
# 若当前在github action环境中运行,且设定为不在该环境中使用该QQ,则认为未启用
return False
return self.enable
def on_config_update(self, raw_config: dict):
self.sDeviceID = self.getSDeviceID()
self.aes_key = "84e6c6dc0f9p4a56"
self.rsa_public_key_file = "public_key.der"
self.updateUinSkey(self.account_info.uin, self.account_info.skey)
self.drift_send_qq_list = [str(qq) for qq in self.drift_send_qq_list]
self.spring_fudai_receiver_qq_list = [str(qq) for qq in self.spring_fudai_receiver_qq_list]
def updateUinSkey(self, uin, skey):
self.account_info.uin = uin
self.account_info.skey = skey
self.g_tk = str(getACSRFTokenForAMS(self.account_info.skey))
self.sDjcSign = getDjcSignParams(self.aes_key, self.rsa_public_key_file, uin2qq(self.account_info.uin), self.sDeviceID, appVersion)
def getSDeviceID(self):
sDeviceIdFileName = os.path.join(cached_dir, f".sDeviceID.{self.name}.txt")
if os.path.isfile(sDeviceIdFileName):
with open(sDeviceIdFileName, "r", encoding="utf-8") as file:
sDeviceID = file.read()
if len(sDeviceID) == 36:
# print("use cached sDeviceID", sDeviceID)
return sDeviceID
sDeviceID = str(uuid.uuid1())
# print("create new sDeviceID", sDeviceID, len(sDeviceID))
with open(sDeviceIdFileName, "w", encoding="utf-8") as file:
file.write(sDeviceID)
return sDeviceID
class LoginConfig(ConfigInterface):
def __init__(self):
# 重试次数
self.max_retry_count = 6
# 重试间隔时间(秒)
self.retry_wait_time = 1200
# 打开网页后等待时长
self.open_url_wait_time = 3
# 加载页面的超时时间,以登录按钮出现为完成标志
self.load_page_timeout = 15
# 点击登录按钮后的超时时间,加载登录iframe,以其显示出来为完成标志
self.load_login_iframe_timeout = 8
# 登录的超时时间,从登录界面显示为开始,以用户完成登录为结束标志
self.login_timeout = 60
# 等待登录完成的超时时间,以活动结束的按钮弹出来标志
self.login_finished_timeout = 60
# 自动处理滑动验证码
self.auto_resolve_captcha = True
# 每次尝试滑动验证码的偏移值,为相对值,填倍数,表示相当于该倍数的滑块宽度
self.move_captcha_delta_width_rate = 0.2
# 推荐登录重试间隔变化率r。新的推荐值 = (1-r)*旧的推荐值 + r*本次成功重试的间隔
self.recommended_retry_wait_time_change_rate = 0.125
class RetryConfig(ConfigInterface):
def __init__(self):
# 每次兑换请求之间的间隔时间(秒),避免请求过快而报错,目前测试1s正好不会报错~
self.request_wait_time = 2
# 当提示【"msg": "系统繁忙,请稍候再试。", "ret": "-9905"】时的最大重试次数
self.max_retry_count = 3
# 上述情况下的重试间隔时间(秒)
self.retry_wait_time = 5
class XinYueConfig(ConfigInterface):
def __init__(self):
# 在每日几点后才尝试提交心悦的成就点任务,避免在没有上游戏时执行心悦成就点任务,导致高成就点的任务没法完成,只能完成低成就点的
self.submit_task_after = 0
class FixedTeamConfig(ConfigInterface):
reg_qq = r'\d+'
def __init__(self):
# 是否启用该固定队
self.enable = False
# 固定队伍id,仅用于本地区分用
self.id = "1"
# 固定队成员,必须是两个,则必须都配置在本地的账号列表中了,否则将报错,不生效
self.members = ["小队第一个账号的QQ号", "小队第二个账号的QQ号"]
def on_config_update(self, raw_config: dict):
# 由于经常会有人填写成数字,如[123, 456],导致后面从各个dict中取值时出错(dict中都默认QQ为str类型,若传入int类型,会取不到对应的值)
# 所以这里做下兼容,强制转换为str
self.members = [str(qq) for qq in self.members]
def check(self) -> bool:
if len(self.members) != 2:
return False
for qq in self.members:
if re.fullmatch(self.reg_qq, qq) is None:
return False
return True
class CommonConfig(ConfigInterface):
log_level_map = {
"debug": logging.DEBUG,
"info": logging.INFO,
"warning": logging.WARNING,
"error": logging.ERROR,
"critical": logging.CRITICAL,
}
def __init__(self):
# 仅运行首个账户(调试用)
self.run_first_account_only = False
# 账号数目,将在读取完配置后动态设定为当前设置的账号数目
self.account_count = 1
# 测试模式,若开启,则一些实验性功能将会启用
self.test_mode = False
# 是否启用处于安全模式的账号
self.enable_in_safe_mode_accounts = False
# 是否展示小助手的累积使用情况
self._show_usage = False
# 是否启用多进程功能
self.enable_multiprocessing = True
# 进程池大小,若为0,则默认为当前cpu核心数,若为-1,则默认为当前账号数
self.multiprocessing_pool_size = -1
# 是否强制使用打包附带的便携版chrome
self.force_use_portable_chrome = False
# 强制使用特定大版本的chrome,默认为0,表示使用小助手默认设定的版本。
self.force_use_chrome_major_version = 0
# http(s)请求超时时间(秒)
self.http_timeout = 10
# 是否展示chrome的debug日志,如DevTools listening,Bluetooth等
self._debug_show_chrome_logs = False
# 自动登录模式是否不显示浏览器界面
self.run_in_headless_mode = False
# 日志等级, 级别从低到高依次为 "debug", "info", "warning", "error", "critical"
self.log_level = "info"
# 日志目录最大允许大小(单位为MiB),当超出该大小时将进行清理
self.max_logs_size = 1024
# 日志目录保留大小(单位为Mib),每次清理时将按时间顺序清理日志,直至剩余日志大小不超过该值
self.keep_logs_size = 512
# 是否检查更新
self.check_update_on_start = True
self.readme_page = "https://github.com/fzls/djc_helper/blob/master/README.MD"
self.changelog_page = "https://github.com/fzls/djc_helper/blob/master/CHANGELOG.MD"
# 是否启用自动更新功能
self.auto_update_on_start = True
# 是否仅允许单个运行实例
self.allow_only_one_instance = True
# 是否尝试自动绑定新活动
self.try_auto_bind_new_activity = True
# 提前多少天提示付费过期
self.notify_pay_expired_in_days = 7
# 马杰洛新春版本赠送卡片目标QQ
self.majieluo_send_card_target_qq = ""
# 抽卡汇总展示色彩
self.ark_lottery_summary_show_color = ""
# 是否在活动最后一天消耗所有卡牌来抽奖(若还有卡)
self.cost_all_cards_and_do_lottery_on_last_day = False
# 调整日志等级对应颜色,颜色表可以运行log.py获取
self.log_colors = {} # type: Dict[str, str]
# 自动赠送卡片的目标QQ数组,这些QQ必须是配置的账号之一,若配置则会在程序结束时尝试从其他小号赠送卡片给这些账号,且这些账号不会赠送卡片给其他账号,若不配置则不启用。
# 赠送策略为:如果该QQ仍有可兑换奖励,将赠送目标QQ最需要的卡片;否则将赠送目标QQ其他QQ最富余的卡片
self.auto_send_card_target_qqs = [] # type: List[str]
# 接受福签赠送的scode列表,点赠送后查看链接中的sCode参数可知
self.scode_list_accept_give = []
# 接受福签索要的scode列表,点索要后查看链接中的sCode参数可知
self.scode_list_accept_ask = []
# 登录各个阶段的最大等待时间,单位秒(仅二维码登录和自动登录需要配置,数值越大容错性越好)
self.login = LoginConfig()
# 各种操作的通用重试配置
self.retry = RetryConfig()
# 心悦相关配置
self.xinyue = XinYueConfig()
# 固定队相关配置。用于本地两个号来组成一个固定队伍,完成心悦任务。
self.fixed_teams = [] # type: List[FixedTeamConfig]
# 赛利亚活动拜访目标QQ列表
self.sailiyam_visit_target_qqs = [] # type: List[str]
def fields_to_fill(self):
return [
('fixed_teams', FixedTeamConfig),
]
def on_config_update(self, raw_config: dict):
log_level = self.log_level_map[self.log_level]
consoleHandler.setLevel(log_level)
try:
from lanzou.api.utils import logger as lanzou_logger
# 将lanzou的日志也显示
lanzou_logger.setLevel(log_level)
except Exception:
pass
if type(self.log_colors) is dict:
for level, log_color in self.log_colors.items():
consoleLogFormatter.log_colors[level] = log_color
# 由于经常会有人填写成数字的列表,如[123, 456],导致后面从各个dict中取值时出错(dict中都默认QQ为str类型,若传入int类型,会取不到对应的值)
# 所以这里做下兼容,强制转换为str
self.auto_send_card_target_qqs = [str(qq) for qq in self.auto_send_card_target_qqs]
self.sailiyam_visit_target_qqs = [str(qq) for qq in self.sailiyam_visit_target_qqs]
class Config(ConfigInterface):
def __init__(self):
# 所有账号共用的配置
self.common = CommonConfig()
# 兑换道具信息
self.account_configs = [] # type: List[AccountConfig]
def fields_to_fill(self):
return [
('account_configs', AccountConfig),
]
def on_config_update(self, raw_config: dict):
if not self.check():
logger.error("配置有误,请根据提示信息修改")
exit(-1)
if len(self.account_configs) != 0 and self.common.run_first_account_only:
logger.warning(color("bold_yellow") + f"当前是调试模式,仅处理第一个账号")
self.account_configs = self.account_configs[:1]
self.common.account_count = len(self.account_configs)
if len(self.account_configs) == 1 and self.common.enable_multiprocessing and not os.path.isfile("config.toml.local"):
logger.info(color("bold_green") + "当前仅有一个账号,没必要开启多进程模式,将关闭多进程模式~")
self.common.enable_multiprocessing = False
def check(self) -> bool:
name2index = {}
for _idx, account in enumerate(self.account_configs):
idx = _idx + 1
# 检查是否填写名称
if len(account.name) == 0:
logger.error(color("fg_bold_red") + f"第{idx}个账号未设置名称,请确保已填写对应账号配置的name")
return False
# 检查名称是否重复
if account.name in name2index:
logger.error(color("fg_bold_red") + f"第{idx}个账号的名称 {account.name} 与第{name2index[account.name]}个账号的名称重复,请调整为不同的名字")
return False
name2index[account.name] = idx
# 检查dnf助手的userId是否误填为了昵称
dhi = account.dnf_helper_info
if dhi.userId != "":
try:
int(dhi.userId)
except ValueError:
logger.error(color("fg_bold_red") + (
f"第{idx}个账号配置的dnf助手信息的社区ID(userId)=[{dhi.userId}]似乎为昵称,请仔细检查是否与昵称(nickName)=[{dhi.nickName}]的值填反了?"
"id应该类似[504051073],而昵称则形如[风之凌殇]"
))
return False
return True
def is_all_account_auto_login(self) -> bool:
for account in self.account_configs:
if account.login_mode != "auto_login":
return False
return True
def get_pool_size(self) -> int:
if not self.common.enable_multiprocessing:
return 0
pool_size = self.common.multiprocessing_pool_size
if pool_size == 0:
# 若为0,则默认为当前cpu核心数
return cpu_count()
elif pool_size == -1:
# 若为-1,则默认为当前账号数
return len(self.account_configs)
else:
return pool_size
def get_account_config_by_name(self, name: str) -> Optional[AccountConfig]:
for account_config in self.account_configs:
if account_config.name == name:
return account_config
return None
g_config = Config()
# 读取程序config
def load_config(config_path="config.toml", local_config_path="config.toml.local"):
global g_config
# 首先尝试读取config.toml(受版本管理系统控制)
try:
raw_config = toml.load(config_path)
g_config.auto_update_config(raw_config)
except UnicodeDecodeError as error:
logger.error(color("fg_bold_yellow") + f"{config_path}的编码格式有问题,应为utf-8,如果使用系统自带记事本的话,请下载vscode或notepad++等文本编辑器\n错误信息:{error}\n")
exit(0)
except Exception as error:
if encoding_error_str in str(error):
logger.error(color("fg_bold_yellow") + f"{config_path}的编码格式有问题,应为utf-8,如果使用系统自带记事本的话,请下载vscode或notepad++等文本编辑器\n错误信息:{error}\n")
exit(0)
logger.error(color("fg_bold_red") + f"读取{config_path}文件出错,是否直接在压缩包中打开了或者toml语法有问题?\n具体出错为:{error}\n" +
color("fg_bold_yellow") + "若未完整解压,请先解压。否则请根据上面的英文报错信息,自行百度学习toml的基本语法,然后处理对应行的语法错误(看不懂的话自行用百度翻译或有道翻译)")
exit(-1)
# 然后尝试读取本地文件(不受版本管理系统控制)
try:
if local_config_path != "":
raw_config = toml.load(local_config_path)
g_config.auto_update_config(raw_config)
except Exception as e:
pass
# 最后尝试从环境变量获取配置,主要用于github action自动运行
if is_run_in_github_action():
logger.info("当前在github action环境下运行,将从环境变量中读取配置信息强制覆盖~")
raw_config = toml.loads(get_config_from_env())
g_config.auto_update_config(raw_config)
def gen_config_for_github_action():
# 读取配置
load_config()
cfg = config()
# 检查是否所有账号都是账密登录,不是则抛异常退出
for account_cfg in cfg.account_configs:
if account_cfg.login_mode != "auto_login":
raise Exception("github action专用配置应全部使用账密自动登录模式,请修改~")
# note: 做一些github action上专门的改动
# 不打开浏览器(因为没有显示器可以看<_<)
cfg.common.run_in_headless_mode = True
# 不显示使用情况
cfg.common._show_usage = False
# 强制使用便携版(因为必定没有安装chrome)
cfg.common.force_use_portable_chrome = True
# 不展示chrome调试日志
cfg.common._debug_show_chrome_logs = False
# 设置日志级别为log,方便查问题
cfg.common.log_level = "debug"
# 不必检查更新,必定是最新版本
cfg.common.check_update_on_start = False
# 不必自动更新,同理
cfg.common.auto_update_on_start = False
# 调低网络超时时间(github的网络情况比较稳定,降低时间也可以减少整体运行时长)
cfg.common.http_timeout = 5
cfg.common.login.load_page_timeout = 60
cfg.common.login.login_timeout = 60
cfg.common.login.login_finished_timeout = 60
# 保存到专门配置文件
show_config_size(cfg, "精简前")
# hack: 官方文档写secrets最多64KB,实测最多45022个字符。
# https://docs.github.com/en/actions/reference/encrypted-secrets#limits-for-secrets
# 因此这里特殊处理一些账号级别开关,若配置与默认配置相同,则直接从配置文件中移除~
remove_unnecessary_configs(cfg.common, CommonConfig())
remove_unnecessary_configs(cfg.common.login, LoginConfig())
remove_unnecessary_configs(cfg.common.retry, RetryConfig())
remove_unnecessary_configs(cfg.common.xinyue, XinYueConfig())
for account_cfg in cfg.account_configs:
remove_unnecessary_configs(account_cfg, AccountConfig())
remove_unnecessary_configs(account_cfg.account_info, AccountInfoConfig())
remove_unnecessary_configs(account_cfg.function_switches, FunctionSwitchesConfig())
remove_unnecessary_configs(account_cfg.mobile_game_role_info, MobileGameRoleInfoConfig())
remove_unnecessary_configs(account_cfg.ark_lottery, ArkLotteryConfig())
remove_unnecessary_configs(account_cfg.vip_mentor, VipMentorConfig())
remove_unnecessary_configs(account_cfg.dnf_helper_info, DnfHelperInfoConfig())
remove_unnecessary_configs(account_cfg.hello_voice, HelloVoiceInfoConfig())
remove_unnecessary_configs(account_cfg.firecrackers, FirecrackersConfig())
show_config_size(cfg, "精简后")
save_filename = 'config.toml.github_action'
save_config(cfg, save_filename)
logger.info(f"已经保存到 {save_filename}")
def show_config_size(cfg: Config, ctx):
data_to_save = json.loads(json.dumps(to_raw_type(cfg)))
toml_str = toml.dumps(data_to_save)
total_size = len(toml_str)
total_lines = toml_str.count('\n')
logger.info(f"{ctx} 生成配置文件大小为{total_size},总行数为{total_lines}")
def remove_unnecessary_configs(cfg, default_cfg):
attrs_to_remove = []
for attr, value in cfg.__dict__.items():
if not hasattr(default_cfg, attr) or getattr(default_cfg, attr) == value:
attrs_to_remove.append(attr)
for attr in attrs_to_remove:
delattr(cfg, attr)
def save_config(cfg: Config, config_path="config.toml"):
with open(config_path, 'w', encoding='utf-8') as save_file:
data_to_save = json.loads(json.dumps(to_raw_type(cfg)))
toml.dump(data_to_save, save_file)
def config():
return g_config
if __name__ == '__main__':
load_config("config.toml", "config.toml.local")
logger.info(config().common.account_count)
cfg = config()
# cfg.common.auto_update_on_start = True
# save_config(cfg)
# gen_config_for_github_action()