Find userscripts for the current website from popular script repositories
// ==UserScript==
// @name Find Scripts For This Site
// @name:zh-CN 查找适用于当前网站的脚本
// @name:zh-TW 查找適用於當前網站的腳本
// @name:ja このサイト用のスクリプトを探す
// @name:ko 이 사이트용 스크립트 찾기
// @name:es Buscar scripts para este sitio
// @name:fr Trouver des scripts pour ce site
// @name:de Skripte für diese Website finden
// @name:ru Найти скрипты для этого сайта
// @namespace https://github.com/utags
// @homepageURL https://github.com/utags/userscripts#readme
// @supportURL https://github.com/utags/userscripts/issues
// @version 0.2.4
// @description Find userscripts for the current website from popular script repositories
// @description:zh-CN 查找适用于当前网站的用户脚本,支持多个脚本仓库
// @description:zh-TW 查找適用於當前網站的用戶腳本,支持多個腳本倉庫
// @description:ja 人気のスクリプトリポジトリから現在のウェブサイト用のユーザースクリプトを見つける
// @description:ko 인기 스크립트 저장소에서 현재 웹사이트용 사용자 스크립트 찾기
// @description:es Encuentra userscripts para el sitio web actual desde repositorios populares
// @description:fr Trouvez des scripts utilisateur pour le site Web actuel à partir de référentiels de scripts populaires
// @description:de Finden Sie Benutzerskripte für die aktuelle Website aus beliebten Skript-Repositories
// @description:ru Найдите пользовательские скрипты для текущего веб-сайта из популярных репозиториев скриптов
// @author Pipecraft
// @license MIT
// @match *://*/*
// @icon data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20viewBox%3D%220%200%2064%2064%22%20fill%3D%22none%22%3E%3Ctext%20x%3D%2232%22%20y%3D%2232%22%20text-anchor%3D%22middle%22%20dominant-baseline%3D%22middle%22%20font-family%3D%22Menlo%2C%20Monaco%2C%20Consolas%2C%20Courier%20New%2C%20monospace%22%20font-size%3D%2242%22%20font-weight%3D%22700%22%20fill%3D%22%231f2937%22%3E%7B%7D%3C/text%3E%3C/svg%3E
// @noframes
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_openInTab
// @grant GM.getValue
// @grant GM_getValue
// @grant GM.setValue
// @grant GM_setValue
// @grant GM_addStyle
// @grant GM.addValueChangeListener
// @grant GM_addValueChangeListener
// ==/UserScript==
//
;(() => {
'use strict'
var __defProp = Object.defineProperty
var __getOwnPropSymbols = Object.getOwnPropertySymbols
var __hasOwnProp = Object.prototype.hasOwnProperty
var __propIsEnum = Object.prototype.propertyIsEnumerable
var __defNormalProp = (obj, key, value) =>
key in obj
? __defProp(obj, key, {
enumerable: true,
configurable: true,
writable: true,
value,
})
: (obj[key] = value)
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop])
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop])
}
return a
}
var style_default =
'#find-scripts-settings-overlay{align-items:center;background:rgba(0,0,0,.5);display:flex;height:100%;justify-content:center;left:0;position:fixed;top:0;width:100%;z-index:9999}#find-scripts-settings-dialog{background:#fff;border-radius:8px;box-shadow:0 4px 20px rgba(0,0,0,.2);max-height:90%;max-width:90%;overflow-y:auto;padding:20px;width:400px}#find-scripts-settings-dialog h2{border-bottom:1px solid #eee;font-size:18px;margin-bottom:15px;margin-top:0;padding-bottom:10px}.find-scripts-setting-item{align-items:center;display:flex;margin-bottom:12px}.find-scripts-setting-item label{flex-grow:1;margin-left:8px}.find-scripts-buttons{display:flex;gap:10px;justify-content:flex-end;margin-top:15px}.find-scripts-buttons button{background:#f5f5f5;border:1px solid #ccc;border-radius:4px;cursor:pointer;padding:6px 12px}.find-scripts-buttons button:hover{background:#e5e5e5}.find-scripts-buttons button.primary{background:#4a86e8;border-color:#3a76d8;color:#fff}.find-scripts-buttons button.primary:hover{background:#3a76d8}'
function registerMenu(caption, onClick) {
if (typeof GM_registerMenuCommand === 'function') {
return GM_registerMenuCommand(caption, onClick)
}
return 0
}
function unregisterMenu(menuId) {
if (typeof GM_unregisterMenuCommand === 'function') {
GM_unregisterMenuCommand(menuId)
}
}
function openInTab(url, options) {
if (typeof GM_openInTab === 'function') {
GM_openInTab(url, options)
return
}
globalThis.open(url, '_blank')
}
async function getValue(key, defaultValue) {
if (typeof GM !== 'undefined' && typeof GM.getValue === 'function') {
return GM.getValue(key, defaultValue)
}
if (typeof GM_getValue === 'function') {
return GM_getValue(key, defaultValue)
}
return defaultValue
}
async function setValue(key, value) {
if (typeof GM !== 'undefined' && typeof GM.setValue === 'function') {
await GM.setValue(key, value)
return
}
if (typeof GM_setValue === 'function') {
GM_setValue(key, value)
}
}
function addStyle(css) {
if (typeof GM_addStyle === 'function') {
GM_addStyle(css)
return
}
const style = document.createElement('style')
style.textContent = css
document.head.append(style)
}
async function addValueChangeListener(key, callback) {
if (
typeof GM !== 'undefined' &&
typeof GM.addValueChangeListener === 'function'
) {
return GM.addValueChangeListener(key, callback)
}
if (typeof GM_addValueChangeListener === 'function') {
return GM_addValueChangeListener(key, callback)
}
return 0
}
var CONFIG = {
REPOSITORIES: [
{
id: 'greasy_fork',
name: 'Greasy Fork',
domainSearchUrl:
'https://greasyfork.org/scripts/by-site/{domain}?filter_locale=0',
domainSearchEnabled: true,
keywordSearchUrl:
'https://greasyfork.org/scripts?filter_locale=0&q={keyword}',
keywordSearchEnabled: true,
icon: '\u{1F374}',
},
{
id: 'openuserjs',
name: 'OpenUserJS',
keywordSearchUrl: 'https://openuserjs.org/?q={keyword}',
keywordSearchEnabled: true,
icon: '\u{1F4DC}',
},
{
id: 'scriptcat',
name: 'ScriptCat',
domainSearchUrl: 'https://scriptcat.org/search?domain={domain}',
domainSearchEnabled: true,
keywordSearchUrl: 'https://scriptcat.org/search?keyword={keyword}',
keywordSearchEnabled: true,
icon: '\u{1F431}',
},
{
id: 'github',
name: 'GitHub',
keywordSearchUrl:
'https://github.com/search?type=code&q=language%3AJavaScript+%22%3D%3DUserScript%3D%3D%22+{keyword}',
keywordSearchEnabled: true,
icon: '\u{1F419}',
},
{
id: 'github_gist',
name: 'GitHub Gist',
keywordSearchUrl:
'https://gist.github.com/search?l=JavaScript&q=%22%3D%3DUserScript%3D%3D%22+{keyword}',
keywordSearchEnabled: true,
icon: '\u{1F4DD}',
},
{
id: 'sleazy_fork',
name: 'Sleazy Fork',
domainSearchUrl:
'https://sleazyfork.org/scripts/by-site/{domain}?filter_locale=0',
domainSearchEnabled: false,
keywordSearchUrl:
'https://sleazyfork.org/scripts?filter_locale=0&q={keyword}',
keywordSearchEnabled: false,
icon: '\u{1F51E}',
},
],
DEBUG: false,
SETTINGS_KEY: 'find_scripts_settings',
}
var I18N = {
en: {
menu_domain: '{icon} Find scripts by domain on {name}',
menu_keyword: '{icon} Find scripts by keyword on {name}',
title_settings: 'Repository Settings',
btn_save: 'Save',
btn_cancel: 'Cancel',
title_domain: 'Domain Search',
title_keyword: 'Keyword Search',
menu_settings: '\u2699\uFE0F Settings',
},
'zh-CN': {
menu_domain:
'{icon} \u5728 {name} \u4E0A\u6309\u57DF\u540D\u67E5\u627E\u811A\u672C',
menu_keyword:
'{icon} \u5728 {name} \u4E0A\u6309\u5173\u952E\u5B57\u67E5\u627E\u811A\u672C',
title_settings: '\u4ED3\u5E93\u8BBE\u7F6E',
btn_save: '\u4FDD\u5B58',
btn_cancel: '\u53D6\u6D88',
title_domain: '\u57DF\u540D\u641C\u7D22',
title_keyword: '\u5173\u952E\u5B57\u641C\u7D22',
menu_settings: '\u2699\uFE0F \u8BBE\u7F6E',
},
'zh-TW': {
menu_domain:
'{icon} \u5728 {name} \u4E0A\u6309\u57DF\u540D\u67E5\u627E\u8173\u672C',
menu_keyword:
'{icon} \u5728 {name} \u4E0A\u6309\u95DC\u9375\u5B57\u67E5\u627E\u8173\u672C',
title_settings: '\u5009\u5EAB\u8A2D\u7F6E',
btn_save: '\u4FDD\u5B58',
btn_cancel: '\u53D6\u6D88',
title_domain: '\u57DF\u540D\u641C\u7D22',
title_keyword: '\u95DC\u9375\u5B57\u641C\u7D22',
menu_settings: '\u2699\uFE0F \u8A2D\u7F6E',
},
ja: {
menu_domain:
'{icon} {name} \u3067\u30C9\u30E1\u30A4\u30F3\u304B\u3089\u30B9\u30AF\u30EA\u30D7\u30C8\u3092\u63A2\u3059',
menu_keyword:
'{icon} {name} \u3067\u30AD\u30FC\u30EF\u30FC\u30C9\u304B\u3089\u30B9\u30AF\u30EA\u30D7\u30C8\u3092\u63A2\u3059',
title_settings: '\u30EA\u30DD\u30B8\u30C8\u30EA\u8A2D\u5B9A',
btn_save: '\u4FDD\u5B58',
btn_cancel: '\u30AD\u30E3\u30F3\u30BB\u30EB',
title_domain: '\u30C9\u30E1\u30A4\u30F3\u691C\u7D22',
title_keyword: '\u30AD\u30FC\u30EF\u30FC\u30C9\u691C\u7D22',
menu_settings: '\u2699\uFE0F \u8A2D\u5B9A',
},
ko: {
menu_domain:
'{icon} {name}\uC5D0\uC11C \uB3C4\uBA54\uC778\uC73C\uB85C \uC2A4\uD06C\uB9BD\uD2B8 \uCC3E\uAE30',
menu_keyword:
'{icon} {name}\uC5D0\uC11C \uD0A4\uC6CC\uB4DC\uB85C \uC2A4\uD06C\uB9BD\uD2B8 \uCC3E\uAE30',
title_settings: '\uC800\uC7A5\uC18C \uC124\uC815',
btn_save: '\uC800\uC7A5',
btn_cancel: '\uCDE8\uC18C',
title_domain: '\uB3C4\uBA54\uC778 \uAC80\uC0C9',
title_keyword: '\uD0A4\uC6CC\uB4DC \uAC80\uC0C9',
menu_settings: '\u2699\uFE0F \uC124\uC815',
},
es: {
menu_domain: '{icon} Buscar scripts por dominio en {name}',
menu_keyword: '{icon} Buscar scripts por palabra clave en {name}',
title_settings: 'Configuraci\xF3n de repositorios',
btn_save: 'Guardar',
btn_cancel: 'Cancelar',
title_domain: 'B\xFAsqueda por dominio',
title_keyword: 'B\xFAsqueda por palabra clave',
menu_settings: '\u2699\uFE0F Configuraci\xF3n',
},
fr: {
menu_domain: '{icon} Trouver des scripts par domaine sur {name}',
menu_keyword: '{icon} Trouver des scripts par mot-cl\xE9 sur {name}',
title_settings: 'Param\xE8tres des d\xE9p\xF4ts',
btn_save: 'Enregistrer',
btn_cancel: 'Annuler',
title_domain: 'Recherche par domaine',
title_keyword: 'Recherche par mot-cl\xE9',
menu_settings: '\u2699\uFE0F Param\xE8tres',
},
de: {
menu_domain: '{icon} Skripte nach Domain auf {name} finden',
menu_keyword: '{icon} Skripte nach Stichwort auf {name} finden',
title_settings: 'Repository-Einstellungen',
btn_save: 'Speichern',
btn_cancel: 'Abbrechen',
title_domain: 'Domain-Suche',
title_keyword: 'Stichwortsuche',
menu_settings: '\u2699\uFE0F Einstellungen',
},
ru: {
menu_domain:
'{icon} \u041D\u0430\u0439\u0442\u0438 \u0441\u043A\u0440\u0438\u043F\u0442\u044B \u043F\u043E \u0434\u043E\u043C\u0435\u043D\u0443 \u043D\u0430 {name}',
menu_keyword:
'{icon} \u041D\u0430\u0439\u0442\u0438 \u0441\u043A\u0440\u0438\u043F\u0442\u044B \u043F\u043E \u043A\u043B\u044E\u0447\u0435\u0432\u043E\u043C\u0443 \u0441\u043B\u043E\u0432\u0443 \u043D\u0430 {name}',
title_settings:
'\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432',
btn_save: '\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C',
btn_cancel: '\u041E\u0442\u043C\u0435\u043D\u0430',
title_domain:
'\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u0434\u043E\u043C\u0435\u043D\u0443',
title_keyword:
'\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u043A\u043B\u044E\u0447\u0435\u0432\u043E\u043C\u0443 \u0441\u043B\u043E\u0432\u0443',
menu_settings:
'\u2699\uFE0F \u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438',
},
}
var USER_LANG = detectLanguage()
var LANG_MAP =
USER_LANG === 'en'
? I18N.en
: __spreadValues(__spreadValues({}, I18N.en), I18N[USER_LANG])
function t(key) {
return LANG_MAP[key]
}
function detectLanguage() {
try {
const browserLang = (
navigator.language ||
navigator.userLanguage ||
'en'
).toLowerCase()
const supportedLangs = Object.keys(I18N)
if (supportedLangs.includes(browserLang)) {
return browserLang
}
const langBase = browserLang.split('-')[0]
const matchingLang = supportedLangs.find((lang) =>
lang.startsWith(langBase + '-')
)
if (matchingLang) {
return matchingLang
}
return 'en'
} catch (error) {
debugLog('Error detecting language:', error)
return 'en'
}
}
function debugLog(message, data = null) {
if (CONFIG.DEBUG) {
console.log('[Find Scripts] '.concat(message), data || '')
}
}
function extractDomain() {
try {
const hostname = globalThis.location.hostname
let domain = hostname.replace(/^www\./, '')
const parts = domain.split('.')
if (parts.length > 2) {
const secondLevelDomains = [
'co',
'com',
'org',
'net',
'edu',
'gov',
'mil',
]
const thirdLevelDomain = parts[parts.length - 2]
domain =
parts.length > 3 && secondLevelDomains.includes(thirdLevelDomain)
? parts.slice(-3).join('.')
: parts.slice(-2).join('.')
}
debugLog('Extracted domain:', domain)
return domain
} catch (error) {
debugLog('Error extracting domain:', error)
return globalThis.location.hostname
}
}
function getLocalizedMenuText(repo, isKeywordSearch = false) {
const key = isKeywordSearch ? 'menu_keyword' : 'menu_domain'
const template = t(key)
return template.replace('{icon}', repo.icon).replace('{name}', repo.name)
}
var MENU_IDS = []
var SETTINGS_MENU_ID
function clearMenus() {
for (const id of MENU_IDS) {
unregisterMenu(id)
}
MENU_IDS = []
if (SETTINGS_MENU_ID) {
unregisterMenu(SETTINGS_MENU_ID)
SETTINGS_MENU_ID = void 0
}
}
function registerAllMenus() {
const domain = extractDomain()
registerMenuCommands(domain)
registerSettingsMenu()
}
function registerMenuCommands(domain) {
for (const repo of CONFIG.REPOSITORIES) {
if (repo.domainSearchUrl && repo.domainSearchEnabled) {
const url = repo.domainSearchUrl.replace('{domain}', domain)
const menuText = getLocalizedMenuText(repo)
const id = registerMenu(menuText, () => {
debugLog('Opening '.concat(repo.name, ' for domain:'), domain)
openInTab(url, { active: true, insert: true })
})
MENU_IDS.push(id)
}
if (repo.keywordSearchUrl && repo.keywordSearchEnabled) {
const keywordUrl = repo.keywordSearchUrl.replace('{keyword}', domain)
const keywordMenuText = getLocalizedMenuText(repo, true)
const id = registerMenu(keywordMenuText, () => {
debugLog('Opening '.concat(repo.name, ' for keyword search:'), domain)
openInTab(keywordUrl, { active: true, insert: true })
})
MENU_IDS.push(id)
}
}
}
async function loadSettings() {
try {
const savedSettings = await getValue(CONFIG.SETTINGS_KEY)
if (savedSettings) {
for (const repo of CONFIG.REPOSITORIES) {
if (
repo.domainSearchUrl &&
savedSettings['domain_'.concat(repo.id)] !== void 0
) {
repo.domainSearchEnabled = savedSettings['domain_'.concat(repo.id)]
}
if (
repo.keywordSearchUrl &&
savedSettings['keyword_'.concat(repo.id)] !== void 0
) {
repo.keywordSearchEnabled =
savedSettings['keyword_'.concat(repo.id)]
}
}
debugLog('Settings loaded:', savedSettings)
}
} catch (error) {
debugLog('Error loading settings:', error)
}
}
async function saveSettings() {
var _a, _b
try {
const settings = {}
for (const repo of CONFIG.REPOSITORIES) {
if (repo.domainSearchUrl) {
settings['domain_'.concat(repo.id)] =
(_a = repo.domainSearchEnabled) != null ? _a : false
}
if (repo.keywordSearchUrl) {
settings['keyword_'.concat(repo.id)] =
(_b = repo.keywordSearchEnabled) != null ? _b : false
}
}
await setValue(CONFIG.SETTINGS_KEY, settings)
debugLog('Settings saved:', settings)
} catch (error) {
debugLog('Error saving settings:', error)
}
}
function showSettingsDialog() {
var _a, _b
addStyle(style_default)
const overlay = document.createElement('div')
overlay.id = 'find-scripts-settings-overlay'
const dialog = document.createElement('div')
dialog.id = 'find-scripts-settings-dialog'
const titleEl = document.createElement('h2')
titleEl.textContent = t('title_settings')
const content = document.createElement('div')
content.id = 'find-scripts-settings-content'
const btns = document.createElement('div')
btns.className = 'find-scripts-buttons'
const cancelBtn = document.createElement('button')
cancelBtn.id = 'find-scripts-cancel'
cancelBtn.textContent = t('btn_cancel')
const saveBtn = document.createElement('button')
saveBtn.id = 'find-scripts-save'
saveBtn.className = 'primary'
saveBtn.textContent = t('btn_save')
btns.append(cancelBtn, saveBtn)
dialog.append(titleEl, content, btns)
const contentWrap = dialog.querySelector('#find-scripts-settings-content')
const domainSection = document.createElement('div')
const domainTitle = document.createElement('h3')
domainTitle.textContent = t('title_domain')
domainSection.append(domainTitle)
contentWrap.append(domainSection)
for (const repo of CONFIG.REPOSITORIES) {
if (repo.domainSearchUrl) {
const item = document.createElement('div')
item.className = 'find-scripts-setting-item'
const checkbox = document.createElement('input')
checkbox.type = 'checkbox'
checkbox.id = 'find-scripts-domain-'.concat(repo.id)
checkbox.checked = (_a = repo.domainSearchEnabled) != null ? _a : false
const label = document.createElement('label')
label.htmlFor = 'find-scripts-domain-'.concat(repo.id)
label.textContent = ''.concat(repo.icon, ' ').concat(repo.name)
item.append(checkbox)
item.append(label)
domainSection.append(item)
}
}
const keywordSection = document.createElement('div')
const keywordTitle = document.createElement('h3')
keywordTitle.textContent = t('title_keyword')
keywordSection.append(keywordTitle)
contentWrap.append(keywordSection)
for (const repo of CONFIG.REPOSITORIES) {
if (repo.keywordSearchUrl) {
const item = document.createElement('div')
item.className = 'find-scripts-setting-item'
const checkbox = document.createElement('input')
checkbox.type = 'checkbox'
checkbox.id = 'find-scripts-keyword-'.concat(repo.id)
checkbox.checked = (_b = repo.keywordSearchEnabled) != null ? _b : false
const label = document.createElement('label')
label.htmlFor = 'find-scripts-keyword-'.concat(repo.id)
label.textContent = ''.concat(repo.icon, ' ').concat(repo.name)
item.append(checkbox)
item.append(label)
keywordSection.append(item)
}
}
overlay.append(dialog)
document.body.append(overlay)
saveBtn.addEventListener('click', () => {
for (const repo of CONFIG.REPOSITORIES) {
if (repo.domainSearchUrl) {
const domainCheckbox = document.querySelector(
'#find-scripts-domain-'.concat(repo.id)
)
repo.domainSearchEnabled = Boolean(domainCheckbox.checked)
}
if (repo.keywordSearchUrl) {
const keywordCheckbox = document.querySelector(
'#find-scripts-keyword-'.concat(repo.id)
)
repo.keywordSearchEnabled = Boolean(keywordCheckbox.checked)
}
}
void saveSettings()
overlay.remove()
})
cancelBtn.addEventListener('click', () => {
overlay.remove()
})
overlay.addEventListener('click', (event) => {
const target = event.target
if (target === overlay) overlay.remove()
})
}
function registerSettingsMenu() {
const menuText = t('menu_settings')
SETTINGS_MENU_ID = registerMenu(menuText, showSettingsDialog)
}
async function initialize() {
await loadSettings()
registerAllMenus()
void addValueChangeListener(CONFIG.SETTINGS_KEY, () => {
void (async () => {
await loadSettings()
clearMenus()
registerAllMenus()
})()
})
}
void initialize()
})()