您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add various search links to Audible (MyAnonaMouse, AudioBookBay, Mobilism, Goodreads, Anna's Archive, Z-Library & more)
当前为
// ==UserScript== // @name Audible Search Hub // @namespace https://greasyfork.org/en/users/1370284 // @version 0.2.0 // @license MIT // @description Add various search links to Audible (MyAnonaMouse, AudioBookBay, Mobilism, Goodreads, Anna's Archive, Z-Library & more) // @match https://*.audible.*/pd/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @require https://openuserjs.org/src/libs/sizzle/GM_config.js // ==/UserScript== const sites = { mam: { label: '🐭 MAM', name: 'MyAnonaMouse', url: 'https://www.myanonamouse.net', searchBy: { title: true, titleAuthor: true }, getLink: (search) => { const baseUrl = GM_config.get('url_mam'); const url = new URL(`${baseUrl}/tor/browse.php`); url.searchParams.set('tor[text]', search); return url.href; } }, abb: { label: '🎧 ABB', name: 'AudioBookBay', url: 'https://audiobookbay.lu', searchBy: { title: false, titleAuthor: true }, getLink: (search) => { const baseUrl = GM_config.get('url_abb'); const url = new URL(baseUrl); url.searchParams.set('s', search.toLowerCase()); return url.href; } }, mobilism: { label: '📱 Mobilism', name: 'Mobilism', url: 'https://forum.mobilism.org', searchBy: { title: false, titleAuthor: true }, getLink: (search) => { const baseUrl = GM_config.get('url_mobilism'); const url = new URL(`${baseUrl}/search.php`); url.searchParams.set('keywords', search); url.searchParams.set('sr', 'topics'); url.searchParams.set('sf', 'titleonly'); return url.href; } }, goodreads: { label: '🔖 Goodreads', name: 'Goodreads', url: 'https://www.goodreads.com', searchBy: { title: false, titleAuthor: true }, getLink: (search) => { const baseUrl = GM_config.get('url_goodreads'); const url = new URL(`${baseUrl}/search`); url.searchParams.set('q', search); return url.href; } }, anna: { label: '📚 Anna', name: "Anna's Archive", url: 'https://annas-archive.org', searchBy: { title: false, titleAuthor: true }, getLink: (search) => { const baseUrl = GM_config.get('url_anna'); const url = new URL(`${baseUrl}/search`); url.searchParams.set('q', search); url.searchParams.set('lang', 'en'); return url.href; } }, zlib: { label: '📕 zLib', name: 'Z-Library', url: 'https://z-lib.gs', searchBy: { title: false, titleAuthor: true }, getLink: (search) => { const baseUrl = GM_config.get('url_zlib'); const url = new URL(`${baseUrl}/s/${search}`); return url.href; } }, libgen: { label: '📗 Libgen', name: 'Libgen', url: 'https://libgen.rs', searchBy: { title: false, titleAuthor: true }, getLink: (search) => { const baseUrl = GM_config.get('url_libgen'); const url = new URL(`${baseUrl}/search`); url.searchParams.set('req', search); return url.href; } }, tgx: { label: '🌌 TGX', name: 'TorrentGalaxy', url: 'https://tgx.rs/torrents.php', searchBy: { title: false, titleAuthor: true }, getLink: (search) => { const baseUrl = GM_config.get('url_tgx'); const url = new URL(baseUrl); url.searchParams.set('search', search); return url.href; } }, btdig: { label: '⛏️ BTDig', name: 'BTDig', url: 'https://btdig.com', searchBy: { title: false, titleAuthor: true }, getLink: (search) => { const baseUrl = GM_config.get('url_btdig'); const url = new URL(`${baseUrl}/search`); url.searchParams.set('q', search); return url.href; } } }; const searchByFields = { title: { label: 't', description: 'title', }, titleAuthor: { label: 't+a', description: 'title + author', }, }; const addSiteConfig = (site) => { return { [`section_${site}`]: { label: `-------------- ${sites[site].name} 👇🏻 --------------`, type: 'hidden' }, [`enable_${site}`]: { label: 'Enable', type: 'checkbox', default: true }, [`url_${site}`]: { label: 'URL', type: 'text', default: sites[site].url }, [`enable_search_title_${site}`]: { label: 'Enable Search by Title', type: 'checkbox', default: sites[site].searchBy.title }, [`enable_search_titleAuthor_${site}`]: { label: 'Enable Search by Title + Author', type: 'checkbox', default: sites[site].searchBy.titleAuthor }, } } const perSiteFields = Object.keys(sites).reduce((acc, siteKey) => { return { ...acc, ...addSiteConfig(siteKey, sites[siteKey]) }; }, {}); GM_config.init({ id: 'audible-search-sites', title: 'Search Sites', fields: { open_in_new_tab: { label: 'Open Links in New Tab', type: 'checkbox', default: true }, ...perSiteFields, } }); GM_registerMenuCommand('Open Settings', () => { GM_config.open(); }); async function extractBookData(document2) { try { const acceptedTypes = ['Audiobook', 'Product', 'BreadcrumbList'] const result = {} const ldJsonScripts = document2.querySelectorAll( 'script[type="application/ld+json"]' ) ldJsonScripts.forEach((script) => { try { const jsonLdData = JSON.parse(script.textContent?.trim() || '') const items = Array.isArray(jsonLdData) ? jsonLdData : [jsonLdData] items.forEach((item) => { if (acceptedTypes.includes(item['@type'])) { result[item['@type']] = { ...result[item['@type']], ...item } } }) } catch (error) { console.error('Error parsing JSON-LD:', error) } }) return result } catch (error) { console.error(`Error parsing data: `, error) return {} } } const waitForBookDataScripts = () => new Promise((resolve, reject) => { const checkLdJson = async () => { const data = await extractBookData(document); if (data?.Audiobook) resolve(data); }; const observer = new MutationObserver(async (mutationsList) => { mutationsList.forEach((mutation) => mutation.addedNodes.forEach(async (node) => { if (node.nodeType === 1 && node.tagName === 'SCRIPT' && node.type === 'application/ld+json') { await checkLdJson(); } }) ); }); observer.observe(document, { childList: true, subtree: true }); checkLdJson().then((data) => data.Audiobook && observer.disconnect() && resolve(data)); setTimeout(() => { observer.disconnect(); reject(new Error('Timeout: ld+json script not found')); }, 2000); }); const style = document.createElement('style'); style.textContent = ` .custom-bc-tag { text-decoration: none; transition: background-color 0.2s ease; } .custom-bc-tag:hover { background-color: #f0f0f0; text-decoration: none; } `; document.head.appendChild(style); waitForBookDataScripts() .then((data) => { injectSearchLinks(data) }) .catch((error) => { console.error('Error:', error.message) }) function createLink(text, href, title) { const link = document.createElement('a'); link.href = href; link.textContent = text; link.target = GM_config.get('open_in_new_tab') ? '_blank' : '_self'; link.target = '_blank'; link.classList.add('bc-tag', 'bc-size-footnote', 'bc-tag-outline', 'bc-badge-tag', 'bc-badge', 'custom-bc-tag'); link.style.whiteSpace = 'nowrap'; link.title = title || text; return link; }; function createLinksContainer() { const container = document.createElement('div'); container.style.marginTop = '8px' container.style.display = 'flex' container.style.alignItems = 'center' container.style.flexWrap = 'wrap' container.style.gap = '4px' container.style.maxWidth = '340px' return container; } async function injectSearchLinks(data) { const title = data.Audiobook?.name const author = data.Audiobook?.author?.[0]?.name const titleAuthor = `${title} ${author} ` const authorLabelEl = document.querySelector('.authorLabel') const infoParentEl = authorLabelEl?.parentElement if (!infoParentEl) { console.warn("Can't find the parent element to inject links.") return } const linksContainer = createLinksContainer() Object.keys(sites).forEach((siteKey) => { if (GM_config.get(`enable_${siteKey}`)) { const { label, name, getLink } = sites[siteKey]; const enabledSearchFields = Object.keys(searchByFields).filter((field) => GM_config.get(`enable_search_${field}_${siteKey}`) ); const isMultipleEnabled = enabledSearchFields.length > 1; enabledSearchFields.forEach((field) => { const { label: searchLabel, description } = searchByFields[field]; const finalLabel = isMultipleEnabled ? `${label} (${searchLabel})` : label; const searchValue = field === 'titleAuthor' ? titleAuthor : title; const link = createLink(finalLabel, getLink(searchValue), `Search ${name} by ${description}`); linksContainer.append(link); }); } }); infoParentEl.parentElement.appendChild(linksContainer) }