Download src/auth.js from pigracing/wq: direct link, hf CLI and curl.
- Browser
- Download file 6.55 kB
-
https://huggingface.co/spaces/pigracing/wq/resolve/main/src/auth.js
- Command line
-
hf download hf://spaces/pigracing/wq/src/auth.js
-
curl -L -o auth.js https://huggingface.co/spaces/pigracing/wq/resolve/main/src/auth.js
6.55 kB
| import { createHash, randomUUID } from 'crypto'; | |
| import { readFileSync, writeFileSync } from 'fs'; | |
| import { resolve, dirname } from 'path'; | |
| import { fileURLToPath } from 'url'; | |
| import { requestHeaders } from './headers.js'; | |
| const __dirname = dirname(fileURLToPath(import.meta.url)); | |
| const ENV_PATH = resolve(__dirname, '..', '.env'); | |
| function persistTokensToEnv() { | |
| try { | |
| let content = readFileSync(ENV_PATH, 'utf-8'); | |
| const aliveTokens = accountPool.filter(t => t.token).map(t => t.token); | |
| if (aliveTokens.length === 0) return; | |
| const line = `QWEN_TOKENS=${aliveTokens.join(',')}`; | |
| const lines = content.split(/\r?\n/); | |
| let found = false; | |
| for (let i = 0; i < lines.length; i++) { | |
| if (lines[i].startsWith('QWEN_TOKENS=')) { | |
| lines[i] = line; | |
| found = true; | |
| break; | |
| } | |
| } | |
| if (!found) lines.push(line); | |
| writeFileSync(ENV_PATH, lines.join('\n')); | |
| } catch (err) { | |
| console.warn('Failed to persist tokens to .env:', err.message); | |
| } | |
| } | |
| const BASE_URL = 'https://chat.qwen.ai'; | |
| const MAX_CONCURRENT_PER_TOKEN = 10; | |
| function sha256(text) { | |
| return createHash('sha256').update(text, 'utf-8').digest('hex'); | |
| } | |
| function decodeJWT(token) { | |
| try { | |
| const payload = token.split('.')[1]; | |
| return JSON.parse(Buffer.from(payload, 'base64').toString()); | |
| } catch { return null; } | |
| } | |
| function isTokenExpired(token) { | |
| const decoded = decodeJWT(token); | |
| if (!decoded?.exp) return true; | |
| return decoded.exp * 1000 < Date.now() + 5 * 60 * 1000; // 5 min buffer | |
| } | |
| // Account entry: { email, password, token, expiresAt, errorCount, activeRequests } | |
| const accountPool = []; | |
| export function loadAccounts() { | |
| const accountsStr = process.env.QWEN_ACCOUNTS?.trim(); | |
| const tokensStr = process.env.QWEN_TOKENS?.trim(); | |
| if (accountsStr) { | |
| for (const entry of accountsStr.split(',')) { | |
| const [email, ...passParts] = entry.trim().split(':'); | |
| const password = passParts.join(':'); | |
| if (email && password) { | |
| accountPool.push({ email, password, token: null, expiresAt: 0, errorCount: 0, activeRequests: 0 }); | |
| } | |
| } | |
| } | |
| if (tokensStr) { | |
| for (const token of tokensStr.split(',').map(t => t.trim()).filter(Boolean)) { | |
| const decoded = decodeJWT(token); | |
| accountPool.push({ | |
| email: decoded?.id || 'token-user', | |
| password: null, | |
| token, | |
| expiresAt: (decoded?.exp || 0) * 1000, | |
| errorCount: 0, | |
| activeRequests: 0, | |
| }); | |
| } | |
| } | |
| if (accountPool.length === 0) { | |
| throw new Error('No QWEN_ACCOUNTS or QWEN_TOKENS configured'); | |
| } | |
| return accountPool; | |
| } | |
| async function login(email, password) { | |
| const res = await fetch(`${BASE_URL}/api/v1/auths/signin`, { | |
| method: 'POST', | |
| headers: requestHeaders(), | |
| body: JSON.stringify({ email, password: sha256(password) }), | |
| }); | |
| const json = await res.json(); | |
| if (!json.token) throw new Error(`Login failed for ${email}: ${JSON.stringify(json)}`); | |
| return json.token; | |
| } | |
| async function ensureToken(entry) { | |
| if (entry.token && !isTokenExpired(entry.token)) return entry.token; | |
| if (!entry.password) { | |
| entry.errorCount++; | |
| throw new Error(`Token expired for ${entry.email}, no password to refresh`); | |
| } | |
| try { | |
| entry.token = await login(entry.email, entry.password); | |
| const decoded = decodeJWT(entry.token); | |
| entry.expiresAt = (decoded?.exp || 0) * 1000; | |
| entry.errorCount = 0; | |
| console.log(` Logged in: ${entry.email}, token expires ${new Date(entry.expiresAt).toISOString()}`); | |
| return entry.token; | |
| } catch (err) { | |
| entry.errorCount++; | |
| throw err; | |
| } | |
| } | |
| export async function initAccountPool() { | |
| console.log(`Account pool: ${accountPool.length} account(s), max ${MAX_CONCURRENT_PER_TOKEN} concurrent each`); | |
| for (const entry of accountPool) { | |
| try { | |
| await ensureToken(entry); | |
| } catch (err) { | |
| console.warn(` Failed to init ${entry.email}: ${err.message}`); | |
| } | |
| } | |
| } | |
| export function acquireToken() { | |
| let candidates = accountPool.filter(t => t.errorCount < 3 && t.activeRequests < MAX_CONCURRENT_PER_TOKEN && t.token); | |
| if (candidates.length === 0) { | |
| candidates = accountPool.filter(t => t.activeRequests < MAX_CONCURRENT_PER_TOKEN && t.token); | |
| } | |
| if (candidates.length === 0) { | |
| return null; | |
| } | |
| candidates.sort((a, b) => a.activeRequests - b.activeRequests); | |
| const chosen = candidates[0]; | |
| chosen.activeRequests++; | |
| let released = false; | |
| const release = () => { | |
| if (released) return; | |
| released = true; | |
| chosen.activeRequests = Math.max(0, chosen.activeRequests - 1); | |
| }; | |
| return { token: chosen.token, account: chosen, release }; | |
| } | |
| export function reportTokenError(token) { | |
| const entry = accountPool.find(t => t.token === token); | |
| if (entry) entry.errorCount++; | |
| } | |
| export function reportTokenSuccess(token) { | |
| const entry = accountPool.find(t => t.token === token); | |
| if (entry) entry.errorCount = 0; | |
| } | |
| export async function refreshToken(entry) { | |
| return ensureToken(entry); | |
| } | |
| export function addTokenToPool(tokenStr) { | |
| const token = tokenStr.trim(); | |
| const existing = accountPool.find(t => t.token === token); | |
| if (existing) return existing; | |
| const decoded = decodeJWT(token); | |
| const entry = { | |
| email: decoded?.id || 'token-user', | |
| password: null, | |
| token, | |
| expiresAt: (decoded?.exp || 0) * 1000, | |
| errorCount: 0, | |
| activeRequests: 0, | |
| }; | |
| accountPool.push(entry); | |
| persistTokensToEnv(); | |
| return entry; | |
| } | |
| export async function loginAndAddToken(email, password) { | |
| const token = await login(email, password); | |
| const existing = accountPool.find(t => t.email === email); | |
| if (existing) { | |
| existing.token = token; | |
| const decoded = decodeJWT(token); | |
| existing.expiresAt = (decoded?.exp || 0) * 1000; | |
| existing.errorCount = 0; | |
| persistTokensToEnv(); | |
| return existing; | |
| } | |
| const decoded = decodeJWT(token); | |
| const entry = { | |
| email, | |
| password, | |
| token, | |
| expiresAt: (decoded?.exp || 0) * 1000, | |
| errorCount: 0, | |
| activeRequests: 0, | |
| }; | |
| accountPool.push(entry); | |
| persistTokensToEnv(); | |
| return entry; | |
| } | |
| export function getPoolInfo() { | |
| return accountPool.map(t => ({ | |
| email: t.email, | |
| hasToken: !!t.token, | |
| expiresAt: t.expiresAt ? new Date(t.expiresAt).toISOString() : null, | |
| errorCount: t.errorCount, | |
| activeRequests: t.activeRequests, | |
| maxConcurrent: MAX_CONCURRENT_PER_TOKEN, | |
| })); | |
| } | |
| export function getTotalCapacity() { | |
| return accountPool.filter(t => t.errorCount < 3 && t.token).length * MAX_CONCURRENT_PER_TOKEN; | |
| } |