用户脚本
用户脚本是一段 JS 代码,能为网站添加新的功能。编写 JS 脚本比 crx 扩展简单得多,不必为了实现一个简单的功能加入其他的文件。
使用用户脚本需要安装一个脚本管理器,最常用的就是油猴(Tampermonkey)或暴力猴(Violentmonkey),Firefox 应用商店、Edge 应用商店或 chrome 应用商店搜索 Tampermonkey 或 Violentmonkey,然后下载安装即可。
Tampermonkey vs Violentmonkey
云端备份、导出备份、自动更新、筛选排序这些脚本管理的重要功能,油猴和暴力猴上都有。
Tampermonkey 多了一些定制,编辑工具功能也多一些。
Violentmonkey 安装包体积较小,界面比较清爽一些,设置也比较简单。
Violentmonkey 还多了一个比较实用的功能,为当前网站查找匹配的脚本。
编写脚本
以 Violentmonkey 为例。
- 新建脚本。首先点击 Violentmonkey 扩展图标上面的 + 号,新建一个脚本。
- 设置脚本。以 // ==UserScript==开头,// ==/UserScript==结尾。下面是一些常用的设置:
- @namespace 和 @name 组合是发布到脚本网站时,用户脚本的唯一标识符。
- @author:脚本的作者。
- @version:脚本版本,用于更新发布脚本。
- @description:脚本的描述,可以添加命名来国际化,比如 @description:en。
- @include、@exclude:脚本应该和不应该运行的页面。允许指定多个。
- @require:加载外部脚本的 URL。允许指定多个。
- @resource:一些外部静态资源。可以通过 GM_getResourceURL 和 GM_getResourceText 方法访问。1 
 2// @resource logo https://my.cdn.com/logo.png 
 // @resource text https://my.cdn.com/some-text.txt
- @connect:定义允许被 GM_xmlhttpRequest 方法访问的域名。允许指定多个。
- @grant:给 GM_*方法授权并可在脚本执行时使用。如果使用任何特殊 API,则必须明确授予。除了GM API之外,还可以授予以下权限:1 
 2
 3
 4
 5// @grant GM_getValue 
 // @grant GM_setValue
 // @grant GM_setClipboard
 // @grant GM_openInTab
 // @grant GM_xmlhttpRequest1 
 2
 3
 4
 5
 6
 7
 8// @grant window.close 
 // @grant window.focus
 // @grant window.onurlchange
 if (window.onurlchange === null) {
 // feature is supported
 window.addEventListener('urlchange', (info) => ...);
 }
- @run-at:定义脚本何时执行。- document-end 默认值- 脚本在 DOMContentLoaded 被触发时执行。此时,页面的基本 HTML 已准备就绪,图像等其他资源可能仍在加载中。
 
- document-start- 脚本会尽快执行。不能保证脚本在页面中的其他脚本之前执行。在 Greasemonkey v3 中,甚至可以在加载 HTML 之前确保脚本执行,但对于 Violentmonkey 作为 Web 扩展来说这是不可能的。
 
