-
Notifications
You must be signed in to change notification settings - Fork 5
/
todolist.js
403 lines (359 loc) · 15.2 KB
/
todolist.js
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
// ==UserScript==
// @name 洛谷超级任务计划(第三方)
// @namespace http://tampermonkey.net/
// @version 1.12
// @description 洛谷超级任务计划(第三方),不限题目数量
// @author Anguei, Legendword
// @match https://www.luogu.org/problemnew/show/*
// @match https://www.luogu.org/
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
// 可能将要实现的功能:
// 1. 添加异常处理,如 get 请求没有 200
// 2. 显示当前任务计划总数
// 3. 在只知道题号的情况下进行导入
// 感谢 @memset0 提供创意
// 感谢 @Legendword 协助完成 jQuery 相关代码
// 感谢 @memset0, @Legendword 帮助找 bug
var originalLimit = 28;
var nowUrl = window.location.href;
var LuoguSuperTodolist = {
settings: {
keepOriginalList: false,
debugMode: false // 发布前将此设为 false
}
};
// var myUid = document.cookie.match(/_uid=[0-9]+/)[0].substr(5);
// console.log(myUid)
// console.log('如果上面获取到的 uid 不正确,请反馈作者,谢谢')
var myUid = GM_getValue("myUid");
if (myUid == undefined || myUid == 'undefined')
myUid = prompt("任务计划脚本更新,请正确输入您的 uid(数字)以保障插件正常运行");
GM_setValue("myUid", myUid);
function getOriginalList() {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://www.luogu.org/', false);
xhr.send(null);
if (xhr.status == 200) {
console.log('get original todo list: 200');
return extractData(xhr.responseText);
} else {
return {};
}
function extractData(content) {
var psid = content.split('" target="_blank"><b>');
if (psid[0].indexOf("还没有计划完成的题目<br>") != -1) {
return {};
}
return clearData(psid);
function clearData(psid) { // psid: problems' id
var res = {}
for (var i = 1; i < psid.length; i++) { // 从 1 开始循环,因为 split 导致 psid[0] 是垃圾文本串
var pId = '', pName = '', j = 0;
for (; j < psid[i].length; j++) { // 获取题号
if (psid[i][j] != '<') {
pId = pId.concat(psid[i][j]);
} else break;
}
for (j += 5; j < psid[i].length; j++) { // 获取题目名称
if (psid[i][j] != '<') {
pName = pName.concat(psid[i][j]);
} else break;
}
res[pId] = pName;
if (psid[i].match(/智能推荐/) != null) break;
}
return res;
}
}
}
function syncList() {
console.log('syncing');
LuoguSuperTodolist.problems = getOriginalList();
initList(); // 把洛谷原计划保存到脚本
function initList() {
var old = GM_getValue('problems');
if (old != undefined || old != 'undefined') {
for (var i in old) {
LuoguSuperTodolist.problems[i] = old[i]
}
}
GM_setValue('problems', LuoguSuperTodolist.problems);
}
}
function getAc(uid) { // 从原来代码复制过来的
// 向指定的个人空间发送 get 请求,获取 AC 列表
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://' + window.location.host + '/space/show?uid=' + uid, false);
xhr.send(null);
console.log('got ' + uid + "'s AC list: " + xhr.status);
if (xhr.status == 200) {
return extractData(xhr.responseText); // 返回 AC 列表
} else {
return []; // 空列表
}
function extractData(content) {
// 如果你有一个问题打算用正则表达式来解决,那么就是两个问题了。
// 所以窝还是用 split() 解决这一个问题吧!
var acs = content.replace(/<span style=\"display:none\">\n.*?\n<\/span>/g, ''); // 把随机的干扰题号去除
acs = acs.split('[<a data-pjax href="/problem/show?pid='); // 使用 split() 方法把通过的题目分割出来
acs = clearData(acs); // 把分割好的数据清洁一下
return acs;
function clearData(acs) {
var res = new Array();
res.push(new Array());
res.push(new Array());
var g = 0;
for (var i = 1; i < acs.length; i++) { // 把每一行非题号字符删掉(从 1 开始循环为了避开 split 之后产生的垃圾)
var tmpStr = "";
for (var j = 0; j < acs[i].length; j++) {
if (acs[i][j] != '"') { // 引号后面的不是题号部分字符
tmpStr = tmpStr.concat(acs[i][j]); // 拼接字符串
}
else break;
}
res[g].push(tmpStr);
if (acs[i].length > 50) { // 这是最后一个题目 / 下一个是「尝试过的题目」
g++;
}
}
return res;
}
}
}
function getDictLength(dict) {
var res = 0;
for (var i in dict) res++;
return res;
}
function updateMainPageList() {
var tmp = getAc(myUid);
var myAc = tmp[0], myAttempt = tmp[1];
myAc.sort();
myAttempt.sort();
// 清除官方的任务计划
if (!LuoguSuperTodolist.settings.keepOriginalList) {
$("h2:contains('智能推荐')").prevAll().remove();
// 当官方计划为空时,删除那句话
if ($("h2:contains('智能推荐')").parent().html().indexOf("<h2") > 0) {
$("h2:contains('智能推荐')").parent().html($("h2:contains('智能推荐')").parent().html().slice($("h2:contains('智能推荐')").parent().html().indexOf("<h2")));
}
}
// 在 Luogu 官方任务计划后面添加第三方计划
var problems = GM_getValue('problems')
$("h2:contains('智能推荐')").before('<h2>任务计划 (' + getDictLength(problems) + ' 题)' + '<button class="am-btn am-btn-sm am-btn-primary lg-right" id="LuoguSuperTodolist-export">编辑</button></h2>');
for (var i in problems) {
var state = getState(i);
var color = { 'Y': 'green', 'N': 'black', '?': 'orange' };
var content = { 'Y': '<i class="am-icon-check"></i>', 'N': '<i class="am-icon-minus"></i>', '?': '?' };
$("h2:contains('智能推荐')").before(
'<div class="tasklist-item LuoguSuperTodolist-tasklist-item" data-pid="'
+ i
+ '"><div><a href="/recordnew/lists?uid='
+ myUid
+ '&pid='
+ i
+ '" target="_blank"><strong class="lg-fg-'
+ color[state]
+ '">' + content[state]
+ '</strong></a>'
+ '<a class="colored" style="padding-left: 3px" href="/problemnew/show/'
+ i
+ '" target="_blank"><b>'
+ i
+ '</b> '
+ problems[i]
+ '</a></div></div>'
);
}
$("#LuoguSuperTodolist-export").click(function () {
if (LuoguSuperTodolist.exportOpen) { // 响应「完成」按钮
/*while (!importProblem()) {
$("#edit-problem")[0].value = generateExportedList(); // 恢复原文本串
} // 直到成功导入*/
importProblem();
$("#LuoguSuperTodolist-export").html("编辑");
$("#LuoguSuperTodolist-exportRes").remove();
} else { // 响应「导入 / 导出」按钮
var listString = generateExportedList();
$("h2:contains('任务计划')").after(
"<div id='LuoguSuperTodolist-exportRes'>编辑下方文本进行编辑操作。</br>格式:[题号] + '#'(井号) + [题目标题],一行一题。</br>拖动右下角可以改变编辑框大小。"
+ "<div class='am-form-group am-form'><textarea id='edit-problem'>"
+ listString
+ "</textarea></div>"
);
console.log(listString);
$("#LuoguSuperTodolist-export").html("完成");
}
LuoguSuperTodolist.exportOpen ^= 1; // 使用异或运算切换状态,简便快捷
function importProblem() { // 尚不支持检测题号是否合法
var input = $("#edit-problem").val()
input = input.split('\n'); // jQuery 自带 split 有问题,必须用 string 的是 split
console.log(input);
// var problems = LuoguSuperTodolist.problems;
var problems = {}; // for oier sp
for (var i = 0; i < input.length; i++) {
if (!checkString(input[i])) {
alert('输入数据不合法!')
return false;
}
input[i] = input[i].split(' # '); // 注意这里不能是单纯的井号
if (problems[input[i][0]] == undefined) {
problems[input[i][0]] = input[i][1];
console.log(problems[input[i][0]]);
}
}
LuoguSuperTodolist.problems = problems;
console.log(LuoguSuperTodolist.problems);
GM_setValue('problems', LuoguSuperTodolist.problems);
updateMainPageList(); // 导入成功后需要更新显示
function checkString(s) { // 计算字符串中 # 号数量,检查格式
var pos = -1;
for (var i = 0; i < s.length; i++) {
if (s[i] == '#') {
if (pos == -1) {
pos = i;
} else {
pos = -2;
}
}
}
return pos > 0 && s[pos - 1] == ' ' && s[pos + 1] == ' ';
}
}
function generateExportedList() {
var problems = LuoguSuperTodolist.problems;
var res = "";
for (var c in problems) {
res += "\n" + c + " # " + problems[c];
}
return res;
}
});
function getState(pid) {
if (binarySearch(pid, myAc)) return 'Y';
else if (binarySearch(pid, myAttempt)) return '?';
else return 'N';
}
function binarySearch(target, array) { // 使用二分查找算法进行比较
var l = 0, r = array.length;
while (l < r) {
var mid = parseInt((l + r) / 2); // JavaScript 除法默认不是整数。。
if (target == array[mid]) return true;
else if (target > array[mid]) l = mid + 1;
else r = mid;
}
return false;
}
}
function addButton() {
$('#remove-tasklist').remove();
$('#add-tasklist').remove(); // 移除旧的按钮
var problemId = nowUrl.match(/[A-Z]+[0-9]+/)[0];
if (nowUrl.match(/[A-Z]+[0-9]+[A-Z]/) != null) {
problemId = nowUrl.match(/[A-Z]+[0-9]+[A-Z]/)[0]; // CF 题库特判
}
console.log(problemId);
var problemTitle = getTitle();
if (!isInList()) {
$(".lg-summary-content")
.append('<p>'
+ '<a href="javascript: ;" '
+ 'id="update-todolist" '
+ 'class="am-btn am-btn-sm am-btn-primary">'
+ '添加至超级任务计划'
+ '</a>'
+ '</p>');
$("#update-todolist").click(swapState);
} else {
$(".lg-summary-content")
.append('<p>'
+ '<a href="javascript: ;" '
+ 'id="update-todolist" '
+ 'class="am-btn am-btn-sm am-btn-danger">'
+ '从任务计划移除'
+ '</a>'
+ '</p>');
$("#update-todolist").click(swapState);
}
function getTitle() {
return document.title.substr((problemId.length + 1), document.title.length - (problemId.length + 1) - 5)
}
function isInList() {
var nowList = GM_getValue('problems');
if (nowList == undefined) return false;
return nowList[problemId] != undefined;
}
function swapState(ev) { // 是不是应该改成检测现在的属性,然后交换属性
if (isInList()) { // 已经在任务计划列表,删掉它。变成蓝色按钮
removeFromList(problemId, problemTitle);
$("#update-todolist").attr("class", "am-btn am-btn-sm am-btn-primary");
$("#update-todolist").html("添加至超级任务计划");
} else { // 不在任务计划当中,加进去,变成红色按钮
addToList(problemId, problemTitle)
$("#update-todolist").attr("class", "am-btn am-btn-sm am-btn-danger");
$("#update-todolist").html("从任务计划移除");
}
function addToList(id, title) {
var nowList = GM_getValue('problems');
nowList[id] = title;
GM_setValue('problems', nowList);
LuoguSuperTodolist.problems = GM_getValue('problems')
if (getDictLength(getOriginalList()) < originalLimit) { // 原计划长度足够装下新题目,装进去
$.post("/api/user/tasklistAdd", { pid: id });
}
}
function removeFromList(id, title) {
var nowList = GM_getValue('problems');
delete nowList[id];
GM_setValue('problems', nowList);
LuoguSuperTodolist.problems = GM_getValue('problems')
var originalList = getOriginalList();
if (findInDict(id, originalList)) { // 删除的题目在原计划当中,在原计划也删除
$.post("/api/user/tasklistRemove", { pid: id })
}
checkOther(); // 看剩余空间够不够再同步几道题进去
function findInDict(target, dict) {
for (var i in dict) if (i == target) return true;
return false;
}
function checkOther() {
var diff = originalLimit - (getDictLength(originalList) - 1); // 长度 - 1 是因为刚才删掉了一个
console.log(diff);
if (diff > 0) {
var cnt = 0;
var arr = new Array();
for (var i in LuoguSuperTodolist.problems) {
if (!findInDict(i, originalList)) { // superList 当中不在 originalList 的题目
arr.push(i);
cnt += 1;
if (cnt == diff) break;
}
}
for (var i = 0; i < arr.length; i++) {
$.post("/api/user/tasklistAdd", { pid: arr[i] });
}
}
}
}
}
}
function start() {
var lastUse = GM_getValue('lastUse');
var date = Date();
date = date.split(' ');
date = date[0] + date[1];
GM_setValue('lastUse', date);
if (lastUse == undefined || lastUse != date || LuoguSuperTodolist.settings.debugMode) { // 首次运行脚本,将原任务计划保存
console.log('更新后首次运行脚本,请耐心等待初始化');
syncList();
}
LuoguSuperTodolist.problems = GM_getValue('problems')
if (nowUrl == 'https://www.luogu.org/') {
updateMainPageList(); // 更新主页的 todolist
} else if (nowUrl.match(/problem/) != null) { // 题目页面运行脚本,添加按钮
addButton();
}
}
start();