// ==UserScript==
// @name Captcha Utility Module
// @namespace http://your-namespace.com
// @version 1.0
// @description Complete utility functions for captcha system
// @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js
// ==/UserScript==
const CaptchaUtils = {
// █████ 图像处理工具 █████
Image: {
/**
* 将页面元素转换为Base64图像
* 支持:img、canvas、div背景图
*/
async elementToBase64(element) {
try {
const tagName = element.tagName.toLowerCase();
let base64 = '';
if (tagName === 'img') {
base64 = await this.processImgElement(element);
} else if (tagName === 'canvas') {
base64 = this.processCanvasElement(element);
} else if (tagName === 'div') {
base64 = await this.processDivElement(element);
}
return this.validateImage(base64);
} catch (error) {
console.error('图像转换失败:', error);
return null;
}
},
async processImgElement(img) {
// 处理跨域图像
if (this.isCrossOrigin(img)) {
return await this.fetchCrossOriginImage(img.src);
}
return this.canvasToBase64(img);
},
processCanvasElement(canvas) {
return canvas.toDataURL('image/png').split(',')[1];
},
async processDivElement(div) {
const style = getComputedStyle(div);
const bgImage = style.backgroundImage.match(/url\(["']?(.*?)["']?\)/)[1];
return bgImage.startsWith('data:') ?
bgImage.split(',')[1] :
await this.fetchCrossOriginImage(bgImage);
},
async fetchCrossOriginImage(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
url,
method: 'GET',
responseType: 'blob',
onload: res => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result.split(',')[1]);
reader.readAsDataURL(res.response);
},
onerror: reject
});
});
},
canvasToBase64(img) {
const canvas = document.createElement('canvas');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
canvas.getContext('2d').drawImage(img, 0, 0);
return canvas.toDataURL('image/jpeg', 0.8).split(',')[1];
}
},
// █████ 加密安全工具 █████
Security: {
generateRequestSignature(params, secret) {
const sorted = Object.keys(params).sort();
const signStr = sorted.map(k => `${k}=${params[k]}`).join('&');
return CryptoJS.HmacSHA256(signStr, secret).toString();
},
validateResponse(response, secret) {
const signature = response.sign;
delete response.sign;
const calcSig = this.generateRequestSignature(response, secret);
return signature === calcSig;
},
encryptData(data, key) {
return CryptoJS.AES.encrypt(JSON.stringify(data), key).toString();
},
decryptData(ciphertext, key) {
const bytes = CryptoJS.AES.decrypt(ciphertext, key);
return JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
}
},
// █████ DOM元素工具 █████
Element: {
findAssociatedInput(imageElement) {
// 在3层父级内查找最近的输入框
let parent = imageElement.parentElement;
for (let i = 0; i < 3; i++) {
const inputs = parent.querySelectorAll('input[type="text"], input:not([type])');
if (inputs.length > 0) return inputs[inputs.length - 1];
parent = parent.parentElement;
}
return null;
},
generateSelector(element) {
const path = [];
while (element.parentElement) {
let selector = element.tagName.toLowerCase();
if (element.id) {
selector += `#${element.id}`;
} else {
const siblings = element.parentElement.children;
if (siblings.length > 1) {
const index = Array.from(siblings).indexOf(element);
selector += `:nth-child(${index + 1})`;
}
}
path.unshift(selector);
element = element.parentElement;
}
return path.join(' > ');
},
triggerValidationEvents(input) {
['input', 'change', 'keydown', 'keyup'].forEach(event => {
input.dispatchEvent(new Event(event, { bubbles: true }));
});
}
},
// █████ 数学计算工具 █████
Math: {
calculateSlideDistance(rawValue, calibration) {
// 应用校准公式:基础比例 + 偏移量
const base = rawValue * (calibration.baseRatio || 1);
const calibrated = base + (calibration.offset || 0);
return Math.max(calibrated, calibration.minDistance || 0);
},
getColorComplexity(imageData) {
const colorMap = new Map();
const data = new Uint32Array(imageData.data.buffer);
data.forEach(pixel => {
colorMap.set(pixel, (colorMap.get(pixel) || 0) + 1);
});
// 计算颜色熵值
let entropy = 0;
const total = data.length;
colorMap.forEach(count => {
const p = count / total;
entropy -= p * Math.log2(p);
});
return entropy;
},
calculateTextDensity(element) {
const rect = element.getBoundingClientRect();
const area = rect.width * rect.height;
const textLength = element.textContent.trim().length;
return textLength / (area || 1);
}
},
// █████ 缓存管理工具 █████
Cache: {
getWithCache(key, fetcher, ttl = 300) {
const cached = GM_getValue(key);
if (cached && Date.now() < cached.expire) {
return cached.data;
}
return fetcher().then(data => {
GM_setValue(key, {
data,
expire: Date.now() + ttl * 1000
});
return data;
});
},
memoize(func, resolver = JSON.stringify) {
const cache = new Map();
return (...args) => {
const key = resolver(args);
if (cache.has(key)) return cache.get(key);
const result = func(...args);
cache.set(key, result);
return result;
};
}
},
// █████ 错误处理工具 █████
Error: {
createErrorLog(error, context) {
return {
timestamp: Date.now(),
message: error.message,
stack: error.stack,
context: this.sanitizeContext(context),
systemInfo: this.getSystemInfo()
};
},
sanitizeContext(context) {
const sanitized = { ...context };
// 过滤敏感信息
['image', 'token'].forEach(k => {
if (sanitized[k]) sanitized[k] = '****';
});
return sanitized;
},
getSystemInfo() {
return {
userAgent: navigator.userAgent,
screen: `${screen.width}x${screen.height}`,
timestamp: Date.now()
};
}
}
};
/* 使用示例:
// 图像处理
const base64 = await CaptchaUtils.Image.elementToBase64(imgElement);
// 安全签名
const signature = CaptchaUtils.Security.generateRequestSignature(params, secret);
// 查找输入框
const input = CaptchaUtils.Element.findAssociatedInput(captchaImage);
// 计算滑块距离
const distance = CaptchaUtils.Math.calculateSlideDistance(raw, calibrationConfig);
*/