- document-idle- 脚本在 DOMContentLoaded 触发后执行。
 
 
- document-end 默认值
- 加入代码。 - 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- // ==UserScript== 
 // @name 无需梯子 谷歌划词翻译 translate.google.cn
 // @namespace https://violentmonkey.github.io
 // @version 1.13
 // @description 基于 translate.google.cn,中译英,英译中,拼音、音标显示
 // @license https://www.apache.org/licenses/LICENSE-2.0
 // @author zkrisj
 // @include *
 // @exclude https://juejin.cn/editor/drafts/*
 // @exclude https://translate.google.cn/*
 // @run-at document-end
 // @connect translate.google.cn
 // @grant GM_xmlhttpRequest
 // ==/UserScript==
 (function() {
 'use strict';
 // var googleUrl = 'https://translate.google.cn/translate_a/single?client=gtx&dt=t&dt=bd&dj=1&source=input&hl=auto&sl=auto';
 // 无需梯子
 var googleUrl = 'https://translate.google.cn/_/TranslateWebserverUi/data/batchexecute?&source-path=%2F&rpcids=MkEWBc&soc-app=1&soc-platform=1&soc-device=1&_reqid=632656&rt=c';
 var icon = document.createElement('div');
 var word = '';
 icon.innerHTML = '<svg style="position: absolute;margin: 4px;" width="24" height="24" viewBox="0 0 768 768">' +
 '<path d="M672 640.5v-417c0-18-13.5-31.5-31.5-31.5h-282l37.5 129h61.5v-33h34.5v33h115.5v33h-40.5c-10.5 40.5-33 79.5-61.5 112.5l87 85.5-22.5 24-87-85.5-28.5 28.5 25.5 88.5-64.5 64.5h225c18 0 31.5-13.5 31.5-31.5zM447 388.5c7.5 15 19.5 34.5 36 54 39-46.5 49.5-88.5 49.5-88.5h-127.5l10.5 34.5h31.5zM423 412.5l19.5 70.5 18-16.5c-15-16.5-27-34.5-37.5-54zM355.5 339c0-7.381-0.211-16.921-3-22.5h-126v49.5h70.5c-4.5 19.5-24 48-67.5 48-42 0-76.5-36-76.5-78s34.5-78 76.5-78c24 0 39 10.5 48 19.5l3 1.5 39-37.5-3-1.5c-24-22.5-54-34.5-87-34.5-72 0-130.5 58.5-130.5 130.5s58.5 130.5 130.5 130.5c73.5 0 126-52.5 126-127.5zM640.5 160.5c34.5 0 63 28.5 63 63v417c0 34.5-28.5 63-63 63h-256.5l-31.5-96h-225c-34.5 0-63-28.5-63-63v-417c0-34.5 28.5-63 63-63h192l28.5 96h292.5z" style="fill:#3e84f4;"/></svg>';
 icon.setAttribute('style', 'width:32px;' + 'height:32px;' + 'display:none;' + 'background:#fff;' + 'border-radius:16px;' +
 'box-shadow:4px 4px 8px #888;' + 'position:absolute;' + 'z-index:2147483647;');
 // 添加翻译图标到 DOM
 document.documentElement.appendChild(icon);
 document.addEventListener('mousedown', function(e) {
 if (e.target == icon || (e.target.parentNode && e.target.parentNode == icon) || (e.target.parentNode.parentNode && e.target.parentNode
 .parentNode == icon)) { // 点击翻译图标时阻止选中的文本消失
 e.preventDefault();
 }
 });
 // 选中变化事件
 document.addEventListener("selectionchange", function() {
 if (!window.getSelection().toString().trim()) {
 icon.style.display = 'none';
 // server.containerDestroy();
 }
 });
 // 显示、隐藏翻译图标
 document.addEventListener('mouseup', function(e) {
 if (e.target == icon || (e.target.parentNode && e.target.parentNode == icon) || (e.target.parentNode.parentNode && e.target.parentNode
 .parentNode == icon)) { // 点击了翻译图标
 e.preventDefault();
 return;
 }
 var text = window.getSelection().toString().trim();
 if (text && text.length < 800 && icon.style.display == 'none') {
 icon.style.top = e.pageY + 12 + 'px';
 icon.style.left = e.pageX + 'px';
 icon.style.display = 'block';
 } else if (!text) {
 icon.style.display = 'none';
 for (var i = 0; i < server.rendered.length; i++) { // 点击了翻译内容面板
 if (e.target == server.rendered[i]) return; // 不再创建翻译图标
 }
 server.containerDestroy(); // 销毁翻译内容面板
 }
 });
 // 翻译图标点击事件
 icon.addEventListener('click', function(e) {
 var text = window.getSelection().toString().trim();
 if (text) {
 icon.style.display = 'none';
 server.containerDestroy(); // 销毁翻译内容面板
 // 新建翻译内容面板
 var container = server.container();
 container.style.top = e.pageY + 'px';
 if (e.pageX + 350 <= document.body.clientWidth) // container 面板css最大宽度为250px
 container.style.left = e.pageX + 'px';
 else container.style.left = document.body.clientWidth - 350 + 'px';
 document.body.appendChild(container);
 server.rendered.push(container);
 if (isChina(text)) {
 // ajax(googleUrl + '&tl=en&q=' + encodeURIComponent(text), container);
 ajax(googleUrl, container, 'POST', "f.req=" + JSON.stringify([[["MkEWBc", "[[" + encodeURIComponent(text) + ",'zh-CN','en']]"]]]));
 } else {
 // ajax(googleUrl + '&tl=zh&dt=t&q=' + encodeURIComponent(text), container);
 text = text.replace(/[A-Z][^A-Z ]/g, function(v) { return ' ' + v.toLowerCase() }).replace(/\p{P}/gu, ' ').replace(/ /g, ' ').trim();
 word = text;
 ajax(googleUrl, container, 'POST', "f.req=" + JSON.stringify([[["MkEWBc", "[[" + encodeURIComponent(text) + ",'auto','zh-CN']]"]]]));
 }
 }
 });
 function isChina(str) {
 var reg = /^([\u4E00-\u9FA5]|[\uFF00-\uFF20]|[\u3000-\u301C])+$/;
 return reg.test(str);
 }
 function ajax(url, element, method, data, headers) {
 if (!method) method = 'GET';
 // 因为Tampermonkey跨域访问(a.com)时会自动携带对应域名(a.com)的对应cookie,不会携带当前域名的cookie
 // 所以,GM_xmlhttpRequest【不存在】cookie跨域访问安全性问题
 if (!headers) headers = { "content-type": "application/x-www-form-urlencoded;charset=UTF-8", };
 GM_xmlhttpRequest({
 method: method,
 url: url,
 headers: headers,
 data: data,
 onload: function(res) {
 console.log(url, data, res);
 // google(res.responseText, element);
 if (res.responseText.startsWith('<!DOCTYPE html>')) {
 displaycontainer("获取失败", element);
 } else {
 res = JSON.parse(JSON.parse(res.responseText.match(/^\)]}'\n\n\d+\n(\[\[.*(?!\n\d)\]\])/)[1])[0][2]);
 var phonetic = res[0][0] ? res[0][0] + "\r\n" : "";
 var translation = res[1][0][0][5][0][0];
 if (res[3] && word === res[3][0] && res[3][5] && res[3][5][0] && res[3][5][0][0] && res[3][5][0][0][1] && res[3][5][0][0][1][0])
 translation = res[3][5][0][0][1][0][0];
 displaycontainer(phonetic.toLowerCase() + translation, element);
 }
 },
 onerror: function(res) {
 displaycontainer("连接失败", element);
 }
 });
 }
 function google(rst, element) {
 var json = JSON.parse(rst), html = '';
 console.log(json);
 for (var i = 0; i < json.sentences.length; i++) {
 html += json.sentences[i].trans;
 }
 displaycontainer(html, element);
 // console.log(word, html, element);
 }
 function displaycontainer(text, element) {
 element.textContent = text;
 element.style.display = 'block'; // 显示结果
 }
 var server = {
 // 存放已经生成的翻译内容面板(销毁的时候用)
 rendered: [],
 // 销毁已经生成的翻译内容面板
 containerDestroy: function() {
 for (var i = this.rendered.length - 1; i >= 0; i--) {
 if (this.rendered[i] && this.rendered[i].parentNode) {
 this.rendered[i].parentNode.removeChild(this.rendered[i]);
 }
 }
 },
 // 生成翻译结果面板 DOM (此时还未添加到页面)
 container: function() {
 var pre = document.createElement('pre');
 pre.setAttribute('style', 'display:none;' + 'position:absolute;' + 'font-size:13px;' + 'overflow:auto;' + 'background:#fff;' +
 'font-family:sans-serif,Arial;' + 'font-weight:normal;' + 'text-align:left;' + 'color:#000;' + 'padding:0.5em 1em;' +
 'line-height:1.5em;' + 'border-radius:5px;' + 'border:1px solid #ccc;' + 'box-shadow:4px 4px 8px #888;' + 'max-width:350px;' +
 'max-height:216px;' + 'z-index:2147483647;');
 return pre;
 }
 };
 })();- 如果自己使用,不需要分享给其他人,下面步骤就省了。 
- GreasyFork 上面发布。没有注册需要先注册。 
- 脚本地址:谷歌划词翻译 translate.google.cn,谷歌划词翻译 translate.googleapis.com 
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 张坤的博客!
 评论