├── .browserslistrc ├── .editorconfig ├── .env.example ├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── 01_bug_report.yml │ └── 02_feature_request.yml ├── .gitignore ├── .swcrc ├── .vscode ├── extensions.json └── launch.json ├── CHANGELOG.user.md ├── LICENSE ├── README.de.md ├── README.es.md ├── README.fr.md ├── README.it.md ├── README.ja.md ├── README.md ├── README.nl.md ├── README.pl.md ├── README.pt.md ├── README.ru.md ├── README.zh-tw.md ├── README.zh.md ├── UserJS.json ├── Userscript-Plus.code-workspace ├── assets ├── install-userscript.gif ├── preview.png ├── step-1.png ├── step-2.png ├── step-3.png └── using-tabs.gif ├── browser-issues.md ├── dist ├── magic-userjs.meta.js └── magic-userjs.user.js ├── eslint.config.js ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── prettier.config.js ├── src ├── UserJS │ ├── header.js │ └── main.js ├── _locales │ ├── ar │ │ └── messages.json │ ├── de │ │ └── messages.json │ ├── en │ │ └── messages.json │ ├── en_GB │ │ └── messages.json │ ├── es │ │ └── messages.json │ ├── fr │ │ └── messages.json │ ├── ja │ │ └── messages.json │ ├── nl │ │ └── messages.json │ ├── pl │ │ └── messages.json │ ├── ru │ │ └── messages.json │ ├── zh │ │ └── messages.json │ ├── zh_CN │ │ └── messages.json │ └── zh_TW │ │ └── messages.json ├── html │ └── popup.html ├── img │ ├── greasyfork.svg │ ├── icon_128.png │ ├── icon_16.png │ ├── icon_32.png │ ├── icon_48.png │ ├── icon_64.png │ └── verified-svgrepo-com.svg ├── js │ ├── XMap.js │ ├── background.js │ ├── constants.js │ ├── container.js │ ├── ext.js │ ├── i18n.js │ ├── logger.js │ ├── messager.js │ ├── mu.js │ ├── network.js │ ├── popup.js │ ├── punycode.js │ ├── querySelector.js │ ├── request-code.js │ ├── storage.js │ └── util.js ├── manifest │ ├── chrome.json │ └── firefox.json ├── sass │ ├── _main.scss │ ├── fontawesome │ │ ├── _animated.scss │ │ ├── _bordered-pulled.scss │ │ ├── _core.scss │ │ ├── _fixed-width.scss │ │ ├── _functions.scss │ │ ├── _icons.scss │ │ ├── _list.scss │ │ ├── _mixins.scss │ │ ├── _rotated-flipped.scss │ │ ├── _screen-reader.scss │ │ ├── _shims.scss │ │ ├── _sizing.scss │ │ ├── _stacked.scss │ │ ├── _variables.scss │ │ ├── brands.scss │ │ ├── fontawesome.scss │ │ ├── regular.scss │ │ ├── solid.scss │ │ └── v4-shims.scss │ ├── magicuserjs.scss │ └── web-ext.scss ├── typings │ ├── UserJS.d.ts │ ├── WebExt.d.ts │ ├── scheduler.d.ts │ └── types.d.ts └── webfonts │ ├── fa-brands-400.ttf │ ├── fa-brands-400.woff2 │ ├── fa-regular-400.ttf │ ├── fa-regular-400.woff2 │ ├── fa-solid-900.ttf │ ├── fa-solid-900.woff2 │ ├── fa-v4compatibility.ttf │ └── fa-v4compatibility.woff2 ├── tools ├── userscript.js ├── web-ext.mjs └── webpack.config.js ├── tsconfig.json ├── utils ├── builder │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.d.ts │ │ └── index.js │ └── typings │ │ └── index.d.ts ├── i18n │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.js │ └── typings │ │ └── index.d.ts └── user.js │ ├── README.md │ ├── package.json │ ├── src │ └── index.js │ └── typings │ └── index.d.ts └── wiki ├── Build.md └── README.md /.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 10 | 11 | [*.{js,json,ts,tsx}] 12 | charset = utf-8 -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | JS_ENV="development" 2 | JS_ROOT="./" 3 | JS_i18n="../../../src/_locales" -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: magicoflolis 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/01_bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "🐞 Bug report" 2 | description: File a bug report. 3 | title: "[bug]: " 4 | labels: ["bug 🐞"] 5 | assignees: 6 | - magicoflolis 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thanks for taking the time to fill out this bug report! 12 | - type: checkboxes 13 | attributes: 14 | label: Is there an existing issue for this? 15 | description: Please search to see if an issue already exists for the bug you encountered. 16 | options: 17 | - label: I have searched the existing issues 18 | required: true 19 | - type: dropdown 20 | id: type 21 | attributes: 22 | label: Type 23 | description: How are you using Magic Userscript+? 24 | multiple: true 25 | options: 26 | - User Script 27 | - Web Extension 28 | validations: 29 | required: true 30 | - type: input 31 | id: browser 32 | attributes: 33 | label: Web Browser 34 | description: What browser are you seeing this bug on? 35 | validations: 36 | required: true 37 | - type: input 38 | id: userjs-manager 39 | attributes: 40 | label: User Script Manager 41 | description: What user script manager are you using? - not required when using Web Extension 42 | validations: 43 | required: false 44 | - type: input 45 | id: url 46 | attributes: 47 | label: URL 48 | description: What URL did the bug occur on? 49 | validations: 50 | required: true 51 | - type: textarea 52 | id: what-happened 53 | attributes: 54 | label: What happened? 55 | description: Describe the bug. 56 | placeholder: A clear and concise description of the bug. 57 | value: "A bug happened!" 58 | validations: 59 | required: true 60 | - type: textarea 61 | id: bug-reproduction 62 | attributes: 63 | label: Steps To Reproduce 64 | description: Steps to reproduce the bug. 65 | placeholder: | 66 | 1. Navigated to '...' 67 | 1. Console error '...' 68 | 1. etc. 69 | validations: 70 | required: false 71 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/02_feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "💡 Feature request" 2 | description: Suggest an idea for this project 3 | title: "[feat]: " 4 | labels: ["feature-request 💡"] 5 | assignees: 6 | - magicoflolis 7 | body: 8 | - type: textarea 9 | id: description 10 | attributes: 11 | label: Feature description 12 | description: A clear and concise description, please include if your feature request is related to a problem. 13 | validations: 14 | required: true 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore haters 2 | haters/ 3 | 4 | build 5 | node_modules 6 | 7 | Notes 8 | .history 9 | web-ext 10 | web-ext-artifacts 11 | web-server 12 | 13 | *.ini 14 | 15 | *.log 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | lerna-debug.log* 20 | .pnpm-debug.log* 21 | *.tsbuildinfo 22 | 23 | # dotenv environment variable files 24 | .env 25 | .env.development.local 26 | .env.test.local 27 | .env.production.local 28 | .env.local 29 | -------------------------------------------------------------------------------- /.swcrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/swcrc", 3 | "jsc": { 4 | "parser": { 5 | "syntax": "ecmascript", 6 | "jsx": false, 7 | "dynamicImport": false, 8 | "privateMethod": false, 9 | "functionBind": false, 10 | "exportDefaultFrom": false, 11 | "exportNamespaceFrom": false, 12 | "decorators": false, 13 | "decoratorsBeforeExport": false, 14 | "topLevelAwait": false, 15 | "importMeta": false 16 | }, 17 | "target": "es2020" 18 | }, 19 | "module": { 20 | "type": "es6", 21 | "strict": false, 22 | "strictMode": true, 23 | "lazy": false, 24 | "noInterop": false 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "mrmlnc.vscode-autoprefixer", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode", 6 | "mrmlnc.vscode-scss", 7 | "adpyke.vscode-userscript", 8 | "xyz.local-history" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "compounds": [ 4 | { 5 | "name": "Attach/Launch", 6 | "configurations": ["SF", "Attach:Firefox"] 7 | } 8 | ], 9 | "configurations": [ 10 | { 11 | "name": "Attach:Firefox", 12 | "type": "firefox", 13 | "request": "attach", 14 | }, 15 | { 16 | "name": "SF", 17 | "type": "firefox", 18 | "request": "launch", 19 | "profile": "UserJS", 20 | "clearConsoleOnReload": true, 21 | "reAttach": true, 22 | "reloadOnAttach": true, 23 | "keepProfileChanges": false, 24 | "url": "https://www.google.com/", 25 | "addonPath": "${workspaceFolder}/build/firefox", 26 | "reloadOnChange": "${workspaceFolder}/src/**", 27 | "firefoxArgs": ["--devtools"], 28 | "pathMappings": [ 29 | { 30 | "url": "webpack:///js", 31 | "path": "${workspaceFolder}/src/js" 32 | }, 33 | { 34 | "url": "webpack:///src/", 35 | "path": "${webRoot}/src/" 36 | } 37 | ], 38 | "log": { 39 | "fileName": "${workspaceFolder}/log.txt", 40 | "fileLevel": { 41 | "default": "Debug" 42 | } 43 | } 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2024 Magic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.de.md: -------------------------------------------------------------------------------- 1 | # Magic Userscript+ 2 | 3 | | Preview(s) | 4 | |:----------:| 5 | ![Demo 1](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo3.gif)| 6 | ![Demo 2](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo2.gif)| 7 | ![Demo 3](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo1.png)| 8 | 9 | ## Source Code 10 | 11 | * [GitHub](https://github.com/magicoflolis/Userscript-Plus) 12 | 13 | ### Contacts 14 | 15 | [GitHub](https://github.com/magicoflolis) 16 | 17 | [Twitter](https://twitter.com/for_lollipops) 18 | 19 | [Greasy Fork](https://greasyfork.org/users/166061) 20 | -------------------------------------------------------------------------------- /README.es.md: -------------------------------------------------------------------------------- 1 | # Magic Userscript+ 2 | 3 | | Preview(s) | 4 | |:----------:| 5 | ![Demo 1](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo3.gif)| 6 | ![Demo 2](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo2.gif)| 7 | ![Demo 3](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo1.png)| 8 | 9 | ## Source Code 10 | 11 | * [GitHub](https://github.com/magicoflolis/Userscript-Plus) 12 | 13 | ### Contacts 14 | 15 | [GitHub](https://github.com/magicoflolis) 16 | 17 | [Twitter](https://twitter.com/for_lollipops) 18 | 19 | [Greasy Fork](https://greasyfork.org/users/166061) 20 | -------------------------------------------------------------------------------- /README.fr.md: -------------------------------------------------------------------------------- 1 | # Magic Userscript+ 2 | 3 | | Preview(s) | 4 | |:----------:| 5 | ![Demo 1](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo3.gif)| 6 | ![Demo 2](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo2.gif)| 7 | ![Demo 3](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo1.png)| 8 | 9 | ## Source Code 10 | 11 | * [GitHub](https://github.com/magicoflolis/Userscript-Plus) 12 | 13 | ### Contacts 14 | 15 | [GitHub](https://github.com/magicoflolis) 16 | 17 | [Twitter](https://twitter.com/for_lollipops) 18 | 19 | [Greasy Fork](https://greasyfork.org/users/166061) 20 | -------------------------------------------------------------------------------- /README.it.md: -------------------------------------------------------------------------------- 1 | # Magic Userscript+ 2 | 3 | | Preview(s) | 4 | |:----------:| 5 | ![Demo 1](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo3.gif)| 6 | ![Demo 2](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo2.gif)| 7 | ![Demo 3](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo1.png)| 8 | 9 | ## Source Code 10 | 11 | * [GitHub](https://github.com/magicoflolis/Userscript-Plus) 12 | 13 | ### Contacts 14 | 15 | [GitHub](https://github.com/magicoflolis) 16 | 17 | [Twitter](https://twitter.com/for_lollipops) 18 | 19 | [Greasy Fork](https://greasyfork.org/users/166061) 20 | -------------------------------------------------------------------------------- /README.ja.md: -------------------------------------------------------------------------------- 1 | # Magic Userscript+ 2 | 3 | | Preview(s) | 4 | |:----------:| 5 | ![Demo 1](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo3.gif)| 6 | ![Demo 2](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo2.gif)| 7 | ![Demo 3](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo1.png)| 8 | 9 | ## Source Code 10 | 11 | * [GitHub](https://github.com/magicoflolis/Userscript-Plus) 12 | 13 | ### Contacts 14 | 15 | [GitHub](https://github.com/magicoflolis) 16 | 17 | [Twitter](https://twitter.com/for_lollipops) 18 | 19 | [Greasy Fork](https://greasyfork.org/users/166061) 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Badge License](https://img.shields.io/github/license/magicoflolis/Userscript-Plus?style=flat-square)](https://github.com/magicoflolis/Userscript-Plus/blob/master/LICENSE) 2 | [![Badge Issues](https://img.shields.io/github/issues/magicoflolis/Userscript-Plus?style=flat-square)](https://github.com/magicoflolis/Userscript-Plus/issues) 3 | [![Badge Greasy Fork](https://img.shields.io/greasyfork/dt/421603?style=flat-square)](https://greasyfork.org/scripts/421603) 4 | [![Badge Stars](https://img.shields.io/github/stars/magicoflolis/Userscript-Plus?style=flat-square)](https://github.com/magicoflolis/Userscript-Plus/stargazers) 5 | 6 | --- 7 | 8 |

9 | 10 | 11 | 12 | Magic Userscript+ 13 |

