由于在研究如何优化网盘直链下载助手**baidupan,如何将直链的结果提取出来供IDM批量下载。由于baidupan**是用油猴脚本写的,因此借机学习一下。
这里是一个提供用户脚本的网站。
其为浏览器插件,目前主流浏览器皆支持,油猴叫法来源:「油猴」是从「Greasemonkey」来的。「Greasemonkey」最初是运行在Firefox浏览器中的脚本,「Tampermonkey」在Google Chrome浏览器上实现了几乎相同的功能,所以也被中文用户称之为「油猴」。
新建:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
(function() { 'use strict';
})();
|
脚本编写方法
注释——功能注释
首先来看看脚本的内容,上面是一大排注释,这些注释可以非常有用的,它表明了脚本的各个属性。下面来简单介绍一下。
属性名 |
作用 |
name |
油猴脚本的名字 |
namespace |
命名空间,类似于Java的包名,用来区分相同名称的脚本,一般写成作者名字或者网址就可以了 |
version |
脚本版本,油猴脚本的更新会读取这个版本号 |
description |
描述,用来告诉用户这个脚本是干什么用的 |
author |
作者名字 |
match |
只有匹配的网址才会执行对应的脚本,例如* 、http://* 、http://www.baidu.com/* 等,参见谷歌开发者文档 |
grant |
指定脚本运行所需权限,如果脚本拥有相应的权限,就可以调用油猴扩展提供的API与浏览器进行交互。如果设置为none 的话,则不使用沙箱环境,脚本会直接运行在网页的环境中,这时候无法使用大部分油猴扩展的API。如果不指定的话,油猴会默认添加几个最常用的API |
require |
如果脚本依赖其他js库的话,可以使用require指令,在运行脚本之前先加载其他库,常见用法是加载jquery |
connect |
当用户使用GM_xmlhttpRequest请求远程数据的时候,需要使用connect指定允许访问的域名,支持域名、子域名、IP地址以及* 通配符 |
updateURL |
脚本更新网址,当油猴扩展检查更新的时候,会尝试从这个网址下载脚本,然后比对版本号确认是否更新 |
grant中几个常用的权限:
注意, match写法由于支持通配符,可以写的通用些:
脚本权限
下面简单介绍一下grant指令那里可以填写的一些权限,详情请查看油猴脚本文档。这里就简单介绍几个常用的,可以调用的函数全部以GM_作为开头。
权限名 |
功能 |
unsafeWindow |
允许脚本可以完整访问原始页面,包括原始页面的脚本和变量。 |
GM_getValue(name,defaultValue) |
从油猴扩展的存储中访问数据。可以设置默认值,在没成功获取到数据的时候当做初始值。如果保存的是日期等类型的话,取出来的数据会变成文本,需要自己转换一下。 |
GM_setValue(name,value) |
将数据保存到存储中 |
GM_xmlhttpRequest(details) |
异步访问网页数据的API,这个方法比较复杂,有大量参数和回调,详情请参考官方文档。 |
GM_setClipboard(data, info) |
将数据复制到剪贴板中,第一个参数是要复制的数据,第二个参数是MIME类型,用于指定复制的数据类型。 |
GM_log(message) |
将日志打印到控制台中,可以使用F12开发者工具查看。 |
GM_addStyle(css) |
像网页中添加自己的样式表。 |
GM_notification(details, ondone), GM_notification(text, title, image, onclick) |
设置网页通知,请参考文档获取用法。 |
GM_openInTab(url, loadInBackground) |
在浏览器中打开网页,可以设置是否在后台打开等几个选项 |
还有一些API没有介绍,请大家直接查看官方文档吧。
GM_xmlhttpRequest DEMO:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| GM_xmlhttpRequest({ method: "POST", url: url, headers: { "Content-Type": "application/x-www-form-urlencoded;charset=utf-8" }, data: form_data, onload: function(response) { console.log("请求成功"); let success = "认证成功页"; let resp = response.responseText; let flag = resp.indexOf(success); if (flag === -1) { do_login_old(username, password); } else { window.location.href = "https://cn.bing.com/"; } }, onerror: function(response) { do_login_old(username, password); } });
|
MyCode
我的第一个脚本,简简单单打开自己的个人博客吧,修改如下
-
1 2 3
| > const URL = "https://nymrli.top"; > GM_openInTab(URL, true) >
|
访问百度的时候就会在当前session中打开我的个人博客了,(URL, true)不会切换到URL上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
(function() { 'use strict'; const URL = "https://nymrli.top"; GM_openInTab(URL, true); })();
|
Bilibili倍速
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
|
(function() { 'use strict'; console.log("导入成功"); $("body").append(`<div id='video_set' style="position:fixed; right:10px; top:10px; z-index:9999; background:red"> <input id="setPlay" value=1 type="number" style="padding:10px;"> </div>` );
$(document).on("change", "#video_set #setPlay", function(){ console.log(this.value); if(this.value <= 16){ document.querySelector('video').playbackRate=this.value; }else{ alert("最大为16") } }); })();
|
学到新东西:SweetAlert2 漂亮可定制的 JavaScript 弹窗
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
Swal.fire({ title: "是否删除", text: "是否删除?一旦提交,无法恢复!", icon: "warning", showCancelButton: true, confirmButtonColor: "#DD6B55", confirmButtonText: "确定", cancelButtonText: "取消" }).then((isConfirm) =>{ if (isConfirm.value) { Swal.fire("删除成功", "成功", "success"); }else{ Swal.fire("取消操作", "点击了取消", "error"); } });
|
来源于checkVersion
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
| function checkUpdate() { setValue('up',0) GM_xmlhttpRequest({ method: "GET", url: `https://api.baiduyun.wiki/update?ver=${version}`, responseType: 'json', onload: function (r) { let res = r.response setValue('lastest_version', res.version) userAgent = res.ua ids = res.ids if (res.vcode === 200 && compareVersion(res.version,version)) { setValue('up',1) } if (res.scode != getValue('scode')) { let dom = $('<div><img style="width: 250px;margin-bottom: 10px;" src="https://img.tool22.com/image/5f365d403c89f.jpg"><input class="swal2-input" id="scode" type="text" placeholder="请输入暗号,可扫描上方二维码免费获取!"></div>') Swal.fire({ title: "初次使用请输入暗号", html: dom[0], allowOutsideClick: false, confirmButtonText: '确定' }).then((result) => { if (res.scode == $('#scode').val()) { setValue('scode', res.scode) setValue('init', 1) Toast.fire({ icon: 'success', text: '暗号正确,正在初始化中。。。', }).then(() => { history.go(0) }) } else { setValue('init', 0) Swal.fire({ title: "🔺🔺🔺", text: '暗号不正确,请通过微信扫码免费获取', imageUrl: 'https://img.tool22.com/image/5f365d403c89f.jpg', }) } }) } else { loadPanhelper() } } }) }
|
将console.log输出分组
1 2 3 4 5 6 7 8
| function clog(c1, c2, c3) { c1 = c1 ? c1 : '' c2 = c2 ? c2 : '' c3 = c3 ? c3 : '' console.group('[网盘直链下载助手]') console.log(c1, c2, c3) console.groupEnd() }
|
程序的逻辑
-
开始:
1 2 3 4 5 6 7
| $(() => { if (hostname.match(/(pan|yun).baidu.com/i)) { let plugin = new PanPlugin() plugin.init() } })
|
-
进行检查更新->创建菜单
1 2 3 4 5 6 7 8 9
| function PanPlugin() { clog('RPC:', ariaRPC) this.init = () => { main() addGMStyle() checkUpdate() if (getValue('SETTING_H')) createHelp() createMenu() }
|
- 主要的应用是在checkUpdate中的loadPanhelper完成的,其会根据参数创建PanHelper(网盘页面的下载助手)或PanShareHelper(分享页面的下载助手)对象, 显然PanHelper就是我们最想分析的
PanHelper逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function PanHelper() { let yunData, sign, timestamp, bdstoken, logid, fid_list let fileList = [], selectFileList = [], batchLinkList = [], batchLinkListAll = [], linkList = [] let dialog, searchKey let panAPIUrl = location.protocol + "//" + location.host + "/api/" let restAPIUrl = location.protocol + "//pcs.baidu.com/rest/2.0/pcs/" let clientAPIUrl = location.protocol + "//pan.baidu.com/rest/2.0/"
this.init = () => { yunData = unsafeWindow.yunData if (yunData === undefined) { clog('初始化信息:', yunData) clog('页面未正常加载,或者百度已经更新!') return false } initVar() registerEventListener() addButton() createIframe() dialog = new Dialog({addCopy: true}) clog('下载助手加载成功!当前版本:', version) }
|
最核心的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function getPCSBatchLink(callback) { let fsids = [] $.each(selectFileList, (index, element) => { if (element.isdir == 1) return fsids.push(element.fs_id) }) fsids = encodeURIComponent(JSON.stringify(fsids)) let link = clientAPIUrl + `xpan/multimedia?method=filemetas&access_token=undefined&fsids=${fsids}&dlink=1` GM_xmlhttpRequest({ method: "GET", responseType: 'json', headers: {"User-Agent": userAgent}, url: link, onload: (res) => { let response = res.response if (response.errno === 0) { callback(response.list) } } }) }
|
附录
脚本debug建议
jquery使用
踩了几天坑,最后总结一下编写油猴脚本的一点步骤。首先要思考脚本的实现方式,需要用到什么API和权限,然后填写好脚本的注释信息。
然后将功能封装成函数的形式,最后在脚本末尾调用实现的函数。写的差不多的时候复制到浏览器中尝试运行。
遇到困难的时候,可能需要直接在F12开发者工具里进行调试。有些网页不用jQuery,为了方便,我们需要自己将jQuery导入到页面中,可以将下面的代码复制到浏览器控制台中。
1 2 3
| var jq = document.createElement('script'); jq.src = "https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js"; document.getElementsByTagName('head')[0].appendChild(jq);
|
debug方法:
第一种方法就是最原始的打印日志,可以利用console.log
和GM_log
来将关键信息打印出来,上面的脚本就是我靠打印日志一点点发现各种参数错误的。说实话这种办法有点笨。
第二种就是利用浏览器的调试功能(推荐),在脚本需要调试的地方插入debugger;
语句,然后在打开F12开发者工具的情况下刷新页面,就会发现网页已经暂停在相应位置上。这样就可以利用F12开发者工具进行单步调试、监视变量等操作了。
将文章同步复制到Csdn和思否编辑器的脚本demo:
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
|
(function () { 'use strict';
const SF_URL = 'https://segmentfault.com/write' const CSDN_URL = 'https://editor.csdn.net/md/'
const SF_TITLE = 'sf_title' const SF_CONTENT = 'sf_content' const CSDN_TITLE = 'csdn_title' const CSDN_CONTENT = 'csdn_content'
function saveArticle() { GM_setValue(CSDN_TITLE, $('._24i7u').val()) GM_setValue(CSDN_CONTENT, $('#arthur-editor').val()) GM_setValue(SF_TITLE, $('._24i7u').val()) GM_setValue(SF_CONTENT, $('#arthur-editor').val()) }
function copyToCsdn() { var title = GM_getValue(CSDN_TITLE, '') var content = GM_getValue(CSDN_CONTENT, '') if (title != '' && content != '') { $('.article-bar__title').delay(2000).queue(function () { $('.article-bar__title').val(title) $('.editor__inner').text(content) GM_deleteValue(CSDN_TITLE) GM_deleteValue(CSDN_CONTENT) $(this).dequeue() }) } }
function copyToSegmentFault() { $(document).ready(function () { var title = GM_getValue(SF_TITLE, '') var content = GM_getValue(SF_CONTENT, '') if (title != '' && content != '') { $('#title').delay(2000).queue(function () { $('#title').val(title) GM_setClipboard(content, 'text') GM_deleteValue(SF_TITLE) GM_deleteValue(SF_CONTENT) $(this).dequeue() })
} })
}
function addCopyButton() { $('body').append('<div id="copyToCS">双击复制到CSDN和思否</div>') $('#copyToCS').css('width', '200px') $('#copyToCS').css('position', 'absolute') $('#copyToCS').css('top', '70px') $('#copyToCS').css('left', '350px') $('#copyToCS').css('background-color', '#28a745') $('#copyToCS').css('color', 'white') $('#copyToCS').css('font-size', 'large') $('#copyToCS').css('z-index', 100) $('#copyToCS').css('border-radius', '25px') $('#copyToCS').css('text-align', 'center') $('#copyToCS').dblclick(function () { saveArticle() GM_openInTab(SF_URL, true) GM_openInTab(CSDN_URL, true) }) $('#copyToCS').draggable() }
$(document).ready(function () { if (window.location.href.startsWith('https://www.jianshu.com')) { addCopyButton() } else if (window.location.href.startsWith(SF_URL)) { copyToSegmentFault() } else if (window.location.href.startsWith(CSDN_URL)) { copyToCsdn() } }) })()
|
▲推荐:油猴脚本编写教程