您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Convert links using AllDebrid.com. Uses regex for accurate matching
// ==UserScript== // @name AllDebrid Premium Link Converter // @namespace https://gf.qytechs.cn/en/users/807108-jeremy-r // @version 1.1 // @description Convert links using AllDebrid.com. Uses regex for accurate matching // @author JRem // @include *://* // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_notification // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @run-at document-end // @license MIT // ==/UserScript== (function () { 'use strict'; const STORAGE_KEY = 'alldebrid_apikey'; const REGEXPS_KEY = 'alldebrid_host_regexps'; const TARGET_HOST = 'alldebrid.com'; const APIKEYS_URL = 'https://alldebrid.com/apikeys/'; const HOSTS_API_BASE = 'https://api.alldebrid.com/v4/hosts'; const UNLOCK_API_BASE = 'https://api.alldebrid.com/v4/link/unlock'; const MAGNET_UPLOAD_API = 'https://api.alldebrid.com/v4/magnet/upload'; // ---------- Utilities ---------- function isOnTargetHost() { const host = (window.location.hostname || '').toLowerCase(); return host === TARGET_HOST || host.endsWith('.' + TARGET_HOST); } function showToast(message, duration = 3000) { const id = 'alldebrid-apikey-toast'; let el = document.getElementById(id); if (!el) { el = document.createElement('div'); el.id = id; Object.assign(el.style, { position: 'fixed', right: '20px', bottom: '20px', zIndex: 999999, padding: '10px 16px', background: 'rgba(0,0,0,0.85)', color: 'white', fontFamily: 'sans-serif', fontSize: '13px', borderRadius: '6px', boxShadow: '0 2px 8px rgba(0,0,0,0.5)', opacity: '0', transition: 'opacity 200ms', pointerEvents: 'auto' }); document.body.appendChild(el); } el.textContent = message; requestAnimationFrame(() => { el.style.opacity = '1'; }); if (el._hideTimeout) clearTimeout(el._hideTimeout); el._hideTimeout = setTimeout(() => { el.style.opacity = '0'; el._hideTimeout = setTimeout(() => { if (el.parentNode) el.parentNode.removeChild(el); }, 220); }, duration); } // ---------- Storage helpers ---------- async function saveApiKey(key) { try { if (typeof GM_setValue === 'function') { GM_setValue(STORAGE_KEY, key); } else if (typeof GM !== 'undefined' && GM.setValue) { await GM.setValue(STORAGE_KEY, key); } else { localStorage.setItem(STORAGE_KEY, key); } console.log('Saved apikey:', key); return true; } catch (e) { console.error('saveApiKey error:', e); return false; } } async function readApiKey() { try { if (typeof GM_getValue === 'function') { return GM_getValue(STORAGE_KEY); } else if (typeof GM !== 'undefined' && GM.getValue) { return await GM.getValue(STORAGE_KEY); } else { return localStorage.getItem(STORAGE_KEY); } } catch (e) { console.error('readApiKey error:', e); return null; } } async function saveRegexps(list) { try { const json = JSON.stringify(list || []); if (typeof GM_setValue === 'function') { GM_setValue(REGEXPS_KEY, json); } else if (typeof GM !== 'undefined' && GM.setValue) { await GM.setValue(REGEXPS_KEY, json); } else { localStorage.setItem(REGEXPS_KEY, json); } console.log('Saved host regexps (count):', list ? list.length : 0); return true; } catch (e) { console.error('saveRegexps error:', e); return false; } } async function readRegexps() { try { let json = null; if (typeof GM_getValue === 'function') { json = GM_getValue(REGEXPS_KEY); } else if (typeof GM !== 'undefined' && GM.getValue) { json = await GM.getValue(REGEXPS_KEY); } else { json = localStorage.getItem(REGEXPS_KEY); } if (!json) return []; try { const arr = JSON.parse(json); return Array.isArray(arr) ? arr : []; } catch (e) { console.warn('Stored regexps parse error, clearing storage.', e); return []; } } catch (e) { console.error('readRegexps error:', e); return []; } } // ---------- Fetch helpers ---------- async function fetchSameOrigin(url) { try { const resp = await fetch(url, { method: 'GET', credentials: 'include', headers: { 'Accept': 'application/json, text/html, text/plain, */*' } }); const text = await resp.text(); let data = null; const ct = (resp.headers.get('content-type') || '').toLowerCase(); if (ct.includes('application/json')) { try { data = JSON.parse(text); } catch (e) { data = text; } } else { data = text; } return { success: true, status: resp.status, data, url }; } catch (e) { return { success: false, error: e.message || e, url }; } } function fetchWithGM(url, opts = {}) { return new Promise((resolve) => { const gm = (typeof GM_xmlhttpRequest !== 'undefined') ? GM_xmlhttpRequest : (typeof GM !== 'undefined' && GM.xmlHttpRequest) ? GM.xmlHttpRequest : null; if (!gm) { resolve({ success: false, error: 'GM_xmlhttpRequest not available', url }); return; } try { const cfg = { method: opts.method || 'GET', url, headers: opts.headers || { 'Accept': 'application/json, text/html, text/plain, */*' }, data: opts.data || null, withCredentials: !!opts.withCredentials, onload: function (res) { const raw = res.responseText || res.response || ''; let data = raw; const headers = (res.responseHeaders || '').toLowerCase(); try { if (headers.includes('application/json') || /^[\s{[]/.test(raw)) { data = JSON.parse(raw); } } catch (e) { // leave as text } resolve({ success: true, status: res.status, data, url, headers: res.responseHeaders, raw }); }, onerror: function (err) { resolve({ success: false, error: err, url }); }, ontimeout: function () { resolve({ success: false, error: 'timeout', url }); } }; gm(cfg); } catch (e) { resolve({ success: false, error: e.message || e, url }); } }); } // ---------- APIKey fetch ---------- async function tryFetchApikey() { console.log('Attempting to fetch', APIKEYS_URL); let resp; if (isOnTargetHost()) { resp = await fetchSameOrigin(APIKEYS_URL); if (!resp.success) resp = await fetchWithGM(APIKEYS_URL); } else { try { resp = await fetchSameOrigin(APIKEYS_URL); if (!resp.success || (resp.status >= 400 && resp.status !== 0)) { resp = await fetchWithGM(APIKEYS_URL); } } catch (e) { resp = await fetchWithGM(APIKEYS_URL); } } if (!resp || !resp.success) { console.warn('Fetch failed for apikeys URL:', resp && resp.error); return { found: false, reason: 'fetch_failed', detail: resp && resp.error }; } const data = resp.data; if (typeof data === 'object' && data !== null) { if ('apikey' in data && data.apikey) return { found: true, apikey: String(data.apikey) }; const key = findApiKeyInObject(data); if (key) return { found: true, apikey: key }; try { const s = JSON.stringify(data); const key2 = findApiKeyInText(s); if (key2) return { found: true, apikey: key2 }; } catch (e) { /* ignore */ } } if (typeof data === 'string') { const key = findApiKeyInText(data); if (key) return { found: true, apikey: key }; } return { found: false, reason: 'not_found_in_response', detail: resp.data }; } function findApiKeyInObject(obj, depth = 0) { if (obj === null || depth > 6) return null; if (typeof obj === 'string' || typeof obj === 'number') return null; if (Array.isArray(obj)) { for (const item of obj) { const found = findApiKeyInObject(item, depth + 1); if (found) return found; } return null; } const keys = Object.keys(obj || {}); for (const k of keys) { if (/apikey/i.test(k)) { const v = obj[k]; if (v !== null && v !== undefined) { const s = String(v).trim(); if (s.length >= 8) return s; } } } for (const k of keys) { try { const found = findApiKeyInObject(obj[k], depth + 1); if (found) return found; } catch (e) { /* ignore */ } } return null; } function findApiKeyInText(text) { if (!text || typeof text !== 'string') return null; let m = text.match(/["']\s*apikey\s*["']\s*:\s*["']([^"']{8,})["']/i); if (m && m[1]) return m[1]; m = text.match(/apikey\s*[:=]\s*["']([^"']{8,})["']/i); if (m && m[1]) return m[1]; m = text.match(/data-?apikey\s*=\s*["']([^"']{8,})["']/i); if (m && m[1]) return m[1]; m = text.match(/apikey=([A-Za-z0-9\-_]{8,})/i); if (m && m[1]) return m[1]; m = text.match(/["']\s*key\s*["']\s*:\s*["']([^"']{8,})["']/i); if (m && m[1]) return m[1]; return null; } // ---------- Fetch hosts and extract regexps (supports data.hosts.<site>.regexp) ---------- async function fetchHostsUsingApiKey(apikey) { const url = HOSTS_API_BASE + '?agent=userscript&apikey=' + encodeURIComponent(apikey); console.log('Fetching hosts API:', url); let resp; if (isOnTargetHost()) { resp = await fetchSameOrigin(url); if (!resp.success) resp = await fetchWithGM(url); } else { try { resp = await fetchSameOrigin(url); if (!resp.success || (resp.status >= 400 && resp.status !== 0)) { resp = await fetchWithGM(url); } } catch (e) { resp = await fetchWithGM(url); } } if (!resp || !resp.success) { console.warn('Hosts fetch failed:', resp && resp.error); return { success: false, reason: 'fetch_failed', detail: resp && resp.error, resp }; } const data = resp.data; if (!data || typeof data !== 'object') { if (typeof resp.data === 'string') { try { const parsed = JSON.parse(resp.data); return { success: true, payload: parsed, raw: resp.data }; } catch (e) { return { success: false, reason: 'unexpected_response', detail: resp.data, resp }; } } return { success: false, reason: 'unexpected_response', detail: resp.data, resp }; } return { success: true, payload: data, raw: resp.data }; } function extractRegexpsFromHostsPayload(payload) { const collected = []; if (!payload || typeof payload !== 'object') return []; function collectFromHostEntry(entry) { if (!entry) return; if (Array.isArray(entry)) { for (const r of entry) if (typeof r === 'string' && r.trim()) collected.push(r.trim()); return; } if (typeof entry === 'string') { if (entry.trim()) collected.push(entry.trim()); return; } const keys = Object.keys(entry || {}); const tryKeys = ['regexps', 'regexp', 'regex', 'patterns', 'pattern', 'match', 'matches']; for (const k of tryKeys) { if (k in entry && entry[k]) { const val = entry[k]; if (Array.isArray(val)) { for (const r of val) if (typeof r === 'string' && r.trim()) collected.push(r.trim()); } else if (typeof val === 'string') { if (val.trim()) collected.push(val.trim()); } } } for (const k of keys) { const v = entry[k]; if (!v) continue; if (typeof v === 'string' && /[\\\/\.\*\+\?\|\(\)\[\]\^]/.test(v) && v.length > 8) { collected.push(v.trim()); } else if (Array.isArray(v)) { for (const item of v) { if (typeof item === 'string' && item.trim()) collected.push(item.trim()); } } else if (typeof v === 'object' && v !== null) { for (const k2 of Object.keys(v)) { const vv = v[k2]; if (typeof vv === 'string' && vv.trim()) collected.push(vv.trim()); if (Array.isArray(vv)) for (const item of vv) if (typeof item === 'string' && item.trim()) collected.push(item.trim()); } } } } if (payload.data && payload.data.hosts) { const hosts = payload.data.hosts; if (Array.isArray(hosts)) { for (const h of hosts) collectFromHostEntry(h.regexps || h.regexp || h); } else if (typeof hosts === 'object') { for (const siteKey of Object.keys(hosts)) { const h = hosts[siteKey]; if (h && typeof h === 'object') { if ('regexp' in h && h.regexp) collectFromHostEntry(h.regexp); else if ('regexps' in h && h.regexps) collectFromHostEntry(h.regexps); else collectFromHostEntry(h); } else { collectFromHostEntry(h); } } } } else if (payload.hosts) { const hosts = payload.hosts; if (Array.isArray(hosts)) { for (const h of hosts) collectFromHostEntry(h.regexps || h.regexp || h); } else if (typeof hosts === 'object') { for (const siteKey of Object.keys(hosts)) { const h = hosts[siteKey]; if (h && typeof h === 'object') { if ('regexp' in h && h.regexp) collectFromHostEntry(h.regexp); else if ('regexps' in h && h.regexps) collectFromHostEntry(h.regexps); else collectFromHostEntry(h); } else { collectFromHostEntry(h); } } } } else if (Array.isArray(payload)) { for (const h of payload) collectFromHostEntry(h.regexps || h.regexp || h); } else { for (const k of Object.keys(payload)) { if (/hosts?/i.test(k) || /list/i.test(k)) { const candidate = payload[k]; if (Array.isArray(candidate)) { for (const h of candidate) collectFromHostEntry(h.regexps || h.regexp || h); } else if (typeof candidate === 'object') { for (const siteKey of Object.keys(candidate)) { collectFromHostEntry(candidate[siteKey]); } } } } } const uniq = Array.from(new Set(collected.map(s => (s || '').trim()).filter(s => s && s.length > 0))); return uniq; } function compileRegexpStrings(list) { const compiled = []; for (const s of (list || [])) { if (!s || typeof s !== 'string') continue; let pattern = s; let flags = 'i'; const m = s.match(/^\/(.+)\/([gimsuy]*)$/); if (m) { pattern = m[1]; flags = m[2] || ''; try { compiled.push(new RegExp(pattern, flags)); continue; } catch (e) { console.warn('Invalid regexp literal from hosts, skipping:', s, e); continue; } } try { compiled.push(new RegExp(pattern, flags)); } catch (e) { try { const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); compiled.push(new RegExp(escaped, flags)); } catch (e2) { console.warn('Failed to compile host regexp, skipping:', s, e2); } } } return compiled; } // ---------- Unlock API call (link) ---------- async function unlockLinkWithApi(apikey, targetUrl) { if (!apikey) return { ok: false, error: 'no_apikey' }; const url = UNLOCK_API_BASE + '?agent=userscript&apikey=' + encodeURIComponent(apikey) + '&link=' + encodeURIComponent(targetUrl); const headers = { 'Accept': 'application/json', 'Authorization': 'Bearer ' + apikey }; console.log('Calling unlock API (with Authorization):', url); try { const resp = await fetch(url, { method: 'GET', credentials: 'omit', headers }); const text = await resp.text(); let data = null; try { data = JSON.parse(text); } catch (e) { data = text; } return { ok: true, status: resp.status, data, raw: text }; } catch (e) { console.warn('Fetch unlock failed, trying GM_xmlhttpRequest fallback:', e); const gmResp = await fetchWithGM(url, { method: 'GET', headers }); if (!gmResp.success) return { ok: false, error: gmResp.error }; return { ok: true, status: gmResp.status, data: gmResp.data, raw: gmResp.raw }; } } // ---------- Magnet upload API call (POST magnets[]) ---------- async function uploadMagnetWithApi(apikey, magnetUrl) { if (!apikey) return { ok: false, error: 'no_apikey' }; const url = MAGNET_UPLOAD_API + '?agent=userscript&apikey=' + encodeURIComponent(apikey); const headers = { 'Accept': 'application/json', 'Authorization': 'Bearer ' + apikey, 'Content-Type': 'application/x-www-form-urlencoded' }; // body: magnets[]=<magnetUrl> const bodyStr = new URLSearchParams(); bodyStr.append('magnets[]', magnetUrl); console.log('Uploading magnet to API (with Authorization):', url); try { const resp = await fetch(url, { method: 'POST', credentials: 'omit', headers, body: bodyStr.toString() }); const text = await resp.text(); let data = null; try { data = JSON.parse(text); } catch (e) { data = text; } return { ok: true, status: resp.status, data, raw: text }; } catch (e) { console.warn('Fetch magnet upload failed, trying GM_xmlhttpRequest fallback:', e); // GM xhr fallback const gmResp = await fetchWithGM(url, { method: 'POST', headers, data: bodyStr.toString() }); if (!gmResp.success) return { ok: false, error: gmResp.error }; return { ok: true, status: gmResp.status, data: gmResp.data, raw: gmResp.raw }; } } // ---------- Attach buttons to matching anchors ---------- const BUTTON_CLASS = 'alldebrid-send-button'; const BUTTON_STYLE = ` .alldebrid-send-button { background: #fac63f; color: #111; border: none; border-radius: 4px; padding: 4px 8px; margin-left: 6px; cursor: pointer; font-size: 12px; font-family: sans-serif; } .alldebrid-send-button[disabled] { opacity: 0.6; cursor: default; } `; try { GM_addStyle(BUTTON_STYLE); } catch (e) { const styleEl = document.createElement('style'); styleEl.textContent = BUTTON_STYLE; document.head && document.head.appendChild(styleEl); } function findMatchingAnchors(compiledRegexps) { const anchors = Array.from(document.querySelectorAll('a[href]')); const matches = []; for (const a of anchors) { const href = a.getAttribute('href') || ''; if (!href) continue; for (const r of compiledRegexps) { try { if (r.test(href)) { matches.push(a); break; } } catch (e) { // skip problematic regex } finally { try { if (r.global) r.lastIndex = 0; } catch (e) {} } } // also include magnet: links even if not matched by hosts regexps if (href.startsWith('magnet:') && !matches.includes(a)) { matches.push(a); } } return matches; } async function attachButtonsToMatchingAnchors(compiledRegexps) { try { const anchors = findMatchingAnchors(compiledRegexps); if (!anchors || anchors.length === 0) { console.log('No matching anchors found to attach buttons to.'); return []; } const attached = []; const apikey = await readApiKey(); for (const a of anchors) { if (a.dataset && a.dataset.alldebridButtonAttached) continue; const btn = document.createElement('button'); btn.className = BUTTON_CLASS; btn.type = 'button'; btn.textContent = 'Send to AD'; btn.title = 'Send this link to Alldebrid'; btn.style.whiteSpace = 'nowrap'; const originalHref = a.href || a.getAttribute('href'); btn.addEventListener('click', async function (ev) { ev.preventDefault(); ev.stopPropagation(); if (btn.disabled) return; btn.disabled = true; const prevText = btn.textContent; btn.textContent = 'Sending...'; try { let apiResp; if ((originalHref || '').startsWith('magnet:')) { // Magnet upload apiResp = await uploadMagnetWithApi(apikey, originalHref); } else { // Regular unlock apiResp = await unlockLinkWithApi(apikey, originalHref); } if (!apiResp || apiResp.ok === false) { console.error('API request failed:', apiResp); showToast('Request failed. See console.', 3500); btn.textContent = prevText; btn.disabled = false; return; } const payload = apiResp.data || null; let newLink = null; // Try to extract new link depending on response shape if (payload && typeof payload === 'object') { // link unlock format: { status: true, data: { link: '...' } } if (payload.data && typeof payload.data === 'object') { if (Array.isArray(payload.data.magnets) && payload.data.magnets.length) { // magnet upload: try to find first magnets[].link or magnets[].download or magnets[].id const m0 = payload.data.magnets[0]; if (m0 && typeof m0 === 'object') { if (m0.link) newLink = m0.link; else if (m0.download) newLink = m0.download; else if (m0.file && m0.file.link) newLink = m0.file.link; } } if (!newLink && payload.data.link) newLink = payload.data.link; } // sometimes top-level 'link' exists if (!newLink && payload.link) newLink = payload.link; // fallback: deep search for "link" string in JSON if (!newLink) { try { const s = JSON.stringify(payload); const m = s.match(/"link"\s*:\s*"([^"]+)"/); if (m && m[1]) newLink = m[1]; } catch (e) { /* ignore */ } } } else if (typeof payload === 'string') { try { const parsed = JSON.parse(payload); if (parsed && parsed.data && parsed.data.link) newLink = parsed.data.link; else if (parsed && parsed.link) newLink = parsed.link; } catch (e) { /* not JSON */ } } if (newLink) { try { // Replace href and visible text with new URL to make it clear it worked a.href = newLink; a.textContent = newLink; showToast('Success: link replaced.', 3000); console.log('API success: replaced', { original: originalHref, newLink, resp: apiResp }); btn.textContent = 'Unlocked'; btn.disabled = true; btn.style.background = '#8fd38f'; } catch (e) { console.error('Error updating link on page:', e); showToast('Succeeded but failed to update link on page. See console.', 4500); btn.textContent = prevText; btn.disabled = false; } } else { console.warn('API response did not contain a usable link:', apiResp); showToast('No new link returned. See console.', 4500); btn.textContent = prevText; btn.disabled = false; } } catch (e) { console.error('Error during API call:', e); showToast('Error during request. See console.', 3500); btn.textContent = 'Send to AD'; btn.disabled = false; } }); try { a.parentNode && a.parentNode.insertBefore(btn, a.nextSibling); if (a.dataset) a.dataset.alldebridButtonAttached = '1'; attached.push({ anchor: a, button: btn }); } catch (e) { console.warn('Failed to insert button next to anchor:', e); } } console.log('Attached Send to AD buttons count:', attached.length); return attached; } catch (e) { console.error('attachButtonsToMatchingAnchors error:', e); return []; } } // ---------- Scanning helpers ---------- function scanPageWithCompiledRegexps(compiledRegexps) { const results = new Set(); try { const anchors = Array.from(document.querySelectorAll('a[href]')); for (const a of anchors) { const href = a.getAttribute('href') || ''; for (const r of compiledRegexps) { try { const matched = href.match(r); if (matched) { results.add(href); } } catch (e) { // skip invalid regex } finally { try { if (r.global) r.lastIndex = 0; } catch (e) {} } } if (href.startsWith('magnet:')) results.add(href); } const html = document.documentElement && document.documentElement.innerHTML ? document.documentElement.innerHTML : document.body && document.body.innerHTML ? document.body.innerHTML : ''; if (html) { for (const r of compiledRegexps) { try { let flags = r.flags || ''; if (!flags.includes('g')) { try { const r2 = new RegExp(r.source, flags + 'g'); let m; while ((m = r2.exec(html)) !== null) { results.add(m[0]); } continue; } catch (e) { /* fallback */ } } let m; while ((m = r.exec(html)) !== null) { results.add(m[0]); if (!r.global) break; } } catch (e) { /* skip invalid */ } } } } catch (e) { console.error('scanPageWithCompiledRegexps error:', e); } return Array.from(results); } async function scanPageUsingStoredRegexps() { const rawList = await readRegexps(); const compiled = compileRegexpStrings(rawList); const found = scanPageWithCompiledRegexps(compiled); console.log('Scan using stored regexps — found matches:', found); showToast(`Scan complete — ${found.length} matches found (see console).`, 3500); await attachButtonsToMatchingAnchors(compiled); return found; } // ---------- Public action: fetch hosts, save regexps, compile, attach buttons & scan ---------- async function actionUpdateHostsAndScan() { try { const apikey = await readApiKey(); if (!apikey) { showToast('No API key stored. Auto-grab may have failed — please log in to alldebrid.com and reload, or set API key manually via the menu.', 7000); console.warn('Cannot fetch hosts: no API key available.'); return; } showToast('Fetching hosts list from API...', 2500); const resp = await fetchHostsUsingApiKey(apikey); if (!resp.success) { console.warn('Hosts fetch failed:', resp); showToast('Failed to fetch hosts. See console. You may need a valid API key or network access.', 6000); return; } const regexps = extractRegexpsFromHostsPayload(resp.payload); if (!regexps || regexps.length === 0) { console.warn('No regexps extracted from hosts payload:', resp.payload); showToast('No host regexps found in API response. See console.', 5000); return; } await saveRegexps(regexps); const compiled = compileRegexpStrings(regexps); showToast(`Fetched and saved ${regexps.length} host regexps. Now scanning page...`, 3000); const found = scanPageWithCompiledRegexps(compiled); await attachButtonsToMatchingAnchors(compiled); showToast(`Host scan complete — ${found.length} unique matches found and buttons attached (see console).`, 4500); console.log('Alldebrid host regexps (count):', regexps.length); console.log('Compiled regexps:', compiled); console.log('Matches found on page:', found); } catch (e) { console.error('actionUpdateHostsAndScan error:', e); showToast('Error while updating hosts. See console.', 3500); } } // ---------- Menu & manual API key entry ---------- async function actionGrabApiKey() { showToast('Attempting to grab apikey...', 2000); console.log('Alldebrid: Grab APIKey started. On target host?', isOnTargetHost()); const result = await tryFetchApikey(); if (result.found) { const ok = await saveApiKey(result.apikey); if (ok) { showToast('API key grabbed and saved.', 3500); console.log('API key grabbed:', result.apikey); await actionUpdateHostsAndScan(); } else { showToast('API key found but failed to save. See console.', 4000); } } else { console.warn('No apikey found:', result); if (result.reason === 'not_found_in_response' || result.reason === 'fetch_failed') { showToast('No API key found. You may need to log in to alldebrid.com and reload, or set a key manually via the menu.', 7000); } else { showToast('No API key found. See console for details.', 5000); } } } async function actionSetManual() { try { const current = await readApiKey(); const promptText = current ? `Current key: ${current}\n\nEnter new API key (or cancel):` : 'Enter your Alldebrid API key:'; const val = prompt(promptText); if (val === null) { console.log('Manual API key entry canceled.'); showToast('Manual entry canceled.', 1800); return; } const key = String(val).trim(); if (!key || key.length < 8) { showToast('Entered value looks too short to be a valid API key.', 4000); console.warn('Manual entry too short:', key); return; } const ok = await saveApiKey(key); if (ok) { showToast('API key saved (manual). Fetching hosts now...', 3000); console.log('Manual API key saved.'); await actionUpdateHostsAndScan(); } else { showToast('Failed to save API key. See console.', 3000); } } catch (e) { console.error('actionSetManual error:', e); showToast('Error during manual set. See console.', 3500); } } // ---------- Register menu and expose functions ---------- function registerMenu(name, fn) { try { if (typeof GM_registerMenuCommand === 'function') { GM_registerMenuCommand(name, fn); } else if (typeof GM !== 'undefined' && GM.registerMenuCommand) { GM.registerMenuCommand(name, fn); } else { console.log('Menu registration not supported. Call functions from console: actionGrabApiKey(), actionSetApiKeyManual(), actionUpdateHostsAndScan(), scanPageUsingStoredRegexps().'); } } catch (e) { console.warn('registerMenu error:', e); } } window.actionGrabApiKey = actionGrabApiKey; window.actionSetApiKeyManual = actionSetManual; window.actionUpdateHostsAndScan = actionUpdateHostsAndScan; window.scanPageUsingStoredRegexps = scanPageUsingStoredRegexps; registerMenu('Alldebrid: Grab APIKey', actionGrabApiKey); registerMenu('Alldebrid: Set APIKey (manual)', actionSetManual); registerMenu('Alldebrid: Update hosts regexps & scan page', actionUpdateHostsAndScan); registerMenu('Alldebrid: Scan page with stored regexps', scanPageUsingStoredRegexps); console.log('Alldebrid APIKey + Hosts Regexps + Send Buttons (v1.7) loaded.'); // ---------- Auto-grab on load if not already stored; then auto-fetch hosts ---------- (async function initAuto() { try { const existing = await readApiKey(); if (existing) { console.log('Alldebrid API key already stored. Updating hosts now.'); await actionUpdateHostsAndScan(); return; } console.log('No stored API key — attempting auto-grab now.'); await actionGrabApiKey(); const after = await readApiKey(); if (!after) { showToast('No API key found automatically. If you are not logged in, please log in to https://alldebrid.com/ and reload this page (or set a key manually via the menu).', 8000); console.warn('API key not found after auto-grab — user may need to log in to alldebrid.com'); } } catch (e) { console.error('initAuto error:', e); } })(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址