// ==UserScript==
// @name Cookie 管理工具
// @namespace cookie
// @version 1.7
// @description 在开发环境下方便地种植和管理 Cookie,支持设置过期时间
// @author wangshiwei
// @license MIT
// @match *://*/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_registerMenuCommand
// @grant GM_addStyle
// @grant GM_notification
// @grant GM_log
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// 添加调试信息
console.log('Cookie Tool Script Loaded on:', window.location.href);
// 样式
GM_addStyle(`
.cookie-dialog {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 9999;
width: 400px;
font-family: Arial, sans-serif;
}
.cookie-dialog h3 {
margin-top: 0;
color: #333;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.tab-container {
margin-bottom: 20px;
}
.tab-buttons {
display: flex;
border-bottom: 1px solid #ddd;
margin-bottom: 20px;
}
.tab-button {
flex: 1;
padding: 10px 15px;
background: none;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: bold;
color: #666;
border-bottom: 2px solid transparent;
transition: all 0.3s ease;
}
.tab-button.active {
color: #4285f4;
border-bottom-color: #4285f4;
}
.tab-button:hover {
color: #4285f4;
background-color: #f5f5f5;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
.form-group input, .form-group select {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
.button-group {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
}
.button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
}
.primary-button {
background-color: #4285f4;
color: white;
}
.secondary-button {
background-color: #f1f1f1;
color: #333;
}
.danger-button {
background-color: #dc3545;
color: white;
}
.cookie-list {
max-height: 200px;
overflow-y: auto;
margin-top: 15px;
border: 1px solid #eee;
border-radius: 4px;
padding: 10px;
}
.cookie-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 0;
border-bottom: 1px solid #f1f1f1;
}
.cookie-item:last-child {
border-bottom: none;
}
.cookie-item .cookie-info {
flex: 1;
min-width: 0;
margin-right: 10px;
}
.cookie-item .cookie-key {
font-weight: bold;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 150px;
}
.cookie-item .cookie-value {
color: #666;
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 150px;
}
.cookie-item .cookie-actions {
display: flex;
gap: 5px;
flex-shrink: 0;
}
.cookie-item button {
background: none;
border: none;
cursor: pointer;
font-size: 12px;
padding: 2px 6px;
border-radius: 3px;
transition: background-color 0.2s;
}
.cookie-item button:hover {
background-color: #f0f0f0;
}
.cookie-item button.set-cookie {
color: #4285f4;
}
.cookie-item button.delete-cookie {
color: #dc3545;
}
.domain-info {
font-size: 12px;
color: #666;
margin-bottom: 15px;
}
.cookie-trigger {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 9998;
padding: 8px 16px;
background-color: #4285f4;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
user-select: none;
}
.cookie-trigger:hover {
background-color: #3367d6;
}
.cookie-trigger.dragging {
opacity: 0.8;
}
.expiry-info {
font-size: 12px;
color: #666;
margin-top: 5px;
}
.current-cookies {
max-height: 200px;
overflow-y: auto;
margin-top: 15px;
border: 1px solid #eee;
border-radius: 4px;
padding: 10px;
}
.current-cookie-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 0;
border-bottom: 1px solid #f1f1f1;
}
.current-cookie-item:last-child {
border-bottom: none;
}
.current-cookie-item .cookie-info {
flex: 1;
min-width: 0;
margin-right: 10px;
}
.current-cookie-item .cookie-key {
font-weight: bold;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 150px;
}
.current-cookie-item .cookie-value {
color: #666;
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 150px;
}
.current-cookie-item .cookie-actions {
display: flex;
gap: 5px;
flex-shrink: 0;
}
.current-cookie-item button {
background: none;
border: none;
color: #dc3545;
cursor: pointer;
font-size: 12px;
padding: 2px 6px;
border-radius: 3px;
transition: background-color 0.2s;
}
.current-cookie-item button:hover {
background-color: #f0f0f0;
}
.security-warning {
background-color: #fff3cd;
border: 1px solid #ffeaa7;
color: #856404;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
font-size: 12px;
}
.cookie-details {
font-size: 11px;
color: #666;
margin-top: 5px;
}
`);
// 存储键前缀
const STORAGE_PREFIX = 'cookie_tool_';
const POSITION_KEY = 'cookie_tool_position';
const DEFAULT_EXPIRY_DAYS = 7; // 默认过期时间7天
// 过期时间选项
const EXPIRY_OPTIONS = [
{ value: 1, label: "1天" },
{ value: 7, label: "7天" },
{ value: 30, label: "30天" },
{ value: 365, label: "1年" },
{ value: 3650, label: "10年" },
{ value: -1, label: "会话Cookie(关闭浏览器失效)" }
];
// 获取当前域名
const currentDomain = window.location.hostname;
// 获取当前页面的所有Cookie(包含详细信息)
function getCurrentCookies() {
const cookies = document.cookie.split(';');
const cookieList = [];
cookies.forEach(cookie => {
const trimmed = cookie.trim();
if (trimmed) {
const [key, value] = trimmed.split('=');
if (key && value) {
cookieList.push({
key: key.trim(),
value: value.trim(),
// 尝试获取更多信息
canDelete: true // 默认假设可以删除
});
}
}
});
return cookieList;
}
// 检查Cookie是否可以被JavaScript删除
function checkCookieDeletability(key) {
try {
// 尝试设置一个测试Cookie
document.cookie = `test_${key}=test; path=/`;
const canSet = document.cookie.includes(`test_${key}=test`);
if (canSet) {
// 尝试删除测试Cookie
document.cookie = `test_${key}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
const canDelete = !document.cookie.includes(`test_${key}=`);
return canDelete;
}
return false;
} catch (e) {
console.warn('Cookie deletability check failed:', e);
return false;
}
}
// 增强的Cookie删除函数
function deleteCookie(key) {
console.log('Attempting to delete cookie:', key);
// 获取当前页面的所有Cookie
const currentCookies = getCurrentCookies();
const targetCookie = currentCookies.find(c => c.key === key);
if (!targetCookie) {
console.log('Cookie not found in current page:', key);
return Promise.resolve(false);
}
console.log('Found cookie to delete:', targetCookie);
// 检查Cookie是否可以被删除
const canDelete = checkCookieDeletability(key);
if (!canDelete) {
console.warn('Cookie may be protected by browser security policies:', key);
}
// 使用多种方式删除Cookie
const deletionMethods = [
// 方法1: 标准删除 - 设置过期时间为过去
() => {
const pastDate = new Date(0).toUTCString();
document.cookie = `${key}=; expires=${pastDate}; path=/`;
document.cookie = `${key}=; expires=${pastDate}; path=/; domain=${currentDomain}`;
document.cookie = `${key}=; expires=${pastDate}; path=/; domain=.${currentDomain}`;
},
// 方法2: 设置空值
() => {
document.cookie = `${key}=; path=/`;
document.cookie = `${key}=; path=/; domain=${currentDomain}`;
document.cookie = `${key}=; path=/; domain=.${currentDomain}`;
},
// 方法3: 使用昨天的时间
() => {
const yesterday = new Date();
yesterday.setTime(yesterday.getTime() - 24 * 60 * 60 * 1000);
const expires = yesterday.toUTCString();
document.cookie = `${key}=; expires=${expires}; path=/`;
document.cookie = `${key}=; expires=${expires}; path=/; domain=${currentDomain}`;
document.cookie = `${key}=; expires=${expires}; path=/; domain=.${currentDomain}`;
},
// 方法4: 尝试不同的path组合
() => {
const pastDate = new Date(0).toUTCString();
const paths = ['/', '/path/', window.location.pathname, window.location.pathname + '/'];
const domains = [currentDomain, '.' + currentDomain, ''];
paths.forEach(path => {
domains.forEach(domain => {
const domainStr = domain ? `; domain=${domain}` : '';
document.cookie = `${key}=; expires=${pastDate}; path=${path}${domainStr}`;
});
});
},
// 方法5: 尝试设置SameSite属性
() => {
const pastDate = new Date(0).toUTCString();
document.cookie = `${key}=; expires=${pastDate}; path=/; SameSite=Lax`;
document.cookie = `${key}=; expires=${pastDate}; path=/; SameSite=Strict`;
document.cookie = `${key}=; expires=${pastDate}; path=/; SameSite=None`;
}
];
// 执行所有删除方法
deletionMethods.forEach((method, index) => {
try {
console.log(`Trying deletion method ${index + 1}`);
method();
} catch (e) {
console.warn(`Deletion method ${index + 1} failed:`, e);
}
});
// 等待一段时间后验证删除结果
return new Promise((resolve) => {
setTimeout(() => {
const cookies = document.cookie.split(';');
const stillExists = cookies.some(cookie => {
const trimmed = cookie.trim();
return trimmed.startsWith(key + '=') && trimmed.split('=')[1];
});
if (!stillExists) {
console.log('Cookie successfully deleted:', key);
resolve(true);
} else {
console.warn('Cookie deletion failed or still exists:', key);
console.log('Current cookies after deletion attempt:', document.cookie);
resolve(false);
}
}, 500); // 增加等待时间
});
}
// 截断文本
function truncateText(text, maxLength = 18) {
if (text.length <= maxLength) return text;
return text.substring(0, maxLength) + '...';
}
// 显示对话框
function showCookieDialog() {
console.log('Opening cookie dialog for domain:', currentDomain);
// 如果已经存在对话框,则先移除
const existingDialog = document.querySelector('.cookie-dialog');
if (existingDialog) {
existingDialog.remove();
}
// 创建对话框元素
const dialog = document.createElement('div');
dialog.className = 'cookie-dialog';
// 获取已保存的cookie
const savedCookies = GM_getValue(`${STORAGE_PREFIX}${currentDomain}`, []);
const currentCookies = getCurrentCookies();
// 生成过期时间选项
const expiryOptionsHTML = EXPIRY_OPTIONS.map(option =>
`<option value="${option.value}">${option.label}</option>`
).join('');
dialog.innerHTML = `
<h3>Cookie 管理工具</h3>
<div class="domain-info">当前域名: ${currentDomain}</div>
<div class="security-warning">
<strong>注意:</strong>某些Cookie可能由于浏览器安全策略(如HttpOnly、SameSite等)无法通过JavaScript删除。
如果删除失败,请手动在浏览器开发者工具中删除。
</div>
<div class="tab-container">
<div class="tab-buttons">
<button class="tab-button active" data-tab="add">添加 Cookie</button>
<button class="tab-button" data-tab="delete">删除 Cookie</button>
</div>
<!-- 添加Cookie标签页 -->
<div class="tab-content active" id="add-tab">
<div class="form-group">
<label for="cookie-key">Key:</label>
<input type="text" id="cookie-key" placeholder="输入 cookie 名称">
</div>
<div class="form-group">
<label for="cookie-value">Value:</label>
<input type="text" id="cookie-value" placeholder="输入 cookie 值">
</div>
<div class="form-group">
<label for="cookie-expiry">过期时间:</label>
<select id="cookie-expiry">
${expiryOptionsHTML}
</select>
<div class="expiry-info">默认: ${DEFAULT_EXPIRY_DAYS}天</div>
</div>
${savedCookies.length > 0 ? `
<div class="cookie-list">
<h4>已保存的 Cookie</h4>
${savedCookies.map(cookie => `
<div class="cookie-item">
<div class="cookie-info">
<div class="cookie-key" title="${cookie.key}">${truncateText(cookie.key)}</div>
<div class="cookie-value" title="${cookie.value}">${truncateText(cookie.value)}</div>
</div>
<div class="cookie-actions">
<button class="set-cookie" data-key="${cookie.key}" data-value="${cookie.value}" data-expiry="${cookie.expiry || DEFAULT_EXPIRY_DAYS}">设置</button>
<button class="delete-cookie" data-key="${cookie.key}">删除</button>
</div>
</div>
`).join('')}
</div>
` : ''}
<div class="button-group">
<button class="button secondary-button" id="cancel-button">取消</button>
<button class="button primary-button" id="confirm-button">确定</button>
</div>
</div>
<!-- 删除Cookie标签页 -->
<div class="tab-content" id="delete-tab">
<div class="form-group">
<label for="delete-cookie-key">要删除的 Cookie Key:</label>
<input type="text" id="delete-cookie-key" placeholder="输入要删除的 cookie 名称">
</div>
${currentCookies.length > 0 ? `
<div class="current-cookies">
<h4>当前页面的 Cookie</h4>
${currentCookies.map(cookie => `
<div class="current-cookie-item">
<div class="cookie-info">
<div class="cookie-key" title="${cookie.key}">${truncateText(cookie.key)}</div>
<div class="cookie-value" title="${cookie.value}">${truncateText(cookie.value)}</div>
<div class="cookie-details">${cookie.canDelete ? '可删除' : '可能受保护'}</div>
</div>
<div class="cookie-actions">
<button class="delete-current-cookie" data-key="${cookie.key}">删除</button>
</div>
</div>
`).join('')}
</div>
` : '<p>当前页面没有Cookie</p>'}
<div class="button-group">
<button class="button secondary-button" id="cancel-delete-button">取消</button>
<button class="button danger-button" id="confirm-delete-button">删除</button>
</div>
</div>
</div>
`;
// 设置默认选中7天
dialog.querySelector('#cookie-expiry').value = DEFAULT_EXPIRY_DAYS;
document.body.appendChild(dialog);
// Tab切换功能
dialog.querySelectorAll('.tab-button').forEach(button => {
button.addEventListener('click', () => {
const tabName = button.dataset.tab;
// 移除所有active类
dialog.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active'));
dialog.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
// 添加active类
button.classList.add('active');
dialog.querySelector(`#${tabName}-tab`).classList.add('active');
});
});
// 添加Cookie事件监听器
dialog.querySelector('#confirm-button').addEventListener('click', () => {
const key = dialog.querySelector('#cookie-key').value.trim();
const value = dialog.querySelector('#cookie-value').value.trim();
const expiryDays = parseInt(dialog.querySelector('#cookie-expiry').value);
if (key && value) {
setCookie(key, value, expiryDays);
saveCookie(key, value, expiryDays);
dialog.remove();
console.log('Cookie set:', key, '=', value);
} else {
alert('请输入有效的 Key 和 Value');
}
});
// 删除Cookie事件监听器
dialog.querySelector('#confirm-delete-button').addEventListener('click', async () => {
const key = dialog.querySelector('#delete-cookie-key').value.trim();
if (key) {
const deleted = await deleteCookie(key);
if (deleted) {
// 刷新当前标签页的Cookie列表
setTimeout(() => {
showCookieDialog();
}, 100);
} else {
alert(`Cookie "${key}" 删除失败。可能原因:\n1. Cookie设置了HttpOnly标志\n2. Cookie设置了SameSite限制\n3. 浏览器安全策略限制\n\n请手动在浏览器开发者工具中删除。`);
}
} else {
alert('请输入要删除的 Cookie Key');
}
});
dialog.querySelector('#cancel-button').addEventListener('click', () => {
dialog.remove();
});
dialog.querySelector('#cancel-delete-button').addEventListener('click', () => {
dialog.remove();
});
// 为已保存的cookie添加事件
dialog.querySelectorAll('.set-cookie').forEach(button => {
button.addEventListener('click', () => {
const key = button.dataset.key;
const value = button.dataset.value;
const expiryDays = parseInt(button.dataset.expiry) || DEFAULT_EXPIRY_DAYS;
setCookie(key, value, expiryDays);
console.log('Cookie set from saved:', key, '=', value);
});
});
dialog.querySelectorAll('.delete-cookie').forEach(button => {
button.addEventListener('click', () => {
const key = button.dataset.key;
deleteSavedCookie(key);
// 刷新添加标签页的Cookie列表
setTimeout(() => {
showCookieDialog();
}, 100);
console.log('Cookie deleted from saved:', key);
});
});
// 为当前页面的cookie添加删除事件
dialog.querySelectorAll('.delete-current-cookie').forEach(button => {
button.addEventListener('click', async () => {
const key = button.dataset.key;
const deleted = await deleteCookie(key);
if (deleted) {
// 刷新当前标签页的Cookie列表
setTimeout(() => {
showCookieDialog();
}, 100);
} else {
alert(`Cookie "${key}" 删除失败。可能原因:\n1. Cookie设置了HttpOnly标志\n2. Cookie设置了SameSite限制\n3. 浏览器安全策略限制\n\n请手动在浏览器开发者工具中删除。`);
}
console.log('Current cookie deleted:', key);
});
});
}
// 设置cookie
function setCookie(key, value, days = DEFAULT_EXPIRY_DAYS) {
let expires = "";
if (days > 0) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "expires=" + date.toUTCString() + ";";
}
const cookieString = `${key}=${value}; ${expires} path=/; domain=${currentDomain}`;
document.cookie = cookieString;
console.log('Cookie set:', cookieString);
// 验证cookie是否设置成功
setTimeout(() => {
const cookies = document.cookie.split(';');
const targetCookie = cookies.find(cookie => cookie.trim().startsWith(key + '='));
if (targetCookie) {
console.log('Cookie verified:', targetCookie.trim());
} else {
console.warn('Cookie setting failed or domain restriction applied');
}
}, 100);
}
// 保存cookie到存储
function saveCookie(key, value, expiryDays = DEFAULT_EXPIRY_DAYS) {
const savedCookies = GM_getValue(`${STORAGE_PREFIX}${currentDomain}`, []);
// 检查是否已存在
const existingIndex = savedCookies.findIndex(c => c.key === key);
if (existingIndex >= 0) {
// 更新现有cookie
savedCookies[existingIndex].value = value;
savedCookies[existingIndex].expiry = expiryDays;
} else {
// 添加新cookie
savedCookies.push({ key, value, expiry: expiryDays });
}
GM_setValue(`${STORAGE_PREFIX}${currentDomain}`, savedCookies);
console.log('Cookie saved to storage:', key, '=', value);
}
// 从存储中删除cookie
function deleteSavedCookie(key) {
const savedCookies = GM_getValue(`${STORAGE_PREFIX}${currentDomain}`, []);
const updatedCookies = savedCookies.filter(c => c.key !== key);
GM_setValue(`${STORAGE_PREFIX}${currentDomain}`, updatedCookies);
}
// 创建可拖动的触发按钮
function createDraggableTrigger() {
console.log('Creating draggable trigger button...');
const trigger = document.createElement('button');
trigger.className = 'cookie-trigger';
trigger.textContent = 'Cookie Tool';
// 获取保存的位置或使用默认位置
const savedPosition = GM_getValue(POSITION_KEY, { bottom: '20px', right: '20px' });
Object.assign(trigger.style, savedPosition);
// 点击事件 - 显示对话框
trigger.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
showCookieDialog();
});
// 拖动功能
let isDragging = false;
let offsetX, offsetY;
trigger.addEventListener('mousedown', (e) => {
if (e.button !== 0) return; // 只响应左键
isDragging = true;
trigger.classList.add('dragging');
// 计算鼠标在按钮内的偏移量
const rect = trigger.getBoundingClientRect();
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
e.preventDefault();
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
// 计算新位置
const x = e.clientX - offsetX;
const y = e.clientY - offsetY;
// 确保按钮不会移出视口
const maxX = window.innerWidth - trigger.offsetWidth;
const maxY = window.innerHeight - trigger.offsetHeight;
const clampedX = Math.max(0, Math.min(x, maxX));
const clampedY = Math.max(0, Math.min(y, maxY));
// 应用新位置
trigger.style.left = `${clampedX}px`;
trigger.style.top = `${clampedY}px`;
trigger.style.right = 'auto';
trigger.style.bottom = 'auto';
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
trigger.classList.remove('dragging');
// 保存位置
const position = {
left: trigger.style.left,
top: trigger.style.top,
right: trigger.style.right,
bottom: trigger.style.bottom
};
GM_setValue(POSITION_KEY, position);
}
});
document.body.appendChild(trigger);
console.log('Draggable trigger button created successfully');
}
// 初始化脚本
function initScript() {
console.log('Initializing Cookie Tool...');
console.log('Current domain:', currentDomain);
// 检查是否已经存在按钮
const existingTrigger = document.querySelector('.cookie-trigger');
if (existingTrigger) {
console.log('Trigger button already exists, removing...');
existingTrigger.remove();
}
createDraggableTrigger();
// 注册菜单命令
GM_registerMenuCommand("打开 Cookie 种植工具", showCookieDialog);
console.log('Cookie Tool initialized successfully');
}
// 页面加载完成后创建可拖动按钮
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initScript);
} else {
initScript();
}
// 添加全局错误处理
window.addEventListener('error', (e) => {
console.error('Cookie Tool Error:', e.error);
});
})();