├── .gitignore ├── .vscode └── settings.json ├── README.md ├── dist ├── better-pcgamer.user.js ├── download-twitter-videos.user.js ├── hackernews.user.js ├── hide-reddit-awards.js ├── obsidian-omnisearch-bing.user.js ├── obsidian-omnisearch-ddg.user.js ├── obsidian-omnisearch-google.user.js └── obsidian-omnisearch-kagi.user.js ├── package.json ├── pnpm-lock.yaml ├── src ├── better-pcgamer.user.ts ├── download-twitter-videos.user.ts ├── globals.d.ts ├── hackernews.user.ts ├── hide-reddit-awards.ts ├── obsidian-omnisearch-bing.user.ts ├── obsidian-omnisearch-ddg.user.ts ├── obsidian-omnisearch-google.user.ts ├── obsidian-omnisearch-kagi.user.ts └── types │ └── gm_config.d.ts ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.autoFixOnSave": true, 3 | "typescript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": false, 4 | "editor.codeActionsOnSave": { 5 | "source.organizeImports": "never", 6 | "source.fixAll.tslint": "explicit", 7 | "source.fixAll.eslint": "explicit" 8 | }, 9 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Userscripts 2 | 3 | Those scripts may or may not be maintained. 4 | 5 | Your userscripts plugin should update them automatically, if installed through their url. 6 | 7 | ## Obsidian Omnisearch results injection in your favorite web search engine 8 | 9 | - [Install for Kagi](https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-kagi.user.js) 10 | - [Install for DuckDuckGo](https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-ddg.user.js) 11 | - [Install for Google](https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-google.user.js) 12 | - [Install for Bing](https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-bing.user.js) 13 | 14 | 15 | Inject Obsidian Omnisearch results directly into your search engine. [More information](https://publish.obsidian.md/omnisearch/Inject+Omnisearch+results+into+your+search+engine) 16 | 17 | ## Hacker News 18 | 19 | [Install](https://github.com/scambier/userscripts/raw/master/dist/hackernews.user.js) 20 | 21 | Adds emojis to show most upvoted 🔥 and most commented 👄 links of Hacker News. 22 | 23 | ## Better PCGamer 24 | 25 | [Install](https://github.com/scambier/userscripts/raw/master/dist/better-pcgamer.user.js) 26 | 27 | Enhance your reading experience of PCGamer articles. Removes the useless sidebar, interstitial unrelated videos, and Disqus comments. 28 | 29 | ## What are userscripts, and how to install them 30 | 31 | Userscripts are kind of micro-addons for your browser. They're typically simple JavaScript files that are automatically executed on specific site, to alter their behavior or appearance. 32 | 33 | They are managed with a "real" add-on, usually [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/) or [Tampermonkey](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo). 34 | 35 | More information [here](https://openuserjs.org/about/Userscript-Beginners-HOWTO). 36 | -------------------------------------------------------------------------------- /dist/better-pcgamer.user.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // ==UserScript== 3 | // @name Better PCGamer 4 | // @namespace https://github.com/scambier/userscripts 5 | // @version 0.1 6 | // @license ISC 7 | // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js 8 | // @require https://openuserjs.org/src/libs/sizzle/GM_config.min.js 9 | // @description Enhance the reading experience of PCGamer articles 10 | // @author Simon Cambier 11 | // @match https://www.pcgamer.com/* 12 | // @grant GM_getValue 13 | // @grant GM_setValue 14 | // ==/UserScript== 15 | (function () { 16 | GM_config.init({ 17 | id: "PCG_Config", 18 | title: "Better PCGamer Config", 19 | fields: { 20 | disableRecommendedVideos: { 21 | label: 'Disable "recommended videos"', 22 | type: "checkbox", 23 | default: true, 24 | }, 25 | disableDisqusComments: { 26 | label: "Disable Disqus comments", 27 | type: "checkbox", 28 | default: true, 29 | }, 30 | disableBell: { 31 | label: 'Disable the "You\'ve blocked notifications" bell', 32 | type: "checkbox", 33 | default: true, 34 | }, 35 | disableSidebar: { 36 | label: "Disable sidebar, and widen the article width if possible", 37 | type: "checkbox", 38 | default: true, 39 | }, 40 | }, 41 | css: ``, 42 | events: { 43 | save: () => { 44 | location.reload(); 45 | }, 46 | }, 47 | }); 48 | $(document).on("click", "#PCGConfigBtn", function () { 49 | GM_config.open(); 50 | }); 51 | function main() { 52 | // Add config button 53 | if (!$("#PCGConfigBtn")[0]) { 54 | const btn = document.createElement("a"); 55 | btn.setAttribute("style", ` 56 | position: fixed; 57 | bottom: 5px; 58 | left: 5px; 59 | cursor: pointer 60 | `); 61 | btn.id = "PCGConfigBtn"; 62 | btn.appendChild(document.createTextNode("⚙️")); 63 | document.querySelector("body").appendChild(btn); 64 | } 65 | // Recommended videos 66 | if (GM_config.get("disableRecommendedVideos")) { 67 | $('div:contains("RECOMMENDED VIDEOS FOR YOU...")') 68 | .closest(".future__jwplayer--carousel") 69 | .remove(); 70 | // $('.leaderboardAdPresent').remove() 71 | } 72 | // Comments 73 | if (GM_config.get("disableDisqusComments")) { 74 | $(".jump-to-comments").remove(); 75 | $("#article-comments").remove(); 76 | $("ul.socialite-widget-ul li.comment").remove(); 77 | } 78 | // "Disabled notification" bell 79 | if (GM_config.get("disableBell")) { 80 | $("#onesignal-bell-container").remove(); 81 | } 82 | // Ads 83 | if (GM_config.get("disableSidebar")) { 84 | $("#sidebar").remove(); 85 | // $('.slot-single-height-0-500').remove() 86 | $("#content-after-image").attr("style", "width: 100%; max-width: 100%;"); 87 | $(".news-article").attr("style", "width: 100%; max-width: 100%;"); 88 | } 89 | } 90 | const observer = new MutationObserver((mutations) => { 91 | main(); 92 | }); 93 | observer.observe(document.documentElement, { 94 | attributes: false, 95 | characterData: false, 96 | childList: true, 97 | subtree: true, 98 | attributeOldValue: false, 99 | characterDataOldValue: false, 100 | }); 101 | main(); 102 | })(); 103 | -------------------------------------------------------------------------------- /dist/download-twitter-videos.user.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // ==UserScript== 3 | // @name Dowload Twitter Videos 4 | // @namespace https://github.com/scambier/userscripts 5 | // @author Simon Cambier 6 | // @version 0.5.7 7 | // @description Adds a download button to quickly fetch gifs and videos embedded in tweets 8 | // @license ISC 9 | // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js 10 | // @include https://twitter.com/* 11 | // @include https://tweetdeck.twitter.com/* 12 | // @include http://twittervideodownloader.com/?url=* 13 | // @include https://www.savetweetvid.com/?url=* 14 | // @include https://twdown.net/?url=* 15 | // @include https://twittervideodownloader.online/?url=* 16 | // @grant none 17 | // @run-at document-end 18 | // ==/UserScript== 19 | // console = unsafeWindow.console; 20 | (() => { 21 | 'use strict'; 22 | function main() { 23 | } 24 | // interface IMediaBlock { 25 | // videoContainer: Element, 26 | // src?: string 27 | // } 28 | // const externalDomains = [ 29 | // 'http://savetweetvid.com/?url=', 30 | // 'https://twdown.net/?url=', 31 | // 'http://twittervideodownloader.com/?url=', 32 | // 'https://twittervideodownloader.online/?url=' 33 | // // 'https://twdownload.com/?url=', 34 | // // 'https://twdownloader.net/?url=' 35 | // ] 36 | // const externalDomainsBlockquotes = [ 37 | // 'http://twittervideodownloader.com/?url=', 38 | // // 'https://twdownloader.net/?url=' 39 | // ] 40 | // // Do not reexecute script when Twitter location changes 41 | // if (window.location.href.includes('twitter.com/i/cards')) { 42 | // return 43 | // } 44 | // //#region JQuery events 45 | // $(document).on('click', '[data-dtv]', function(e): void { 46 | // let mediaUrl = $(this).attr('data-dtv-media-url')! 47 | // const tweetUrl = $(this).attr('data-dtv-tweet-url')! 48 | // const blockquote = $(this).attr('data-dtv-blockquote') === 'true' 49 | // if (mediaUrl.startsWith('blob:') || mediaUrl.startsWith('https://t.co')) { 50 | // // If it's a blob video, redirect to twdownload.com 51 | // mediaUrl = ( 52 | // blockquote 53 | // ? getRandomItem(externalDomainsBlockquotes) // These urls are compatible with blockquotes 54 | // : getRandomItem(externalDomains) 55 | // ) + tweetUrl 56 | // } 57 | // e.stopPropagation() 58 | // window.open(mediaUrl) 59 | // }) 60 | // $(document).on('click', 'a.tweet-action, a.tweet-detail-action', function(e): void { 61 | // setTimeout(() => { 62 | // const tweetUrl = getTweetUrl_tweetDeck(this) 63 | // const article: JQuery = $(this).closest('article') 64 | // const mediaUrl = article.attr('data-dtv-video') 65 | // const actions = article.find('.js-dropdown-content ul') 66 | // if (mediaUrl && !actions.find('[data-dtv-video]').length) { 67 | // actions.append(` 68 | //
  • 69 | // Download media 70 | //
  • `) 71 | // } 72 | // }, 0) 73 | // }) 74 | // $(document).on('mouseenter', '.is-selectable', function(e): void { 75 | // $(this).addClass('is-selected') 76 | // }) 77 | // $(document).on('mouseleave', '.is-selectable', function(e): void { 78 | // $(this).removeClass('is-selected') 79 | // }) 80 | // //#endregion 81 | // //#region CSS 82 | // const style = document.createElement('style') 83 | // style.setAttribute('type', 'text/css') 84 | // style.appendChild(document.createTextNode(` 85 | // .dtv-link { 86 | // cursor: pointer; 87 | // color: white; 88 | // position: absolute; 89 | // font-size: 1em; 90 | // background-color: #14171A; 91 | // padding: 2px 2px 0 3px; 92 | // border-radius: 2px; 93 | // top: 7px; 94 | // left: 7px; 95 | // opacity: 0; 96 | // transition: 0.2s; 97 | // z-index: 999; 98 | // } 99 | // .dtv-link img { 100 | // border: none; 101 | // } 102 | // .dtv-link:visited { 103 | // color: white; 104 | // } 105 | // .dtv-link:hover { 106 | // color: white; 107 | // } 108 | // article:hover .dtv-link { 109 | // opacity: 1; 110 | // transition: 0.2s; 111 | // } 112 | // `)) 113 | // document.head.appendChild(style) 114 | // //#endregion CSS 115 | // //#region Twitter 116 | // function getTweetUrl_twitter(videoContainer: Element): string { 117 | // return location.origin + $(videoContainer).closest('article').find('a[href*="status"]')[0].getAttribute('href')! 118 | // } 119 | // function addButtonOverVideo(video: IMediaBlock): void { 120 | // // Make sure that the link exists 121 | // if (!video.src) { return } 122 | // // If it's a blockquote 123 | // const blockquote = $(video.videoContainer).closest('div[role="blockquote"]')[0] 124 | // // If button is already added 125 | // if ($(video.videoContainer).find('[data-dtv]').length) { return } 126 | // // video.container.setAttribute('style', 'position: relative;') 127 | // // Create the button (not a real button, just a link) 128 | // const link = document.createElement('a') 129 | // link.className = 'dtv-link' 130 | // // Add the download icon 131 | // const icon = document.createElement('img') 132 | // icon.setAttribute('src', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAABhUlEQVQ4ja2UvWpUURSFvz0MQUKYYoiCU0qUFCIiqUVSTOETWOUxLHyD1FMFGzufwFLyAlNIggg+gPgHwWCISXB9FrlXruNMIpJzinvhnP2x9l7r3hK5itW/8FTWgGsA6sfq1dcL7s7fSVbUXfWtuq8+W3RXXKyoqpbVe8CwqgBu/39rrWrP51jUwju9yyCNmkvXn4pkGdhUh8igqpbUFrZm3Gre94A9inRqO1tHSXbVI/VYNYlJVM/UoyTf1Kdqv1s7z6376rsupAP7qU6SDGfr/jZSe+q4hbXABvIyyeo8++en4hz2IMl+wzpplNxYlKNKMgK2qupmx+5U1WvgVN2uqjfqpKoeA9c79nwCXlB8IMk4ycnsTNQvSZ6od9WNJK/Us+bMjtJxm+w+sNRmprVbXa2qHWAKjKpqHTgEPgO3gPfAnTZCvS5gThAHwCaw3rQ8rarnwA9g0jx/z+NRkoOZtrpuzdrf5utYPVAftsMeABvAyr9+Do0Aquo7MKU4rKv6sf0CJZXR6U2U6EQAAAAASUVORK5CYII=') 133 | // link.setAttribute('data-dtv', '') 134 | // link.setAttribute('data-dtv-media-url', video.src) 135 | // link.setAttribute('data-dtv-tweet-url', getTweetUrl_twitter(video.videoContainer)) 136 | // if (blockquote) { 137 | // link.setAttribute('data-dtv-blockquote', 'true') 138 | // } 139 | // link.appendChild(icon) 140 | // video.videoContainer.appendChild(link) 141 | // } 142 | // function getVideos_twitter(): IMediaBlock[] { 143 | // const elems: IMediaBlock[] = [] 144 | // const videos: JQuery = $('video') 145 | // for (const video of videos) { 146 | // const videoContainer = $(video).parent().get(0) 147 | // // if (!container || container.querySelector('[data-dtv]')) { continue } 148 | // elems.push({ 149 | // videoContainer, 150 | // src: video.currentSrc 151 | // }) 152 | // } 153 | // return elems 154 | // } 155 | // //#endregion Twitter 156 | // //#region Tweetdeck 157 | // function getTweetUrl_tweetDeck(elem: Element): string | null { 158 | // const article = elem.closest('article')! 159 | // const tweetLink = article.querySelector('[rel="url"]') 160 | // if (tweetLink) { 161 | // return tweetLink.getAttribute('href') 162 | // } 163 | // throw new Error('DTV - Could not found tweet url') 164 | // } 165 | // function getVideos_tweetdeck(): IMediaBlock[] { 166 | // const elems: IMediaBlock[] = [] 167 | // const gifs: JQuery = $('.js-media-gif.media-item-gif') 168 | // for (const gif of gifs) { 169 | // const container = gif.closest('article')! 170 | // if (container.querySelector('[data-dtv]')) { continue } 171 | // elems.push({ 172 | // videoContainer: container, 173 | // src: gif.getAttribute('src')! 174 | // }) 175 | // } 176 | // const videos = $('div.is-video') 177 | // for (const video of videos) { 178 | // // Only keep "internal" twitter videos 179 | // const src = video.querySelector('[rel=mediaPreview]')!.getAttribute('href')! 180 | // const container = video.closest('article')! 181 | // if (src.startsWith('https://t.co/') && !container.querySelector('[data-dtv]')) { 182 | // elems.push({ 183 | // videoContainer: container, 184 | // src 185 | // }) 186 | // } 187 | // } 188 | // return elems 189 | // } 190 | // //#endregion Tweetdeck 191 | // //#region External services 192 | // function download_twdownload(): void { 193 | // const url = getUrlQuery() 194 | // if (url) { 195 | // const form = document.querySelector('form[action="/download-track/"]') as HTMLElement 196 | // const input = form.querySelector('input[name="twitter-url"]') as HTMLElement 197 | // const submit = form.querySelector('[type="submit"]') as HTMLElement 198 | // input.setAttribute('value', url) 199 | // submit.click() 200 | // } 201 | // } 202 | // function download_twittervideodownloader(): void { 203 | // const url = getUrlQuery() 204 | // if (url) { 205 | // const form = document.querySelector('form[action="/download"]') as HTMLElement 206 | // const input = form.querySelector('input[name="tweet"]') as HTMLElement 207 | // const submit = form.querySelector('[type="submit"]') as HTMLElement 208 | // input.setAttribute('value', url) 209 | // submit.click() 210 | // } 211 | // } 212 | // function download_twittervideodownloader_online(): void { 213 | // const url = getUrlQuery() 214 | // if (url) { 215 | // const input = document.querySelector('input#twitter_url') as HTMLInputElement 216 | // const submit = document.querySelector('button#button') as HTMLButtonElement 217 | // input.setAttribute('value', url) 218 | // setTimeout(() => { 219 | // submit.click() 220 | // }, 100) 221 | // } 222 | // } 223 | // function download_savetweetvid(): void { 224 | // const url = getUrlQuery() 225 | // if (url) { 226 | // const form = document.getElementById('form_download') as HTMLElement 227 | // const input = form.querySelector('input[name="url"]') as HTMLElement 228 | // const submit = form.querySelector('[type="submit"]') as HTMLElement 229 | // input.setAttribute('value', url) 230 | // submit.click() 231 | // } 232 | // } 233 | // function download_twdownloader(): void { 234 | // const url = getUrlQuery() 235 | // if (url) { 236 | // const form = document.querySelector('form[action="/download/"]') as HTMLElement 237 | // const input = form.querySelector('input[name="tweet"]') as HTMLElement 238 | // const submit = form.querySelector('[type="submit"]') as HTMLElement 239 | // input.setAttribute('value', url) 240 | // submit.click() 241 | // } 242 | // } 243 | // function download_twdown(): void { 244 | // const url = getUrlQuery() 245 | // if (url) { 246 | // const form = document.querySelector('form[action="download.php"]') as HTMLElement 247 | // const input = form.querySelector('input[name="URL"]') as HTMLElement 248 | // const submit = form.querySelector('[type="submit"]') as HTMLElement 249 | // input.setAttribute('value', url) 250 | // submit.click() 251 | // } 252 | // } 253 | // //#endregion External services 254 | // //#region Utils 255 | // function isTwitter(): boolean { 256 | // return location.host === 'twitter.com' 257 | // } 258 | // function isTweetdeck(): boolean { 259 | // return location.host === 'tweetdeck.twitter.com' 260 | // } 261 | // function getUrlQuery(): string | null { 262 | // const urlParams = new URLSearchParams(window.location.search) 263 | // return urlParams.get('url') 264 | // } 265 | // function getRandomItem(items: T[]): T { 266 | // return items[Math.floor(Math.random() * items.length)] 267 | // } 268 | // //#endregion Utils 269 | // /** 270 | // * Create download links on the timeline 271 | // */ 272 | // function main(): void { 273 | // if (isTwitter()) { 274 | // const videos = getVideos_twitter() 275 | // for (const video of videos) { 276 | // addButtonOverVideo(video) 277 | // } 278 | // } 279 | // else if (isTweetdeck()) { 280 | // const videos = getVideos_tweetdeck() 281 | // for (const video of videos) { 282 | // if (!video.src) { continue } 283 | // video.videoContainer.setAttribute('data-dtv-video', video.src) 284 | // } 285 | // } 286 | // } 287 | if (location.hostname.includes('twitter.com')) { 288 | setInterval(() => { 289 | main(); 290 | }, 1000); 291 | } 292 | // else { 293 | // switch (window.location.hostname) { 294 | // case 'twdownload.com': 295 | // download_twdownload() 296 | // break 297 | // case 'www.savetweetvid.com': 298 | // download_savetweetvid() 299 | // break 300 | // case 'twittervideodownloader.com': 301 | // download_twittervideodownloader() 302 | // break 303 | // case 'twdownloader.net': 304 | // download_twdownloader() 305 | // break 306 | // case 'twdown.net': 307 | // download_twdown() 308 | // break 309 | // case 'twittervideodownloader.online': 310 | // download_twittervideodownloader_online() 311 | // break 312 | // } 313 | // } 314 | })(); 315 | -------------------------------------------------------------------------------- /dist/hackernews.user.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* globals jQuery, $, waitForKeyElements */ 3 | // ==UserScript== 4 | // @name Hacker News - Most upvoted & most commented links 5 | // @namespace https://github.com/scambier/userscripts 6 | // @downloadURL https://github.com/scambier/userscripts/raw/refs/heads/master/dist/hackernews.user.js 7 | // @updateURL https://github.com/scambier/userscripts/raw/refs/heads/master/dist/hackernews.user.js 8 | // @author Simon Cambier 9 | // @version 0.0.10 10 | // @description Show top 🔥👄 links of Hacker News 11 | // @license ISC 12 | // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js 13 | // @match https://news.ycombinator.com/ 14 | // @match https://news.ycombinator.com/ask* 15 | // @match https://news.ycombinator.com/news* 16 | // @match https://news.ycombinator.com/show* 17 | // @match https://news.ycombinator.com/front* 18 | // @grant none 19 | // @run-at document-end 20 | // ==/UserScript== 21 | (() => { 22 | // Firefox mobile fix, do nothing if icons are already injected 23 | if ([...$('[data-userscript="scambier"]')].length) 24 | return; 25 | const rows = [...$("tr.athing")]; 26 | // Select lines 27 | const items = rows 28 | // Filter out ads 29 | .filter((tr) => $(tr).find("td.votelinks").length) 30 | // Get id and score 31 | .map((tr) => { 32 | var _a, _b; 33 | return { 34 | id: $(tr).attr("id"), 35 | score: Number((_a = $(tr).next().find(".score")[0].textContent) === null || _a === void 0 ? void 0 : _a.split(" ")[0]), 36 | // Weirdly, .split(' ') does not work 37 | comments: Number((_b = $(tr) 38 | .next() 39 | .find('a:contains("comments")') 40 | .text() 41 | .split("comments")[0] 42 | .trim()) !== null && _b !== void 0 ? _b : 0), 43 | }; 44 | }); 45 | // Top 10% for new entries, top 20% for other pages 46 | const ratio = location.href.includes("news.ycombinator.com/newest") 47 | ? 0.1 48 | : 0.2; 49 | // Inject icons 50 | items.forEach((o) => { 51 | const title = $(`tr#${o.id}`).find("span.titleline"); 52 | if (getTop(items, "comments", ratio).includes(o) && o.comments > 0) { 53 | title.before('👄 '); 54 | } 55 | if (getTop(items, "score", ratio).includes(o) && o.score > 0) { 56 | title.before('🔥 '); 57 | } 58 | }); 59 | /** 60 | * Get [ratio] best [items], ordered by [key] 61 | * 62 | * @param items 63 | * @param key 64 | * @param ratio 65 | */ 66 | function getTop(items, key, ratio) { 67 | const count = rows.length; 68 | return [...items].sort((a, b) => b[key] - a[key]).slice(0, count * ratio); 69 | } 70 | })(); 71 | -------------------------------------------------------------------------------- /dist/hide-reddit-awards.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | -------------------------------------------------------------------------------- /dist/obsidian-omnisearch-bing.user.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // ==UserScript== 3 | // @name Obsidian Omnisearch in Bing 4 | // @version 0.3 5 | // @description Injects Obsidian notes into Bing search results 6 | // @author ever 7 | // @namespace https://github.com/scambier/userscripts 8 | // @downloadURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-bing.user.js 9 | // @updateURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-bing.user.js 10 | // @match *://*bing.com/* 11 | // @match https://bing.com/* 12 | // @match https://www.bing.com/* 13 | // @icon https://obsidian.md/favicon.ico 14 | // @require https://code.jquery.com/jquery-3.7.1.min.js 15 | // @require https://raw.githubusercontent.com/sizzlemctwizzle/GM_config/master/gm_config.js 16 | // @require https://gist.githubusercontent.com/scambier/109932d45b7592d3decf24194008be4d/raw/9c97aa67ff9c5d56be34a55ad6c18a314e5eb548/waitForKeyElements.js 17 | // @grant GM.xmlHttpRequest 18 | // @grant GM_getValue 19 | // @grant GM_setValue 20 | // @grant GM.getValue 21 | // @grant GM.setValue 22 | // ==/UserScript== 23 | (function () { 24 | "use strict"; 25 | // Bing's right "sidebar" selector for additional content 26 | const sidebarSelector = "#b_context"; 27 | // The results div 28 | const resultsDivId = "OmnisearchObsidianResultsBing"; 29 | // The "loading"/"no results" label 30 | const loadingSpanId = "OmnisearchObsidianLoadingBing"; 31 | // Configure GM_config 32 | // The `new GM_config()` syntax is not recognized by the TS compiler 33 | // @ts-ignore 34 | const gmc = new GM_config({ 35 | id: "ObsidianOmnisearchBing", 36 | title: "Omnisearch in Bing - Configuration", 37 | fields: { 38 | port: { 39 | label: "HTTP Port", 40 | type: "text", 41 | default: "51361", 42 | }, 43 | nbResults: { 44 | label: "Number of results to display", 45 | type: "int", 46 | default: 3, 47 | }, 48 | }, 49 | events: { 50 | save: () => location.reload(), 51 | init: () => { }, 52 | }, 53 | }); 54 | // Promise resolves when initialization completes 55 | const onInit = (config) => new Promise((resolve) => { 56 | let isInit = () => setTimeout(() => (config.isInit ? resolve() : isInit()), 0); 57 | isInit(); 58 | }); 59 | function omnisearch() { 60 | const port = gmc.get("port"); 61 | const nbResults = gmc.get("nbResults"); 62 | const params = new URLSearchParams(window.location.search); 63 | const query = params.get("q"); 64 | if (!query) 65 | return; 66 | injectLoadingLabel(); 67 | GM.xmlHttpRequest({ 68 | method: "GET", 69 | url: `http://localhost:${port}/search?q=${encodeURIComponent(query)}`, 70 | onload: function (response) { 71 | const data = JSON.parse(response.responseText); 72 | removeLoadingLabel(data.length > 0); 73 | data.splice(nbResults); 74 | const resultsDiv = $(`#${resultsDivId}`); 75 | resultsDiv.empty(); // Clear previous results 76 | data.forEach((item) => { 77 | const url = `obsidian://open?vault=${encodeURIComponent(item.vault)}&file=${encodeURIComponent(item.path)}`; 78 | const resultHTML = ` 79 |
    80 |

    81 | ${item.basename} 82 |

    83 |

    ${item.excerpt.replace(//gi, " ")}

    84 |
    85 | `; 86 | resultsDiv.append(resultHTML); 87 | }); 88 | }, 89 | onerror: function () { 90 | const span = $(`#${loadingSpanId}`); 91 | if (span.length) { 92 | span.html(`Error: Obsidian is not running or the Omnisearch server is not enabled. 93 |
    Open Obsidian.`); 94 | } 95 | } 96 | }); 97 | } 98 | function injectLoadingLabel() { 99 | if (!$(`#${loadingSpanId}`).length) { 100 | $(sidebarSelector).prepend(`Loading...`); 101 | } 102 | } 103 | function removeLoadingLabel(foundResults = true) { 104 | if (foundResults) { 105 | $(`#${loadingSpanId}`).remove(); 106 | } 107 | else { 108 | $(`#${loadingSpanId}`).text("No results found"); 109 | } 110 | } 111 | function injectResultsContainer() { 112 | if (!$(`#${resultsDivId}`).length) { 113 | $(sidebarSelector).prepend(`
    `); 114 | } 115 | } 116 | function showSettingsDialog() { 117 | const nbResults = gmc.get("nbResults"); 118 | const newNbResults = prompt("Enter the number of results to display:", nbResults); 119 | if (newNbResults !== null) { 120 | gmc.set("nbResults", parseInt(newNbResults)); 121 | gmc.save(); 122 | } 123 | } 124 | console.log("Loading Omnisearch injector"); 125 | let init = onInit(gmc); 126 | init.then(() => { 127 | gmc.init(); 128 | injectResultsContainer(); 129 | omnisearch(); // Make an initial call, just to avoid an improbable race condition 130 | console.log("Loaded Omnisearch injector"); 131 | // Add a button to show settings dialog 132 | const settingsButton = $("").css({ 133 | marginRight: "10px", 134 | }).click(showSettingsDialog); 135 | const headerContainer = $("
    ").css({ 136 | display: "flex", 137 | alignItems: "center", 138 | justifyContent: "space-between", 139 | }); 140 | const resultsHeader = $(`

    Obsidian Search Results

    `); 141 | headerContainer.append(resultsHeader, settingsButton); // Append both the header and the button to the container 142 | $(sidebarSelector).prepend(headerContainer); // Prepend the container instead of the header 143 | }); 144 | })(); 145 | -------------------------------------------------------------------------------- /dist/obsidian-omnisearch-ddg.user.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // ==UserScript== 3 | // @name Obsidian Omnisearch in DuckDuckGo 4 | // @namespace https://github.com/scambier/userscripts 5 | // @downloadURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-ddg.user.js 6 | // @updateURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-ddg.user.js 7 | // @version 0.1.0 8 | // @description Injects Obsidian notes in DuckDuckGo search results 9 | // @author Simon Cambier 10 | // @match https://duckduckgo.com/* 11 | // @match https://www.duckduckgo.com/* 12 | // @icon https://obsidian.md/favicon.ico 13 | // @require https://code.jquery.com/jquery-3.7.1.min.js 14 | // @require https://gist.githubusercontent.com/scambier/109932d45b7592d3decf24194008be4d/raw/9c97aa67ff9c5d56be34a55ad6c18a314e5eb548/waitForKeyElements.js 15 | // @require https://cdn.jsdelivr.net/npm/vue@3 16 | // @require https://raw.githubusercontent.com/sizzlemctwizzle/GM_config/master/gm_config.js 17 | // @grant GM.xmlHttpRequest 18 | // @grant GM_getValue 19 | // @grant GM_setValue 20 | // @grant GM.getValue 21 | // @grant GM.setValue 22 | // ==/UserScript== 23 | /* globals GM_config, jQuery, $, waitForKeyElements */ 24 | (function () { 25 | "use strict"; 26 | // The right sidebar that will contain the results div 27 | const sidebarSelector = '[data-area="sidebar"]'; 28 | // The results div 29 | const resultsDivId = "OmnisearchObsidianResults"; 30 | // The "loading"/"no results" label 31 | const loadingSpanId = "OmnisearchObsidianLoading"; 32 | // The `new GM_config()` syntax is not recognized by the TS compiler 33 | // @ts-ignore 34 | const gmc = new GM_config({ 35 | id: "ObsidianOmnisearchDdg", 36 | title: "Omnisearch in DuckDuckGo - Configuration", 37 | fields: { 38 | port: { 39 | label: "HTTP Port", 40 | type: "text", 41 | default: "51361", 42 | }, 43 | nbResults: { 44 | label: "Number of results to display", 45 | type: "int", 46 | default: 3, 47 | }, 48 | }, 49 | events: { 50 | save: () => { 51 | location.reload(); 52 | }, 53 | init: () => { }, 54 | }, 55 | }); 56 | // Promise resolves when initialization completes 57 | const onInit = (config) => new Promise((resolve) => { 58 | let isInit = () => setTimeout(() => (config.isInit ? resolve() : isInit()), 0); 59 | isInit(); 60 | }); 61 | // Obsidian logo 62 | const logo = ` 63 | 67 | 68 | `; 69 | function omnisearch() { 70 | const port = gmc.get("port"); 71 | const nbResults = gmc.get("nbResults"); 72 | // Extract the ?q= part of the URL with URLSearchParams 73 | const params = new URLSearchParams(window.location.search); 74 | const query = params.get("q"); 75 | if (!query) 76 | return; 77 | injectLoadingLabel(); 78 | GM.xmlHttpRequest({ 79 | method: "GET", 80 | url: `http://localhost:${port}/search?q=${query}`, 81 | headers: { 82 | "Content-Type": "application/json", 83 | }, 84 | onload: function (res) { 85 | const data = JSON.parse(res.response); 86 | removeLoadingLabel(data.length > 0); 87 | // Keep the x first results 88 | data.splice(nbResults); 89 | const resultsDiv = $(`#${resultsDivId}`); 90 | // Delete all existing data-omnisearch 91 | $("[data-omnisearch-result]").remove(); 92 | // Inject results 93 | for (const item of data) { 94 | const url = `obsidian://open?vault=${encodeURIComponent(item.vault)}&file=${encodeURIComponent(item.path)}`; 95 | const element = $(` 96 |
    97 | 98 | 105 | 106 |
    107 |

    108 | 111 | ${item.basename} 112 | 113 |

    114 |
    115 | 116 |
    117 |
    118 |
    119 | ${item.excerpt.replaceAll("
    ", " ")} 120 |
    121 |
    122 |
    123 |
    `); 124 | resultsDiv.append(element); 125 | } 126 | }, 127 | onerror: function (res) { 128 | console.log("Omnisearch error", res); 129 | const span = $("#" + loadingSpanId)[0]; 130 | if (span) { 131 | span.innerHTML = `Error: Obsidian is not running or the Omnisearch server is not enabled. 132 |
    Open Obsidian.`; 133 | } 134 | }, 135 | }); 136 | } 137 | function injectResultsContainer() { 138 | $(`#${resultsDivId}`).remove(); 139 | const resultsDiv = $(`
    `); 140 | $(sidebarSelector).prepend(resultsDiv); 141 | } 142 | function injectTitle() { 143 | const id = "OmnisearchObsidianConfig"; 144 | if (!$("#" + id)[0]) { 145 | const btn = $(`
    146 | ${logo} Omnisearch results 147 | 148 | (settings) 149 | 150 |
    `); 151 | $(`#${resultsDivId}`).append(btn); 152 | $(document).on("click", "#" + id, function () { 153 | gmc.open(); 154 | }); 155 | } 156 | } 157 | function injectLoadingLabel() { 158 | if (!$("#" + loadingSpanId)[0]) { 159 | const label = $(`Loading...`); 160 | $(`#${resultsDivId}`).append(label); 161 | } 162 | } 163 | function removeLoadingLabel(foundResults = true) { 164 | if (foundResults) { 165 | $("#" + loadingSpanId).remove(); 166 | } 167 | else { 168 | $("#" + loadingSpanId).text("No result found"); 169 | } 170 | } 171 | console.log("Loading Omnisearch injector"); 172 | let init = onInit(gmc); 173 | init.then(() => { 174 | injectResultsContainer(); 175 | injectTitle(); 176 | waitForKeyElements("#layout-v2", omnisearch); 177 | omnisearch(); // Make an initial call, just to avoid an improbable race condition 178 | console.log("Loaded Omnisearch injector"); 179 | }); 180 | })(); 181 | -------------------------------------------------------------------------------- /dist/obsidian-omnisearch-google.user.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // ==UserScript== 3 | // @name Obsidian Omnisearch in Google 4 | // @namespace https://github.com/scambier/userscripts 5 | // @downloadURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-google.user.js 6 | // @updateURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-google.user.js 7 | // @version 0.3.4 8 | // @description Injects Obsidian notes in Google search results 9 | // @author Simon Cambier 10 | // @match https://google.com/* 11 | // @match https://www.google.com/* 12 | // @icon https://obsidian.md/favicon.ico 13 | // @require https://code.jquery.com/jquery-3.7.1.min.js 14 | // @require https://raw.githubusercontent.com/sizzlemctwizzle/GM_config/master/gm_config.js 15 | // @require https://gist.githubusercontent.com/scambier/109932d45b7592d3decf24194008be4d/raw/9c97aa67ff9c5d56be34a55ad6c18a314e5eb548/waitForKeyElements.js 16 | // @grant GM.xmlHttpRequest 17 | // @grant GM_getValue 18 | // @grant GM_setValue 19 | // @grant GM.getValue 20 | // @grant GM.setValue 21 | // ==/UserScript== 22 | /* globals GM_config, jQuery, $, waitForKeyElements */ 23 | (function () { 24 | "use strict"; 25 | // Google's right "sidebar" that will contain the results div 26 | const sidebarSelector = "#rhs"; 27 | // The results div 28 | const resultsDivId = "OmnisearchObsidianResults"; 29 | // The "loading"/"no results" label 30 | const loadingSpanId = "OmnisearchObsidianLoading"; 31 | // The `new GM_config()` syntax is not recognized by the TS compiler 32 | // @ts-ignore 33 | const gmc = new GM_config({ 34 | id: "ObsidianOmnisearchGoogle", 35 | title: "Omnisearch in Google - Configuration", 36 | fields: { 37 | port: { 38 | label: "HTTP Port", 39 | type: "text", 40 | default: "51361", 41 | }, 42 | nbResults: { 43 | label: "Number of results to display", 44 | type: "int", 45 | default: 3, 46 | }, 47 | }, 48 | events: { 49 | save: () => { 50 | location.reload(); 51 | }, 52 | init: () => { }, 53 | }, 54 | }); 55 | // Promise resolves when initialization completes 56 | const onInit = (config) => new Promise((resolve) => { 57 | let isInit = () => setTimeout(() => (config.isInit ? resolve() : isInit()), 0); 58 | isInit(); 59 | }); 60 | // Obsidian logo 61 | const logo = ` 62 | 66 | 67 | `; 68 | function omnisearch() { 69 | const port = gmc.get("port"); 70 | const nbResults = gmc.get("nbResults"); 71 | // Extract the ?q= part of the URL with URLSearchParams 72 | const params = new URLSearchParams(window.location.search); 73 | const query = params.get("q"); 74 | if (!query) 75 | return; 76 | injectLoadingLabel(); 77 | GM.xmlHttpRequest({ 78 | method: "GET", 79 | url: `http://localhost:${port}/search?q=${query}`, 80 | headers: { 81 | "Content-Type": "application/json", 82 | }, 83 | onload: function (res) { 84 | const data = JSON.parse(res.response); 85 | removeLoadingLabel(data.length > 0); 86 | // Keep the x first results 87 | data.splice(nbResults); 88 | const resultsDiv = $(`#${resultsDivId}`); 89 | // Delete all existing data-omnisearch-result 90 | $("[data-omnisearch-result]").remove(); 91 | // Inject results 92 | for (const item of data) { 93 | const url = `obsidian://open?vault=${encodeURIComponent(item.vault)}&file=${encodeURIComponent(item.path)}`; 94 | const element = $(` 95 |
    96 |
    97 |
    98 | 128 |
    129 |
    133 | ${item.excerpt 134 | .replaceAll("
    ", " ") 135 | .replaceAll("
    ", " ")}
    136 |
    137 |
    138 |
    139 |
    140 |
    141 | `); 142 | resultsDiv.append(element); 143 | } 144 | }, 145 | onerror: function (res) { 146 | console.log("Omnisearch error", res); 147 | const span = $("#" + loadingSpanId)[0]; 148 | if (span) { 149 | span.innerHTML = `Error: Obsidian is not running or the Omnisearch server is not enabled. 150 |
    Open Obsidian.`; 151 | } 152 | }, 153 | }); 154 | } 155 | function injectTitle() { 156 | const id = "OmnisearchObsidianConfig"; 157 | if (!$("#" + id)[0]) { 158 | const btn = $(`
    159 | ${logo} Omnisearch results 160 | () 161 |
    `); 162 | $(`#${resultsDivId}`).append(btn); 163 | $(document).on("click", "#" + id, function () { 164 | gmc.open(); 165 | }); 166 | } 167 | } 168 | function injectResultsContainer() { 169 | const resultsDiv = $(`
    `); 170 | $(sidebarSelector).prepend(resultsDiv); 171 | } 172 | function injectLoadingLabel() { 173 | if (!$("#" + loadingSpanId)[0]) { 174 | const label = $(`Loading...`); 175 | $(`#${resultsDivId}`).append(label); 176 | } 177 | } 178 | function removeLoadingLabel(foundResults = true) { 179 | if (foundResults) { 180 | $("#" + loadingSpanId).remove(); 181 | } 182 | else { 183 | $("#" + loadingSpanId).text("No results found"); 184 | } 185 | } 186 | console.log("Loading Omnisearch injector"); 187 | let init = onInit(gmc); 188 | init.then(() => { 189 | // Make sure the results container is there 190 | if (!$(sidebarSelector)[0]) { 191 | $("#rcnt").append('
    '); 192 | } 193 | injectResultsContainer(); 194 | injectTitle(); 195 | omnisearch(); // Make an initial call, just to avoid an improbable race condition 196 | console.log("Loaded Omnisearch injector"); 197 | // Keep the results on top 198 | waitForKeyElements(sidebarSelector, () => { 199 | $(resultsDivId).prependTo(sidebarSelector); 200 | }); 201 | }); 202 | })(); 203 | -------------------------------------------------------------------------------- /dist/obsidian-omnisearch-kagi.user.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // ==UserScript== 3 | // @name Obsidian Omnisearch in Kagi 4 | // @namespace https://github.com/scambier/userscripts 5 | // @downloadURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-kagi.user.js 6 | // @updateURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-kagi.user.js 7 | // @version 0.3.2 8 | // @description Injects Obsidian notes in Kagi search results 9 | // @author Simon Cambier 10 | // @match https://kagi.com/* 11 | // @match https://www.kagi.com/* 12 | // @icon https://obsidian.md/favicon.ico 13 | // @require https://code.jquery.com/jquery-3.7.1.min.js 14 | // @require https://gist.githubusercontent.com/scambier/109932d45b7592d3decf24194008be4d/raw/9c97aa67ff9c5d56be34a55ad6c18a314e5eb548/waitForKeyElements.js 15 | // @require https://raw.githubusercontent.com/sizzlemctwizzle/GM_config/master/gm_config.js 16 | // @grant GM.xmlHttpRequest 17 | // @grant GM_getValue 18 | // @grant GM_setValue 19 | // @grant GM.getValue 20 | // @grant GM.setValue 21 | // ==/UserScript== 22 | /* globals GM_config, jQuery, $, waitForKeyElements */ 23 | (function () { 24 | "use strict"; 25 | // The right sidebar that will contain the results div 26 | const sidebarSelector = ".right-content-box"; 27 | // The results div 28 | const resultsDivId = "OmnisearchObsidianResults"; 29 | // The "loading"/"no results" label 30 | const loadingSpanId = "OmnisearchObsidianLoading"; 31 | // The `new GM_config()` syntax is not recognized by the TS compiler 32 | // @ts-ignore 33 | const gmc = new GM_config({ 34 | id: "ObsidianOmnisearchKagi", 35 | title: "Omnisearch in Kagi - Configuration", 36 | fields: { 37 | port: { 38 | label: "HTTP Port", 39 | type: "text", 40 | default: "51361", 41 | }, 42 | nbResults: { 43 | label: "Number of results to display", 44 | type: "int", 45 | default: 3, 46 | }, 47 | }, 48 | events: { 49 | save: () => { 50 | location.reload(); 51 | }, 52 | init: () => { }, 53 | }, 54 | }); 55 | // Promise resolves when initialization completes 56 | const onInit = (config) => new Promise((resolve) => { 57 | let isInit = () => setTimeout(() => (config.isInit ? resolve() : isInit()), 0); 58 | isInit(); 59 | }); 60 | // Obsidian logo 61 | const logo = ` 62 | 66 | 67 | `; 68 | function omnisearch() { 69 | const port = gmc.get("port"); 70 | const nbResults = gmc.get("nbResults"); 71 | // Extract the ?q= part of the URL with URLSearchParams 72 | const params = new URLSearchParams(window.location.search); 73 | const query = params.get("q"); 74 | if (!query) 75 | return; 76 | injectLoadingLabel(); 77 | GM.xmlHttpRequest({ 78 | method: "GET", 79 | url: `http://localhost:${port}/search?q=${query}`, 80 | headers: { 81 | "Content-Type": "application/json", 82 | }, 83 | onload: function (res) { 84 | const data = JSON.parse(res.response); 85 | removeLoadingLabel(data.length > 0); 86 | // Keep the x first results 87 | data.splice(nbResults); 88 | const resultsDiv = $(`#${resultsDivId}`); 89 | // Delete all existing data-omnisearch 90 | $("[data-omnisearch-result]").remove(); 91 | // Inject results 92 | for (const item of data) { 93 | const url = `obsidian://open?vault=${encodeURIComponent(item.vault)}&file=${encodeURIComponent(item.path)}`; 94 | const element = $(` 95 |
    96 |
    97 |

    98 | 101 | ${item.basename} 102 | 103 |

    104 |
    105 | 106 | 113 |
    114 |
    115 |
    116 | ${item.excerpt.replaceAll("
    ", " ")} 117 |
    118 |
    119 |
    120 |
    `); 121 | resultsDiv.append(element); 122 | } 123 | }, 124 | onerror: function (res) { 125 | console.log("Omnisearch error", res); 126 | const span = $("#" + loadingSpanId)[0]; 127 | if (span) { 128 | span.innerHTML = `Error: Obsidian is not running or the Omnisearch server is not enabled. 129 |
    Open Obsidian.`; 130 | } 131 | }, 132 | }); 133 | } 134 | function injectResultsContainer() { 135 | $(`#${resultsDivId}`).remove(); 136 | const resultsDiv = $(`
    `); 137 | $(sidebarSelector).prepend(resultsDiv); 138 | } 139 | function injectTitle() { 140 | const id = "OmnisearchObsidianConfig"; 141 | if (!$("#" + id)[0]) { 142 | const btn = $(`
    143 | ${logo} Omnisearch results 144 | 145 | (settings) 146 | 147 |
    `); 148 | $(`#${resultsDivId}`).append(btn); 149 | $(document).on("click", "#" + id, function () { 150 | gmc.open(); 151 | }); 152 | } 153 | } 154 | function injectLoadingLabel() { 155 | if (!$("#" + loadingSpanId)[0]) { 156 | const label = $(`Loading...`); 157 | $(`#${resultsDivId}`).append(label); 158 | } 159 | } 160 | function removeLoadingLabel(foundResults = true) { 161 | if (foundResults) { 162 | $("#" + loadingSpanId).remove(); 163 | } 164 | else { 165 | $("#" + loadingSpanId).text("No result found"); 166 | } 167 | } 168 | console.log("Loading Omnisearch injector"); 169 | let init = onInit(gmc); 170 | init.then(() => { 171 | injectResultsContainer(); 172 | injectTitle(); 173 | waitForKeyElements("#layout-v2", omnisearch); 174 | omnisearch(); // Make an initial call, just to avoid an improbable race condition 175 | console.log("Loaded Omnisearch injector"); 176 | }); 177 | })(); 178 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "userscripts", 3 | "version": "1.0.0", 4 | "description": "A collection of userscripts", 5 | "main": "''", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "tsc" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/scambier/userscripts.git" 13 | }, 14 | "author": "Simon Cambier", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/scambier/userscripts/issues" 18 | }, 19 | "homepage": "https://github.com/scambier/userscripts#readme", 20 | "devDependencies": { 21 | "@types/jquery": "^3.5.14", 22 | "@typescript-eslint/eslint-plugin": "^3.10.1", 23 | "@typescript-eslint/parser": "^3.10.1", 24 | "eslint": "^7.32.0", 25 | "typescript": "^3.9.10" 26 | }, 27 | "dependencies": { 28 | "@types/tampermonkey": "^4.20.3" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.1' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | dependencies: 8 | '@types/tampermonkey': 9 | specifier: ^4.20.3 10 | version: 4.20.3 11 | 12 | devDependencies: 13 | '@types/jquery': 14 | specifier: ^3.5.14 15 | version: 3.5.14 16 | '@typescript-eslint/eslint-plugin': 17 | specifier: ^3.10.1 18 | version: 3.10.1(@typescript-eslint/parser@3.10.1)(eslint@7.32.0)(typescript@3.9.10) 19 | '@typescript-eslint/parser': 20 | specifier: ^3.10.1 21 | version: 3.10.1(eslint@7.32.0)(typescript@3.9.10) 22 | eslint: 23 | specifier: ^7.32.0 24 | version: 7.32.0 25 | typescript: 26 | specifier: ^3.9.10 27 | version: 3.9.10 28 | 29 | packages: 30 | 31 | /@babel/code-frame@7.12.11: 32 | resolution: {integrity: sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==} 33 | dependencies: 34 | '@babel/highlight': 7.18.6 35 | dev: true 36 | 37 | /@babel/helper-validator-identifier@7.19.1: 38 | resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} 39 | engines: {node: '>=6.9.0'} 40 | dev: true 41 | 42 | /@babel/highlight@7.18.6: 43 | resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} 44 | engines: {node: '>=6.9.0'} 45 | dependencies: 46 | '@babel/helper-validator-identifier': 7.19.1 47 | chalk: 2.4.2 48 | js-tokens: 4.0.0 49 | dev: true 50 | 51 | /@eslint/eslintrc@0.4.3: 52 | resolution: {integrity: sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==} 53 | engines: {node: ^10.12.0 || >=12.0.0} 54 | dependencies: 55 | ajv: 6.12.6 56 | debug: 4.3.4 57 | espree: 7.3.1 58 | globals: 13.17.0 59 | ignore: 4.0.6 60 | import-fresh: 3.3.0 61 | js-yaml: 3.14.1 62 | minimatch: 3.1.2 63 | strip-json-comments: 3.1.1 64 | transitivePeerDependencies: 65 | - supports-color 66 | dev: true 67 | 68 | /@humanwhocodes/config-array@0.5.0: 69 | resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==} 70 | engines: {node: '>=10.10.0'} 71 | dependencies: 72 | '@humanwhocodes/object-schema': 1.2.1 73 | debug: 4.3.4 74 | minimatch: 3.1.2 75 | transitivePeerDependencies: 76 | - supports-color 77 | dev: true 78 | 79 | /@humanwhocodes/object-schema@1.2.1: 80 | resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} 81 | dev: true 82 | 83 | /@types/eslint-visitor-keys@1.0.0: 84 | resolution: {integrity: sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==} 85 | dev: true 86 | 87 | /@types/jquery@3.5.14: 88 | resolution: {integrity: sha512-X1gtMRMbziVQkErhTQmSe2jFwwENA/Zr+PprCkF63vFq+Yt5PZ4AlKqgmeNlwgn7dhsXEK888eIW2520EpC+xg==} 89 | dependencies: 90 | '@types/sizzle': 2.3.3 91 | dev: true 92 | 93 | /@types/json-schema@7.0.11: 94 | resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} 95 | dev: true 96 | 97 | /@types/sizzle@2.3.3: 98 | resolution: {integrity: sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==} 99 | dev: true 100 | 101 | /@types/tampermonkey@4.20.3: 102 | resolution: {integrity: sha512-/n4ECoOyBmYxlOxkohAYQApOwlMEw5IVF4GJUcgB0ISiuasDWoguxbYLOzCO8hPyHuSzsa41wYcmhK2qiqtFVg==} 103 | dev: false 104 | 105 | /@typescript-eslint/eslint-plugin@3.10.1(@typescript-eslint/parser@3.10.1)(eslint@7.32.0)(typescript@3.9.10): 106 | resolution: {integrity: sha512-PQg0emRtzZFWq6PxBcdxRH3QIQiyFO3WCVpRL3fgj5oQS3CDs3AeAKfv4DxNhzn8ITdNJGJ4D3Qw8eAJf3lXeQ==} 107 | engines: {node: ^10.12.0 || >=12.0.0} 108 | peerDependencies: 109 | '@typescript-eslint/parser': ^3.0.0 110 | eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 111 | typescript: '*' 112 | peerDependenciesMeta: 113 | typescript: 114 | optional: true 115 | dependencies: 116 | '@typescript-eslint/experimental-utils': 3.10.1(eslint@7.32.0)(typescript@3.9.10) 117 | '@typescript-eslint/parser': 3.10.1(eslint@7.32.0)(typescript@3.9.10) 118 | debug: 4.3.4 119 | eslint: 7.32.0 120 | functional-red-black-tree: 1.0.1 121 | regexpp: 3.2.0 122 | semver: 7.3.8 123 | tsutils: 3.21.0(typescript@3.9.10) 124 | typescript: 3.9.10 125 | transitivePeerDependencies: 126 | - supports-color 127 | dev: true 128 | 129 | /@typescript-eslint/experimental-utils@3.10.1(eslint@7.32.0)(typescript@3.9.10): 130 | resolution: {integrity: sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==} 131 | engines: {node: ^10.12.0 || >=12.0.0} 132 | peerDependencies: 133 | eslint: '*' 134 | dependencies: 135 | '@types/json-schema': 7.0.11 136 | '@typescript-eslint/types': 3.10.1 137 | '@typescript-eslint/typescript-estree': 3.10.1(typescript@3.9.10) 138 | eslint: 7.32.0 139 | eslint-scope: 5.1.1 140 | eslint-utils: 2.1.0 141 | transitivePeerDependencies: 142 | - supports-color 143 | - typescript 144 | dev: true 145 | 146 | /@typescript-eslint/parser@3.10.1(eslint@7.32.0)(typescript@3.9.10): 147 | resolution: {integrity: sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw==} 148 | engines: {node: ^10.12.0 || >=12.0.0} 149 | peerDependencies: 150 | eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 151 | typescript: '*' 152 | peerDependenciesMeta: 153 | typescript: 154 | optional: true 155 | dependencies: 156 | '@types/eslint-visitor-keys': 1.0.0 157 | '@typescript-eslint/experimental-utils': 3.10.1(eslint@7.32.0)(typescript@3.9.10) 158 | '@typescript-eslint/types': 3.10.1 159 | '@typescript-eslint/typescript-estree': 3.10.1(typescript@3.9.10) 160 | eslint: 7.32.0 161 | eslint-visitor-keys: 1.3.0 162 | typescript: 3.9.10 163 | transitivePeerDependencies: 164 | - supports-color 165 | dev: true 166 | 167 | /@typescript-eslint/types@3.10.1: 168 | resolution: {integrity: sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==} 169 | engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} 170 | dev: true 171 | 172 | /@typescript-eslint/typescript-estree@3.10.1(typescript@3.9.10): 173 | resolution: {integrity: sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==} 174 | engines: {node: ^10.12.0 || >=12.0.0} 175 | peerDependencies: 176 | typescript: '*' 177 | peerDependenciesMeta: 178 | typescript: 179 | optional: true 180 | dependencies: 181 | '@typescript-eslint/types': 3.10.1 182 | '@typescript-eslint/visitor-keys': 3.10.1 183 | debug: 4.3.4 184 | glob: 7.2.3 185 | is-glob: 4.0.3 186 | lodash: 4.17.21 187 | semver: 7.3.8 188 | tsutils: 3.21.0(typescript@3.9.10) 189 | typescript: 3.9.10 190 | transitivePeerDependencies: 191 | - supports-color 192 | dev: true 193 | 194 | /@typescript-eslint/visitor-keys@3.10.1: 195 | resolution: {integrity: sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==} 196 | engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} 197 | dependencies: 198 | eslint-visitor-keys: 1.3.0 199 | dev: true 200 | 201 | /acorn-jsx@5.3.2(acorn@7.4.1): 202 | resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 203 | peerDependencies: 204 | acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 205 | dependencies: 206 | acorn: 7.4.1 207 | dev: true 208 | 209 | /acorn@7.4.1: 210 | resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} 211 | engines: {node: '>=0.4.0'} 212 | hasBin: true 213 | dev: true 214 | 215 | /ajv@6.12.6: 216 | resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 217 | dependencies: 218 | fast-deep-equal: 3.1.3 219 | fast-json-stable-stringify: 2.1.0 220 | json-schema-traverse: 0.4.1 221 | uri-js: 4.4.1 222 | dev: true 223 | 224 | /ajv@8.11.0: 225 | resolution: {integrity: sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==} 226 | dependencies: 227 | fast-deep-equal: 3.1.3 228 | json-schema-traverse: 1.0.0 229 | require-from-string: 2.0.2 230 | uri-js: 4.4.1 231 | dev: true 232 | 233 | /ansi-colors@4.1.3: 234 | resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} 235 | engines: {node: '>=6'} 236 | dev: true 237 | 238 | /ansi-regex@5.0.1: 239 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 240 | engines: {node: '>=8'} 241 | dev: true 242 | 243 | /ansi-styles@3.2.1: 244 | resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} 245 | engines: {node: '>=4'} 246 | dependencies: 247 | color-convert: 1.9.3 248 | dev: true 249 | 250 | /ansi-styles@4.3.0: 251 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 252 | engines: {node: '>=8'} 253 | dependencies: 254 | color-convert: 2.0.1 255 | dev: true 256 | 257 | /argparse@1.0.10: 258 | resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} 259 | dependencies: 260 | sprintf-js: 1.0.3 261 | dev: true 262 | 263 | /astral-regex@2.0.0: 264 | resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} 265 | engines: {node: '>=8'} 266 | dev: true 267 | 268 | /balanced-match@1.0.2: 269 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 270 | dev: true 271 | 272 | /brace-expansion@1.1.11: 273 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 274 | dependencies: 275 | balanced-match: 1.0.2 276 | concat-map: 0.0.1 277 | dev: true 278 | 279 | /callsites@3.1.0: 280 | resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 281 | engines: {node: '>=6'} 282 | dev: true 283 | 284 | /chalk@2.4.2: 285 | resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} 286 | engines: {node: '>=4'} 287 | dependencies: 288 | ansi-styles: 3.2.1 289 | escape-string-regexp: 1.0.5 290 | supports-color: 5.5.0 291 | dev: true 292 | 293 | /chalk@4.1.2: 294 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 295 | engines: {node: '>=10'} 296 | dependencies: 297 | ansi-styles: 4.3.0 298 | supports-color: 7.2.0 299 | dev: true 300 | 301 | /color-convert@1.9.3: 302 | resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} 303 | dependencies: 304 | color-name: 1.1.3 305 | dev: true 306 | 307 | /color-convert@2.0.1: 308 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 309 | engines: {node: '>=7.0.0'} 310 | dependencies: 311 | color-name: 1.1.4 312 | dev: true 313 | 314 | /color-name@1.1.3: 315 | resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} 316 | dev: true 317 | 318 | /color-name@1.1.4: 319 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 320 | dev: true 321 | 322 | /concat-map@0.0.1: 323 | resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} 324 | dev: true 325 | 326 | /cross-spawn@7.0.3: 327 | resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 328 | engines: {node: '>= 8'} 329 | dependencies: 330 | path-key: 3.1.1 331 | shebang-command: 2.0.0 332 | which: 2.0.2 333 | dev: true 334 | 335 | /debug@4.3.4: 336 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 337 | engines: {node: '>=6.0'} 338 | peerDependencies: 339 | supports-color: '*' 340 | peerDependenciesMeta: 341 | supports-color: 342 | optional: true 343 | dependencies: 344 | ms: 2.1.2 345 | dev: true 346 | 347 | /deep-is@0.1.4: 348 | resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 349 | dev: true 350 | 351 | /doctrine@3.0.0: 352 | resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} 353 | engines: {node: '>=6.0.0'} 354 | dependencies: 355 | esutils: 2.0.3 356 | dev: true 357 | 358 | /emoji-regex@8.0.0: 359 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 360 | dev: true 361 | 362 | /enquirer@2.3.6: 363 | resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} 364 | engines: {node: '>=8.6'} 365 | dependencies: 366 | ansi-colors: 4.1.3 367 | dev: true 368 | 369 | /escape-string-regexp@1.0.5: 370 | resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} 371 | engines: {node: '>=0.8.0'} 372 | dev: true 373 | 374 | /escape-string-regexp@4.0.0: 375 | resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 376 | engines: {node: '>=10'} 377 | dev: true 378 | 379 | /eslint-scope@5.1.1: 380 | resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} 381 | engines: {node: '>=8.0.0'} 382 | dependencies: 383 | esrecurse: 4.3.0 384 | estraverse: 4.3.0 385 | dev: true 386 | 387 | /eslint-utils@2.1.0: 388 | resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} 389 | engines: {node: '>=6'} 390 | dependencies: 391 | eslint-visitor-keys: 1.3.0 392 | dev: true 393 | 394 | /eslint-visitor-keys@1.3.0: 395 | resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} 396 | engines: {node: '>=4'} 397 | dev: true 398 | 399 | /eslint-visitor-keys@2.1.0: 400 | resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} 401 | engines: {node: '>=10'} 402 | dev: true 403 | 404 | /eslint@7.32.0: 405 | resolution: {integrity: sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==} 406 | engines: {node: ^10.12.0 || >=12.0.0} 407 | hasBin: true 408 | dependencies: 409 | '@babel/code-frame': 7.12.11 410 | '@eslint/eslintrc': 0.4.3 411 | '@humanwhocodes/config-array': 0.5.0 412 | ajv: 6.12.6 413 | chalk: 4.1.2 414 | cross-spawn: 7.0.3 415 | debug: 4.3.4 416 | doctrine: 3.0.0 417 | enquirer: 2.3.6 418 | escape-string-regexp: 4.0.0 419 | eslint-scope: 5.1.1 420 | eslint-utils: 2.1.0 421 | eslint-visitor-keys: 2.1.0 422 | espree: 7.3.1 423 | esquery: 1.4.0 424 | esutils: 2.0.3 425 | fast-deep-equal: 3.1.3 426 | file-entry-cache: 6.0.1 427 | functional-red-black-tree: 1.0.1 428 | glob-parent: 5.1.2 429 | globals: 13.17.0 430 | ignore: 4.0.6 431 | import-fresh: 3.3.0 432 | imurmurhash: 0.1.4 433 | is-glob: 4.0.3 434 | js-yaml: 3.14.1 435 | json-stable-stringify-without-jsonify: 1.0.1 436 | levn: 0.4.1 437 | lodash.merge: 4.6.2 438 | minimatch: 3.1.2 439 | natural-compare: 1.4.0 440 | optionator: 0.9.1 441 | progress: 2.0.3 442 | regexpp: 3.2.0 443 | semver: 7.3.8 444 | strip-ansi: 6.0.1 445 | strip-json-comments: 3.1.1 446 | table: 6.8.0 447 | text-table: 0.2.0 448 | v8-compile-cache: 2.3.0 449 | transitivePeerDependencies: 450 | - supports-color 451 | dev: true 452 | 453 | /espree@7.3.1: 454 | resolution: {integrity: sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==} 455 | engines: {node: ^10.12.0 || >=12.0.0} 456 | dependencies: 457 | acorn: 7.4.1 458 | acorn-jsx: 5.3.2(acorn@7.4.1) 459 | eslint-visitor-keys: 1.3.0 460 | dev: true 461 | 462 | /esprima@4.0.1: 463 | resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} 464 | engines: {node: '>=4'} 465 | hasBin: true 466 | dev: true 467 | 468 | /esquery@1.4.0: 469 | resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} 470 | engines: {node: '>=0.10'} 471 | dependencies: 472 | estraverse: 5.3.0 473 | dev: true 474 | 475 | /esrecurse@4.3.0: 476 | resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} 477 | engines: {node: '>=4.0'} 478 | dependencies: 479 | estraverse: 5.3.0 480 | dev: true 481 | 482 | /estraverse@4.3.0: 483 | resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} 484 | engines: {node: '>=4.0'} 485 | dev: true 486 | 487 | /estraverse@5.3.0: 488 | resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 489 | engines: {node: '>=4.0'} 490 | dev: true 491 | 492 | /esutils@2.0.3: 493 | resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 494 | engines: {node: '>=0.10.0'} 495 | dev: true 496 | 497 | /fast-deep-equal@3.1.3: 498 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 499 | dev: true 500 | 501 | /fast-json-stable-stringify@2.1.0: 502 | resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 503 | dev: true 504 | 505 | /fast-levenshtein@2.0.6: 506 | resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 507 | dev: true 508 | 509 | /file-entry-cache@6.0.1: 510 | resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} 511 | engines: {node: ^10.12.0 || >=12.0.0} 512 | dependencies: 513 | flat-cache: 3.0.4 514 | dev: true 515 | 516 | /flat-cache@3.0.4: 517 | resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} 518 | engines: {node: ^10.12.0 || >=12.0.0} 519 | dependencies: 520 | flatted: 3.2.7 521 | rimraf: 3.0.2 522 | dev: true 523 | 524 | /flatted@3.2.7: 525 | resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} 526 | dev: true 527 | 528 | /fs.realpath@1.0.0: 529 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 530 | dev: true 531 | 532 | /functional-red-black-tree@1.0.1: 533 | resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} 534 | dev: true 535 | 536 | /glob-parent@5.1.2: 537 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 538 | engines: {node: '>= 6'} 539 | dependencies: 540 | is-glob: 4.0.3 541 | dev: true 542 | 543 | /glob@7.2.3: 544 | resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 545 | dependencies: 546 | fs.realpath: 1.0.0 547 | inflight: 1.0.6 548 | inherits: 2.0.4 549 | minimatch: 3.1.2 550 | once: 1.4.0 551 | path-is-absolute: 1.0.1 552 | dev: true 553 | 554 | /globals@13.17.0: 555 | resolution: {integrity: sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==} 556 | engines: {node: '>=8'} 557 | dependencies: 558 | type-fest: 0.20.2 559 | dev: true 560 | 561 | /has-flag@3.0.0: 562 | resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} 563 | engines: {node: '>=4'} 564 | dev: true 565 | 566 | /has-flag@4.0.0: 567 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 568 | engines: {node: '>=8'} 569 | dev: true 570 | 571 | /ignore@4.0.6: 572 | resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} 573 | engines: {node: '>= 4'} 574 | dev: true 575 | 576 | /import-fresh@3.3.0: 577 | resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} 578 | engines: {node: '>=6'} 579 | dependencies: 580 | parent-module: 1.0.1 581 | resolve-from: 4.0.0 582 | dev: true 583 | 584 | /imurmurhash@0.1.4: 585 | resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 586 | engines: {node: '>=0.8.19'} 587 | dev: true 588 | 589 | /inflight@1.0.6: 590 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 591 | dependencies: 592 | once: 1.4.0 593 | wrappy: 1.0.2 594 | dev: true 595 | 596 | /inherits@2.0.4: 597 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 598 | dev: true 599 | 600 | /is-extglob@2.1.1: 601 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 602 | engines: {node: '>=0.10.0'} 603 | dev: true 604 | 605 | /is-fullwidth-code-point@3.0.0: 606 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 607 | engines: {node: '>=8'} 608 | dev: true 609 | 610 | /is-glob@4.0.3: 611 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 612 | engines: {node: '>=0.10.0'} 613 | dependencies: 614 | is-extglob: 2.1.1 615 | dev: true 616 | 617 | /isexe@2.0.0: 618 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 619 | dev: true 620 | 621 | /js-tokens@4.0.0: 622 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 623 | dev: true 624 | 625 | /js-yaml@3.14.1: 626 | resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} 627 | hasBin: true 628 | dependencies: 629 | argparse: 1.0.10 630 | esprima: 4.0.1 631 | dev: true 632 | 633 | /json-schema-traverse@0.4.1: 634 | resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 635 | dev: true 636 | 637 | /json-schema-traverse@1.0.0: 638 | resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} 639 | dev: true 640 | 641 | /json-stable-stringify-without-jsonify@1.0.1: 642 | resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} 643 | dev: true 644 | 645 | /levn@0.4.1: 646 | resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 647 | engines: {node: '>= 0.8.0'} 648 | dependencies: 649 | prelude-ls: 1.2.1 650 | type-check: 0.4.0 651 | dev: true 652 | 653 | /lodash.merge@4.6.2: 654 | resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 655 | dev: true 656 | 657 | /lodash.truncate@4.4.2: 658 | resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} 659 | dev: true 660 | 661 | /lodash@4.17.21: 662 | resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 663 | dev: true 664 | 665 | /lru-cache@6.0.0: 666 | resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 667 | engines: {node: '>=10'} 668 | dependencies: 669 | yallist: 4.0.0 670 | dev: true 671 | 672 | /minimatch@3.1.2: 673 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 674 | dependencies: 675 | brace-expansion: 1.1.11 676 | dev: true 677 | 678 | /ms@2.1.2: 679 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 680 | dev: true 681 | 682 | /natural-compare@1.4.0: 683 | resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 684 | dev: true 685 | 686 | /once@1.4.0: 687 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 688 | dependencies: 689 | wrappy: 1.0.2 690 | dev: true 691 | 692 | /optionator@0.9.1: 693 | resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} 694 | engines: {node: '>= 0.8.0'} 695 | dependencies: 696 | deep-is: 0.1.4 697 | fast-levenshtein: 2.0.6 698 | levn: 0.4.1 699 | prelude-ls: 1.2.1 700 | type-check: 0.4.0 701 | word-wrap: 1.2.3 702 | dev: true 703 | 704 | /parent-module@1.0.1: 705 | resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 706 | engines: {node: '>=6'} 707 | dependencies: 708 | callsites: 3.1.0 709 | dev: true 710 | 711 | /path-is-absolute@1.0.1: 712 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 713 | engines: {node: '>=0.10.0'} 714 | dev: true 715 | 716 | /path-key@3.1.1: 717 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 718 | engines: {node: '>=8'} 719 | dev: true 720 | 721 | /prelude-ls@1.2.1: 722 | resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 723 | engines: {node: '>= 0.8.0'} 724 | dev: true 725 | 726 | /progress@2.0.3: 727 | resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} 728 | engines: {node: '>=0.4.0'} 729 | dev: true 730 | 731 | /punycode@2.1.1: 732 | resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} 733 | engines: {node: '>=6'} 734 | dev: true 735 | 736 | /regexpp@3.2.0: 737 | resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} 738 | engines: {node: '>=8'} 739 | dev: true 740 | 741 | /require-from-string@2.0.2: 742 | resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} 743 | engines: {node: '>=0.10.0'} 744 | dev: true 745 | 746 | /resolve-from@4.0.0: 747 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 748 | engines: {node: '>=4'} 749 | dev: true 750 | 751 | /rimraf@3.0.2: 752 | resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} 753 | hasBin: true 754 | dependencies: 755 | glob: 7.2.3 756 | dev: true 757 | 758 | /semver@7.3.8: 759 | resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} 760 | engines: {node: '>=10'} 761 | hasBin: true 762 | dependencies: 763 | lru-cache: 6.0.0 764 | dev: true 765 | 766 | /shebang-command@2.0.0: 767 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 768 | engines: {node: '>=8'} 769 | dependencies: 770 | shebang-regex: 3.0.0 771 | dev: true 772 | 773 | /shebang-regex@3.0.0: 774 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 775 | engines: {node: '>=8'} 776 | dev: true 777 | 778 | /slice-ansi@4.0.0: 779 | resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} 780 | engines: {node: '>=10'} 781 | dependencies: 782 | ansi-styles: 4.3.0 783 | astral-regex: 2.0.0 784 | is-fullwidth-code-point: 3.0.0 785 | dev: true 786 | 787 | /sprintf-js@1.0.3: 788 | resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} 789 | dev: true 790 | 791 | /string-width@4.2.3: 792 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 793 | engines: {node: '>=8'} 794 | dependencies: 795 | emoji-regex: 8.0.0 796 | is-fullwidth-code-point: 3.0.0 797 | strip-ansi: 6.0.1 798 | dev: true 799 | 800 | /strip-ansi@6.0.1: 801 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 802 | engines: {node: '>=8'} 803 | dependencies: 804 | ansi-regex: 5.0.1 805 | dev: true 806 | 807 | /strip-json-comments@3.1.1: 808 | resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 809 | engines: {node: '>=8'} 810 | dev: true 811 | 812 | /supports-color@5.5.0: 813 | resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} 814 | engines: {node: '>=4'} 815 | dependencies: 816 | has-flag: 3.0.0 817 | dev: true 818 | 819 | /supports-color@7.2.0: 820 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 821 | engines: {node: '>=8'} 822 | dependencies: 823 | has-flag: 4.0.0 824 | dev: true 825 | 826 | /table@6.8.0: 827 | resolution: {integrity: sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==} 828 | engines: {node: '>=10.0.0'} 829 | dependencies: 830 | ajv: 8.11.0 831 | lodash.truncate: 4.4.2 832 | slice-ansi: 4.0.0 833 | string-width: 4.2.3 834 | strip-ansi: 6.0.1 835 | dev: true 836 | 837 | /text-table@0.2.0: 838 | resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} 839 | dev: true 840 | 841 | /tslib@1.14.1: 842 | resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} 843 | dev: true 844 | 845 | /tsutils@3.21.0(typescript@3.9.10): 846 | resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} 847 | engines: {node: '>= 6'} 848 | peerDependencies: 849 | typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' 850 | dependencies: 851 | tslib: 1.14.1 852 | typescript: 3.9.10 853 | dev: true 854 | 855 | /type-check@0.4.0: 856 | resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 857 | engines: {node: '>= 0.8.0'} 858 | dependencies: 859 | prelude-ls: 1.2.1 860 | dev: true 861 | 862 | /type-fest@0.20.2: 863 | resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} 864 | engines: {node: '>=10'} 865 | dev: true 866 | 867 | /typescript@3.9.10: 868 | resolution: {integrity: sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==} 869 | engines: {node: '>=4.2.0'} 870 | hasBin: true 871 | dev: true 872 | 873 | /uri-js@4.4.1: 874 | resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 875 | dependencies: 876 | punycode: 2.1.1 877 | dev: true 878 | 879 | /v8-compile-cache@2.3.0: 880 | resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} 881 | dev: true 882 | 883 | /which@2.0.2: 884 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 885 | engines: {node: '>= 8'} 886 | hasBin: true 887 | dependencies: 888 | isexe: 2.0.0 889 | dev: true 890 | 891 | /word-wrap@1.2.3: 892 | resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} 893 | engines: {node: '>=0.10.0'} 894 | dev: true 895 | 896 | /wrappy@1.0.2: 897 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 898 | dev: true 899 | 900 | /yallist@4.0.0: 901 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 902 | dev: true 903 | -------------------------------------------------------------------------------- /src/better-pcgamer.user.ts: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Better PCGamer 3 | // @namespace https://github.com/scambier/userscripts 4 | // @version 0.1 5 | // @license ISC 6 | // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js 7 | // @require https://openuserjs.org/src/libs/sizzle/GM_config.min.js 8 | // @description Enhance the reading experience of PCGamer articles 9 | // @author Simon Cambier 10 | // @match https://www.pcgamer.com/* 11 | // @grant GM_getValue 12 | // @grant GM_setValue 13 | // ==/UserScript== 14 | 15 | (function () { 16 | GM_config.init({ 17 | id: "PCG_Config", 18 | title: "Better PCGamer Config", 19 | fields: { 20 | disableRecommendedVideos: { 21 | label: 'Disable "recommended videos"', 22 | type: "checkbox", 23 | default: true, 24 | }, 25 | disableDisqusComments: { 26 | label: "Disable Disqus comments", 27 | type: "checkbox", 28 | default: true, 29 | }, 30 | disableBell: { 31 | label: 'Disable the "You\'ve blocked notifications" bell', 32 | type: "checkbox", 33 | default: true, 34 | }, 35 | disableSidebar: { 36 | label: "Disable sidebar, and widen the article width if possible", 37 | type: "checkbox", 38 | default: true, 39 | }, 40 | }, 41 | css: ``, 42 | events: { 43 | save: () => { 44 | location.reload(); 45 | }, 46 | }, 47 | }); 48 | 49 | $(document).on("click", "#PCGConfigBtn", function () { 50 | GM_config.open(); 51 | }); 52 | 53 | function main() { 54 | // Add config button 55 | if (!$("#PCGConfigBtn")[0]) { 56 | const btn = document.createElement("a"); 57 | btn.setAttribute( 58 | "style", 59 | ` 60 | position: fixed; 61 | bottom: 5px; 62 | left: 5px; 63 | cursor: pointer 64 | ` 65 | ); 66 | btn.id = "PCGConfigBtn"; 67 | btn.appendChild(document.createTextNode("⚙️")); 68 | document.querySelector("body")!.appendChild(btn); 69 | } 70 | 71 | // Recommended videos 72 | if (GM_config.get("disableRecommendedVideos")) { 73 | $('div:contains("RECOMMENDED VIDEOS FOR YOU...")') 74 | .closest(".future__jwplayer--carousel") 75 | .remove(); 76 | // $('.leaderboardAdPresent').remove() 77 | } 78 | 79 | // Comments 80 | if (GM_config.get("disableDisqusComments")) { 81 | $(".jump-to-comments").remove(); 82 | $("#article-comments").remove(); 83 | $("ul.socialite-widget-ul li.comment").remove(); 84 | } 85 | 86 | // "Disabled notification" bell 87 | if (GM_config.get("disableBell")) { 88 | $("#onesignal-bell-container").remove(); 89 | } 90 | 91 | // Ads 92 | if (GM_config.get("disableSidebar")) { 93 | $("#sidebar").remove(); 94 | // $('.slot-single-height-0-500').remove() 95 | $("#content-after-image").attr("style", "width: 100%; max-width: 100%;"); 96 | $(".news-article").attr("style", "width: 100%; max-width: 100%;"); 97 | } 98 | } 99 | 100 | const observer = new MutationObserver((mutations) => { 101 | main(); 102 | }); 103 | observer.observe(document.documentElement, { 104 | attributes: false, 105 | characterData: false, 106 | childList: true, 107 | subtree: true, 108 | attributeOldValue: false, 109 | characterDataOldValue: false, 110 | }); 111 | 112 | main(); 113 | })(); 114 | -------------------------------------------------------------------------------- /src/download-twitter-videos.user.ts: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Dowload Twitter Videos 3 | // @namespace https://github.com/scambier/userscripts 4 | // @author Simon Cambier 5 | // @version 0.5.7 6 | // @description Adds a download button to quickly fetch gifs and videos embedded in tweets 7 | // @license ISC 8 | // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js 9 | // @include https://twitter.com/* 10 | // @include https://tweetdeck.twitter.com/* 11 | // @include http://twittervideodownloader.com/?url=* 12 | // @include https://www.savetweetvid.com/?url=* 13 | // @include https://twdown.net/?url=* 14 | // @include https://twittervideodownloader.online/?url=* 15 | // @grant none 16 | // @run-at document-end 17 | // ==/UserScript== 18 | 19 | // console = unsafeWindow.console; 20 | 21 | (() => { 22 | 'use strict' 23 | 24 | function main() { 25 | 26 | } 27 | 28 | // interface IMediaBlock { 29 | // videoContainer: Element, 30 | // src?: string 31 | // } 32 | 33 | // const externalDomains = [ 34 | // 'http://savetweetvid.com/?url=', 35 | // 'https://twdown.net/?url=', 36 | // 'http://twittervideodownloader.com/?url=', 37 | // 'https://twittervideodownloader.online/?url=' 38 | // // 'https://twdownload.com/?url=', 39 | // // 'https://twdownloader.net/?url=' 40 | // ] 41 | 42 | // const externalDomainsBlockquotes = [ 43 | // 'http://twittervideodownloader.com/?url=', 44 | // // 'https://twdownloader.net/?url=' 45 | // ] 46 | 47 | // // Do not reexecute script when Twitter location changes 48 | // if (window.location.href.includes('twitter.com/i/cards')) { 49 | // return 50 | // } 51 | 52 | // //#region JQuery events 53 | 54 | // $(document).on('click', '[data-dtv]', function(e): void { 55 | // let mediaUrl = $(this).attr('data-dtv-media-url')! 56 | // const tweetUrl = $(this).attr('data-dtv-tweet-url')! 57 | // const blockquote = $(this).attr('data-dtv-blockquote') === 'true' 58 | 59 | // if (mediaUrl.startsWith('blob:') || mediaUrl.startsWith('https://t.co')) { 60 | // // If it's a blob video, redirect to twdownload.com 61 | // mediaUrl = ( 62 | // blockquote 63 | // ? getRandomItem(externalDomainsBlockquotes) // These urls are compatible with blockquotes 64 | // : getRandomItem(externalDomains) 65 | // ) + tweetUrl 66 | // } 67 | 68 | // e.stopPropagation() 69 | // window.open(mediaUrl) 70 | // }) 71 | 72 | // $(document).on('click', 'a.tweet-action, a.tweet-detail-action', function(e): void { 73 | // setTimeout(() => { 74 | // const tweetUrl = getTweetUrl_tweetDeck(this) 75 | // const article: JQuery = $(this).closest('article') 76 | // const mediaUrl = article.attr('data-dtv-video') 77 | // const actions = article.find('.js-dropdown-content ul') 78 | // if (mediaUrl && !actions.find('[data-dtv-video]').length) { 79 | // actions.append(` 80 | //
  • 81 | // Download media 82 | //
  • `) 83 | // } 84 | // }, 0) 85 | // }) 86 | 87 | // $(document).on('mouseenter', '.is-selectable', function(e): void { 88 | // $(this).addClass('is-selected') 89 | // }) 90 | // $(document).on('mouseleave', '.is-selectable', function(e): void { 91 | // $(this).removeClass('is-selected') 92 | // }) 93 | 94 | // //#endregion 95 | 96 | // //#region CSS 97 | 98 | // const style = document.createElement('style') 99 | // style.setAttribute('type', 'text/css') 100 | // style.appendChild(document.createTextNode(` 101 | // .dtv-link { 102 | // cursor: pointer; 103 | // color: white; 104 | // position: absolute; 105 | // font-size: 1em; 106 | // background-color: #14171A; 107 | // padding: 2px 2px 0 3px; 108 | // border-radius: 2px; 109 | // top: 7px; 110 | // left: 7px; 111 | // opacity: 0; 112 | // transition: 0.2s; 113 | // z-index: 999; 114 | // } 115 | // .dtv-link img { 116 | // border: none; 117 | // } 118 | // .dtv-link:visited { 119 | // color: white; 120 | // } 121 | // .dtv-link:hover { 122 | // color: white; 123 | // } 124 | 125 | // article:hover .dtv-link { 126 | // opacity: 1; 127 | // transition: 0.2s; 128 | // } 129 | 130 | // `)) 131 | // document.head.appendChild(style) 132 | 133 | // //#endregion CSS 134 | 135 | // //#region Twitter 136 | 137 | // function getTweetUrl_twitter(videoContainer: Element): string { 138 | // return location.origin + $(videoContainer).closest('article').find('a[href*="status"]')[0].getAttribute('href')! 139 | // } 140 | 141 | // function addButtonOverVideo(video: IMediaBlock): void { 142 | // // Make sure that the link exists 143 | // if (!video.src) { return } 144 | 145 | // // If it's a blockquote 146 | // const blockquote = $(video.videoContainer).closest('div[role="blockquote"]')[0] 147 | 148 | // // If button is already added 149 | // if ($(video.videoContainer).find('[data-dtv]').length) { return } 150 | 151 | // // video.container.setAttribute('style', 'position: relative;') 152 | 153 | // // Create the button (not a real button, just a link) 154 | // const link = document.createElement('a') 155 | // link.className = 'dtv-link' 156 | 157 | // // Add the download icon 158 | // const icon = document.createElement('img') 159 | // icon.setAttribute('src', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAABhUlEQVQ4ja2UvWpUURSFvz0MQUKYYoiCU0qUFCIiqUVSTOETWOUxLHyD1FMFGzufwFLyAlNIggg+gPgHwWCISXB9FrlXruNMIpJzinvhnP2x9l7r3hK5itW/8FTWgGsA6sfq1dcL7s7fSVbUXfWtuq8+W3RXXKyoqpbVe8CwqgBu/39rrWrP51jUwju9yyCNmkvXn4pkGdhUh8igqpbUFrZm3Gre94A9inRqO1tHSXbVI/VYNYlJVM/UoyTf1Kdqv1s7z6376rsupAP7qU6SDGfr/jZSe+q4hbXABvIyyeo8++en4hz2IMl+wzpplNxYlKNKMgK2qupmx+5U1WvgVN2uqjfqpKoeA9c79nwCXlB8IMk4ycnsTNQvSZ6od9WNJK/Us+bMjtJxm+w+sNRmprVbXa2qHWAKjKpqHTgEPgO3gPfAnTZCvS5gThAHwCaw3rQ8rarnwA9g0jx/z+NRkoOZtrpuzdrf5utYPVAftsMeABvAyr9+Do0Aquo7MKU4rKv6sf0CJZXR6U2U6EQAAAAASUVORK5CYII=') 160 | 161 | // link.setAttribute('data-dtv', '') 162 | // link.setAttribute('data-dtv-media-url', video.src) 163 | // link.setAttribute('data-dtv-tweet-url', getTweetUrl_twitter(video.videoContainer)) 164 | // if (blockquote) { 165 | // link.setAttribute('data-dtv-blockquote', 'true') 166 | // } 167 | 168 | // link.appendChild(icon) 169 | // video.videoContainer.appendChild(link) 170 | // } 171 | 172 | // function getVideos_twitter(): IMediaBlock[] { 173 | // const elems: IMediaBlock[] = [] 174 | // const videos: JQuery = $('video') 175 | // for (const video of videos) { 176 | // const videoContainer = $(video).parent().get(0) 177 | // // if (!container || container.querySelector('[data-dtv]')) { continue } 178 | // elems.push({ 179 | // videoContainer, 180 | // src: video.currentSrc 181 | // }) 182 | // } 183 | // return elems 184 | // } 185 | 186 | // //#endregion Twitter 187 | 188 | // //#region Tweetdeck 189 | 190 | // function getTweetUrl_tweetDeck(elem: Element): string | null { 191 | // const article = elem.closest('article')! 192 | // const tweetLink = article.querySelector('[rel="url"]') 193 | // if (tweetLink) { 194 | // return tweetLink.getAttribute('href') 195 | // } 196 | // throw new Error('DTV - Could not found tweet url') 197 | // } 198 | 199 | // function getVideos_tweetdeck(): IMediaBlock[] { 200 | // const elems: IMediaBlock[] = [] 201 | 202 | // const gifs: JQuery = $('.js-media-gif.media-item-gif') 203 | // for (const gif of gifs) { 204 | // const container = gif.closest('article')! 205 | // if (container.querySelector('[data-dtv]')) { continue } 206 | // elems.push({ 207 | // videoContainer: container, 208 | // src: gif.getAttribute('src')! 209 | // }) 210 | // } 211 | 212 | // const videos = $('div.is-video') 213 | // for (const video of videos) { 214 | // // Only keep "internal" twitter videos 215 | // const src = video.querySelector('[rel=mediaPreview]')!.getAttribute('href')! 216 | // const container = video.closest('article')! 217 | // if (src.startsWith('https://t.co/') && !container.querySelector('[data-dtv]')) { 218 | // elems.push({ 219 | // videoContainer: container, 220 | // src 221 | // }) 222 | // } 223 | // } 224 | // return elems 225 | // } 226 | 227 | // //#endregion Tweetdeck 228 | 229 | // //#region External services 230 | 231 | // function download_twdownload(): void { 232 | // const url = getUrlQuery() 233 | // if (url) { 234 | // const form = document.querySelector('form[action="/download-track/"]') as HTMLElement 235 | // const input = form.querySelector('input[name="twitter-url"]') as HTMLElement 236 | // const submit = form.querySelector('[type="submit"]') as HTMLElement 237 | // input.setAttribute('value', url) 238 | // submit.click() 239 | // } 240 | // } 241 | 242 | // function download_twittervideodownloader(): void { 243 | // const url = getUrlQuery() 244 | // if (url) { 245 | // const form = document.querySelector('form[action="/download"]') as HTMLElement 246 | // const input = form.querySelector('input[name="tweet"]') as HTMLElement 247 | // const submit = form.querySelector('[type="submit"]') as HTMLElement 248 | // input.setAttribute('value', url) 249 | // submit.click() 250 | // } 251 | // } 252 | 253 | // function download_twittervideodownloader_online(): void { 254 | // const url = getUrlQuery() 255 | // if (url) { 256 | // const input = document.querySelector('input#twitter_url') as HTMLInputElement 257 | // const submit = document.querySelector('button#button') as HTMLButtonElement 258 | // input.setAttribute('value', url) 259 | // setTimeout(() => { 260 | // submit.click() 261 | // }, 100) 262 | // } 263 | // } 264 | 265 | // function download_savetweetvid(): void { 266 | // const url = getUrlQuery() 267 | // if (url) { 268 | // const form = document.getElementById('form_download') as HTMLElement 269 | // const input = form.querySelector('input[name="url"]') as HTMLElement 270 | // const submit = form.querySelector('[type="submit"]') as HTMLElement 271 | // input.setAttribute('value', url) 272 | // submit.click() 273 | // } 274 | // } 275 | 276 | // function download_twdownloader(): void { 277 | // const url = getUrlQuery() 278 | // if (url) { 279 | // const form = document.querySelector('form[action="/download/"]') as HTMLElement 280 | // const input = form.querySelector('input[name="tweet"]') as HTMLElement 281 | // const submit = form.querySelector('[type="submit"]') as HTMLElement 282 | // input.setAttribute('value', url) 283 | // submit.click() 284 | // } 285 | // } 286 | 287 | // function download_twdown(): void { 288 | // const url = getUrlQuery() 289 | // if (url) { 290 | // const form = document.querySelector('form[action="download.php"]') as HTMLElement 291 | // const input = form.querySelector('input[name="URL"]') as HTMLElement 292 | // const submit = form.querySelector('[type="submit"]') as HTMLElement 293 | // input.setAttribute('value', url) 294 | // submit.click() 295 | // } 296 | // } 297 | 298 | // //#endregion External services 299 | 300 | // //#region Utils 301 | 302 | // function isTwitter(): boolean { 303 | // return location.host === 'twitter.com' 304 | // } 305 | 306 | // function isTweetdeck(): boolean { 307 | // return location.host === 'tweetdeck.twitter.com' 308 | // } 309 | 310 | // function getUrlQuery(): string | null { 311 | // const urlParams = new URLSearchParams(window.location.search) 312 | // return urlParams.get('url') 313 | // } 314 | 315 | // function getRandomItem(items: T[]): T { 316 | // return items[Math.floor(Math.random() * items.length)] 317 | // } 318 | 319 | // //#endregion Utils 320 | 321 | // /** 322 | // * Create download links on the timeline 323 | // */ 324 | // function main(): void { 325 | 326 | // if (isTwitter()) { 327 | // const videos = getVideos_twitter() 328 | 329 | // for (const video of videos) { 330 | // addButtonOverVideo(video) 331 | // } 332 | // } 333 | 334 | // else if (isTweetdeck()) { 335 | // const videos = getVideos_tweetdeck() 336 | // for (const video of videos) { 337 | // if (!video.src) { continue } 338 | // video.videoContainer.setAttribute('data-dtv-video', video.src) 339 | // } 340 | // } 341 | // } 342 | 343 | if (location.hostname.includes('twitter.com')) { 344 | setInterval(() => { 345 | main() 346 | }, 1000) 347 | } 348 | 349 | // else { 350 | // switch (window.location.hostname) { 351 | // case 'twdownload.com': 352 | // download_twdownload() 353 | // break 354 | 355 | // case 'www.savetweetvid.com': 356 | // download_savetweetvid() 357 | // break 358 | 359 | // case 'twittervideodownloader.com': 360 | // download_twittervideodownloader() 361 | // break 362 | 363 | // case 'twdownloader.net': 364 | // download_twdownloader() 365 | // break 366 | 367 | // case 'twdown.net': 368 | // download_twdown() 369 | // break 370 | 371 | // case 'twittervideodownloader.online': 372 | // download_twittervideodownloader_online() 373 | // break 374 | // } 375 | // } 376 | 377 | })() 378 | -------------------------------------------------------------------------------- /src/globals.d.ts: -------------------------------------------------------------------------------- 1 | declare function waitForKeyElements(selector: string, callback: () => void): void; 2 | 3 | -------------------------------------------------------------------------------- /src/hackernews.user.ts: -------------------------------------------------------------------------------- 1 | /* globals jQuery, $, waitForKeyElements */ 2 | // ==UserScript== 3 | // @name Hacker News - Most upvoted & most commented links 4 | // @namespace https://github.com/scambier/userscripts 5 | // @downloadURL https://github.com/scambier/userscripts/raw/refs/heads/master/dist/hackernews.user.js 6 | // @updateURL https://github.com/scambier/userscripts/raw/refs/heads/master/dist/hackernews.user.js 7 | // @author Simon Cambier 8 | // @version 0.0.10 9 | // @description Show top 🔥👄 links of Hacker News 10 | // @license ISC 11 | // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js 12 | // @match https://news.ycombinator.com/ 13 | // @match https://news.ycombinator.com/ask* 14 | // @match https://news.ycombinator.com/news* 15 | // @match https://news.ycombinator.com/show* 16 | // @match https://news.ycombinator.com/front* 17 | // @grant none 18 | // @run-at document-end 19 | // ==/UserScript== 20 | 21 | (() => { 22 | // Firefox mobile fix, do nothing if icons are already injected 23 | if ([...$('[data-userscript="scambier"]')].length) return; 24 | 25 | const rows = [...$("tr.athing")]; 26 | 27 | // Select lines 28 | const items = rows 29 | // Filter out ads 30 | .filter((tr) => $(tr).find("td.votelinks").length) 31 | // Get id and score 32 | .map((tr) => { 33 | return { 34 | id: $(tr).attr("id")!, 35 | score: Number( 36 | $(tr).next().find(".score")[0].textContent?.split(" ")[0] 37 | ), 38 | // Weirdly, .split(' ') does not work 39 | comments: Number( 40 | $(tr) 41 | .next() 42 | .find('a:contains("comments")') 43 | .text() 44 | .split("comments")[0] 45 | .trim() ?? 0 46 | ), 47 | }; 48 | }); 49 | 50 | // Top 10% for new entries, top 20% for other pages 51 | const ratio = location.href.includes("news.ycombinator.com/newest") 52 | ? 0.1 53 | : 0.2; 54 | 55 | // Inject icons 56 | items.forEach((o) => { 57 | const title = $(`tr#${o.id}`).find("span.titleline"); 58 | if (getTop(items, "comments", ratio).includes(o) && o.comments > 0) { 59 | title.before( 60 | '👄 ' 61 | ); 62 | } 63 | if (getTop(items, "score", ratio).includes(o) && o.score > 0) { 64 | title.before( 65 | '🔥 ' 66 | ); 67 | } 68 | }); 69 | 70 | /** 71 | * Get [ratio] best [items], ordered by [key] 72 | * 73 | * @param items 74 | * @param key 75 | * @param ratio 76 | */ 77 | function getTop( 78 | items: T[], 79 | key: string, 80 | ratio: number 81 | ): T[] { 82 | const count = rows.length; 83 | return [...items].sort((a, b) => b[key] - a[key]).slice(0, count * ratio); 84 | } 85 | })(); 86 | -------------------------------------------------------------------------------- /src/hide-reddit-awards.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scambier/userscripts/03dcc3c827881118b3b2cbc62d5fe69f676a98bb/src/hide-reddit-awards.ts -------------------------------------------------------------------------------- /src/obsidian-omnisearch-bing.user.ts: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Obsidian Omnisearch in Bing 3 | // @version 0.3 4 | // @description Injects Obsidian notes into Bing search results 5 | // @author ever 6 | // @namespace https://github.com/scambier/userscripts 7 | // @downloadURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-bing.user.js 8 | // @updateURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-bing.user.js 9 | // @match *://*bing.com/* 10 | // @match https://bing.com/* 11 | // @match https://www.bing.com/* 12 | // @icon https://obsidian.md/favicon.ico 13 | // @require https://code.jquery.com/jquery-3.7.1.min.js 14 | // @require https://raw.githubusercontent.com/sizzlemctwizzle/GM_config/master/gm_config.js 15 | // @require https://gist.githubusercontent.com/scambier/109932d45b7592d3decf24194008be4d/raw/9c97aa67ff9c5d56be34a55ad6c18a314e5eb548/waitForKeyElements.js 16 | // @grant GM.xmlHttpRequest 17 | // @grant GM_getValue 18 | // @grant GM_setValue 19 | // @grant GM.getValue 20 | // @grant GM.setValue 21 | // ==/UserScript== 22 | 23 | (function() { 24 | "use strict"; 25 | 26 | // Bing's right "sidebar" selector for additional content 27 | const sidebarSelector = "#b_context"; 28 | // The results div 29 | const resultsDivId = "OmnisearchObsidianResultsBing"; 30 | // The "loading"/"no results" label 31 | const loadingSpanId = "OmnisearchObsidianLoadingBing"; 32 | 33 | // Configure GM_config 34 | // The `new GM_config()` syntax is not recognized by the TS compiler 35 | // @ts-ignore 36 | const gmc = new GM_config({ 37 | id: "ObsidianOmnisearchBing", 38 | title: "Omnisearch in Bing - Configuration", 39 | fields: { 40 | port: { 41 | label: "HTTP Port", 42 | type: "text", 43 | default: "51361", 44 | }, 45 | nbResults: { 46 | label: "Number of results to display", 47 | type: "int", 48 | default: 3, 49 | }, 50 | }, 51 | events: { 52 | save: () => location.reload(), 53 | init: () => {}, 54 | }, 55 | }); 56 | 57 | 58 | // Promise resolves when initialization completes 59 | const onInit = (config:any) => new Promise((resolve) => { 60 | let isInit = () => setTimeout(() => (config.isInit ? resolve() : isInit()), 0); 61 | isInit(); 62 | }); 63 | 64 | function omnisearch() { 65 | const port = gmc.get("port"); 66 | const nbResults = gmc.get("nbResults"); 67 | const params = new URLSearchParams(window.location.search); 68 | const query = params.get("q"); 69 | 70 | if (!query) return; 71 | 72 | injectLoadingLabel(); 73 | 74 | GM.xmlHttpRequest({ 75 | method: "GET", 76 | url: `http://localhost:${port}/search?q=${encodeURIComponent(query)}`, 77 | onload: function(response) { 78 | const data = JSON.parse(response.responseText); 79 | removeLoadingLabel(data.length > 0); 80 | data.splice(nbResults); 81 | 82 | const resultsDiv = $(`#${resultsDivId}`); 83 | resultsDiv.empty(); // Clear previous results 84 | interface Item { 85 | vault: string; 86 | path: string; 87 | basename: string; 88 | excerpt: string; 89 | } 90 | data.forEach((item: Item) => { 91 | const url = `obsidian://open?vault=${encodeURIComponent(item.vault)}&file=${encodeURIComponent(item.path)}`; 92 | const resultHTML = ` 93 |
    94 |

    95 | ${item.basename} 96 |

    97 |

    ${item.excerpt.replace(//gi, " ")}

    98 |
    99 | `; 100 | resultsDiv.append(resultHTML); 101 | }); 102 | }, 103 | onerror: function() { 104 | const span = $(`#${loadingSpanId}`); 105 | if (span.length) { 106 | span.html(`Error: Obsidian is not running or the Omnisearch server is not enabled. 107 |
    Open Obsidian.`); 108 | } 109 | } 110 | }); 111 | } 112 | 113 | function injectLoadingLabel() { 114 | if (!$(`#${loadingSpanId}`).length) { 115 | $(sidebarSelector).prepend(`Loading...`); 116 | } 117 | } 118 | 119 | function removeLoadingLabel(foundResults = true) { 120 | if (foundResults) { 121 | $(`#${loadingSpanId}`).remove(); 122 | } else { 123 | $(`#${loadingSpanId}`).text("No results found"); 124 | } 125 | } 126 | 127 | function injectResultsContainer() { 128 | if (!$(`#${resultsDivId}`).length) { 129 | $(sidebarSelector).prepend(`
    `); 130 | } 131 | } 132 | 133 | function showSettingsDialog() { 134 | const nbResults = gmc.get("nbResults"); 135 | const newNbResults = prompt("Enter the number of results to display:", nbResults); 136 | if (newNbResults !== null) { 137 | gmc.set("nbResults", parseInt(newNbResults)); 138 | gmc.save(); 139 | } 140 | } 141 | 142 | console.log("Loading Omnisearch injector"); 143 | let init = onInit(gmc); 144 | init.then(() => { 145 | gmc.init(); 146 | injectResultsContainer(); 147 | omnisearch(); // Make an initial call, just to avoid an improbable race condition 148 | console.log("Loaded Omnisearch injector"); 149 | 150 | // Add a button to show settings dialog 151 | const settingsButton = $("").css({ 152 | marginRight: "10px", // Add margin for spacing 153 | }).click(showSettingsDialog); 154 | 155 | const headerContainer = $("
    ").css({ 156 | display: "flex", // Use flexbox for layout 157 | alignItems: "center", // Align items vertically 158 | justifyContent: "space-between", // Space evenly between items 159 | }); 160 | 161 | const resultsHeader = $(`

    Obsidian Search Results

    `); 162 | headerContainer.append(resultsHeader, settingsButton); // Append both the header and the button to the container 163 | $(sidebarSelector).prepend(headerContainer); // Prepend the container instead of the header 164 | 165 | }); 166 | })(); 167 | -------------------------------------------------------------------------------- /src/obsidian-omnisearch-ddg.user.ts: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Obsidian Omnisearch in DuckDuckGo 3 | // @namespace https://github.com/scambier/userscripts 4 | // @downloadURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-ddg.user.js 5 | // @updateURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-ddg.user.js 6 | // @version 0.1.0 7 | // @description Injects Obsidian notes in DuckDuckGo search results 8 | // @author Simon Cambier 9 | // @match https://duckduckgo.com/* 10 | // @match https://www.duckduckgo.com/* 11 | // @icon https://obsidian.md/favicon.ico 12 | // @require https://code.jquery.com/jquery-3.7.1.min.js 13 | // @require https://gist.githubusercontent.com/scambier/109932d45b7592d3decf24194008be4d/raw/9c97aa67ff9c5d56be34a55ad6c18a314e5eb548/waitForKeyElements.js 14 | // @require https://cdn.jsdelivr.net/npm/vue@3 15 | // @require https://raw.githubusercontent.com/sizzlemctwizzle/GM_config/master/gm_config.js 16 | // @grant GM.xmlHttpRequest 17 | // @grant GM_getValue 18 | // @grant GM_setValue 19 | // @grant GM.getValue 20 | // @grant GM.setValue 21 | // ==/UserScript== 22 | 23 | /* globals GM_config, jQuery, $, waitForKeyElements */ 24 | (function () { 25 | "use strict"; 26 | 27 | // The right sidebar that will contain the results div 28 | const sidebarSelector = '[data-area="sidebar"]'; 29 | 30 | // The results div 31 | const resultsDivId = "OmnisearchObsidianResults"; 32 | 33 | // The "loading"/"no results" label 34 | const loadingSpanId = "OmnisearchObsidianLoading"; 35 | 36 | // The `new GM_config()` syntax is not recognized by the TS compiler 37 | // @ts-ignore 38 | const gmc = new GM_config({ 39 | id: "ObsidianOmnisearchDdg", 40 | title: "Omnisearch in DuckDuckGo - Configuration", 41 | fields: { 42 | port: { 43 | label: "HTTP Port", 44 | type: "text", 45 | default: "51361", 46 | }, 47 | nbResults: { 48 | label: "Number of results to display", 49 | type: "int", 50 | default: 3, 51 | }, 52 | }, 53 | events: { 54 | save: () => { 55 | location.reload(); 56 | }, 57 | init: () => {}, 58 | }, 59 | }); 60 | 61 | // Promise resolves when initialization completes 62 | const onInit = (config: any) => 63 | new Promise((resolve) => { 64 | let isInit = () => 65 | setTimeout(() => (config.isInit ? resolve() : isInit()), 0); 66 | isInit(); 67 | }); 68 | 69 | // Obsidian logo 70 | const logo = ` 71 | 75 | 76 | `; 77 | 78 | function omnisearch() { 79 | const port = gmc.get("port"); 80 | const nbResults = gmc.get("nbResults"); 81 | 82 | // Extract the ?q= part of the URL with URLSearchParams 83 | const params = new URLSearchParams(window.location.search); 84 | const query = params.get("q"); 85 | if (!query) return; 86 | 87 | injectLoadingLabel(); 88 | 89 | GM.xmlHttpRequest({ 90 | method: "GET", 91 | url: `http://localhost:${port}/search?q=${query}`, 92 | headers: { 93 | "Content-Type": "application/json", 94 | }, 95 | onload: function (res) { 96 | const data = JSON.parse(res.response); 97 | 98 | removeLoadingLabel(data.length > 0); 99 | 100 | // Keep the x first results 101 | data.splice(nbResults); 102 | const resultsDiv = $(`#${resultsDivId}`); 103 | 104 | // Delete all existing data-omnisearch 105 | $("[data-omnisearch-result]").remove(); 106 | 107 | // Inject results 108 | for (const item of data) { 109 | const url = `obsidian://open?vault=${encodeURIComponent( 110 | item.vault 111 | )}&file=${encodeURIComponent(item.path)}`; 112 | const element = $(` 113 |
    114 | 115 | 124 | 125 |
    126 |

    127 | 130 | ${item.basename} 131 | 132 |

    133 |
    134 | 135 |
    136 |
    137 |
    138 | ${item.excerpt.replaceAll("
    ", " ")} 139 |
    140 |
    141 |
    142 |
    `); 143 | 144 | resultsDiv.append(element); 145 | } 146 | }, 147 | onerror: function (res) { 148 | console.log("Omnisearch error", res); 149 | const span = $("#" + loadingSpanId)[0]; 150 | if (span) { 151 | span.innerHTML = `Error: Obsidian is not running or the Omnisearch server is not enabled. 152 |
    Open Obsidian.`; 153 | } 154 | }, 155 | }); 156 | } 157 | 158 | function injectResultsContainer() { 159 | $(`#${resultsDivId}`).remove(); 160 | const resultsDiv = $( 161 | `
    ` 162 | ); 163 | $(sidebarSelector).prepend(resultsDiv); 164 | } 165 | 166 | function injectTitle() { 167 | const id = "OmnisearchObsidianConfig"; 168 | if (!$("#" + id)[0]) { 169 | const btn = $( 170 | `
    171 | ${logo} Omnisearch results 172 | 173 | (settings) 174 | 175 |
    ` 176 | ); 177 | $(`#${resultsDivId}`).append(btn); 178 | $(document).on("click", "#" + id, function () { 179 | gmc.open(); 180 | }); 181 | } 182 | } 183 | 184 | function injectLoadingLabel() { 185 | if (!$("#" + loadingSpanId)[0]) { 186 | const label = $(`Loading...`); 187 | $(`#${resultsDivId}`).append(label); 188 | } 189 | } 190 | 191 | function removeLoadingLabel(foundResults = true) { 192 | if (foundResults) { 193 | $("#" + loadingSpanId).remove(); 194 | } else { 195 | $("#" + loadingSpanId).text("No result found"); 196 | } 197 | } 198 | 199 | console.log("Loading Omnisearch injector"); 200 | let init = onInit(gmc); 201 | init.then(() => { 202 | injectResultsContainer(); 203 | injectTitle(); 204 | 205 | waitForKeyElements("#layout-v2", omnisearch); 206 | omnisearch(); // Make an initial call, just to avoid an improbable race condition 207 | console.log("Loaded Omnisearch injector"); 208 | }); 209 | })(); 210 | -------------------------------------------------------------------------------- /src/obsidian-omnisearch-google.user.ts: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Obsidian Omnisearch in Google 3 | // @namespace https://github.com/scambier/userscripts 4 | // @downloadURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-google.user.js 5 | // @updateURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-google.user.js 6 | // @version 0.3.4 7 | // @description Injects Obsidian notes in Google search results 8 | // @author Simon Cambier 9 | // @match https://google.com/* 10 | // @match https://www.google.com/* 11 | // @icon https://obsidian.md/favicon.ico 12 | // @require https://code.jquery.com/jquery-3.7.1.min.js 13 | // @require https://raw.githubusercontent.com/sizzlemctwizzle/GM_config/master/gm_config.js 14 | // @require https://gist.githubusercontent.com/scambier/109932d45b7592d3decf24194008be4d/raw/9c97aa67ff9c5d56be34a55ad6c18a314e5eb548/waitForKeyElements.js 15 | // @grant GM.xmlHttpRequest 16 | // @grant GM_getValue 17 | // @grant GM_setValue 18 | // @grant GM.getValue 19 | // @grant GM.setValue 20 | // ==/UserScript== 21 | 22 | /* globals GM_config, jQuery, $, waitForKeyElements */ 23 | 24 | (function () { 25 | "use strict"; 26 | 27 | // Google's right "sidebar" that will contain the results div 28 | const sidebarSelector = "#rhs"; 29 | 30 | // The results div 31 | const resultsDivId = "OmnisearchObsidianResults"; 32 | 33 | // The "loading"/"no results" label 34 | const loadingSpanId = "OmnisearchObsidianLoading"; 35 | 36 | // The `new GM_config()` syntax is not recognized by the TS compiler 37 | // @ts-ignore 38 | const gmc = new GM_config({ 39 | id: "ObsidianOmnisearchGoogle", 40 | title: "Omnisearch in Google - Configuration", 41 | fields: { 42 | port: { 43 | label: "HTTP Port", 44 | type: "text", 45 | default: "51361", 46 | }, 47 | nbResults: { 48 | label: "Number of results to display", 49 | type: "int", 50 | default: 3, 51 | }, 52 | }, 53 | events: { 54 | save: () => { 55 | location.reload(); 56 | }, 57 | init: () => {}, 58 | }, 59 | }); 60 | 61 | // Promise resolves when initialization completes 62 | const onInit = (config: any) => 63 | new Promise((resolve) => { 64 | let isInit = () => 65 | setTimeout(() => (config.isInit ? resolve() : isInit()), 0); 66 | isInit(); 67 | }); 68 | 69 | // Obsidian logo 70 | const logo = ` 71 | 75 | 76 | `; 77 | 78 | function omnisearch() { 79 | const port = gmc.get("port"); 80 | const nbResults = gmc.get("nbResults"); 81 | 82 | // Extract the ?q= part of the URL with URLSearchParams 83 | const params = new URLSearchParams(window.location.search); 84 | const query = params.get("q"); 85 | 86 | if (!query) return; 87 | 88 | injectLoadingLabel(); 89 | 90 | GM.xmlHttpRequest({ 91 | method: "GET", 92 | url: `http://localhost:${port}/search?q=${query}`, 93 | headers: { 94 | "Content-Type": "application/json", 95 | }, 96 | onload: function (res) { 97 | const data = JSON.parse(res.response); 98 | 99 | removeLoadingLabel(data.length > 0); 100 | 101 | // Keep the x first results 102 | data.splice(nbResults); 103 | const resultsDiv = $(`#${resultsDivId}`); 104 | 105 | // Delete all existing data-omnisearch-result 106 | $("[data-omnisearch-result]").remove(); 107 | 108 | // Inject results 109 | for (const item of data) { 110 | const url = `obsidian://open?vault=${encodeURIComponent( 111 | item.vault 112 | )}&file=${encodeURIComponent(item.path)}`; 113 | const element = $(` 114 |
    115 |
    116 |
    117 | 147 |
    148 |
    152 | ${item.excerpt 153 | .replaceAll("
    ", " ") 154 | .replaceAll("
    ", " ")}
    155 |
    156 |
    157 |
    158 |
    159 |
    160 | `); 161 | 162 | resultsDiv.append(element); 163 | } 164 | }, 165 | onerror: function (res) { 166 | console.log("Omnisearch error", res); 167 | const span = $("#" + loadingSpanId)[0]; 168 | if (span) { 169 | span.innerHTML = `Error: Obsidian is not running or the Omnisearch server is not enabled. 170 |
    Open Obsidian.`; 171 | } 172 | }, 173 | }); 174 | } 175 | 176 | function injectTitle() { 177 | const id = "OmnisearchObsidianConfig"; 178 | if (!$("#" + id)[0]) { 179 | const btn = $( 180 | `
    181 | ${logo} Omnisearch results 182 | () 183 |
    ` 184 | ); 185 | $(`#${resultsDivId}`).append(btn); 186 | $(document).on("click", "#" + id, function () { 187 | gmc.open(); 188 | }); 189 | } 190 | } 191 | 192 | function injectResultsContainer() { 193 | const resultsDiv = $( 194 | `
    ` 195 | ); 196 | $(sidebarSelector).prepend(resultsDiv); 197 | } 198 | 199 | function injectLoadingLabel() { 200 | if (!$("#" + loadingSpanId)[0]) { 201 | const label = $(`Loading...`); 202 | $(`#${resultsDivId}`).append(label); 203 | } 204 | } 205 | 206 | function removeLoadingLabel(foundResults = true) { 207 | if (foundResults) { 208 | $("#" + loadingSpanId).remove(); 209 | } else { 210 | $("#" + loadingSpanId).text("No results found"); 211 | } 212 | } 213 | 214 | console.log("Loading Omnisearch injector"); 215 | let init = onInit(gmc); 216 | init.then(() => { 217 | // Make sure the results container is there 218 | if (!$(sidebarSelector)[0]) { 219 | $("#rcnt").append('
    '); 220 | } 221 | 222 | injectResultsContainer(); 223 | injectTitle(); 224 | 225 | omnisearch(); // Make an initial call, just to avoid an improbable race condition 226 | console.log("Loaded Omnisearch injector"); 227 | 228 | // Keep the results on top 229 | waitForKeyElements(sidebarSelector, () => { 230 | $(resultsDivId).prependTo(sidebarSelector); 231 | }); 232 | }); 233 | })(); 234 | -------------------------------------------------------------------------------- /src/obsidian-omnisearch-kagi.user.ts: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Obsidian Omnisearch in Kagi 3 | // @namespace https://github.com/scambier/userscripts 4 | // @downloadURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-kagi.user.js 5 | // @updateURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-kagi.user.js 6 | // @version 0.3.2 7 | // @description Injects Obsidian notes in Kagi search results 8 | // @author Simon Cambier 9 | // @match https://kagi.com/* 10 | // @match https://www.kagi.com/* 11 | // @icon https://obsidian.md/favicon.ico 12 | // @require https://code.jquery.com/jquery-3.7.1.min.js 13 | // @require https://gist.githubusercontent.com/scambier/109932d45b7592d3decf24194008be4d/raw/9c97aa67ff9c5d56be34a55ad6c18a314e5eb548/waitForKeyElements.js 14 | // @require https://raw.githubusercontent.com/sizzlemctwizzle/GM_config/master/gm_config.js 15 | // @grant GM.xmlHttpRequest 16 | // @grant GM_getValue 17 | // @grant GM_setValue 18 | // @grant GM.getValue 19 | // @grant GM.setValue 20 | // ==/UserScript== 21 | 22 | /* globals GM_config, jQuery, $, waitForKeyElements */ 23 | (function () { 24 | "use strict"; 25 | 26 | // The right sidebar that will contain the results div 27 | const sidebarSelector = ".right-content-box"; 28 | 29 | // The results div 30 | const resultsDivId = "OmnisearchObsidianResults"; 31 | 32 | // The "loading"/"no results" label 33 | const loadingSpanId = "OmnisearchObsidianLoading"; 34 | 35 | // The `new GM_config()` syntax is not recognized by the TS compiler 36 | // @ts-ignore 37 | const gmc = new GM_config({ 38 | id: "ObsidianOmnisearchKagi", 39 | title: "Omnisearch in Kagi - Configuration", 40 | fields: { 41 | port: { 42 | label: "HTTP Port", 43 | type: "text", 44 | default: "51361", 45 | }, 46 | nbResults: { 47 | label: "Number of results to display", 48 | type: "int", 49 | default: 3, 50 | }, 51 | }, 52 | events: { 53 | save: () => { 54 | location.reload(); 55 | }, 56 | init: () => {}, 57 | }, 58 | }); 59 | 60 | // Promise resolves when initialization completes 61 | const onInit = (config: any) => 62 | new Promise((resolve) => { 63 | let isInit = () => 64 | setTimeout(() => (config.isInit ? resolve() : isInit()), 0); 65 | isInit(); 66 | }); 67 | 68 | // Obsidian logo 69 | const logo = ` 70 | 74 | 75 | `; 76 | 77 | function omnisearch() { 78 | const port = gmc.get("port"); 79 | const nbResults = gmc.get("nbResults"); 80 | 81 | // Extract the ?q= part of the URL with URLSearchParams 82 | const params = new URLSearchParams(window.location.search); 83 | const query = params.get("q"); 84 | if (!query) return; 85 | 86 | injectLoadingLabel(); 87 | 88 | GM.xmlHttpRequest({ 89 | method: "GET", 90 | url: `http://localhost:${port}/search?q=${query}`, 91 | headers: { 92 | "Content-Type": "application/json", 93 | }, 94 | onload: function (res) { 95 | const data = JSON.parse(res.response); 96 | 97 | removeLoadingLabel(data.length > 0); 98 | 99 | // Keep the x first results 100 | data.splice(nbResults); 101 | const resultsDiv = $(`#${resultsDivId}`); 102 | 103 | // Delete all existing data-omnisearch 104 | $("[data-omnisearch-result]").remove(); 105 | 106 | // Inject results 107 | for (const item of data) { 108 | const url = `obsidian://open?vault=${encodeURIComponent( 109 | item.vault 110 | )}&file=${encodeURIComponent(item.path)}`; 111 | const element = $(` 112 |
    113 |
    114 |

    115 | 118 | ${item.basename} 119 | 120 |

    121 |
    122 | 123 | 132 |
    133 |
    134 |
    135 | ${item.excerpt.replaceAll("
    ", " ")} 136 |
    137 |
    138 |
    139 |
    `); 140 | 141 | resultsDiv.append(element); 142 | } 143 | }, 144 | onerror: function (res) { 145 | console.log("Omnisearch error", res); 146 | const span = $("#" + loadingSpanId)[0]; 147 | if (span) { 148 | span.innerHTML = `Error: Obsidian is not running or the Omnisearch server is not enabled. 149 |
    Open Obsidian.`; 150 | } 151 | }, 152 | }); 153 | } 154 | 155 | function injectResultsContainer() { 156 | $(`#${resultsDivId}`).remove(); 157 | const resultsDiv = $( 158 | `
    ` 159 | ); 160 | $(sidebarSelector).prepend(resultsDiv); 161 | } 162 | 163 | function injectTitle() { 164 | const id = "OmnisearchObsidianConfig"; 165 | if (!$("#" + id)[0]) { 166 | const btn = $( 167 | `
    168 | ${logo} Omnisearch results 169 | 170 | (settings) 171 | 172 |
    ` 173 | ); 174 | $(`#${resultsDivId}`).append(btn); 175 | $(document).on("click", "#" + id, function () { 176 | gmc.open(); 177 | }); 178 | } 179 | } 180 | 181 | function injectLoadingLabel() { 182 | if (!$("#" + loadingSpanId)[0]) { 183 | const label = $(`Loading...`); 184 | $(`#${resultsDivId}`).append(label); 185 | } 186 | } 187 | 188 | function removeLoadingLabel(foundResults = true) { 189 | if (foundResults) { 190 | $("#" + loadingSpanId).remove(); 191 | } else { 192 | $("#" + loadingSpanId).text("No result found"); 193 | } 194 | } 195 | 196 | console.log("Loading Omnisearch injector"); 197 | let init = onInit(gmc); 198 | init.then(() => { 199 | injectResultsContainer(); 200 | injectTitle(); 201 | 202 | waitForKeyElements("#layout-v2", omnisearch); 203 | omnisearch(); // Make an initial call, just to avoid an improbable race condition 204 | console.log("Loaded Omnisearch injector"); 205 | }); 206 | })(); 207 | -------------------------------------------------------------------------------- /src/types/gm_config.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009+, GM_config Contributors (https://github.com/sizzlemctwizzle/GM_config) 3 | 4 | GM_config Collaborators/Contributors: 5 | Mike Medley 6 | Joe Simmons 7 | Izzy Soft 8 | Marti Martz 9 | Adam Thompson-Sharpe 10 | 11 | GM_config is distributed under the terms of the GNU Lesser General Public License. 12 | 13 | GM_config is free software: you can redistribute it and/or modify 14 | it under the terms of the GNU Lesser General Public License as published by 15 | the Free Software Foundation, either version 3 of the License, or 16 | (at your option) any later version. 17 | 18 | This program is distributed in the hope that it will be useful, 19 | but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | GNU Lesser General Public License for more details. 22 | 23 | You should have received a copy of the GNU Lesser General Public License 24 | along with this program. If not, see . 25 | */ 26 | 27 | // Minimum TypeScript Version: 2.8 28 | 29 | type FieldValue = string | number | boolean; 30 | /** Valid types for Field `type` property */ 31 | type FieldTypes = 32 | | 'text' 33 | | 'textarea' 34 | | 'button' 35 | | 'radio' 36 | | 'select' 37 | | 'checkbox' 38 | | 'unsigned int' 39 | | 'unsinged integer' 40 | | 'int' 41 | | 'integer' 42 | | 'float' 43 | | 'number' 44 | | 'hidden'; 45 | 46 | /** Init options where no custom types are defined */ 47 | interface InitOptionsNoCustom { 48 | /** Used for this instance of GM_config */ 49 | id: string; 50 | /** Label the opened config window */ 51 | title?: string | HTMLElement; 52 | fields: Record; 53 | /** Optional styling to apply to the menu */ 54 | css?: string; 55 | /** Element to use for the config panel */ 56 | frame?: HTMLElement; 57 | 58 | /** Handlers for different events */ 59 | events?: { 60 | init?: GM_configStruct['onInit']; 61 | open?: GM_configStruct['onOpen']; 62 | save?: GM_configStruct['onSave']; 63 | close?: GM_configStruct['onClose']; 64 | reset?: GM_configStruct['onReset']; 65 | }; 66 | } 67 | 68 | /** Init options where custom types are defined */ 69 | interface InitOptionsCustom extends Omit { 70 | fields: Record>; 71 | /** Custom fields */ 72 | types: { [type in CustomTypes]: CustomType }; 73 | } 74 | 75 | /** Init options where the types key is only required if custom types are used */ 76 | type InitOptions = InitOptionsNoCustom | InitOptionsCustom; 77 | 78 | interface Field { 79 | [key: string]: any; 80 | /** Display label for the field */ 81 | label?: string | HTMLElement; 82 | /** Type of input */ 83 | type: FieldTypes | CustomTypes; 84 | /** Text to show on hover */ 85 | title?: string; 86 | /** Default value for field */ 87 | default?: FieldValue; 88 | save?: boolean; 89 | } 90 | 91 | interface CustomType { 92 | default?: FieldValue | null; 93 | toNode?: GM_configField['toNode']; 94 | toValue?: GM_configField['toValue']; 95 | reset?: GM_configField['reset']; 96 | } 97 | 98 | /* GM_configStruct and related */ 99 | 100 | /** Initialize a GM_configStruct */ 101 | declare function GM_configInit( 102 | config: GM_configStruct, 103 | // tslint:disable-next-line:no-unnecessary-generics 104 | options: InitOptions, 105 | ): void; 106 | 107 | declare function GM_configDefaultValue(type: FieldTypes): FieldValue; 108 | 109 | /** Create multiple GM_config instances */ 110 | declare class GM_configStruct { 111 | constructor(options: InitOptions) 112 | 113 | /** Initialize GM_config */ 114 | // tslint:disable-next-line:no-unnecessary-generics 115 | init(options: InitOptions): void; 116 | 117 | /** Display the config panel */ 118 | open(): void; 119 | /** Close the config panel */ 120 | close(): void; 121 | 122 | /** Directly set the value of a field */ 123 | set(fieldId: string, value: FieldValue): void; 124 | /** 125 | * Get a config value 126 | * @param getLive If true, runs `field.toValue()` instead of just getting `field.value` 127 | */ 128 | get(fieldId: string, getLive?: boolean): FieldValue; 129 | /** Save the current values */ 130 | save(): void; 131 | 132 | read(store?: string): any; 133 | 134 | write(store?: string, obj?: any): any; 135 | 136 | /** 137 | * 138 | * @param args If only one arg is passed, argument is passed to `document.createTextNode`. 139 | * With any other amount, args[0] is passed to `document.createElement` and the second arg 140 | * has something to do with event listeners? 141 | * 142 | * @todo Improve types based on 143 | * 144 | */ 145 | create(...args: [string] | [string, any] | []): HTMLElement; 146 | 147 | center(): void; 148 | 149 | remove(el: HTMLElement): void; 150 | 151 | /* Computed */ 152 | 153 | /** Whether GreaseMonkey functions are present */ 154 | isGM: boolean; 155 | /** 156 | * Either calls `localStorage.setItem` or `GM_setValue`. 157 | * Shouldn't be directly called 158 | */ 159 | setValue(name: string, value: FieldValue): Promise | void; 160 | /** 161 | * Get a value. Shouldn't be directly called 162 | * 163 | * @param name The name of the value 164 | * @param def The default to return if the value is not defined. 165 | * Only for localStorage fallback 166 | */ 167 | getValue(name: string, def: FieldValue): FieldValue; 168 | 169 | /** Converts a JSON object to a string */ 170 | stringify(obj: any): string; 171 | /** 172 | * Converts a string to a JSON object 173 | * @returns `undefined` if the string was an invalid object, 174 | * otherwise returns the parsed object 175 | */ 176 | parser(jsonString: string): any; 177 | 178 | /** Log a string with multiple fallbacks */ 179 | log(data: string): void; 180 | 181 | /* Created from GM_configInit */ 182 | id: string; 183 | title: string; 184 | css: { 185 | basic: string[]; 186 | basicPrefix: string; 187 | stylish: string; 188 | }; 189 | frame?: HTMLElement; 190 | fields: Record; 191 | onInit?: (this: GM_configStruct) => void; 192 | onOpen?: (this: GM_configStruct, document: Document, window: Window, frame: HTMLElement) => void; 193 | onSave?: (this: GM_configStruct, values: any) => void; 194 | onClose?: (this: GM_configStruct) => void; 195 | onReset?: (this: GM_configStruct) => void; 196 | isOpen: boolean; 197 | } 198 | 199 | /** Default GM_config object */ 200 | declare let GM_config: GM_configStruct; 201 | 202 | /* GM_configField and related */ 203 | 204 | declare class GM_configField { 205 | constructor( 206 | settings: Field, 207 | stored: FieldValue | undefined, 208 | id: string, 209 | customType: CustomType | undefined, 210 | configId: string, 211 | ) 212 | 213 | [key: string]: any; 214 | settings: Field; 215 | id: string; 216 | configId: string; 217 | node: HTMLElement | null; 218 | wrapper: HTMLElement | null; 219 | save: boolean; 220 | /** The stored value */ 221 | value: FieldValue; 222 | default: FieldValue; 223 | 224 | create: GM_configStruct['create']; 225 | 226 | toNode(this: GM_configField, configId?: string): HTMLElement; 227 | 228 | /** Get value from field */ 229 | toValue(this: GM_configField): FieldValue | null; 230 | 231 | reset(this: GM_configField): void; 232 | 233 | remove(el?: HTMLElement): void; 234 | 235 | reload(): void; 236 | 237 | _checkNumberRange(num: number, warn: string): true | null; 238 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | "lib": ["dom", "es2017"], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./dist", /* Redirect output structure to the directory. */ 15 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "removeComments": true, /* Do not emit comments to output. */ 18 | // "noEmit": true, /* Do not emit outputs. */ 19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | "strict": true, /* Enable all strict type-checking options. */ 25 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | // "strictNullChecks": true, /* Enable strict null checks. */ 27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 29 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 30 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 31 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 32 | 33 | /* Additional Checks */ 34 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 35 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 36 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 37 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 38 | 39 | /* Module Resolution Options */ 40 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 41 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 42 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 43 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 44 | // "typeRoots": [], /* List of folders to include type definitions from. */ 45 | // "types": [], /* Type declaration files to be included in compilation. */ 46 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 47 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 48 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 49 | 50 | /* Source Map Options */ 51 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 52 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 53 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 54 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 55 | 56 | /* Experimental Options */ 57 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 58 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 59 | } 60 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "linterOptions": { 7 | "exclude": [ 8 | "**/*.spec.ts", 9 | "**/*.test.ts" 10 | ] 11 | }, 12 | "jsRules": {}, 13 | "rules": { 14 | "semicolon": [ 15 | true, 16 | "never" 17 | ], 18 | "quotemark": [ 19 | true, 20 | "single" 21 | ], 22 | "no-var-requires": false, 23 | "ordered-imports": true, 24 | "arrow-parens": false, 25 | "trailing-comma": [ 26 | true, 27 | "never" 28 | ], 29 | "object-literal-sort-keys": false, 30 | "one-line": false, 31 | "variable-name": [ 32 | true, 33 | "ban-keywords", 34 | "check-format", 35 | "allow-leading-underscore", 36 | "allow-pascal-case" 37 | ], 38 | "align": false, 39 | "typedef": [ 40 | true, 41 | "call-signature" 42 | ], 43 | "no-unused-expression": false, 44 | "interface-name": false, 45 | "no-console": [ 46 | true, 47 | "log" 48 | ], 49 | "no-bitwise": false, 50 | "interface-over-type-literal": false, 51 | "max-line-length": [ 52 | false 53 | ], 54 | "max-classes-per-file": false, 55 | "no-empty": false 56 | }, 57 | "rulesDirectory": [] 58 | } --------------------------------------------------------------------------------