├── .eslintrc.yml ├── README.md ├── android └── bilibili.user.js ├── aqistudy.user.js ├── arch-packages.user.js ├── bilibili.user.js ├── chito.user.js ├── cks.user.js ├── docsrs.user.js ├── duolingo.user.js ├── element.user.js ├── github-tweak.user.js ├── gitlab-tweak.user.js ├── gitlab.user.js ├── google_cache_redirect.user.js ├── google_searchbar.user.js ├── googlegroups_remove_translation_tip.user.js ├── ipip-tweak.user.js ├── mp.weixin.user.js ├── readthedocs.user.js ├── taobao.user.js ├── telegram-web.user.js ├── visited-links.user.js ├── wikipedia-lang-reorder.user.js ├── youtube-cc-lang.user.js ├── youtube-space.user.js ├── zdic.user.js └── zhihu-tweak.user.js /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | es6: true 3 | browser: true 4 | extends: 'eslint:recommended' 5 | rules: 6 | indent: 7 | - off 8 | linebreak-style: 9 | - error 10 | - unix 11 | quotes: 12 | - error 13 | - single 14 | semi: 15 | - error 16 | - never 17 | no-console: 18 | - off 19 | no-unused-vars: 20 | - warn 21 | - varsIgnorePattern: ^_ 22 | argsIgnorePattern: ^_ 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | My various Tampermonkey / GreaseMonkey scripts 2 | 3 | github-tweak 4 | === 5 | * use gzip format for downloading 6 | * use ssh protocol for initialization instructions 7 | 8 | [Install](https://github.com/lilydjwg/userscripts/raw/master/github-tweak.user.js) 9 | 10 | gitlab-tweak 11 | === 12 | * move notification button / status out to sidebar 13 | 14 | [Install](https://github.com/lilydjwg/userscripts/raw/master/gitlab-tweak.user.js) 15 | 16 | docsrs 17 | === 18 | * use `/latest/` version links for search results 19 | * show visited search results in a different color 20 | 21 | [Install](https://github.com/lilydjwg/userscripts/raw/master/docsrs.user.js) 22 | 23 | element 24 | === 25 | 26 | For the Element Web Matrix client. 27 | 28 | * mark Telegram bridge users with a small icon at the corner of avatar 29 | 30 | [Install](https://github.com/lilydjwg/userscripts/raw/master/element.user.js) 31 | 32 | Arch Packages time 33 | === 34 | 35 | Use local time format for package dates. 36 | 37 | [Install](https://github.com/lilydjwg/userscripts/raw/master/arch-packages.user.js) 38 | 39 | zhihu-tweak 40 | === 41 | * 问答 42 | * 评论时,Tab 之后首先跳转到提交评论的按钮 43 | * 文章 44 | * 修复中键/Ctrl+左键点击在后台标签页中打开链接 45 | * 图片、视频立即加载不等待,GIF 自动播放 46 | * TODO: 刷新之后,浏览器无法恢复当前滚动位置 47 | 48 | [Install](https://github.com/lilydjwg/userscripts/raw/master/zhihu-tweak.user.js) 49 | 50 | wikipedia-lang-reorder 51 | === 52 | * Move Chinese and English languages to the front in the language list 53 | * Hide Translate link 54 | * For Vector 2022 theme, copy the language list back to bottom of left column 55 | 56 | [Install](https://github.com/lilydjwg/userscripts/raw/master/wikipedia-lang-reorder.user.js) 57 | 58 | google_cache_redirect 59 | === 60 | * link for Google Search to bypass redirecting (for visiting or copying) 61 | * no unencrypted URL redirecting 62 | 63 | [Install](https://github.com/lilydjwg/userscripts/raw/master/google_cache_redirect.user.js) 64 | 65 | google_searchbar 66 | === 67 | * middle-click clear & paste on Google Searchbar (hold alt to not clear) 68 | 69 | [Install](https://github.com/lilydjwg/userscripts/raw/master/google_searchbar.user.js) 70 | 71 | readthedocs 72 | === 73 | * Toggle navigation column to save space 74 | 75 | [Install](https://github.com/lilydjwg/userscripts/raw/master/readthedocs.user.js) 76 | 77 | telegram-web 78 | === 79 | * make the link in unsafe link popups a real link 80 | 81 | [Install](https://github.com/lilydjwg/userscripts/raw/master/telegram-web.user.js) 82 | 83 | ipip-tweak 84 | === 85 | * DNS 查询页面清除输入框并聚集 86 | 87 | [Install](https://github.com/lilydjwg/userscripts/raw/master/ipip-tweak.user.js) 88 | 89 | googlegroups_remove_translation_tip 90 | === 91 | * Don't show the translate-to-Chinese tip. It's annoying. 92 | 93 | [Install](https://github.com/lilydjwg/userscripts/raw/master/googlegroups_remove_translation_tip.user.js) 94 | 95 | visited-links 96 | === 97 | * use purple (or other) color for visited links 98 | 99 | [Install](https://github.com/lilydjwg/userscripts/raw/master/visited-links.user.js) 100 | 101 | chito 102 | === 103 | * show IP geolocations in Chito admin pages 104 | 105 | This script requires a local HTTP service. 106 | 107 | [Install](https://github.com/lilydjwg/userscripts/raw/master/chito.user.js) 108 | 109 | 汉典 110 | === 111 | * 生僻字使用文字显示 112 | 113 | [Install](https://github.com/lilydjwg/userscripts/raw/master/zdic.user.js) 114 | 115 | aqistudy 116 | === 117 | * 修复主内容区域不能正确显示的问题 118 | 119 | [Install](https://github.com/lilydjwg/userscripts/raw/master/aqistudy.user.js) 120 | 121 | mp.weixin 122 | === 123 | * 修正微信文章标题 124 | 125 | [Install](https://github.com/lilydjwg/userscripts/raw/master/mp.weixin.user.js) 126 | 127 | cks 128 | === 129 | * Make titles of Chris's Wiki better 130 | 131 | [Install](https://github.com/lilydjwg/userscripts/raw/master/cks.user.js) 132 | 133 | Bilibili 134 | === 135 | B 站优化。 136 | 137 | * 禁用自动静音播放 138 | * 移除未登录的各种烦人提示 139 | * 双击视频播放器切换全屏 140 | 141 | [Install](https://github.com/lilydjwg/userscripts/raw/master/bilibili.user.js) 142 | 143 | Taobao 144 | === 145 | 淘宝优化。 146 | 147 | * 禁用 Alt+数字快捷键(与 Linux 下浏览器切换标签页冲突) 148 | 149 | [Install](https://github.com/lilydjwg/userscripts/raw/master/taobao.user.js) 150 | 151 | Duolingo 152 | === 153 | Duolingo 优化 154 | 155 | * 中国大陆网民也要有朋友! 156 | 157 | [Install](https://github.com/lilydjwg/userscripts/raw/master/duolingo.user.js) 158 | -------------------------------------------------------------------------------- /android/bilibili.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name bilibili background play 3 | // @namespace https://github.com/lilydjwg/userscripts 4 | // @description bilibili background play 5 | // @include https://*.bilibili.com/* 6 | // @version 0.1 7 | // @grant none 8 | // ==/UserScript== 9 | 10 | 'use strict' 11 | 12 | window.addEventListener('visibilitychange', evt => { 13 | evt.stopImmediatePropagation() 14 | }, true) 15 | 16 | document.addEventListener('click', evt => { 17 | if(evt.target.matches('#player_fullpage *')) { 18 | const v = document.querySelector('video') 19 | v.requestFullscreen() 20 | evt.stopImmediatePropagation() 21 | } 22 | }, true) 23 | -------------------------------------------------------------------------------- /aqistudy.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name aqistudy fixes 3 | // @namespace https://github.com/lilydjwg/userscripts 4 | // @description aqistudy fixes 5 | // @include https://www.aqistudy.cn/* 6 | // @version 0.1 7 | // @grant none 8 | // ==/UserScript== 9 | 10 | (function() { 11 | 'use strict' 12 | 13 | function fix() { 14 | console.log('fixing') 15 | const el = document.getElementById('maintabs') 16 | if(!el){ 17 | console.log('maintabs not found') 18 | return 19 | } 20 | 21 | const right_panel = el.lastChild 22 | if(!right_panel){ 23 | console.log('right_panel not found') 24 | return 25 | } 26 | 27 | const {y, width} = right_panel.getBoundingClientRect() 28 | console.log('y, width=', y, width) 29 | if(y > window.innerHeight / 2){ 30 | right_panel.style.width = `${width-1}px` 31 | } 32 | 33 | } 34 | 35 | window.addEventListener('resize', function(){ 36 | // run after content scripts 37 | setTimeout(fix, 200) 38 | }) 39 | fix() 40 | 41 | })() 42 | -------------------------------------------------------------------------------- /arch-packages.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Arch Packages time 3 | // @namespace https://github.com/lilydjwg/userscripts 4 | // @version 0.3 5 | // @description use local time format for package dates 6 | // @match https://archlinux.org/packages/* 7 | // @require https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.12/dayjs.min.js 8 | // @require https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.12/locale/en.min.js 9 | // @require https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.12/plugin/customParseFormat.min.js 10 | // @grant none 11 | // ==/UserScript== 12 | 13 | 'use strict' 14 | 15 | function format_relative_time(d1, d2) { 16 | // in miliseconds 17 | const units = { 18 | year: 24 * 60 * 60 * 1000 * 365, 19 | month: (24 * 60 * 60 * 1000 * 365) / 12, 20 | day: 24 * 60 * 60 * 1000, 21 | hour: 60 * 60 * 1000, 22 | minute: 60 * 1000, 23 | second: 1000, 24 | }; 25 | 26 | const rtf = new Intl.RelativeTimeFormat() 27 | // https://stackoverflow.com/a/60688789 28 | const elapsed = d1.valueOf() - d2.valueOf() 29 | 30 | for (const [u, period] of Object.entries(units)) { 31 | if (Math.abs(elapsed) > period || u === "second") { 32 | // https://stackoverflow.com/a/64972112 33 | return rtf.format( 34 | Math.round(elapsed / period), 35 | u 36 | ) 37 | } 38 | } 39 | } 40 | 41 | // July 31, 2024, 6:45 a.m. UTC 42 | const parse_format = 'MMMM D, YYYY, H:mm a ZZ' 43 | const NOW = new Date() 44 | 45 | function parse_date(t) { 46 | t = t.replace(/([ap])\.m\./, '$1m') 47 | t = t.replace('UTC', '+0000') 48 | // e.g. Dec. 22, 2023, 11 p.m. UTC 49 | t = t.replace(/( [0-9]+) ([ap]m)/, '$1:00 $2') 50 | return dayjs(t, parse_format, 'en') 51 | } 52 | 53 | { 54 | const nodes = document.evaluate('//table[@id="pkginfo"]//td[contains(text(), "UTC")]', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null) 55 | for(let i=0, len=nodes.snapshotLength; i'+ans[i]+'') 38 | } 39 | $(ip_header).after('地址') 40 | }, 41 | }) 42 | } 43 | 44 | function GM_wait(){ 45 | if(/\/(comments|messages|spams)\b/.test(location)){ 46 | if(typeof unsafeWindow.jQuery == 'undefined') { 47 | setTimeout(GM_wait, 500) 48 | }else{ 49 | $ = unsafeWindow.jQuery 50 | letsJQuery() 51 | } 52 | } 53 | } 54 | 55 | GM_wait() 56 | -------------------------------------------------------------------------------- /cks.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Better title for Chris's Wiki 3 | // @namespace https://github.com/lilydjwg/userscripts 4 | // @description Better title for Chris's Wiki 5 | // @include https://utcc.utoronto.ca/~cks/* 6 | // @version 0.2.1 7 | // @grant none 8 | // ==/UserScript== 9 | 10 | (function() { 11 | 'use strict' 12 | 13 | const h2s = document.querySelectorAll('h2') 14 | if(h2s.length == 1) { 15 | const title = h2s[0].textContent 16 | document.title = `Chris's Wiki :: ${title}` 17 | } 18 | 19 | })() 20 | -------------------------------------------------------------------------------- /docsrs.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Docs.rs tweak 3 | // @namespace https://github.com/lilydjwg/userscripts 4 | // @description Docs.rs tweak 5 | // @include https://docs.rs/* 6 | // @version 0.2 7 | // @run-at document-end 8 | // @grant none 9 | // ==/UserScript== 10 | 11 | 12 | for(const link of document.querySelectorAll('.recent-releases-container > ul > li > a.release')) { 13 | if(link.href.indexOf('https://docs.rs/crate/') === 0) { 14 | continue 15 | } 16 | link.href = link.href.replace(/^(https:\/\/docs.rs\/[^/]+\/)[^/]+/, '$1latest') 17 | } 18 | 19 | 20 | function addStyle(css) { 21 | const head = document.getElementsByTagName('head')[0] 22 | if (head) { 23 | const style = document.createElement('style') 24 | style.setAttribute('type', 'text/css') 25 | style.textContent = css 26 | head.appendChild(style) 27 | return style 28 | } 29 | } 30 | 31 | const css = 'a:visited .name { color: #964dae !important; }' 32 | 33 | addStyle(css) 34 | 35 | -------------------------------------------------------------------------------- /duolingo.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name duolingo 3 | // @namespace https://github.com/lilydjwg/userscripts 4 | // @description duolingo tweaks 5 | // @include https://*.duolingo.com/* 6 | // @version 0.1 7 | // @grant none 8 | // @run-at document-start 9 | // ==/UserScript== 10 | 11 | // License: Mozilla Public License Version 2.0 12 | // Modified from https://addons.mozilla.org/firefox/addon/spoof-timezone/ 13 | 14 | (function() { 15 | 'use strict' 16 | 17 | const prefs = ` 18 | Date.prefs = Date.prefs || [ 19 | 'Asia/Singapore', -480, new Date().getTimezoneOffset(), 'Singapore Standard Time' 20 | ]; 21 | try { // get preferences for subframes synchronously 22 | Date.prefs = window.parent.Date.prefs; 23 | } 24 | catch (e) {} 25 | `; 26 | 27 | const intl = ` 28 | const ODateTimeFormat = Intl.DateTimeFormat; 29 | Intl.DateTimeFormat = function(locales, options = {}) { 30 | Object.assign(options, { 31 | timeZone: Date.prefs[0] 32 | }); 33 | return ODateTimeFormat(locales, options); 34 | }; 35 | Intl.DateTimeFormat.prototype = Object.create(ODateTimeFormat.prototype); 36 | Intl.DateTimeFormat.supportedLocalesOf = ODateTimeFormat.supportedLocalesOf; 37 | `; 38 | 39 | const shiftedDate = ` 40 | const clean = str => { 41 | const toGMT = offset => { 42 | const z = n => (n < 10 ? '0' : '') + n; 43 | const sign = offset <= 0 ? '+' : '-'; 44 | offset = Math.abs(offset); 45 | return sign + z(offset / 60 | 0) + z(offset % 60); 46 | }; 47 | str = str.replace(/([T\\(])[\\+-]\\d+/g, '$1' + toGMT(Date.prefs[1])); 48 | if (str.indexOf(' (') !== -1) { 49 | str = str.split(' (')[0] + ' (' + Date.prefs[3] + ')'; 50 | } 51 | return str; 52 | } 53 | 54 | const ODate = Date; 55 | const { 56 | getTime, getDate, getDay, getFullYear, getHours, getMilliseconds, getMinutes, getMonth, getSeconds, getYear, 57 | toDateString, toLocaleString, toString, toTimeString, toLocaleTimeString, toLocaleDateString, 58 | setYear, setHours, setTime, setFullYear, setMilliseconds, setMinutes, setMonth, setSeconds, setDate, 59 | setUTCDate, setUTCFullYear, setUTCHours, setUTCMilliseconds, setUTCMinutes, setUTCMonth, setUTCSeconds 60 | } = ODate.prototype; 61 | class ShiftedDate extends ODate { 62 | constructor(...args) { 63 | super(...args); 64 | this.nd = new ODate( 65 | getTime.apply(this) + (Date.prefs[2] - Date.prefs[1]) * 60 * 1000 66 | ); 67 | } 68 | // get 69 | toLocaleString(...args) { 70 | return toLocaleString.apply(this.nd, args); 71 | } 72 | toLocaleTimeString(...args) { 73 | return toLocaleTimeString.apply(this.nd, args); 74 | } 75 | toLocaleDateString(...args) { 76 | return toLocaleDateString.apply(this.nd, args); 77 | } 78 | toDateString(...args) { 79 | return toDateString.apply(this.nd, args); 80 | } 81 | getDate(...args) { 82 | return getDate.apply(this.nd, args); 83 | } 84 | getDay(...args) { 85 | return getDay.apply(this.nd, args); 86 | } 87 | getFullYear(...args) { 88 | return getFullYear.apply(this.nd, args); 89 | } 90 | getHours(...args) { 91 | return getHours.apply(this.nd, args); 92 | } 93 | getMilliseconds(...args) { 94 | return getMilliseconds.apply(this.nd, args); 95 | } 96 | getMinutes(...args) { 97 | return getMinutes.apply(this.nd, args); 98 | } 99 | getMonth(...args) { 100 | return getMonth.apply(this.nd, args); 101 | } 102 | getSeconds(...args) { 103 | return getSeconds.apply(this.nd, args); 104 | } 105 | getYear(...args) { 106 | return getYear.apply(this.nd, args); 107 | } 108 | // set 109 | setHours(...args) { 110 | const a = getTime.call(this.nd); 111 | const b = setHours.apply(this.nd, args); 112 | setTime.call(this, getTime.call(this) + b - a); 113 | return b; 114 | } 115 | setFullYear(...args) { 116 | const a = getTime.call(this.nd); 117 | const b = setFullYear.apply(this.nd, args); 118 | setTime.call(this, getTime.call(this) + b - a); 119 | return b; 120 | } 121 | setMilliseconds(...args) { 122 | const a = getTime.call(this.nd); 123 | const b = setMilliseconds.apply(this.nd, args); 124 | setTime.call(this, getTime.call(this) + b - a); 125 | return b; 126 | } 127 | setMinutes(...args) { 128 | const a = getTime.call(this.nd); 129 | const b = setMinutes.apply(this.nd, args); 130 | setTime.call(this, getTime.call(this) + b - a); 131 | return b; 132 | } 133 | setMonth(...args) { 134 | const a = getTime.call(this.nd); 135 | const b = setMonth.apply(this.nd, args); 136 | setTime.call(this, getTime.call(this) + b - a); 137 | return b; 138 | } 139 | setSeconds(...args) { 140 | const a = getTime.call(this.nd); 141 | const b = setSeconds.apply(this.nd, args); 142 | setTime.call(this, getTime.call(this) + b - a); 143 | return b; 144 | } 145 | setDate(...args) { 146 | const a = getTime.call(this.nd); 147 | const b = setDate.apply(this.nd, args); 148 | setTime.call(this, getTime.call(this) + b - a); 149 | return b; 150 | } 151 | setYear(...args) { 152 | const a = getTime.call(this.nd); 153 | const b = setYear.apply(this.nd, args); 154 | setTime.call(this, getTime.call(this) + b - a); 155 | return b; 156 | } 157 | setTime(...args) { 158 | const a = getTime.call(this); 159 | const b = setTime.apply(this, args); 160 | setTime.call(this.nd, getTime.call(this.nd) + b - a); 161 | return b; 162 | } 163 | setUTCDate(...args) { 164 | const a = getTime.call(this); 165 | const b = setUTCDate.apply(this, args); 166 | setTime.call(this.nd, getTime.call(this.nd) + b - a); 167 | return b; 168 | } 169 | setUTCFullYear(...args) { 170 | const a = getTime.call(this); 171 | const b = setUTCFullYear.apply(this, args); 172 | setTime.call(this.nd, getTime.call(this.nd) + b - a); 173 | return b; 174 | } 175 | setUTCHours(...args) { 176 | const a = getTime.call(this); 177 | const b = setUTCHours.apply(this, args); 178 | setTime.call(this.nd, getTime.call(this.nd) + b - a); 179 | return b; 180 | } 181 | setUTCMilliseconds(...args) { 182 | const a = getTime.call(this); 183 | const b = setUTCMilliseconds.apply(this, args); 184 | setTime.call(this.nd, getTime.call(this.nd) + b - a); 185 | return b; 186 | } 187 | setUTCMinutes(...args) { 188 | const a = getTime.call(this); 189 | const b = setUTCMinutes.apply(this, args); 190 | setTime.call(this.nd, getTime.call(this.nd) + b - a); 191 | return b; 192 | } 193 | setUTCMonth(...args) { 194 | const a = getTime.call(this); 195 | const b = setUTCMonth.apply(this, args); 196 | setTime.call(this.nd, getTime.call(this.nd) + b - a); 197 | return b; 198 | } 199 | setUTCSeconds(...args) { 200 | const a = getTime.call(this); 201 | const b = setUTCSeconds.apply(this, args); 202 | setTime.call(this.nd, getTime.call(this.nd) + b - a); 203 | return b; 204 | } 205 | // toString 206 | toString(...args) { 207 | return clean(toString.apply(this.nd, args)); 208 | } 209 | toTimeString(...args) { 210 | return clean(toTimeString.apply(this.nd, args)); 211 | } 212 | // offset 213 | getTimezoneOffset() { 214 | return Date.prefs[1]; 215 | } 216 | } 217 | `; 218 | 219 | // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts#constructors_from_the_page_context 220 | const script = document.createElement('script'); 221 | script.textContent = `{ 222 | ${prefs} 223 | ${intl} 224 | ${shiftedDate} 225 | Date = ShiftedDate; 226 | }`; 227 | document.documentElement.appendChild(script); 228 | script.remove(); 229 | 230 | })() 231 | -------------------------------------------------------------------------------- /element.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Element.io tweak 3 | // @namespace https://github.com/lilydjwg/userscripts 4 | // @description Element.io tweak 5 | // @match https://chat.mozilla.org/* 6 | // @match https://app.element.io/* 7 | // @match https://element.catgirl.cloud/* 8 | // @match https://app.schildi.chat/* 9 | // @version 0.1.4 10 | // @run-at document-idle 11 | // @grant none 12 | // @sandbox JavaScript 13 | // ==/UserScript== 14 | 15 | const ICON_URL = 'https://telegram.org/img/website_icon.svg' 16 | 17 | const img = document.createElement('img') 18 | img.src = ICON_URL 19 | img.style.position = 'relative' 20 | img.style.top = '20px' 21 | img.style.left = '-10px' 22 | img.style.pointerEvents = 'none' 23 | 24 | const img_typing = img.cloneNode() 25 | img_typing.style.width = '12px' 26 | img_typing.style.height = '12px' 27 | img_typing.style.top = '16px' 28 | img_typing.style.left = '-8px' 29 | 30 | const img_member = img.cloneNode() 31 | img_member.style.width = '8px' 32 | img_member.style.height = '8px' 33 | img_member.style.top = '7px' 34 | img_member.style.left = '-6px' 35 | 36 | // no longer works 37 | const img_reply = img.cloneNode() 38 | img_reply.style.width = '8px' 39 | img_reply.style.height = '8px' 40 | img_reply.style.top = '6px' 41 | img_reply.style.left = '-6px' 42 | img_reply.style.marginRight = '-6px' 43 | 44 | function insertAfter(location, newnode) { 45 | if(location.nextSibling) { 46 | location.parentNode.insertBefore(newnode, location.nextSibling) 47 | }else{ 48 | location.parentNode.appendChild(newnode) 49 | } 50 | } 51 | 52 | function doit() { 53 | for(let avatar of document.querySelectorAll('ol.mx_RoomView_MessageList [title^="@telegram_"][title$=":t2bot.io"], ol.mx_RoomView_MessageList [title^="@telegram_"][title$=":nichi.co"], ol.mx_RoomView_MessageList [title^="@telegram_"][title$=":moe.cat"], ol.mx_RoomView_MessageList [title^="@perigram_"][title$=":neo.angry.im"], ol.mx_RoomView_MessageList [title^="@tg_"][title$=":moechat.kimiblock.top"]')) { 54 | const width = avatar.width 55 | if(avatar.parentNode.tagName == 'SPAN' && avatar.parentNode.classList.contains('mx_Pill')) { // in reply to 56 | if(avatar.nextSibling.nodeType == Node.TEXT_NODE) { 57 | avatar.parentNode.insertBefore(img_reply.cloneNode(), avatar.nextSibling) 58 | } 59 | continue 60 | } 61 | if(!avatar.nextSibling || avatar.nextSibling.classList.contains('mx_DisambiguatedProfile')) { 62 | if(width >= 24) { 63 | insertAfter(avatar, img_typing.cloneNode()) 64 | }else if(width >= 14 && width <= 16) { 65 | insertAfter(avatar, img_member.cloneNode()) 66 | } else { 67 | insertAfter(avatar, img.cloneNode()) 68 | } 69 | } 70 | } 71 | } 72 | 73 | setInterval(doit, 1100) 74 | -------------------------------------------------------------------------------- /github-tweak.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name github tweaks 3 | // @namespace https://github.com/lilydjwg/userscripts 4 | // @description use gzip, use ssh 5 | // @match https://github.com/* 6 | // @version 1.10 7 | // @grant GM_addElement 8 | // ==/UserScript== 9 | 10 | (function() { 11 | 'use strict' 12 | 13 | function prefer_gzip() { 14 | console.log('tweak: prefer_gzip starts') 15 | 16 | const dl = document.evaluate('//span[text()="Download ZIP"]', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0) 17 | console.log('tweak: dl', dl) 18 | if(dl){ 19 | const re = /^https:\/\/github\.com\/([^\/]+)\/([^\/]+)(\/tree\/([^\/]+))?/ 20 | const m = re.exec(location.href) 21 | const button_ref = document.querySelector('button#branch-picker-repos-header-ref-selector').textContent.trim() 22 | const ref = m[4] || button_ref 23 | dl.textContent = '' 24 | GM_addElement(dl, 'a', { 25 | href: `/${m[1]}/${m[2]}/archive/${ref}.tar.gz`, 26 | textContent: 'Download tar.gz', 27 | }) 28 | console.log('tweak: download link added') 29 | } 30 | } 31 | 32 | console.log('tweak: start') 33 | document.addEventListener('click', function() { 34 | setTimeout(prefer_gzip, 100) 35 | }) 36 | 37 | const repourl = document.querySelectorAll('.js-live-clone-url') 38 | const re = /https:\/\/github\.com\/([^\/]+)\/(.*)/ 39 | let span, m 40 | let i, len 41 | for(i=0, len=repourl.length; i div') 41 | if(notif) { 42 | if(is_issue) { 43 | add_item(notif, sidebar) 44 | add_item(issueref, sidebar) 45 | }else{ 46 | sidebar.insertBefore(notif, sidebar.firstChild) 47 | } 48 | return true 49 | } else { 50 | return false 51 | } 52 | } 53 | 54 | const doit = function(count) { 55 | if(count <= 0) return 56 | if(!main()) { 57 | setTimeout(() => { doit(count-1) }, 1000) 58 | } 59 | } 60 | 61 | doit(10) 62 | 63 | })() 64 | 65 | -------------------------------------------------------------------------------- /gitlab.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name GitLab fixer 3 | // @namespace https://github.com/lilydjwg/userscripts 4 | // @description Fix GitLab for CN, HK, MO users 5 | // @match https://gitlab.com/* 6 | // @match https://about.gitlab.com/* 7 | // @version 0.1.0 8 | // @run-at document-start 9 | // ==/UserScript== 10 | 11 | (function() { 12 | 'use strict' 13 | 14 | Object.defineProperty(window.navigator, 'language', { 15 | configurable: true, 16 | get: function() { 17 | return 'en-US' 18 | }, 19 | }) 20 | 21 | Object.defineProperty(window.navigator, 'languages', { 22 | configurable: true, 23 | get: function() { 24 | return ['en-US', 'en'] 25 | }, 26 | }) 27 | 28 | })() 29 | 30 | -------------------------------------------------------------------------------- /google_cache_redirect.user.js: -------------------------------------------------------------------------------- 1 | // Google Cache Redirect Remover 2 | // GNU General Public License 3 | 4 | // ==UserScript== 5 | // @name Google Search Redirect Changer Remover 6 | // @namespace https://github.com/lilydjwg/userscripts 7 | // @description Change or remove the Google Search redirect in cached links 8 | // @include https://www.google.com/* 9 | // @include https://encrypted.google.*/* 10 | // @include https://www.google.*/* 11 | // @version 1.0 12 | // @grant none 13 | // ==/UserScript== 14 | 15 | const REDIRECT_VISITED_HTTPS = true 16 | const REMOVE_SEARCH_LINKS = false 17 | const APPEND_SEARCH_LINKS = true 18 | 19 | const doit = function() { 20 | if(REMOVE_SEARCH_LINKS) { 21 | const links = document.querySelectorAll('h3.r > a') 22 | for(let linkNode of links) { 23 | try { 24 | linkNode.removeAttribute('onmousedown') 25 | } catch(e) { 26 | console.error(e) 27 | continue 28 | } 29 | } 30 | } 31 | 32 | if(APPEND_SEARCH_LINKS) { 33 | const links = document.querySelectorAll('h3.r > a') 34 | for(let linkNode of links) { 35 | try { 36 | const newLinkNode = document.createElement('a') 37 | newLinkNode.href = linkNode.href 38 | newLinkNode.className = 'fl' 39 | newLinkNode.setAttribute('target', '_blank') 40 | newLinkNode.textContent = '原始链接' 41 | const urlNode = linkNode.parentNode.nextSibling.querySelector('cite') 42 | console.log(urlNode) 43 | urlNode.parentNode.appendChild(document.createTextNode(' - ')) 44 | urlNode.parentNode.appendChild(newLinkNode) 45 | } catch(e) { 46 | console.error(e) 47 | continue 48 | } 49 | } 50 | } 51 | } 52 | 53 | doit() 54 | 55 | document.addEventListener('overflow', function(e){ 56 | const main = document.querySelector('h2.hd') 57 | if(e.target == main){ 58 | doit() 59 | } 60 | }) 61 | 62 | //visited link indication fix 63 | if(REDIRECT_VISITED_HTTPS){ 64 | document.addEventListener('mouseup', function(e){ 65 | let el = e.target 66 | //find up to because there will be etc inside. 67 | while(el.tagName != 'A' && el.parentNode){ 68 | el = el.parentNode 69 | } 70 | //major search results or related ones 71 | if(el.parentNode && el.parentNode.className == 'r' || el.className == 'fl'){ 72 | el.href = el.href.replace('http://www.google.com/url', 'https://www.google.com/url') 73 | } 74 | }) 75 | } 76 | -------------------------------------------------------------------------------- /google_searchbar.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Google Searchbar 3 | // @namespace https://github.com/lilydjwg/userscripts 4 | // @description middle click paste in Google Searchbar 5 | // @include https://www.google.com/* 6 | // @include https://encrypted.google.*/* 7 | // @include https://www.google.*/* 8 | // @version 1.0 9 | // @grant none 10 | // ==/UserScript== 11 | 12 | document.addEventListener('mousedown', function(e){ 13 | const input = document.getElementById('lst-ib') 14 | if(e.target == input){ 15 | if(e.button == 1 && !e.altKey){ 16 | input.value = '' 17 | } 18 | return true 19 | } 20 | }, false) 21 | 22 | document.addEventListener('mouseup', function(e){ 23 | const input = document.getElementById('lst-ib') 24 | if(e.target == input){ 25 | if(e.button == 1 && !e.altKey){ 26 | setTimeout(function(){ 27 | document.querySelector('button[name="btnG"]').click() 28 | }, 100, false) 29 | } 30 | return true 31 | } 32 | }, false) 33 | -------------------------------------------------------------------------------- /googlegroups_remove_translation_tip.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Google Group 清理 3 | // @namespace https://github.com/lilydjwg/tampermonkey-scripts 4 | // @description 不用将帖子翻译成中文…… 5 | // @include https://groups.google.com/* 6 | // @include https://productforums.google.com/* 7 | // @version 1.1 8 | // @grant none 9 | // ==/UserScript== 10 | 11 | function doit() { 12 | console.log('cleaning up...') 13 | var divs = document.evaluate('//div[@class="gux-confirm-panel-c"]/div/a[text() = "将帖子翻译为中文"]/parent::div/parent::div', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null) 14 | 15 | for(var i=0, len=divs.snapshotLength; i strong') 20 | if(link) { 21 | const linkParent = link.parentNode 22 | const a = document.createElement('a') 23 | a.target = '_blank' 24 | a.href = link.textContent 25 | a.appendChild(link) 26 | linkParent.appendChild(a) 27 | } 28 | } 29 | } 30 | } 31 | } 32 | }) 33 | 34 | observer.observe(document.body, { childList: true }) 35 | 36 | })() 37 | 38 | -------------------------------------------------------------------------------- /visited-links.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name visited links 3 | // @namespace https://github.com/lilydjwg/userscripts 4 | // @description Use default color for visited links 5 | // @version 2.0 6 | // @include http://www.yaml.org/* 7 | // @include https://pythonhosted.org/* 8 | // @include http://www.freelists.org/* 9 | // @include https://*.readthedocs.io/* 10 | // @include http://docs.wand-py.org/* 11 | // ==/UserScript== 12 | 13 | const css = 'a:visited { color: #551a8b !important; }' 14 | addStyle(css) 15 | 16 | function addStyle(css) { 17 | const head = document.getElementsByTagName('head')[0] 18 | if (head) { 19 | const style = document.createElement('style') 20 | style.setAttribute('type', 'text/css') 21 | style.textContent = css 22 | head.appendChild(style) 23 | return style 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /wikipedia-lang-reorder.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Wikipedia language tweak 3 | // @description Move Chinese and English languages to outside 4 | // @namespace https://github.com/lilydjwg/userscripts 5 | // @match https://*.wikipedia.org/* 6 | // @match https://*.wiktionary.org/* 7 | // @match https://*.wikisource.org/* 8 | // @version 2.2 9 | // @grant GM_addStyle 10 | // ==/UserScript== 11 | 12 | (function() { 13 | 'use strict' 14 | 15 | const link_en = document.querySelector('.interlanguage-link.interwiki-en') 16 | const link_zh = document.querySelector('.interlanguage-link.interwiki-zh') 17 | const bar = document.querySelector('#p-views .vector-menu-content-list') 18 | may_add_it(bar, link_en) 19 | may_add_it(bar, link_zh) 20 | 21 | function may_add_it(bar, node) { 22 | if(node) { 23 | const new_link = node.cloneNode(true) 24 | new_link.className = 'mw-list-item vector-tab-noicon' 25 | bar.insertBefore(new_link, bar.childNodes[2]) 26 | } 27 | } 28 | 29 | GM_addStyle('.vector-tab-noicon.mw-list-item { line-height: 1.2; }') 30 | 31 | })() 32 | 33 | -------------------------------------------------------------------------------- /youtube-cc-lang.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name YouTube subtitles lang attribute 3 | // @namespace https://github.com/lilydjwg/userscripts 4 | // @description set YouTube subtitles lang attribute 5 | // @match https://www.youtube.com/* 6 | // @version 0.4.1 7 | // @grant window.onurlchange 8 | // ==/UserScript== 9 | 10 | (function() { 11 | 'use strict' 12 | 13 | const ZH_TW_CHANNELS = ['PanSci 泛科学', '舞秋風', '歷史衛視 History Channel'] 14 | 15 | let observer 16 | 17 | const run = function(el) { 18 | if(observer) { 19 | observer.disconnect() 20 | } 21 | observer = new MutationObserver((mutationList, observer) => { 22 | mutationList.forEach((mutation) => { 23 | const name = mutation.target.textContent 24 | const cc = document.getElementById('ytp-caption-window-container') 25 | if(!cc) { 26 | return; 27 | } 28 | console.log('ytcc: 字幕', mutation.target.textContent) 29 | if(name.includes('台湾') || name.includes('繁体')) { 30 | cc.lang = 'zh-TW' 31 | }else if(name.includes('日语')) { 32 | cc.lang = 'ja' 33 | }else if(name.includes('韩语')) { 34 | cc.lang = 'ko' 35 | }else{ 36 | const channel = document.getElementById('text-container').textContent.trim() 37 | console.log('ytcc: channel', channel) 38 | if(ZH_TW_CHANNELS.includes(channel)) { 39 | cc.lang = 'zh-TW' 40 | }else{ 41 | cc.removeAttribute('lang') 42 | } 43 | } 44 | }) 45 | }) 46 | 47 | console.log('ytcc: observing') 48 | observer.observe(el, { childList: true }) 49 | } 50 | 51 | let btn_clicked = false 52 | let retry_times = 3 53 | 54 | const start = function() { 55 | console.log('ytcc: start') 56 | if(location.href.indexOf('https://www.youtube.com/watch?') != 0) { 57 | console.log('ytcc: not watching, returning') 58 | return; 59 | } 60 | const button = document.querySelector('.ytp-popup.ytp-settings-menu') 61 | if(!button) { 62 | console.log('ytcc: no button, retry after 1000') 63 | setTimeout(start, 1000) 64 | } 65 | // const el = document.querySelector('.ytp-popup.ytp-settings-menu .ytp-menuitem:nth-child(2) .ytp-menuitem-content') 66 | 67 | const r = document.evaluate('//div[@class="ytp-popup ytp-settings-menu"]//span[text()="字幕"]/parent::div/parent::div/parent::div/div[@class="ytp-menuitem-content"]', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null) 68 | if(r.snapshotLength == 0) { 69 | if(!btn_clicked) { 70 | const settings_btn = document.querySelector('.ytp-settings-button') 71 | if(settings_btn) { 72 | settings_btn.click() 73 | btn_clicked = true 74 | } 75 | } 76 | if(retry_times > 0) { 77 | console.log('ytcc: no subtitles button, retry after 500') 78 | retry_times -= 1 79 | setTimeout(start, 500) 80 | } else { 81 | console.log('ytcc: no subtitles, giving up.') 82 | } 83 | } else { 84 | document.querySelector('.ytp-settings-button').click() 85 | run(r.snapshotItem(0)) 86 | } 87 | } 88 | 89 | start() 90 | 91 | // https://www.tampermonkey.net/documentation.php#api:window.onurlchange 92 | if(window.onurlchange === null) { 93 | // feature is supported 94 | window.addEventListener('urlchange', (info) => { 95 | console.log('ytcc: restart on urlchange', info) 96 | if(info.url.indexOf('https://www.youtube.com/watch?') == 0) { 97 | btn_clicked = false 98 | retry_times = 3 99 | start() 100 | } 101 | }) 102 | } 103 | 104 | })() 105 | 106 | -------------------------------------------------------------------------------- /youtube-space.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name YouTube space to pause / play 3 | // @namespace https://github.com/lilydjwg/userscripts 4 | // @description Use space key to pause / play video 5 | // @match https://www.youtube.com/* 6 | // @version 0.3 7 | // @grant window.onurlchange 8 | // ==/UserScript== 9 | 10 | (function() { 11 | 'use strict' 12 | 13 | const run = function() { 14 | console.log('ytspace: start') 15 | if(location.href.indexOf('https://www.youtube.com/watch?') != 0) { 16 | console.log('ytspace: not watch, returning') 17 | return; 18 | } 19 | 20 | const v = document.querySelector('video.video-stream.html5-main-video') 21 | if(!v) { 22 | console.log('ytspace: no video, retry after 500ms') 23 | setTimeout(run, 500) 24 | return 25 | } 26 | 27 | console.log('ytspace: video element', v) 28 | 29 | let last_pause = 0 30 | let last_play = 0 31 | 32 | v.addEventListener('pause', (e) => { 33 | console.log('ytspace: paused') 34 | last_pause = e.timeStamp 35 | }) 36 | v.addEventListener('play', (e) => { 37 | console.log('ytspace: played') 38 | last_play = e.timeStamp 39 | }) 40 | 41 | 42 | document.addEventListener('keyup', (e) => { 43 | // https://developer.mozilla.org/en-US/docs/Web/API/Element/keyup_event 44 | // console.log('keyup', e) 45 | if (e.isComposing || e.keyCode === 229) { 46 | return 47 | } 48 | 49 | if (e.keyCode === 0x20) { 50 | if (['TEXTAREA', 'INPUT'].includes(e.target.tagName) || e.target.contentEditable === "true") { 51 | // console.log('skip editable element', e.target, e.target.tagName, e.target.contentEditable) 52 | return 53 | } 54 | 55 | // console.log('setTimeout') 56 | setTimeout(function() { // run after YT's own 57 | if (v.paused && Math.abs(e.timeStamp - last_pause) > 200) { 58 | console.log('ytspace: play') 59 | v.play() 60 | } else if (Math.abs(e.timeStamp - last_play) > 200) { 61 | console.log('ytspace: pause') 62 | v.pause() 63 | } 64 | }, 100) 65 | } 66 | 67 | }, { 68 | passive: true, 69 | }) 70 | } 71 | 72 | run() 73 | 74 | if(window.onurlchange === null) { 75 | // feature is supported 76 | window.addEventListener('urlchange', (info) => { 77 | run() 78 | }) 79 | } 80 | 81 | })() 82 | 83 | -------------------------------------------------------------------------------- /zdic.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name 汉典修正 3 | // @namespace https://github.com/lilydjwg/userscripts 4 | // @description 汉典字体调整 5 | // @match https://www.zdic.net/* 6 | // @version 0.2.1 7 | // @grant none 8 | // ==/UserScript== 9 | 10 | // 「生僻字转文字」 11 | setTimeout(function(){ 12 | const hanzi2char = document.getElementById('twbon') 13 | if(hanzi2char){ 14 | const evt = document.createEvent("MouseEvents") 15 | evt.initMouseEvent("click", true, true, window, 16 | 0, 0, 0, 0, 0, false, false, false, false, 0, null) 17 | hanzi2char.dispatchEvent(evt) 18 | } 19 | }, 1500) 20 | 21 | -------------------------------------------------------------------------------- /zhihu-tweak.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name 知乎修正 3 | // @namespace https://github.com/lilydjwg/userscripts 4 | // @version 0.7 5 | // @description 中键后台标签页、评论区 Tab 到提交按钮、图片立即加载、去除无意义链接 6 | // @author lilydjwg 7 | // @match https://zhuanlan.zhihu.com/p/* 8 | // @match https://www.zhihu.com/* 9 | // @grant none 10 | // @run-at document-end 11 | // ==/UserScript== 12 | 13 | const func = function() { 14 | const content_node = document.querySelector('.PostIndex-content') 15 | if(!content_node) { 16 | return 17 | } 18 | const cloned = content_node.cloneNode(true) 19 | const parent = content_node.parentNode 20 | parent.insertBefore(cloned, content_node) 21 | parent.removeChild(content_node) 22 | 23 | for(let el of document.querySelectorAll('.PostIndex-content .VagueImage[data-src]')) { 24 | const img = document.createElement('img') 25 | img.src = el.dataset.src 26 | const a = document.createElement('a') 27 | a.href = el.dataset.src.replace(/_b(?=\.)/, '_r') 28 | a.appendChild(img) 29 | el.appendChild(a) 30 | } 31 | for(let el of document.querySelectorAll('.PostIndex-content img.column-gif')) { 32 | el.src = el.src.replace(/\.jpg$/, '.gif') 33 | el.parentNode.classList.add('is-playing') 34 | } 35 | 36 | for(let el of document.querySelectorAll('.PostIndex-content div.VagueImage-mask')) { 37 | el.parentNode.removeChild(el) 38 | } 39 | 40 | // videos 41 | for(let el of document.querySelectorAll('.PostIndex-content div.Video[data-za-module-info]')) { 42 | const info = JSON.parse(el.dataset.zaModuleInfo).card.content 43 | if(info.sub_type == 'SelfHosted') { 44 | const vid = info.video_id 45 | const vframe = document.createElement('iframe') 46 | vframe.src = `https://www.zhihu.com/video/${vid}` 47 | vframe.frameBorder = '0' 48 | vframe.allowFullscreen = true 49 | 50 | const player = el.querySelector('.VideoCard-player') 51 | player.appendChild(vframe) 52 | } 53 | } 54 | } 55 | 56 | setTimeout(func, 500) 57 | 58 | const check_comment_actions = function(evt) { 59 | const el = evt.target 60 | if(el.classList.contains('public-DraftStyleDefault-block')){ 61 | const actions = document.querySelectorAll('.CommentEditor-actions > .Button--plain:first-child') 62 | console.debug('actions', actions) 63 | for(let el of actions) { 64 | const p = el.parentNode 65 | const primary = el.nextSibling 66 | p.insertBefore(primary, el) 67 | } 68 | } 69 | return true 70 | } 71 | 72 | document.body.addEventListener('click', check_comment_actions) 73 | 74 | const remove_meaningless_links = function() { 75 | for(const node of document.querySelectorAll('span > a[data-za-not-track-link]')){ 76 | const text = node.textContent 77 | const el = document.createElement('span') 78 | el.textContent = text 79 | node.parentNode.parentNode.replaceChild(el, node.parentNode) 80 | } 81 | } 82 | 83 | setInterval(remove_meaningless_links, 1000) 84 | 85 | // mobile site 86 | const continue_in_browser_on_load = function() { 87 | let ell = document.querySelectorAll('.ModalWrap-itemBtn') 88 | if(ell.length == 2) { 89 | ell[1].click() 90 | } 91 | } 92 | setTimeout(continue_in_browser_on_load, 500) 93 | 94 | const continue_in_browser = function() { 95 | let el = document.querySelector('.SkipModal .Button--plain') 96 | if(el) { 97 | el.click() 98 | } 99 | } 100 | 101 | document.addEventListener('click', function(e) { 102 | if(e.target.classList.contains('ContentItem-expandButton')) { 103 | setTimeout(continue_in_browser, 100) 104 | } 105 | }) 106 | 107 | --------------------------------------------------------------------------------