-
Notifications
You must be signed in to change notification settings - Fork 0
/
Aria2_Helper_for_115_from_Chao_QU.user.js
407 lines (357 loc) · 14.4 KB
/
Aria2_Helper_for_115_from_Chao_QU.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
// ==UserScript==
// @name Aria2 Helper for 115
// @name:zh-CN 115 网盘 Aria2 助手
// @namespace Watsilla
// @version 0.1.5
// @description Add 115 download links to Aria2 via RPC
// @description:zh-CN 直接将所选 115 下载链接发送至 Aria2
// @author Chao QU
// @match *://115.com/?ct=file*
// @encoding utf-8
// @grant GM_setClipboard
// @grant GM_xmlhttpRequest
// @grant GM_log
// @grant GM_notification
// @grant unsafeWindow
// @run-at document-end
// @license MIT
// @homepage https://quchao.com/entry/aira2-helper-for-115
// downloadURL https://github.com/QuChao/Watsilla/raw/master/userScripts/aria2HelperFor115.user.js
// ==/UserScript==
// @version 0.1.0 @ 2017-04-21: Initialize release.
// @version 0.1.1 @ 2017-04-22: One could still copy download link when failed to send it to Aria2.
// @version 0.1.2 @ 2017-04-24: Add comments on configuration items.
// @version 0.1.3 @ 2017-07-28: Fix download link fetching & copying issue on non-Chrome explorers.
// @version 0.1.4 @ 2017-10-18: Fix an issue that a batch task only sends the first file to Aria2.
// @version 0.1.5 @ 2018-06-19: New cookie and user-agent auth method is supported now. Fixed by MartianZ <[email protected]>.
// @inspiredBy https://greasyfork.org/en/scripts/7749-115-download-helper
// @inspiredBy https://github.com/robbielj/chrome-aria2-integration
/* jshint -W097 */
'use strict';
// Configs
let Configs = {
'debug_mode' : false, // 是否开启调试模式
'cookie' :'',
'split' :'1', //默认线程数为1
"sync_clipboard": true, // 是否将下载链接同步到剪贴板,部分浏览器(如 Safari )不支持
'use_http' : false, // 115 下载链接是否从 https 转换为 http (老版本 Aria2 需要)
"rpc_path" : 'http://localhost:6800/jsonrpc', // RPC 地址
"rpc_user" : '', // RPC 用户名(若设置密码,请填写至 token 项)
"rpc_token" : '' // RPC Token ,v1.18.4+ 支持,与用户名认证方式互斥
};
// Debug Func
let debug = Configs.debug_mode ? GM_log : function () {};
let emptyFunc = function () {};
// Aria2RPC
let Aria2RPC = (function ($win, $doc) {
// privates
// send
function _addTask() {
let rpcHeaders = {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
};
// auth method, pt.1
if ('' !== Configs.rpc_user) {
// user/password
rpcHeaders['Authorization'] = 'Basic ' + $win.btoa(Configs.rpc_user + ':' + Configs.rpc_token);
}
return function (link, options, loadHandler, errorHandler) {
// new task
let reqParams = {
'jsonrpc': '2.0',
'method' : 'aria2.addUri',
'id' : (+new Date()).toString(),
'params' : []
};
// auth method, pt.2
if ('' === Configs.rpc_user && '' !== Configs.rpc_token) {
// secret, since v1.18.4
reqParams.params.unshift('token:' + Configs.rpc_token);
}
// download link
if ('undefined' !== typeof link) {
// @todo: multiple sources?
reqParams.params.push([link]);
} else {
// link is required
//errorHandler({});
return;
}
// options
if ('undefined' !== typeof options) {
reqParams.params.push(options);
}
// send to aria2, @todo: support metalink?
GM_xmlhttpRequest({
method : 'POST',
url : Configs.rpc_path,
headers: rpcHeaders,
data : JSON.stringify(reqParams),
onload : loadHandler || emptyFunc,
onerror: errorHandler || emptyFunc
});
};
}
return {
// public
add: _addTask()
};
})(unsafeWindow, unsafeWindow.document);
// Queue Manager
let QueueManager = (function ($win, $doc) {
// constants
const STATUS_SENT_TO_ARIA2 = 1;
const STATUS_UNFINISHED = 0;
const STATUS_DOWNLOAD_FAILURE = -1;
const STATUS_LINK_FETCH_FAILURE = -2;
const STATUS_UNDOWNLOADABLE = -3;
// variable
// constructor
function Mgr(options) {
// options
this.options = Mgr.validateOptions(options);
// err msgs
this.errMsgs = [];
// get selected ones
let selectedNodes = $doc.getElementById('js_cantain_box').querySelectorAll('li.selected');
// build the queue
this.queue = Array.from(selectedNodes).map(function (node) {
return {
'out' : node.getAttribute('title'),
'code' : node.getAttribute('pick_code'),
'link' : null,
'cookie' : null,
// -3: , -2: failed to fetch link, -1: failed to download, 0: unfinished, 1: sent to aria2
'status': '1' === node.getAttribute('file_type') ? STATUS_UNFINISHED : STATUS_UNDOWNLOADABLE
};
}, this);
}
// static
Mgr.defaultOptions = {
'copyOnly': false
};
Mgr.validateOptions = function (options) {
// validation
for (let key in options) {
// skip the inherit ones
if (!options.hasOwnProperty(key)) {
continue;
}
if (!(key in Mgr.defaultOptions)) {
// check existence
throw Error('Invalid option: ' + key);
} else if (typeof options[key] !== typeof Mgr.defaultOptions[key]) {
// check type
throw Error('Invalid option type: ' + key);
}
}
// merge the options
return Object.assign({}, Mgr.defaultOptions, options);
};
// methods
Mgr.prototype.errorHandler = function (errCode, idx, resp) {
debug(resp);
this.errMsgs.push('File #' + idx + ': ');
this.errMsgs.push("\t" + 'File Info: ' + JSON.stringify(this.queue[idx]));
this.errMsgs.push("\t" + 'HTTP Status: ' + resp.status + ' - ' + resp.statusText);
let errMsg = 'Unknown';
if ('responseText' in resp) {
try {
let err = JSON.parse(resp.responseText);
errMsg = err.error.message;
} catch (e) {
errMsg = e;
}
} else if ('msg' in resp) {
errMsg = resp.msg;
}
this.errMsgs.push("\t" + 'Err Msg:' + errMsg);
// update the status
this.queue[idx].status = errCode;
this.next();
};
Mgr.prototype.downloadHandler = function (idx, resp) {
debug(resp);
if (200 === resp.status && 'responseText' in resp) {
// update the status
this.queue[idx].status = STATUS_SENT_TO_ARIA2;
this.next();
} else {
// failed
this.errorHandler.call(this, STATUS_DOWNLOAD_FAILURE, idx, resp);
}
};
Mgr.prototype.download = function (idx) {
// send to aria2
if (!this.options.copyOnly) {
Aria2RPC.add(this.queue[idx].link,
{
'out':this.queue[idx].out, //指定文件名,避免出现文件名空格消失
//'referer': $doc.URL,
'referer': 'http://115.com',
//'header' : ['Cookie: ' + this.queue[idx].cookie, 'User-Agent: ' + $win.navigator.userAgent]
'header' : [//'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0',
'User-Agent: '+$win.navigator.userAgent,
'Accept: */*',
'Cookie: ' + this.queue[idx].cookie,//+'; '+ Configs.cookie
],
'split':Configs.split
},
this.downloadHandler.bind(this, idx),
this.errorHandler.bind(this, STATUS_DOWNLOAD_FAILURE, idx)
);
} else {
// update the status, @todo: another status code?
this.queue[idx].status = STATUS_SENT_TO_ARIA2;
this.next();
}
};
Mgr.prototype.fetchLinkHandler = function (idx, raw_resp) {
debug(raw_resp.responseHeaders);
let header_arr = raw_resp.responseHeaders.trim().split(/[\r\n]+/);
var headerMap = {};
header_arr.forEach(function (line) {
var parts = line.split(': ');
var header = parts.shift();
var value = parts.join(': ');
headerMap[header] = value;
});
let set_cookie_string = headerMap["set-cookie"];
let final_cookie = set_cookie_string.split(';')[0];
debug(final_cookie);
let resp = JSON.parse(raw_resp.responseText);
if ('file_url' in resp) {
// update the link
this.queue[idx].link = Configs.use_http
? resp.file_url.replace('https://', 'http://') // http only?
: resp.file_url.replace('http://', 'https://'); // https
this.queue[idx].cookie = final_cookie;
this.next();
} else {
this.errorHandler.call(this, STATUS_LINK_FETCH_FAILURE, idx, resp);
}
};
Mgr.prototype.fetchLink = function (idx) {
// get the download link first
// $win.top.UA$.ajax({
debug('http://webapi.115.com/files/download?pickcode=' + this.queue[idx].code);
GM_xmlhttpRequest({
url : 'http://webapi.115.com/files/download?pickcode=' + this.queue[idx].code,
method : 'GET',
ignoreCache : true,
onload : this.fetchLinkHandler.bind(this, idx),
onerror : this.errorHandler.bind(this, STATUS_LINK_FETCH_FAILURE, idx)
})
};
Mgr.prototype.next = function () {
// check if it's the queue is empty
let nextIdx = this.queue.findIndex(function (file) {
return STATUS_UNFINISHED === file.status;
});
// handle the next file
if (-1 === nextIdx) {
let report = this.queue.reduce(function (accumulator, file) {
switch (file.status) {
// task finished
case STATUS_SENT_TO_ARIA2:
accumulator.finished += 1;
case STATUS_DOWNLOAD_FAILURE:
accumulator.links.push(file.link);
break;
// task finished
case STATUS_UNDOWNLOADABLE:
accumulator.undownloadable += 1;
break;
}
return accumulator;
}, {
'links' : [],
'finished' : 0,
'undownloadable': 0
});
let queueSize = this.queue.length;
let msg = [];
if (queueSize === report.undownloadable) {
msg.push('所选 ' + queueSize + ' 项类型均为目录,暂不支持。');
} else {
msg.push('所选 ' + queueSize + ' 项已处理完毕:');
if (!this.options.copyOnly) {
if (0 < report.finished) {
msg.push((queueSize === report.finished ? '全部' : '其中 ' + report.finished + ' 项') + '成功发送至 Aria2 进行下载。');
} else {
msg.push((0 === report.undownloadable ? '全部' : '其中 ' + (queueSize - report.undownloadable) + ' 项') + '发送至 Aria2 失败。');
}
}
if (this.options.copyOnly || Configs.sync_clipboard) {
let downloadLinks = report.links.join("\n");
if (false === /\sSafari\/\d+\.\d+\.\d+/.test($win.navigator.userAgent)) {
// sync to clipboard
GM_setClipboard(downloadLinks, 'text');
msg.push('下载地址已同步至剪贴板。');
} else if (this.options.copyOnly) {
prompt('本浏览器不支持访问剪贴板,请手动全选复制', downloadLinks);
}
}
if (0 < report.undownloadable) {
msg.push('另有 ' + report.undownloadable + ' 项类型为目录,暂不支持。');
}
}
// notify the user
GM_notification(msg.join("\n"));
if (this.errMsgs.length) {
throw Error(this.errMsgs.join("\n"));
}
} else if (null === this.queue[nextIdx].link) {
// fetch link with the code
this.fetchLink(nextIdx);
} else {
// download it
this.download(nextIdx);
}
};
return Mgr;
})(unsafeWindow, unsafeWindow.document);
// UI Helper
let UiHelper = (function ($win, $doc) {
// privates
let _triggerId = 'aria2Trigger';
function _clickHandler(evt) {
(new QueueManager({
'copyOnly': evt.ctrlKey || evt.metaKey // press ctrl/cmd key while clicking to copy the download link only
})).next();
// kill the listener
evt.target.removeEventListener('click', _clickHandler, false);
}
function _recordHandler(record) {
//debug(record);
// place the trigger
let ariaTrigger = $doc.createElement('li');
ariaTrigger.id = _triggerId;
ariaTrigger.title = '按住 Ctrl 点击可仅复制下载链接';
ariaTrigger.innerHTML = '<span>发送至 Aria2</span>';
record.target.firstChild.appendChild(ariaTrigger);
// make it clickable
ariaTrigger.addEventListener('click', _clickHandler, false);
// stop the observation
//_observer.disconnect();
return true;
}
// initialization
function _init() {
let container = $doc.getElementById('js_operate_box');
// create a observer on the container
new MutationObserver(function (records) {
records.filter(function () {
return null === $doc.getElementById(_triggerId);
}).some(_recordHandler);
}).observe(container, {
'childList': true,
});
}
return {
// public
init: _init
};
})(unsafeWindow, unsafeWindow.document);
// fire
UiHelper.init();