|
|
|
|
|
const express = require('express'); |
|
const bodyParser = require('body-parser'); |
|
const axios = require('axios'); |
|
const path = require('path'); |
|
const mysql = require('mysql2'); |
|
const morgan = require('morgan'); |
|
const helmet = require('helmet'); |
|
const { body, validationResult } = require('express-validator'); |
|
require('dotenv').config(); |
|
|
|
const app = express(); |
|
const port = process.env.PORT || 3000; |
|
|
|
|
|
app.use(bodyParser.urlencoded({ extended: true })); |
|
app.use(bodyParser.json()); |
|
app.use(helmet()); |
|
app.use(morgan('combined')); |
|
app.use(express.static('public')); |
|
|
|
|
|
const pool = mysql.createPool({ |
|
host: process.env.DB_HOST, |
|
port: process.env.DB_PORT || 3306, |
|
user: process.env.DB_USER, |
|
password: process.env.DB_PASSWORD, |
|
database: process.env.DB_NAME, |
|
waitForConnections: true, |
|
connectionLimit: 10, |
|
queueLimit: 0 |
|
}); |
|
|
|
|
|
const promisePool = pool.promise(); |
|
|
|
|
|
async function initializeDatabase() { |
|
try { |
|
|
|
const createRedemptionCodesTable = ` |
|
CREATE TABLE IF NOT EXISTS redemption_codes ( |
|
id INT AUTO_INCREMENT PRIMARY KEY, |
|
code TEXT NOT NULL, |
|
name VARCHAR(255), |
|
quota INT, |
|
count INT, |
|
user_ip VARCHAR(45), |
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP |
|
) |
|
`; |
|
|
|
await promisePool.query(createRedemptionCodesTable); |
|
console.log('Redemption codes table is ready.'); |
|
|
|
|
|
const createSessionCookiesTable = ` |
|
CREATE TABLE IF NOT EXISTS session_cookies ( |
|
id INT AUTO_INCREMENT PRIMARY KEY, |
|
username VARCHAR(255) NOT NULL, |
|
session_cookies TEXT NOT NULL, |
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP |
|
) |
|
`; |
|
|
|
await promisePool.query(createSessionCookiesTable); |
|
console.log('Session_cookies table is ready.'); |
|
|
|
|
|
const createTokensTable = ` |
|
CREATE TABLE IF NOT EXISTS tokens ( |
|
id INT AUTO_INCREMENT PRIMARY KEY, |
|
name VARCHAR(255) NOT NULL, |
|
token TEXT NOT NULL, |
|
token_key VARCHAR(255) NOT NULL, |
|
remain_quota INT NOT NULL, |
|
expired_time INT NOT NULL, |
|
unlimited_quota BOOLEAN NOT NULL, |
|
model_limits_enabled BOOLEAN NOT NULL, |
|
model_limits TEXT, |
|
allow_ips TEXT, |
|
group_name VARCHAR(255), |
|
user_ip VARCHAR(45), |
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP |
|
) |
|
`; |
|
|
|
await promisePool.query(createTokensTable); |
|
console.log('Tokens table is ready.'); |
|
} catch (err) { |
|
console.error('Error initializing database tables:', err); |
|
process.exit(1); |
|
} |
|
} |
|
|
|
|
|
initializeDatabase(); |
|
|
|
|
|
app.get('/', (req, res) => { |
|
res.sendFile(path.join(__dirname, 'views', 'index.html')); |
|
}); |
|
|
|
|
|
function getUserIP(req) { |
|
const xForwardedFor = req.headers['x-forwarded-for']; |
|
if (xForwardedFor) { |
|
const ips = xForwardedFor.split(',').map(ip => ip.trim()); |
|
return ips[0]; |
|
} |
|
return req.socket.remoteAddress; |
|
} |
|
|
|
|
|
async function saveSessionCookies(username, sessionCookies) { |
|
try { |
|
const insertQuery = ` |
|
INSERT INTO session_cookies (username, session_cookies) |
|
VALUES (?, ?) |
|
`; |
|
|
|
await promisePool.query(insertQuery, [username, sessionCookies]); |
|
console.log(`New session cookies for user '${username}' saved successfully.`); |
|
} catch (err) { |
|
console.error(`Error saving session cookies for user '${username}':`, err); |
|
throw err; |
|
} |
|
} |
|
|
|
|
|
async function performLogin() { |
|
const loginUrl = `${process.env.BASE_URL}/api/user/login?turnstile=`; |
|
|
|
const headers = { |
|
'Accept': 'application/json, text/plain, */*', |
|
'Accept-Language': 'en-US,en;q=0.9', |
|
'Connection': 'keep-alive', |
|
'Content-Type': 'application/json', |
|
'Origin': process.env.BASE_URL, |
|
'Referer': `${process.env.BASE_URL}/login`, |
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', |
|
'VoApi-User': '-1' |
|
}; |
|
|
|
const data = { |
|
username: process.env.ADMIN_USERNAME, |
|
password: process.env.ADMIN_PASSWORD |
|
}; |
|
|
|
try { |
|
const response = await axios.post(loginUrl, data, { |
|
headers: headers, |
|
withCredentials: true, |
|
httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }) |
|
}); |
|
|
|
|
|
const setCookieHeader = response.headers['set-cookie']; |
|
if (setCookieHeader) { |
|
|
|
const cookies = setCookieHeader.map(cookie => cookie.split(';')[0]); |
|
const cookieString = cookies.join('; '); |
|
|
|
if (cookieString) { |
|
console.log('Session cookies obtained from login.'); |
|
return cookieString; |
|
} |
|
} |
|
|
|
throw new Error('No session cookies received from login response.'); |
|
} catch (error) { |
|
if (error.response) { |
|
throw new Error(`Login API Error: ${JSON.stringify(error.response.data)}`); |
|
} else if (error.request) { |
|
throw new Error('Login API Error: No response received from the login service.'); |
|
} else { |
|
throw new Error(`Login Error: ${error.message}`); |
|
} |
|
} |
|
} |
|
|
|
|
|
async function generateRedemptionCode(name, quota, count, sessionCookies) { |
|
console.log(`Using session cookies: ${sessionCookies}`); |
|
|
|
const url = `${process.env.BASE_URL}/api/redemption/`; |
|
|
|
const headers = { |
|
'Accept': 'application/json, text/plain, */*', |
|
'Accept-Language': 'en-US,en;q=0.9', |
|
'Connection': 'keep-alive', |
|
'Content-Type': 'application/json', |
|
'Origin': process.env.BASE_URL, |
|
'Referer': `${process.env.BASE_URL}/redemption`, |
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', |
|
'VoApi-User': '1', |
|
'Cookie': sessionCookies |
|
}; |
|
|
|
const data = { |
|
name: name, |
|
quota: parseInt(quota), |
|
count: parseInt(count) |
|
}; |
|
|
|
try { |
|
const response = await axios.post(url, data, { |
|
headers: headers, |
|
httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }) |
|
}); |
|
|
|
console.log('Redemption code generated successfully.'); |
|
return response.data; |
|
} catch (error) { |
|
if (error.response) { |
|
throw new Error(`API Error: ${JSON.stringify(error.response.data)}`); |
|
} else if (error.request) { |
|
throw new Error('API Error: No response received from the redemption service.'); |
|
} else { |
|
throw new Error(`Error: ${error.message}`); |
|
} |
|
} |
|
} |
|
|
|
|
|
async function saveRedemptionCode(codeData, name, quota, count, userIP) { |
|
try { |
|
|
|
const code = codeData.code || JSON.stringify(codeData); |
|
|
|
const insertQuery = ` |
|
INSERT INTO redemption_codes (code, name, quota, count, user_ip) |
|
VALUES (?, ?, ?, ?, ?) |
|
`; |
|
|
|
await promisePool.query(insertQuery, [code, name, quota, count, userIP]); |
|
console.log('Redemption code saved to database.'); |
|
} catch (err) { |
|
console.error('Error saving redemption code to database:', err); |
|
throw err; |
|
} |
|
} |
|
|
|
|
|
async function createToken(name, remain_quota, expired_time, unlimited_quota, model_limits_enabled, model_limits, allow_ips, group_name, sessionCookies) { |
|
console.log(`Using session cookies for token creation: ${sessionCookies}`); |
|
|
|
const url = `${process.env.BASE_URL}/api/token/`; |
|
|
|
const headers = { |
|
'Accept': 'application/json, text/plain, */*', |
|
'Accept-Language': 'en-US,en;q=0.9,ru;q=0.8', |
|
'Connection': 'keep-alive', |
|
'Content-Type': 'application/json', |
|
'Origin': process.env.BASE_URL, |
|
'Referer': `${process.env.BASE_URL}/token`, |
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', |
|
'VoApi-User': '1', |
|
'Cookie': sessionCookies |
|
}; |
|
|
|
const data = { |
|
name: name, |
|
remain_quota: parseInt(remain_quota), |
|
expired_time: parseInt(expired_time), |
|
unlimited_quota: unlimited_quota, |
|
model_limits_enabled: model_limits_enabled, |
|
model_limits: model_limits || '', |
|
allow_ips: allow_ips || '', |
|
group: group_name || '' |
|
}; |
|
|
|
try { |
|
const response = await axios.post(url, data, { |
|
headers: headers, |
|
httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }) |
|
}); |
|
|
|
console.log('Token creation API call successful.'); |
|
return response.data; |
|
} catch (error) { |
|
if (error.response) { |
|
throw new Error(`Token API Error: ${JSON.stringify(error.response.data)}`); |
|
} else if (error.request) { |
|
throw new Error('Token API Error: No response received from the token service.'); |
|
} else { |
|
throw new Error(`Error: ${error.message}`); |
|
} |
|
} |
|
} |
|
|
|
|
|
async function listTokens(sessionCookies, p = 0, size = 100) { |
|
console.log(`Listing tokens with session cookies: ${sessionCookies}`); |
|
|
|
const url = `${process.env.BASE_URL}/api/token/?p=${p}&size=${size}`; |
|
|
|
const headers = { |
|
'Accept': 'application/json, text/plain, */*', |
|
'Accept-Language': 'en-US,en;q=0.9,ru;q=0.8', |
|
'Connection': 'keep-alive', |
|
'Referer': `${process.env.BASE_URL}/token`, |
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', |
|
'VoApi-User': '1', |
|
'Cookie': sessionCookies |
|
}; |
|
|
|
try { |
|
const response = await axios.get(url, { |
|
headers: headers, |
|
httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }) |
|
}); |
|
|
|
console.log('Tokens list retrieved successfully.'); |
|
return response.data; |
|
} catch (error) { |
|
if (error.response) { |
|
throw new Error(`List Tokens API Error: ${JSON.stringify(error.response.data)}`); |
|
} else if (error.request) { |
|
throw new Error('List Tokens API Error: No response received from the token service.'); |
|
} else { |
|
throw new Error(`Error: ${error.message}`); |
|
} |
|
} |
|
} |
|
|
|
|
|
async function saveToken(tokenData, name, remain_quota, expired_time, unlimited_quota, model_limits_enabled, model_limits, allow_ips, group_name, userIP) { |
|
try { |
|
|
|
const key = tokenData.key ? `sk-${tokenData.key}` : null; |
|
|
|
if (!key) { |
|
throw new Error('Token key not found in the response.'); |
|
} |
|
|
|
|
|
const insertQuery = ` |
|
INSERT INTO tokens (name, token, token_key, remain_quota, expired_time, unlimited_quota, model_limits_enabled, model_limits, allow_ips, group_name, user_ip) |
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
|
`; |
|
|
|
const [result] = await promisePool.query(insertQuery, [ |
|
name, |
|
JSON.stringify(tokenData), |
|
key, |
|
parseInt(remain_quota), |
|
parseInt(expired_time), |
|
unlimited_quota, |
|
model_limits_enabled, |
|
model_limits || '', |
|
allow_ips || '', |
|
group_name || '', |
|
userIP |
|
]); |
|
|
|
console.log(`Token saved to database with ID: ${result.insertId} and key: ${key}`); |
|
} catch (err) { |
|
console.error('Error saving token to database:', err); |
|
throw err; |
|
} |
|
} |
|
|
|
|
|
app.post('/api/create', [ |
|
body('username').isString().trim().notEmpty(), |
|
body('name').isString().trim().notEmpty(), |
|
body('quota').isInt({ min: 1 }), |
|
body('count').isInt({ min: 1 }) |
|
], async (req, res) => { |
|
|
|
const errors = validationResult(req); |
|
if (!errors.isEmpty()) { |
|
return res.status(400).json({ success: false, errors: errors.array() }); |
|
} |
|
|
|
const { username, name, quota, count } = req.body; |
|
const userIP = getUserIP(req); |
|
const apiKey = req.headers['x-api-key']; |
|
|
|
|
|
if (apiKey !== process.env.API_KEY) { |
|
return res.status(401).json({ |
|
success: false, |
|
error: 'Unauthorized: Invalid API key' |
|
}); |
|
} |
|
|
|
try { |
|
|
|
console.log('Performing login to obtain new session cookies...'); |
|
const sessionCookies = await performLogin(); |
|
|
|
|
|
await saveSessionCookies(username, sessionCookies); |
|
console.log('New session cookies saved to the database.'); |
|
|
|
|
|
const redemptionCode = await generateRedemptionCode(name, quota, count, sessionCookies); |
|
|
|
|
|
await saveRedemptionCode(redemptionCode, name, quota, count, userIP); |
|
|
|
res.json({ |
|
success: true, |
|
data: redemptionCode |
|
}); |
|
} catch (error) { |
|
console.error('Error:', error.message); |
|
res.status(500).json({ |
|
success: false, |
|
error: error.message |
|
}); |
|
} |
|
}); |
|
|
|
|
|
app.post('/api/token', [ |
|
body('name').isString().trim().notEmpty(), |
|
body('remain_quota').isInt({ min: 1 }), |
|
body('expired_time').isInt(), |
|
body('unlimited_quota').isBoolean(), |
|
body('model_limits_enabled').isBoolean(), |
|
body('model_limits').optional().isString(), |
|
body('allow_ips').optional().isString(), |
|
body('group').optional().isString() |
|
], async (req, res) => { |
|
|
|
const errors = validationResult(req); |
|
if (!errors.isEmpty()) { |
|
return res.status(400).json({ success: false, errors: errors.array() }); |
|
} |
|
|
|
const { |
|
name, |
|
remain_quota, |
|
expired_time, |
|
unlimited_quota, |
|
model_limits_enabled, |
|
model_limits, |
|
allow_ips, |
|
group |
|
} = req.body; |
|
|
|
const userIP = getUserIP(req); |
|
const apiKey = req.headers['x-api-key']; |
|
|
|
|
|
if (apiKey !== process.env.API_KEY) { |
|
return res.status(401).json({ |
|
success: false, |
|
error: 'Unauthorized: Invalid API key' |
|
}); |
|
} |
|
|
|
try { |
|
|
|
console.log('Performing login to obtain new session cookies for token creation...'); |
|
const sessionCookies = await performLogin(); |
|
|
|
|
|
await saveSessionCookies(process.env.ADMIN_USERNAME, sessionCookies); |
|
console.log('New session cookies saved to the database.'); |
|
|
|
|
|
const tokenData = await createToken( |
|
name, |
|
remain_quota, |
|
expired_time, |
|
unlimited_quota, |
|
model_limits_enabled, |
|
model_limits, |
|
allow_ips, |
|
group, |
|
sessionCookies |
|
); |
|
|
|
|
|
let tokenKey = tokenData.key; |
|
if (!tokenKey) { |
|
console.log('Token key not found in creation response. Fetching from tokens list...'); |
|
const tokensList = await listTokens(sessionCookies, 0, 100); |
|
|
|
if (tokensList.success && Array.isArray(tokensList.data) && tokensList.data.length > 0) { |
|
|
|
const latestToken = tokensList.data[0]; |
|
tokenKey = latestToken.key; |
|
|
|
if (!tokenKey) { |
|
throw new Error('Token key not found in the tokens list response.'); |
|
} |
|
} else { |
|
throw new Error('Failed to retrieve tokens list or no tokens available.'); |
|
} |
|
} |
|
|
|
|
|
tokenData.key = `sk-${tokenKey}`; |
|
|
|
|
|
await saveToken( |
|
tokenData, |
|
name, |
|
remain_quota, |
|
expired_time, |
|
unlimited_quota, |
|
model_limits_enabled, |
|
model_limits, |
|
allow_ips, |
|
group, |
|
userIP |
|
); |
|
|
|
res.json({ |
|
success: true, |
|
data: tokenData.key |
|
}); |
|
} catch (error) { |
|
console.error('Error:', error.message); |
|
res.status(500).json({ |
|
success: false, |
|
error: error.message |
|
}); |
|
} |
|
}); |
|
|
|
|
|
app.get('/health', (req, res) => { |
|
res.status(200).json({ status: 'OK' }); |
|
}); |
|
|
|
|
|
app.listen(port, () => { |
|
console.log(`App is running at http://localhost:${port}`); |
|
}); |
|
|