自动短信登录流程 + 文件下载监控 + 黑名单过滤,支持自定义配置,提升工作效率
目前為
// ==UserScript==
// @name DevBlueChat
// @namespace http://tampermonkey.net/
// @version 1.0.0
// @description 自动短信登录流程 + 文件下载监控 + 黑名单过滤,支持自定义配置,提升工作效率
// @author Eachann
// @match https://codigger.onecloud.cn/*
// @icon https://files.catbox.moe/8l13tx.jpg
// @homepage https://github.com/eachann1024/ILoveWork/blob/master/DevXiaoHui.js
// @supportURL https://github.com/eachann1024/ILoveWork/issues
// @license MIT
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_xmlhttpRequest
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// ==================== 配置管理 ====================
const CONFIG_KEYS = {
DOWNLOAD_ENABLED: 'downloadEnabled',
BLACKLIST_ENABLED: 'blacklistEnabled',
FILE_BLACKLIST: 'fileBlacklist',
PHONE_NUMBER: 'phoneNumber'
};
// 默认配置
const DEFAULT_CONFIG = {
[CONFIG_KEYS.DOWNLOAD_ENABLED]: true,
[CONFIG_KEYS.BLACKLIST_ENABLED]: false,
[CONFIG_KEYS.FILE_BLACKLIST]: '.exe,.bat,.cmd,.scr,.pif',
[CONFIG_KEYS.PHONE_NUMBER]: ''
};
// 获取配置
function getConfig(key) {
return GM_getValue(key, DEFAULT_CONFIG[key]);
}
// 设置配置
function setConfig(key, value) {
GM_setValue(key, value);
}
// 创建设置界面
function createSettingsPanel() {
const panel = document.createElement('div');
panel.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: linear-gradient(135deg, #000000 0%, #1a1a1a 100%);
border: 1px solid #333;
border-radius: 16px;
padding: 0;
z-index: 10000;
box-shadow: 0 20px 60px rgba(0,0,0,0.8);
min-width: 480px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: white;
overflow: hidden;
`;
const downloadEnabled = getConfig(CONFIG_KEYS.DOWNLOAD_ENABLED);
const blacklistEnabled = getConfig(CONFIG_KEYS.BLACKLIST_ENABLED);
panel.innerHTML = `
<div style="padding: 32px;">
<!-- 作者信息区域 -->
<div style="display: flex; align-items: center; margin-bottom: 32px; padding-bottom: 24px; border-bottom: 1px solid #333;">
<img src="https://files.catbox.moe/rnx7yz.jpeg"
style="width: 64px; height: 64px; border-radius: 50%; margin-right: 20px; border: 2px solid #E31937;">
<div>
<h2 style="margin: 0; font-size: 24px; font-weight: 600; color: #E31937;">Eachann</h2>
<p style="margin: 4px 0 0 0; color: #888; font-size: 14px;">早点下班吧 别卷了</p>
</div>
</div>
<h3 style="margin: 0 0 32px 0; font-size: 28px; font-weight: 300; text-align: center;">脚本设置</h3>
<!-- 下载功能开关 -->
<div style="margin: 24px 0; display: flex; justify-content: space-between; align-items: center; padding: 16px 0;">
<div>
<div style="font-size: 18px; font-weight: 500; margin-bottom: 4px;">启用文件下载功能</div>
<div style="color: #888; font-size: 14px;">自动检测并下载聊天中的文件</div>
</div>
<div id="downloadToggle" style="
width: 60px; height: 32px; border-radius: 16px; cursor: pointer; position: relative;
background: ${downloadEnabled ? '#E31937' : '#333'};
transition: all 0.3s ease;
">
<div style="
width: 28px; height: 28px; border-radius: 50%; background: white;
position: absolute; top: 2px; left: ${downloadEnabled ? '30px' : '2px'};
transition: all 0.3s ease; box-shadow: 0 2px 8px rgba(0,0,0,0.3);
"></div>
</div>
</div>
<!-- 黑名单开关 -->
<div style="margin: 24px 0; display: flex; justify-content: space-between; align-items: center; padding: 16px 0;">
<div>
<div style="font-size: 18px; font-weight: 500; margin-bottom: 4px;">启用黑名单过滤</div>
<div style="color: #888; font-size: 14px;">过滤指定类型的文件</div>
</div>
<div id="blacklistToggle" style="
width: 60px; height: 32px; border-radius: 16px; cursor: pointer; position: relative;
background: ${blacklistEnabled ? '#E31937' : '#333'};
transition: all 0.3s ease;
">
<div style="
width: 28px; height: 28px; border-radius: 50%; background: white;
position: absolute; top: 2px; left: ${blacklistEnabled ? '30px' : '2px'};
transition: all 0.3s ease; box-shadow: 0 2px 8px rgba(0,0,0,0.3);
"></div>
</div>
</div>
<!-- 黑名单输入 -->
<div style="margin: 24px 0;">
<label style="font-size: 18px; font-weight: 500; display: block; margin-bottom: 12px;">文件扩展名黑名单</label>
<input type="text" id="fileBlacklist" value="${getConfig(CONFIG_KEYS.FILE_BLACKLIST)}"
style="
width: 100%; padding: 16px; border: 1px solid #333; border-radius: 8px;
background: #1a1a1a; color: white; font-size: 16px; box-sizing: border-box;
transition: border-color 0.3s ease;
"
placeholder="例如:.js,.zip,.exe,.bat">
<small style="color: #888; font-size: 12px; margin-top: 8px; display: block;">用逗号分隔多个扩展名</small>
</div>
<!-- 手机号输入 -->
<div style="margin: 24px 0;">
<label style="font-size: 18px; font-weight: 500; display: block; margin-bottom: 12px;">手机号码</label>
<input type="tel" id="phoneNumber" value="${getConfig(CONFIG_KEYS.PHONE_NUMBER)}"
style="
width: 100%; padding: 16px; border: 1px solid #333; border-radius: 8px;
background: #1a1a1a; color: white; font-size: 16px; box-sizing: border-box;
transition: border-color 0.3s ease;
"
placeholder="请输入手机号码">
<small style="color: #888; font-size: 12px; margin-top: 8px; display: block;">用于自动登录功能</small>
</div>
<!-- 按钮区域 -->
<div style="margin-top: 40px; display: flex; gap: 16px; justify-content: flex-end;">
<button id="cancelSettings" style="
padding: 12px 32px; border: 1px solid #333; border-radius: 8px;
background: transparent; color: #888; font-size: 16px; cursor: pointer;
transition: all 0.3s ease;
">取消</button>
<button id="saveSettings" style="
padding: 12px 32px; border: none; border-radius: 8px;
background: #E31937; color: white; font-size: 16px; cursor: pointer;
transition: all 0.3s ease; font-weight: 500;
">保存设置</button>
</div>
</div>
`;
document.body.appendChild(panel);
// 切换按钮状态
let downloadState = downloadEnabled;
let blacklistState = blacklistEnabled;
// 下载功能切换
const downloadToggle = panel.querySelector('#downloadToggle');
downloadToggle.onclick = () => {
downloadState = !downloadState;
const toggle = downloadToggle.querySelector('div');
downloadToggle.style.background = downloadState ? '#E31937' : '#333';
toggle.style.left = downloadState ? '30px' : '2px';
};
// 黑名单切换
const blacklistToggle = panel.querySelector('#blacklistToggle');
blacklistToggle.onclick = () => {
blacklistState = !blacklistState;
const toggle = blacklistToggle.querySelector('div');
blacklistToggle.style.background = blacklistState ? '#E31937' : '#333';
toggle.style.left = blacklistState ? '30px' : '2px';
};
// 输入框焦点效果
const fileBlacklistInput = panel.querySelector('#fileBlacklist');
fileBlacklistInput.onfocus = () => {
fileBlacklistInput.style.borderColor = '#E31937';
};
fileBlacklistInput.onblur = () => {
fileBlacklistInput.style.borderColor = '#333';
};
// 按钮悬停效果
const saveBtn = panel.querySelector('#saveSettings');
const cancelBtn = panel.querySelector('#cancelSettings');
saveBtn.onmouseenter = () => {
saveBtn.style.background = '#ff1f47';
saveBtn.style.transform = 'translateY(-2px)';
};
saveBtn.onmouseleave = () => {
saveBtn.style.background = '#E31937';
saveBtn.style.transform = 'translateY(0)';
};
cancelBtn.onmouseenter = () => {
cancelBtn.style.borderColor = '#666';
cancelBtn.style.color = '#fff';
};
cancelBtn.onmouseleave = () => {
cancelBtn.style.borderColor = '#333';
cancelBtn.style.color = '#888';
};
// 保存设置
saveBtn.onclick = () => {
setConfig(CONFIG_KEYS.DOWNLOAD_ENABLED, downloadState);
setConfig(CONFIG_KEYS.BLACKLIST_ENABLED, blacklistState);
setConfig(CONFIG_KEYS.FILE_BLACKLIST, fileBlacklistInput.value);
setConfig(CONFIG_KEYS.PHONE_NUMBER, document.querySelector('#phoneNumber').value);
document.body.removeChild(panel);
// 显示保存成功提示并刷新页面
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed; top: 20px; right: 20px; z-index: 10001;
background: #E31937; color: white; padding: 16px 24px;
border-radius: 8px; font-size: 16px; font-weight: 500;
box-shadow: 0 4px 20px rgba(227, 25, 55, 0.3);
animation: slideIn 0.3s ease;
`;
notification.innerHTML = '✅ 设置已保存,页面即将刷新...';
// 添加动画样式
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
`;
document.head.appendChild(style);
document.body.appendChild(notification);
// 2秒后刷新页面
setTimeout(() => {
window.location.reload();
}, 2000);
};
// 取消设置
cancelBtn.onclick = () => {
document.body.removeChild(panel);
};
}
// 注册菜单命令
GM_registerMenuCommand('打开设置', createSettingsPanel);
// 检查当前路由是否为登录页
function isLoginPage() {
return window.location.href.includes('/chat/#/login');
}
// 检查当前路由是否为聊天页
function isChatPage() {
return window.location.href.includes('/chat/#/chat');
}
// ==================== HTTP 请求监听 ====================
// 检查文件是否在黑名单中
function isFileBlacklisted(fileName) {
if (!getConfig(CONFIG_KEYS.BLACKLIST_ENABLED)) return false;
const blacklist = getConfig(CONFIG_KEYS.FILE_BLACKLIST).split(',').map(ext => ext.trim().toLowerCase());
const fileExt = fileName.toLowerCase().substring(fileName.lastIndexOf('.'));
return blacklist.includes(fileExt);
}
// 触发下载按钮点击
function triggerDownload() {
try {
// 多种选择器尝试查找下载图标
const selectors = [
'svg.svg-icon.link use[href="#icon-下载"]',
'svg[aria-hidden="true"].svg-icon.link use[href="#icon-下载"]',
'use[href="#icon-下载"]',
'svg.svg-icon.link',
'.svg-icon.link'
];
let downloadIcons = [];
// 尝试不同的选择器
for (let selector of selectors) {
downloadIcons = document.querySelectorAll(selector);
if (downloadIcons.length > 0) {
// console.log(`✅ 找到 ${downloadIcons.length} 个下载图标,使用选择器: ${selector}`);
break;
}
}
if (downloadIcons.length > 0) {
// 获取最后一个(最新的)下载图标
const lastDownloadIcon = downloadIcons[downloadIcons.length - 1];
// 调试信息:输出元素结构
console.warn('🔍 找到的下载图标元素:', {
tagName: lastDownloadIcon.tagName,
className: lastDownloadIcon.className,
outerHTML: lastDownloadIcon.outerHTML.substring(0, 200),
parentElement: lastDownloadIcon.parentElement ? lastDownloadIcon.parentElement.outerHTML.substring(0, 200) : 'null'
});
// 尝试不同的点击目标
let clickTarget = null;
if (lastDownloadIcon.tagName === 'use') {
// 如果是 use 元素,找到父级 svg
clickTarget = lastDownloadIcon.closest('svg');
} else if (lastDownloadIcon.tagName === 'svg') {
// 如果直接是 svg 元素
clickTarget = lastDownloadIcon;
} else {
// 其他情况,直接使用该元素
clickTarget = lastDownloadIcon;
}
console.warn('🎯 选择的点击目标:', {
tagName: clickTarget ? clickTarget.tagName : 'null',
className: clickTarget ? clickTarget.className : 'null',
outerHTML: clickTarget ? clickTarget.outerHTML.substring(0, 200) : 'null'
});
if (clickTarget) {
// 使用最有效的点击方式
try {
// 方式1: 直接点击
clickTarget.click();
console.log('✅ 自动触发文件下载 (直接点击)');
return true;
} catch (e) {
try {
// 方式2: 简化的事件分发 (已验证有效)
const clickEvent = new Event('click', { bubbles: true });
clickTarget.dispatchEvent(clickEvent);
console.log('✅ 自动触发文件下载 (事件分发)');
return true;
} catch (e2) {
console.error('❌ 下载触发失败:', e2);
}
}
}
} else {
console.warn('⚠️ 未找到下载图标,可能页面还未完全加载');
// 尝试查找所有可能的下载相关元素
const allSvgs = document.querySelectorAll('svg');
console.log(`🔍 页面中共有 ${allSvgs.length} 个 SVG 元素`);
// 输出一些调试信息
allSvgs.forEach((svg, index) => {
if (svg.classList.contains('svg-icon') || svg.classList.contains('link')) {
console.log(`SVG ${index}:`, svg.outerHTML.substring(0, 100));
}
});
}
} catch (error) {
console.error('❌ 触发下载失败:', error);
}
return false;
}
// 监听DOM变化,等待新的下载按钮出现
function watchForNewDownloadButton() {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
// 检查新增的节点中是否有下载按钮
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
// 在新增的节点中查找下载图标
const downloadIcon = node.querySelector && node.querySelector('svg.svg-icon.link use[href="#icon-下载"]');
if (downloadIcon) {
console.log('🎯 检测到新的下载按钮,尝试点击');
setTimeout(() => {
if (triggerDownload()) {
observer.disconnect(); // 成功后停止监听
}
}, 100);
}
}
});
}
});
});
// 监听聊天区域的变化
const chatContainer = document.querySelector('.chat-content') ||
document.querySelector('.message-list') ||
document.querySelector('.chat-messages') ||
document.body;
if (chatContainer) {
observer.observe(chatContainer, {
childList: true,
subtree: true
});
// 5秒后停止监听,避免无限监听
setTimeout(() => {
observer.disconnect();
console.warn('⏰ DOM监听超时,停止监听新下载按钮');
}, 5000);
}
}
// 拦截 XMLHttpRequest
function interceptXHR() {
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method, url, ...args) {
this._url = url;
return originalOpen.apply(this, [method, url, ...args]);
};
XMLHttpRequest.prototype.send = function(data) {
const xhr = this;
// 监听请求载荷(发送的数据)
if (xhr._url && xhr._url.includes('add/record') && data) {
try {
let requestData;
// 尝试解析请求数据
if (typeof data === 'string') {
requestData = JSON.parse(data);
} else if (data instanceof FormData) {
// 如果是 FormData,尝试获取数据
const formDataObj = {};
for (let [key, value] of data.entries()) {
formDataObj[key] = value;
}
requestData = formDataObj;
} else {
requestData = data;
}
console.log('🔍 监听到 add/record 请求载荷:', requestData);
// 检查是否是文件类型
if (requestData && requestData.chatType === 'file') {
console.log('📁 检测到文件消息:', requestData.fileName);
// 检查是否启用下载功能
if (!getConfig(CONFIG_KEYS.DOWNLOAD_ENABLED)) {
console.log('⚠️ 文件下载功能已禁用');
return originalSend.call(this, data);
}
// 检查文件是否在黑名单中
if (isFileBlacklisted(requestData.fileName)) {
console.log('🚫 文件在黑名单中,跳过下载:', requestData.fileName);
return originalSend.call(this, data);
}
// 延迟触发下载,等待DOM更新
setTimeout(() => {
triggerDownload();
}, 1000); // 增加延迟时间,确保消息已经渲染到页面
// 如果第一次尝试失败,使用 MutationObserver 监听DOM变化
setTimeout(() => {
if (!triggerDownload()) {
watchForNewDownloadButton();
}
}, 2000);
}
} catch (error) {
console.error('❌ 解析请求载荷失败:', error);
}
}
return originalSend.call(this, data);
};
}
// 拦截 fetch
function interceptFetch() {
const originalFetch = window.fetch;
window.fetch = function(...args) {
const url = args[0];
const options = args[1] || {};
// 检查是否是 add/record 请求并且有请求体
if (url && url.includes && url.includes('add/record') && options.body) {
try {
let requestData;
// 尝试解析请求数据
if (typeof options.body === 'string') {
requestData = JSON.parse(options.body);
} else {
requestData = options.body;
}
console.log('🔍 监听到 fetch add/record 请求载荷:', requestData);
// 检查是否是文件类型
if (requestData && requestData.chatType === 'file') {
console.log('📁 检测到文件消息:', requestData.fileName);
// 检查是否启用下载功能
if (!getConfig(CONFIG_KEYS.DOWNLOAD_ENABLED)) {
console.log('⚠️ 文件下载功能已禁用');
return originalFetch.apply(this, args);
}
// 检查文件是否在黑名单中
if (isFileBlacklisted(requestData.fileName)) {
console.log('🚫 文件在黑名单中,跳过下载:', requestData.fileName);
return originalFetch.apply(this, args);
}
// 延迟触发下载,等待DOM更新
setTimeout(() => {
triggerDownload();
}, 1000); // 增加延迟时间,确保消息已经渲染到页面
// 如果第一次尝试失败,使用 MutationObserver 监听DOM变化
setTimeout(() => {
if (!triggerDownload()) {
watchForNewDownloadButton();
}
}, 2000);
}
} catch (error) {
console.error('❌ 解析 fetch 请求载荷失败:', error);
}
}
return originalFetch.apply(this, args);
};
}
// ==================== 主要功能初始化 ====================
// 初始化所有功能
function initializeFeatures() {
// 如果在登录页,执行登录逻辑
if (isLoginPage()) {
console.warn('当前是登录页面,执行自动登录');
executeAutoLogin();
}
// 如果在聊天页或登录页,都启动HTTP监听
if (isChatPage() || isLoginPage()) {
console.warn('启动HTTP请求监听');
interceptXHR();
interceptFetch();
}
}
/**
* 等待元素加载完成
* @param {string} selector - CSS选择器
* @param {number} timeout - 超时时间(ms)
* @returns {Promise<Element>}
*/
function waitForElement(selector, timeout = 3000) {
return new Promise((resolve, reject) => {
const element = document.querySelector(selector);
if (element) {
return resolve(element);
}
const observer = new MutationObserver(() => {
const element = document.querySelector(selector);
if (element) {
resolve(element);
observer.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
setTimeout(() => {
observer.disconnect();
console.error(`等待元素超时: ${selector}`);
reject(new Error(`等待元素超时: ${selector}`));
}, timeout);
});
}
/**
* 设置输入框的值并触发事件
* @param {Element} input - 输入框元素
* @param {string} value - 要设置的值
*/
function setInputValue(input, value) {
input.value = value;
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
}
// 主要逻辑 - 短信登录流程
async function executeAutoLogin() {
try {
const phoneNumber = getConfig(CONFIG_KEYS.PHONE_NUMBER);
if (!phoneNumber) {
console.warn('未设置手机号,跳过自动登录');
return;
}
// 1.5. 选择COC域名
try {
const cocDomain = document.querySelector('.domain-item span');
if (cocDomain && cocDomain.textContent.includes('COC')) {
cocDomain.closest('.domain-item').click();
await new Promise(resolve => setTimeout(resolve, 100));
} else {
// 如果没有直接找到,尝试查找所有域名选项
const domainItems = document.querySelectorAll('.domain-item');
for (let item of domainItems) {
if (item.textContent.includes('COC')) {
item.click();
await new Promise(resolve => setTimeout(resolve, 100));
break;
}
}
}
} catch (error) {
console.error('域名选择失败:', error);
}
// 2. 点击短信登录标签
const smsTab = await waitForElement('.ant-tabs-tab');
if (smsTab) {
// 查找包含"短信登录"文本的标签
const tabs = document.querySelectorAll('.ant-tabs-tab');
for (let tab of tabs) {
if (tab.textContent.includes('短信登录')) {
tab.click();
await new Promise(resolve => setTimeout(resolve, 100));
break;
}
}
}
// 3. 等待手机号输入框并输入手机号
const phoneInput = await waitForElement('input[name="phone"]');
setInputValue(phoneInput, phoneNumber);
await new Promise(resolve => setTimeout(resolve, 100));
// 4. 点击发送验证码按钮
const codeBtn = await waitForElement('.code-btn.mini-font-size');
codeBtn.click();
await new Promise(resolve => setTimeout(resolve, 200));
// 5. 等待验证码输入框并自动填写验证码
const codeInput = await waitForElement('input[name="code"]');
setInputValue(codeInput, '123456');
await new Promise(resolve => setTimeout(resolve, 100));
// 6. 点击登录按钮
const loginBtn = await waitForElement('.ant-btn.ant-btn-primary.login-btn');
loginBtn.click();
} catch (error) {
console.error('自动短信登录脚本执行失败:', error);
}
}
// ==================== 脚本启动 ====================
// 页面加载完成后执行
if (document.readyState === 'complete') {
initializeFeatures();
} else {
window.addEventListener('load', () => {
initializeFeatures();
});
}
// 添加延迟执行作为备用方案
setTimeout(() => {
initializeFeatures();
}, 1000);
})();