forked from IceTiki/ruoli-sign-optimization
-
Notifications
You must be signed in to change notification settings - Fork 0
/
handler.py
483 lines (435 loc) · 16.9 KB
/
handler.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
import random
import traceback
import os
from liteTools import UserDefined, LL, TT, DT, HSF, ST, RT, ProxyGet, TaskError
from actions.teacherSign import teacherSign
from actions.workLog import workLog
from actions.sleepCheck import sleepCheck
from actions.collection import Collection
from actions.autoSign import AutoSign
from actions.sendMessage import SendMessage
from todayLoginService import TodayLoginService
class SignTask:
userSessions = {}
codeHeadCounts = 5
statusMsg_lite = {
0: '待命',
1: '完成',
2: '跳过',
3: '出错',
4: '缺失',
}
statusMsg = {
0: "等待执行",
1: "出现错误(等待重试)",
100: "任务已被完成",
101: "该任务正常执行完成",
200: "用户设置不执行该任务",
201: "该任务不在执行时间",
300: "出错",
301: "当前情况无法完成该任务",
400: "没有找到需要执行的任务"
}
def __init__(self, userConfig: dict, maxTry: int = 3):
'''
:params userConfig: 用户配置
:params maxTry: 最大尝试次数
'''
self.config: dict = userConfig
self.msg: str = ""
self.code: int = 0
self.maxTry: int = int(maxTry) # 最大触发次数
self.attempts: int = 0 # 任务触发次数, 当达到最大触发次数, 可能哪怕执行失败也会触发消息推送等
self.username = userConfig.get("username", "?username?")
# 检查任务是否在执行时间
if TT.isInTimeList(userConfig['taskTimeRange']):
self.code = 0
else:
self.code = 201
self.msg = '该任务不在执行时间'
def execute(self):
'''
任务执行函数(含异常处理、重试次数等)
'''
# 检查是否已经完成该任务
if not self.codeHead == 0:
return
self.attempts += 1
LL.log(1, '即将在第%d轮尝试中为[%s]签到' % (self.attempts, self.username))
# 执行签到
try:
# 执行前准备
self._beforeExecute()
# 执行签到任务
self._execute()
except TaskError as e:
self.code = e.code
self.msg = str(e)
except Exception as e:
self.code = 1
self.msg = f"[{e}]\n{traceback.format_exc()}"
LL.log(3, ST.notionStr(self.msg),
self.config['username']+'签到失败'+self.msg)
finally:
# 收尾工作
self._afterExecute()
def formatMsg(self, pattern: str = ""):
return ST.stringFormating(pattern, self.webhook)
def _login(self):
'''
登录, 更新self.session和self.host
'''
LL.log(1, '准备登录')
uuid = self.uuid
userSessions = SignTask.userSessions
if userSessions.get(uuid):
LL.log(1, '正在复用登录Session')
uSession = userSessions[uuid]['session']
uHost = userSessions[uuid]['host']
else:
LL.log(1, '正在尝试进行登录')
today = TodayLoginService(self.config)
today.login()
uSession = today.session
uHost = today.host
userSessions[uuid] = {
'session': uSession, 'host': uHost}
LL.log(1, '登录完成')
# 更新数据
self.session = uSession
self.host = uHost
return
def _beforeExecute(self):
'''
执行前准备工作
'''
# 随机延迟
RT.randomSleep(self.config['delay'])
# 用户自定义函数触发
event = {
"msg": f"『{self.username}』个人任务即将执行", # 触发消息
"from": "task start", # 触发位置
"code": 200,
}
UserDefined.trigger(event, self.webhook)
# 登录
self._login()
def _execute(self):
'''任务执行函数'''
type_ = self.config.get('type', None)
# 通过type判断当前属于 信息收集、签到、查寝
# 信息收集
if type_ == 0:
# 以下代码是信息收集的代码
LL.log(1, '即将开始信息收集填报')
collection = Collection(self)
collection.queryForm()
collection.fillForm()
msg = collection.submitForm()
elif type_ == 1:
# 以下代码是签到的代码
LL.log(1, '即将开始签到')
sign = AutoSign(self)
sign.getUnSignTask()
sign.getDetailTask()
sign.fillForm()
msg = sign.submitForm()
elif type_ == 2:
# 以下代码是查寝的代码
LL.log(1, '即将开始查寝填报')
check = sleepCheck(self)
check.getUnSignedTasks()
check.getDetailTask()
check.fillForm()
msg = check.submitForm()
elif type_ == 3:
# 以下代码是工作日志的代码
raise TaskError('工作日志模块已失效')
LL.log(1, '即将开始工作日志填报')
work = workLog(today, user)
work.checkHasLog()
work.getFormsByWids()
work.fillForms()
msg = work.submitForms()
elif type_ == 4:
# 以下代码是政工签到的代码
LL.log(1, '即将开始政工签到填报')
check = teacherSign(self)
check.getUnSignedTasks()
check.getDetailTask()
check.fillForm()
msg = check.submitForm()
else:
raise Exception('任务类型出错,请检查您的user的type')
self.msg = msg
return msg
def _afterExecute(self):
'''
执行后收尾工作
'''
LL.log(1, self.defaultFormatTitle + "\n" + self.defaultFormatMsg)
# 消息推送(当本任务最后一次执行时)
if self.codeHead != 0 or self.maxTry == self.attempts:
sm = self.sendMsg
sm.send(self.defaultFormatMsg, self.defaultFormatTitle)
LL.log(1, f"『{self.username}』用户推送情况", sm.log_str)
# 用户自定义函数触发
event = {
"msg": f"『{self.username}』个人任务执行完成", # 触发消息
"from": "task end", # 触发位置
"code": 201,
}
UserDefined.trigger(event, self.webhook)
@ property
def webhook(self):
return {
"scriptVersion": LL.prefix, # 脚本版本
"username": self.username, # 用户账号
"remarkName": self.config["remarkName"], # 用户备注名
"attempts": self.attempts, # 执行尝试次数
"maxtry": self.maxTry, # 最大尝试次数
"msg": self.msg, # 任务执行消息
"statusCode": self.code,
"statusCodehead": self.codeHead,
"statusMsg": self.statusMsg[self.code], # 状态信息(完整版)
"statusMsgLite": self.statusMsg_lite[self.codeHead], # 状态信息(短版)
}
@property
def defaultFormatTitle(self):
return self.formatMsg("『用户签到情况({statusMsgLite})[{scriptVersion}]』")
@property
def defaultFormatMsg(self):
return self.formatMsg("[{remarkName}|{username}]({attempts})\n>>{msg}")
@ property
def sendMsg(self):
'''
任务绑定的消息发送类
:returns SendMessage
'''
return SendMessage(self.config.get('sendMessage'))
@ property
def uuid(self):
'''
根据用户名和学校给每个用户分配一个uuid。
用于一个用户有多个任务时, 登录状态的Sesssion复用。
'''
return HSF.strHash(self.config.get('schoolName', '') + self.config.get('username', ''), 256)
@ property
def codeHead(self):
return int(self.code/100)
@ staticmethod
def cleanSession(uuid=None):
'''
清理用户Session, 如果uuid为空, 则清理全部Session; 否则清理对应uuid的Session。
'''
if not uuid:
SignTask.userSessions.clear()
else:
SignTask.userSessions.pop(uuid, None)
class MainHandler:
def __init__(self, entranceType: str, event: dict = {}, context: dict = {}):
'''
初始化
:params entranceType: 执行方式, 主要区分云函数与本地执行(因为云函数一般无法写入文件)
'''
self.entrance: str = entranceType
self.event: dict = event
self.context: dict = context
# ==========参数初始化==========
self.geneLogFile = True
self.configDir = "config.yml"
if self.entrance == "__main__" and event.get("args", {}).get("environment", None) == "qinglong":
# 如果运行环境是『青龙面板』
self.geneLogFile = False
LL.log(1, "当前环境为青龙面板, 脚本不自行生成日志文件, 请从日志管理页面获取日志")
self.configDir = event.get("args", {}).get("configfile", None)
LL.log(1, f"当前环境为青龙面板, 根据命令参数确定配置文件位置「{self.configDir}」")
elif self.entrance in ("handler", "main_handler"):
# 如果运行入口是『云函数』(不可写入文件)
self.geneLogFile = False
# ==========参数初始化==========
self.config: dict = self.loadConfig()
self._setMsgOut()
self._maxTry = self.config['maxTry']
self.taskList = [SignTask(u, self._maxTry)
for u in self.config['users']]
def execute(self):
'''
执行签到任务
'''
# 用户自定义函数触发
event = {
"msg": f"任务序列即将执行", # 触发消息
"from": "global start", # 触发位置
"code": 100,
}
UserDefined.trigger(event, self.webhook)
LL.log(1, "任务开始执行")
maxTry = self._maxTry
for tryTimes in range(1, maxTry+1):
'''自动重试'''
LL.log(1, '正在进行第%d轮尝试' % tryTimes)
for task in self.taskList:
'''遍历执行任务'''
# 执行
task.execute()
# 清理无用session
self._cleanSession(task.uuid)
# 清理session池
SignTask.cleanSession()
# 签到情况推送
LL.log(1, self.defaultFormatTitle + "\n" + self.defaultFormatMsg)
sm = self.sendMsg
sm.send(msg=self.defaultFormatMsg, title=self.defaultFormatTitle, attachments=[(LL.msgOut.log.encode(encoding='utf-8'),
TT.formatStartTime("LOG#t=%Y-%m-%d--%H-%M-%S##.txt"))])
LL.log(1, '全局推送情况', sm.log_str)
# 用户自定义函数触发
event = {
"msg": f"任务序列执行完毕", # 触发消息
"from": "global end", # 触发位置
"code": 101,
}
UserDefined.trigger(event, self.webhook)
LL.log(1, "==========函数执行完毕==========")
def formatMsg(self, pattern: str = ""):
return ST.stringFormating(pattern, self.webhook)
def _cleanSession(self, uuid: str):
'''
登录状态内存释放: 如果同用户还有没有未执行的任务, 则删除session
'''
for i in self.taskList:
if i.code == 0 and i.uuid == uuid:
break
else:
SignTask.cleanSession(uuid)
def _setMsgOut(self):
'''
设置日志输出
:returns msgOut: FileOut
'''
if self.geneLogFile:
logDir = self.config.get('logDir')
if type(logDir) == str:
logDir = os.path.join(logDir, TT.formatStartTime(
"LOG#t=%Y-%m-%d--%H-%M-%S##.txt"))
LL.msgOut.setFileOut(logDir)
return
else:
return
def loadConfig(self):
'''
配置文件载入
:returns config: dict
'''
# 检查config.yml是否存在
if not os.path.isfile(self.configDir):
if os.path.isfile("config.yml.sample"):
raise Exception(
"读取配置文件出错, 请将「config.yml.sample」重命名为「config.yml」")
elif os.path.isfile("sample_config.yml"):
raise Exception(
"读取配置文件出错, 请将「sample_config.yml」重命名为「config.yml」")
else:
raise Exception("读取配置文件出错, 未找到「config.yml」")
# 读取config.yml
try:
config = DT.loadYml(self.configDir)
except Exception as e:
errmsg = f"""读取配置文件出错
请尝试检查配置文件(建议下载VSCode并安装yaml插件进行检查)
错误信息: {e}"""
LL.log(4, ST.notionStr(errmsg))
raise e
# 全局配置初始化
defaultConfig = {
'delay': (5, 10),
'locationOffsetRange': 50,
"shuffleTask": False
}
defaultConfig.update(config)
config.update(defaultConfig)
# 用户配置初始化
if config['shuffleTask']:
LL.log(1, "随机打乱任务列表")
random.shuffle(config['users'])
for user in config['users']:
LL.log(1, f"正在初始化{user['username']}的配置")
user: dict
# 初始化静态配置项目
defaultConfig = {
'remarkName': '默认备注名',
'model': 'OPPO R11 Plus',
'appVersion': '9.0.14',
'systemVersion': '4.4.4',
'systemName': 'android',
"signVersion": "first_v3",
"calVersion": "firstv",
'taskTimeRange': "1-7 1-12 1-31 0-23 0-59",
'getHistorySign': False,
'title': 0,
'signLevel': 1,
'abnormalReason': "回家",
'qrUuid': None,
'delay': config['delay']
}
defaultConfig.update(user)
user.update(defaultConfig)
# 用户设备ID
user['deviceId'] = user.get(
'deviceId', RT.genDeviceID(user.get('schoolName', '')+user.get('username', '')))
# 用户代理
user.setdefault('proxy')
user['proxy'] = ProxyGet(user['proxy'])
# 坐标随机偏移
user['global_locationOffsetRange'] = config['locationOffsetRange']
if 'lon' in user and 'lat' in user:
user['lon'], user['lat'] = RT.locationOffset(
user['lon'], user['lat'], config['locationOffsetRange'])
return config
@property
def webhook(self):
codecount = self.codeCount
return {
"taskcount_all": sum(codecount), # 全部任务数
"taskcount_todo": codecount[0], # 待命任务数
"taskcount_done": codecount[1], # 完成任务数
"taskcount_skip": codecount[2], # 跳过任务数
"taskcount_error": codecount[3], # 出错任务数
"taskcount_notFound": codecount[4], # 缺失任务数(没有找到相关任务)
# 被执行任务数(没有被跳过的任务)
"taskcount_executed": sum(codecount) - codecount[2],
"scriptVersion": LL.prefix, # 脚本版本
"runTime": TT.formatStartTime(), # 脚本启动时间(%Y-%m-%d %H:%M:%S格式)
"usedTime": TT.executionSeconds(), # 运行消耗时间(浮点数, 单位:秒)
# 一个列表, 包含所有任务的webhook参数
"taskWebhook": [i.webhook for i in self.taskList],
}
@property
def defaultFormatTitle(self):
return self.formatMsg("『全局签到情况({taskcount_done}/{taskcount_executed})[{scriptVersion}]』")
@property
def defaultFormatMsg(self):
userMsg = []
for i in self.taskList:
if i.codeHead != 2:
userMsg.append(i.defaultFormatMsg)
return self.formatMsg("\n".join([
"\n".join(userMsg),
"运行于{runTime}, 用时{usedTime}秒",
"{taskcount_all}任务| {taskcount_todo}待命, {taskcount_done}完成, {taskcount_skip}跳过, {taskcount_error}错误, {taskcount_notFound}缺失"
]))
@ property
def codeCount(self):
"""状态码统计"""
codeList = [i.codeHead for i in self.taskList]
codeCount = [0]*SignTask.codeHeadCounts
for i in codeList:
codeCount[i] += 1
return codeCount
@ property
def sendMsg(self):
'''
全局绑定的消息发送类
:returns SendMessage
'''
return SendMessage(self.config.get('sendMessage'))