This repository has been archived by the owner on Nov 25, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 17
/
WorkCommand.cs
338 lines (307 loc) · 14.7 KB
/
WorkCommand.cs
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
using CxSignHelper;
using CxSignHelper.Models;
using McMaster.Extensions.CommandLineUtils;
using Newtonsoft.Json.Linq;
using Serilog;
using System;
using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using Serilog.Core;
using Websocket.Client;
namespace cx_auto_sign
{
[Command(Description = "工作模式, 监听签到任务并自动签到")]
public class WorkCommand : CommandBase
{
private static readonly DateTime DateTime1970 = new(1970, 1, 1, 8, 0, 0);
// ReSharper disable UnassignedGetOnlyAutoProperty
[Option("-u", Description = "指定用户名(学号)")]
private string Username { get; }
// ReSharper restore UnassignedGetOnlyAutoProperty
private WebsocketClient _ws;
protected override async Task<int> OnExecuteAsync(CommandLineApplication app)
{
var appConfig = new AppDataConfig();
var user = Username ?? appConfig.DefaultUsername;
if (user == null)
{
Log.Error("没有设置用户,可以使用 -u 指定用户");
return 1;
}
var userConfig = new UserDataConfig(user);
var auConfig = new UserConfig(appConfig, userConfig);
var username = userConfig.Username;
var password = userConfig.Password;
var fid = userConfig.Fid;
Log.Information("正在登录账号:{Username}", username);
var client = await CxSignClient.LoginAsync(username, password, fid);
Log.Information("成功登录账号");
var (imToken, uid) = await client.GetImTokenAsync();
var enableWeiApi = false;
var webApi = userConfig.WebApi;
if (webApi != null)
{
string rule = null;
// ReSharper disable once ConvertIfStatementToSwitchStatement
if (webApi.Type == JTokenType.Boolean)
{
if (webApi.Value<bool>())
{
rule = "http://localhost:5743";
}
}
else if (webApi.Type == JTokenType.String)
{
rule = webApi.Value<string>();
}
if (rule != null)
{
// 启动 WebApi 服务
enableWeiApi = true;
Log.Information("启动 WebApi 服务");
WebApi.Startup.Rule = rule;
WebApi.IntervalData.Status = new WebApi.Status
{
Username = username,
CxAutoSignEnabled = true
};
_ = Task.Run(() => { WebApi.Program.Main(null); });
}
}
// 创建 Websocket 对象,监听消息
var exitEvent = new ManualResetEvent(false);
var url = new Uri("wss://im-api-vip6-v2.easemob.com/ws/032/xvrhfd2j/websocket");
using (_ws = new WebsocketClient(url, () => new ClientWebSocket
{
Options =
{
KeepAliveInterval = TimeSpan.FromMilliseconds(-1)
}
}))
{
_ws.ReconnectionHappened.Subscribe(info =>
Log.Warning("CXIM: Reconnection happened, type: {Type}", info.Type));
async void OnMessageReceived(ResponseMessage msg)
{
var startTime = GetTimestamp();
try
{
Log.Information("CXIM: Message received: {Message}", msg);
if (msg.Text.StartsWith("o"))
{
Log.Information("CXIM 登录");
var loginPackage = Cxim.BuildLoginPackage(uid, imToken);
Log.Information("CXIM: Message send: {Message}", loginPackage);
_ws.Send(loginPackage);
return;
}
if (!msg.Text.StartsWith("a"))
{
return;
}
var arrMsg = JArray.Parse(msg.Text[1..]);
foreach (var message in arrMsg)
{
Logger log = null;
try
{
var pkgBytes = Convert.FromBase64String(message.Value<string>());
if (pkgBytes.Length <= 5)
{
continue;
}
var header = new byte[5];
Array.Copy(pkgBytes, header, 5);
if (!header.SequenceEqual(new byte[] { 0x08, 0x00, 0x40, 0x02, 0x4a }))
{
continue;
}
if (pkgBytes[5] != 0x2b)
{
Log.Warning("可能不是课程消息");
continue;
}
Log.Information("接收到课程消息");
string chatId;
try
{
chatId = Cxim.GetChatId(pkgBytes);
}
catch (Exception e)
{
throw new Exception("解析失败,无法获取 ChatId", e);
}
log = Notification.CreateLogger(auConfig, GetTimestamp());
log.Information("消息时间:{Time}", startTime);
log.Information("ChatId: {ChatId}", chatId);
var course = userConfig.GetCourse(chatId);
log.Information("获取 {CourseName} 签到任务中", course.CourseName);
var courseConfig = new CourseConfig(appConfig, userConfig, course);
var tasks = await client.GetSignTasksAsync(course.CourseId, course.ClassId);
if (tasks.Count == 0)
{
Log.Error("没有活动任务");
log = null;
continue;
}
var task = tasks[0];
var taskTime = task["startTime"]!.Value<long>();
log.Information("任务时间: {Time}", taskTime);
var takenTime = startTime - taskTime;
log.Information("消息与任务相差: {Time}ms", takenTime);
if (takenTime > 5000)
{
// 当教师发布作业的等操作也触发「接收到课程消息」
// 但这些操作不会体现在「活动列表」中
// 因此,这里通过活动开始的时间来判断接收到的是否是活动消息
Log.Warning("不是活动消息");
log = null;
continue;
}
var type = task["type"];
if (type?.Type != JTokenType.Integer || type.Value<int>() != 2)
{
Log.Warning("不是签到任务");
log = null;
continue;
}
var activeId = task["id"]?.ToString();
if (string.IsNullOrEmpty(activeId))
{
Log.Error("解析失败,ActiveId 为空");
log = null;
continue;
}
log.Information("准备签到 ActiveId: {ActiveId}", activeId);
var data = await client.GetActiveDetailAsync(activeId);
var signType = GetSignType(data);
log.Information("签到类型:{Type}", GetSignTypeName(signType));
// ReSharper disable once ConvertIfStatementToSwitchStatement
if (signType == SignType.Gesture)
{
log.Information("手势:{Code}", data["signCode"]?.Value<string>());
}
else if (signType == SignType.Qr)
{
log.Warning("暂时无法二维码签到");
continue;
}
if (enableWeiApi && !WebApi.IntervalData.Status.CxAutoSignEnabled)
{
log.Information("因 WebApi 设置,跳过签到");
continue;
}
if (!courseConfig.SignEnable)
{
log.Information("因用户配置,跳过签到");
continue;
}
var signOptions = courseConfig.GetSignOptions(signType);
if (signOptions == null)
{
log.Warning("因用户课程配置,跳过签到");
continue;
}
if (signType == SignType.Photo)
{
signOptions.ImageId = await courseConfig.GetImageIdAsync(client, log);
log.Information("预览:{Url}",
$"https://p.ananas.chaoxing.com/star3/170_220c/{signOptions.ImageId}");
}
log.Information("签到准备完毕,耗时:{Time}ms",
GetTimestamp() - startTime);
takenTime = GetTimestamp() - taskTime;
log.Information("签到已发布:{Time}ms", takenTime);
var delay = courseConfig.SignDelay;
log.Information("用户配置延迟签到:{Time}s", delay);
if (delay > 0)
{
delay = (int) (delay * 1000 - takenTime);
if (delay > 0)
{
log.Information("将等待:{Delay}ms", delay);
await Task.Delay(delay);
}
}
log.Information("开始签到");
var ok = false;
var content = await client.SignAsync(activeId, signOptions);
switch (content)
{
case "success":
content = "签到完成";
ok = true;
break;
case "您已签到过了":
ok = true;
break;
default:
log.Error("签到失败");
break;
}
log.Information(content);
Notification.Status(log, ok);
}
catch (Exception e)
{
(log ?? Log.Logger).Error(e, "CXIM 接收到课程消息时出错");
}
finally
{
if (log != null)
{
Notification.Send(log);
}
}
}
}
catch (Exception e)
{
Log.Error(e, "CXIM 接收到消息处理时出错");
}
}
_ws.MessageReceived.Subscribe(OnMessageReceived);
await _ws.Start();
exitEvent.WaitOne();
}
Console.ReadKey();
return 0;
}
private static SignType GetSignType(JToken data)
{
var otherId = data["otherId"].Value<int>();
switch (otherId)
{
case 2:
return SignType.Qr;
case 3:
return SignType.Gesture;
case 4:
return SignType.Location;
default:
var token = data["ifphoto"];
return token?.Type == JTokenType.Integer && token.Value<int>() != 0
? SignType.Photo
: SignType.Normal;
}
}
private static string GetSignTypeName(SignType type)
{
return type switch
{
SignType.Normal => "普通签到",
SignType.Photo => "图片签到",
SignType.Qr => "二维码签到",
SignType.Gesture => "手势签到",
SignType.Location => "位置签到",
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
}
private static double GetTimestamp()
{
return (DateTime.Now - DateTime1970).TotalMilliseconds;
}
}
}