507 lines
21 KiB
JavaScript
507 lines
21 KiB
JavaScript
// ==UserScript==
|
||
// @name BIN文件上传提取Token工具
|
||
// @namespace http://tampermonkey.net/
|
||
// @version 0.5
|
||
// @description 上传BIN文件提取RoleToken并生成WSS链接
|
||
// @author 豆包编程助手
|
||
// @match *://*/*
|
||
// @grant GM_xmlhttpRequest
|
||
// @grant GM_setClipboard
|
||
// ==/UserScript==
|
||
|
||
(function() {
|
||
'use strict';
|
||
|
||
// 界面状态变量
|
||
let toolContainer = null;
|
||
let isToolVisible = false;
|
||
|
||
// 创建工具界面
|
||
function createToolUI() {
|
||
// 检查是否已存在界面
|
||
if (document.getElementById('bin-token-extractor')) {
|
||
toolContainer = document.getElementById('bin-token-extractor');
|
||
return toolContainer;
|
||
}
|
||
|
||
// 创建容器(固定宽度380px)
|
||
const container = document.createElement('div');
|
||
container.id = 'bin-token-extractor';
|
||
container.style.cssText = `
|
||
position: fixed;
|
||
top: 50%;
|
||
right: 20px;
|
||
transform: translateY(-50%);
|
||
background: linear-gradient(180deg, #ffffff 0%, #f9fbfd 100%);
|
||
border-radius: 16px;
|
||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12);
|
||
width: 380px;
|
||
max-height: 80vh;
|
||
overflow-y: auto;
|
||
padding: 25px;
|
||
z-index: 99999;
|
||
display: none; /* 默认隐藏 */
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
`;
|
||
|
||
// 优化后的HTML结构
|
||
container.innerHTML = `
|
||
<div style="text-align: center; margin-bottom: 15px;">
|
||
<h2 style="margin: 0 0 10px 0; color: #2c3e50; font-size: 18px; font-weight: 600;">BIN文件Token提取工具</h2>
|
||
<p style="margin: 0; color: #7f8c8d; font-size: 13px;">上传文件,提取RoleToken并生成WSS链接</p>
|
||
</div>
|
||
|
||
<div id="uploadArea" style="border: 2px dashed #d1d9e6; border-radius: 12px; padding: 30px 15px; margin-bottom: 20px; cursor: pointer; transition: all 0.3s; background-color: #f8fafc; position: relative;">
|
||
<div style="font-size: 48px; color: #3b82f6; margin-bottom: 12px;">📂</div>
|
||
<p style="font-size: 16px; color: #334155; margin-bottom: 5px; font-weight: 500;">点击或拖放BIN文件</p>
|
||
<p style="font-size: 12px; color: #94a3b8; margin: 0;">仅支持 .bin 格式文件</p>
|
||
<input type="file" id="fileInput" style="position: absolute; width: 100%; height: 100%; top: 0; left: 0; opacity: 0; cursor: pointer;" accept=".bin">
|
||
</div>
|
||
|
||
<div id="fileInfo" style="margin-top: 10px; padding: 10px; background-color: #eff6ff; border-radius: 8px; font-size: 13px; color: #3b82f6; display: none; border-left: 3px solid #3b82f6;"></div>
|
||
|
||
<div id="progressContainer" style="margin: 15px 0; display: none;">
|
||
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
|
||
<span style="font-size: 12px; color: #64748b; font-weight: 500;">上传进度</span>
|
||
<span id="progressText" style="font-size: 12px; color: #64748b;">0%</span>
|
||
</div>
|
||
<div style="height: 6px; background-color: #e2e8f0; border-radius: 3px; overflow: hidden;">
|
||
<div id="progressBar" style="height: 100%; background: linear-gradient(90deg, #3b82f6 0%, #60a5fa 100%); width: 0%; transition: width 0.4s ease;"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="statusMessage" style="padding: 10px; border-radius: 8px; margin: 10px 0; text-align: center; font-weight: 500; display: none; font-size: 13px; transition: all 0.3s;"></div>
|
||
|
||
<button id="uploadBtn" style="background: linear-gradient(90deg, #3b82f6 0%, #60a5fa 100%); color: white; border: none; padding: 12px 15px; width: 100%; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.3s; box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); margin-top: 5px; opacity: 0.7; transform: translateY(2px);" disabled>上传并提取Token</button>
|
||
|
||
<div id="resultContainer" style="margin-top: 20px; padding: 18px; background-color: #f8fafc; border-radius: 12px; text-align: left; display: none; border: 1px solid #e2e8f0; transform: translateY(10px); opacity: 0; transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);">
|
||
<h3 style="color: #1e40af; margin: 0 0 12px 0; font-size: 15px; display: flex; align-items: center;">
|
||
<span style="margin-right: 8px;">🔗</span>完整WebSocket链接
|
||
</h3>
|
||
<div id="wssLinkDisplay" style="word-break: break-all; font-family: monospace; font-size: 12px; color: #1e293b; line-height: 1.6; padding: 12px; background-color: white; border-radius: 8px; border: 1px solid #e2e8f0; margin-top: 5px; max-height: 120px; overflow-y: auto;"></div>
|
||
<button id="copyWssLinkBtn" style="background: #10b981; color: white; border: none; padding: 7px 14px; border-radius: 6px; cursor: pointer; font-size: 13px; transition: all 0.3s; margin-top: 10px; box-shadow: 0 2px 6px rgba(16, 185, 129, 0.25);">
|
||
<span style="margin-right: 5px;">📋</span>复制WSS链接
|
||
</button>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(container);
|
||
toolContainer = container;
|
||
return container;
|
||
}
|
||
|
||
// 切换工具显示/隐藏状态
|
||
function toggleTool() {
|
||
if (!toolContainer) {
|
||
createToolUI();
|
||
}
|
||
|
||
isToolVisible = !isToolVisible;
|
||
|
||
if (isToolVisible) {
|
||
// 显示工具并添加淡入动画
|
||
toolContainer.style.display = 'block';
|
||
setTimeout(() => {
|
||
toolContainer.style.opacity = '1';
|
||
toolContainer.style.transform = 'translateY(-50%) scale(1)';
|
||
}, 10);
|
||
// 初始化工具功能
|
||
initToolFunctions();
|
||
} else {
|
||
// 添加淡出动画后隐藏
|
||
toolContainer.style.opacity = '0';
|
||
toolContainer.style.transform = 'translateY(-50%) scale(0.95)';
|
||
setTimeout(() => {
|
||
toolContainer.style.display = 'none';
|
||
}, 300);
|
||
}
|
||
}
|
||
|
||
// 初始化工具功能
|
||
function initToolFunctions() {
|
||
if (!toolContainer) return;
|
||
|
||
// 获取DOM元素
|
||
const uploadArea = toolContainer.querySelector('#uploadArea');
|
||
const fileInput = toolContainer.querySelector('#fileInput');
|
||
const fileInfo = toolContainer.querySelector('#fileInfo');
|
||
const uploadBtn = toolContainer.querySelector('#uploadBtn');
|
||
const progressContainer = toolContainer.querySelector('#progressContainer');
|
||
const progressBar = toolContainer.querySelector('#progressBar');
|
||
const progressText = toolContainer.querySelector('#progressText');
|
||
const statusMessage = toolContainer.querySelector('#statusMessage');
|
||
const resultContainer = toolContainer.querySelector('#resultContainer');
|
||
const wssLinkDisplay = toolContainer.querySelector('#wssLinkDisplay');
|
||
const copyWssLinkBtn = toolContainer.querySelector('#copyWssLinkBtn');
|
||
|
||
let selectedFile = null;
|
||
let extractedToken = null;
|
||
|
||
// 点击上传区域触发文件选择
|
||
uploadArea.addEventListener('click', function(e) {
|
||
if (e.target !== fileInput) {
|
||
fileInput.click();
|
||
}
|
||
});
|
||
|
||
// 拖放功能
|
||
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
||
uploadArea.addEventListener(eventName, preventDefaults, false);
|
||
});
|
||
|
||
function preventDefaults(e) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
}
|
||
|
||
['dragenter', 'dragover'].forEach(eventName => {
|
||
uploadArea.addEventListener(eventName, highlight, false);
|
||
});
|
||
|
||
['dragleave', 'drop'].forEach(eventName => {
|
||
uploadArea.addEventListener(eventName, unhighlight, false);
|
||
});
|
||
|
||
function highlight() {
|
||
uploadArea.style.borderColor = '#3b82f6';
|
||
uploadArea.style.backgroundColor = 'rgba(59, 130, 246, 0.05)';
|
||
uploadArea.style.transform = 'scale(1.02)';
|
||
}
|
||
|
||
function unhighlight() {
|
||
uploadArea.style.borderColor = '#d1d9e6';
|
||
uploadArea.style.backgroundColor = '#f8fafc';
|
||
uploadArea.style.transform = 'scale(1)';
|
||
}
|
||
|
||
// 文件拖放处理
|
||
uploadArea.addEventListener('drop', handleDrop, false);
|
||
|
||
function handleDrop(e) {
|
||
const dt = e.dataTransfer;
|
||
const files = dt.files;
|
||
|
||
if (files.length) {
|
||
handleFiles(files);
|
||
}
|
||
}
|
||
|
||
// 文件选择处理
|
||
fileInput.addEventListener('change', function() {
|
||
if (this.files.length) {
|
||
handleFiles(this.files);
|
||
}
|
||
});
|
||
|
||
function handleFiles(files) {
|
||
const file = files[0];
|
||
|
||
// 检查文件类型
|
||
if (!file.name.toLowerCase().endsWith('.bin')) {
|
||
showStatus('请选择.bin格式的文件', 'error');
|
||
return;
|
||
}
|
||
|
||
selectedFile = file;
|
||
updateFileInfo(file);
|
||
uploadBtn.disabled = false;
|
||
uploadBtn.style.opacity = '1';
|
||
uploadBtn.style.transform = 'translateY(0)';
|
||
}
|
||
|
||
function updateFileInfo(file) {
|
||
fileInfo.textContent = `已选择: ${file.name} (${formatFileSize(file.size)})`;
|
||
fileInfo.style.display = 'block';
|
||
// 添加淡入动画
|
||
fileInfo.style.opacity = '0';
|
||
setTimeout(() => {
|
||
fileInfo.style.opacity = '1';
|
||
}, 10);
|
||
}
|
||
|
||
function formatFileSize(bytes) {
|
||
if (bytes === 0) return '0 Bytes';
|
||
const k = 1024;
|
||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||
}
|
||
|
||
// 上传按钮点击事件
|
||
uploadBtn.addEventListener('click', function() {
|
||
if (!selectedFile) {
|
||
showStatus('请先选择文件', 'error');
|
||
return;
|
||
}
|
||
|
||
// 禁用上传按钮,防止重复点击
|
||
uploadBtn.disabled = true;
|
||
uploadBtn.style.opacity = '0.7';
|
||
uploadBtn.style.transform = 'translateY(2px)';
|
||
|
||
// 隐藏之前的结果
|
||
resultContainer.style.display = 'none';
|
||
|
||
// 显示进度条
|
||
progressContainer.style.display = 'block';
|
||
progressBar.style.width = '0%';
|
||
progressText.textContent = '0%';
|
||
|
||
// 直接上传文件
|
||
uploadFile(selectedFile);
|
||
});
|
||
|
||
function uploadFile(file) {
|
||
showStatus('正在上传文件...', '');
|
||
|
||
// 读取文件内容
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
const arrayBuffer = e.target.result;
|
||
|
||
// 配置请求
|
||
GM_xmlhttpRequest({
|
||
method: 'POST',
|
||
url: 'https://xxz-xyzw.hortorgames.com/login/authuser?_seq=1',
|
||
data: arrayBuffer,
|
||
headers: {
|
||
'Content-Type': 'application/octet-stream'
|
||
},
|
||
responseType: 'arraybuffer',
|
||
onprogress: function(e) {
|
||
if (e.lengthComputable) {
|
||
const percentComplete = (e.loaded / e.total) * 100;
|
||
progressBar.style.width = percentComplete + '%';
|
||
progressText.textContent = Math.round(percentComplete) + '%';
|
||
}
|
||
},
|
||
onload: function(response) {
|
||
if (response.status >= 200 && response.status < 300) {
|
||
try {
|
||
// 处理二进制响应
|
||
const arrayBuffer = response.response;
|
||
if (arrayBuffer) {
|
||
// 提取RoleToken
|
||
extractRoleToken(arrayBuffer);
|
||
showStatus('Token提取成功!', 'success');
|
||
} else {
|
||
showStatus('上传成功,但响应为空', 'error');
|
||
}
|
||
} catch (e) {
|
||
showStatus('处理响应时出错: ' + e.message, 'error');
|
||
}
|
||
} else {
|
||
showStatus('上传失败: ' + response.statusText, 'error');
|
||
}
|
||
|
||
// 重新启用上传按钮
|
||
uploadBtn.disabled = false;
|
||
uploadBtn.style.opacity = '1';
|
||
uploadBtn.style.transform = 'translateY(0)';
|
||
},
|
||
onerror: function() {
|
||
showStatus('上传过程中发生错误', 'error');
|
||
uploadBtn.disabled = false;
|
||
uploadBtn.style.opacity = '1';
|
||
uploadBtn.style.transform = 'translateY(0)';
|
||
}
|
||
});
|
||
};
|
||
|
||
reader.onerror = function() {
|
||
showStatus('读取文件失败', 'error');
|
||
uploadBtn.disabled = false;
|
||
uploadBtn.style.opacity = '1';
|
||
uploadBtn.style.transform = 'translateY(0)';
|
||
};
|
||
|
||
reader.readAsArrayBuffer(file);
|
||
}
|
||
|
||
function extractRoleToken(arrayBuffer) {
|
||
try {
|
||
// 将ArrayBuffer转换为Uint8Array以便处理
|
||
const bytes = new Uint8Array(arrayBuffer);
|
||
|
||
// 转换为ASCII字符串以便搜索
|
||
let asciiString = '';
|
||
for (let i = 0; i < bytes.length; i++) {
|
||
// 只转换可打印的ASCII字符(32-126)
|
||
if (bytes[i] >= 32 && bytes[i] <= 126) {
|
||
asciiString += String.fromCharCode(bytes[i]);
|
||
} else {
|
||
asciiString += '.'; // 用点号表示不可打印字符
|
||
}
|
||
}
|
||
|
||
// 搜索Token的位置 - 查找 "Token" 字符串
|
||
const tokenIndex = asciiString.indexOf('Token');
|
||
|
||
if (tokenIndex !== -1) {
|
||
// 找到Token标记,提取Token值
|
||
let tokenStart = tokenIndex + 5; // "Token"长度为5
|
||
|
||
// 跳过可能的非Base64字符,直到找到Base64字符
|
||
while (tokenStart < asciiString.length) {
|
||
const char = asciiString[tokenStart];
|
||
if (isBase64Char(char)) {
|
||
break;
|
||
}
|
||
tokenStart++;
|
||
}
|
||
|
||
// 提取Base64 Token
|
||
let tokenEnd = tokenStart;
|
||
while (tokenEnd < asciiString.length && isBase64Char(asciiString[tokenEnd])) {
|
||
tokenEnd++;
|
||
}
|
||
|
||
const tokenValue = asciiString.substring(tokenStart, tokenEnd);
|
||
|
||
if (tokenValue.length > 0) {
|
||
extractedToken = tokenValue;
|
||
resultContainer.style.display = 'block';
|
||
|
||
// 触发动画
|
||
setTimeout(() => {
|
||
resultContainer.style.transform = 'translateY(0)';
|
||
resultContainer.style.opacity = '1';
|
||
}, 10);
|
||
|
||
// 生成并显示完整的WSS链接
|
||
generateAndDisplayWssLink(extractedToken);
|
||
|
||
// 平滑滚动到结果区域
|
||
resultContainer.scrollIntoView({ behavior: 'smooth' });
|
||
} else {
|
||
showStatus('找到Token标记但未找到Token值', 'error');
|
||
}
|
||
} else {
|
||
showStatus('在响应中未找到Token标记', 'error');
|
||
}
|
||
} catch (error) {
|
||
showStatus('提取Token时发生错误: ' + error.message, 'error');
|
||
}
|
||
}
|
||
|
||
function isBase64Char(char) {
|
||
// Base64字符集: A-Z, a-z, 0-9, +, /, =
|
||
return /[A-Za-z0-9+/=]/.test(char);
|
||
}
|
||
|
||
function showStatus(message, type) {
|
||
statusMessage.textContent = message;
|
||
statusMessage.className = '';
|
||
statusMessage.style.backgroundColor = '';
|
||
statusMessage.style.color = '';
|
||
statusMessage.style.boxShadow = 'none';
|
||
|
||
if (type === 'success') {
|
||
statusMessage.style.backgroundColor = 'rgba(16, 185, 129, 0.1)';
|
||
statusMessage.style.color = '#059669';
|
||
statusMessage.style.borderLeft = '3px solid #10b981';
|
||
} else if (type === 'error') {
|
||
statusMessage.style.backgroundColor = 'rgba(239, 68, 68, 0.1)';
|
||
statusMessage.style.color = '#dc2626';
|
||
statusMessage.style.borderLeft = '3px solid #ef4444';
|
||
} else {
|
||
statusMessage.style.backgroundColor = 'rgba(59, 130, 246, 0.1)';
|
||
statusMessage.style.color = '#2563eb';
|
||
statusMessage.style.borderLeft = '3px solid #3b82f6';
|
||
}
|
||
|
||
statusMessage.style.display = 'block';
|
||
statusMessage.style.opacity = '0';
|
||
statusMessage.style.transform = 'translateY(10px)';
|
||
|
||
// 触发淡入动画
|
||
setTimeout(() => {
|
||
statusMessage.style.opacity = '1';
|
||
statusMessage.style.transform = 'translateY(0)';
|
||
}, 10);
|
||
|
||
// 3秒后自动隐藏非错误状态
|
||
if (type !== 'error') {
|
||
setTimeout(() => {
|
||
statusMessage.style.opacity = '0';
|
||
statusMessage.style.transform = 'translateY(10px)';
|
||
setTimeout(() => {
|
||
statusMessage.style.display = 'none';
|
||
}, 300);
|
||
}, 3000);
|
||
}
|
||
}
|
||
|
||
// 复制完整WSS链接按钮事件
|
||
copyWssLinkBtn.addEventListener('click', function() {
|
||
const wssLink = wssLinkDisplay.textContent;
|
||
|
||
if (!wssLink) return;
|
||
|
||
GM_setClipboard(wssLink);
|
||
// 按钮点击反馈
|
||
this.style.backgroundColor = '#059669';
|
||
setTimeout(() => {
|
||
this.style.backgroundColor = '#10b981';
|
||
}, 200);
|
||
showStatus('WSS链接已复制', 'success');
|
||
});
|
||
|
||
// 生成并显示完整的WSS链接
|
||
function generateAndDisplayWssLink(token) {
|
||
// 生成随机的会话ID和连接ID
|
||
const currentTime = Date.now();
|
||
const sessId = currentTime * 100 + Math.floor(Math.random() * 100);
|
||
const connId = currentTime + Math.floor(Math.random() * 10);
|
||
|
||
// 构建WebSocket参数
|
||
const wssParams = `{"roleToken":"${token}","sessId":${sessId},"connId":${connId},"isRestore":0}`;
|
||
|
||
// 显示完整的WSS链接参数
|
||
wssLinkDisplay.textContent = wssParams;
|
||
}
|
||
}
|
||
|
||
// 创建切换按钮(兼具显示和关闭功能)
|
||
function createToggleButton() {
|
||
// 创建按钮
|
||
const toggleBtn = document.createElement('button');
|
||
toggleBtn.innerHTML = `<span style="margin-right: 6px;">🔑</span>BIN Token提取`;
|
||
toggleBtn.style.cssText = `
|
||
position: fixed;
|
||
bottom: 20px;
|
||
right: 20px;
|
||
background: linear-gradient(90deg, #3b82f6 0%, #60a5fa 100%);
|
||
color: white;
|
||
border: none;
|
||
padding: 10px 18px;
|
||
border-radius: 50px;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3);
|
||
white-space: nowrap;
|
||
z-index: 99998;
|
||
transition: all 0.3s;
|
||
`;
|
||
|
||
// 添加悬停效果
|
||
toggleBtn.addEventListener('mouseenter', () => {
|
||
toggleBtn.style.transform = 'translateY(-2px)';
|
||
toggleBtn.style.boxShadow = '0 6px 20px rgba(59, 130, 246, 0.4)';
|
||
});
|
||
|
||
toggleBtn.addEventListener('mouseleave', () => {
|
||
toggleBtn.style.transform = 'translateY(0)';
|
||
toggleBtn.style.boxShadow = '0 4px 15px rgba(59, 130, 246, 0.3)';
|
||
});
|
||
|
||
// 添加点击事件 - 切换显示/隐藏
|
||
toggleBtn.addEventListener('click', toggleTool);
|
||
|
||
document.body.appendChild(toggleBtn);
|
||
}
|
||
|
||
// 页面加载完成后创建切换按钮
|
||
window.addEventListener('load', createToggleButton);
|
||
})(); |