// ==UserScript== // @name 京东CK获取并提交青龙_优化版 // @namespace jdck // @version 1.1.0 // @description 在前台显示美化面板,一键推送CK并提交到青龙,支持一键登出,带日志记录;支持单独复制CK // @author 超级帅气 + ChatGPT // @match https://home.m.jd.com/myJd/newhome.action* // @match https://m.jd.com/* // @match https://my.m.jd.com/* // @grant GM_cookie // @grant GM_addValueChangeListener // @grant GM_setValue // @grant GM_getValue // @grant GM_notification // @grant GM_xmlhttpRequest // @grant GM_setClipboard // @grant GM_log // @connect jd.com // ==/UserScript== /* ==UserConfig== 青龙配置: url: title: 青龙面板地址 description: '例如: http://127.0.0.1:5700' clientId: title: clientId description: 系统设置->应用设置->client id clientSecret: title: clientSecret description: 系统设置->应用设置->client secret password: true CK推送策略: jdMode: title: JD_COOKIE策略 description: '当青龙已有 JD_COOKIE 时:replace=删除原CK仅保留新CK;append=在原变量末尾按行附加新CK(推荐多账号)' type: select default: append values: [append, replace] ==/UserConfig== */ (function () { // ----------- 前台UI部分 ----------- const panel = document.createElement('div'); panel.id = "jdck-panel"; panel.innerHTML = `

京东CK工具

