eaae16e33a
- /change-race-character (3€), /change-faction-character (5€, valida condiciones: sin hermandad/correo/subastas/capitán-arena y oro <= cap por nivel), /level-up-character (10€), /gold-character (oro por correo SOAP). Cada uno borra su ruta antigua y actualiza el link del panel. - SumUp: hook precheck en PaidServiceConfig (condiciones cambio de facción); columna home_stripelog.metadata para campos extra (gold_amount). - /quest-character: rastreador de estado de misiones/cadenas (clase, Hijos de Hodir, misión por ID) en personaje offline; cooldowns por categoría, Turnstile, IDs de misión verificados en wotlkdb 3.3.5a. Tabla home_questsearchhistory. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
80 lines
3.2 KiB
TypeScript
80 lines
3.2 KiB
TypeScript
import type { RowDataPacket } from 'mysql2'
|
|
import { getSession } from '@/lib/session'
|
|
import { db, DB } from '@/lib/db'
|
|
import { getClassCss } from '@/lib/character-info'
|
|
import { verifyTurnstile } from '@/lib/turnstile'
|
|
import { QUEST_OPTIONS, questStatuses, cooldownRemainingMs, recordSearch, type QuestCategory } from '@/lib/quest-tracker'
|
|
|
|
export async function POST(request: Request) {
|
|
const session = await getSession()
|
|
if (!session.accountId || !session.username) {
|
|
return Response.json({ success: false, error: 'notAuthenticated' }, { status: 401 })
|
|
}
|
|
|
|
let body: Record<string, unknown> = {}
|
|
try {
|
|
body = await request.json()
|
|
} catch {
|
|
return Response.json({ success: false, error: 'invalidRequest' }, { status: 400 })
|
|
}
|
|
|
|
const character = String(body.character ?? '').trim()
|
|
const optionKey = String(body.option ?? '').trim()
|
|
if (!character || !optionKey) return Response.json({ success: false, error: 'missingFields' })
|
|
|
|
const ip = (request.headers.get('x-forwarded-for') || '').split(',')[0].trim() || undefined
|
|
if (!(await verifyTurnstile(String(body.turnstile ?? ''), ip))) {
|
|
return Response.json({ success: false, error: 'captcha' })
|
|
}
|
|
|
|
// Personaje: debe ser de la cuenta y estar desconectado.
|
|
let guid = 0
|
|
let level = 0
|
|
let classCss = ''
|
|
try {
|
|
const [rows] = await db(DB.characters).query<RowDataPacket[]>(
|
|
'SELECT guid, level, class, online FROM characters WHERE name = ? AND account = ? LIMIT 1',
|
|
[character, session.accountId],
|
|
)
|
|
const c = rows[0]
|
|
if (!c) return Response.json({ success: false, error: 'characterNotOwned' })
|
|
if (c.online === 1) return Response.json({ success: false, error: 'characterOnline' })
|
|
guid = Number(c.guid)
|
|
level = Number(c.level)
|
|
classCss = getClassCss(Number(c.class))
|
|
} catch {
|
|
return Response.json({ success: false, error: 'genericError' })
|
|
}
|
|
|
|
// Resuelve categoría + misiones a comprobar.
|
|
let category: QuestCategory
|
|
let quests: number[]
|
|
let label: string
|
|
if (optionKey === 'specific') {
|
|
const qid = Number(body.questId)
|
|
if (!Number.isInteger(qid) || qid <= 0) return Response.json({ success: false, error: 'invalidQuestId' })
|
|
if (level < 10) return Response.json({ success: false, error: 'levelTooLow' })
|
|
category = 'specific'
|
|
quests = [qid]
|
|
label = `Misión #${qid}`
|
|
} else {
|
|
const opt = QUEST_OPTIONS.find((o) => o.key === optionKey)
|
|
if (!opt) return Response.json({ success: false, error: 'invalidOption' })
|
|
if (opt.classCss && opt.classCss !== classCss) return Response.json({ success: false, error: 'wrongClass' })
|
|
if (level < opt.minLevel) return Response.json({ success: false, error: 'levelTooLow' })
|
|
if (opt.quests.length === 0) return Response.json({ success: false, error: 'noData' })
|
|
category = opt.category
|
|
quests = opt.quests
|
|
label = opt.label
|
|
}
|
|
|
|
const remaining = await cooldownRemainingMs(character, category)
|
|
if (remaining > 0) {
|
|
return Response.json({ success: false, error: 'cooldown', minutes: Math.ceil(remaining / 60000) })
|
|
}
|
|
|
|
const statuses = await questStatuses(guid, quests)
|
|
await recordSearch(character, category)
|
|
return Response.json({ success: true, label, statuses })
|
|
}
|