14 | 15 | *A complete rewrite of [Userscript+ : Show Site All UserJS](https://github.com/jae-jae/Userscript-Plus)* 16 | 17 | Finds available UserScripts and UserStyles for the current webpage, the power of [Greasy Fork](https://greasyfork.org) on the go! 18 | 19 | [UserScript Changelog](https://github.com/magicoflolis/Userscript-Plus/blob/master/CHANGELOG.user.md) 20 | 21 | [Web Extension Changelog](https://github.com/magicoflolis/Userscript-Plus/releases) 22 | 23 | [List of known issues by Web Browser](https://github.com/magicoflolis/Userscript-Plus/blob/master/browser-issues.md) 24 | 25 | ## **Download** 26 | 27 | **UserScript:** 28 | 29 | > [!IMPORTANT] 30 | > The UserScript only works on `HTTPS` sites! ([https://example.com](https://example.com)) 31 | 32 | * [Greasy Fork](https://greasyfork.org/scripts/421603) 33 | * [GitHub Repo](https://github.com/magicoflolis/Userscript-Plus/blob/master/dist/magic-userjs.user.js?raw=1) 34 | * [Open UserJS](https://openuserjs.org/scripts/Magic/Magic_Userscript+_Show_Site_All_UserJS) - Outdated 35 | 36 | **Web Extension:** 37 | 38 | * [GitHub Repo](https://github.com/magicoflolis/Userscript-Plus/releases) 39 | * [Firefox Add-ons](https://addons.mozilla.org/firefox/addon/userscript-plus/) 40 | * [Chrome Web Store](https://chromewebstore.google.com/detail/kbelpalpbddhjhoakbjkfookjeiennbo) 41 | * [Edge Add-ons](https://microsoftedge.microsoft.com/addons/detail/golkolijobdaldjgefapmcknlmkjlhdh) 42 | * ~~[Opera Add-ons](https://github.com/magicoflolis/Userscript-Plus/releases)~~ - Under review 43 | 44 | **Bookmarklet (not recommended):** 45 | 46 | Save this URL as a bookmark, clicking it will cause the **UserScript version** to inject itself into the current webpage. 47 | 48 | ```JS 49 | javascript:(function(){['https://cdn.jsdelivr.net/gh/magicoflolis/Userscript-Plus@master/userscript/dist/magic-userjs.user.js'].map(s=>document.body.appendChild(document.createElement('script')).src=s)})(); 50 | ``` 51 | 52 | ## Features 53 | 54 | * General: 55 | * UI designed for mobile and desktop devices 56 | * Multiple language support - date formats are based on your current language 57 | * Import / export config and theme 58 | * Customize theme UI 59 | * Query UserScripts and UserStyles from: 60 | * [Greasy Fork](https://greasyfork.org) - enabled by default 61 | * [Sleazy Fork](https://sleazyfork.org) - disabled by default 62 | * [GitHub](https://github.com/search?l=JavaScript&o=desc&q="==UserScript==") ( requires [Personal Access Token](https://github.com/settings/tokens), no permissions are required ) - disabled by default 63 | * Built-in UserScripts: 64 | * [GreasyFork Bullshit Filter](https://greasyfork.org/scripts/12179) - disabled by default 65 | * [Greasyfork Search with Sleazyfork Results include](https://greasyfork.org/scripts/23840) - disabled by default 66 | * Automation: 67 | * Fetch on load - query on page load 68 | * Blacklist: 69 | * Attempts to exclude certain hosts from being queried - localhost, bank, government, etc. 70 | * Menu: 71 | * Search for UserScripts - for shortcuts see [Wiki](https://github.com/magicoflolis/Userscript-Plus/blob/master/wiki/README.md#shortcuts) 72 | * Filter UserScripts which do not match your current language 73 | * Sort UserScripts, default sorting "Daily Installs" 74 | * Preview UserScripts code before install 75 | * Save UserScript as a local file 76 | * UserScript highlights: 77 | * UserScripts created by the [author](https://greasyfork.org/users/166061) - enabled by default 78 | * UserScript recommendations - enabled by default 79 | 80 | **UserScript Features:** 81 | 82 | > Tested and compatible with [TamperMonkey](https://www.tampermonkey.net/) or [ViolentMonkey](https://violentmonkey.github.io/) 83 | 84 | * General: 85 | * Maximize, minimize, or close menu 86 | * Sync config with UserScript manager or per host 87 | * Customize timeout window - can be re-injected using your managers User Script Commands menu 88 | * Query UserScripts and UserStyles from: 89 | * [Open UserJS](https://openuserjs.org) ( limited availability, will read `Too many requests...` if limit is reached ) - disabled by default 90 | * Automation: 91 | * Inject on load - injects menu on page load 92 | * Automatic fullscreen - maximizes menu when opened 93 | * UserScript Commands via `GM_registerMenuCommand`: 94 | * Inject Userscript+ - injects menu into the page 95 | * Close Userscript+ - removes menu from the page 96 | 97 | **Web Extension Features:** 98 | 99 | * General: 100 | * You can fullscreen the list and it will open it to a new tab 101 | 102 | ## Previews 103 | 104 |

105 | 106 | 107 |

108 | 109 | ## FAQ / Troubleshooting 110 | 111 | **(UserScript) How do I open the menu?:** 112 | 113 | * Click or touch the bottom right of a webpage 114 | 115 | **(UserScript) Nothing appears bottom right:** 116 | 117 | > [List of known issues by Web Browser](https://github.com/magicoflolis/Userscript-Plus/blob/master/browser-issues.md) 118 | 119 | * Try again on another webpage [[Test Page](https://youtube.com)] 120 | * Default timeout is 10000ms before the count disappears 121 | * If issue persists, see [View Console Logs](#view-console-logs) or submit a [New Issue](https://github.com/magicoflolis/Userscript-Plus/issues/new/choose) 122 | 123 | **(UserScript) Error occurred while injecting Container:** 124 | 125 | * Try again on another webpage [[Test Page](https://youtube.com)] 126 | * This error is caused by the current webpage not supporting [attachShadow](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow) 127 | 128 | **(UserScript) Error occurred while loading UserJS for this webpage:** 129 | 130 | * Reload the webpage or try again on a different webpage [[Test Page](https://youtube.com)] 131 | * This error *may* be caused by 132 | * An error occurred in an enabled search engine while fetching content 133 | * Script is unable to fetch content on current or all webpages 134 | 135 | **No available UserJS for this webpage:** 136 | 137 | * This error *can* be caused when no UserJS could be found in enabled search engines 138 | * If there are known UserJS to exist in enabled search engines, enable `Filter out other languages` 139 | 140 | ## View Console Logs 141 | 142 | * Open your web browsers Inspect Element and navigate to it's Console 143 | * Locate the following **[UserJS] < message >** ( you can filter your Console by entering **UserJS** or **[** ) 144 | * **If nothing appears, this means the script is not executing at all!** 145 | * For any additional help, submit a [New Issue](https://github.com/magicoflolis/Userscript-Plus/issues/new/choose) 146 | 147 | ## Want to Contribute? 148 | 149 | Fork this repo and open a [pull request](https://github.com/magicoflolis/Userscript-Plus/pulls) - [Rules](https://github.com/magicoflolis/Userscript-Plus/blob/master/wiki/Build.md#contribution-rules) - [Guide](https://github.com/magicoflolis/Userscript-Plus/blob/master/wiki/Build.md#contribution-guide) 150 | 151 | How to add or edit translations? How to build? - [Build](https://github.com/magicoflolis/Userscript-Plus/blob/master/wiki/Build.md) 152 | 153 | ### Roadmap 154 | 155 | * See TODO section in [UserScript Changelog](https://github.com/magicoflolis/Userscript-Plus/blob/master/CHANGELOG.user.md) 156 | * See TODO section in [Web Extension Changelog](https://github.com/magicoflolis/Userscript-Plus/releases) 157 | 158 | ### Source Code 159 | 160 | * [https://github.com/magicoflolis/Userscript-Plus](https://github.com/magicoflolis/Userscript-Plus) 161 | -------------------------------------------------------------------------------- /README.nl.md: -------------------------------------------------------------------------------- 1 | # Magic Userscript+ 2 | 3 | | Preview(s) | 4 | |:----------:| 5 | ![Demo 1](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo3.gif)| 6 | ![Demo 2](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo2.gif)| 7 | ![Demo 3](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo1.png)| 8 | 9 | ## Source Code 10 | 11 | * [GitHub](https://github.com/magicoflolis/Userscript-Plus) 12 | 13 | ### Contacts 14 | 15 | [GitHub](https://github.com/magicoflolis) 16 | 17 | [Twitter](https://twitter.com/for_lollipops) 18 | 19 | [Greasy Fork](https://greasyfork.org/users/166061) 20 | -------------------------------------------------------------------------------- /README.pl.md: -------------------------------------------------------------------------------- 1 | # Magic Userscript+ 2 | 3 | | Preview(s) | 4 | |:----------:| 5 | ![Demo 1](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo3.gif)| 6 | ![Demo 2](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo2.gif)| 7 | ![Demo 3](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo1.png)| 8 | 9 | ## Source Code 10 | 11 | * [GitHub](https://github.com/magicoflolis/Userscript-Plus) 12 | 13 | ### Contacts 14 | 15 | [GitHub](https://github.com/magicoflolis) 16 | 17 | [Twitter](https://twitter.com/for_lollipops) 18 | 19 | [Greasy Fork](https://greasyfork.org/users/166061) 20 | -------------------------------------------------------------------------------- /README.pt.md: -------------------------------------------------------------------------------- 1 | # Magic Userscript+ 2 | 3 | | Preview(s) | 4 | |:----------:| 5 | ![Demo 1](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo3.gif)| 6 | ![Demo 2](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo2.gif)| 7 | ![Demo 3](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo1.png)| 8 | 9 | ## Source Code 10 | 11 | * [GitHub](https://github.com/magicoflolis/Userscript-Plus) 12 | 13 | ### Contacts 14 | 15 | [GitHub](https://github.com/magicoflolis) 16 | 17 | [Twitter](https://twitter.com/for_lollipops) 18 | 19 | [Greasy Fork](https://greasyfork.org/users/166061) 20 | -------------------------------------------------------------------------------- /README.ru.md: -------------------------------------------------------------------------------- 1 | # Magic Userscript+ 2 | 3 | | Preview(s) | 4 | |:----------:| 5 | ![Demo 1](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo3.gif)| 6 | ![Demo 2](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo2.gif)| 7 | ![Demo 3](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo1.png)| 8 | 9 | ## Source Code 10 | 11 | * [GitHub](https://github.com/magicoflolis/Userscript-Plus) 12 | 13 | ### Contacts 14 | 15 | [GitHub](https://github.com/magicoflolis) 16 | 17 | [Twitter](https://twitter.com/for_lollipops) 18 | 19 | [Greasy Fork](https://greasyfork.org/users/166061) 20 | -------------------------------------------------------------------------------- /README.zh-tw.md: -------------------------------------------------------------------------------- 1 | # Magic Userscript+ 2 | 3 | | Preview(s) | 4 | |:----------:| 5 | ![Demo 1](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo3.gif)| 6 | ![Demo 2](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo2.gif)| 7 | ![Demo 3](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo1.png)| 8 | 9 | ## Source Code 10 | 11 | * [GitHub](https://github.com/magicoflolis/Userscript-Plus) 12 | 13 | ### Contacts 14 | 15 | [GitHub](https://github.com/magicoflolis) 16 | 17 | [Twitter](https://twitter.com/for_lollipops) 18 | 19 | [Greasy Fork](https://greasyfork.org/users/166061) 20 | -------------------------------------------------------------------------------- /README.zh.md: -------------------------------------------------------------------------------- 1 | # Magic Userscript+ 2 | 3 | | Preview(s) | 4 | |:----------:| 5 | ![Demo 1](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo3.gif)| 6 | ![Demo 2](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo2.gif)| 7 | ![Demo 3](https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/master/assets/demo1.png)| 8 | 9 | ## Source Code 10 | 11 | * [GitHub](https://github.com/magicoflolis/Userscript-Plus) 12 | 13 | ### Contacts 14 | 15 | [GitHub](https://github.com/magicoflolis) 16 | 17 | [Twitter](https://twitter.com/for_lollipops) 18 | 19 | [Greasy Fork](https://greasyfork.org/users/166061) 20 | -------------------------------------------------------------------------------- /UserJS.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "7.6.6", 3 | "name": "Magic Userscript+ : Show Site All UserJS", 4 | "description": "Finds available userscripts for the current webpage.", 5 | "author": "Magic ", 6 | "bugs": "https://github.com/magicoflolis/Userscript-Plus/issues", 7 | "homepage": "https://github.com/magicoflolis/Userscript-Plus", 8 | "icon": "./src/img/icon_64.png", 9 | "downloadURL": "https://github.com/magicoflolis/Userscript-Plus/raw/master/dist/magic-userjs.user.js", 10 | "updateURL": "https://github.com/magicoflolis/Userscript-Plus/raw/master/dist/magic-userjs.meta.js", 11 | "license": "MIT", 12 | "build": { 13 | "source": { 14 | "metadata": "./src/UserJS/header.js", 15 | "code": "./src/UserJS/main.js", 16 | "mainCSS": "./src/sass/magicuserjs.scss" 17 | }, 18 | "watch": { 19 | "files": ["main.js", "header.js", "_main.scss"], 20 | "dirs": ["src/UserJS", "src/sass"] 21 | }, 22 | "paths": { 23 | "fileName": "magic-userjs", 24 | "dir": "./dist", 25 | "i18n": { 26 | "default": "en", 27 | "dir": "../../../src/_locales" 28 | }, 29 | "dev": { 30 | "fileName": "magic-userjs.dev", 31 | "dir": "./web-server" 32 | } 33 | } 34 | }, 35 | "metadata": { 36 | "compatible": ["chrome", "firefox", "edge", "opera", "safari"], 37 | "connect": [ 38 | "greasyfork.org", 39 | "sleazyfork.org", 40 | "github.com", 41 | "githubusercontent.com", 42 | "openuserjs.org" 43 | ], 44 | "grant": [ 45 | "GM_addElement", 46 | "GM_info", 47 | "GM_getValue", 48 | "GM_openInTab", 49 | "GM_setValue", 50 | "GM_registerMenuCommand", 51 | "GM_xmlhttpRequest", 52 | "GM.addElement", 53 | "GM.info", 54 | "GM.getValue", 55 | "GM.openInTab", 56 | "GM.setValue", 57 | "GM.registerMenuCommand", 58 | "GM.xmlHttpRequest" 59 | ], 60 | "exclude": [], 61 | "include": [], 62 | "exclude-match": [], 63 | "match": ["https://*/*"], 64 | "noframes": true, 65 | "resource": {}, 66 | "require": [], 67 | "run-at": "document-start" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Userscript-Plus.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "search.exclude": { 9 | "**/.vscode/**": true, 10 | "**/build/**": true, 11 | "**/dist/**": true, 12 | "**/node_modules/**": true, 13 | "**/Notes/**": true 14 | }, 15 | "explorer.excludeGitIgnore": true, 16 | "files.exclude": { 17 | "**/.vscode/**": true, 18 | "**/build/**": true, 19 | "**/dist/**": true, 20 | "**/node_modules/**": true, 21 | "**/Notes/**": true 22 | }, 23 | "local-history.daysLimit": 3, 24 | "local-history.maxDisplay": 10, 25 | "local-history.saveDelay": 0, 26 | "local-history.dateLocale": "en-US", 27 | "local-history.exclude": [ 28 | "**/.history/**", 29 | "**/.vscode/**", 30 | "**/node_modules/**", 31 | "**/typings/**", 32 | "**/out/**", 33 | "**/Code/User/**", 34 | "**/*.code-workspace", 35 | "**/build/**", 36 | "**/dist/**", 37 | "**/Notes/**" 38 | ], 39 | "local-history.path": "${workspaceFolder}/.vscode" 40 | } 41 | } -------------------------------------------------------------------------------- /assets/install-userscript.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/0a917ca3a695c990ccc8dc06c7953cd3b9fec7eb/assets/install-userscript.gif -------------------------------------------------------------------------------- /assets/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/0a917ca3a695c990ccc8dc06c7953cd3b9fec7eb/assets/preview.png -------------------------------------------------------------------------------- /assets/step-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/0a917ca3a695c990ccc8dc06c7953cd3b9fec7eb/assets/step-1.png -------------------------------------------------------------------------------- /assets/step-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/0a917ca3a695c990ccc8dc06c7953cd3b9fec7eb/assets/step-2.png -------------------------------------------------------------------------------- /assets/step-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/0a917ca3a695c990ccc8dc06c7953cd3b9fec7eb/assets/step-3.png -------------------------------------------------------------------------------- /assets/using-tabs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/0a917ca3a695c990ccc8dc06c7953cd3b9fec7eb/assets/using-tabs.gif -------------------------------------------------------------------------------- /browser-issues.md: -------------------------------------------------------------------------------- 1 | # Known issues by Web Browser 2 | 3 | > Last updated: 5/2/2024 4 | 5 | [Submit a New Issue](https://github.com/magicoflolis/Userscript-Plus/issues/new/choose) 6 | 7 | - [Known issues by Web Browser](#known-issues-by-web-browser) 8 | - [Kiwi Browser](#kiwi-browser) 9 | - [Opera](#opera) 10 | 11 | ## Kiwi Browser 12 | 13 | **Installed using (from .zip/.crx/.user.js):** 14 | 15 | - Userscript version does not have access to Privileged APIs (`GM_*` or `GM.*`) 16 | - Recommended to install using a user script manager _[Violentmonkey](https://violentmonkey.github.io/) or [Tampermonkey](https://www.tampermonkey.net/)_ 17 | 18 | ## Opera 19 | 20 | **General:** 21 | 22 | - Cannot load on search engine sites (common example: [google.com](https://www.google.com)) 23 | - As stated using ViolentMonkey: _Violentmonkey cannot run userscripts in this page (common examples: browser UI, an extension, blocked via policies, a search engine site in Opera)_ 24 | -------------------------------------------------------------------------------- /dist/magic-userjs.meta.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @version 7.6.6 3 | // @name Magic Userscript+ : Show Site All UserJS 4 | // @name:ar Magic Userscript+: عرض جميع ملفات UserJS 5 | // @name:de Magic Userscript+ : Website anzeigen Alle UserJS 6 | // @name:es Magic Userscript+: Mostrar sitio todos los UserJS 7 | // @name:fr Magic Userscript+ : Afficher le site Tous les UserJS 8 | // @name:ja Magic Userscript+ : サイトをすべて表示 UserJS 9 | // @name:nl Magic Userscript+: Site alle UserJS tonen 10 | // @name:pl Magic Userscript+ : Pokaż witrynę Wszystkie UserJS 11 | // @name:ru Magic Userscript+: показать сайт всем UserJS 12 | // @name:zh Magic Userscript+ :显示站点所有 UserJS 13 | // @name:zh-CN Magic Userscript+ :显示站点所有 UserJS 14 | // @name:zh-TW Magic Userscript+ :显示站点所有 UserJS 15 | // @description Finds available userscripts for the current webpage. 16 | // @description:ar يبحث عن نصوص المستخدمين المتاحة لصفحة الويب الحالية. 17 | // @description:de Findet verfügbare Benutzerskripte für die aktuelle Webseite. 18 | // @description:es Busca los usercripts disponibles para la página web actual. 19 | // @description:fr Recherche les userscripts disponibles pour la page web en cours. 20 | // @description:ja 現在のウェブページで利用可能なユーザスクリプトを検索します。 21 | // @description:nl Zoekt beschikbare gebruikerscripts voor de huidige webpagina. 22 | // @description:pl Wyszukuje dostępne skrypty użytkownika dla bieżącej strony internetowej. 23 | // @description:ru Находит доступные юзерскрипты для текущей веб-страницы. 24 | // @description:zh 为当前网页查找可用的用户脚本。 25 | // @description:zh-CN 为当前网页查找可用的用户脚本。 26 | // @description:zh-TW 为当前网页查找可用的用户脚本。 27 | // @author Magic 28 | // @supportURL https://github.com/magicoflolis/Userscript-Plus/issues 29 | // @namespace https://github.com/magicoflolis/Userscript-Plus 30 | // @homepageURL https://github.com/magicoflolis/Userscript-Plus 31 | // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV8TRZFKBzuIOGSoTnZREcGlVLEIFkpboVUHk0u/oElDkuLiKLgWHPxYrDq4OOvq4CoIgh8gzg5Oii5S4v+SQosYD4778e7e4+4dIDSrTLN6YoCm22Y6EZdy+VWp7xUigghhDqLMLCOZWczCd3zdI8DXuyjP8j/35xhUCxYDAhJxjBmmTbxBPLNpG5z3icOsLKvE58QTJl2Q+JHrisdvnEsuCzwzbGbT88RhYqnUxUoXs7KpEU8TR1RNp3wh57HKeYuzVq2z9j35C4MFfSXDdZqjSGAJSaQgQUEdFVRhI0qrToqFNO3Hffwjrj9FLoVcFTByLKAGDbLrB/+D391axalJLykYB3pfHOdjDOjbBVoNx/k+dpzWCSA+A1d6x19rArOfpDc6WuQICG0DF9cdTdkDLneA4SdDNmVXEmkKxSLwfkbflAeGboGBNa+39j5OH4AsdbV8AxwcAuMlyl73eXd/d2//nmn39wOjunK6jS33SAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+gDDBAAJWyXgRAAABRPSURBVHjazVt9VNRl9v9854VhhjdBiHcUaFQEDTVCdk1IstTV0jb1LLZkJZrl4knLTnZaKjdqdXdLXcvfyoHUErPUBBEMLREJ6egJFBUUBF9WMd6R4WW+M/P5/eHMdxkYEJS0e8498MzzzPN87537PM+9n3u/An49kgFwA+AJIACAP4AxAEIA+AFwBzDEPLYJQB2AqwDOATgN4AqAywBubNu2rTk+Pl4EAJIyABAEwTQYDyn8CoIrAEQD+BOAMLPgLgAcAGDs2LGQyWSora1FYGAg6urqUFZWBgCIiYnBqVOn0NDQAAA6T0/Pm/X19ZflcvkZo9G4w2AwHAFgwG+Q5ACCg4KClixZsuT82rVrTTt27CCAHpybm8vq6moWFRXRZDKxpqaGkyZN4q0fl3zyySelsaIoMiYmhg899BD/9re/mQAUAogHEGxe8/5TaGiob0RERMrSpUuvZ2Zmmo4cOcIjR44wLy/PpgIOHjzI2tpaTp06lVqtlhUVFUxLS5MU8MQTT0hjLQqZPHkySRIAFy1aZAwNDa0C8C/z9rpvNBbAPwBctyVob3zw4EEWFBRQJpNJFvH999/3WwE5OTncs2ePZUwVgDjzWXPHB9Wd0GsACuRy+UoXFxev5cuXo7OzEyStWBBsHzGiKMJkunWGmUwmyGQy6X+lUgkAcHBw6HXxoUOHWv4dDiAFwNcAJtzpgTUQUgNYIgjCP2bNmiX7+9//jo0bN2Lz5s3IyspCUFCQJIz5xB7Q5FVVVZg9ezby8vKwYsWKgTzTFACZAFYC+BZA+6ArQKlUOouiuAZAgiAIMq1WixEjRuCjjz6Cq6srPvzwQ1RUVNze5GQyKyV1bW/atAnJycmIj49Henr6/66qLpbUdbzRaMS0adOQm5sLAN4A/g/ARADvAGgZvCNeLvdUq9VbzVcQAXDo0KHcs2cPDQYD29ramJiYSKVSedszICIigpGRkTbb9vb2nDRpEmNiYujk5MQZM2bQ3d2dbm5unDFjhtX4qKgonj59mt7e3t3XEAH82+xnDAp5AThjSxgPDw9++eWXNJlMbGpq4htvvCEdbr82r1ixgr/73e966zeaz4W7VoIzgAyZTMaoqChOnTqVarW6hxIyMzNpMBio0+m4ZMmSflnCPWCj2RKc71R4NYD1AIwODg4sKipiU1MTV69e3UNAT09Pfv311yTJxsZGLl++/J5Zwm1YNMugvhMFxAFoA0CFQsH33nuPHR0dbGtr49KlS6lSqawW8/LyYnZ2Ng0GA2/evMkXXniBCoXit6CEFrNbPiB6SalUtncVwNnZmcnJySTJlpYWvv7665TL5VaL+fj4cN++fSTJ+vp6vvzyy31agrOzc1/7eMAcFRXFIUOG2Oq7NhA/YaRcLr85bNgwuri4WE2kUqn48ccfs7OzkzqdjgkJCT0swdfXl7m5uTQajWxubuaCBQskRYWGhjIrK4uCIBAAY2JiSFJq3y2T5PTp03vrP9wfj1EA8PkjjzzC8+fPMysriz4+PlYTubi4cN26dSTJ5uZmm/vdz8+PBw4cIEnW1tbyxRdfpCAIDA4OpsFgsBo7WML3Y64287buk7x9fX07oqOj2dLSQpPJxMzMzB5KUKvV3LRpE/V6PW/evMmFCxfSzs7Oaoy/vz9/+OEHGo1GNjY2cu7cudRoNExNTeWePXuYkZHBsLAw7t+/n4IgUBAEPv/889y+fTu/+OILvvDCC5JAGRkZjIiI4OrVq/nll19aRYxdOSMjg+PHjycAJiUl8cMPP+TMmTO5Y8cOzpo1yxI7ePYV0v5rzpw51Gg0/Mtf/kKdTkeDwcCcnBx6enpaLebq6sr169dLJ7+t/a7Vann+/HmS5I0bN7hgwQK6urpy1apVLCwslLaASqViSkoKW1tbWVJSwuLiYt68eZNpaWlUKpUkyR9//JE///wzL126xOrqagYFBdncAhblZGdns66ujoWFhTx16hQ7Ozvp7+9PcxRpM5QOBlBu2cdOTk5877332NraSpPJxN27d9PLy8tqQQcHB27ZsoWiKLKlpYULFiyQrkhHR0e+++671Ov1tFB9fT1nz54tKSo6OpokOWbMGDY0NPC1116jWq2mvb09ly9fzsbGRoaHh5MkP/nkE9rb23PMmDESTnA7BVy4cIHBwcF0cXHhTz/9xH/+8580yxhsSwHx5nuT0dHRTEpKokql4sqVK9ne3k5RFLl//356eHhYLerm5sbPPvtMEvDFF1+kvb09165dy/b2dnan69ev86mnnqIgCJJwlnC366Hr7OxMk8nE2NhYkpRcYVvASW8KyM/Pl/qys7OZk5Nj8Q3ibQVFhZbBjz32GHU6HQFQqVQyOTmZOp2ORqOR6enpPZTg5OTErVu30mAwsKmpiSkpKezs7GRvdO7cOXp7e9PHx4cGg4GRkZHs7Ozkww8/LM05fvx4dnR0cNKkST0E7q8Cjh49aqUAC0gjCEJh90Aw1uw6SgqwABAWc37rrbfY0dFBURS5d+9eurm5WS3u7u7OlJQU9of0ej2fffZZOjk5UafT0dPTkydOnGBpaSmfeeYZzpkzh6dOnWJxcTG9vLwGTQGWtqOjo9EsM2Rm/lNXcKQ7kOHs7IwNGzZg48aNEEURTz31FD7++OOuwATa2tpw/vx5GAy3xyyVSiV8fX1hMplgMplw48YNvPTSS7h48SJ27NiB9PR0XL58GQsXLkRNTY3t+7oXsMXyeV9h9/z5861kdgdwvPt92tULtFxTarWaSUlJ1Ov11Ov13LVrF4cMGUKlUsl33nmHOp2uXxYgiiLnz59Pi5ttWUcmk1GhUFChUFjdKAqFwuqO79629blcLrfyVLu2zX+PW6LFUAD/7a+zoVKp+Mknn7C9vZ0Gg4Fbtmzh66+/zo6ODvaXzp07R19f3/sdI/zXLDumA2gdyJcdHByYnJxMURSp1+v7PPC6k8lk4ubNm38LIXMrgOlyM572zEAiJVEUUVBQAHd3d4wbNw4qlar/mRhBwKhRo3DlyhWcPXtWAkfvA9kBOCoHsBDAIwP9tsFgwNGjR6HRaBAZGWl14Nx2ZTs7PProo6irq0NxcfGAwdNBpEsAcOhOzSgoKIhqtZr/+c9/BnQGWKilpYVxcXE2cYO3336b77777q+9DQ4BQNmdfDk0NJS5ubm0s7Ojs7Mz169fT6PRaHPP90W//PIL4+Pje8QRmzZtYkpKyqAIGh0dzRMnTrC5uZm7du2in5+fpa8M5qzsgCaUyWRMT0+3+NbS7fDBBx+wvLyc9fX1rK2t5cmTJ/nSSy/xq6++oiiKvSqhqamJc+fO/VUQpMDAQNbW1jI9PZ0JCQksLy/nd999R3t7ewKow7fffmuqq6tjWlqapBlRFBkfH8+srCzJJe4e6lZUVEggaVlZGbVaLRUKBYOCgjhp0iRGRkbS29ubgiDQ3d2d27dv79MSampqOH/+fOke379/Pw8fPmxz24WHh9vkBx98sMf41atXs7KykgEBAQTAWbNmsba2lsHBwQRgwLx584wrVqxgdXU18/PzOXToUJJkVVUVk5KSGBcX12PShx9+WHJhAXDt2rVWgYeDgwMfe+wxenh48NVXX6W/vz/d3Ny4Z8+ePi2hsbGRs2fPplwuZ0JCAg8dOtRj7cOHD/f6/cLCwh7j09PTWVhYKDlBTk5OFEWR4eHhtKTa6yzwVFtbGydOnEiSfOutt3pFWCzRm+UunzBhAltaWjh69GgC4LJly5iXl0dBEOjm5sbg4GA6OTnxgQce4M6dO/u0hGvXrtGCSSQkJPRYWy6XS95id+6OUQJgZmam1Y8jCAJJWhRQpzArYOjJkyehVquhVt9CkH/++ederyeDwQCDwQCVSgVRFFFWVobS0lIkJCRg5cqVWLFiBdasWQMPDw9s2bIFTzzxBGpraxEXF4dly5ZBo9FgxowZkMt74hLe3t5ISUnB888/j23btvXof+ONNzB27Fibz1VWVob333/f6rOGhgZ4enrCzs4Oer0e7u7u0Ov1aGtrA4A6mUKh+K9SqcTTTz+NlpYWNDc3S0L2Ro2NjWhoaEBQUBAAQKfTYdeuXZgyZQoWL14MOzs7HDt2DFqtFjNnzsTcuXMRFhYGPz8/hIaGYvHixdi3b1+v87u5uWHz5s2IjY3t0Tds2DCEhoba5MDAwB7ji4qK4OPjg5CQEMhkMsyZMwcNDQ1obGwEgKtITEzcn5mZyaamJn7zzTfUaDQkyccff7zXk9XR0ZGFhYVctmyZ9NmQIUN45coV3rhxg/v27aNcLmd4eDibm5uZm5vLvXv3sqCggBcvXpSSKdnZ2TavTgvV1tbyySefvKski6urK8vLy1lSUsLdu3ezvr6eqampljk3YsGCBe/n5+fzzTfflCDu0tJSTpw4sc+Jly5dypycHKt9l5SUxNLSUgmalsvlXLp0KY8dO8ZPP/2U0dHRPHPmDAEwODiYPj4+zMjI6PNMuHTpUp8/Rn94+PDh/OKLL1hUVMQ1a9ZIcsrl8iV3FAxZMrm7d+9mWFjYXT2ct7e3lEfo64qMjY0d7HRbq6+v79MDDod7i7/vhv39/Zmdnd2nJVRVVXHy5MmDGg4LghBqExC5H+zv788jR4706Tpfu3aN0dHRg2UJEiAiM9fZ3PdE5vDhw5mbm9unJVRUVDAqKmow1kvpCgNagaL3WwnHjh3r0xKuXLnC3//+93ez/SRQ1AoWDwwM5PDhwwc84eTJkymKIgEwICCAw4YNs+rXaDSSlwiAo0ePpoODQ6/toKAgHjlypE9LKC8vt4LRB8g9YHFoNJqF5eXlpr/+9a8DntCS4rL46l1dz64wu+UX6w/MrdVqefz48T4tobq6mpGRkQO1BKvEiKwLrJ2/bt26ms8++wwAEBAQgGHDhsHNzQ0jRoyAp+etnKK7uztGjBgBb29vm16c0Wjs4UVaYC+S8Pf3BwD4+fkhJCQEo0aNAgD4+/sjJCQEDg4OCAwMRGVlJf785z/jxx9/7NVjDAgIQFpamuQay2Qy+Pj4YOTIkdBqtXB1dZVgOA8PD/j6+kIQhIsACmwmR11cXDZYNPX999+zsrJSMsWysjJOmTKFRUVF0rVkMeuuFvDdd99JlZ8WtmR3ANzWtKdNm0aSfPPNNxkUFMSQkBCeOHHitgfjhAkTqFarmZKSQpPJRKPRyIKCAoaEhDA0NJR5eXkcNWpUn8lRmFPHVZYS1oaGBs6ZM4djx45ldXU1q6urrdobN260qQCj0cj29naJLaixIAi0s7MjSc6cOVNKhHZty2QykuSZM2c4cuRIqlQqjho1isePH+8TaT59+jRHjx5NjUZDZ2dnhoSEMD8/n3v37uVXX33FxMREm+nx7oWSNwC8bb4i1GfPnkVGRgaMRiMqKythb29v1X7wwQdtavHChQv46KOPJKB05MiRWLVqFQBAr9dLf9vb/1fQ2b29bt06lJeXAwCuXr2KxYsXY9u2bXjooYdsIs1hYWHYu3cv5s2bh6qqKjz99NPo6OjA1KlTcfHiRSQkJLSbZbtxu1rhHACFDQ0N0Ov1MBqN0j7u2jYajbC3t7epgOvXr+Pzzz9HamoqUlNTceDAgQHDtR0dHdL/EyZMQGtrK5577jmcPHmy1+9otVps2bIFSqUSxcXF8PLywoULF/D++++jpaWl0CzbbYulGwCsysrKaureMVjwdUdHBwICAnptA8DLL78MX19feHt7IykpCYmJiaioqMCSJUtQWlraa84hIiICr732GnJychAbG4tFixbhm2++uQ5glVm2flWLn9y5c+erhw8f7uwtIdlbctJWX/f2gQMHsGHDBnR0dCA2NrZHGwBu3ryJkpISVFZWIiwsDPv378fIkSNRX1+P5557DiUlJb2uHx0dDZlMhk2bNuHSpUut5iLqkwMulHR0dPy3pWhixowZ/MMf/iCd7NOnT5fa3t7eXLx4cY/Pu0Z8ln4AfOCBBzhv3jwmJCTQz8/Pqu3r6yv5BY8//jgXLlzIRx55hDKZjF5eXlKhxMSJE3nu3Dmbh+LWrVspCAI1Gs1dFUpaSmX/fa/d5O4vTvTG48ePZ3FxsZXwlZWVluvOBODTuymVtZC7ufDYeC8VMG3atH6NHTFiBNPS0piXl8ddu3Z19QwPDGbFuLvZEsR7oQCNRmMT4e2NlUolhwwZYkl2mABsHUzhu26H9ebaW/5GWXe3FeL9qSD/k7n29p4K5+Pjc7uiimvmZ1PjHtAEc+1t271SwKFDh1hQUGAVLU6dOpX+/v7t5me5Jy9NSX4CgLkApgH4ALfe3vpVyWAwQBRFAMCUKVNgNBrxww8/VBsMhrfNHl7DHb0OdBfP1I5b7/juBCAPDg72GjdunLOXl5csMTERoaGhOH36NERRhEajQVxcHOLj4zF58mSIoojLly9Dq9UiOTkZJ06cwMqVK/Hoo4/i7NmzaGtrg4ODA1555RU8++yz0Ov1GDduHI4ePYqSkhJDfX191blz57aaTKYXzNheO+4zyZVK5YMajWahq6vrT4sWLTLl5eUxNTWVLi4uzMnJYW1tLauqqnj58mXW1dVx1qxZUqlsVlYWq6qq2Nrays2bN1OlUnH79u2sqqriwYMH+corr9DLy8uoUCiOC4Lw23p1tpdtFatSqVLHjx9fEhUV9curr77aYYHaVCoVk5KSWFRUxJiYGOr1es6bN49yuZxxcXFcv349PTw8+Mc//rHd09PzuvkXTjFjeIrBflihl/2mkMlkTgAUwi1HXiDZaPbpXVtbW39xcnKiOUByNcf6wi23X2gAgPj4eHl4ePiI4uJij5qaGv+Wlhbftra2EL1erzWZTN4k3a5everk4+ODixcvNpGs02g01wIDA6uvXr16IiIioik/P/9UZ2dnjZ+fX8OVK1dczOuZBEGQ63S6RkdHR5vRGUlPk8nUJJfLOy1tAI2CIOi7j/1/l0eTL0xHMHkAAAAASUVORK5CYII= 32 | // @downloadURL https://github.com/magicoflolis/Userscript-Plus/raw/master/dist/magic-userjs.user.js 33 | // @updateURL https://github.com/magicoflolis/Userscript-Plus/raw/master/dist/magic-userjs.meta.js 34 | // @license MIT 35 | // @compatible chrome 36 | // @compatible firefox 37 | // @compatible edge 38 | // @compatible opera 39 | // @compatible safari 40 | // @connect greasyfork.org 41 | // @connect sleazyfork.org 42 | // @connect github.com 43 | // @connect githubusercontent.com 44 | // @connect openuserjs.org 45 | // @grant GM_addElement 46 | // @grant GM_info 47 | // @grant GM_getValue 48 | // @grant GM_openInTab 49 | // @grant GM_setValue 50 | // @grant GM_registerMenuCommand 51 | // @grant GM_xmlhttpRequest 52 | // @grant GM.addElement 53 | // @grant GM.info 54 | // @grant GM.getValue 55 | // @grant GM.openInTab 56 | // @grant GM.setValue 57 | // @grant GM.registerMenuCommand 58 | // @grant GM.xmlHttpRequest 59 | // @match https://*/* 60 | // @noframes 61 | // @run-at document-start 62 | // ==/UserScript== -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'eslint/config'; 2 | 3 | import globals from 'globals'; 4 | import pluginJs from '@eslint/js'; 5 | import eslintConfigPrettier from 'eslint-config-prettier/flat'; 6 | 7 | export default defineConfig([ 8 | pluginJs.configs.recommended, 9 | eslintConfigPrettier, 10 | { 11 | rules: { 12 | 'no-var': 'error', 13 | 'prefer-const': ['error', { destructuring: 'all' }], 14 | 'prefer-promise-reject-errors': 'error', 15 | 'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }], 16 | quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: false }], 17 | 'space-before-blocks': ['error', 'always'] 18 | }, 19 | languageOptions: { 20 | ecmaVersion: 'latest', 21 | sourceType: 'module', 22 | globals: globals.es2024 23 | } 24 | }, 25 | { 26 | files: ['src/js/*.js'], 27 | languageOptions: { 28 | globals: { 29 | ...globals.browser, 30 | ...globals.webextensions 31 | } 32 | } 33 | }, 34 | { 35 | files: ['src/UserJS/main.js'], 36 | languageOptions: { 37 | sourceType: 'script', 38 | globals: { 39 | main_css: 'readonly', 40 | translations: 'writable', 41 | userjs: 'writable', 42 | ...globals.browser, 43 | ...globals.greasemonkey 44 | } 45 | } 46 | }, 47 | { 48 | files: ['src/UserJS/header.js'], 49 | languageOptions: { 50 | sourceType: 'script', 51 | globals: { 52 | code: 'readonly', 53 | metadata: 'readonly', 54 | languageList: 'readonly', 55 | ...globals.browser 56 | } 57 | }, 58 | rules: { 59 | quotes: 'off', 60 | 'no-unused-vars': 'off' 61 | } 62 | }, 63 | { 64 | files: ['tools/*.js', 'utils/**/*.js'], 65 | languageOptions: { 66 | globals: globals.node 67 | } 68 | } 69 | ]); 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "userscript-plus", 3 | "description": "Finds available userscripts for the current webpage.", 4 | "author": "Magic ", 5 | "version": "6.0.0", 6 | "license": "MIT", 7 | "homepage": "https://github.com/magicoflolis/Userscript-Plus", 8 | "bugs": { 9 | "url": "https://github.com/magicoflolis/Userscript-Plus/issues" 10 | }, 11 | "type": "module", 12 | "scripts": { 13 | "dev:UserJS": "concurrently \"dotenvx run -f .env --quiet -- node ./tools/userscript.js\" \"http-server ./web-server -p 9090 -s --no-dotfiles -c-1\"", 14 | "dev:FF": "concurrently \"sass --embed-sources -s expanded -w ./src/sass:./build/firefox/css\" \"pnpm run webpack:FF\"", 15 | "dev:Cr": "concurrently \"sass --embed-sources -s expanded -w ./src/sass:./build/chrome/css\" \"pnpm run webpack:Cr\"", 16 | "pub:UserJS": "dotenvx run --env JS_ENV=production -f .env --quiet -- node ./tools/userscript.js", 17 | "webpack:Cr": "webpack --progress --mode development --config=tools/webpack.config.js --env brws=chrome", 18 | "webpack:FF": "webpack --progress --mode development --config=tools/webpack.config.js --env brws=firefox", 19 | "web-ext:run": "web-ext run --config=./tools/web-ext.mjs", 20 | "web-ext:android": "web-ext run --config=./tools/web-ext.mjs -t firefox-android --firefox-apk org.mozilla.firefox" 21 | }, 22 | "devDependencies": { 23 | "@dotenvx/dotenvx": "^1.39.0", 24 | "@eslint/js": "^9.22.0", 25 | "@swc/cli": "^0.6.0", 26 | "@swc/core": "^1.11.11", 27 | "@types/chrome": "0.0.309", 28 | "@types/eslint-config-prettier": "^6.11.3", 29 | "@types/firefox-webext-browser": "120.0.4", 30 | "@types/greasemonkey": "4.0.7", 31 | "@types/node": "^22.13.10", 32 | "@types/tampermonkey": "^5.0.4", 33 | "@types/trusted-types": "^2.0.7", 34 | "@types/webpack": "^5.28.5", 35 | "@violentmonkey/types": "0.2.0", 36 | "browserslist": "^4.24.4", 37 | "concurrently": "^9.1.2", 38 | "copy-webpack-plugin": "^13.0.0", 39 | "crx3": "^1.1.3", 40 | "css-loader": "^7.1.2", 41 | "eslint": "^9.22.0", 42 | "eslint-config-prettier": "^10.1.1", 43 | "globals": "^16.0.0", 44 | "http-server": "^14.1.1", 45 | "mini-css-extract-plugin": "^2.9.2", 46 | "path-browserify": "^1.0.1", 47 | "prettier": "^3.5.3", 48 | "sass-embedded": "^1.86.0", 49 | "sass-loader": "^16.0.5", 50 | "swc-loader": "^0.2.6", 51 | "terser-webpack-plugin": "^5.3.14", 52 | "typescript": "^5.8.2", 53 | "user.js": "workspace:^", 54 | "web-ext": "^8.5.0", 55 | "webpack": "^5.98.0", 56 | "webpack-cli": "^6.0.1", 57 | "webpack-merge": "^6.0.1" 58 | }, 59 | "repository": { 60 | "type": "git", 61 | "url": "git+https://github.com/magicoflolis/Userscript-Plus.git" 62 | }, 63 | "private": true, 64 | "keywords": [ 65 | "magicuserscriptplus", 66 | "userscript", 67 | "userjs", 68 | "userscript", 69 | "greasemonkey", 70 | "tampermonkey", 71 | "violentmonkey" 72 | ], 73 | "webExt": { 74 | "artifactsDir": "./build", 75 | "build": { 76 | "overwriteDest": true 77 | }, 78 | "sourceDir": "./build/firefox" 79 | }, 80 | "engines": { 81 | "node": ">=20", 82 | "pnpm": ">=10" 83 | }, 84 | "packageManager": "pnpm@10.3.0+sha512.ee592eda8815a8a293c206bb0917c4bb0ff274c50def7cbc17be05ec641fc2d1b02490ce660061356bd0d126a4d7eb2ec8830e6959fb8a447571c631d5a2442d", 85 | "pnpm": { 86 | "onlyBuiltDependencies": [ 87 | "@swc/core", 88 | "spawn-sync" 89 | ] 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'utils/*' 3 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | 2 | /** @type {import("prettier").Config} */ 3 | const config = { 4 | singleQuote: true, 5 | tabWidth: 2, 6 | printWidth: 100, 7 | trailingComma: 'none' 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /src/UserJS/header.js: -------------------------------------------------------------------------------- 1 | [[metadata]] 2 | (() => { 3 | 'use strict'; 4 | /******************************************************************************/ 5 | const inIframe = (() => { 6 | try { 7 | return window.self !== window.top; 8 | } catch (e) { 9 | return true; 10 | } 11 | })(); 12 | if (inIframe) { 13 | return; 14 | } 15 | let userjs = self.userjs; 16 | /** 17 | * Skip text/plain documents, based on uBlock Origin `vapi.js` file 18 | * 19 | * [source code](https://github.com/gorhill/uBlock/blob/68962453ff6eec7ff109615a738beb8699b9844a/platform/common/vapi.js#L35) 20 | */ 21 | if ( 22 | (document instanceof Document || 23 | (document instanceof XMLDocument && document.createElement('div') instanceof HTMLDivElement)) && 24 | /^text\/html|^application\/(xhtml|xml)/.test(document.contentType || '') === true && 25 | (self.userjs instanceof Object === false || userjs.UserJS !== true) 26 | ) { 27 | userjs = self.userjs = { UserJS: true }; 28 | } else { 29 | console.error('[%cMagic Userscript+%c] %cERROR','color: rgb(29, 155, 240);','','color: rgb(249, 24, 128);', `MIME type is not a document, got "${document.contentType || ''}"`); 30 | } 31 | if (!(typeof userjs === 'object' && userjs.UserJS)) { 32 | return; 33 | } 34 | /** Native implementation exists */ 35 | if (window.trustedTypes && window.trustedTypes.createPolicy) window.trustedTypes.createPolicy('default', { createHTML: (string) => string }); 36 | /** [i18n directory](https://github.com/magicoflolis/Userscript-Plus/tree/master/src/_locales) */ 37 | const translations = [[languageList]]; 38 | /** [source code](https://github.com/magicoflolis/Userscript-Plus/blob/master/src/sass/_main.scss) */ 39 | const main_css = `[[mainCSS]]`; 40 | /******************************************************************************/ 41 | [[code]] 42 | })(); 43 | -------------------------------------------------------------------------------- /src/_locales/ar/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "نص المستخدم السحري +", 4 | "description": "Must not exceed 45 characters" 5 | }, 6 | "extDesc": { 7 | "message": "العثور على UserJS المتاح لصفحة الويب الحالية، وقوة Greasy Fork أثناء التنقل!", 8 | "description": "Must not exceed 132 characters" 9 | }, 10 | "extShortDesc": { 11 | "message": "قوة Greasy Fork أثناء التنقل!", 12 | "description": "Must not exceed 112 characters" 13 | }, 14 | "userjs_name": { 15 | "message": "Magic Userscript+: عرض جميع ملفات UserJS" 16 | }, 17 | "userjs_description": { 18 | "message": "يبحث عن نصوص المستخدمين المتاحة لصفحة الويب الحالية." 19 | }, 20 | "createdby": { 21 | "message": "انشأ من قبل" 22 | }, 23 | "name": { 24 | "message": "اسم" 25 | }, 26 | "daily_installs": { 27 | "message": "التثبيت اليومي" 28 | }, 29 | "close": { 30 | "message": "يغلق" 31 | }, 32 | "filterA": { 33 | "message": "منقي" 34 | }, 35 | "max": { 36 | "message": "تحقيق أقصى قدر" 37 | }, 38 | "min": { 39 | "message": "تصغير" 40 | }, 41 | "search": { 42 | "message": "يبحث" 43 | }, 44 | "search_placeholder": { 45 | "message": "بحث في البرامج النصية" 46 | }, 47 | "install": { 48 | "message": "تثبيت" 49 | }, 50 | "issue": { 51 | "message": "إصدار جديد" 52 | }, 53 | "version_number": { 54 | "message": "الإصدار" 55 | }, 56 | "updated": { 57 | "message": "آخر تحديث" 58 | }, 59 | "total_installs": { 60 | "message": "إجمالي التثبيت" 61 | }, 62 | "ratings": { 63 | "message": "التقييمات" 64 | }, 65 | "good": { 66 | "message": "جيد" 67 | }, 68 | "ok": { 69 | "message": "جيد" 70 | }, 71 | "bad": { 72 | "message": "سيء" 73 | }, 74 | "created_date": { 75 | "message": "تم إنشاؤه" 76 | }, 77 | "redirect": { 78 | "message": "شوكة دهنية للكبار" 79 | }, 80 | "filter": { 81 | "message": "تصفية اللغات الأخرى" 82 | }, 83 | "dtime": { 84 | "message": "عرض المهلة" 85 | }, 86 | "save": { 87 | "message": "حفظ" 88 | }, 89 | "reset": { 90 | "message": "إعادة تعيين" 91 | }, 92 | "preview_code": { 93 | "message": "كود المعاينة" 94 | }, 95 | "saveFile": { 96 | "message": "احفظ الملف" 97 | }, 98 | "newTab": { 99 | "message": "علامة تبويب جديدة" 100 | }, 101 | "applies_to": { 102 | "message": "ينطبق على" 103 | }, 104 | "license": { 105 | "message": "الترخيص" 106 | }, 107 | "no_license": { 108 | "message": "لا يوجد" 109 | }, 110 | "antifeatures": { 111 | "message": "إعلانات" 112 | }, 113 | "userjs_fullscreen": { 114 | "message": "ملء الشاشة الكاملة التلقائي" 115 | }, 116 | "listing_none": { 117 | "message": "(لا يوجد)" 118 | }, 119 | "export_config": { 120 | "message": "تهيئة التصدير" 121 | }, 122 | "export_theme": { 123 | "message": "تصدير السمة" 124 | }, 125 | "import_config": { 126 | "message": "استيراد تهيئة الاستيراد" 127 | }, 128 | "import_theme": { 129 | "message": "استيراد النسق" 130 | }, 131 | "code_size": { 132 | "message": "حجم الرمز" 133 | }, 134 | "prmpt_css": { 135 | "message": "التثبيت كأسلوب المستخدم؟" 136 | }, 137 | "userjs_inject": { 138 | "message": "حقن Userscript+" 139 | }, 140 | "userjs_close": { 141 | "message": "إغلاق Userscript+" 142 | }, 143 | "userjs_sync": { 144 | "message": "Sync" 145 | }, 146 | "userjs_autoinject": { 147 | "message": "Inject on load" 148 | }, 149 | "auto_fetch": { 150 | "message": "Fetch on load" 151 | }, 152 | "code": { 153 | "message": "Code" 154 | }, 155 | "metadata": { 156 | "message": "Metadata" 157 | }, 158 | "preview_metadata": { 159 | "message": "Preview Metadata" 160 | }, 161 | "recommend_author": { 162 | "message": "Recommend Author" 163 | }, 164 | "recommend_other": { 165 | "message": "Recommend Others" 166 | }, 167 | "default_sort": { 168 | "message": "Default Sort" 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/_locales/de/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "Magic Userscript+", 4 | "description": "Must not exceed 45 characters" 5 | }, 6 | "extDesc": { 7 | "message": "Finds available UserJS for the current webpage, the power of Greasy Fork on the go!", 8 | "description": "Must not exceed 132 characters" 9 | }, 10 | "extShortDesc": { 11 | "message": "The power of Greasy Fork on the go!", 12 | "description": "Must not exceed 112 characters" 13 | }, 14 | "userjs_name": { 15 | "message": "Magic Userscript+ : Website anzeigen Alle UserJS" 16 | }, 17 | "userjs_description": { 18 | "message": "Findet verfügbare Benutzerskripte für die aktuelle Webseite." 19 | }, 20 | "createdby": { 21 | "message": "Erstellt von" 22 | }, 23 | "name": { 24 | "message": "Name" 25 | }, 26 | "daily_installs": { 27 | "message": "Tägliche Installationen" 28 | }, 29 | "close": { 30 | "message": "Schließen Sie" 31 | }, 32 | "filterA": { 33 | "message": "Filter" 34 | }, 35 | "max": { 36 | "message": "Maximieren Sie" 37 | }, 38 | "min": { 39 | "message": "minimieren" 40 | }, 41 | "search": { 42 | "message": "Suche" 43 | }, 44 | "search_placeholder": { 45 | "message": "Suche nach Userscripts" 46 | }, 47 | "install": { 48 | "message": "Installieren Sie" 49 | }, 50 | "issue": { 51 | "message": "Neue Ausgabe" 52 | }, 53 | "version_number": { 54 | "message": "Version" 55 | }, 56 | "updated": { 57 | "message": "Zuletzt aktualisiert" 58 | }, 59 | "total_installs": { 60 | "message": "Installationen insgesamt" 61 | }, 62 | "ratings": { 63 | "message": "Bewertungen" 64 | }, 65 | "good": { 66 | "message": "Gut" 67 | }, 68 | "ok": { 69 | "message": "Okay" 70 | }, 71 | "bad": { 72 | "message": "Schlecht" 73 | }, 74 | "created_date": { 75 | "message": "Erstellt" 76 | }, 77 | "redirect": { 78 | "message": "Greasy Fork für Erwachsene" 79 | }, 80 | "filter": { 81 | "message": "Andere Sprachen herausfiltern" 82 | }, 83 | "dtime": { 84 | "message": "Zeitüberschreitung anzeigen" 85 | }, 86 | "save": { 87 | "message": "Speichern Sie" 88 | }, 89 | "reset": { 90 | "message": "Zurücksetzen" 91 | }, 92 | "preview_code": { 93 | "message": "Vorschau Code" 94 | }, 95 | "saveFile": { 96 | "message": "Datei speichern" 97 | }, 98 | "newTab": { 99 | "message": "Neue Registerkarte" 100 | }, 101 | "applies_to": { 102 | "message": "Gilt für" 103 | }, 104 | "license": { 105 | "message": "Lizenz" 106 | }, 107 | "no_license": { 108 | "message": "N/A" 109 | }, 110 | "antifeatures": { 111 | "message": "Antifeatures" 112 | }, 113 | "userjs_fullscreen": { 114 | "message": "Automatischer Vollbildmodus" 115 | }, 116 | "listing_none": { 117 | "message": "(Keine)" 118 | }, 119 | "export_config": { 120 | "message": "Konfig exportieren" 121 | }, 122 | "export_theme": { 123 | "message": "Thema exportieren" 124 | }, 125 | "import_config": { 126 | "message": "Konfig importieren" 127 | }, 128 | "import_theme": { 129 | "message": "Thema importieren" 130 | }, 131 | "code_size": { 132 | "message": "Code Größe" 133 | }, 134 | "prmpt_css": { 135 | "message": "Als UserStyle installieren?" 136 | }, 137 | "userjs_inject": { 138 | "message": "Userscript+ einfügen" 139 | }, 140 | "userjs_close": { 141 | "message": "Userscript+ schließen" 142 | }, 143 | "userjs_sync": { 144 | "message": "Sync" 145 | }, 146 | "userjs_autoinject": { 147 | "message": "Inject on load" 148 | }, 149 | "auto_fetch": { 150 | "message": "Fetch on load" 151 | }, 152 | "code": { 153 | "message": "Quelltext" 154 | }, 155 | "metadata": { 156 | "message": "Metadata" 157 | }, 158 | "preview_metadata": { 159 | "message": "Preview Metadata" 160 | }, 161 | "recommend_author": { 162 | "message": "Recommend Author" 163 | }, 164 | "recommend_other": { 165 | "message": "Recommend Others" 166 | }, 167 | "default_sort": { 168 | "message": "Default Sort" 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "Magic Userscript+", 4 | "description": "Must not exceed 45 characters" 5 | }, 6 | "extDesc": { 7 | "message": "Finds available UserJS for the current webpage, the power of Greasy Fork on the go!", 8 | "description": "Must not exceed 132 characters" 9 | }, 10 | "extShortDesc": { 11 | "message": "The power of Greasy Fork on the go!", 12 | "description": "Must not exceed 112 characters" 13 | }, 14 | "userjs_name": { 15 | "message": "Magic Userscript+ : Show Site All UserJS" 16 | }, 17 | "userjs_description": { 18 | "message": "Finds available userscripts for the current webpage." 19 | }, 20 | "createdby": { 21 | "message": "Created by" 22 | }, 23 | "name": { 24 | "message": "Name" 25 | }, 26 | "daily_installs": { 27 | "message": "Daily Installs" 28 | }, 29 | "close": { 30 | "message": "Close" 31 | }, 32 | "filterA": { 33 | "message": "Filter" 34 | }, 35 | "max": { 36 | "message": "Maximize" 37 | }, 38 | "min": { 39 | "message": "Minimize" 40 | }, 41 | "search": { 42 | "message": "Search" 43 | }, 44 | "search_placeholder": { 45 | "message": "Search for userscripts" 46 | }, 47 | "install": { 48 | "message": "Install" 49 | }, 50 | "issue": { 51 | "message": "New Issue" 52 | }, 53 | "version_number": { 54 | "message": "Version" 55 | }, 56 | "updated": { 57 | "message": "Last Updated" 58 | }, 59 | "total_installs": { 60 | "message": "Total Installs" 61 | }, 62 | "ratings": { 63 | "message": "Ratings" 64 | }, 65 | "good": { 66 | "message": "Good" 67 | }, 68 | "ok": { 69 | "message": "Okay" 70 | }, 71 | "bad": { 72 | "message": "Bad" 73 | }, 74 | "created_date": { 75 | "message": "Created" 76 | }, 77 | "redirect": { 78 | "message": "Greasy Fork for adults" 79 | }, 80 | "filter": { 81 | "message": "Filter out other languages" 82 | }, 83 | "dtime": { 84 | "message": "Display Timeout" 85 | }, 86 | "save": { 87 | "message": "Save" 88 | }, 89 | "reset": { 90 | "message": "Reset" 91 | }, 92 | "preview_code": { 93 | "message": "Preview Code" 94 | }, 95 | "saveFile": { 96 | "message": "Download" 97 | }, 98 | "newTab": { 99 | "message": "New Tab" 100 | }, 101 | "applies_to": { 102 | "message": "Applies to" 103 | }, 104 | "license": { 105 | "message": "License" 106 | }, 107 | "no_license": { 108 | "message": "N/A" 109 | }, 110 | "antifeatures": { 111 | "message": "Antifeatures" 112 | }, 113 | "userjs_fullscreen": { 114 | "message": "Automatic Fullscreen" 115 | }, 116 | "listing_none": { 117 | "message": "(None)" 118 | }, 119 | "export_config": { 120 | "message": "Export Config" 121 | }, 122 | "export_theme": { 123 | "message": "Export Theme" 124 | }, 125 | "import_config": { 126 | "message": "Import Config" 127 | }, 128 | "import_theme": { 129 | "message": "Import Theme" 130 | }, 131 | "code_size": { 132 | "message": "Code Size" 133 | }, 134 | "prmpt_css": { 135 | "message": "Install as UserStyle?" 136 | }, 137 | "userjs_inject": { 138 | "message": "Inject Userscript+" 139 | }, 140 | "userjs_close": { 141 | "message": "Close Userscript+" 142 | }, 143 | "userjs_sync": { 144 | "message": "Sync" 145 | }, 146 | "userjs_autoinject": { 147 | "message": "Inject on load" 148 | }, 149 | "auto_fetch": { 150 | "message": "Fetch on load" 151 | }, 152 | "code": { 153 | "message": "Code" 154 | }, 155 | "metadata": { 156 | "message": "Metadata" 157 | }, 158 | "preview_metadata": { 159 | "message": "Preview Metadata" 160 | }, 161 | "recommend_author": { 162 | "message": "Recommend Author" 163 | }, 164 | "recommend_other": { 165 | "message": "Recommend Others" 166 | }, 167 | "default_sort": { 168 | "message": "Default Sort" 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/_locales/en_GB/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "Magic Userscript+", 4 | "description": "Must not exceed 45 characters" 5 | }, 6 | "extDesc": { 7 | "message": "Finds available UserJS for the current webpage, the power of Greasy Fork on the go!", 8 | "description": "Must not exceed 132 characters" 9 | }, 10 | "extShortDesc": { 11 | "message": "The power of Greasy Fork on the go!", 12 | "description": "Must not exceed 112 characters" 13 | }, 14 | "userjs_name": { 15 | "message": "Magic Userscript+ : Show Site All UserJS" 16 | }, 17 | "userjs_description": { 18 | "message": "Finds available userscripts for the current webpage." 19 | }, 20 | "createdby": { 21 | "message": "Created by" 22 | }, 23 | "name": { 24 | "message": "Name" 25 | }, 26 | "daily_installs": { 27 | "message": "Daily Installs" 28 | }, 29 | "close": { 30 | "message": "Close" 31 | }, 32 | "filterA": { 33 | "message": "Filter" 34 | }, 35 | "max": { 36 | "message": "Maximize" 37 | }, 38 | "min": { 39 | "message": "Minimize" 40 | }, 41 | "search": { 42 | "message": "Search" 43 | }, 44 | "search_placeholder": { 45 | "message": "Search for userscripts" 46 | }, 47 | "install": { 48 | "message": "Install" 49 | }, 50 | "issue": { 51 | "message": "New Issue" 52 | }, 53 | "version_number": { 54 | "message": "Version" 55 | }, 56 | "updated": { 57 | "message": "Last Updated" 58 | }, 59 | "total_installs": { 60 | "message": "Total Installs" 61 | }, 62 | "ratings": { 63 | "message": "Ratings" 64 | }, 65 | "good": { 66 | "message": "Good" 67 | }, 68 | "ok": { 69 | "message": "Okay" 70 | }, 71 | "bad": { 72 | "message": "Bad" 73 | }, 74 | "created_date": { 75 | "message": "Created" 76 | }, 77 | "redirect": { 78 | "message": "Greasy Fork for adults" 79 | }, 80 | "filter": { 81 | "message": "Filter out other languages" 82 | }, 83 | "dtime": { 84 | "message": "Display Timeout" 85 | }, 86 | "save": { 87 | "message": "Save" 88 | }, 89 | "reset": { 90 | "message": "Reset" 91 | }, 92 | "preview_code": { 93 | "message": "Preview Code" 94 | }, 95 | "saveFile": { 96 | "message": "Download" 97 | }, 98 | "newTab": { 99 | "message": "New Tab" 100 | }, 101 | "applies_to": { 102 | "message": "Applies to" 103 | }, 104 | "license": { 105 | "message": "License" 106 | }, 107 | "no_license": { 108 | "message": "N/A" 109 | }, 110 | "antifeatures": { 111 | "message": "Antifeatures" 112 | }, 113 | "userjs_fullscreen": { 114 | "message": "Automatic Fullscreen" 115 | }, 116 | "listing_none": { 117 | "message": "(None)" 118 | }, 119 | "export_config": { 120 | "message": "Export Config" 121 | }, 122 | "export_theme": { 123 | "message": "Export Theme" 124 | }, 125 | "import_config": { 126 | "message": "Import Config" 127 | }, 128 | "import_theme": { 129 | "message": "Import Theme" 130 | }, 131 | "code_size": { 132 | "message": "Code Size" 133 | }, 134 | "prmpt_css": { 135 | "message": "Install as UserStyle?" 136 | }, 137 | "userjs_inject": { 138 | "message": "Inject Userscript+" 139 | }, 140 | "userjs_close": { 141 | "message": "Close Userscript+" 142 | }, 143 | "userjs_sync": { 144 | "message": "Sync" 145 | }, 146 | "userjs_autoinject": { 147 | "message": "Inject on load" 148 | }, 149 | "auto_fetch": { 150 | "message": "Fetch on load" 151 | }, 152 | "code": { 153 | "message": "Code" 154 | }, 155 | "metadata": { 156 | "message": "Metadata" 157 | }, 158 | "preview_metadata": { 159 | "message": "Preview Metadata" 160 | }, 161 | "recommend_author": { 162 | "message": "Recommend Author" 163 | }, 164 | "recommend_other": { 165 | "message": "Recommend Others" 166 | }, 167 | "default_sort": { 168 | "message": "Default Sort" 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/_locales/es/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "Magic Userscript+", 4 | "description": "Must not exceed 45 characters" 5 | }, 6 | "extDesc": { 7 | "message": "The power of Greasy Fork on the go! Finds available UserJS for the current webpage.", 8 | "description": "Must not exceed 132 characters" 9 | }, 10 | "extShortDesc": { 11 | "message": "Finds available UserJS for the current webpage.", 12 | "description": "Must not exceed 112 characters" 13 | }, 14 | "userjs_name": { 15 | "message": "Magic Userscript+: Mostrar sitio todos los UserJS" 16 | }, 17 | "userjs_description": { 18 | "message": "Busca los usercripts disponibles para la página web actual." 19 | }, 20 | "createdby": { 21 | "message": "Creado por" 22 | }, 23 | "name": { 24 | "message": "Nombre" 25 | }, 26 | "daily_installs": { 27 | "message": "Instalaciones diarias" 28 | }, 29 | "close": { 30 | "message": "Ya no se muestra" 31 | }, 32 | "filterA": { 33 | "message": "Filtro" 34 | }, 35 | "max": { 36 | "message": "Maximizar" 37 | }, 38 | "min": { 39 | "message": "Minimizar" 40 | }, 41 | "search": { 42 | "message": "Busque en" 43 | }, 44 | "search_placeholder": { 45 | "message": "Buscar userscripts" 46 | }, 47 | "install": { 48 | "message": "Instalar" 49 | }, 50 | "issue": { 51 | "message": "Nueva edición" 52 | }, 53 | "version_number": { 54 | "message": "Versión" 55 | }, 56 | "updated": { 57 | "message": "Última actualización" 58 | }, 59 | "total_installs": { 60 | "message": "Total de instalaciones" 61 | }, 62 | "ratings": { 63 | "message": "Clasificaciones" 64 | }, 65 | "good": { 66 | "message": "Bueno" 67 | }, 68 | "ok": { 69 | "message": "Ok" 70 | }, 71 | "bad": { 72 | "message": "Malo" 73 | }, 74 | "created_date": { 75 | "message": "Creado" 76 | }, 77 | "redirect": { 78 | "message": "Greasy Fork para adultos" 79 | }, 80 | "filter": { 81 | "message": "Filtrar otros idiomas" 82 | }, 83 | "dtime": { 84 | "message": "Mostrar el tiempo de espera" 85 | }, 86 | "save": { 87 | "message": "Guardar" 88 | }, 89 | "reset": { 90 | "message": "Reiniciar" 91 | }, 92 | "preview_code": { 93 | "message": "Vista previa del código" 94 | }, 95 | "saveFile": { 96 | "message": "Guardar archivo" 97 | }, 98 | "newTab": { 99 | "message": "Guardar archivo" 100 | }, 101 | "applies_to": { 102 | "message": "Se aplica a" 103 | }, 104 | "license": { 105 | "message": "Licencia" 106 | }, 107 | "no_license": { 108 | "message": "Desconocida" 109 | }, 110 | "antifeatures": { 111 | "message": "Características indeseables" 112 | }, 113 | "userjs_fullscreen": { 114 | "message": "Pantalla completa automática" 115 | }, 116 | "listing_none": { 117 | "message": "(Ninguno)" 118 | }, 119 | "export_config": { 120 | "message": "Exportar configuración" 121 | }, 122 | "export_theme": { 123 | "message": "Exportar tema" 124 | }, 125 | "import_config": { 126 | "message": "Importar configuración" 127 | }, 128 | "import_theme": { 129 | "message": "Importar tema" 130 | }, 131 | "code_size": { 132 | "message": "Código Tamaño" 133 | }, 134 | "prmpt_css": { 135 | "message": "¿Instalar como UserStyle?" 136 | }, 137 | "userjs_inject": { 138 | "message": "Inyectar Userscript+" 139 | }, 140 | "userjs_close": { 141 | "message": "Cerrar Userscript+" 142 | }, 143 | "userjs_sync": { 144 | "message": "Sync" 145 | }, 146 | "userjs_autoinject": { 147 | "message": "Inject on load" 148 | }, 149 | "auto_fetch": { 150 | "message": "Fetch on load" 151 | }, 152 | "code": { 153 | "message": "Código" 154 | }, 155 | "metadata": { 156 | "message": "Metadata" 157 | }, 158 | "preview_metadata": { 159 | "message": "Preview Metadata" 160 | }, 161 | "recommend_author": { 162 | "message": "Recommend Author" 163 | }, 164 | "recommend_other": { 165 | "message": "Recommend Others" 166 | }, 167 | "default_sort": { 168 | "message": "Default Sort" 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/_locales/fr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "Magic Userscript+", 4 | "description": "Must not exceed 45 characters" 5 | }, 6 | "extDesc": { 7 | "message": "The power of Greasy Fork on the go! Finds available UserJS for the current webpage.", 8 | "description": "Must not exceed 132 characters" 9 | }, 10 | "extShortDesc": { 11 | "message": "Finds available UserJS for the current webpage.", 12 | "description": "Must not exceed 112 characters" 13 | }, 14 | "userjs_name": { 15 | "message": "Magic Userscript+ : Afficher le site Tous les UserJS" 16 | }, 17 | "userjs_description": { 18 | "message": "Recherche les userscripts disponibles pour la page web en cours." 19 | }, 20 | "createdby": { 21 | "message": "Créé par" 22 | }, 23 | "name": { 24 | "message": "Nom" 25 | }, 26 | "daily_installs": { 27 | "message": "Installations quotidiennes" 28 | }, 29 | "close": { 30 | "message": "Ne plus montrer" 31 | }, 32 | "filterA": { 33 | "message": "Filtre" 34 | }, 35 | "max": { 36 | "message": "Maximiser" 37 | }, 38 | "min": { 39 | "message": "Minimiser" 40 | }, 41 | "search": { 42 | "message": "Recherche" 43 | }, 44 | "search_placeholder": { 45 | "message": "Rechercher des userscripts" 46 | }, 47 | "install": { 48 | "message": "Installer" 49 | }, 50 | "issue": { 51 | "message": "Nouveau numéro" 52 | }, 53 | "version_number": { 54 | "message": "Version" 55 | }, 56 | "updated": { 57 | "message": "Dernière mise à jour" 58 | }, 59 | "total_installs": { 60 | "message": "Total des installations" 61 | }, 62 | "ratings": { 63 | "message": "Notations" 64 | }, 65 | "good": { 66 | "message": "Bon" 67 | }, 68 | "ok": { 69 | "message": "Ok" 70 | }, 71 | "bad": { 72 | "message": "Mauvais" 73 | }, 74 | "created_date": { 75 | "message": "Créé" 76 | }, 77 | "redirect": { 78 | "message": "Greasy Fork pour les adultes" 79 | }, 80 | "filter": { 81 | "message": "Filtrer les autres langues" 82 | }, 83 | "dtime": { 84 | "message": "Délai d'affichage" 85 | }, 86 | "save": { 87 | "message": "Sauvez" 88 | }, 89 | "reset": { 90 | "message": "Réinitialiser" 91 | }, 92 | "preview_code": { 93 | "message": "Prévisualiser le code" 94 | }, 95 | "saveFile": { 96 | "message": "Enregistrer le fichier" 97 | }, 98 | "newTab": { 99 | "message": "Nouvel onglet" 100 | }, 101 | "applies_to": { 102 | "message": "S'applique à" 103 | }, 104 | "license": { 105 | "message": "Licence" 106 | }, 107 | "no_license": { 108 | "message": "N/A" 109 | }, 110 | "antifeatures": { 111 | "message": "Antifeatures" 112 | }, 113 | "userjs_fullscreen": { 114 | "message": "Plein écran automatique" 115 | }, 116 | "listing_none": { 117 | "message": "(Aucun)" 118 | }, 119 | "export_config": { 120 | "message": "Export Config" 121 | }, 122 | "export_theme": { 123 | "message": "Exporter le thème" 124 | }, 125 | "import_config": { 126 | "message": "Importer la configuration" 127 | }, 128 | "import_theme": { 129 | "message": "Importer le thème" 130 | }, 131 | "code_size": { 132 | "message": "Code Taille" 133 | }, 134 | "prmpt_css": { 135 | "message": "Installer comme UserStyle ?" 136 | }, 137 | "userjs_inject": { 138 | "message": "Injecter Userscript+" 139 | }, 140 | "userjs_close": { 141 | "message": "Fermer Userscript+" 142 | }, 143 | "userjs_sync": { 144 | "message": "Sync" 145 | }, 146 | "userjs_autoinject": { 147 | "message": "Inject on load" 148 | }, 149 | "auto_fetch": { 150 | "message": "Fetch on load" 151 | }, 152 | "code": { 153 | "message": "Code" 154 | }, 155 | "metadata": { 156 | "message": "Metadata" 157 | }, 158 | "preview_metadata": { 159 | "message": "Preview Metadata" 160 | }, 161 | "recommend_author": { 162 | "message": "Recommend Author" 163 | }, 164 | "recommend_other": { 165 | "message": "Recommend Others" 166 | }, 167 | "default_sort": { 168 | "message": "Default Sort" 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/_locales/ja/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "Magic Userscript+", 4 | "description": "Must not exceed 45 characters" 5 | }, 6 | "extDesc": { 7 | "message": "The power of Greasy Fork on the go! Finds available UserJS for the current webpage.", 8 | "description": "Must not exceed 132 characters" 9 | }, 10 | "extShortDesc": { 11 | "message": "Finds available UserJS for the current webpage.", 12 | "description": "Must not exceed 112 characters" 13 | }, 14 | "userjs_name": { 15 | "message": "Magic Userscript+ : サイトをすべて表示 UserJS" 16 | }, 17 | "userjs_description": { 18 | "message": "現在のウェブページで利用可能なユーザスクリプトを検索します。" 19 | }, 20 | "createdby": { 21 | "message": "によって作成された" 22 | }, 23 | "name": { 24 | "message": "名前" 25 | }, 26 | "daily_installs": { 27 | "message": "デイリーインストール" 28 | }, 29 | "close": { 30 | "message": "表示されなくなりました" 31 | }, 32 | "filterA": { 33 | "message": "フィルター" 34 | }, 35 | "max": { 36 | "message": "最大化" 37 | }, 38 | "min": { 39 | "message": "ミニマム" 40 | }, 41 | "search": { 42 | "message": "検索" 43 | }, 44 | "search_placeholder": { 45 | "message": "ユーザースクリプトの検索" 46 | }, 47 | "install": { 48 | "message": "インストール" 49 | }, 50 | "issue": { 51 | "message": "新刊のご案内" 52 | }, 53 | "version_number": { 54 | "message": "バージョン" 55 | }, 56 | "updated": { 57 | "message": "最終更新日" 58 | }, 59 | "total_installs": { 60 | "message": "総インストール数" 61 | }, 62 | "ratings": { 63 | "message": "レーティング" 64 | }, 65 | "good": { 66 | "message": "グッド" 67 | }, 68 | "ok": { 69 | "message": "良い" 70 | }, 71 | "bad": { 72 | "message": "悪い" 73 | }, 74 | "created_date": { 75 | "message": "作成" 76 | }, 77 | "redirect": { 78 | "message": "大人のGreasyfork" 79 | }, 80 | "filter": { 81 | "message": "他の言語をフィルタリングする" 82 | }, 83 | "dtime": { 84 | "message": "表示タイムアウト" 85 | }, 86 | "save": { 87 | "message": "拯救" 88 | }, 89 | "reset": { 90 | "message": "リセット" 91 | }, 92 | "preview_code": { 93 | "message": "コードのプレビュー" 94 | }, 95 | "saveFile": { 96 | "message": "ファイルを保存" 97 | }, 98 | "newTab": { 99 | "message": "新しいタブ" 100 | }, 101 | "applies_to": { 102 | "message": "適用対象" 103 | }, 104 | "license": { 105 | "message": "ライセンス" 106 | }, 107 | "no_license": { 108 | "message": "不明" 109 | }, 110 | "antifeatures": { 111 | "message": "アンチ機能" 112 | }, 113 | "userjs_fullscreen": { 114 | "message": "自動フルスクリーン" 115 | }, 116 | "listing_none": { 117 | "message": "(なし)" 118 | }, 119 | "export_config": { 120 | "message": "エクスポート設定" 121 | }, 122 | "export_theme": { 123 | "message": "テーマのエクスポート" 124 | }, 125 | "import_config": { 126 | "message": "設定のインポート" 127 | }, 128 | "import_theme": { 129 | "message": "テーマのインポート" 130 | }, 131 | "code_size": { 132 | "message": "コード・サイズ" 133 | }, 134 | "prmpt_css": { 135 | "message": "UserStyleとしてインストールしますか?" 136 | }, 137 | "userjs_inject": { 138 | "message": "Userscript+ を挿入" 139 | }, 140 | "userjs_close": { 141 | "message": "Userscript+ を閉じる" 142 | }, 143 | "userjs_sync": { 144 | "message": "Sync" 145 | }, 146 | "userjs_autoinject": { 147 | "message": "Inject on load" 148 | }, 149 | "auto_fetch": { 150 | "message": "Fetch on load" 151 | }, 152 | "code": { 153 | "message": "コード" 154 | }, 155 | "metadata": { 156 | "message": "Metadata" 157 | }, 158 | "preview_metadata": { 159 | "message": "Preview Metadata" 160 | }, 161 | "recommend_author": { 162 | "message": "Recommend Author" 163 | }, 164 | "recommend_other": { 165 | "message": "Recommend Others" 166 | }, 167 | "default_sort": { 168 | "message": "Default Sort" 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/_locales/nl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "Magic Userscript+", 4 | "description": "Must not exceed 45 characters" 5 | }, 6 | "extDesc": { 7 | "message": "The power of Greasy Fork on the go! Finds available UserJS for the current webpage.", 8 | "description": "Must not exceed 132 characters" 9 | }, 10 | "extShortDesc": { 11 | "message": "Finds available UserJS for the current webpage.", 12 | "description": "Must not exceed 112 characters" 13 | }, 14 | "userjs_name": { 15 | "message": "Magic Userscript+: Site alle UserJS tonen" 16 | }, 17 | "userjs_description": { 18 | "message": "Zoekt beschikbare gebruikerscripts voor de huidige webpagina." 19 | }, 20 | "createdby": { 21 | "message": "Gemaakt door" 22 | }, 23 | "name": { 24 | "message": "Naam" 25 | }, 26 | "daily_installs": { 27 | "message": "Dagelijkse Installaties" 28 | }, 29 | "close": { 30 | "message": "Sluit" 31 | }, 32 | "filterA": { 33 | "message": "Filter" 34 | }, 35 | "max": { 36 | "message": "Maximaliseer" 37 | }, 38 | "min": { 39 | "message": "Minimaliseer" 40 | }, 41 | "search": { 42 | "message": "Zoek" 43 | }, 44 | "search_placeholder": { 45 | "message": "Zoeken naar gebruikersscripts" 46 | }, 47 | "install": { 48 | "message": "Installeer" 49 | }, 50 | "issue": { 51 | "message": "Nieuw Issue" 52 | }, 53 | "version_number": { 54 | "message": "Versie" 55 | }, 56 | "updated": { 57 | "message": "Laatste Update" 58 | }, 59 | "total_installs": { 60 | "message": "Totale Installaties" 61 | }, 62 | "ratings": { 63 | "message": "Beoordeling" 64 | }, 65 | "good": { 66 | "message": "Goed" 67 | }, 68 | "ok": { 69 | "message": "Ok" 70 | }, 71 | "bad": { 72 | "message": "Slecht" 73 | }, 74 | "created_date": { 75 | "message": "Aangemaakt" 76 | }, 77 | "redirect": { 78 | "message": "Greasy Fork voor volwassenen" 79 | }, 80 | "filter": { 81 | "message": "Filter andere talen" 82 | }, 83 | "dtime": { 84 | "message": "Weergave timeout" 85 | }, 86 | "save": { 87 | "message": "Opslaan" 88 | }, 89 | "reset": { 90 | "message": "Opnieuw instellen" 91 | }, 92 | "preview_code": { 93 | "message": "Voorbeeldcode" 94 | }, 95 | "saveFile": { 96 | "message": "Bestand opslaan" 97 | }, 98 | "newTab": { 99 | "message": "Nieuw tabblad" 100 | }, 101 | "applies_to": { 102 | "message": "Geldt voor" 103 | }, 104 | "license": { 105 | "message": "Licentie" 106 | }, 107 | "no_license": { 108 | "message": "N.v.t." 109 | }, 110 | "antifeatures": { 111 | "message": "Functies voor eigen gewin" 112 | }, 113 | "userjs_fullscreen": { 114 | "message": "Automatisch volledig scherm" 115 | }, 116 | "listing_none": { 117 | "message": "(Geen)" 118 | }, 119 | "export_config": { 120 | "message": "Configuratie exporteren" 121 | }, 122 | "export_theme": { 123 | "message": "Thema exporteren" 124 | }, 125 | "import_config": { 126 | "message": "Configuratie importeren" 127 | }, 128 | "import_theme": { 129 | "message": "Thema importeren" 130 | }, 131 | "code_size": { 132 | "message": "Code Grootte" 133 | }, 134 | "prmpt_css": { 135 | "message": "Installeren als UserStyle?" 136 | }, 137 | "userjs_inject": { 138 | "message": "Injecteer Userscript+" 139 | }, 140 | "userjs_close": { 141 | "message": "Sluit Userscript+" 142 | }, 143 | "userjs_sync": { 144 | "message": "Sync" 145 | }, 146 | "userjs_autoinject": { 147 | "message": "Inject on load" 148 | }, 149 | "auto_fetch": { 150 | "message": "Fetch on load" 151 | }, 152 | "code": { 153 | "message": "Code" 154 | }, 155 | "metadata": { 156 | "message": "Metadata" 157 | }, 158 | "preview_metadata": { 159 | "message": "Preview Metadata" 160 | }, 161 | "recommend_author": { 162 | "message": "Recommend Author" 163 | }, 164 | "recommend_other": { 165 | "message": "Recommend Others" 166 | }, 167 | "default_sort": { 168 | "message": "Default Sort" 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/_locales/pl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "Magic Userscript+", 4 | "description": "Must not exceed 45 characters" 5 | }, 6 | "extDesc": { 7 | "message": "Finds available UserJS for the current webpage, the power of Greasy Fork on the go!", 8 | "description": "Must not exceed 132 characters" 9 | }, 10 | "extShortDesc": { 11 | "message": "The power of Greasy Fork on the go!", 12 | "description": "Must not exceed 112 characters" 13 | }, 14 | "userjs_name": { 15 | "message": "Magic Userscript+ : Pokaż witrynę Wszystkie UserJS" 16 | }, 17 | "userjs_description": { 18 | "message": "Wyszukuje dostępne skrypty użytkownika dla bieżącej strony internetowej." 19 | }, 20 | "createdby": { 21 | "message": "Stworzony przez" 22 | }, 23 | "name": { 24 | "message": "Nazwa" 25 | }, 26 | "daily_installs": { 27 | "message": "Codzienne instalacje" 28 | }, 29 | "close": { 30 | "message": "Zamknij" 31 | }, 32 | "filterA": { 33 | "message": "Filtr" 34 | }, 35 | "max": { 36 | "message": "Maksymalizuj" 37 | }, 38 | "min": { 39 | "message": "Minimalizuj" 40 | }, 41 | "search": { 42 | "message": "Wyszukiwanie" 43 | }, 44 | "search_placeholder": { 45 | "message": "Wyszukiwanie skryptów użytkownika" 46 | }, 47 | "install": { 48 | "message": "Instalacja" 49 | }, 50 | "issue": { 51 | "message": "Nowy numer" 52 | }, 53 | "version_number": { 54 | "message": "Wersja" 55 | }, 56 | "updated": { 57 | "message": "Ostatnia aktualizacja" 58 | }, 59 | "total_installs": { 60 | "message": "Łączna liczba instalacji" 61 | }, 62 | "ratings": { 63 | "message": "Oceny" 64 | }, 65 | "good": { 66 | "message": "Dobry" 67 | }, 68 | "ok": { 69 | "message": "Ok" 70 | }, 71 | "bad": { 72 | "message": "Zły" 73 | }, 74 | "created_date": { 75 | "message": "Utworzony" 76 | }, 77 | "redirect": { 78 | "message": "Greasy Fork dla dorosłych" 79 | }, 80 | "filter": { 81 | "message": "Odfiltruj inne języki" 82 | }, 83 | "dtime": { 84 | "message": "Limit czasu wyświetlania" 85 | }, 86 | "save": { 87 | "message": "Zapisz" 88 | }, 89 | "reset": { 90 | "message": "Reset" 91 | }, 92 | "preview_code": { 93 | "message": "Kod podglądu" 94 | }, 95 | "saveFile": { 96 | "message": "Zapisz plik" 97 | }, 98 | "newTab": { 99 | "message": "Nowa karta" 100 | }, 101 | "applies_to": { 102 | "message": "Dotyczy" 103 | }, 104 | "license": { 105 | "message": "Licencja" 106 | }, 107 | "no_license": { 108 | "message": "N/A" 109 | }, 110 | "antifeatures": { 111 | "message": "Antywzorce" 112 | }, 113 | "userjs_fullscreen": { 114 | "message": "Automatyczny pełny ekran" 115 | }, 116 | "listing_none": { 117 | "message": "(Brak)" 118 | }, 119 | "export_config": { 120 | "message": "Konfiguracja eksportu" 121 | }, 122 | "export_theme": { 123 | "message": "Motyw eksportu" 124 | }, 125 | "import_config": { 126 | "message": "Importuj konfigurację" 127 | }, 128 | "import_theme": { 129 | "message": "Importuj motyw" 130 | }, 131 | "code_size": { 132 | "message": "Kod Rozmiar" 133 | }, 134 | "prmpt_css": { 135 | "message": "Zainstalować jako UserStyle?" 136 | }, 137 | "userjs_inject": { 138 | "message": "Wstrzyknij Userscript+" 139 | }, 140 | "userjs_close": { 141 | "message": "Zamknij Userscript+" 142 | }, 143 | "userjs_sync": { 144 | "message": "Sync" 145 | }, 146 | "userjs_autoinject": { 147 | "message": "Inject on load" 148 | }, 149 | "auto_fetch": { 150 | "message": "Fetch on load" 151 | }, 152 | "code": { 153 | "message": "Kod" 154 | }, 155 | "metadata": { 156 | "message": "Metadata" 157 | }, 158 | "preview_metadata": { 159 | "message": "Preview Metadata" 160 | }, 161 | "recommend_author": { 162 | "message": "Recommend Author" 163 | }, 164 | "recommend_other": { 165 | "message": "Recommend Others" 166 | }, 167 | "default_sort": { 168 | "message": "Default Sort" 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/_locales/ru/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "Magic Userscript+", 4 | "description": "Must not exceed 45 characters" 5 | }, 6 | "extDesc": { 7 | "message": "The power of Greasy Fork on the go! Finds available UserJS for the current webpage.", 8 | "description": "Must not exceed 132 characters" 9 | }, 10 | "extShortDesc": { 11 | "message": "Finds available UserJS for the current webpage.", 12 | "description": "Must not exceed 112 characters" 13 | }, 14 | "userjs_name": { 15 | "message": "Magic Userscript+: показать сайт всем UserJS" 16 | }, 17 | "userjs_description": { 18 | "message": "Находит доступные юзерскрипты для текущей веб-страницы." 19 | }, 20 | "createdby": { 21 | "message": "Сделано" 22 | }, 23 | "name": { 24 | "message": "Имя" 25 | }, 26 | "daily_installs": { 27 | "message": "Ежедневные установки" 28 | }, 29 | "close": { 30 | "message": "Больше не показывать" 31 | }, 32 | "filterA": { 33 | "message": "Фильтр" 34 | }, 35 | "max": { 36 | "message": "Максимизировать" 37 | }, 38 | "min": { 39 | "message": "Минимизировать" 40 | }, 41 | "search": { 42 | "message": "Поиск" 43 | }, 44 | "search_placeholder": { 45 | "message": "Поиск юзерскриптов" 46 | }, 47 | "install": { 48 | "message": "Установите" 49 | }, 50 | "issue": { 51 | "message": "Новый выпуск" 52 | }, 53 | "version_number": { 54 | "message": "Версия" 55 | }, 56 | "updated": { 57 | "message": "Последнее обновление" 58 | }, 59 | "total_installs": { 60 | "message": "Всего установок" 61 | }, 62 | "ratings": { 63 | "message": "Рейтинги" 64 | }, 65 | "good": { 66 | "message": "Хорошо" 67 | }, 68 | "ok": { 69 | "message": "Хорошо" 70 | }, 71 | "bad": { 72 | "message": "Плохо" 73 | }, 74 | "created_date": { 75 | "message": "Создано" 76 | }, 77 | "redirect": { 78 | "message": "Greasy Fork для взрослых" 79 | }, 80 | "filter": { 81 | "message": "Отфильтровать другие языки" 82 | }, 83 | "dtime": { 84 | "message": "Тайм-аут отображения" 85 | }, 86 | "save": { 87 | "message": "Сохранить" 88 | }, 89 | "reset": { 90 | "message": "Перезагрузить" 91 | }, 92 | "preview_code": { 93 | "message": "Предварительный просмотр кода" 94 | }, 95 | "saveFile": { 96 | "message": "Сохранить файл" 97 | }, 98 | "newTab": { 99 | "message": "Новая вкладка" 100 | }, 101 | "applies_to": { 102 | "message": "Применяется к" 103 | }, 104 | "license": { 105 | "message": "Лицензия" 106 | }, 107 | "no_license": { 108 | "message": "Недоступно" 109 | }, 110 | "antifeatures": { 111 | "message": "Нежелательная функциональность" 112 | }, 113 | "userjs_fullscreen": { 114 | "message": "Автоматический полноэкранный режим" 115 | }, 116 | "listing_none": { 117 | "message": "(нет)" 118 | }, 119 | "export_config": { 120 | "message": "Экспорт конфигурации" 121 | }, 122 | "export_theme": { 123 | "message": "Экспорт темы" 124 | }, 125 | "import_config": { 126 | "message": "Импорт конфигурации" 127 | }, 128 | "import_theme": { 129 | "message": "Импортировать тему" 130 | }, 131 | "code_size": { 132 | "message": "Код Размер" 133 | }, 134 | "prmpt_css": { 135 | "message": "Установить как UserStyle?" 136 | }, 137 | "userjs_inject": { 138 | "message": "Вставить Userscript+" 139 | }, 140 | "userjs_close": { 141 | "message": "Закрыть Userscript+" 142 | }, 143 | "userjs_sync": { 144 | "message": "Sync" 145 | }, 146 | "userjs_autoinject": { 147 | "message": "Inject on load" 148 | }, 149 | "auto_fetch": { 150 | "message": "Fetch on load" 151 | }, 152 | "code": { 153 | "message": "Исходный код" 154 | }, 155 | "metadata": { 156 | "message": "Metadata" 157 | }, 158 | "preview_metadata": { 159 | "message": "Preview Metadata" 160 | }, 161 | "recommend_author": { 162 | "message": "Recommend Author" 163 | }, 164 | "recommend_other": { 165 | "message": "Recommend Others" 166 | }, 167 | "default_sort": { 168 | "message": "Default Sort" 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/_locales/zh/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "Magic Userscript+", 4 | "description": "Must not exceed 45 characters" 5 | }, 6 | "extDesc": { 7 | "message": "为当前网页查找可用的 UserJS,随时随地发挥 Greasy Fork 的威力!", 8 | "description": "Must not exceed 132 characters" 9 | }, 10 | "extShortDesc": { 11 | "message": "Finds available UserJS for the current webpage.", 12 | "description": "Must not exceed 112 characters" 13 | }, 14 | "userjs_name": { 15 | "message": "Magic Userscript+ :显示站点所有 UserJS" 16 | }, 17 | "userjs_description": { 18 | "message": "为当前网页查找可用的用户脚本。" 19 | }, 20 | "createdby": { 21 | "message": "由...制作" 22 | }, 23 | "name": { 24 | "message": "姓名" 25 | }, 26 | "daily_installs": { 27 | "message": "日常安装" 28 | }, 29 | "close": { 30 | "message": "不再显示" 31 | }, 32 | "filterA": { 33 | "message": "过滤器" 34 | }, 35 | "max": { 36 | "message": "最大化" 37 | }, 38 | "min": { 39 | "message": "最小化" 40 | }, 41 | "search": { 42 | "message": "搜索" 43 | }, 44 | "search_placeholder": { 45 | "message": "搜索用户脚本" 46 | }, 47 | "install": { 48 | "message": "安装" 49 | }, 50 | "issue": { 51 | "message": "新问题" 52 | }, 53 | "version_number": { 54 | "message": "版本" 55 | }, 56 | "updated": { 57 | "message": "最后更新" 58 | }, 59 | "total_installs": { 60 | "message": "总安装量" 61 | }, 62 | "ratings": { 63 | "message": "评级" 64 | }, 65 | "good": { 66 | "message": "好的" 67 | }, 68 | "ok": { 69 | "message": "好的" 70 | }, 71 | "bad": { 72 | "message": "不好" 73 | }, 74 | "created_date": { 75 | "message": "创建" 76 | }, 77 | "redirect": { 78 | "message": "大人的Greasyfork" 79 | }, 80 | "filter": { 81 | "message": "过滤掉其他语言" 82 | }, 83 | "dtime": { 84 | "message": "显示超时" 85 | }, 86 | "save": { 87 | "message": "拯救" 88 | }, 89 | "reset": { 90 | "message": "重置" 91 | }, 92 | "preview_code": { 93 | "message": "预览代码" 94 | }, 95 | "saveFile": { 96 | "message": "保存存档" 97 | }, 98 | "newTab": { 99 | "message": "新标签" 100 | }, 101 | "applies_to": { 102 | "message": "适用于" 103 | }, 104 | "license": { 105 | "message": "许可证" 106 | }, 107 | "no_license": { 108 | "message": "暂无" 109 | }, 110 | "antifeatures": { 111 | "message": "可能不受欢迎的功能" 112 | }, 113 | "userjs_fullscreen": { 114 | "message": "自动全屏" 115 | }, 116 | "listing_none": { 117 | "message": "(无)" 118 | }, 119 | "export_config": { 120 | "message": "导出配置" 121 | }, 122 | "export_theme": { 123 | "message": "导出主题" 124 | }, 125 | "import_config": { 126 | "message": "导入配置" 127 | }, 128 | "import_theme": { 129 | "message": "导入主题" 130 | }, 131 | "code_size": { 132 | "message": "代码 尺寸" 133 | }, 134 | "prmpt_css": { 135 | "message": "安装为用户风格?" 136 | }, 137 | "userjs_inject": { 138 | "message": "注入 Userscript+" 139 | }, 140 | "userjs_close": { 141 | "message": "关闭 Userscript+" 142 | }, 143 | "userjs_sync": { 144 | "message": "Sync" 145 | }, 146 | "userjs_autoinject": { 147 | "message": "Inject on load" 148 | }, 149 | "auto_fetch": { 150 | "message": "Fetch on load" 151 | }, 152 | "code": { 153 | "message": "代码" 154 | }, 155 | "metadata": { 156 | "message": "Metadata" 157 | }, 158 | "preview_metadata": { 159 | "message": "Preview Metadata" 160 | }, 161 | "recommend_author": { 162 | "message": "Recommend Author" 163 | }, 164 | "recommend_other": { 165 | "message": "Recommend Others" 166 | }, 167 | "default_sort": { 168 | "message": "Default Sort" 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/_locales/zh_CN/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "Magic Userscript+", 4 | "description": "Must not exceed 45 characters" 5 | }, 6 | "extDesc": { 7 | "message": "The power of Greasy Fork on the go! Finds available UserJS for the current webpage.", 8 | "description": "Must not exceed 132 characters" 9 | }, 10 | "extShortDesc": { 11 | "message": "Finds available UserJS for the current webpage.", 12 | "description": "Must not exceed 112 characters" 13 | }, 14 | "userjs_name": { 15 | "message": "Magic Userscript+ :显示站点所有 UserJS" 16 | }, 17 | "userjs_description": { 18 | "message": "为当前网页查找可用的用户脚本。" 19 | }, 20 | "createdby": { 21 | "message": "由...制作" 22 | }, 23 | "name": { 24 | "message": "姓名" 25 | }, 26 | "daily_installs": { 27 | "message": "日常安装" 28 | }, 29 | "close": { 30 | "message": "不再显示" 31 | }, 32 | "filterA": { 33 | "message": "过滤器" 34 | }, 35 | "max": { 36 | "message": "最大化" 37 | }, 38 | "min": { 39 | "message": "最小化" 40 | }, 41 | "search": { 42 | "message": "搜索" 43 | }, 44 | "search_placeholder": { 45 | "message": "搜索用户脚本" 46 | }, 47 | "install": { 48 | "message": "安装" 49 | }, 50 | "issue": { 51 | "message": "新问题" 52 | }, 53 | "version_number": { 54 | "message": "版本" 55 | }, 56 | "updated": { 57 | "message": "最后更新" 58 | }, 59 | "total_installs": { 60 | "message": "总安装量" 61 | }, 62 | "ratings": { 63 | "message": "评级" 64 | }, 65 | "good": { 66 | "message": "好的" 67 | }, 68 | "ok": { 69 | "message": "好的" 70 | }, 71 | "bad": { 72 | "message": "不好" 73 | }, 74 | "created_date": { 75 | "message": "创建" 76 | }, 77 | "redirect": { 78 | "message": "大人的Greasyfork" 79 | }, 80 | "filter": { 81 | "message": "过滤掉其他语言" 82 | }, 83 | "dtime": { 84 | "message": "显示超时" 85 | }, 86 | "save": { 87 | "message": "拯救" 88 | }, 89 | "reset": { 90 | "message": "重置" 91 | }, 92 | "preview_code": { 93 | "message": "预览代码" 94 | }, 95 | "saveFile": { 96 | "message": "保存存档" 97 | }, 98 | "newTab": { 99 | "message": "新标签" 100 | }, 101 | "applies_to": { 102 | "message": "适用于" 103 | }, 104 | "license": { 105 | "message": "许可证" 106 | }, 107 | "no_license": { 108 | "message": "暂无" 109 | }, 110 | "antifeatures": { 111 | "message": "可能不受欢迎的功能" 112 | }, 113 | "userjs_fullscreen": { 114 | "message": "自动全屏" 115 | }, 116 | "listing_none": { 117 | "message": "(无)" 118 | }, 119 | "export_config": { 120 | "message": "导出配置" 121 | }, 122 | "export_theme": { 123 | "message": "导出主题" 124 | }, 125 | "import_config": { 126 | "message": "导入配置" 127 | }, 128 | "import_theme": { 129 | "message": "导入主题" 130 | }, 131 | "code_size": { 132 | "message": "代码 尺寸" 133 | }, 134 | "prmpt_css": { 135 | "message": "安装为用户风格?" 136 | }, 137 | "userjs_inject": { 138 | "message": "注入 Userscript+" 139 | }, 140 | "userjs_close": { 141 | "message": "关闭 Userscript+" 142 | }, 143 | "userjs_sync": { 144 | "message": "Sync" 145 | }, 146 | "userjs_autoinject": { 147 | "message": "Inject on load" 148 | }, 149 | "auto_fetch": { 150 | "message": "Fetch on load" 151 | }, 152 | "code": { 153 | "message": "代码" 154 | }, 155 | "metadata": { 156 | "message": "Metadata" 157 | }, 158 | "preview_metadata": { 159 | "message": "Preview Metadata" 160 | }, 161 | "recommend_author": { 162 | "message": "Recommend Author" 163 | }, 164 | "recommend_other": { 165 | "message": "Recommend Others" 166 | }, 167 | "default_sort": { 168 | "message": "Default Sort" 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/_locales/zh_TW/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "Magic Userscript+", 4 | "description": "Must not exceed 45 characters" 5 | }, 6 | "extDesc": { 7 | "message": "The power of Greasy Fork on the go! Finds available UserJS for the current webpage.", 8 | "description": "Must not exceed 132 characters" 9 | }, 10 | "extShortDesc": { 11 | "message": "Finds available UserJS for the current webpage.", 12 | "description": "Must not exceed 112 characters" 13 | }, 14 | "userjs_name": { 15 | "message": "Magic Userscript+ :显示站点所有 UserJS" 16 | }, 17 | "userjs_description": { 18 | "message": "为当前网页查找可用的用户脚本。" 19 | }, 20 | "createdby": { 21 | "message": "由...制作" 22 | }, 23 | "name": { 24 | "message": "姓名" 25 | }, 26 | "daily_installs": { 27 | "message": "日常安装" 28 | }, 29 | "close": { 30 | "message": "不再显示" 31 | }, 32 | "filterA": { 33 | "message": "过滤器" 34 | }, 35 | "max": { 36 | "message": "最大化" 37 | }, 38 | "min": { 39 | "message": "最小化" 40 | }, 41 | "search": { 42 | "message": "搜索" 43 | }, 44 | "search_placeholder": { 45 | "message": "搜索用户脚本" 46 | }, 47 | "install": { 48 | "message": "安装" 49 | }, 50 | "issue": { 51 | "message": "新问题" 52 | }, 53 | "version_number": { 54 | "message": "版本" 55 | }, 56 | "updated": { 57 | "message": "最后更新" 58 | }, 59 | "total_installs": { 60 | "message": "总安装量" 61 | }, 62 | "ratings": { 63 | "message": "评级" 64 | }, 65 | "good": { 66 | "message": "好的" 67 | }, 68 | "ok": { 69 | "message": "好的" 70 | }, 71 | "bad": { 72 | "message": "不好" 73 | }, 74 | "created_date": { 75 | "message": "创建" 76 | }, 77 | "redirect": { 78 | "message": "大人的Greasyfork" 79 | }, 80 | "filter": { 81 | "message": "过滤掉其他语言" 82 | }, 83 | "dtime": { 84 | "message": "显示超时" 85 | }, 86 | "save": { 87 | "message": "拯救" 88 | }, 89 | "reset": { 90 | "message": "重置" 91 | }, 92 | "preview_code": { 93 | "message": "预览代码" 94 | }, 95 | "saveFile": { 96 | "message": "保存存档" 97 | }, 98 | "newTab": { 99 | "message": "新标签" 100 | }, 101 | "applies_to": { 102 | "message": "适用于" 103 | }, 104 | "license": { 105 | "message": "许可证" 106 | }, 107 | "no_license": { 108 | "message": "暂无" 109 | }, 110 | "antifeatures": { 111 | "message": "可能不受欢迎的功能" 112 | }, 113 | "userjs_fullscreen": { 114 | "message": "自动全屏" 115 | }, 116 | "listing_none": { 117 | "message": "(无)" 118 | }, 119 | "export_config": { 120 | "message": "导出配置" 121 | }, 122 | "export_theme": { 123 | "message": "导出主题" 124 | }, 125 | "import_config": { 126 | "message": "导入配置" 127 | }, 128 | "import_theme": { 129 | "message": "导入主题" 130 | }, 131 | "code_size": { 132 | "message": "代码 尺寸" 133 | }, 134 | "prmpt_css": { 135 | "message": "作為使用者樣式安裝?" 136 | }, 137 | "userjs_inject": { 138 | "message": "注入用戶腳本+" 139 | }, 140 | "userjs_close": { 141 | "message": "關閉用戶腳本+" 142 | }, 143 | "userjs_sync": { 144 | "message": "Sync" 145 | }, 146 | "userjs_autoinject": { 147 | "message": "Inject on load" 148 | }, 149 | "auto_fetch": { 150 | "message": "Fetch on load" 151 | }, 152 | "code": { 153 | "message": "代碼" 154 | }, 155 | "metadata": { 156 | "message": "Metadata" 157 | }, 158 | "preview_metadata": { 159 | "message": "Preview Metadata" 160 | }, 161 | "recommend_author": { 162 | "message": "Recommend Author" 163 | }, 164 | "recommend_other": { 165 | "message": "Recommend Others" 166 | }, 167 | "default_sort": { 168 | "message": "Default Sort" 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/html/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 35 |
36 | 37 |
38 |
39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/img/icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/0a917ca3a695c990ccc8dc06c7953cd3b9fec7eb/src/img/icon_128.png -------------------------------------------------------------------------------- /src/img/icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/0a917ca3a695c990ccc8dc06c7953cd3b9fec7eb/src/img/icon_16.png -------------------------------------------------------------------------------- /src/img/icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/0a917ca3a695c990ccc8dc06c7953cd3b9fec7eb/src/img/icon_32.png -------------------------------------------------------------------------------- /src/img/icon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/0a917ca3a695c990ccc8dc06c7953cd3b9fec7eb/src/img/icon_48.png -------------------------------------------------------------------------------- /src/img/icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/0a917ca3a695c990ccc8dc06c7953cd3b9fec7eb/src/img/icon_64.png -------------------------------------------------------------------------------- /src/img/verified-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/js/XMap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Class representing an extended version of the Map class that supports storing more than 3 | * 16,777,215 keys by using multiple Map instances internally. This class provides a unified 4 | * interface to interact with these maps as if it were a single map. 5 | * 6 | * Based on InfinityMap by Fabio Spampinato (https://github.com/fabiospampinato/infinity-map) released under the MIT License. 7 | */ 8 | 9 | class XMap { 10 | #pool; 11 | 12 | /** 13 | * Creates an instance of XMap. 14 | * @param {Array} [entries=[]] - Initial set of key-value pairs to add to the map. 15 | */ 16 | 17 | constructor(entries = []) { 18 | /** 19 | * @private 20 | * @type {Array>} - Array of Map instances used to store key-value pairs. 21 | */ 22 | this.#pool = [new Map(entries)]; 23 | } 24 | 25 | /** 26 | * Gets the total number of key-value pairs across all maps. 27 | * @return {number} The total number of key-value pairs. 28 | */ 29 | get size() { 30 | return this.#pool.reduce((sum, map) => sum + map.size, 0); 31 | } 32 | 33 | /** 34 | * Removes all key-value pairs from the XMap. 35 | */ 36 | clear() { 37 | this.#pool = [new Map()]; 38 | } 39 | 40 | /** 41 | * Deletes a key-value pair from the XMap. 42 | * @param {any} key - The key of the element to remove. 43 | * @return {boolean} True if an element in the XMap existed and has been removed, 44 | * or false if the element does not exist. 45 | */ 46 | delete(key) { 47 | return this.#pool.some(map => map.delete(key)); 48 | } 49 | 50 | /** 51 | * Returns a specified element from the XMap. 52 | * @param {any} key - The key of the element to return. 53 | * @return {any} The element associated with the specified key, or undefined if the 54 | * key can't be found in the XMap. 55 | */ 56 | get(key) { 57 | for (const map of this.#pool) { 58 | if (map.has(key)) { 59 | return map.get(key); 60 | } 61 | } 62 | } 63 | 64 | /** 65 | * Checks whether an element with the specified key exists in the XMap. 66 | * @param {any} key - The key to check for. 67 | * @return {boolean} True if an element with the specified key exists, otherwise false. 68 | */ 69 | has(key) { 70 | return this.#pool.some(map => map.has(key)); 71 | } 72 | 73 | /** 74 | * Sets the value for the key in the XMap. 75 | * @param {any} key - The key of the element to add. 76 | * @param {any} value - The value of the element to add. 77 | * @return {XMap} The XMap instance, allowing for chaining. 78 | */ 79 | set(key, value) { 80 | let targetMap = this.#pool[0]; 81 | 82 | if (this.#pool.length > 1) { 83 | for (const map of this.#pool) { 84 | if (map.has(key)) { 85 | targetMap = map; 86 | break; 87 | } 88 | } 89 | } 90 | 91 | targetMap.set(key, value); 92 | 93 | if (this.#pool[0].size === 16777215) { 94 | this.#pool.unshift(new Map()); 95 | } 96 | 97 | return this; 98 | } 99 | 100 | /** 101 | * Creates an iterator that contains all [key, value] pairs for each element in the XMap in insertion order. 102 | * @return {Iterator} An iterator for the XMap entries. 103 | */ 104 | *[Symbol.iterator]() { 105 | for (const map of this.#pool) { 106 | yield* map; 107 | } 108 | } 109 | 110 | /** 111 | * Creates an iterator that contains the keys for each element in the XMap in insertion order. 112 | * @return {Iterator} An iterator for the XMap keys. 113 | */ 114 | *keys() { 115 | for (const map of this.#pool) { 116 | yield* map.keys(); 117 | } 118 | } 119 | 120 | /** 121 | * Creates an iterator that contains the values for each element in the XMap in insertion order. 122 | * @return {Iterator} An iterator for the XMap values. 123 | */ 124 | *values() { 125 | for (const map of this.#pool) { 126 | yield* map.values(); 127 | } 128 | } 129 | 130 | /** 131 | * Creates an iterator that contains an array of [key, value] for each element in the XMap in insertion order. 132 | * @return {Iterator} An iterator for the XMap entries. 133 | */ 134 | *entries() { 135 | for (const map of this.#pool) { 136 | yield* map.entries(); 137 | } 138 | } 139 | 140 | /** 141 | * Executes a provided function once for each XMap element. 142 | * @param {Function} callback - Function to execute for each element, taking three arguments: 143 | * value, key, and the XMap instance. 144 | * @param {any} [thisArg] - Value to use as this when executing callback. 145 | */ 146 | forEach(callback, thisArg) { 147 | for (const [key, value] of this) { 148 | callback.call(thisArg, value, key, this); 149 | } 150 | } 151 | } 152 | 153 | export { XMap }; -------------------------------------------------------------------------------- /src/js/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const template = { 4 | id: 0, 5 | bad_ratings: 0, 6 | good_ratings: 0, 7 | ok_ratings: 0, 8 | daily_installs: 0, 9 | total_installs: 0, 10 | name: 'NOT FOUND', 11 | description: 'NOT FOUND', 12 | version: '0.0.0', 13 | url: 'about:blank', 14 | code_url: 'about:blank', 15 | created_at: Date.now(), 16 | code_updated_at: Date.now(), 17 | locale: 'NOT FOUND', 18 | deleted: false, 19 | users: [] 20 | }; 21 | /** Unsupport host for search engines */ 22 | const engineUnsupported = { 23 | greasyfork: ['pornhub.com'], 24 | sleazyfork: ['pornhub.com'], 25 | openuserjs: [], 26 | github: [] 27 | }; 28 | const builtinList = { 29 | local: /localhost|router|gov|(\d+\.){3}\d+/, 30 | finance: 31 | /school|pay|bank|money|cart|checkout|authorize|bill|wallet|venmo|zalo|skrill|bluesnap|coin|crypto|currancy|insurance|finance/, 32 | social: /login|join|signin|signup|sign-up|password|reset|password_reset/, 33 | unsupported: { 34 | host: 'fakku.net', 35 | pathname: '/hentai/.+/read/page/.+' 36 | } 37 | }; 38 | const BLANK_PAGE = 'about:blank'; 39 | /** Lets highlight me :) */ 40 | const authorID = 166061; 41 | /** 42 | * Some UserJS I personally enjoy - `https://greasyfork.org/scripts/{id}` 43 | */ 44 | const goodUserJS = [ 45 | 33005, 46 | 394820, 47 | 438684, 48 | 4870, 49 | 394420, 50 | 25068, 51 | 483444, 52 | 1682, 53 | 22587, 54 | 789, 55 | 28497, 56 | 386908, 57 | 24204, 58 | 404443, 59 | 4336, 60 | 368183, 61 | 393396, 62 | 473830, 63 | 12179, 64 | 423001, 65 | 376510, 66 | 23840, 67 | 40525, 68 | 6456, 69 | 'https://openuserjs.org/install/Patabugen/Always_Remember_Me.user.js', 70 | 'https://openuserjs.org/install/nokeya/Direct_links_out.user.js', 71 | 'https://github.com/jijirae/y2monkey/raw/main/y2monkey.user.js', 72 | 'https://github.com/jijirae/r2monkey/raw/main/r2monkey.user.js', 73 | 'https://github.com/TagoDR/MangaOnlineViewer/raw/master/Manga_OnlineViewer.user.js', 74 | 'https://github.com/jesus2099/konami-command/raw/master/INSTALL-USER-SCRIPT.user.js', 75 | 'https://github.com/TagoDR/MangaOnlineViewer/raw/master/dist/Manga_OnlineViewer_Adult.user.js' 76 | ]; 77 | /** Remove UserJS from banned accounts */ 78 | const badUserJS = [478597]; 79 | 80 | export { 81 | authorID, 82 | template, 83 | engineUnsupported, 84 | builtinList, 85 | BLANK_PAGE, 86 | goodUserJS, 87 | badUserJS 88 | } 89 | -------------------------------------------------------------------------------- /src/js/container.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { isEmpty, isFN } from './util.js'; 4 | import { BLANK_PAGE, engineUnsupported } from './constants.js'; 5 | import { webext } from './ext.js'; 6 | 7 | const MUJS_ORIGIN = webext.runtime.getURL('').replace(/\/$/, ''); 8 | 9 | /** 10 | * @type { import("../typings/UserJS.d.ts").Container } 11 | */ 12 | export class BaseContainer { 13 | constructor() { 14 | this.toElem = this.toElem.bind(this); 15 | this.cache = new Map(); 16 | this.userjsCache = new Map(); 17 | } 18 | 19 | checkBlacklist() { 20 | return true; 21 | } 22 | 23 | toElem() { 24 | return Array.from(this).map(({ _mujs }) => { 25 | return _mujs.root; 26 | }); 27 | } 28 | 29 | *[Symbol.iterator]() { 30 | const arr = Array.from(this.userjsCache.values()).filter(({ _mujs }) => { 31 | return !isEmpty(_mujs) && _mujs.info.engine.enabled; 32 | }); 33 | for (const userjs of arr) { 34 | yield userjs; 35 | } 36 | } 37 | } 38 | 39 | export class BaseList { 40 | intEngines; 41 | /** 42 | * @type { string | undefined } 43 | */ 44 | intHost; 45 | /** 46 | * @param {string} [hostname] 47 | * @param {BaseContainer} [container] 48 | * @param {import("../typings/types").config} [cfg] 49 | */ 50 | constructor(hostname = undefined, container = new BaseContainer(), cfg = {}) { 51 | this.groupBy = this.groupBy.bind(this); 52 | this.container = container; 53 | this.intEngines = cfg.engines ?? []; 54 | this.host = hostname; 55 | } 56 | 57 | setEngines(engines = []) { 58 | const { host } = this; 59 | return engines.filter((e) => { 60 | if (!e.enabled) { 61 | return false; 62 | } 63 | const v = engineUnsupported[e.name] ?? []; 64 | if (v.includes(host)) { 65 | return false; 66 | } 67 | return true; 68 | }); 69 | } 70 | 71 | set engines(engines) { 72 | this.intEngines = this.setEngines(engines); 73 | } 74 | 75 | get engines() { 76 | return this.intEngines; 77 | } 78 | 79 | set host(hostname) { 80 | if (!MUJS_ORIGIN.includes(hostname)) { 81 | this.intHost = hostname ?? BLANK_PAGE; 82 | } else if (isEmpty(this.intHost)) { 83 | this.intHost = BLANK_PAGE; 84 | } 85 | 86 | if (!this.container.cache.has(hostname)) { 87 | const engineTemplate = {}; 88 | for (const engine of this.engines) { 89 | engineTemplate[engine.name] = []; 90 | } 91 | this.container.cache.set(hostname, engineTemplate); 92 | } 93 | this.blacklisted = this.container.checkBlacklist(hostname); 94 | this.intEngines = this.setEngines(this.engines); 95 | this.domain = this.getDomain(this.intHost); 96 | } 97 | 98 | get host() { 99 | return this.intHost; 100 | } 101 | 102 | /** 103 | * @template { string } S 104 | * @param { S } str 105 | * @returns { S | 'all-sites' } 106 | */ 107 | getDomain(str) { 108 | if (str === '*') { 109 | return 'all-sites'; 110 | } 111 | return str.split('.').at(-2) ?? BLANK_PAGE; 112 | } 113 | 114 | groupBy() { 115 | const arr = Array.from(this); 116 | const callback = ({ _mujs }) => _mujs.info.engine.name; 117 | if (isFN(Object.groupBy)) { 118 | return Object.groupBy(arr, callback); 119 | } 120 | /** [Object.groupBy polyfill](https://gist.github.com/gtrabanco/7c97bd41aa74af974fa935bfb5044b6e) */ 121 | return arr.reduce((acc = {}, ...args) => { 122 | const key = callback(...args); 123 | acc[key] ??= []; 124 | acc[key].push(args[0]); 125 | return acc; 126 | }, {}); 127 | } 128 | 129 | *[Symbol.iterator]() { 130 | const { intHost, engines } = this; 131 | const arr = Array.from(this.container).filter( 132 | ({ _mujs }) => 133 | _mujs.info.host === intHost && 134 | engines.find((engine) => engine.enabled && engine.name === _mujs.info.engine.name) 135 | ); 136 | for (const userjs of arr) { 137 | yield userjs; 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/js/ext.js: -------------------------------------------------------------------------------- 1 | export const webext = 2 | self.browser instanceof Object && self.browser instanceof Element === false 3 | ? self.browser 4 | : self.chrome; 5 | export const runtime = webext.runtime; 6 | 7 | /******************************************************************************/ 8 | 9 | // The extension's service worker can be evicted at any time, so when we 10 | // send a message, we try a few more times when the message fails to be sent. 11 | 12 | export function sendMessage(msg) { 13 | return new Promise((resolve, reject) => { 14 | let i = 5; 15 | const send = () => { 16 | runtime 17 | .sendMessage(msg) 18 | .then((response) => { 19 | resolve(response); 20 | }) 21 | .catch((reason) => { 22 | i -= 1; 23 | if (i <= 0) { 24 | reject(reason); 25 | } else { 26 | setTimeout(send, 200); 27 | } 28 | }); 29 | }; 30 | send(); 31 | }); 32 | } 33 | 34 | /******************************************************************************/ 35 | -------------------------------------------------------------------------------- /src/js/i18n.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const i18n = 4 | self.browser instanceof Object && self.browser instanceof Element === false 5 | ? self.browser.i18n 6 | : self.chrome.i18n; 7 | 8 | class i18nHandler { 9 | /** 10 | * @param {string | Date | number} str 11 | */ 12 | static toDate(str = '') { 13 | return new Intl.DateTimeFormat(navigator.language).format( 14 | typeof str === 'string' ? new Date(str) : str 15 | ); 16 | } 17 | /** 18 | * @param {number | bigint} number 19 | */ 20 | static toNumber(number) { 21 | return new Intl.NumberFormat(navigator.language).format(number); 22 | } 23 | /** 24 | * @param {...string} args 25 | */ 26 | static i18n$(...args) { 27 | return i18n.getMessage(...args); 28 | } 29 | } 30 | 31 | const { i18n$, toDate, toNumber } = i18nHandler; 32 | const language = { i18n$, toDate, toNumber }; 33 | 34 | const isBackgroundProcess = document && document.title === 'Magic UserJS+ Background Page'; 35 | 36 | if (isBackgroundProcess !== true) { 37 | // Helper to deal with the i18n'ing of HTML files. 38 | i18n.render = function (context) { 39 | const docu = document; 40 | const root = context || docu; 41 | for (const elem of root.querySelectorAll('[data-i18n]')) { 42 | const text = i18n$(elem.getAttribute('data-i18n')); 43 | if (!text) { 44 | continue; 45 | } 46 | elem.textContent = text; 47 | } 48 | for (const elem of root.querySelectorAll('[placeholder]')) { 49 | const text = i18n$(elem.getAttribute('placeholder')); 50 | if (text === '') { 51 | continue; 52 | } 53 | elem.setAttribute('placeholder', text); 54 | } 55 | }; 56 | i18n.render(); 57 | } 58 | 59 | export { i18n, i18n$, language }; 60 | -------------------------------------------------------------------------------- /src/js/logger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // #region Console 4 | const dbg = (...msg) => { 5 | const dt = new Date(); 6 | console.debug( 7 | '[%cMagic Userscript+%c] %cDBG', 8 | 'color: rgb(29, 155, 240);', 9 | '', 10 | 'color: rgb(255, 212, 0);', 11 | `[${dt.getHours()}:${('0' + dt.getMinutes()).slice(-2)}:${('0' + dt.getSeconds()).slice(-2)}]`, 12 | ...msg 13 | ); 14 | }; 15 | const err = (...msg) => { 16 | console.error( 17 | '[%cMagic Userscript+%c] %cERROR', 18 | 'color: rgb(29, 155, 240);', 19 | '', 20 | 'color: rgb(249, 24, 128);', 21 | ...msg 22 | ); 23 | }; 24 | const info = (...msg) => { 25 | console.info( 26 | '[%cMagic Userscript+%c] %cINF', 27 | 'color: rgb(29, 155, 240);', 28 | '', 29 | 'color: rgb(0, 186, 124);', 30 | ...msg 31 | ); 32 | }; 33 | const log = (...msg) => { 34 | console.log( 35 | '[%cMagic Userscript+%c] %cLOG', 36 | 'color: rgb(29, 155, 240);', 37 | '', 38 | 'color: rgb(219, 160, 73);', 39 | ...msg 40 | ); 41 | }; 42 | // #endregion 43 | 44 | export { dbg, err, info, log }; 45 | -------------------------------------------------------------------------------- /src/js/messager.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { webext } from './ext.js'; 4 | 5 | export const hermes = { 6 | port: null, 7 | portTimer: null, 8 | portTimerDelay: 10000, 9 | msgIdGenerator: 1, 10 | pending: new Map(), 11 | waitStartTime: 0, 12 | shuttingDown: false, 13 | shutdown: function () { 14 | this.shuttingDown = true; 15 | this.destroyPort(); 16 | }, 17 | disconnectListener: function () { 18 | void webext.runtime.lastError; 19 | this.port = null; 20 | this.destroyPort(); 21 | }, 22 | disconnectListenerBound: null, 23 | messageListener: function (details) { 24 | if (typeof details !== 'object' || details === null) { 25 | return; 26 | } 27 | // Response to specific message previously sent 28 | if (details.msgId !== undefined) { 29 | const resolver = this.pending.get(details.msgId); 30 | if (resolver !== undefined) { 31 | this.pending.delete(details.msgId); 32 | resolver(details.msg); 33 | return; 34 | } 35 | } 36 | }, 37 | messageListenerBound: null, 38 | canDestroyPort: function () { 39 | return this.pending.size === 0; 40 | }, 41 | portPoller: function () { 42 | this.portTimer = null; 43 | if (this.port !== null && this.canDestroyPort()) { 44 | return this.destroyPort(); 45 | } 46 | this.portTimer = setTimeout(this.portPollerBound, this.portTimerDelay); 47 | this.portTimerDelay = Math.min(this.portTimerDelay * 2, 60 * 60 * 1000); 48 | }, 49 | portPollerBound: null, 50 | destroyPort: function () { 51 | if (this.portTimer !== null) { 52 | clearTimeout(this.portTimer); 53 | this.portTimer = null; 54 | } 55 | const port = this.port; 56 | if (port !== null) { 57 | port.disconnect(); 58 | port.onMessage.removeListener(this.messageListenerBound); 59 | port.onDisconnect.removeListener(this.disconnectListenerBound); 60 | this.port = null; 61 | } 62 | // service pending callbacks 63 | if (this.pending.size !== 0) { 64 | const pending = this.pending; 65 | this.pending = new Map(); 66 | for (const resolver of pending.values()) { 67 | resolver(); 68 | } 69 | } 70 | }, 71 | createPort: function () { 72 | if (this.shuttingDown) { 73 | return null; 74 | } 75 | if (this.messageListenerBound === null) { 76 | this.messageListenerBound = this.messageListener.bind(this); 77 | this.disconnectListenerBound = this.disconnectListener.bind(this); 78 | this.portPollerBound = this.portPoller.bind(this); 79 | } 80 | try { 81 | this.port = webext.runtime.connect({ name: 'hermes' }) || null; 82 | // eslint-disable-next-line no-unused-vars 83 | } catch (ex) { 84 | this.port = null; 85 | } 86 | if (this.port === null) { 87 | return null; 88 | } 89 | this.port.onMessage.addListener(this.messageListenerBound); 90 | this.port.onDisconnect.addListener(this.disconnectListenerBound); 91 | this.portTimerDelay = 10000; 92 | if (this.portTimer === null) { 93 | this.portTimer = setTimeout(this.portPollerBound, this.portTimerDelay); 94 | } 95 | return this.port; 96 | }, 97 | getPort: function () { 98 | return this.port ?? this.createPort(); 99 | }, 100 | send: function (channel, msg) { 101 | // Too large a gap between the last request and the last response means 102 | // the main process is no longer reachable: memory leaks and bad 103 | // performance become a risk -- especially for long-lived, dynamic 104 | // pages. Guard against this. 105 | if (this.pending.size > 64) { 106 | if (Date.now() - this.waitStartTime > 60000) { 107 | this.shutdown(); 108 | } 109 | } 110 | const port = this.getPort(); 111 | if (port === null) { 112 | return Promise.resolve(); 113 | } 114 | if (this.pending.size === 0) { 115 | this.waitStartTime = Date.now(); 116 | } 117 | const msgId = this.msgIdGenerator++; 118 | const promise = new Promise((resolve) => { 119 | this.pending.set(msgId, resolve); 120 | }); 121 | port.postMessage({ channel, msgId, msg }); 122 | return promise; 123 | } 124 | }; 125 | -------------------------------------------------------------------------------- /src/js/mu.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-const, no-unused-vars */ 2 | 'use strict'; 3 | 4 | /** @type { import("./mu.d.ts").webext } */ 5 | let webext = (self.webext = typeof browser == 'undefined' ? chrome : browser); 6 | /** @type { import("./mu.d.ts").userjs } */ 7 | let userjs = (self.userjs = {}); 8 | 9 | /** Skip text/plain documents */ 10 | if ( 11 | (document instanceof Document || 12 | (document instanceof XMLDocument && document.createElement('div') instanceof HTMLDivElement)) && 13 | /^image\/|^text\/plain/.test(document.contentType || '') === false && 14 | (self.userjs instanceof Object === false || userjs.UserJS !== true) 15 | ) { 16 | userjs = self.userjs = { UserJS: true }; 17 | } 18 | 19 | void 0; -------------------------------------------------------------------------------- /src/js/network.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import { isEmpty, isFN } from './util.js'; 3 | 4 | /** 5 | * @type { import("../typings/UserJS.d.ts").Network } 6 | */ 7 | const Network = { 8 | async req(url, method = 'GET', responseType = 'json', data = {}) { 9 | if (isEmpty(url)) { 10 | throw new Error('"url" parameter is empty'); 11 | } 12 | data = Object.assign({}, data); 13 | method = this.bscStr(method, false); 14 | responseType = this.bscStr(responseType); 15 | const params = { 16 | method, 17 | ...data 18 | }; 19 | return new Promise((resolve, reject) => { 20 | fetch(url, params) 21 | .then((response_1) => { 22 | if (!response_1.ok) reject(response_1); 23 | const check = (str_2 = 'text') => { 24 | return isFN(response_1[str_2]) ? response_1[str_2]() : response_1; 25 | }; 26 | if (responseType.match(/buffer/)) { 27 | resolve(check('arrayBuffer')); 28 | } else if (responseType.match(/json/)) { 29 | resolve(check('json')); 30 | } else if (responseType.match(/text/)) { 31 | resolve(check('text')); 32 | } else if (responseType.match(/blob/)) { 33 | resolve(check('blob')); 34 | } else if (responseType.match(/formdata/)) { 35 | resolve(check('formData')); 36 | } else if (responseType.match(/clone/)) { 37 | resolve(check('clone')); 38 | } else if (responseType.match(/document/)) { 39 | const respTxt = check('text'); 40 | const domParser = new DOMParser(); 41 | if (respTxt instanceof Promise) { 42 | respTxt.then((txt) => { 43 | const doc = domParser.parseFromString(txt, 'text/html'); 44 | resolve(doc); 45 | }); 46 | } else { 47 | const doc = domParser.parseFromString(respTxt, 'text/html'); 48 | resolve(doc); 49 | } 50 | } else { 51 | resolve(response_1); 52 | } 53 | }) 54 | .catch(reject); 55 | }); 56 | }, 57 | format(bytes, decimals = 2) { 58 | if (Number.isNaN(bytes)) return `0 ${this.sizes[0]}`; 59 | const k = 1024; 60 | const dm = decimals < 0 ? 0 : decimals; 61 | const i = Math.floor(Math.log(bytes) / Math.log(k)); 62 | return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${this.sizes[i]}`; 63 | }, 64 | sizes: ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], 65 | bscStr(str = '', lowerCase = true) { 66 | const txt = str[lowerCase ? 'toLowerCase' : 'toUpperCase'](); 67 | return txt.replaceAll(/\W/g, ''); 68 | } 69 | }; 70 | 71 | export default Network; 72 | -------------------------------------------------------------------------------- /src/js/querySelector.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const SafeAnimationFrame = class { 4 | constructor(callback) { 5 | this.fid = this.tid = undefined; 6 | this.callback = callback; 7 | } 8 | start(delay) { 9 | if (delay === undefined) { 10 | if (this.fid === undefined) { 11 | this.fid = requestAnimationFrame(() => { 12 | this.onRAF(); 13 | }); 14 | } 15 | if (this.tid === undefined) { 16 | this.tid = setTimeout(() => { 17 | this.onSTO(); 18 | }, 20000); 19 | } 20 | return; 21 | } 22 | if (this.fid === undefined && this.tid === undefined) { 23 | this.tid = setTimeout(() => { 24 | this.macroToMicro(); 25 | }, delay); 26 | } 27 | } 28 | clear() { 29 | if (this.fid !== undefined) { 30 | cancelAnimationFrame(this.fid); 31 | this.fid = undefined; 32 | } 33 | if (this.tid !== undefined) { 34 | clearTimeout(this.tid); 35 | this.tid = undefined; 36 | } 37 | } 38 | macroToMicro() { 39 | this.tid = undefined; 40 | this.start(); 41 | } 42 | onRAF() { 43 | if (this.tid !== undefined) { 44 | clearTimeout(this.tid); 45 | this.tid = undefined; 46 | } 47 | this.fid = undefined; 48 | this.callback(); 49 | } 50 | onSTO() { 51 | if (this.fid !== undefined) { 52 | cancelAnimationFrame(this.fid); 53 | this.fid = undefined; 54 | } 55 | this.tid = undefined; 56 | this.callback(); 57 | } 58 | }; 59 | /** 60 | * @type { import("../typings/types.d.ts").isElem } 61 | */ 62 | const isElem = (obj) => { 63 | /** @type { string } */ 64 | const s = Object.prototype.toString.call(obj); 65 | return s.includes('Element'); 66 | }; 67 | /** 68 | * @type { import("../typings/types.d.ts").normalizeTarget } 69 | */ 70 | const normalizeTarget = (target, toQuery = true, root) => { 71 | if (Object.is(target, null) || Object.is(target, undefined)) { 72 | return []; 73 | } 74 | if (Array.isArray(target)) { 75 | return target; 76 | } 77 | if (typeof target === 'string') { 78 | return toQuery ? Array.from((root || document).querySelectorAll(target)) : [target]; 79 | } 80 | if (isElem(target)) { 81 | return [target]; 82 | } 83 | return Array.from(target); 84 | }; 85 | 86 | /** 87 | * @param { string } selector 88 | * @param { (this: EventTarget, event: Event) => void } callback 89 | */ 90 | const makeEventHandler = (selector, callback) => { 91 | /** 92 | * @param { Event } event 93 | */ 94 | return function (event) { 95 | const dispatcher = event.currentTarget; 96 | if ( 97 | dispatcher instanceof HTMLElement === false || 98 | typeof dispatcher.querySelectorAll !== 'function' 99 | ) { 100 | return; 101 | } 102 | const receiver = event.target; 103 | const ancestor = receiver.closest(selector); 104 | if (ancestor === receiver && ancestor !== dispatcher && dispatcher.contains(ancestor)) { 105 | callback.call(receiver, event); 106 | } 107 | }; 108 | }; 109 | 110 | class dom { 111 | /** 112 | * @template { HTMLElement } T 113 | * @param { T } target 114 | * @param { string } attr 115 | * @param value 116 | */ 117 | static attr(target, attr, value = undefined) { 118 | for (const elem of normalizeTarget(target)) { 119 | if (value === undefined) { 120 | return elem.getAttribute(attr); 121 | } 122 | if (value === null) { 123 | elem.removeAttribute(attr); 124 | } else { 125 | elem.setAttribute(attr, value); 126 | } 127 | } 128 | } 129 | 130 | /** 131 | * @template { HTMLElement } T 132 | * @param { T } target 133 | */ 134 | static clone(target) { 135 | return normalizeTarget(target)[0].cloneNode(true); 136 | } 137 | 138 | /** 139 | * @param { string } a 140 | */ 141 | static create(a) { 142 | if (typeof a === 'string') { 143 | return document.createElement(a); 144 | } 145 | } 146 | 147 | /** 148 | * @template { HTMLElement } T 149 | * @param { T } target 150 | * @param { string } prop 151 | * @param value 152 | * @returns { keyof T | void } 153 | */ 154 | static prop(target, prop, value = undefined) { 155 | for (const elem of normalizeTarget(target)) { 156 | if (value === undefined) { 157 | return elem[prop]; 158 | } 159 | elem[prop] = value; 160 | } 161 | } 162 | 163 | /** 164 | * @template { HTMLElement } T 165 | * @param { T } target 166 | * @param { string } [text] 167 | */ 168 | static text(target, text) { 169 | const targets = normalizeTarget(target); 170 | if (text === undefined) { 171 | return targets.length !== 0 ? targets[0].textContent : undefined; 172 | } 173 | for (const elem of targets) { 174 | elem.textContent = text; 175 | } 176 | } 177 | 178 | /** 179 | * @template { HTMLElement } T 180 | * @param { T } target 181 | */ 182 | static remove(target) { 183 | for (const elem of normalizeTarget(target)) { 184 | elem.remove(); 185 | } 186 | } 187 | 188 | // target, type, callback, [options] 189 | // target, type, subtarget, callback, [options] 190 | 191 | /** 192 | * @template { HTMLElement } T 193 | * @template { keyof HTMLElementEventMap } K 194 | * @param { T } target 195 | * @param { K } type 196 | * @param subtarget 197 | * @param { (this: E, ev: HTMLElementEventMap[K]) => any } callback 198 | * @param { AddEventListenerOptions | boolean } options 199 | */ 200 | static on(target, type, subtarget, callback, options) { 201 | if (typeof subtarget === 'function') { 202 | options = callback; 203 | callback = subtarget; 204 | subtarget = undefined; 205 | if (typeof options === 'boolean') { 206 | options = { capture: true }; 207 | } 208 | } else { 209 | callback = makeEventHandler(subtarget, callback); 210 | if (options === undefined || typeof options === 'boolean') { 211 | options = { capture: true }; 212 | } else { 213 | options.capture = true; 214 | } 215 | } 216 | const targets = 217 | target instanceof Window || target instanceof Document ? [target] : normalizeTarget(target); 218 | for (const elem of targets) { 219 | elem.addEventListener(type, callback, options); 220 | } 221 | } 222 | 223 | /** 224 | * @template { HTMLElement } T 225 | * @template { keyof HTMLElementEventMap } K 226 | * @param { T } target 227 | * @param { K } type 228 | * @param { (this: E, ev: HTMLElementEventMap[K]) => any } callback 229 | * @param { AddEventListenerOptions | boolean } options 230 | */ 231 | static off(target, type, callback, options) { 232 | if (typeof callback !== 'function') { 233 | return; 234 | } 235 | if (typeof options === 'boolean') { 236 | options = { capture: true }; 237 | } 238 | const targets = 239 | target instanceof Window || target instanceof Document ? [target] : normalizeTarget(target); 240 | for (const elem of targets) { 241 | elem.removeEventListener(type, callback, options); 242 | } 243 | } 244 | 245 | /** 246 | * @template { HTMLElement } T 247 | * @param { T } target 248 | */ 249 | static click(target) { 250 | for (const elem of normalizeTarget(target)) { 251 | elem.dispatchEvent(new MouseEvent('click')); 252 | } 253 | } 254 | } 255 | 256 | dom.cl = class { 257 | /** 258 | * @template { HTMLElement } T 259 | * @param { T } target 260 | * @param { string | string[] } name 261 | */ 262 | static add(target, name) { 263 | if (Array.isArray(name)) { 264 | for (const elem of normalizeTarget(target)) { 265 | elem.classList.add(...name); 266 | } 267 | } else { 268 | for (const elem of normalizeTarget(target)) { 269 | elem.classList.add(name); 270 | } 271 | } 272 | } 273 | 274 | /** 275 | * @template { HTMLElement } T 276 | * @param { T } target 277 | * @param { string | string[] } name 278 | */ 279 | static remove(target, name) { 280 | if (Array.isArray(name)) { 281 | for (const elem of normalizeTarget(target)) { 282 | elem.classList.remove(...name); 283 | } 284 | } else { 285 | for (const elem of normalizeTarget(target)) { 286 | elem.classList.remove(name); 287 | } 288 | } 289 | } 290 | 291 | /** 292 | * @template { HTMLElement } T 293 | * @param { T } target 294 | * @param { string } name 295 | * @param { boolean } [state] 296 | */ 297 | static toggle(target, name, state) { 298 | let r; 299 | for (const elem of normalizeTarget(target)) { 300 | r = elem.classList.toggle(name, state); 301 | } 302 | return r; 303 | } 304 | 305 | /** 306 | * @template { HTMLElement } T 307 | * @param { T } target 308 | * @param { string } name 309 | */ 310 | static has(target, name) { 311 | for (const elem of normalizeTarget(target)) { 312 | if (elem.classList.contains(name)) { 313 | return true; 314 | } 315 | } 316 | return false; 317 | } 318 | }; 319 | 320 | /******************************************************************************/ 321 | 322 | /** 323 | * @type { import("../typings/types.d.ts").qsA } 324 | */ 325 | const qsA = (selectors, root) => { 326 | try { 327 | return (root || document).querySelectorAll(selectors); 328 | } catch (ex) { 329 | console.error(ex); 330 | } 331 | return []; 332 | }; 333 | /** 334 | * @type { import("../typings/types.d.ts").qs } 335 | */ 336 | const qs = (selector, root) => { 337 | try { 338 | return (root || document).querySelector(selector); 339 | } catch (ex) { 340 | console.error(ex); 341 | } 342 | return null; 343 | }; 344 | 345 | /** 346 | * @type { import("../typings/WebExt.d.ts").query } 347 | */ 348 | const query = async (selector, root) => { 349 | let el = null; 350 | try { 351 | el = root || document; 352 | while (el.querySelector(selector) === null) { 353 | // await new Promise((resolve) => requestAnimationFrame(resolve)); 354 | await new Promise((resolve) => { 355 | const queryTimer = new SafeAnimationFrame(resolve); 356 | queryTimer.start(1); 357 | }); 358 | } 359 | return el.querySelector(selector); 360 | } catch (ex) { 361 | console.error(ex); 362 | } 363 | return el; 364 | }; 365 | 366 | export { dom, normalizeTarget, qs, qsA, query, SafeAnimationFrame }; 367 | -------------------------------------------------------------------------------- /src/js/request-code.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { err } from './logger.js'; 4 | import Network from './network.js'; 5 | import { isNull, isBlank, isEmpty, union } from './util.js'; 6 | 7 | const TLD_EXPANSION = ['com', 'net', 'org', 'de', 'co.uk']; 8 | const APPLIES_TO_ALL_PATTERNS = [ 9 | 'http://*', 10 | 'https://*', 11 | 'http://*/*', 12 | 'https://*/*', 13 | 'http*://*', 14 | 'http*://*/*', 15 | '*', 16 | '*://*', 17 | '*://*/*', 18 | 'http*' 19 | ]; 20 | class ParseUserJS { 21 | /** 22 | * @type { string } 23 | */ 24 | code; 25 | /** 26 | * @type { string } 27 | */ 28 | data_meta_block; 29 | /** 30 | * @type { string } 31 | */ 32 | data_code_block; 33 | /** 34 | * @type { { [meta: string]: string | string[] | { [resource: string]: string } } } 35 | */ 36 | data_meta; 37 | /** 38 | * @type { {text: string;domain: boolean;tld_extra: boolean}[] } 39 | */ 40 | data_names; 41 | constructor(code, isUserCSS) { 42 | this.isUserCSS = isUserCSS ?? false; 43 | this.META_START_COMMENT = this.isUserCSS ? '/* ==UserStyle==' : '// ==UserScript=='; 44 | this.META_END_COMMENT = this.isUserCSS ? '==/UserStyle== */' : '// ==/UserScript=='; 45 | if (code) { 46 | this.code = code; 47 | this.get_meta_block(); 48 | this.get_code_block(); 49 | this.parse_meta(); 50 | this.calculate_applies_to_names(); 51 | } 52 | } 53 | get_meta_block() { 54 | if (isEmpty(this.code)) { 55 | return null; 56 | } 57 | if (this.data_meta_block) { 58 | return this.data_meta_block; 59 | } 60 | const start_block = this.code.indexOf(this.META_START_COMMENT); 61 | if (isNull(start_block)) { 62 | return null; 63 | } 64 | const end_block = this.code.indexOf(this.META_END_COMMENT, start_block); 65 | if (isNull(end_block)) { 66 | return null; 67 | } 68 | const meta_block = this.code.substring(start_block + this.META_START_COMMENT.length, end_block); 69 | this.data_meta_block = meta_block; 70 | return this.data_meta_block; 71 | } 72 | get_code_block() { 73 | if (isEmpty(this.code)) { 74 | return null; 75 | } 76 | if (this.data_code_block) { 77 | return this.data_code_block; 78 | } 79 | const start_block = this.code.indexOf(this.META_START_COMMENT); 80 | if (isNull(start_block)) { 81 | return null; 82 | } 83 | const end_block = this.code.indexOf(this.META_END_COMMENT, start_block); 84 | if (isNull(end_block)) { 85 | return null; 86 | } 87 | const code_block = this.code.substring( 88 | end_block + this.META_END_COMMENT.length, 89 | this.code.length 90 | ); 91 | this.data_code_block = code_block 92 | .split('\n') 93 | .filter((l) => !isEmpty(l)) 94 | .join('\n'); 95 | return this.data_code_block; 96 | } 97 | parse_meta() { 98 | if (isEmpty(this.code)) { 99 | return null; 100 | } 101 | if (this.data_meta) { 102 | return this.data_meta; 103 | } 104 | const meta = {}; 105 | const meta_block_map = new Map(); 106 | for (const meta_line of this.get_meta_block().split('\n')) { 107 | const meta_match = /\/\/\s+@([a-zA-Z:-]+)\s+(.*)/.exec(meta_line); 108 | if (!meta_match) { 109 | continue; 110 | } 111 | const key = meta_match[1].trim(); 112 | const value = meta_match[2].trim(); 113 | if (!meta_block_map.has(key)) { 114 | meta_block_map.set(key, []); 115 | } 116 | const meta_map = meta_block_map.get(key); 117 | meta_map.push(value); 118 | meta_block_map.set(key, meta_map); 119 | } 120 | for (const [key, value] of meta_block_map) { 121 | if (value.length > 1) { 122 | meta[key] = value; 123 | } else { 124 | meta[key] = value[0]; 125 | } 126 | } 127 | this.data_meta = meta; 128 | return this.data_meta; 129 | } 130 | calculate_applies_to_names() { 131 | if (isEmpty(this.code)) { 132 | return null; 133 | } 134 | if (this.data_names) { 135 | return this.data_names; 136 | } 137 | let patterns = []; 138 | for (const [k, v] of Object.entries(this.parse_meta())) { 139 | if (/include|match/i.test(k)) { 140 | if (Array.isArray(v)) { 141 | patterns = patterns.concat(v); 142 | } else { 143 | patterns = patterns.concat([v]); 144 | } 145 | } 146 | } 147 | if (isEmpty(patterns)) { 148 | return []; 149 | } 150 | if (this.intersect(patterns, APPLIES_TO_ALL_PATTERNS)) { 151 | this.data_names = [ 152 | { 153 | domain: false, 154 | text: 'All sites', 155 | tld_extra: false 156 | } 157 | ]; 158 | return this.data_names; 159 | } 160 | this.data_names = ParseUserJS.getNames(patterns); 161 | return this.data_names; 162 | } 163 | intersect(a, ...arr) { 164 | return !isBlank([...new Set(a)].filter((v) => arr.every((b) => b.includes(v)))); 165 | } 166 | static getNames(patterns = []) { 167 | const name_map = new Map(); 168 | const addObj = (obj) => { 169 | if (name_map.has(obj.text)) { 170 | return; 171 | } 172 | name_map.set(obj.text, obj); 173 | }; 174 | for (let p of patterns) { 175 | try { 176 | const original_pattern = p; 177 | let pre_wildcards = []; 178 | if (p.match(/^\/(.*)\/$/)) { 179 | pre_wildcards = [p]; 180 | } else { 181 | let m = /^\*(https?:.*)/i.exec(p); 182 | if (m) { 183 | p = m[1]; 184 | } 185 | p = p 186 | .replace(/^\*:/i, 'http:') 187 | .replace(/^\*\/\//i, 'http://') 188 | .replace(/^http\*:/i, 'http:') 189 | .replace(/^(https?):([^/])/i, '$1://$2'); 190 | m = /^([a-z]+:\/\/)\*\.?([a-z0-9-]+(?:.[a-z0-9-]+)+.*)/i.exec(p); 191 | if (m) { 192 | p = m[1] + m[2]; 193 | } 194 | m = /^\*\.?([a-z0-9-]+\.[a-z0-9-]+.*)/i.exec(p); 195 | if (m) { 196 | p = `http://${m[1]}`; 197 | } 198 | m = /^http\*(?:\/\/)?\.?((?:[a-z0-9-]+)(?:\.[a-z0-9-]+)+.*)/i.exec(p); 199 | if (m) { 200 | p = `http://${m[1]}`; 201 | } 202 | m = /^([a-z]+:\/\/([a-z0-9-]+(?:\.[a-z0-9-]+)*\.))\*(.*)/.exec(p); 203 | if (m) { 204 | if (m[2].match(/A([0-9]+\.){2,}z/)) { 205 | p = `${m[1]}tld${m[3]}`; 206 | pre_wildcards = [p.split('*')[0]]; 207 | } else { 208 | pre_wildcards = [p]; 209 | } 210 | } else { 211 | pre_wildcards = [p]; 212 | } 213 | } 214 | for (const pre_wildcard of pre_wildcards) { 215 | try { 216 | const urlObj = new URL(pre_wildcard); 217 | const { host } = urlObj; 218 | if (isNull(host)) { 219 | addObj({ text: original_pattern, domain: false, tld_extra: false }); 220 | } else if (!host.includes('.') && host.includes('*')) { 221 | addObj({ text: original_pattern, domain: false, tld_extra: false }); 222 | } else if (host.endsWith('.tld')) { 223 | for (let i = 0; i < TLD_EXPANSION.length; i++) { 224 | const tld = TLD_EXPANSION[i]; 225 | addObj({ 226 | text: host.replace(/tld$/i, tld), 227 | domain: true, 228 | tld_extra: i != 0 229 | }); 230 | } 231 | } else if (host.endsWith('.')) { 232 | addObj({ 233 | text: host.slice(0, -1), 234 | domain: true, 235 | tld_extra: false 236 | }); 237 | } else { 238 | addObj({ 239 | text: host, 240 | domain: true, 241 | tld_extra: false 242 | }); 243 | } 244 | // eslint-disable-next-line no-unused-vars 245 | } catch (ex) { 246 | addObj({ text: original_pattern, domain: false, tld_extra: false }); 247 | } 248 | } 249 | } catch (ex) { 250 | err(ex); 251 | } 252 | } 253 | return [...name_map.values()]; 254 | } 255 | async request(code_url) { 256 | if (this.data_code_block) { 257 | return this; 258 | } 259 | /** @type { string } */ 260 | const code = await Network.req(code_url, 'GET', 'text').catch(err); 261 | if (typeof code !== 'string') { 262 | return this; 263 | } 264 | this.isUserCSS = /\.user\.css$/.test(code_url); 265 | this.META_START_COMMENT = this.isUserCSS ? '/* ==UserStyle==' : '// ==UserScript=='; 266 | this.META_END_COMMENT = this.isUserCSS ? '==/UserStyle== */' : '// ==/UserScript=='; 267 | 268 | this.code = code; 269 | this.get_meta_block(); 270 | this.get_code_block(); 271 | this.parse_meta(); 272 | this.calculate_applies_to_names(); 273 | 274 | const { data_meta } = this; 275 | if (Array.isArray(data_meta.grant)) { 276 | data_meta.grant = union(data_meta.grant); 277 | } 278 | if (data_meta.resource) { 279 | const obj = {}; 280 | if (typeof data_meta.resource === 'string') { 281 | const reg = /(.+)\s+(.+)/.exec(data_meta.resource); 282 | if (reg) { 283 | obj[reg[1].trim()] = reg[2]; 284 | } 285 | } else { 286 | for (const r of data_meta.resource) { 287 | const reg = /(.+)\s+(http.+)/.exec(r); 288 | if (reg) { 289 | obj[reg[1].trim()] = reg[2]; 290 | } 291 | } 292 | } 293 | data_meta.resource = obj; 294 | } 295 | Object.assign(this, { 296 | code_size: [Network.format(code.length)], 297 | meta: data_meta 298 | }); 299 | 300 | return this; 301 | } 302 | } 303 | 304 | export { ParseUserJS }; 305 | -------------------------------------------------------------------------------- /src/js/storage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { webext } from './ext.js'; 4 | 5 | let api = webext.storage.local; 6 | 7 | // #region DEFAULT_CONFIG 8 | /** 9 | * @type { import("../typings/types.d.ts").config } 10 | */ 11 | export const DEFAULT_CONFIG = { 12 | autofetch: true, 13 | // autoinject: true, 14 | autoSort: 'daily_installs', 15 | clearTabCache: true, 16 | // cache: true, 17 | // autoexpand: false, 18 | filterlang: false, 19 | sleazyredirect: false, 20 | // time: 10000, 21 | blacklist: ['userjs-local', 'userjs-finance', 'userjs-social', 'userjs-unsupported'], 22 | preview: { 23 | code: false, 24 | metadata: false 25 | }, 26 | engines: [ 27 | { 28 | enabled: true, 29 | name: 'greasyfork', 30 | query: encodeURIComponent('https://greasyfork.org/scripts/by-site/{host}.json?language=all') 31 | }, 32 | { 33 | enabled: false, 34 | name: 'sleazyfork', 35 | query: encodeURIComponent('https://sleazyfork.org/scripts/by-site/{host}.json?language=all') 36 | }, 37 | // { 38 | // enabled: false, 39 | // name: 'openuserjs', 40 | // query: encodeURIComponent('https://openuserjs.org/?q={host}') 41 | // }, 42 | { 43 | enabled: false, 44 | name: 'github', 45 | token: '', 46 | query: encodeURIComponent( 47 | 'https://api.github.com/search/code?q="// ==UserScript=="+{host}+ "// ==/UserScript=="+in:file+language:js&per_page=30' 48 | ) 49 | } 50 | ], 51 | theme: { 52 | 'even-row': '', 53 | 'odd-row': '', 54 | 'even-err': '', 55 | 'odd-err': '', 56 | 'background-color': '', 57 | 'gf-color': '', 58 | 'sf-color': '', 59 | 'border-b-color': '', 60 | 'gf-btn-color': '', 61 | 'sf-btn-color': '', 62 | 'sf-txt-color': '', 63 | 'txt-color': '', 64 | 'chck-color': '', 65 | 'chck-gf': '', 66 | 'chck-git': '', 67 | 'chck-open': '', 68 | placeholder: '', 69 | 'position-top': '', 70 | 'position-bottom': '', 71 | 'position-left': '', 72 | 'position-right': '', 73 | 'font-family': '' 74 | }, 75 | recommend: { 76 | author: true, 77 | others: true 78 | }, 79 | filters: { 80 | ASCII: { 81 | enabled: false, 82 | name: 'Non-ASCII', 83 | regExp: '[^\\x00-\\x7F\\s]+' 84 | }, 85 | Latin: { 86 | enabled: false, 87 | name: 'Non-Latin', 88 | regExp: '[^\\u0000-\\u024F\\u2000-\\u214F\\s]+' 89 | }, 90 | Games: { 91 | enabled: false, 92 | name: 'Games', 93 | flag: 'iu', 94 | regExp: 95 | 'Aimbot|AntiGame|Agar|agar\\.io|alis\\.io|angel\\.io|ExtencionRipXChetoMalo|AposBot|DFxLite|ZTx-Lite|AposFeedingBot|AposLoader|Balz|Blah Blah|Orc Clan Script|Astro\\s*Empires|^\\s*Attack|^\\s*Battle|BiteFight|Blood\\s*Wars|Bloble|Bonk|Bots|Bots4|Brawler|\\bBvS\\b|Business\\s*Tycoon|Castle\\s*Age|City\\s*Ville|chopcoin\\.io|Comunio|Conquer\\s*Club|CosmoPulse|cursors\\.io|Dark\\s*Orbit|Dead\\s*Frontier|Diep\\.io|\\bDOA\\b|doblons\\.io|DotD|Dossergame|Dragons\\s*of\\s*Atlantis|driftin\\.io|Dugout|\\bDS[a-z]+\\n|elites\\.io|Empire\\s*Board|eRep(ublik)?|Epicmafia|Epic.*War|ExoPlanet|Falcon Tools|Feuerwache|Farming|FarmVille|Fightinfo|Frontier\\s*Ville|Ghost\\s*Trapper|Gladiatus|Goalline|Gondal|gota\\.io|Grepolis|Hobopolis|\\bhwm(\\b|_)|Ikariam|\\bIT2\\b|Jellyneo|Kapi\\s*Hospital|Kings\\s*Age|Kingdoms?\\s*of|knastv(o|oe)gel|Knight\\s*Fight|\\b(Power)?KoC(Atta?ck)?\\b|\\bKOL\\b|Kongregate|Krunker|Last\\s*Emperor|Legends?\\s*of|Light\\s*Rising|lite\\.ext\\.io|Lockerz|\\bLoU\\b|Mafia\\s*(Wars|Mofo)|Menelgame|Mob\\s*Wars|Mouse\\s*Hunt|Molehill\\s*Empire|MooMoo|MyFreeFarm|narwhale\\.io|Neopets|NeoQuest|Nemexia|\\bOGame\\b|Ogar(io)?|Pardus|Pennergame|Pigskin\\s*Empire|PlayerScripts|pokeradar\\.io|Popmundo|Po?we?r\\s*(Bot|Tools)|PsicoTSI|Ravenwood|Schulterglatze|Skribbl|slither\\.io|slitherplus\\.io|slitheriogameplay|SpaceWars|splix\\.io|Survivio|\\bSW_[a-z]+\\n|\\bSnP\\b|The\\s*Crims|The\\s*West|torto\\.io|Travian|Treasure\\s*Isl(and|e)|Tribal\\s*Wars|TW.?PRO|Vampire\\s*Wars|vertix\\.io|War\\s*of\\s*Ninja|World\\s*of\\s*Tanks|West\\s*Wars|wings\\.io|\\bWoD\\b|World\\s*of\\s*Dungeons|wtf\\s*battles|Wurzelimperium|Yohoho|Zombs' 96 | }, 97 | SocialNetworks: { 98 | enabled: false, 99 | name: 'Social Networks', 100 | flag: 'iu', 101 | regExp: 102 | 'Face\\s*book|Google(\\+| Plus)|\\bHabbo|Kaskus|\\bLepra|Leprosorium|MySpace|meinVZ|odnoklassniki|Одноклассники|Orkut|sch(ue|ü)ler(VZ|\\.cc)?|studiVZ|Unfriend|Valenth|VK|vkontakte|ВКонтакте|Qzone|Twitter|TweetDeck' 103 | }, 104 | Clutter: { 105 | enabled: false, 106 | name: 'Clutter', 107 | flag: 'iu', 108 | regExp: 109 | "^\\s*(.{1,3})\\1+\\n|^\\s*(.+?)\\n+\\2\\n*$|^\\s*.{1,5}\\n|do\\s*n('|o)?t (install|download)|nicht installieren|(just )?(\\ban? |\\b)test(ing|s|\\d|\\b)|^\\s*.{0,4}test.{0,4}\\n|\\ntest(ing)?\\s*|^\\s*(\\{@|Smolka|Hacks)|\\[\\d{4,5}\\]|free\\s*download|theme|(night|dark) ?(mode)?" 110 | } 111 | } 112 | }; 113 | // #endregion 114 | 115 | /** @type {{ [prefix: string]: StorageArea }} */ 116 | export const storageByPrefix = {}; 117 | 118 | /** 119 | * @param {function} [fnValue] - (value, newKey, obj) => newValue 120 | * @param {function} [fnKey] - (key, val, obj) => newKey (if newKey is falsy the key is skipped) 121 | * @param {Object} [thisObj] - passed as `this` to both functions 122 | * @return {Object} 123 | */ 124 | function mapEntry(fnValue, fnKey, thisObj) { 125 | const res = {}; 126 | for (let key of Object.keys(this)) { 127 | const val = this[key]; 128 | if (!fnKey || (key = thisObj[fnKey](key, val, this))) { 129 | res[key] = fnValue ? thisObj[fnValue](val, key, this) : val; 130 | } 131 | } 132 | return res; 133 | } 134 | 135 | class StorageArea { 136 | constructor(name, prefix) { 137 | storageByPrefix[prefix] = this; 138 | this.name = name; 139 | this.prefix = prefix; 140 | } 141 | 142 | /** @return {string} */ 143 | toKey(id) { 144 | return this.prefix + id; 145 | } 146 | 147 | /** @return {string} */ 148 | toId(key) { 149 | return key.startsWith(this.prefix) ? key.slice(this.prefix.length) : ''; 150 | } 151 | 152 | /** 153 | * @param {string|number} id 154 | * @return {Promise} 155 | */ 156 | async getOne(id) { 157 | const key = this.toKey(id); 158 | return (await api.get([key]))[key]; 159 | } 160 | 161 | /** 162 | * @param {?string[]} [ids] - if null/absent, the entire storage is returned 163 | * @param {function(val:?,key:string):?} [transform] 164 | * @return {Promise} - single value or object of id:value 165 | */ 166 | async getMulti(ids, transform) { 167 | const keys = ids?.map(this.toKey, this); 168 | const data = await api.get(keys); 169 | return transform || this.prefix ? mapEntry.call(data, transform, 'toId', this) : data; 170 | } 171 | 172 | /** 173 | * @param {string|number|Array} id 174 | * @return {Promise} 175 | */ 176 | async remove(id) { 177 | const keys = (Array.isArray(id) ? id : [id]).filter(Boolean).map(this.toKey, this); 178 | if (keys.length) await api.remove(keys); 179 | } 180 | 181 | async setOne(id, value) { 182 | if (id) return this.set({ [id]: value }); 183 | } 184 | 185 | /** 186 | * @param {Object} data 187 | * @return {Promise} same object 188 | */ 189 | async set(data) { 190 | await api.set(this.prefix ? mapEntry.call(data, null, 'toKey', this) : data); 191 | return data; 192 | } 193 | } 194 | 195 | const storage = { 196 | get api() { 197 | return api; 198 | }, 199 | set api(val) { 200 | api = val; 201 | }, 202 | /** @return {?StorageArea} */ 203 | forKey: (key) => storageByPrefix[/^\w+:|$/.exec(key)[0]], 204 | base: new StorageArea('base', ''), 205 | config: new StorageArea('config', 'cfg:'), 206 | cache: new StorageArea('cache', 'cac:') 207 | }; 208 | 209 | export default storage; 210 | -------------------------------------------------------------------------------- /src/js/util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { webext } from './ext.js'; 4 | import { err } from './logger.js'; 5 | 6 | // #region Utilities 7 | const userjs = {}; 8 | const getUAData = () => { 9 | if (userjs.isMobile !== undefined) { 10 | return userjs.isMobile; 11 | } 12 | try { 13 | if (navigator) { 14 | const { userAgent, userAgentData } = navigator; 15 | const { platform, mobile } = userAgentData ? Object(userAgentData) : {}; 16 | userjs.isMobile = 17 | /Mobile|Tablet/.test(userAgent ? String(userAgent) : '') || 18 | Boolean(mobile) || 19 | /Android|Apple/.test(platform ? String(platform) : ''); 20 | } else { 21 | userjs.isMobile = false; 22 | } 23 | } catch (ex) { 24 | userjs.isMobile = false; 25 | ex.cause = 'getUAData'; 26 | err(ex); 27 | } 28 | return userjs.isMobile; 29 | }; 30 | const isMobile = getUAData(); 31 | const isString = (val) => typeof val === 'string'; 32 | /** 33 | * @type { import("../typings/types").objToStr } 34 | */ 35 | const objToStr = (obj) => { 36 | return Object.prototype.toString.call(obj); 37 | }; 38 | /** 39 | * @template {string | URL} S 40 | * @param {S} str 41 | */ 42 | const strToURL = (str) => { 43 | let url; 44 | try { 45 | url = objToStr(str).includes('URL') ? str : new URL(str); 46 | } catch (ex) { 47 | ex.cause = 'strToURL'; 48 | err(ex); 49 | } 50 | if (url !== undefined) { 51 | return url 52 | } 53 | return str; 54 | }; 55 | /** 56 | * @type { import("../typings/types").isRegExp } 57 | */ 58 | const isRegExp = (obj) => { 59 | const s = objToStr(obj); 60 | return s.includes('RegExp'); 61 | }; 62 | /** 63 | * @type { import("../typings/types").isElem } 64 | */ 65 | const isElem = (obj) => { 66 | const s = objToStr(obj); 67 | return s.includes('Element'); 68 | }; 69 | /** 70 | * @type { import("../typings/types").isObj } 71 | */ 72 | const isObj = (obj) => { 73 | const s = objToStr(obj); 74 | return s.includes('Object'); 75 | }; 76 | /** 77 | * @type { import("../typings/types").isFN } 78 | */ 79 | const isFN = (obj) => { 80 | const s = objToStr(obj); 81 | return s.includes('Function'); 82 | }; 83 | /** 84 | * @type { import("../typings/types").isNull } 85 | */ 86 | const isNull = (obj) => { 87 | return Object.is(obj, null) || Object.is(obj, undefined); 88 | }; 89 | /** 90 | * @type { import("../typings/types").isBlank } 91 | */ 92 | const isBlank = (obj) => { 93 | return ( 94 | (typeof obj === 'string' && Object.is(obj.trim(), '')) || 95 | ((obj instanceof Set || obj instanceof Map) && Object.is(obj.size, 0)) || 96 | (Array.isArray(obj) && Object.is(obj.length, 0)) || 97 | (isObj(obj) && Object.is(Object.keys(obj).length, 0)) 98 | ); 99 | }; 100 | /** 101 | * @type { import("../typings/types").isEmpty } 102 | */ 103 | const isEmpty = (obj) => { 104 | return isNull(obj) || isBlank(obj); 105 | }; 106 | /** 107 | * @type { import("../typings/types").normalizeTarget } 108 | */ 109 | const normalizeTarget = (target, toQuery = true, root) => { 110 | if (Object.is(target, null) || Object.is(target, undefined)) { 111 | return []; 112 | } 113 | if (Array.isArray(target)) { 114 | return target; 115 | } 116 | if (typeof target === 'string') { 117 | return toQuery ? Array.from((root || document).querySelectorAll(target)) : [target]; 118 | } 119 | if (isElem(target)) { 120 | return [target]; 121 | } 122 | return Array.from(target); 123 | }; 124 | /** 125 | * @type { import("../typings/types.d.ts").ael } 126 | */ 127 | const ael = (el, type, listener, options = {}) => { 128 | try { 129 | for (const elem of normalizeTarget(el)) { 130 | if (!elem) { 131 | continue; 132 | } 133 | if (isMobile && type === 'click') { 134 | elem.addEventListener('touchstart', listener, options); 135 | continue; 136 | } 137 | elem.addEventListener(type, listener, options); 138 | } 139 | } catch (ex) { 140 | ex.cause = 'ael'; 141 | err(ex); 142 | } 143 | }; 144 | /** 145 | * @type { import("../typings/types.d.ts").formAttrs } 146 | */ 147 | const formAttrs = (elem, attr = {}) => { 148 | if (!elem) { 149 | return elem; 150 | } 151 | for (const key in attr) { 152 | if (typeof attr[key] === 'object') { 153 | formAttrs(elem[key], attr[key]); 154 | } else if (isFN(attr[key])) { 155 | if (/^on/.test(key)) { 156 | elem[key] = attr[key]; 157 | continue; 158 | } 159 | ael(elem, key, attr[key]); 160 | } else if (key === 'class') { 161 | elem.className = attr[key]; 162 | } else { 163 | elem[key] = attr[key]; 164 | } 165 | } 166 | return elem; 167 | }; 168 | /** 169 | * @type { import("../typings/types.d.ts").make } 170 | */ 171 | const make = (tagName, cname, attrs) => { 172 | let el; 173 | try { 174 | el = document.createElement(tagName); 175 | if (!isEmpty(cname)) { 176 | if (typeof cname === 'string') { 177 | el.className = cname; 178 | } else if (isObj(cname)) { 179 | formAttrs(el, cname); 180 | } 181 | } 182 | if (!isEmpty(attrs)) { 183 | if (typeof attrs === 'string') { 184 | el.textContent = attrs; 185 | } else if (isObj(attrs)) { 186 | formAttrs(el, attrs); 187 | } 188 | } 189 | } catch (ex) { 190 | ex.cause = 'make'; 191 | err(ex); 192 | } 193 | return el; 194 | }; 195 | /** 196 | * @param { string } url - URL of webpage to open 197 | * @param { object } params - GM parameters 198 | * @returns { Promise } 199 | */ 200 | const openInTab = async (url) => { 201 | const newTab = await webext.tabs.create({ url }); 202 | return newTab; 203 | }; 204 | const union = (...arr) => [...new Set(arr.flat())]; 205 | const loadFilters = (cfg) => { 206 | /** @type {Map} */ 207 | const pool = new Map(); 208 | const handles = { 209 | pool, 210 | enabled() { 211 | return [...pool.values()].filter((o) => o.enabled); 212 | }, 213 | refresh() { 214 | if (!Object.is(pool.size, 0)) pool.clear(); 215 | for (const [key, value] of Object.entries(cfg.filters)) { 216 | if (!pool.has(key)) 217 | pool.set(key, { 218 | ...value, 219 | reg: new RegExp(value.regExp, value.flag), 220 | keyReg: new RegExp(key.trim().toLocaleLowerCase(), 'gi'), 221 | valueReg: new RegExp(value.name.trim().toLocaleLowerCase(), 'gi') 222 | }); 223 | } 224 | return this; 225 | }, 226 | get(str) { 227 | return [...pool.values()].find((v) => v.keyReg.test(str) || v.valueReg.test(str)); 228 | }, 229 | /** 230 | * @param { import("../typings/types.d.ts").GSForkQuery } param0 231 | */ 232 | match({ name, users }) { 233 | const p = handles.enabled(); 234 | if (Object.is(p.length, 0)) return true; 235 | for (const v of p) { 236 | if ([{ name }, ...users].find((o) => o.name.match(v.reg))) return false; 237 | } 238 | return true; 239 | } 240 | }; 241 | for (const [key, value] of Object.entries(cfg.filters)) { 242 | if (!pool.has(key)) 243 | pool.set(key, { 244 | ...value, 245 | reg: new RegExp(value.regExp, value.flag), 246 | keyReg: new RegExp(key.trim().toLocaleLowerCase(), 'gi'), 247 | valueReg: new RegExp(value.name.trim().toLocaleLowerCase(), 'gi') 248 | }); 249 | } 250 | return handles.refresh(); 251 | }; 252 | /** 253 | * @param {string} txt 254 | */ 255 | const formatURL = (txt) => 256 | txt 257 | .split('.') 258 | .splice(-2) 259 | .join('.') 260 | .replace(/\/|https:/g, ''); 261 | const matchesFromHostnames = (hostnames) => { 262 | const out = []; 263 | for (const hn of hostnames) { 264 | if (hn === '*' || hn === 'all-urls') { 265 | out.length = 0; 266 | out.push(''); 267 | break; 268 | } 269 | out.push(`*://*.${hn}/*`); 270 | } 271 | return out; 272 | }; 273 | /** 274 | * @param {string[]} origins 275 | */ 276 | const hostnamesFromMatches = (origins) => { 277 | const out = []; 278 | for (const origin of origins) { 279 | if (origin === '') { 280 | out.push('all-urls'); 281 | continue; 282 | } 283 | const match = /^\*:\/\/(?:\*\.)?([^/]+)\/\*/.exec(origin); 284 | if (match === null) { 285 | continue; 286 | } 287 | out.push(match[1]); 288 | } 289 | return out; 290 | }; 291 | /** 292 | * @param {string} hn 293 | */ 294 | const normalizedHostname = (hn) => { 295 | return hn.replace(/^www\./, ''); 296 | }; 297 | /** 298 | * @param {string} str 299 | */ 300 | const decode = (str) => { 301 | try { 302 | if (decodeURI(str) !== decodeURIComponent(str)) { 303 | return decode(decodeURIComponent(str)); 304 | } 305 | } catch (ex) { 306 | err(ex); 307 | } 308 | return str; 309 | }; 310 | // #endregion 311 | 312 | export { 313 | decode, 314 | formatURL, 315 | objToStr, 316 | strToURL, 317 | isRegExp, 318 | isElem, 319 | isObj, 320 | isFN, 321 | isNull, 322 | isBlank, 323 | isEmpty, 324 | isString, 325 | normalizeTarget, 326 | ael, 327 | formAttrs, 328 | make, 329 | openInTab, 330 | union, 331 | loadFilters, 332 | matchesFromHostnames, 333 | hostnamesFromMatches, 334 | normalizedHostname 335 | }; 336 | -------------------------------------------------------------------------------- /src/manifest/chrome.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "default_locale": "en", 4 | "name": "__MSG_extName__", 5 | "description": "__MSG_extShortDesc__", 6 | "short_name": "userJSPlus", 7 | "background": { 8 | "service_worker": "/js/background.js", 9 | "type": "module" 10 | }, 11 | "action": { 12 | "default_icon": { 13 | "16": "img/icon_16.png", 14 | "32": "img/icon_32.png", 15 | "48": "img/icon_48.png", 16 | "64": "img/icon_64.png", 17 | "128": "img/icon_128.png" 18 | }, 19 | "default_popup": "popup.html", 20 | "default_title": "Magic UserJS+" 21 | }, 22 | "minimum_chrome_version": "122.0", 23 | "options_page": "popup.html?mujs=settings", 24 | "permissions": [ 25 | "storage", 26 | "unlimitedStorage", 27 | "tabs", 28 | "activeTab", 29 | "webRequest" 30 | ], 31 | "host_permissions": [ 32 | "https://*/*" 33 | ], 34 | "icons": { 35 | "16": "img/icon_16.png", 36 | "32": "img/icon_32.png", 37 | "48": "img/icon_48.png", 38 | "64": "img/icon_64.png", 39 | "128": "img/icon_128.png" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/manifest/firefox.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "default_locale": "en", 4 | "name": "__MSG_extName__", 5 | "description": "__MSG_extShortDesc__", 6 | "short_name": "userJSPlus", 7 | "background": { 8 | "scripts": [ 9 | "/js/background.js" 10 | ], 11 | "type": "module" 12 | }, 13 | "action": { 14 | "default_area": "navbar", 15 | "default_icon": { 16 | "16": "img/icon_16.png", 17 | "32": "img/icon_32.png", 18 | "48": "img/icon_48.png", 19 | "64": "img/icon_64.png", 20 | "128": "img/icon_128.png" 21 | }, 22 | "default_popup": "popup.html", 23 | "default_title": "Magic UserJS+" 24 | }, 25 | "browser_specific_settings": { 26 | "gecko": { 27 | "id": "uscriptplus@mol.com", 28 | "strict_min_version": "128.0" 29 | }, 30 | "gecko_android": { 31 | "strict_min_version": "128.0" 32 | } 33 | }, 34 | "options_ui": { 35 | "open_in_tab": true, 36 | "page": "popup.html?mujs=settings" 37 | }, 38 | "permissions": [ 39 | "storage", 40 | "unlimitedStorage", 41 | "tabs", 42 | "activeTab", 43 | "webRequest" 44 | ], 45 | "host_permissions": [ 46 | "https://*/*" 47 | ], 48 | "content_security_policy": { 49 | "extension_pages": "script-src 'self'; object-src 'self'" 50 | }, 51 | "icons": { 52 | "16": "img/icon_16.png", 53 | "32": "img/icon_32.png", 54 | "48": "img/icon_48.png", 55 | "64": "img/icon_64.png", 56 | "128": "img/icon_128.png" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/sass/fontawesome/_animated.scss: -------------------------------------------------------------------------------- 1 | // animating icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-beat { 5 | animation-name: #{$fa-css-prefix}-beat; 6 | animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); 7 | animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); 8 | animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); 9 | animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); 10 | animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, ease-in-out); 11 | } 12 | 13 | .#{$fa-css-prefix}-bounce { 14 | animation-name: #{$fa-css-prefix}-bounce; 15 | animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); 16 | animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); 17 | animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); 18 | animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); 19 | animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, cubic-bezier(0.280, 0.840, 0.420, 1)); 20 | } 21 | 22 | .#{$fa-css-prefix}-fade { 23 | animation-name: #{$fa-css-prefix}-fade; 24 | animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); 25 | animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); 26 | animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); 27 | animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); 28 | animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, cubic-bezier(.4,0,.6,1)); 29 | } 30 | 31 | .#{$fa-css-prefix}-beat-fade { 32 | animation-name: #{$fa-css-prefix}-beat-fade; 33 | animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); 34 | animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); 35 | animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); 36 | animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); 37 | animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, cubic-bezier(.4,0,.6,1)); 38 | } 39 | 40 | .#{$fa-css-prefix}-flip { 41 | animation-name: #{$fa-css-prefix}-flip; 42 | animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); 43 | animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); 44 | animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); 45 | animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); 46 | animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, ease-in-out); 47 | } 48 | 49 | .#{$fa-css-prefix}-shake { 50 | animation-name: #{$fa-css-prefix}-shake; 51 | animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); 52 | animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); 53 | animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); 54 | animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); 55 | animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, linear); 56 | } 57 | 58 | .#{$fa-css-prefix}-spin { 59 | animation-name: #{$fa-css-prefix}-spin; 60 | animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); 61 | animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); 62 | animation-duration: var(--#{$fa-css-prefix}-animation-duration, 2s); 63 | animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); 64 | animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, linear); 65 | } 66 | 67 | .#{$fa-css-prefix}-spin-reverse { 68 | --#{$fa-css-prefix}-animation-direction: reverse; 69 | } 70 | 71 | .#{$fa-css-prefix}-pulse, 72 | .#{$fa-css-prefix}-spin-pulse { 73 | animation-name: #{$fa-css-prefix}-spin; 74 | animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); 75 | animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); 76 | animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); 77 | animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, steps(8)); 78 | } 79 | 80 | // if agent or operating system prefers reduced motion, disable animations 81 | // see: https://www.smashingmagazine.com/2020/09/design-reduced-motion-sensitivities/ 82 | // see: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion 83 | @media (prefers-reduced-motion: reduce) { 84 | .#{$fa-css-prefix}-beat, 85 | .#{$fa-css-prefix}-bounce, 86 | .#{$fa-css-prefix}-fade, 87 | .#{$fa-css-prefix}-beat-fade, 88 | .#{$fa-css-prefix}-flip, 89 | .#{$fa-css-prefix}-pulse, 90 | .#{$fa-css-prefix}-shake, 91 | .#{$fa-css-prefix}-spin, 92 | .#{$fa-css-prefix}-spin-pulse { 93 | animation-delay: -1ms; 94 | animation-duration: 1ms; 95 | animation-iteration-count: 1; 96 | transition-delay: 0s; 97 | transition-duration: 0s; 98 | } 99 | } 100 | 101 | @keyframes #{$fa-css-prefix}-beat { 102 | 0%, 90% { transform: scale(1); } 103 | 45% { transform: scale(var(--#{$fa-css-prefix}-beat-scale, 1.25)); } 104 | } 105 | 106 | @keyframes #{$fa-css-prefix}-bounce { 107 | 0% { transform: scale(1,1) translateY(0); } 108 | 10% { transform: scale(var(--#{$fa-css-prefix}-bounce-start-scale-x, 1.1),var(--#{$fa-css-prefix}-bounce-start-scale-y, 0.9)) translateY(0); } 109 | 30% { transform: scale(var(--#{$fa-css-prefix}-bounce-jump-scale-x, 0.9),var(--#{$fa-css-prefix}-bounce-jump-scale-y, 1.1)) translateY(var(--#{$fa-css-prefix}-bounce-height, -0.5em)); } 110 | 50% { transform: scale(var(--#{$fa-css-prefix}-bounce-land-scale-x, 1.05),var(--#{$fa-css-prefix}-bounce-land-scale-y, 0.95)) translateY(0); } 111 | 57% { transform: scale(1,1) translateY(var(--#{$fa-css-prefix}-bounce-rebound, -0.125em)); } 112 | 64% { transform: scale(1,1) translateY(0); } 113 | 100% { transform: scale(1,1) translateY(0); } 114 | } 115 | 116 | @keyframes #{$fa-css-prefix}-fade { 117 | 50% { opacity: var(--#{$fa-css-prefix}-fade-opacity, 0.4); } 118 | } 119 | 120 | @keyframes #{$fa-css-prefix}-beat-fade { 121 | 0%, 100% { 122 | opacity: var(--#{$fa-css-prefix}-beat-fade-opacity, 0.4); 123 | transform: scale(1); 124 | } 125 | 50% { 126 | opacity: 1; 127 | transform: scale(var(--#{$fa-css-prefix}-beat-fade-scale, 1.125)); 128 | } 129 | } 130 | 131 | @keyframes #{$fa-css-prefix}-flip { 132 | 50% { 133 | transform: rotate3d(var(--#{$fa-css-prefix}-flip-x, 0), var(--#{$fa-css-prefix}-flip-y, 1), var(--#{$fa-css-prefix}-flip-z, 0), var(--#{$fa-css-prefix}-flip-angle, -180deg)); 134 | } 135 | } 136 | 137 | @keyframes #{$fa-css-prefix}-shake { 138 | 0% { transform: rotate(-15deg); } 139 | 4% { transform: rotate(15deg); } 140 | 8%, 24% { transform: rotate(-18deg); } 141 | 12%, 28% { transform: rotate(18deg); } 142 | 16% { transform: rotate(-22deg); } 143 | 20% { transform: rotate(22deg); } 144 | 32% { transform: rotate(-12deg); } 145 | 36% { transform: rotate(12deg); } 146 | 40%, 100% { transform: rotate(0deg); } 147 | } 148 | 149 | @keyframes #{$fa-css-prefix}-spin { 150 | 0% { transform: rotate(0deg); } 151 | 100% { transform: rotate(360deg); } 152 | } 153 | -------------------------------------------------------------------------------- /src/sass/fontawesome/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // bordered + pulled icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | border-color: var(--#{$fa-css-prefix}-border-color, #{$fa-border-color}); 6 | border-radius: var(--#{$fa-css-prefix}-border-radius, #{$fa-border-radius}); 7 | border-style: var(--#{$fa-css-prefix}-border-style, #{$fa-border-style}); 8 | border-width: var(--#{$fa-css-prefix}-border-width, #{$fa-border-width}); 9 | padding: var(--#{$fa-css-prefix}-border-padding, #{$fa-border-padding}); 10 | } 11 | 12 | .#{$fa-css-prefix}-pull-left { 13 | float: left; 14 | margin-right: var(--#{$fa-css-prefix}-pull-margin, #{$fa-pull-margin}); 15 | } 16 | 17 | .#{$fa-css-prefix}-pull-right { 18 | float: right; 19 | margin-left: var(--#{$fa-css-prefix}-pull-margin, #{$fa-pull-margin}); 20 | } 21 | -------------------------------------------------------------------------------- /src/sass/fontawesome/_core.scss: -------------------------------------------------------------------------------- 1 | // base icon class definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | font-family: var(--#{$fa-css-prefix}-style-family, '#{$fa-style-family}'); 6 | font-weight: var(--#{$fa-css-prefix}-style, #{$fa-style}); 7 | } 8 | 9 | .fas, 10 | .far, 11 | .fab, 12 | .#{$fa-css-prefix}-solid, 13 | .#{$fa-css-prefix}-regular, 14 | .#{$fa-css-prefix}-brands, 15 | .#{$fa-css-prefix} { 16 | -moz-osx-font-smoothing: grayscale; 17 | -webkit-font-smoothing: antialiased; 18 | display: var(--#{$fa-css-prefix}-display, #{$fa-display}); 19 | font-style: normal; 20 | font-variant: normal; 21 | line-height: 1; 22 | text-rendering: auto; 23 | } 24 | 25 | .fas::before, 26 | .far::before, 27 | .fab::before, 28 | .#{$fa-css-prefix}-solid::before, 29 | .#{$fa-css-prefix}-regular::before, 30 | .#{$fa-css-prefix}-brands::before, 31 | .#{$fa-css-prefix}::before { 32 | content: var(#{$fa-icon-property}); 33 | } 34 | 35 | .#{$fa-css-prefix}-classic, 36 | .fas, 37 | .#{$fa-css-prefix}-solid, 38 | .far, 39 | .#{$fa-css-prefix}-regular { 40 | font-family: 'Font Awesome 6 Free'; 41 | } 42 | .#{$fa-css-prefix}-brands, 43 | .fab { 44 | font-family: 'Font Awesome 6 Brands'; 45 | } 46 | 47 | %fa-icon { 48 | @include fa-icon; 49 | } 50 | -------------------------------------------------------------------------------- /src/sass/fontawesome/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // fixed-width icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-fw { 5 | text-align: center; 6 | width: $fa-fw-width; 7 | } 8 | -------------------------------------------------------------------------------- /src/sass/fontawesome/_functions.scss: -------------------------------------------------------------------------------- 1 | // functions 2 | // -------------------------- 3 | 4 | // fa-content: convenience function used to set content property 5 | @function fa-content($fa-var) { 6 | @return unquote("\"#{ $fa-var }\""); 7 | } 8 | 9 | // fa-divide: Originally obtained from the Bootstrap https://github.com/twbs/bootstrap 10 | // 11 | // Licensed under: The MIT License (MIT) 12 | // 13 | // Copyright (c) 2011-2021 Twitter, Inc. 14 | // Copyright (c) 2011-2021 The Bootstrap Authors 15 | // 16 | // Permission is hereby granted, free of charge, to any person obtaining a copy 17 | // of this software and associated documentation files (the "Software"), to deal 18 | // in the Software without restriction, including without limitation the rights 19 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | // copies of the Software, and to permit persons to whom the Software is 21 | // furnished to do so, subject to the following conditions: 22 | // 23 | // The above copyright notice and this permission notice shall be included in 24 | // all copies or substantial portions of the Software. 25 | // 26 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | // THE SOFTWARE. 33 | 34 | @function fa-divide($dividend, $divisor, $precision: 10) { 35 | $sign: if($dividend > 0 and $divisor > 0, 1, -1); 36 | $dividend: abs($dividend); 37 | $divisor: abs($divisor); 38 | $quotient: 0; 39 | $remainder: $dividend; 40 | @if $dividend == 0 { 41 | @return 0; 42 | } 43 | @if $divisor == 0 { 44 | @error "Cannot divide by 0"; 45 | } 46 | @if $divisor == 1 { 47 | @return $dividend; 48 | } 49 | @while $remainder >= $divisor { 50 | $quotient: $quotient + 1; 51 | $remainder: $remainder - $divisor; 52 | } 53 | @if $remainder > 0 and $precision > 0 { 54 | $remainder: fa-divide($remainder * 10, $divisor, $precision - 1) * .1; 55 | } 56 | @return ($quotient + $remainder) * $sign; 57 | } 58 | -------------------------------------------------------------------------------- /src/sass/fontawesome/_icons.scss: -------------------------------------------------------------------------------- 1 | // specific icon class definition 2 | // ------------------------- 3 | 4 | /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen 5 | readers do not read off random characters that represent icons */ 6 | 7 | @each $name, $icon in $fa-icons { 8 | .#{$fa-css-prefix}-#{$name} { 9 | #{$fa-icon-property}: unquote("\"#{ $icon }\""); 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/sass/fontawesome/_list.scss: -------------------------------------------------------------------------------- 1 | // icons in a list 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | list-style-type: none; 6 | margin-left: var(--#{$fa-css-prefix}-li-margin, #{$fa-li-margin}); 7 | padding-left: 0; 8 | 9 | > li { position: relative; } 10 | } 11 | 12 | .#{$fa-css-prefix}-li { 13 | left: calc(-1 * var(--#{$fa-css-prefix}-li-width, #{$fa-li-width})); 14 | position: absolute; 15 | text-align: center; 16 | width: var(--#{$fa-css-prefix}-li-width, #{$fa-li-width}); 17 | line-height: inherit; 18 | } 19 | -------------------------------------------------------------------------------- /src/sass/fontawesome/_mixins.scss: -------------------------------------------------------------------------------- 1 | // mixins 2 | // -------------------------- 3 | 4 | // base rendering for an icon 5 | @mixin fa-icon { 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | display: inline-block; 9 | font-style: normal; 10 | font-variant: normal; 11 | font-weight: normal; 12 | line-height: 1; 13 | } 14 | 15 | // sets relative font-sizing and alignment (in _sizing) 16 | @mixin fa-size ($font-size) { 17 | font-size: fa-divide($font-size, $fa-size-scale-base) * 1em; // converts step in sizing scale into an em-based value that's relative to the scale's base 18 | line-height: fa-divide(1, $font-size) * 1em; // sets the line-height of the icon back to that of it's parent 19 | vertical-align: (fa-divide(6, $font-size) - fa-divide(3, 8)) * 1em; // vertically centers the icon taking into account the surrounding text's descender 20 | } 21 | 22 | // only display content to screen readers 23 | // see: https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/ 24 | // see: https://hugogiraudel.com/2016/10/13/css-hide-and-seek/ 25 | @mixin fa-sr-only() { 26 | position: absolute; 27 | width: 1px; 28 | height: 1px; 29 | padding: 0; 30 | margin: -1px; 31 | overflow: hidden; 32 | clip: rect(0, 0, 0, 0); 33 | white-space: nowrap; 34 | border-width: 0; 35 | } 36 | 37 | // use in conjunction with .sr-only to only display content when it's focused 38 | @mixin fa-sr-only-focusable() { 39 | &:not(:focus) { 40 | @include fa-sr-only(); 41 | } 42 | } 43 | 44 | // sets a specific icon family to use alongside style + icon mixins 45 | @mixin fa-family-classic() { 46 | @extend .fa-classic; 47 | } 48 | 49 | // convenience mixins for declaring pseudo-elements by CSS variable, 50 | // including all style-specific font properties 51 | @mixin fa-icon-solid($fa-var) { 52 | @extend .fa-solid; 53 | 54 | & { #{$fa-icon-property}: unquote("\"#{ $fa-var }\""); #{$fa-duotone-icon-property}: unquote("\"#{ $fa-var }#{ $fa-var }\""); } 55 | } 56 | @mixin fa-icon-regular($fa-var) { 57 | @extend .fa-regular; 58 | 59 | & { #{$fa-icon-property}: unquote("\"#{ $fa-var }\""); #{$fa-duotone-icon-property}: unquote("\"#{ $fa-var }#{ $fa-var }\""); } 60 | } 61 | @mixin fa-icon-brands($fa-var) { 62 | @extend .fa-brands; 63 | 64 | & { #{$fa-icon-property}: unquote("\"#{ $fa-var }\""); #{$fa-duotone-icon-property}: unquote("\"#{ $fa-var }#{ $fa-var }\""); } 65 | } 66 | -------------------------------------------------------------------------------- /src/sass/fontawesome/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // rotating + flipping icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { 5 | transform: rotate(90deg); 6 | } 7 | 8 | .#{$fa-css-prefix}-rotate-180 { 9 | transform: rotate(180deg); 10 | } 11 | 12 | .#{$fa-css-prefix}-rotate-270 { 13 | transform: rotate(270deg); 14 | } 15 | 16 | .#{$fa-css-prefix}-flip-horizontal { 17 | transform: scale(-1, 1); 18 | } 19 | 20 | .#{$fa-css-prefix}-flip-vertical { 21 | transform: scale(1, -1); 22 | } 23 | 24 | .#{$fa-css-prefix}-flip-both, 25 | .#{$fa-css-prefix}-flip-horizontal.#{$fa-css-prefix}-flip-vertical { 26 | transform: scale(-1, -1); 27 | } 28 | 29 | .#{$fa-css-prefix}-rotate-by { 30 | transform: rotate(var(--#{$fa-css-prefix}-rotate-angle, 0)); 31 | } 32 | -------------------------------------------------------------------------------- /src/sass/fontawesome/_screen-reader.scss: -------------------------------------------------------------------------------- 1 | // screen-reader utilities 2 | // ------------------------- 3 | 4 | // only display content to screen readers 5 | .sr-only, 6 | .#{$fa-css-prefix}-sr-only { 7 | @include fa-sr-only; 8 | } 9 | 10 | // use in conjunction with .sr-only to only display content when it's focused 11 | .sr-only-focusable, 12 | .#{$fa-css-prefix}-sr-only-focusable { 13 | @include fa-sr-only-focusable; 14 | } 15 | -------------------------------------------------------------------------------- /src/sass/fontawesome/_sizing.scss: -------------------------------------------------------------------------------- 1 | // sizing icons 2 | // ------------------------- 3 | 4 | // literal magnification scale 5 | @for $i from 1 through 10 { 6 | .#{$fa-css-prefix}-#{$i}x { 7 | font-size: $i * 1em; 8 | } 9 | } 10 | 11 | // step-based scale (with alignment) 12 | @each $size, $value in $fa-sizes { 13 | .#{$fa-css-prefix}-#{$size} { 14 | @include fa-size($value); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/sass/fontawesome/_stacked.scss: -------------------------------------------------------------------------------- 1 | // stacking icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | display: inline-block; 6 | height: 2em; 7 | line-height: 2em; 8 | position: relative; 9 | vertical-align: $fa-stack-vertical-align; 10 | width: $fa-stack-width; 11 | } 12 | 13 | .#{$fa-css-prefix}-stack-1x, 14 | .#{$fa-css-prefix}-stack-2x { 15 | left: 0; 16 | position: absolute; 17 | text-align: center; 18 | width: 100%; 19 | z-index: var(--#{$fa-css-prefix}-stack-z-index, #{$fa-stack-z-index}); 20 | } 21 | 22 | .#{$fa-css-prefix}-stack-1x { 23 | line-height: inherit; 24 | } 25 | 26 | .#{$fa-css-prefix}-stack-2x { 27 | font-size: 2em; 28 | } 29 | 30 | .#{$fa-css-prefix}-inverse { 31 | color: var(--#{$fa-css-prefix}-inverse, #{$fa-inverse}); 32 | } 33 | -------------------------------------------------------------------------------- /src/sass/fontawesome/brands.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | * Copyright 2024 Fonticons, Inc. 5 | */ 6 | @import 'functions'; 7 | @import 'variables'; 8 | 9 | :root, :host { 10 | --#{$fa-css-prefix}-style-family-brands: 'Font Awesome 6 Brands'; 11 | --#{$fa-css-prefix}-font-brands: normal 400 1em/1 'Font Awesome 6 Brands'; 12 | } 13 | 14 | @font-face { 15 | font-family: 'Font Awesome 6 Brands'; 16 | font-style: normal; 17 | font-weight: 400; 18 | font-display: $fa-font-display; 19 | src: url('#{$fa-font-path}/fa-brands-400.woff2') format('woff2'), 20 | url('#{$fa-font-path}/fa-brands-400.ttf') format('truetype'); 21 | } 22 | 23 | .fab, 24 | .#{$fa-css-prefix}-brands { 25 | font-weight: 400; 26 | } 27 | 28 | @each $name, $icon in $fa-brand-icons { 29 | .#{$fa-css-prefix}-#{$name} { #{$fa-icon-property}: unquote("\"#{ $icon }\""); } 30 | } 31 | -------------------------------------------------------------------------------- /src/sass/fontawesome/fontawesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | * Copyright 2024 Fonticons, Inc. 5 | */ 6 | // Font Awesome core compile (Web Fonts-based) 7 | // ------------------------- 8 | 9 | @import 'functions'; 10 | @import 'variables'; 11 | @import 'mixins'; 12 | @import 'core'; 13 | @import 'sizing'; 14 | @import 'fixed-width'; 15 | @import 'list'; 16 | @import 'bordered-pulled'; 17 | @import 'animated'; 18 | @import 'rotated-flipped'; 19 | @import 'stacked'; 20 | @import 'icons'; 21 | @import 'screen-reader'; 22 | -------------------------------------------------------------------------------- /src/sass/fontawesome/regular.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | * Copyright 2024 Fonticons, Inc. 5 | */ 6 | @import 'functions'; 7 | @import 'variables'; 8 | 9 | :root, :host { 10 | --#{$fa-css-prefix}-style-family-classic: '#{ $fa-style-family }'; 11 | --#{$fa-css-prefix}-font-regular: normal 400 1em/1 '#{ $fa-style-family }'; 12 | } 13 | 14 | 15 | @font-face { 16 | font-family: 'Font Awesome 6 Free'; 17 | font-style: normal; 18 | font-weight: 400; 19 | font-display: $fa-font-display; 20 | src: url('#{$fa-font-path}/fa-regular-400.woff2') format('woff2'), 21 | url('#{$fa-font-path}/fa-regular-400.ttf') format('truetype'); 22 | } 23 | 24 | .far, 25 | .#{$fa-css-prefix}-regular { 26 | font-weight: 400; 27 | } 28 | -------------------------------------------------------------------------------- /src/sass/fontawesome/solid.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | * Copyright 2024 Fonticons, Inc. 5 | */ 6 | @import 'functions'; 7 | @import 'variables'; 8 | 9 | :root, :host { 10 | --#{$fa-css-prefix}-style-family-classic: '#{ $fa-style-family }'; 11 | --#{$fa-css-prefix}-font-solid: normal 900 1em/1 '#{ $fa-style-family }'; 12 | } 13 | 14 | 15 | @font-face { 16 | font-family: 'Font Awesome 6 Free'; 17 | font-style: normal; 18 | font-weight: 900; 19 | font-display: $fa-font-display; 20 | src: url('#{$fa-font-path}/fa-solid-900.woff2') format('woff2'), 21 | url('#{$fa-font-path}/fa-solid-900.ttf') format('truetype'); 22 | } 23 | 24 | .fas, 25 | .#{$fa-css-prefix}-solid { 26 | font-weight: 900; 27 | } 28 | -------------------------------------------------------------------------------- /src/sass/fontawesome/v4-shims.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | * Copyright 2024 Fonticons, Inc. 5 | */ 6 | // V4 shims compile (Web Fonts-based) 7 | // ------------------------- 8 | 9 | @import 'functions'; 10 | @import 'variables'; 11 | @import 'shims'; 12 | -------------------------------------------------------------------------------- /src/sass/magicuserjs.scss: -------------------------------------------------------------------------------- 1 | @charset 'UTF-8'; 2 | 3 | @use 'main'; 4 | -------------------------------------------------------------------------------- /src/sass/web-ext.scss: -------------------------------------------------------------------------------- 1 | // importing core styling file 2 | @use './fontawesome/fontawesome.scss'; 3 | 4 | // our project needs Solid + Brands 5 | @use './fontawesome/solid.scss'; 6 | @use './fontawesome/brands.scss'; 7 | 8 | body.webext-page { 9 | overflow: hidden; 10 | padding: 0; 11 | margin: 0; 12 | background: var(--mujs-background-color, hsl(222, 14%, 33%)); 13 | color: var(--mujs-txt-color, hsl(0, 0%, 100%)); 14 | border: 1px solid rgba(0, 0, 0, 0); 15 | border-bottom-left-radius: 10px; 16 | border-bottom-right-radius: 10px; 17 | font-size: 14px; 18 | min-width: 720px; 19 | 20 | .mujs-cfg { 21 | display: flex; 22 | gap: 10px; 23 | flex-direction: column; 24 | // height: 900px; 25 | textarea { 26 | width: 100%; 27 | width: -moz-available; 28 | width: -webkit-fill-available; 29 | } 30 | } 31 | mujs-main { 32 | min-width: 25em; 33 | height: 492px; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/typings/WebExt.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Prefix for `document.querySelector()` w/ Promise 3 | * 4 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/querySelector) 5 | */ 6 | export declare function query(selector: S, root: E): Promise; -------------------------------------------------------------------------------- /src/typings/scheduler.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Task priorities that determine the order in which tasks run. 19 | * 20 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Prioritized_Task_Scheduling_API#task_priorities) 21 | */ 22 | export type TaskPriority = 'user-blocking' | 'user-visible' | 'background'; 23 | 24 | /** 25 | * {@link Scheduler.postTask} options. 26 | * 27 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Scheduler/postTask#options) 28 | */ 29 | export type SchedulerPostTaskOptions = { 30 | /** The immutable {@link TaskPriority} of the task. One of `"user-blocking"`, `"user-visible"`, or `"background"`. If set, this priority is used for the lifetime of the task and priority set on the `signal` is ignored. */ 31 | priority?: TaskPriority; 32 | /** An {@link AbortSignal} or {@link TaskSignal} that can be used to abort or re-prioritize the task (from its associated controller). The signal's priority is ignored if `priority` is set. */ 33 | signal?: AbortSignal | TaskSignal; 34 | /** The minimum amount of time after which the task will be added to the scheduler queue, in whole milliseconds. The actual delay may be higher than specified, but will not be less. The default delay is 0. */ 35 | delay?: number; 36 | }; 37 | 38 | /** 39 | * {@link TaskController} options. 40 | * 41 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TaskController/TaskController#options) 42 | */ 43 | type TaskControllerOptions = { 44 | /** The {@link TaskPriority} of the signal associated with this {@link TaskController}. One of `"user-blocking"`, `"user-visible"`, or `"background"`. The default is `"user-visible"`. */ 45 | priority?: TaskPriority; 46 | } 47 | 48 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/TaskPriorityChangeEvent/TaskPriorityChangeEvent#options) */ 49 | interface TaskPriorityChangeEventInit extends EventInit { 50 | /** A string indicating the previous priority of the task. One of `"user-blocking"`, `"user-visible"`, or `"background"`. */ 51 | previousPriority: TaskPriority; 52 | } 53 | 54 | declare global { 55 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Scheduler) */ 56 | interface Scheduler { 57 | /** 58 | * Adds a task to the scheduler as a callback, optionally specifying a priority, delay, and/or a signal for aborting the task. 59 | * @param callback A callback function that implements the task. The return value of the callback is used to resolve the promise returned by this function. 60 | * @param options {@link SchedulerPostTaskOptions} options. 61 | * 62 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Scheduler/postTask) 63 | */ 64 | postTask(callback: () => T, options?: SchedulerPostTaskOptions): Promise; 65 | /** 66 | * Returns a promise that yields to the event loop when awaited, allowing continuation in a new task. 67 | * 68 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Scheduler/yield) 69 | */ 70 | yield(): Promise; 71 | } 72 | 73 | /** 74 | * A controller object that can be used to both abort and change the priority of one or more prioritized tasks. 75 | * 76 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TaskController) 77 | */ 78 | class TaskController extends AbortController { 79 | constructor(options?: TaskControllerOptions); 80 | /** Returns a {@link TaskSignal} instance. The signal is passed to tasks so that they can be aborted or re-prioritized by the controller. */ 81 | readonly signal: TaskSignal; 82 | /** 83 | * Sets the priority of the controller's signal, and hence the priority of any tasks with which it is associated. 84 | * 85 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TaskController/setPriority) 86 | */ 87 | setPriority(priority: TaskPriority): void; 88 | } 89 | 90 | /** 91 | * The `prioritychange` event, sent to a {@link TaskSignal} if its priority is changed. 92 | * 93 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TaskPriorityChangeEvent) 94 | */ 95 | class TaskPriorityChangeEvent extends Event { 96 | constructor(type: string, init: TaskPriorityChangeEventInit); 97 | /** 98 | * The priority of the corresponding {@link TaskSignal} _before_ this prioritychange event. 99 | * 100 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TaskPriorityChangeEvent/previousPriority) 101 | */ 102 | readonly previousPriority: TaskPriority; 103 | } 104 | 105 | /** 106 | * A signal object that allows you to communicate with a prioritized task, aborting it or changing the priority (if required) via a {@link TaskController} object. 107 | * 108 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TaskSignal) 109 | */ 110 | class TaskSignal extends AbortSignal { 111 | /** 112 | * The priority of the signal. 113 | * 114 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TaskSignal/priority) 115 | */ 116 | readonly priority: TaskPriority; 117 | /** 118 | * Fired when the priority is changed. 119 | * 120 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TaskSignal/prioritychange_event) 121 | */ 122 | onprioritychange: (event: TaskPriorityChangeEvent) => void; 123 | } 124 | 125 | /** 126 | * The global {@link Scheduler} entry point for using the Prioritized Task Scheduling API. 127 | * 128 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/scheduler) 129 | */ 130 | const scheduler: Scheduler; 131 | } 132 | -------------------------------------------------------------------------------- /src/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/0a917ca3a695c990ccc8dc06c7953cd3b9fec7eb/src/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /src/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/0a917ca3a695c990ccc8dc06c7953cd3b9fec7eb/src/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /src/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/0a917ca3a695c990ccc8dc06c7953cd3b9fec7eb/src/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /src/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/0a917ca3a695c990ccc8dc06c7953cd3b9fec7eb/src/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /src/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/0a917ca3a695c990ccc8dc06c7953cd3b9fec7eb/src/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /src/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/0a917ca3a695c990ccc8dc06c7953cd3b9fec7eb/src/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /src/webfonts/fa-v4compatibility.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/0a917ca3a695c990ccc8dc06c7953cd3b9fec7eb/src/webfonts/fa-v4compatibility.ttf -------------------------------------------------------------------------------- /src/webfonts/fa-v4compatibility.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/0a917ca3a695c990ccc8dc06c7953cd3b9fec7eb/src/webfonts/fa-v4compatibility.woff2 -------------------------------------------------------------------------------- /tools/userscript.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import build from 'user.js'; 3 | 4 | build(); 5 | -------------------------------------------------------------------------------- /tools/web-ext.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | verbose: false, 3 | artifactsDir: './build', 4 | sourceDir: './build/firefox', 5 | ignoreFiles: [ 6 | '*.web-extension-id', 7 | 'web-ext-artifacts' 8 | ], 9 | build: { 10 | asNeeded: false, 11 | overwriteDest: true, 12 | }, 13 | run: { 14 | devtools: true, 15 | firefox: 'deved', 16 | firefoxProfile: 'UserJS', 17 | target: ['firefox-desktop'], 18 | startUrl: ['https://www.google.com'], 19 | watchFile: ['./build/firefox'] 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /tools/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import { fileURLToPath } from 'url'; 6 | import { merge } from 'webpack-merge'; 7 | import CopyPlugin from 'copy-webpack-plugin'; 8 | import TerserPlugin from 'terser-webpack-plugin'; 9 | 10 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 11 | const access = fs.promises.access; 12 | const constants = fs.promises.constants; 13 | const readFile = fs.promises.readFile; 14 | const file = (dir) => path.resolve(path.resolve(__dirname, '..'), dir); 15 | const globOptions = { 16 | dot: true, 17 | gitignore: true, 18 | ignore: ['**/*.txt'] 19 | }; 20 | /** 21 | * Object is `null` or `undefined` 22 | * @template O 23 | * @param { O } obj 24 | * @returns { boolean } 25 | */ 26 | const isNull = (obj) => { 27 | return Object.is(obj, null) || Object.is(obj, undefined); 28 | }; 29 | const canAccess = (filePath, encoding = 'utf-8') => { 30 | return new Promise((resolve, reject) => { 31 | access(filePath, constants.R_OK | constants.W_OK).then((testAccess) => { 32 | if (isNull(testAccess)) { 33 | resolve(readFile(filePath, encoding).then((data) => data.toString())); 34 | } 35 | reject(new Error(`Cannot access provided filePath: ${filePath}`)); 36 | }); 37 | }); 38 | }; 39 | const fileToJSON = async (filePath, encoding = 'utf-8') => { 40 | const testAccess = await canAccess(filePath, encoding); 41 | return JSON.parse(testAccess); 42 | }; 43 | export default async (env, args) => { 44 | if (!env) { 45 | throw new Error('--env flag required') 46 | } 47 | if (!env.brws) { 48 | throw new Error('--env brws= flag required') 49 | } 50 | const brws = env.brws; 51 | const webExtDir = `build/${brws}`; 52 | const webExtSrc = 'src'; 53 | const plugins = [ 54 | new CopyPlugin({ 55 | patterns: [ 56 | { 57 | from: file(`${webExtSrc}/manifest/${brws}.json`), 58 | to: file(`${webExtDir}/manifest.json`), 59 | async transform(content) { 60 | const { version, author, homepage: homepage_url } = await fileToJSON('./package.json'); 61 | const manifest = JSON.parse(content); 62 | return JSON.stringify( 63 | Object.assign(manifest, { 64 | version, 65 | author, 66 | homepage_url 67 | }), 68 | null, 69 | ' ' 70 | ); 71 | } 72 | }, 73 | { 74 | from: file(`${webExtSrc}/_locales`), 75 | to: file(`${webExtDir}/_locales`), 76 | globOptions 77 | }, 78 | { 79 | from: file(`${webExtSrc}/html`), 80 | to: file(`${webExtDir}/[name][ext]`), 81 | globOptions 82 | }, 83 | { 84 | from: file(`${webExtSrc}/img`), 85 | to: file(`${webExtDir}/img`), 86 | globOptions 87 | }, 88 | { 89 | from: file(`${webExtSrc}/webfonts`), 90 | to: file(`${webExtDir}/webfonts`), 91 | globOptions 92 | }, 93 | // { 94 | // from: file(`${webExtSrc}/web_accessible_resources`), 95 | // to: file(`${webExtDir}/web_accessible_resources`), 96 | // globOptions 97 | // }, 98 | { 99 | from: file(`${webExtSrc}/js`), 100 | to: file(`${webExtDir}/js`), 101 | globOptions 102 | // force: true, 103 | } 104 | ] 105 | }) 106 | ]; 107 | /** 108 | * @type { import('@types/webpack').Configuration } 109 | */ 110 | const Config = { 111 | context: file(webExtSrc), 112 | entry: { 113 | mu: './js/mu.js' 114 | }, 115 | output: { 116 | path: file(`${webExtDir}/js`), 117 | clean: true, 118 | filename: '[name].js', 119 | publicPath: `/${webExtDir}` 120 | }, 121 | module: { 122 | rules: [ 123 | { 124 | test: /\.m?js$/, 125 | exclude: /(node_modules|bower_components)/, 126 | use: { 127 | loader: 'swc-loader', 128 | options: { 129 | sync: true, 130 | jsc: { 131 | parser: { 132 | syntax: 'ecmascript' 133 | }, 134 | target: 'es2020' 135 | }, 136 | module: { 137 | type: 'es6' 138 | } 139 | } 140 | } 141 | } 142 | ] 143 | }, 144 | resolve: { 145 | extensions: ['.js'] 146 | }, 147 | plugins, 148 | node: false, 149 | performance: { 150 | hints: false 151 | } 152 | }; 153 | /** 154 | * @type { import('@types/webpack').Configuration } 155 | */ 156 | const Production = { 157 | mode: 'production', 158 | optimization: { 159 | minimize: true, 160 | minimizer: [ 161 | new TerserPlugin({ 162 | terserOptions: { 163 | format: { 164 | comments: false 165 | } 166 | }, 167 | extractComments: false, 168 | parallel: true 169 | }) 170 | ] 171 | } 172 | }; 173 | /** 174 | * @type { import('@types/webpack').Configuration } 175 | */ 176 | const Development = { 177 | mode: 'development', 178 | devtool: 'source-map', 179 | optimization: { 180 | minimize: false 181 | }, 182 | watch: true, 183 | watchOptions: { 184 | ignored: /(node_modules|bower_components)/ 185 | } 186 | }; 187 | switch (args.mode) { 188 | case 'development': 189 | return merge(Config, Development); 190 | case 'production': 191 | return merge(Config, Production); 192 | default: 193 | throw new Error('No matching configuration was found!'); 194 | } 195 | }; 196 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig.json", 3 | "compilerOptions": { 4 | "strict": true, 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "allowSyntheticDefaultImports": true 10 | }, 11 | "include": [ 12 | "src/typings/*.d.ts" 13 | ], 14 | "exclude": ["node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /utils/builder/README.md: -------------------------------------------------------------------------------- 1 | # UserScript Builder 2 | -------------------------------------------------------------------------------- /utils/builder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/package.json", 3 | "name": "@userjs/builder", 4 | "description": "Create a UserScript", 5 | "version": "1.0.0", 6 | "license": "MIT", 7 | "type": "module", 8 | "main": "./src/index.js", 9 | "types": "./typings/index.d.ts", 10 | "exports": { 11 | ".": { 12 | "import": { 13 | "types": "./typings/index.d.ts", 14 | "default": "./src/index.js" 15 | } 16 | } 17 | }, 18 | "directories": { 19 | "lib": "src" 20 | }, 21 | "files": [ 22 | "src", 23 | "typings" 24 | ], 25 | "dependencies": { 26 | "@userjs/i18n": "workspace:^", 27 | "minimist": "^1.2.8", 28 | "sass-embedded": "^1.85.1", 29 | "watchpack": "^2.4.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /utils/builder/src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { PathLike } from 'node:fs'; 2 | 3 | /** 4 | * Loads all the structures in the provided directory. 5 | * 6 | * @param dir - The directory to load the structures from 7 | * @param recursive - Whether to recursively load the structures in the directory 8 | */ 9 | export declare function loadLanguages(dir: PathLike, recursive?: boolean): Promise; 10 | export declare function build(): Promise; 11 | export interface UserJS { 12 | name: string; 13 | description: string; 14 | version: string; 15 | license?: string; 16 | bugs?: URL; 17 | homepage?: URL; 18 | icon?: PathLike; 19 | downloadURL?: URL; 20 | updateURL?: URL; 21 | url_source?: URL; 22 | url?: URL; 23 | build: { 24 | source: { 25 | languageList: string; 26 | [source: string]: PathLike; 27 | }; 28 | watch: { 29 | files: string[]; 30 | dirs: PathLike[]; 31 | }; 32 | paths: { 33 | fileName: string; 34 | dir: PathLike; 35 | dev?: { 36 | fileName?: string; 37 | dir?: PathLike; 38 | }; 39 | }; 40 | }; 41 | metadata: { 42 | compatible: string[]; 43 | connect: string[]; 44 | grant: string[]; 45 | exclude: string[]; 46 | include: string[]; 47 | 'exclude-match': string[]; 48 | match: string[]; 49 | noframes: boolean; 50 | resource: { 51 | [name: string]: string; 52 | }; 53 | require: string[]; 54 | 'run-at': 'document-start' | 'document-body' | 'document-end' | 'document-idle'; 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /utils/builder/typings/index.d.ts: -------------------------------------------------------------------------------- 1 | import { PathLike } from 'node:fs'; 2 | 3 | export interface UserJS { 4 | name: string; 5 | description: string; 6 | version: string; 7 | license?: string; 8 | bugs?: URL; 9 | homepage?: URL; 10 | icon?: PathLike; 11 | downloadURL?: URL; 12 | updateURL?: URL; 13 | url_source?: URL; 14 | url?: URL; 15 | build: { 16 | source: { 17 | languageList: string; 18 | [source: string]: PathLike; 19 | }; 20 | watch: { 21 | files: string[]; 22 | dirs: PathLike[]; 23 | }; 24 | paths: { 25 | fileName: string; 26 | dir: PathLike; 27 | i18n?: { 28 | default: string; 29 | dir: PathLike; 30 | }; 31 | dev?: { 32 | fileName?: string; 33 | dir?: PathLike; 34 | }; 35 | }; 36 | }; 37 | metadata: { 38 | compatible: string[]; 39 | connect: string[]; 40 | grant: string[]; 41 | exclude: string[]; 42 | include: string[]; 43 | 'exclude-match': string[]; 44 | match: string[]; 45 | noframes: boolean; 46 | resource: { 47 | [name: string]: string; 48 | }; 49 | require: string[]; 50 | 'run-at': 'document-start' | 'document-body' | 'document-end' | 'document-idle'; 51 | }; 52 | } 53 | 54 | export declare function build(): Promise; 55 | -------------------------------------------------------------------------------- /utils/i18n/README.md: -------------------------------------------------------------------------------- 1 | # UserScript i18n 2 | -------------------------------------------------------------------------------- /utils/i18n/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/package.json", 3 | "name": "@userjs/i18n", 4 | "description": "Loads languages", 5 | "version": "1.0.0", 6 | "license": "MIT", 7 | "type": "module", 8 | "main": "./src/index.js", 9 | "types": "./typings/index.d.ts", 10 | "exports": { 11 | ".": { 12 | "import": { 13 | "types": "./typings/index.d.ts", 14 | "default": "./src/index.js" 15 | } 16 | } 17 | }, 18 | "directories": { 19 | "lib": "src" 20 | }, 21 | "files": [ 22 | "src", 23 | "typings" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /utils/i18n/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import { readdir, readFile, stat } from 'node:fs/promises'; 3 | import { URL, fileURLToPath } from 'node:url'; 4 | 5 | /** 6 | * 7 | * @type { import('../typings/index.d.ts').loadLanguages } 8 | */ 9 | async function loadLanguages(dir, recursive = true, mapper = new Map()) { 10 | try { 11 | // Get the stats of the directory 12 | const statDir = await stat(dir); 13 | 14 | // If the provided directory path is not a directory, throw an error 15 | if (!statDir.isDirectory()) { 16 | throw new Error(`The directory '${dir}' is not a directory.`); 17 | } 18 | 19 | // Get all the files in the directory 20 | const files = await readdir(dir); 21 | 22 | // Loop through all the files in the directory 23 | for (const file of files) { 24 | // Get the stats of the file 25 | const statFile = await stat(new URL(`${dir}/${file}`)); 26 | 27 | // If the file is a directory and recursive is true, recursively load the structures in the directory 28 | if (statFile.isDirectory() && recursive) { 29 | await loadLanguages(new URL(`${dir}/${file}`), recursive, mapper); 30 | continue; 31 | } 32 | 33 | if (!file.endsWith('.json')) { 34 | continue; 35 | } 36 | const filePath = fileURLToPath(`${dir}/${file}`); 37 | const reg = /_locales\\(.*?)\\messages\.json/g.exec(filePath); 38 | if (reg) { 39 | const structure = await readFile(filePath, 'utf-8'); 40 | const obj = {}; 41 | for (const [k, v] of Object.entries(JSON.parse(structure.toString('utf-8')))) 42 | obj[k] = v.message ?? ''; 43 | mapper.set(reg[1], obj); 44 | } 45 | } 46 | } catch (ex) { 47 | console.error(ex); 48 | } 49 | 50 | return mapper; 51 | } 52 | 53 | export { loadLanguages }; 54 | export default loadLanguages; 55 | -------------------------------------------------------------------------------- /utils/i18n/typings/index.d.ts: -------------------------------------------------------------------------------- 1 | import { PathLike } from 'node:fs'; 2 | 3 | /** 4 | * Loads all the structures in the provided directory. 5 | * 6 | * @param dir - The directory to load the structures from 7 | */ 8 | export declare function loadLanguages(dir: PathLike, recursive?: boolean): Promise>; 9 | // export declare function loadLanguages(dir: PathLike, recursive?: boolean): Promise; 10 | -------------------------------------------------------------------------------- /utils/user.js/README.md: -------------------------------------------------------------------------------- 1 | # UserScript Builder 2 | -------------------------------------------------------------------------------- /utils/user.js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/package.json", 3 | "name": "user.js", 4 | "description": "Create a UserScript", 5 | "version": "1.0.0", 6 | "license": "MIT", 7 | "type": "module", 8 | "main": "./src/index.js", 9 | "types": "./typings/index.d.ts", 10 | "exports": { 11 | ".": { 12 | "import": { 13 | "types": "./typings/index.d.ts", 14 | "default": "./src/index.js" 15 | } 16 | } 17 | }, 18 | "directories": { 19 | "lib": "src" 20 | }, 21 | "files": [ 22 | "src", 23 | "typings" 24 | ], 25 | "dependencies": { 26 | "@userjs/builder": "workspace:^", 27 | "@userjs/i18n": "workspace:^" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /utils/user.js/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import { build } from '@userjs/builder'; 3 | import { loadLanguages } from '@userjs/i18n'; 4 | 5 | export { build, loadLanguages }; 6 | export default build; 7 | -------------------------------------------------------------------------------- /utils/user.js/typings/index.d.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import { build } from '@userjs/builder'; 3 | import { loadLanguages } from '@userjs/i18n'; 4 | 5 | export { build, loadLanguages }; 6 | export default build; 7 | -------------------------------------------------------------------------------- /wiki/Build.md: -------------------------------------------------------------------------------- 1 | # Build 2 | 3 | Install [Node.js](https://nodejs.org/) and [pnpm](https://pnpm.io/) - *the version of Node.js should match or be greater than the `"node"` key in `package.json`.* 4 | 5 | ```sh 6 | # Install dependencies 7 | $ pnpm i 8 | ``` 9 | 10 | **UserScript:** 11 | 12 | Before you begin, rename `.env.example` to `.env` 13 | 14 | **Recommened Testing Environments:** 15 | 16 | - **Desktop:** any browser with [ViolentMonkey](https://violentmonkey.github.io/), see [How to edit scripts with your favorite editor?](https://violentmonkey.github.io/posts/how-to-edit-scripts-with-your-favorite-editor/) 17 | - **Mobile:** 18 | - Desktop: use "Responsive Design Mode (Ctrl+Shift+M)" on [FireFox Developer Edition](https://www.mozilla.org/firefox/developer/) or [FireFox](https://www.mozilla.org/firefox/) 19 | - Android: any browser with UserScript support or support for installing a UserScript manager, I use [Cromite](https://github.com/uazo/cromite) 20 | - IOS: any browser with UserScript support or support for installing a UserScript manager 21 | 22 | ```sh 23 | # Watch and build to local HTTP server 24 | $ pnpm run dev:UserJS 25 | 26 | # Build UserScript to "./dist" 27 | $ pnpm run pub:UserJS 28 | ``` 29 | 30 | --- 31 | 32 | **WebExtension:** 33 | 34 | ```sh 35 | # Build WebExtension (developer version) for "Chromium" to "./build/chrome" 36 | $ pnpm run dev:Cr 37 | # Build WebExtension (developer version) for "Firefox" to "./build/firefox" 38 | $ pnpm run dev:FF 39 | # Build WebExtension (public version) for "Chromium" to "./build/chrome" 40 | $ pnpm run webpack:Cr 41 | # Build WebExtension (public version) for "Firefox" to "./build/firefox" 42 | $ pnpm run webpack:FF 43 | 44 | # [ Testing ] 45 | # Browsers can be launched via "Run and Debug" in VSCode. 46 | # Edit runtimeExecutable in ".vscode/launch.json" to your Chrome executable. 47 | # Recommended to create an additional profile "about:profiles" for Firefox. 48 | ``` 49 | 50 | ## Contribution Rules 51 | 52 | **General:** 53 | 54 | Follow [Semantic Versioning](https://semver.org/) standards, if your unsure you don't need to update the version 55 | 56 | --- 57 | 58 | **Translations:** 59 | 60 | Translations follow [Web Extension Internationalization](https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/Internationalization) standards 61 | 62 | --- 63 | 64 | **UserScript:** 65 | 66 | Do not edit the UserScript found in the `./dist` directory, the file is auto generated from `dev:UserJS` command. Instead edit it from `./src/UserJS` 67 | 68 | ## Contribution Guide 69 | 70 | Fork this repo, make your changes, and open a [pull request](https://github.com/magicoflolis/Userscript-Plus/pulls) 71 | 72 | **Translations:** 73 | 74 | Translations are located in `./src/_locales` 75 | 76 | For versioning translations are a PATCH 77 | 78 | **Adding:** 79 | 80 | Recommend using `./src/_locales/en/messages.json` as a template 81 | 82 | **Updating:** 83 | 84 | Update any key from `messages.json` file 85 | -------------------------------------------------------------------------------- /wiki/README.md: -------------------------------------------------------------------------------- 1 | # Wiki Page 2 | 3 | - [Wiki Page](#wiki-page) 4 | - [GitHub Detection](#github-detection) 5 | - [Shortcuts](#shortcuts) 6 | 7 | ## GitHub Detection 8 | 9 | For this Web Extension / UserScript to detect your UserScript or UserStyle: 10 | 11 | - Your repository must have the following: 12 | - UserScripts **must** be in `.user.js` format 13 | - UserStyles **must** be in `.user.css` format 14 | - In the Topics section of your repository: 15 | - `domain` or `hostname`: 16 | - example: you create a userscript for `google.com` then put `google` in the Topics section 17 | - if you match all sites: `*` or `http*://*` then put `all-sites` in the Topics section 18 | - put `userscript` or `userstyle` in the Topics section 19 | 20 | _if you need to create a build environment, you can use my workspace and tools as a template._ 21 | 22 | ## Shortcuts 23 | 24 | **New Tab:** 25 | 26 | Syntax: `*` - Shortcut for userscipts that apply to `All sites` 27 | 28 | Syntax: `` 29 | 30 | ```txt 31 | # Example 32 | google.com 33 | ``` 34 | 35 | --- 36 | 37 | Syntax: `mujs:` 38 | 39 | ```txt 40 | # Available 41 | settings 42 | 43 | # Example 44 | mujs:settings 45 | ``` 46 | 47 | --- 48 | 49 | **Search Bar:** 50 | 51 | By default it will search by name and description, you can refine your searches using the examples listed below. 52 | 53 | Syntax: `filter:` 54 | 55 | ```txt 56 | # Available 57 | ascii 58 | latin 59 | games 60 | socialnetworks 61 | clutter 62 | 63 | # Example 64 | filter:ascii 65 | ``` 66 | 67 | --- 68 | 69 | Syntax: `code_url:` or `url:` 70 | 71 | ```txt 72 | # Example 73 | code_url:google.com 74 | ``` 75 | 76 | --- 77 | 78 | Syntax: `author:` or `users:` 79 | 80 | ```txt 81 | # Example 82 | author:magicoflolis 83 | ``` 84 | 85 | --- 86 | 87 | Syntax: `locale:` or `i18n:` 88 | 89 | ```txt 90 | # Example 91 | locale:en 92 | ``` 93 | 94 | --- 95 | 96 | Syntax: `id:` 97 | 98 | ```txt 99 | # Example 100 | id:421603 101 | ``` 102 | 103 | --- 104 | 105 | Syntax: `license:` 106 | 107 | ```txt 108 | # Example 109 | license:MIT 110 | ``` 111 | 112 | --- 113 | 114 | Syntax: `name:` 115 | 116 | ```txt 117 | # Example 118 | name:Userscript+ 119 | ``` 120 | 121 | --- 122 | 123 | Syntax: `description:` 124 | 125 | ```txt 126 | # Example 127 | description:Finds available userscripts for the current webpage. 128 | ``` 129 | 130 | --- 131 | 132 | Syntax: `search_engine:` or `engine:` 133 | 134 | ```txt 135 | # Example 136 | search_engine:greasyfork 137 | ``` 138 | 139 | --- 140 | 141 | Syntax: `recommend:` 142 | 143 | ```txt 144 | # Example 145 | recommend: 146 | ``` 147 | --------------------------------------------------------------------------------