`; document.body.appendChild(panel); // 面板开关按钮 const toggleBtn = document.createElement('div'); toggleBtn.id = "jdck-toggle"; toggleBtn.textContent = "📌"; document.body.appendChild(toggleBtn); const style = document.createElement('style'); style.innerHTML = ` #jdck-panel { position: fixed; top: 180px; left: 10px; background: #fff; border: 1px solid #ddd; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); padding: 12px; z-index: 99999; font-family: "Segoe UI", Arial, sans-serif; text-align: center; } #jdck-panel button { display: block; width: 140px; margin: 6px auto; padding: 8px 0; font-size: 13px; border: none; border-radius: 8px; cursor: pointer; transition: 0.2s; } #push-ck { background: #4CAF50; color: #fff; } #push-ck:hover { background: #45a049; } #copy-ck { background: #2196F3; color: #fff; } #copy-ck:hover { background: #0b7dda; } #logout { background: #f44336; color: #fff; } #logout:hover { background: #da190b; } #jdck-toggle { position: fixed; top: 140px; left: 10px; background: #fff; border: 1px solid #ddd; border-radius: 50%; width: 32px; height: 32px; line-height: 32px; text-align: center; cursor: pointer; box-shadow: 0 2px 6px rgba(0,0,0,0.15); z-index: 100000; } `; document.head.appendChild(style); toggleBtn.addEventListener("click", () => { panel.style.display = (panel.style.display === "none" ? "block" : "none"); }); // 显示当前策略 function showModeTip() { const mode = (GM_getValue('策略.jdMode') || 'append').toLowerCase(); const el = document.querySelector('#mode-tip'); el.textContent = `当前 JD_COOKIE 策略:\n${mode === 'replace' ? '替换(删除原CK,仅保留新CK)' : '附加(多账号每行一个)'}`; } showModeTip(); // 前台按钮绑定 panel.addEventListener('click', e => { switch (e.target.id) { case 'push-ck': GM_log("📢 用户点击了『推送CK』按钮"); GM_setValue('push-ck', null); GM_setValue('push-ck', 'request'); break; case 'copy-ck': GM_log("📢 用户点击了『复制CK』按钮"); GM_setValue('copy-ck', null); GM_setValue('copy-ck', 'request'); break; case 'logout': GM_log("📢 用户点击了『一键登出』按钮"); GM_setValue('logout', 'true'); setTimeout(() => { location.href = 'https://m.jd.com/'; }, 1000); break; } }); // ----------- 后台逻辑部分 ----------- if (!window.GM_getCookieStore) { window.GM_getCookieStore = (_, cb) => cb(0, undefined); } function getCk(tabid) { return new Promise((resolve, reject) => { GM_getCookieStore(tabid, (storeId, err) => { if (err) return reject(err); GM_cookie('list', { domain: '.jd.com', name: "pt_key", storeId }, (item) => { if (!item.length) return reject("未找到 pt_key"); let jdCookie = "pt_key=" + item[0].value; GM_cookie('list', { domain: '.jd.com', name: "pt_pin", storeId }, (item2) => { if (!item2.length) return reject("未找到 pt_pin"); const pinRaw = item2[0].value; // 原始(可能URL编码) const pinShown = decodeURI(pinRaw); // 展示/备注用 jdCookie += ";pt_pin=" + pinRaw + ";"; GM_log("✅ 成功获取CK:" + jdCookie); resolve({ jdCookie, pt_pin: pinShown, pt_pin_raw: pinRaw }); }); }); }); }); } // 基础请求 async function ajax(method, url, token, data) { return new Promise((resolve, reject) => { const headers = {}; if (method.toLowerCase() !== 'get') headers['Content-Type'] = 'application/json'; if (token) headers['Authorization'] = 'Bearer ' + token; GM_xmlhttpRequest({ url, method, data: data ? JSON.stringify(data) : null, headers, responseType: "json", onload: resp => { if (resp.status === 200) { GM_log("🌐 请求成功: " + url); resolve(resp.response ?? JSON.parse(resp.responseText || '{}')); } else { const msg = (resp.response && (resp.response.message || resp.response.errmsg)) || resp.responseText || `HTTP ${resp.status}`; GM_log(`❌ 请求失败: ${url} 状态码: ${resp.status} 响应: ${msg}`); reject(msg); } }, onerror: err => { GM_log("⚠️ 网络错误: " + url + " 错误: " + JSON.stringify(err)); reject(err); } }); }); } async function updateToken() { const url = GM_getValue('青龙配置.url'); const clientId = GM_getValue('青龙配置.clientId'); const clientSecret = GM_getValue('青龙配置.clientSecret'); if (!url || !clientId || !clientSecret) throw "请先在脚本设置里填写『青龙面板地址/ClientId/ClientSecret』"; GM_log("🔑 正在更新青龙 token..."); const resp = await ajax('get', `${url}/open/auth/token?client_id=${encodeURIComponent(clientId)}&client_secret=${encodeURIComponent(clientSecret)}`); GM_setValue('ql.token', resp.data.token); GM_setValue('ql.expiration', resp.data.expiration); GM_log("✅ 获取到青龙 token:" + resp.data.token); return resp.data.token; } function tokenExpired() { const expiration = GM_getValue('ql.expiration'); // 提前 60 秒刷新 return !expiration || (expiration <= Math.floor(Date.now() / 1000) + 60); } // —— JD_COOKIE 处理主流程 —— async function setQlCk(ck, pt_pin_shown, pt_pin_raw) { GM_log("📤 正在提交CK到青龙..."); const url = GM_getValue('青龙配置.url'); if (!url) throw "未配置青龙地址"; const ckName = 'JD_COOKIE'; let token = GM_getValue('ql.token'); if (!token || tokenExpired()) token = await updateToken(); const mode = (GM_getValue('策略.jdMode') || 'append').toLowerCase(); // append | replace try { // 1) 查询已有 JD_COOKIE const envs = await ajax('get', `${url}/open/envs?searchValue=${encodeURIComponent(ckName)}`, token); const list = (envs && envs.data) ? envs.data.filter(e => e.name === ckName) : []; // 工具:启用变量 const enableIds = async (ids) => { if (ids && ids.length) { await ajax('put', `${url}/open/envs/enable`, token, ids); } }; // 工具:删除一组变量 const deleteIds = async (ids) => { if (ids && ids.length) { await ajax('delete', `${url}/open/envs`, token, ids); } }; if (list.length === 0) { // 2) 不存在 -> 新建 GM_log("➕ 未找到 JD_COOKIE,创建新变量"); const created = await ajax('post', `${url}/open/envs`, token, [{ name: ckName, value: ck, remarks: `${pt_pin_shown} by ScriptCat` }]); const newId = created?.data?.[0]?.id; await enableIds(newId ? [newId] : []); GM_log("✅ CK 提交青龙成功(新建)"); return; } if (mode === 'replace') { // 3a) 替换:删除所有 JD_COOKIE,再新建 const ids = list.map(e => e.id).filter(Boolean); GM_log(`🗑️ 替换模式:删除旧 JD_COOKIE 共 ${ids.length} 个`); await deleteIds(ids); const created = await ajax('post', `${url}/open/envs`, token, [{ name: ckName, value: ck, remarks: `${pt_pin_shown} by ScriptCat` }]); const newId = created?.data?.[0]?.id; await enableIds(newId ? [newId] : []); GM_log("✅ CK 提交青龙成功(替换)"); return; } // 3b) 附加:更新第一条 JD_COOKIE(或按需要可合并多条),以“每行一个 ck”形式 // 选择目标变量:优先选择含有当前 pt_pin 的;否则选择第一条 const hasCurrentPin = (v) => typeof v === 'string' && v.includes(`pt_pin=${pt_pin_raw};`); let target = list.find(e => hasCurrentPin(e.value)) || list[0]; const oldVal = (target.value || '').trim(); const lines = oldVal ? oldVal.split(/\r?\n/).map(s => s.trim()).filter(Boolean) : []; // 先删除同 pin 的旧行,避免重复 const nextLines = lines.filter(line => !line.includes(`pt_pin=${pt_pin_raw};`)); nextLines.push(ck.trim()); // 在末尾追加新 ck const newValue = nextLines.join('\n'); GM_log(`🔄 附加模式:更新变量 id=${target.id}`); await ajax('put', `${url}/open/envs`, token, { id: target.id, // 注意:是 id,不是 _id name: ckName, value: newValue, remarks: target.remarks || `${pt_pin_shown} by ScriptCat` }); await enableIds([target.id]); GM_log("✅ CK 提交青龙成功(附加/更新)"); } catch (e) { GM_log("❌ CK 提交青龙失败:" + (typeof e === 'string' ? e : JSON.stringify(e))); throw e; } } // 监听登出 GM_addValueChangeListener('logout', (_, __, val, ___, tabid) => { if (!val) return; GM_log("🧹 正在删除京东登录Cookie..."); GM_getCookieStore(tabid, (storeId, err) => { if (err) return; const cookiesToDelete = ["pt_key", "pt_pin", "pt_token", "pwdt_id", "s_key", "s_pin", "sfstoken", "sid", "thor1", "wq_skey"]; cookiesToDelete.forEach(name => GM_cookie('delete', { url: 'https://home.m.jd.com/', name, storeId })); GM_log("✅ Cookie 清理完成"); }); }); // 监听推送CK GM_addValueChangeListener('push-ck', (_, __, val, ___, tabid) => { if (!val) return; getCk(tabid).then(({ jdCookie, pt_pin, pt_pin_raw }) => { setQlCk(jdCookie, pt_pin, pt_pin_raw).then(() => { GM_notification({ title: "京东CK工具", text: "CK 已推送到青龙 ✅" }); }).catch(e => { GM_notification({ title: "京东CK工具", text: "推送失败: " + e }); }); }).catch(() => { GM_log("❌ 获取CK失败,用户未登录"); GM_notification({ title: "京东CK工具", text: "未登录,无法获取CK ❌" }); }); }); // 监听复制CK GM_addValueChangeListener('copy-ck', (_, __, val, ___, tabid) => { if (!val) return; getCk(tabid).then(({ jdCookie }) => { GM_setClipboard(jdCookie); GM_log("📋 CK 已复制到剪贴板"); GM_notification({ title: "京东CK工具", text: "CK 已复制到剪贴板 ✅" }); }).catch(() => { GM_log("❌ 获取CK失败,用户未登录"); GM_notification({ title: "京东CK工具", text: "未登录,无法获取CK ❌" }); }); }); })();