Spaces:
Running
Running
File size: 8,341 Bytes
b88ce1b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
import fs from 'fs/promises';
import AdmZip from 'adm-zip';
import path from 'path';
import { spawn } from 'child_process';
import logger from '../utils/logger.js';
const ACCOUNTS_FILE = path.join(process.cwd(), 'data', 'accounts.json');
// 读取所有账号
export async function loadAccounts() {
try {
const data = await fs.readFile(ACCOUNTS_FILE, 'utf-8');
return JSON.parse(data);
} catch (error) {
if (error.code === 'ENOENT') {
return [];
}
throw error;
}
}
// 保存账号
async function saveAccounts(accounts) {
const dir = path.dirname(ACCOUNTS_FILE);
try {
await fs.access(dir);
} catch {
await fs.mkdir(dir, { recursive: true });
}
await fs.writeFile(ACCOUNTS_FILE, JSON.stringify(accounts, null, 2), 'utf-8');
}
// 删除账号
export async function deleteAccount(index) {
const accounts = await loadAccounts();
if (index < 0 || index >= accounts.length) {
throw new Error('无效的账号索引');
}
accounts.splice(index, 1);
await saveAccounts(accounts);
logger.info(`账号 ${index} 已删除`);
return true;
}
// 启用/禁用账号
export async function toggleAccount(index, enable) {
const accounts = await loadAccounts();
if (index < 0 || index >= accounts.length) {
throw new Error('无效的账号索引');
}
accounts[index].enable = enable;
await saveAccounts(accounts);
logger.info(`账号 ${index} 已${enable ? '启用' : '禁用'}`);
return true;
}
// 触发登录流程
export async function triggerLogin() {
return new Promise((resolve, reject) => {
logger.info('启动登录流程...');
const loginScript = path.join(process.cwd(), 'scripts', 'oauth-server.js');
const child = spawn('node', [loginScript], {
stdio: 'pipe',
shell: true
});
let authUrl = '';
let output = '';
child.stdout.on('data', (data) => {
const text = data.toString();
output += text;
// 提取授权 URL
const urlMatch = text.match(/(https:\/\/accounts\.google\.com\/o\/oauth2\/v2\/auth\?[^\s]+)/);
if (urlMatch) {
authUrl = urlMatch[1];
}
logger.info(text.trim());
});
child.stderr.on('data', (data) => {
logger.error(data.toString().trim());
});
child.on('close', (code) => {
if (code === 0) {
logger.info('登录流程完成');
resolve({ success: true, authUrl, message: '登录成功' });
} else {
reject(new Error('登录流程失败'));
}
});
// 5 秒后返回授权 URL,不等待完成
setTimeout(() => {
if (authUrl) {
resolve({ success: true, authUrl, message: '请在浏览器中完成授权' });
}
}, 5000);
child.on('error', (error) => {
reject(error);
});
});
}
// 获取账号统计信息
export async function getAccountStats() {
const accounts = await loadAccounts();
return {
total: accounts.length,
enabled: accounts.filter(a => a.enable !== false).length,
disabled: accounts.filter(a => a.enable === false).length
};
}
// 从回调链接手动添加 Token
import https from 'https';
const CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com';
const CLIENT_SECRET = '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';
// 获取 Google 账号信息
export async function getAccountName(accessToken) {
return new Promise((resolve, reject) => {
const options = {
hostname: 'www.googleapis.com',
path: '/oauth2/v2/userinfo',
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`
}
};
const req = https.request(options, (res) => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
if (res.statusCode === 200) {
const data = JSON.parse(body);
resolve({
email: data.email,
name: data.name || data.email
});
} else {
resolve({ email: 'Unknown', name: 'Unknown' });
}
});
});
req.on('error', () => resolve({ email: 'Unknown', name: 'Unknown' }));
req.end();
});
}
export async function addTokenFromCallback(callbackUrl) {
// 解析回调链接
const url = new URL(callbackUrl);
const code = url.searchParams.get('code');
const port = url.port || '80';
if (!code) {
throw new Error('回调链接中没有找到授权码 (code)');
}
logger.info(`正在使用授权码换取 Token...`);
// 使用授权码换取 Token
const tokenData = await exchangeCodeForToken(code, port, url.origin);
// 保存账号
const account = {
access_token: tokenData.access_token,
refresh_token: tokenData.refresh_token,
expires_in: tokenData.expires_in,
timestamp: Date.now(),
enable: true
};
const accounts = await loadAccounts();
accounts.push(account);
await saveAccounts(accounts);
logger.info('Token 已成功保存');
return { success: true, message: 'Token 已成功添加' };
}
function exchangeCodeForToken(code, port, origin) {
return new Promise((resolve, reject) => {
const redirectUri = `${origin}/oauth-callback`;
const postData = new URLSearchParams({
code: code,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
redirect_uri: redirectUri,
grant_type: 'authorization_code'
}).toString();
const options = {
hostname: 'oauth2.googleapis.com',
path: '/token',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(postData)
}
};
const req = https.request(options, (res) => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
if (res.statusCode === 200) {
resolve(JSON.parse(body));
} else {
logger.error(`Token 交换失败: ${body}`);
reject(new Error(`Token 交换失败: ${res.statusCode} - ${body}`));
}
});
});
req.on('error', reject);
req.write(postData);
req.end();
});
}
// 批量导入 Token
export async function importTokens(filePath) {
try {
logger.info('开始导入 Token...');
// 检查是否是 ZIP 文件
if (filePath.endsWith('.zip') || true) {
const zip = new AdmZip(filePath);
const zipEntries = zip.getEntries();
// 查找 tokens.json
const tokensEntry = zipEntries.find(entry => entry.entryName === 'tokens.json');
if (!tokensEntry) {
throw new Error('ZIP 文件中没有找到 tokens.json');
}
const tokensContent = tokensEntry.getData().toString('utf8');
const importedTokens = JSON.parse(tokensContent);
// 验证数据格式
if (!Array.isArray(importedTokens)) {
throw new Error('tokens.json 格式错误:应该是一个数组');
}
// 加载现有账号
const accounts = await loadAccounts();
// 添加新账号
let addedCount = 0;
for (const token of importedTokens) {
// 检查是否已存在
const exists = accounts.some(acc => acc.access_token === token.access_token);
if (!exists) {
accounts.push({
access_token: token.access_token,
refresh_token: token.refresh_token,
expires_in: token.expires_in,
timestamp: token.timestamp || Date.now(),
enable: token.enable !== false
});
addedCount++;
}
}
// 保存账号
await saveAccounts(accounts);
// 清理上传的文件
try {
await fs.unlink(filePath);
} catch (e) {
logger.warn('清理上传文件失败:', e);
}
logger.info(`成功导入 ${addedCount} 个 Token 账号`);
return {
success: true,
count: addedCount,
total: importedTokens.length,
skipped: importedTokens.length - addedCount,
message: `成功导入 ${addedCount} 个 Token 账号${importedTokens.length - addedCount > 0 ? `,跳过 ${importedTokens.length - addedCount} 个重复账号` : ''}`
};
}
} catch (error) {
logger.error('导入 Token 失败:', error);
// 清理上传的文件
try {
await fs.unlink(filePath);
} catch (e) {}
throw error;
}
}
|