// ==UserScript==
// @name 光鸭云盘-凭证提取
// @namespace https://scriptcat.org/
// @version 1.6.0
// @description 在光鸭云盘页面直接显示 Client ID 和 Refresh Token
// @author 叽里咕噜 Q群1049357922
// @match *://www.guangyapan.com/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// 光鸭已从 Supabase Auth 迁移到自建认证,优先匹配 credentials_ 前缀的 key
const NEW_KEY_PREFIX = 'credentials_';
const OLD_KEY = '__GYP_SUPABASE_SESSION__';
let SESSION_KEY = null; // 运行时动态确定
let panel = null;
let lastSessionRaw = null;
function findSessionKey() {
// 1. 优先找 credentials_ 开头的 key
for (let i = 0; i < localStorage.length; i++) {
const k = localStorage.key(i);
if (k && k.startsWith(NEW_KEY_PREFIX)) return k;
}
// 2. 回退旧 key
if (localStorage.getItem(OLD_KEY)) return OLD_KEY;
return null;
}
// ====== 劫持 localStorage.setItem,感知账号切换 ======
const _origSetItem = Storage.prototype.setItem;
Storage.prototype.setItem = function(key, value) {
_origSetItem.call(this, key, value);
if (panel && key === SESSION_KEY && value !== lastSessionRaw) {
lastSessionRaw = value;
extractCredentials();
}
// 新 key 写入时自动切换
if (panel && key.startsWith(NEW_KEY_PREFIX) && key !== SESSION_KEY) {
SESSION_KEY = key;
lastSessionRaw = value;
extractCredentials();
}
};
// ====== JWT payload 解码(不验证签名,仅提取字段) ======
function decodeJWTPayload(token) {
try {
const parts = token.split('.');
if (parts.length !== 3) return null;
const payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
const decoded = atob(payload);
return JSON.parse(decoded);
} catch (e) {
return null;
}
}
// ====== UI ======
function createPanel() {
panel = document.createElement('div');
panel.id = 'gyp-credential-panel';
panel.style.cssText = `
position: fixed; top: 20px; right: 20px; z-index: 99999;
background: #1e1e2e; color: #cdd6f4; border: 1px solid #45475a;
border-radius: 12px; padding: 16px; min-width: 400px; max-width: 460px;
font-family: 'Consolas', 'Monaco', monospace; font-size: 13px;
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
`;
panel.innerHTML = `
光鸭云盘-凭证提取
×
`;
document.body.appendChild(panel);
document.getElementById('gyp-close-btn').onclick = () => panel.remove();
document.getElementById('gyp-refresh-btn').onclick = () => { lastSessionRaw = null; SESSION_KEY = findSessionKey(); extractCredentials(); };
document.getElementById('gyp-copy-btn').onclick = copyCredentials;
SESSION_KEY = findSessionKey();
extractCredentials();
}
function extractCredentials() {
const content = document.getElementById('gyp-content');
const copyBtn = document.getElementById('gyp-copy-btn');
if (!content || !copyBtn) return;
try {
if (!SESSION_KEY) SESSION_KEY = findSessionKey();
if (!SESSION_KEY) {
content.innerHTML = '未找到会话数据,请先登录光鸭云盘。
';
copyBtn.style.display = 'none';
return;
}
let raw = localStorage.getItem(SESSION_KEY);
lastSessionRaw = raw;
if (!raw) {
content.innerHTML = '未找到会话数据,请先登录光鸭云盘。
';
copyBtn.style.display = 'none';
return;
}
let clientId, refreshToken, email;
if (SESSION_KEY === OLD_KEY) {
// 旧版 Supabase 格式
const data = JSON.parse(raw);
clientId = data.user?.id || '未找到';
refreshToken = data.refresh_token || '未找到';
email = data.user?.email || '未知';
} else {
// 新版自建认证格式: { sub, refresh_token, access_token, ... }
const data = JSON.parse(raw);
clientId = data.sub || '未找到';
refreshToken = data.refresh_token || '未找到';
// 尝试从 JWT 解码邮箱
email = '未知';
if (data.access_token) {
const jwt = decodeJWTPayload(data.access_token);
if (jwt && jwt.email) email = jwt.email;
else if (jwt && jwt.sub) email = jwt.sub;
}
}
content.innerHTML = `
当前账号: ${email}
Refresh Token
${refreshToken}
`;
copyBtn.style.display = 'block';
copyBtn.dataset.text = `Client ID: ${clientId}\nRefresh Token: ${refreshToken}`;
} catch (e) {
content.innerHTML = `解析失败: ${e.message}
`;
copyBtn.style.display = 'none';
}
}
function copyCredentials() {
const btn = document.getElementById('gyp-copy-btn');
const text = btn.dataset.text || '';
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(() => {
btn.textContent = '已复制!';
setTimeout(() => btn.textContent = '复制全部', 1500);
});
} else {
const ta = document.createElement('textarea');
ta.value = text;
ta.style.cssText = 'position:fixed;opacity:0;';
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
btn.textContent = '已复制!';
setTimeout(() => btn.textContent = '复制全部', 1500);
}
}
// ====== 跨标签页感知 ======
window.addEventListener('storage', function(e) {
if (!panel) return;
if (e.key && e.key.startsWith(NEW_KEY_PREFIX)) {
SESSION_KEY = e.key;
lastSessionRaw = e.newValue;
extractCredentials();
} else if (e.key === OLD_KEY) {
SESSION_KEY = e.key;
lastSessionRaw = e.newValue;
extractCredentials();
}
});
// ====== 切回标签页检查 ======
window.addEventListener('focus', function() {
if (!panel) return;
if (!SESSION_KEY) SESSION_KEY = findSessionKey();
if (!SESSION_KEY) return;
const cur = localStorage.getItem(SESSION_KEY);
if (cur !== lastSessionRaw) {
lastSessionRaw = cur;
extractCredentials();
}
});
// ====== 定时轮询兜底 ======
setInterval(function() {
if (!panel) return;
if (!SESSION_KEY) SESSION_KEY = findSessionKey();
if (!SESSION_KEY) return;
const cur = localStorage.getItem(SESSION_KEY);
if (cur !== lastSessionRaw) {
lastSessionRaw = cur;
extractCredentials();
}
}, 3000);
// ====== 启动 ======
document.addEventListener('DOMContentLoaded', createPanel);
})();