forked from Mapaler/PixivUserBatchDownload
-
Notifications
You must be signed in to change notification settings - Fork 0
/
PixivUserBatchDownload.user.js
3839 lines (3596 loc) · 130 KB
/
PixivUserBatchDownload.user.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
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
// ==UserScript==
// @name PixivUserBatchDownload
// @name:zh-CN P站画师个人作品批量下载工具
// @name:zh-TW P站畫師個人作品批量下載工具
// @name:zh-HK P站畫師個人作品批量下載工具
// @description Batch download pixiv user's images in one key.
// @description:zh-CN 配合Aria2,一键批量下载P站画师的全部作品
// @description:zh-TW 配合Aria2,一鍵批量下載P站畫師的全部作品
// @description:zh-HK 配合Aria2,一鍵批量下載P站畫師的全部作品
// @version 5.12.106
// @author Mapaler <[email protected]>
// @copyright 2016~2020+, Mapaler <[email protected]>
// @namespace http://www.mapaler.com/
// @icon https://www.pixiv.net/favicon.ico
// @homepage https://github.com/Mapaler/PixivUserBatchDownload
// @supportURL https://github.com/Mapaler/PixivUserBatchDownload/issues
// @updateURL https://greasyfork.org/scripts/17879/code/PixivUserBatchDownload.meta.js
//-@downloadURL https://greasyfork.org/scripts/17879/code/PixivUserBatchDownload.user.js
// @include *://www.pixiv.net/*
// @exclude *://www.pixiv.net/search.php*
// @exclude *://www.pixiv.net/upload.php*
// @exclude *://www.pixiv.net/messages.php*
// @exclude *://www.pixiv.net/ranking.php*
// @exclude *://www.pixiv.net/info.php*
// @exclude *://www.pixiv.net/ranking_report_user.php*
// @exclude *://www.pixiv.net/setting*
// @exclude *://www.pixiv.net/stacc*
// @exclude *://www.pixiv.net/premium*
// @exclude *://www.pixiv.net/discovery*
// @exclude *://www.pixiv.net/howto*
// @exclude *://www.pixiv.net/idea*
// @exclude *://www.pixiv.net/ads*
// @exclude *://www.pixiv.net/terms*
// @exclude *://www.pixiv.net/novel*
// @exclude *://www.pixiv.net/cate_r18*
// @exclude *://www.pixiv.net/manage*
// @exclude *://www.pixiv.net/report*
// @resource pubd-style https://github.com/Mapaler/PixivUserBatchDownload/raw/master/PixivUserBatchDownload%20ui.css?v=2020年7月9日
// @require https://cdn.staticfile.org/crypto-js/4.0.0/core.min.js
// @require https://cdn.staticfile.org/crypto-js/4.0.0/md5.min.js
// @grant unsafeWindow
// @grant window.close
// @grant window.focus
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
//-@grant GM_listValues
// @grant GM_addStyle
// @grant GM_getResourceText
//-@grant GM_getResourceURL
// @grant GM_addValueChangeListener
//-@grant GM_notification
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @connect pixiv.net
// @connect pximg.net
// @connect localhost
// @connect 127.0.0.1
// @noframes
// ==/UserScript==
/*jshint esversion: 6, shadow: true */
(function() {
'use strict';
//获取当前是否是本地开发状态
const mdev = Boolean(localStorage.getItem("pubd-dev"));
/*
* 公共变量区
*/
if (mdev) console.log("GM_info信息:",GM_info); //开发模式时显示meta数据
const scriptVersion = GM_info.script.version.trim(); //本程序的版本
const scriptIcon = GM_info.script.icon64 || GM_info.script.icon; //本程序的图标
const scriptName = (defaultName=>{ //本程序的名称
if (typeof(GM_info) != "undefined") //使用了扩展
{
if (GM_info.script.name_i18n)
{
return GM_info.script.name_i18n[navigator.language.replace("-","_")]; //支持Tampermonkey
} else
{
return GM_info.script.localizedName || //支持Greasemonkey 油猴子 3.x
GM_info.script.name; //支持Violentmonkey 暴力猴
}
}
return defaultName;
})('PixivUserBatchDownload');
const pubd = { //储存程序设置
configVersion: 1, //当前设置版本,用于提醒是否需要重置(未启用)
touch: false, //是手机版(未启用)
loggedIn: false, //登陆了(未启用)
start: null, //开始按钮指针
menu: null, //菜单指针
dialog: { //窗口们的指针
config: null, //设置窗口
login: null, //登陆窗口
downthis: null, //下载当前窗口
downillust: null, //下载当前作品窗口
},
auth: null, //储存账号密码
downSchemes: [], //储存下载方案
downbreak: false, //是否停止发送Aria2的flag
fastStarList: null, //储存快速收藏的简单数字
starUserlists: [], //储存完整的下载列表
};
//vue框架的root div
const vueRoot = document.querySelector("#root");
const wrapper = document.querySelector("#wrapper"); //仍然少量存在的老板页面
//储存vue框架下P站页面主要内容的DIV位置,现在由程序自行搜索判断,搜索依据为 mainDivSearchCssSelectorArray。
//后面的 :scope 基本都是指的 mainDiv
var mainDiv = null;
//#root下能够独占区分不同页面的路径
//本来开始按钮插入点可以另外设置,但是刚好可以用,于是就用了同一个了
const mainDivSearchCssSelectorArray = [
'#spa-contents .user-stats', //手机版用户页
'#spa-contents .user-details-card', //手机版作品页
':scope>div>div>div>div:nth-of-type(2)>div:nth-of-type(2)', //用户资料首页
':scope>div>div>div>div:nth-of-type(2)>div>div:nth-of-type(2)', //用户资料首页,版本2
':scope>div>div>aside>section', //作品页
':scope>div>div:nth-of-type(2)>div>div', //关注页
];
//作者页面“主页”按钮的CSS位置(用来获取作者ID)
const userMainPageCssPath = ":scope>div>div:nth-of-type(2)>nav>a";
//作品页,收藏按钮的CSS位置(用来获取当前作品ID)
const artWorkStarCssPath = ":scope>div>div>main>section>div>div>figcaption>div>div>ul>li:nth-of-type(2)>a";
//作品页,作者头像链接的CSS位置(用来获取作者ID)
const artWorkUserHeadCssPath = ":scope>div>div>aside>section>h2>div>a";
//匹配P站内容的正则表达式
const illustPattern = '(https?://([^/]+)/.+/\\d{4}/\\d{2}/\\d{2}/\\d{2}/\\d{2}/\\d{2}/(\\d+(?:-([0-9a-zA-Z]+))?(?:_p|_ugoira)))\\d+(?:_\\w+)?\\.([\\w\\d]+)'; //P站图片地址正则匹配式
const limitingPattern = '(https?://([^/]+)/common/images/(limit_(mypixiv|unknown)))_\\d+\\.([\\w\\d]+)'; //P站上锁图片完整地址正则匹配式
const limitingFilenamePattern = 'limit_(mypixiv|unknown)'; //P站上锁图片文件名正则匹配式
//Header使用
const PixivAppVersion = "5.0.200"; //Pixiv APP的版本
const AndroidVersion = "10.0.0"; //安卓的版本
const UA = "PixivAndroidApp/" + PixivAppVersion + " (Android " + AndroidVersion + "; Android SDK built for x64)"; //向P站请求数据时的UA
const X_Client_Hash_Salt = "28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c"; //X_Client加密的slat,目前是固定值
const Referer = "https://app-api.pixiv.net/";
const ContentType = "application/x-www-form-urlencoded; charset=UTF-8"; //重要
//登陆时的固定参数
const authURL = "https://oauth.secure.pixiv.net/auth/token";
const client_id = "MOBrBDS8blbauoSck0ZfDbtuzpyT"; //安卓版固定数据
const client_secret = "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj"; //安卓版固定数据
const device_token = "pixiv"; //每个设备不一样,不过好像随便写也没事
var thisPageUserid = null, //当前页面的画师ID
thisPageIllustid = null, //当前页面的作品ID
downIllustMenuId = null; //下载当前作品的菜单的ID(Tampermonker菜单内的指针)
/*
* 初始化数据库
*/
if (mdev)
{
const dbName = "PUBD";
var db;
const DBOpenRequest = indexedDB.open(dbName);
DBOpenRequest.onsuccess = function(event) {
db = event.target.result; //DBOpenRequest.result;
console.log("PUBD:数据库已可使用");
};
DBOpenRequest.onerror = function(event) {
// 错误处理
console.log("PUBD:数据库无法启用",event);
};
DBOpenRequest.onupgradeneeded = function(event) {
let db = event.target.result;
// 建立一个对象仓库来存储用户的相关信息,我们选择 user.id 作为键路径(key path)
// 因为 user.id 可以保证是不重复的
let usersStore = db.createObjectStore("users", { keyPath: "user.id" });
// 建立一个索引来通过姓名来搜索用户。名字可能会重复,所以我们不能使用 unique 索引
usersStore.createIndex("name", "user.name", { unique: false });
// 使用账户建立索引,我们确保用户的账户不会重复,所以我们使用 unique 索引
usersStore.createIndex("account", "user.account", { unique: true });
let illustsStore = db.createObjectStore("illusts", { keyPath: "id" });
illustsStore.createIndex("type", "type", { unique: false });
illustsStore.createIndex("userid", "user.id", { unique: false });
// 使用事务的 oncomplete 事件确保在插入数据前对象仓库已经创建完毕
illustsStore.transaction.oncomplete = function(event) {
console.log("PUBD:数据库建立完毕");
};
};
}
/*
* 获取初始状态
*/
//尝试获取旧版网页对象
if (typeof(unsafeWindow) != "undefined")
{ //原来的信息-除少部分页面外已失效2020年7月9日
const pixiv = unsafeWindow.pixiv;
if (pixiv != undefined)
{
if (mdev) console.log("PUBD:本页面存在 pixiv 对象:",pixiv);
thisPageUserid = parseInt(pixiv.context.userId);
if (pixiv.user.loggedIn)
{
pubd.loggedIn = true;
}
if (/touch/i.test(pixiv.touchSourcePath))
{
pubd.touch = true; //新版的手机页面也还是老板结构-2020年7月9日
document.body.classList.add('pubd-touch');
}
}
}
//尝试获取当前页面画师ID
const metaPreloadData = document.querySelector('#meta-preload-data'); //HTML源代码里有,会被前端删掉的数据
if (metaPreloadData != undefined) //更加新的存在于HTML元数据中的页面信息
{
pubd.loggedIn = true;
if (mdev) console.log("PUBD:本页面抢救出 metaPreloadData 对象:",metaPreloadData);
const preloadData = JSON.parse(metaPreloadData.content);
if (mdev) console.log("PUBD:metaPreloadData 中的 preloadData 元数据:",preloadData);
if (preloadData.user) thisPageUserid = parseInt(Object.keys(preloadData.user)[0]);
if (preloadData.illust) thisPageIllustid = parseInt(Object.keys(preloadData.illust)[0]); //必须判断是否存在,否则会出现can't convert undefined to object错误
}
//获取是否为老的手机版
if (location.host.includes("touch")) //typeof(pixiv.AutoView)!="undefined"
{
pubd.touch = true;
}
/*
* 自定义对象区
*/
//生成P站需要的时间格式,如 "2019-09-03T18:51:40+08:00"
Date.prototype.toPixivString = function() {
function pad(num, length=2)
{ //填充截取法补前导0
let ns = num.toString();
if (ns.length >= length)
return ns;
else
{ //这里用slice和substr均可
return (Array(length).join('0') + num.toString()).slice(-length);
}
}
const timezoneOffset = this.getTimezoneOffset(); //时区差值
const str =
[
this.getFullYear(), //年
this.getMonth()+1, //月
this.getDate() //日
].map(n=>pad(n)).join('-') +
'T' +
[
this.getHours(), //时
this.getMinutes(), //秒
this.getSeconds() //分
].map(n=>pad(n)).join(':') +
(timezoneOffset<=0?"+":"-") + //时区正负
[
timezoneOffset/60, //时区差时
timezoneOffset%60, //时区差分
].map(n=>pad(Math.floor(Math.abs(n)))).join(':');
return str;
};
/*
//一个被收藏的画师
class StarUser{
constructor(id){
const user = this;
user.id = id;
user.infoDone = false;
user.downDone = false;
user.userinfo = null;
user.illusts = null;
}
}
*/
//一个画师收藏列表
class UsersStarList{
constructor(title,userArr = []){
this.title = title;
this.users = new Set(Array.from(userArr));
}
add(userid)
{
if (isNaN(userid)) userid = parseInt(userid,10);
this.users.add(userid);
}
delete(userid)
{
if (isNaN(userid)) userid = parseInt(userid,10);
this.users.delete(userid);
}
has(userid)
{
if (isNaN(userid)) userid = parseInt(userid,10);
return this.users.has(userid);
}
toggle(userid)
{ //切换有无
if (isNaN(userid)) userid = parseInt(userid,10);
const _users = this.users;
if (_users.has(userid))
{
_users.delete(userid);
return false;
}else
{
_users.add(userid);
return true;
}
}
importArray(arr)
{
const arrMaxLength = 500000;
if (arr.length>arrMaxLength)
{
alert(`PUBD:收藏用户最多仅允许添加 ${arrMaxLength.toLocaleString()} 个数据。`);
arr = arr.splice(500000); //删除50万以后的
}
const _users = this.users;
arr.forEach(uid=>_users.add(uid));
}
exportArray()
{
return Array.from(this.users);
}
}
//一个本程序使用的headers数据
class HeadersObject{
constructor(obj){
const header = this;
const timeStr = new Date().toPixivString();
header["App-OS"] = "android";
header["App-OS-Version"] = AndroidVersion;
header["App-Version"] = PixivAppVersion;
header["User-Agent"] = UA;
header["Content-Type"] = ContentType; //重要
header["Referer"] = Referer; // jshint ignore:line
header["X-Client-Hash"] = CryptoJS.MD5(timeStr + X_Client_Hash_Salt).toString();
header["X-Client-Time"] = timeStr;
if (typeof(obj) == "object")
{
Object.entries(obj).forEach(entry=> header[entry[0]] = entry[1]);
}
}
}
//储存一项图片列表分析数据的对象
var Works = function(){
this.done = false; //是否分析完毕
this.item = []; //储存图片数据
this.break = false; //储存停止分析的Flag
this.runing = false; //是否正在运行的Flasg
this.next_url = ""; //储存下一页地址(断点续传)
};
//一个认证方案
var Auth = function (username, password, remember) {
this.response = null;
this.needlogin = false;
this.username = username || null;
this.password = password || null;
this.save_account = remember || false,
this.login_date = null; // jshint ignore:line
};
Auth.prototype.newAccount = function(username, password, remember) {
if (typeof(remember) == "boolean") this.save_account = remember;
this.username = username;
this.password = password;
};
Auth.prototype.loadFromAuth = function(auth) {
if (typeof(auth) == "string")
{
try
{
auth = JSON.parse(auth);
}catch(e)
{
console.error("读取的Auth数据是字符串,但非JSON。",e);
return;
}
}else if (auth == undefined)
{
return;
}
const _thisAuth = this;
Object.keys(_thisAuth).forEach(function(key){
if (typeof(auth[key]) != "undefined")
_thisAuth[key] = auth[key];
});
};
Auth.prototype.save = function() {
let saveObj = Object.assign({},this);
if (!saveObj.save_account) {
saveObj.username = "";
saveObj.password = "";
}
GM_setValue("pubd-auth", saveObj);
};
Auth.prototype.login = function(onload_suceess_Cb, onload_hasError_Cb, onload_notJson_Cb, onerror_Cb) {
let _thisAuth = this;
let postObj = new URLSearchParams();
postObj.set("client_id",client_id);//安卓某个版本的数据
postObj.set("client_secret",client_secret);//安卓某个版本的数据
postObj.set("grant_type","password");
postObj.set("username",_thisAuth.username);
postObj.set("password",_thisAuth.password);
postObj.set("device_token",device_token);
postObj.set("get_secure_url","true");
postObj.set("include_policy","true");
//登陆的Auth API
GM_xmlhttpRequest({
url: authURL,
method: "post",
responseType: "text",
headers: new HeadersObject(),
data: postObj.toString(),
onload: function(response) {
var jo;
try {
jo = JSON.parse(response.responseText);
} catch (e) {
console.error("登录失败,返回可能不是JSON格式,或本程序异常。", e, response);
onload_notJson_Cb(response);
return;
}
if (jo)
{
if (jo.has_error || jo.errors) {
console.error("登录失败,返回错误消息", jo);
onload_hasError_Cb(jo);
return;
} else { //登陆成功
_thisAuth.response = jo.response;
_thisAuth.login_date = new Date().getTime();
console.info("登陆成功", jo);
onload_suceess_Cb(jo);
return;
}
}
},
onerror: function(response) {
console.error("登录失败,网络请求发生错误", response);
onerror_Cb(response);
return;
}
});
};
//一个掩码
var Mask = function(name, logic, content){
this.name = name;
this.logic = logic;
this.content = content;
};
//一个下载方案
var DownScheme = function(name) {
//默认值
this.name = name ? name : "默认方案";
this.rpcurl = "http://localhost:6800/jsonrpc";
this.https2http = false;
this.downfilter = "";
this.savedir = "D:/PixivDownload/";
this.savepath = "%{illust.user.id}/%{illust.filename}%{page}.%{illust.extention}";
this.textout = "%{illust.url_without_page}%{page}.%{illust.extention}\n";
this.masklist = [];
};
DownScheme.prototype.maskAdd = function(name, logic, content) {
var mask = new Mask(name, logic, content);
this.masklist.push(mask);
return mask;
};
DownScheme.prototype.maskRemove = function(index) {
this.masklist.splice(index, 1);
};
DownScheme.prototype.loadFromJson = function(json) {
if (typeof(json) == "string") {
try {
json = JSON.parse(json);
} catch (e) {
console.error("读取的方案数据是字符串,但非JSON。",e);
return false;
}
}
const _this = this;
Object.keys(_this).forEach(function(key){
if (key=="masklist")
{
_this.masklist.length = 0; //清空之前的
json.masklist.forEach(function(mask){
_this.masklist.push(new Mask(mask.name, mask.logic, mask.content));
});
}else
{
_this[key] = json[key];
}
});
return true;
};
//创建菜单类
var pubdMenu = function(classname) {
//生成菜单项
function buildMenuItem(title, classname, callback, submenu) {
var item = document.createElement("li");
if (title == 0) //title为0时,只添加一条菜单分割线
{
item.className = "pubd-menu-line" + (classname ? " " + classname : "");
return item;
}
item.className = "pubd-menu-item" + (classname ? " " + classname : "");
//如果有子菜单则添加子菜单
if (typeof(submenu) == "object") {
item.classList.add("pubd-menu-includesub"); //表明该菜单项有子菜单
submenu.classList.add("pubd-menu-submenu"); //表明该菜单是子菜单
//a.addEventListener("mouseenter",function(){callback.show()});
//a.addEventListener("mouseleave",function(){callback.hide()});
item.appendChild(submenu);
item.subitem = submenu;
}else
{
item.subitem = null; //子菜单默认为空
}
//添加链接
var a = item.appendChild(document.createElement("a"));
a.className = "pubd-menu-item-a";
//添加图标
var icon = a.appendChild(document.createElement("i"));
icon.className = "pubd-icon";
//添加文字
var span = a.appendChild(document.createElement("span"));
span.className = "text";
span.innerHTML = title;
//添加菜单操作
if (typeof(callback) == "string") { //为字符串时,当作链接处理
a.target = "_blank";
a.href = callback;
} else if (typeof(callback) == "function") { //为函数时,当作按钮处理
item.addEventListener("click", callback);
//a.onclick = callback;
}
return item;
}
var menu = document.createElement("ul");
menu.className = "pubd-menu display-none" + (classname ? " " + classname : "");
menu.item = [];
//显示该菜单
menu.show = function() {
menu.classList.remove("display-none");
};
menu.hide = function() {
menu.classList.add("display-none");
};
//添加菜单项
menu.add = function(title, classname, callback, submenu) {
var itm = buildMenuItem(title, classname, callback, submenu);
this.appendChild(itm);
this.item.push(itm);
return itm;
};
//鼠标移出菜单时消失
menu.addEventListener("mouseleave", function(e) {
this.hide();
});
return menu;
};
//创建通用对话框类
var Dialog = function(caption, classname, id) {
//构建标题栏按钮
function buildDlgCptBtn(text, classname, callback) {
if (!callback) classname = "";
var btn = document.createElement("a");
btn.className = "dlg-cpt-btn" + (classname ? " " + classname : "");
if (typeof(callback) == "string") {
btn.target = "_blank";
btn.href = callback;
} else {
if (callback)
btn.addEventListener("click", callback);
}
var btnTxt = btn.appendChild(document.createElement("span"));
btnTxt.className = "dlg-cpt-btn-text";
btnTxt.innerHTML = text;
return btn;
}
var dlg = document.createElement("div");
if (id) dlg.id = id;
dlg.className = "pubd-dialog display-none" + (classname ? " " + classname : "");
//添加图标与标题
var cpt = dlg.appendChild(document.createElement("div"));
cpt.className = "caption";
dlg.icon = cpt.appendChild(document.createElement("i"));
dlg.icon.className = "pubd-icon";
var captionDom = cpt.appendChild(document.createElement("span"));
Object.defineProperty(dlg , "caption", {
get() {
return captionDom.textContent;
},
set(str) {
captionDom.innerHTML = str;
}
});
dlg.caption = caption;
//添加标题栏右上角按钮 captionButtons
var cptBtns = dlg.cptBtns = dlg.appendChild(document.createElement("div"));
cptBtns.className = "dlg-cpt-btns";
//添加按钮的函数
cptBtns.add = function(text, classname, callback) {
var btn = buildDlgCptBtn(text, classname, callback);
this.insertBefore(btn, this.firstChild);
return btn;
};
//添加关闭按钮
cptBtns.close = cptBtns.add("X", "dlg-btn-close", (function() {
dlg.classList.add("display-none");
}));
//添加内容区域
var content = dlg.content = dlg.appendChild(document.createElement("div"));
content.className = "dlg-content";
//窗口激活
dlg.active = function() {
if (!this.classList.contains("pubd-dlg-active")) { //如果没有激活的话才执行
var dlgs = document.querySelectorAll(".pubd-dialog"); //获取网页已经载入的所有的窗口
for (var dlgi = 0; dlgi < dlgs.length; dlgi++) { //循环所有窗口
if (dlgs[dlgi] != this)
{
dlgs[dlgi].classList.remove("pubd-dlg-active"); //取消激活
dlgs[dlgi].style.zIndex = parseInt(window.getComputedStyle(dlgs[dlgi], null).getPropertyValue("z-index")) - 1; //从当前网页最终样式获取该窗体z级,并-1.
}
}
this.classList.add("pubd-dlg-active"); //添加激活
this.style.zIndex = ""; //z级归零
}
};
//窗口初始化
dlg.initialise = function() { //窗口初始化默认情况下什么也不做,具体在每个窗口再设置
return;
};
//窗口显示
dlg.show = function(posX, posY, arg) {
if (posX) dlg.style.left = posX + "px"; //更改显示时初始坐标
if (posY) dlg.style.top = posY + "px";
dlg.initialise(arg); //对窗体进行初始化(激活为可见前提前修改窗体内容)
dlg.classList.remove("display-none");
dlg.active(); //激活窗口
};
//窗口隐藏
dlg.hide = function() { //默认情况下等同于关闭窗口
dlg.cptBtns.close.click();
};
//添加鼠标拖拽移动
var drag = dlg.drag = [0, 0]; //[X,Y] 用以储存窗体开始拖动时的鼠标相对窗口坐标差值。
//startDrag(cpt, dlg);
cpt.addEventListener("mousedown", function(e) { //按下鼠标则添加移动事件
var eX = e.pageX>0?e.pageX:0, eY = e.pageY>0?e.pageY:0; //不允许鼠标坐标向上、左超出网页。
drag[0] = eX - dlg.offsetLeft;
drag[1] = eY - dlg.offsetTop;
var handler_mousemove = function(e) { //移动鼠标则修改窗体坐标
var eX = e.pageX>0?e.pageX:0, eY = e.pageY>0?e.pageY:0; //不允许鼠标坐标向上、左超出网页。
dlg.style.left = (eX - drag[0]) + 'px';
dlg.style.top = (eY - drag[1]) + 'px';
};
var handler_mouseup = function(e) { //抬起鼠标则取消移动事件
document.removeEventListener("mousemove", handler_mousemove);
};
document.addEventListener("mousemove", handler_mousemove);
document.addEventListener("mouseup", handler_mouseup, { once: true });
});
//点击窗口任何区域激活窗口
dlg.addEventListener("mousedown", function(e) {
dlg.active();
});
return dlg;
};
//创建框架类
var Frame = function(title, classname) {
var frame = document.createElement("div");
frame.className = "pubd-frame" + (classname ? " " + classname : "");
var caption = frame.caption = frame.appendChild(document.createElement("div"));
caption.className = "pubd-frame-caption";
caption.innerHTML = title;
var content = frame.content = frame.appendChild(document.createElement("div"));
content.className = "pubd-frame-content";
frame.name = function() {
return this.caption.textContent;
};
frame.rename = function(newName) {
if (typeof(newName) == "string" && newName.length > 0) {
this.caption.innerHTML = newName;
return true;
} else
return false;
};
return frame;
};
//创建带Label的Input类
var LabelInput = function(text, classname, name, type, value, beforeText, title) {
var label = document.createElement("label");
if (text) label.appendChild(document.createTextNode(text));
label.className = classname;
if (title) label.title = title;
var ipt = label.input = document.createElement("input");
ipt.name = name;
ipt.id = ipt.name;
ipt.type = type;
ipt.value = value;
if (beforeText && label.childNodes.length>0)
label.insertBefore(ipt, label.firstChild);
else
label.appendChild(ipt);
return label;
};
//创建进度条类
var Progress = function(classname, align_right) {
//强制保留pos位小数,如:2,会在2后面补上00.即2.00
function toDecimal2(num, pos) {
var f = parseFloat(num);
if (isNaN(f)) {
return false;
}
f = Math.round(num * Math.pow(10, pos)) / Math.pow(10, pos);
var s = f.toString();
var rs = s.indexOf('.');
if (pos > 0 && rs < 0) {
rs = s.length;
s += '.';
}
while (s.length <= rs + pos) {
s += '0';
}
return s;
}
var progress = document.createElement("div");
progress.className = "pubd-progress" + (classname ? " " + classname : "");
if (align_right) progress.classList.add("pubd-progress-right");
progress.scaleNum = 0;
var bar = progress.appendChild(document.createElement("div"));
bar.className = "pubd-progress-bar";
var txt = progress.appendChild(document.createElement("span"));
txt.className = "pubd-progress-text";
progress.set = function(scale, pos, str) {
if (pos == undefined) pos = 2;
var percentStr = toDecimal2((scale * 100), pos) + "%";
scale = scale > 1 ? 1 : (scale < 0 ? 0 : scale);
this.scaleNum = scale;
bar.style.width = percentStr;
if (str)
txt.innerHTML = str;
else
txt.innerHTML = percentStr;
};
Object.defineProperty(progress , "scale", {
get() {
return this.scaleNum;
},
set(num) {
progress.set(num);
}
});
return progress;
};
//创建 卡片类
function InfoCard(datas) {
var cardDiv = this.dom = document.createElement("div");
cardDiv.className = "pubd-infoCard";
var thumbnailDiv = cardDiv.appendChild(document.createElement("div"));
thumbnailDiv.className = "pubd-infoCard-thumbnail";
var thumbnailImgDom = thumbnailDiv.appendChild(document.createElement("img"));
var infosDlDom = cardDiv.appendChild(document.createElement("dl"));
infosDlDom.className = "pubd-infoCard-dl";
Object.defineProperty(this , "thumbnail", {
get() {
return thumbnailImgDom.src;
},
set(url) {
thumbnailImgDom.src = url;
}
});
var infoObj;
this.reload = function() //重构Card文本区域
{
infosDlDom.classList.add('display-none');
for (let ci = infosDlDom.childNodes.length-1;ci >= 0;ci--)
{ //删掉所有老子元素
var x = infosDlDom.childNodes[ci];
x.remove();
x = null;
}
const fragment = document.createDocumentFragment();
Object.entries(infoObj).forEach(entry=>{
const dt = fragment.appendChild(document.createElement("dt"));
const dd = fragment.appendChild(document.createElement("dd"));
dt.appendChild(document.createTextNode(entry[0]));
if (entry[1]) dd.appendChild(document.createTextNode(entry[1]));
});
infosDlDom.appendChild(fragment);
infosDlDom.classList.remove('display-none');
};
Object.defineProperty(this , "infos", {
get() {
return infoObj;
},
set(obj) {
infoObj = obj;
this.reload();
}
});
this.infos = datas || {}; //使用传入data进行初始设定
}
//创建下拉框类
var Select = function(classname, name) {
var select = document.createElement("select");
select.className = "pubd-select" + (classname ? " " + classname : "");
select.name = name;
select.id = select.name;
select.add = function(text, value) {
var opt = new Option(text, value);
this.options.add(opt);
};
select.remove = function(index) {
var x = this.options.remove(index);
x = null;
};
return select;
};
//创建Aria2类
var Aria2 = (function() {
var jsonrpc_version = '2.0';
function get_auth(url) {
return url.match(/^(?:(?![^:@]+:[^:@\/]*@)[^:\/?#.]+:)?(?:\/\/)?(?:([^:@]*(?::[^:@]*)?)?@)?/)[1];
}
function request(jsonrpc_path, method, params, callback, priority) {
if (callback == undefined) callback = ()=>{};
var auth = get_auth(jsonrpc_path);
jsonrpc_path = jsonrpc_path.replace(/^((?![^:@]+:[^:@\/]*@)[^:\/?#.]+:)?(\/\/)?(?:(?:[^:@]*(?::[^:@]*)?)?@)?(.*)/, '$1$2$3'); // auth string not allowed in url for firefox
var request_obj = {
jsonrpc: jsonrpc_version,
method: method,
id: priority ? 1 : Date.now(),
};
if (params) request_obj.params = params;
if (auth && auth.indexOf('token:') == 0)
{
if (method == "system.multicall")
{ //多项目操作时单独设置token
params.forEach(function(param){
param.forEach(function(method){
method.params.unshift(auth);
});
});
}else
{
params.unshift(auth);
}
}
var headers = { "Content-Type": ContentType };
if (auth && auth.indexOf('token:') != 0) {
headers.Authorization = "Basic " + btoa(auth);
}
GM_xmlhttpRequest({
url: jsonrpc_path + "?tm=" + (new Date()).getTime().toString(),
method: "POST",
responseType: "text",
data: JSON.stringify(request_obj),
headers: headers,
onload: function(response) {
try {
var JSONreq = JSON.parse(response.response);
callback(JSONreq);
} catch (e) {
console.error("Aria2发送信息错误", e, response);
callback(false);
}
},
onerror: function(response) {
console.error(response);
callback(false);
}
});
}
return function(jsonrpc_path) {
const _this = this;
_this.jsonrpc_path = jsonrpc_path;
_this.addUri = function(uri, options, callback) {
request(_this.jsonrpc_path, 'aria2.addUri', [
[uri, ], options
], callback);
};
_this.addTorrent = function(base64txt, options, callback) {
request(_this.jsonrpc_path, 'aria2.addTorrent', [base64txt, [], options], callback);
};
_this.getVersion = function(callback) {
request(_this.jsonrpc_path, 'aria2.getVersion', [], callback, true);
};
_this.getGlobalOption = function(callback) {
request(_this.jsonrpc_path, 'aria2.getGlobalOption', [], callback, true);
};
_this.system = {
multicall:function(params,callback){
request(_this.jsonrpc_path, 'system.multicall', params, callback);
},
};
return this;
};
})();
/*
* 自定义函数区
*/
//仿GM_notification函数v1.2,发送网页通知。
//此函数非Debug用,为了替换选项较少但是兼容其格式的GM_notification插件
function GM_notification(text, title, image, onclick) {
const options = {};
let rTitle, rText;
let ondone, onclose;
const dataMode = Boolean(typeof(text) == "string"); //GM_notification有两种模式,普通4参数模式和option对象模式
if (dataMode)
{ //普通模式
rTitle = title;
rText = text;
options.body = text;
options.icon = image;
}else
{ //选项模式
const details = text;
rTitle = details.title;
rText = details.text;
if (details.text) options.body = details.text;
if (details.image) options.icon = details.image;
if (details.timeout) options.timestamp = details.timeout;
ondone = title;
onclose = image;
//if (details.highlight) options.highlight = details.highlight; //没找到这个功能
}
function sendNotification(general){
const n = new Notification(rTitle, options);
if (general)
{ //普通模式
if (onclick) n.onclick = onclick;
}else
{ //选项模式,这里和TamperMonkey API不一样,区分了关闭和点击。
if (ondone) n.onclick = ondone;
if (onclose) n.onclose = onclose;
}
}
// 先检查浏览器是否支持
if (!("Notification" in window)) {
alert(rTitle + "\r\n" + rText);
// 检查用户是否同意接受通知
} else if (Notification.permission === "granted") {
Notification.requestPermission(function(permission) {
sendNotification(dataMode);
});
}
// 否则我们需要向用户获取权限
else if (Notification.permission !== 'denied') {
Notification.requestPermission(function(permission) {