// ==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 ❌" });
});
});
})();