1118 lines
26 KiB
TypeScript
1118 lines
26 KiB
TypeScript
/**
|
|
* Entity Adapter
|
|
*
|
|
* Handles read-only access to canonical game data (weapons, characters, summons).
|
|
* This data represents the official game information that users reference
|
|
* but cannot modify.
|
|
*
|
|
* @module adapters/entity
|
|
*/
|
|
|
|
import { BaseAdapter } from './base.adapter'
|
|
import type { AdapterOptions } from './types'
|
|
import { DEFAULT_ADAPTER_CONFIG } from './config'
|
|
|
|
/**
|
|
* Canonical weapon data from the game
|
|
*/
|
|
export interface Weapon {
|
|
id: string
|
|
granblueId: string
|
|
name: {
|
|
en?: string
|
|
ja?: string
|
|
}
|
|
rarity: number
|
|
element: number
|
|
proficiency: number
|
|
series?: number
|
|
weaponType?: number
|
|
minHp?: number
|
|
maxHp?: number
|
|
minAttack?: number
|
|
maxAttack?: number
|
|
flbHp?: number
|
|
flbAttack?: number
|
|
ulbHp?: number
|
|
ulbAttack?: number
|
|
transcendenceHp?: number
|
|
transcendenceAttack?: number
|
|
hp?: {
|
|
minHp?: number
|
|
maxHp?: number
|
|
maxHpFlb?: number
|
|
maxHpUlb?: number
|
|
}
|
|
atk?: {
|
|
minAtk?: number
|
|
maxAtk?: number
|
|
maxAtkFlb?: number
|
|
maxAtkUlb?: number
|
|
}
|
|
uncap?: {
|
|
flb?: boolean
|
|
ulb?: boolean
|
|
transcendence?: boolean
|
|
}
|
|
maxLevel?: number
|
|
skillLevelCap?: number
|
|
weapon_skills?: Array<{
|
|
name?: string
|
|
description?: string
|
|
}>
|
|
awakenings?: Array<{
|
|
id: string
|
|
name: Record<string, string>
|
|
level: number
|
|
}>
|
|
}
|
|
|
|
/**
|
|
* Canonical character data from the game
|
|
*/
|
|
export interface Character {
|
|
id: string
|
|
granblueId: string
|
|
characterId?: number
|
|
name: {
|
|
en?: string
|
|
ja?: string
|
|
}
|
|
rarity: number
|
|
element: number
|
|
gender?: number
|
|
proficiency?: number[]
|
|
proficiency1?: number
|
|
proficiency2?: number
|
|
series?: number
|
|
race?: number[]
|
|
hp?: {
|
|
minHp?: number
|
|
maxHp?: number
|
|
maxHpFlb?: number
|
|
}
|
|
atk?: {
|
|
minAtk?: number
|
|
maxAtk?: number
|
|
maxAtkFlb?: number
|
|
}
|
|
uncap?: {
|
|
flb?: boolean
|
|
ulb?: boolean
|
|
transcendence?: boolean
|
|
}
|
|
special?: boolean
|
|
seasonalId?: string
|
|
awakenings?: Array<{
|
|
id: string
|
|
name: Record<string, string>
|
|
level: number
|
|
}>
|
|
}
|
|
|
|
/**
|
|
* Weapon key data for customizing weapons
|
|
*/
|
|
export interface WeaponKey {
|
|
id: string
|
|
granblue_id: number
|
|
name: {
|
|
en: string
|
|
ja: string
|
|
}
|
|
slug: string
|
|
series: number[]
|
|
slot: number
|
|
group: number
|
|
order: number
|
|
}
|
|
|
|
/**
|
|
* Query parameters for fetching weapon keys
|
|
*/
|
|
export interface WeaponKeyQueryParams {
|
|
series?: number
|
|
slot?: number
|
|
group?: number
|
|
}
|
|
|
|
/**
|
|
* Canonical summon data from the game
|
|
*/
|
|
export interface Summon {
|
|
id: string
|
|
granblueId: string
|
|
name: {
|
|
en?: string
|
|
ja?: string
|
|
}
|
|
rarity: number
|
|
element: number
|
|
series?: number
|
|
minHp?: number
|
|
maxHp?: number
|
|
minAttack?: number
|
|
maxAttack?: number
|
|
flbHp?: number
|
|
flbAttack?: number
|
|
ulbHp?: number
|
|
ulbAttack?: number
|
|
transcendenceHp?: number
|
|
transcendenceAttack?: number
|
|
hp?: {
|
|
minHp?: number
|
|
maxHp?: number
|
|
maxHpFlb?: number
|
|
maxHpUlb?: number
|
|
maxHpXlb?: number
|
|
}
|
|
atk?: {
|
|
minAtk?: number
|
|
maxAtk?: number
|
|
maxAtkFlb?: number
|
|
maxAtkUlb?: number
|
|
maxAtkXlb?: number
|
|
}
|
|
uncap?: {
|
|
flb?: boolean
|
|
ulb?: boolean
|
|
transcendence?: boolean
|
|
}
|
|
subaura?: boolean
|
|
cooldown?: number
|
|
callName?: string
|
|
callDescription?: string
|
|
auraName?: string
|
|
auraDescription?: string
|
|
subAuraName?: string
|
|
subAuraDescription?: string
|
|
}
|
|
|
|
/**
|
|
* Response from character granblue_id validation
|
|
*/
|
|
export interface CharacterValidationResult {
|
|
valid: boolean
|
|
granblueId: string
|
|
existsInDb: boolean
|
|
error?: string
|
|
imageUrls?: {
|
|
main?: string
|
|
grid?: string
|
|
square?: string
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Payload for creating a new character
|
|
*/
|
|
export interface CreateCharacterPayload {
|
|
granblue_id: string
|
|
name_en: string
|
|
name_jp?: string
|
|
character_id?: number[] // Array for dual/trio units
|
|
rarity?: number
|
|
element?: number
|
|
race1?: number | null
|
|
race2?: number | null
|
|
gender?: number
|
|
proficiency1?: number
|
|
proficiency2?: number
|
|
min_hp?: number
|
|
max_hp?: number
|
|
max_hp_flb?: number
|
|
max_hp_ulb?: number
|
|
min_atk?: number
|
|
max_atk?: number
|
|
max_atk_flb?: number
|
|
max_atk_ulb?: number
|
|
base_da?: number
|
|
base_ta?: number
|
|
ougi_ratio?: number
|
|
ougi_ratio_flb?: number
|
|
flb?: boolean
|
|
ulb?: boolean
|
|
special?: boolean
|
|
release_date?: string | null
|
|
flb_date?: string | null
|
|
ulb_date?: string | null
|
|
wiki_en?: string
|
|
wiki_ja?: string
|
|
gamewith?: string
|
|
kamigame?: string
|
|
nicknames_en?: string[]
|
|
nicknames_jp?: string[]
|
|
}
|
|
|
|
/**
|
|
* Response from character image download status
|
|
*/
|
|
export interface CharacterDownloadStatus {
|
|
status: 'queued' | 'processing' | 'completed' | 'failed' | 'not_found'
|
|
progress?: number
|
|
imagesDownloaded?: number
|
|
imagesTotal?: number
|
|
error?: string
|
|
characterId?: string
|
|
granblueId?: string
|
|
images?: Record<string, string[]>
|
|
updatedAt?: string
|
|
}
|
|
|
|
/**
|
|
* Response from summon granblue_id validation
|
|
*/
|
|
export interface SummonValidationResult {
|
|
valid: boolean
|
|
granblueId: string
|
|
existsInDb: boolean
|
|
error?: string
|
|
imageUrls?: {
|
|
main?: string
|
|
grid?: string
|
|
square?: string
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Payload for creating a new summon
|
|
* Note: Frontend uses "transcendence" but API expects "xlb" for stats
|
|
*/
|
|
export interface CreateSummonPayload {
|
|
granblue_id: string
|
|
name_en: string
|
|
name_jp?: string
|
|
summon_id?: string
|
|
rarity?: number
|
|
element?: number
|
|
series?: string
|
|
min_hp?: number
|
|
max_hp?: number
|
|
max_hp_flb?: number
|
|
max_hp_ulb?: number
|
|
max_hp_xlb?: number // transcendence HP
|
|
min_atk?: number
|
|
max_atk?: number
|
|
max_atk_flb?: number
|
|
max_atk_ulb?: number
|
|
max_atk_xlb?: number // transcendence ATK
|
|
max_level?: number
|
|
flb?: boolean
|
|
ulb?: boolean
|
|
transcendence?: boolean
|
|
subaura?: boolean
|
|
limit?: boolean
|
|
release_date?: string | null
|
|
flb_date?: string | null
|
|
ulb_date?: string | null
|
|
transcendence_date?: string | null
|
|
wiki_en?: string
|
|
wiki_ja?: string
|
|
gamewith?: string
|
|
kamigame?: string
|
|
nicknames_en?: string[]
|
|
nicknames_jp?: string[]
|
|
}
|
|
|
|
/**
|
|
* Response from summon image download status
|
|
*/
|
|
export interface SummonDownloadStatus {
|
|
status: 'queued' | 'processing' | 'completed' | 'failed' | 'not_found'
|
|
progress?: number
|
|
imagesDownloaded?: number
|
|
imagesTotal?: number
|
|
error?: string
|
|
summonId?: string
|
|
granblueId?: string
|
|
images?: Record<string, string[]>
|
|
updatedAt?: string
|
|
}
|
|
|
|
/**
|
|
* Response from weapon granblue_id validation
|
|
*/
|
|
export interface WeaponValidationResult {
|
|
valid: boolean
|
|
granblueId: string
|
|
existsInDb: boolean
|
|
error?: string
|
|
imageUrls?: {
|
|
main?: string
|
|
grid?: string
|
|
square?: string
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Payload for creating a new weapon
|
|
* Note: Frontend uses "transcendence" but API expects "xlb" for stats
|
|
*/
|
|
export interface CreateWeaponPayload {
|
|
granblue_id: string
|
|
name_en: string
|
|
name_jp?: string
|
|
rarity?: number
|
|
element?: number
|
|
proficiency?: number
|
|
series?: number
|
|
new_series?: number
|
|
min_hp?: number
|
|
max_hp?: number
|
|
max_hp_flb?: number
|
|
max_hp_ulb?: number
|
|
min_atk?: number
|
|
max_atk?: number
|
|
max_atk_flb?: number
|
|
max_atk_ulb?: number
|
|
max_level?: number
|
|
max_skill_level?: number
|
|
max_awakening_level?: number
|
|
flb?: boolean
|
|
ulb?: boolean
|
|
transcendence?: boolean
|
|
extra?: boolean
|
|
limit?: boolean
|
|
ax?: boolean
|
|
release_date?: string | null
|
|
flb_date?: string | null
|
|
ulb_date?: string | null
|
|
transcendence_date?: string | null
|
|
wiki_en?: string
|
|
wiki_ja?: string
|
|
gamewith?: string
|
|
kamigame?: string
|
|
recruits?: string | null // Character ID reference
|
|
nicknames_en?: string[]
|
|
nicknames_jp?: string[]
|
|
}
|
|
|
|
/**
|
|
* Response from weapon image download status
|
|
*/
|
|
export interface WeaponDownloadStatus {
|
|
status: 'queued' | 'processing' | 'completed' | 'failed' | 'not_found'
|
|
progress?: number
|
|
imagesDownloaded?: number
|
|
imagesTotal?: number
|
|
error?: string
|
|
weaponId?: string
|
|
granblueId?: string
|
|
images?: Record<string, string[]>
|
|
updatedAt?: string
|
|
}
|
|
|
|
/**
|
|
* Raw data response from /raw endpoint
|
|
*/
|
|
export interface EntityRawData {
|
|
wikiRaw: string | null
|
|
gameRawEn: Record<string, unknown> | null
|
|
gameRawJp: Record<string, unknown> | null
|
|
}
|
|
|
|
/**
|
|
* Suggestions for character fields parsed from wiki data
|
|
*/
|
|
export interface CharacterSuggestions {
|
|
nameEn?: string
|
|
nameJp?: string
|
|
granblueId?: string
|
|
characterId?: number[]
|
|
rarity?: number
|
|
element?: number
|
|
gender?: number
|
|
proficiency1?: number
|
|
proficiency2?: number
|
|
race1?: number
|
|
race2?: number
|
|
minHp?: number
|
|
maxHp?: number
|
|
maxHpFlb?: number
|
|
minAtk?: number
|
|
maxAtk?: number
|
|
maxAtkFlb?: number
|
|
flb?: boolean
|
|
ulb?: boolean
|
|
releaseDate?: string
|
|
flbDate?: string
|
|
ulbDate?: string
|
|
gamewith?: string
|
|
kamigame?: string
|
|
}
|
|
|
|
/**
|
|
* Suggestions for weapon fields parsed from wiki data
|
|
*/
|
|
export interface WeaponSuggestions {
|
|
nameEn?: string
|
|
nameJp?: string
|
|
granblueId?: string
|
|
rarity?: number
|
|
element?: number
|
|
proficiency?: number
|
|
minHp?: number
|
|
maxHp?: number
|
|
maxHpFlb?: number
|
|
minAtk?: number
|
|
maxAtk?: number
|
|
maxAtkFlb?: number
|
|
flb?: boolean
|
|
ulb?: boolean
|
|
releaseDate?: string
|
|
flbDate?: string
|
|
ulbDate?: string
|
|
gamewith?: string
|
|
kamigame?: string
|
|
recruits?: string
|
|
}
|
|
|
|
/**
|
|
* Suggestions for summon fields parsed from wiki data
|
|
*/
|
|
export interface SummonSuggestions {
|
|
nameEn?: string
|
|
nameJp?: string
|
|
granblueId?: string
|
|
rarity?: number
|
|
element?: number
|
|
minHp?: number
|
|
maxHp?: number
|
|
maxHpFlb?: number
|
|
minAtk?: number
|
|
maxAtk?: number
|
|
maxAtkFlb?: number
|
|
flb?: boolean
|
|
ulb?: boolean
|
|
subaura?: boolean
|
|
releaseDate?: string
|
|
flbDate?: string
|
|
ulbDate?: string
|
|
gamewith?: string
|
|
kamigame?: string
|
|
}
|
|
|
|
/**
|
|
* Result from batch_preview for a single wiki page
|
|
*/
|
|
export interface BatchPreviewResult<T> {
|
|
wikiPage: string
|
|
status: 'success' | 'error'
|
|
granblueId?: string
|
|
wikiRaw?: string
|
|
suggestions?: T
|
|
imageStatus?: 'pending' | 'exists' | 'error' | 'no_id'
|
|
error?: string
|
|
redirectedFrom?: string
|
|
}
|
|
|
|
/**
|
|
* Response from batch_preview endpoint
|
|
*/
|
|
export interface BatchPreviewResponse<T> {
|
|
results: BatchPreviewResult<T>[]
|
|
}
|
|
|
|
/**
|
|
* Entity adapter for accessing canonical game data
|
|
*/
|
|
export class EntityAdapter extends BaseAdapter {
|
|
|
|
/**
|
|
* Gets canonical weapon data by ID
|
|
*/
|
|
async getWeapon(id: string): Promise<Weapon> {
|
|
return this.request<Weapon>(`/weapons/${id}`, {
|
|
method: 'GET',
|
|
cacheTTL: 600000 // Cache for 10 minutes
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Gets canonical character data by ID
|
|
*/
|
|
async getCharacter(id: string): Promise<Character> {
|
|
return this.request<Character>(`/characters/${id}`, {
|
|
method: 'GET',
|
|
cacheTTL: 600000 // Cache for 10 minutes
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Gets related characters (same character_id) for a given character
|
|
*/
|
|
async getRelatedCharacters(id: string): Promise<Character[]> {
|
|
return this.request<Character[]>(`/characters/${id}/related`, {
|
|
method: 'GET',
|
|
cacheTTL: 600000 // Cache for 10 minutes
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Gets canonical summon data by ID
|
|
*/
|
|
async getSummon(id: string): Promise<Summon> {
|
|
return this.request<Summon>(`/summons/${id}`, {
|
|
method: 'GET',
|
|
cacheTTL: 600000 // Cache for 10 minutes
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Batch fetch multiple weapons
|
|
*/
|
|
async getWeapons(ids: string[]): Promise<Weapon[]> {
|
|
// Fetch in parallel with individual caching
|
|
const promises = ids.map(id => this.getWeapon(id))
|
|
return Promise.all(promises)
|
|
}
|
|
|
|
/**
|
|
* Batch fetch multiple characters
|
|
*/
|
|
async getCharacters(ids: string[]): Promise<Character[]> {
|
|
const promises = ids.map(id => this.getCharacter(id))
|
|
return Promise.all(promises)
|
|
}
|
|
|
|
/**
|
|
* Batch fetch multiple summons
|
|
*/
|
|
async getSummons(ids: string[]): Promise<Summon[]> {
|
|
const promises = ids.map(id => this.getSummon(id))
|
|
return Promise.all(promises)
|
|
}
|
|
|
|
/**
|
|
* Gets weapon keys with optional filtering
|
|
*/
|
|
async getWeaponKeys(params?: WeaponKeyQueryParams): Promise<WeaponKey[]> {
|
|
const searchParams = new URLSearchParams()
|
|
if (params?.series !== undefined) searchParams.set('series', String(params.series))
|
|
if (params?.slot !== undefined) searchParams.set('slot', String(params.slot))
|
|
if (params?.group !== undefined) searchParams.set('group', String(params.group))
|
|
|
|
const queryString = searchParams.toString()
|
|
const url = queryString ? `/weapon_keys?${queryString}` : '/weapon_keys'
|
|
|
|
return this.request<WeaponKey[]>(url, {
|
|
method: 'GET',
|
|
cacheTTL: 3600000 // Cache for 1 hour - weapon keys rarely change
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Clears entity cache
|
|
*/
|
|
clearEntityCache(type?: 'weapons' | 'characters' | 'summons' | 'weapon_keys') {
|
|
if (type) {
|
|
this.clearCache(`/${type}`)
|
|
} else {
|
|
// Clear all entity caches
|
|
this.clearCache('/weapons')
|
|
this.clearCache('/characters')
|
|
this.clearCache('/summons')
|
|
this.clearCache('/weapon_keys')
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// Character Creation & Image Download Methods
|
|
// ============================================
|
|
|
|
/**
|
|
* Validates a character granblue_id by checking if images exist on GBF servers
|
|
* Requires editor role (>= 7)
|
|
*/
|
|
async validateCharacterGranblueId(granblueId: string): Promise<CharacterValidationResult> {
|
|
const response = await this.request<{
|
|
valid: boolean
|
|
granblue_id: string
|
|
exists_in_db: boolean
|
|
error?: string
|
|
image_urls?: {
|
|
main?: string
|
|
grid?: string
|
|
square?: string
|
|
}
|
|
}>(`/characters/validate/${granblueId}`, {
|
|
method: 'GET'
|
|
})
|
|
|
|
return {
|
|
valid: response.valid,
|
|
granblueId: response.granblue_id,
|
|
existsInDb: response.exists_in_db,
|
|
error: response.error,
|
|
imageUrls: response.image_urls
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a new character record
|
|
* Requires editor role (>= 7)
|
|
*/
|
|
async createCharacter(payload: CreateCharacterPayload): Promise<Character> {
|
|
return this.request<Character>('/characters', {
|
|
method: 'POST',
|
|
body: { character: payload }
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Updates an existing character record
|
|
* Requires editor role (>= 7)
|
|
*/
|
|
async updateCharacter(id: string, payload: Partial<CreateCharacterPayload>): Promise<Character> {
|
|
const result = await this.request<Character>(`/characters/${id}`, {
|
|
method: 'PATCH',
|
|
body: { character: payload }
|
|
})
|
|
// Invalidate cache for this character
|
|
this.clearCache(`/characters/${id}`)
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* Triggers async image download for a character
|
|
* Requires editor role (>= 7)
|
|
*/
|
|
async downloadCharacterImages(
|
|
characterId: string,
|
|
options?: { force?: boolean; size?: 'all' | string }
|
|
): Promise<{ status: string; characterId: string; message: string }> {
|
|
return this.request(`/characters/${characterId}/download_images`, {
|
|
method: 'POST',
|
|
body: { options }
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Downloads a single image for a character (synchronous)
|
|
* Requires editor role (>= 7)
|
|
* @param characterId - Character database ID
|
|
* @param size - Image size variant (main, grid, square, detail)
|
|
* @param transformation - Pose variant (01=Base, 02=MLB, 03=FLB, 04=Transcendence)
|
|
* @param force - Force re-download even if image exists
|
|
*/
|
|
async downloadCharacterImage(
|
|
characterId: string,
|
|
size: string,
|
|
transformation?: string,
|
|
force?: boolean
|
|
): Promise<{ success: boolean; error?: string }> {
|
|
return this.request(`/characters/${characterId}/download_image`, {
|
|
method: 'POST',
|
|
body: { size, transformation, force }
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Gets the status of an ongoing character image download
|
|
* Requires editor role (>= 7)
|
|
*/
|
|
async getCharacterDownloadStatus(characterId: string): Promise<CharacterDownloadStatus> {
|
|
const response = await this.request<{
|
|
status: string
|
|
progress?: number
|
|
images_downloaded?: number
|
|
images_total?: number
|
|
error?: string
|
|
character_id?: string
|
|
granblue_id?: string
|
|
images?: Record<string, string[]>
|
|
updated_at?: string
|
|
}>(`/characters/${characterId}/download_status`, {
|
|
method: 'GET'
|
|
})
|
|
|
|
return {
|
|
status: response.status as CharacterDownloadStatus['status'],
|
|
progress: response.progress,
|
|
imagesDownloaded: response.images_downloaded,
|
|
imagesTotal: response.images_total,
|
|
error: response.error,
|
|
characterId: response.character_id,
|
|
granblueId: response.granblue_id,
|
|
images: response.images,
|
|
updatedAt: response.updated_at
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// Summon Creation & Image Download Methods
|
|
// ============================================
|
|
|
|
/**
|
|
* Validates a summon granblue_id by checking if images exist on GBF servers
|
|
* Requires editor role (>= 7)
|
|
*/
|
|
async validateSummonGranblueId(granblueId: string): Promise<SummonValidationResult> {
|
|
const response = await this.request<{
|
|
valid: boolean
|
|
granblue_id: string
|
|
exists_in_db: boolean
|
|
error?: string
|
|
image_urls?: {
|
|
main?: string
|
|
grid?: string
|
|
square?: string
|
|
}
|
|
}>(`/summons/validate/${granblueId}`, {
|
|
method: 'GET'
|
|
})
|
|
|
|
return {
|
|
valid: response.valid,
|
|
granblueId: response.granblue_id,
|
|
existsInDb: response.exists_in_db,
|
|
error: response.error,
|
|
imageUrls: response.image_urls
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a new summon record
|
|
* Requires editor role (>= 7)
|
|
*/
|
|
async createSummon(payload: CreateSummonPayload): Promise<Summon> {
|
|
return this.request<Summon>('/summons', {
|
|
method: 'POST',
|
|
body: { summon: payload }
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Updates an existing summon record
|
|
* Requires editor role (>= 7)
|
|
*/
|
|
async updateSummon(id: string, payload: Partial<CreateSummonPayload>): Promise<Summon> {
|
|
const result = await this.request<Summon>(`/summons/${id}`, {
|
|
method: 'PATCH',
|
|
body: { summon: payload }
|
|
})
|
|
// Invalidate cache for this summon
|
|
this.clearCache(`/summons/${id}`)
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* Triggers async image download for a summon
|
|
* Requires editor role (>= 7)
|
|
*/
|
|
async downloadSummonImages(
|
|
summonId: string,
|
|
options?: { force?: boolean; size?: 'all' | string }
|
|
): Promise<{ status: string; summonId: string; message: string }> {
|
|
return this.request(`/summons/${summonId}/download_images`, {
|
|
method: 'POST',
|
|
body: { options }
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Downloads a single image for a summon (synchronous)
|
|
* Requires editor role (>= 7)
|
|
* @param summonId - Summon database ID
|
|
* @param size - Image size variant (main, grid, wide, square, detail)
|
|
* @param transformation - Pose variant (empty=Base, 02=ULB, 03=Trans1, 04=Trans5)
|
|
* @param force - Force re-download even if image exists
|
|
*/
|
|
async downloadSummonImage(
|
|
summonId: string,
|
|
size: string,
|
|
transformation?: string,
|
|
force?: boolean
|
|
): Promise<{ success: boolean; error?: string }> {
|
|
return this.request(`/summons/${summonId}/download_image`, {
|
|
method: 'POST',
|
|
body: { size, transformation, force }
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Gets the status of an ongoing summon image download
|
|
* Requires editor role (>= 7)
|
|
*/
|
|
async getSummonDownloadStatus(summonId: string): Promise<SummonDownloadStatus> {
|
|
const response = await this.request<{
|
|
status: string
|
|
progress?: number
|
|
images_downloaded?: number
|
|
images_total?: number
|
|
error?: string
|
|
summon_id?: string
|
|
granblue_id?: string
|
|
images?: Record<string, string[]>
|
|
updated_at?: string
|
|
}>(`/summons/${summonId}/download_status`, {
|
|
method: 'GET'
|
|
})
|
|
|
|
return {
|
|
status: response.status as SummonDownloadStatus['status'],
|
|
progress: response.progress,
|
|
imagesDownloaded: response.images_downloaded,
|
|
imagesTotal: response.images_total,
|
|
error: response.error,
|
|
summonId: response.summon_id,
|
|
granblueId: response.granblue_id,
|
|
images: response.images,
|
|
updatedAt: response.updated_at
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// Weapon Creation & Image Download Methods
|
|
// ============================================
|
|
|
|
/**
|
|
* Validates a weapon granblue_id by checking if images exist on GBF servers
|
|
* Requires editor role (>= 7)
|
|
*/
|
|
async validateWeaponGranblueId(granblueId: string): Promise<WeaponValidationResult> {
|
|
const response = await this.request<{
|
|
valid: boolean
|
|
granblue_id: string
|
|
exists_in_db: boolean
|
|
error?: string
|
|
image_urls?: {
|
|
main?: string
|
|
grid?: string
|
|
square?: string
|
|
}
|
|
}>(`/weapons/validate/${granblueId}`, {
|
|
method: 'GET'
|
|
})
|
|
|
|
return {
|
|
valid: response.valid,
|
|
granblueId: response.granblue_id,
|
|
existsInDb: response.exists_in_db,
|
|
error: response.error,
|
|
imageUrls: response.image_urls
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a new weapon record
|
|
* Requires editor role (>= 7)
|
|
*/
|
|
async createWeapon(payload: CreateWeaponPayload): Promise<Weapon> {
|
|
return this.request<Weapon>('/weapons', {
|
|
method: 'POST',
|
|
body: { weapon: payload }
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Updates an existing weapon record
|
|
* Requires editor role (>= 7)
|
|
*/
|
|
async updateWeapon(id: string, payload: Partial<CreateWeaponPayload>): Promise<Weapon> {
|
|
const result = await this.request<Weapon>(`/weapons/${id}`, {
|
|
method: 'PATCH',
|
|
body: { weapon: payload }
|
|
})
|
|
// Invalidate cache for this weapon
|
|
this.clearCache(`/weapons/${id}`)
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* Triggers async image download for a weapon
|
|
* Requires editor role (>= 7)
|
|
*/
|
|
async downloadWeaponImages(
|
|
weaponId: string,
|
|
options?: { force?: boolean; size?: 'all' | string }
|
|
): Promise<{ status: string; weaponId: string; message: string }> {
|
|
return this.request(`/weapons/${weaponId}/download_images`, {
|
|
method: 'POST',
|
|
body: { options }
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Downloads a single image for a weapon (synchronous)
|
|
* Requires editor role (>= 7)
|
|
* @param weaponId - Weapon database ID
|
|
* @param size - Image size variant (main, grid, square, base)
|
|
* @param transformation - Pose variant (empty=Base, 02=Trans1, 03=Trans5)
|
|
* @param force - Force re-download even if image exists
|
|
*/
|
|
async downloadWeaponImage(
|
|
weaponId: string,
|
|
size: string,
|
|
transformation?: string,
|
|
force?: boolean
|
|
): Promise<{ success: boolean; error?: string }> {
|
|
return this.request(`/weapons/${weaponId}/download_image`, {
|
|
method: 'POST',
|
|
body: { size, transformation, force }
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Gets the status of an ongoing weapon image download
|
|
* Requires editor role (>= 7)
|
|
*/
|
|
async getWeaponDownloadStatus(weaponId: string): Promise<WeaponDownloadStatus> {
|
|
const response = await this.request<{
|
|
status: string
|
|
progress?: number
|
|
images_downloaded?: number
|
|
images_total?: number
|
|
error?: string
|
|
weapon_id?: string
|
|
granblue_id?: string
|
|
images?: Record<string, string[]>
|
|
updated_at?: string
|
|
}>(`/weapons/${weaponId}/download_status`, {
|
|
method: 'GET'
|
|
})
|
|
|
|
return {
|
|
status: response.status as WeaponDownloadStatus['status'],
|
|
progress: response.progress,
|
|
imagesDownloaded: response.images_downloaded,
|
|
imagesTotal: response.images_total,
|
|
error: response.error,
|
|
weaponId: response.weapon_id,
|
|
granblueId: response.granblue_id,
|
|
images: response.images,
|
|
updatedAt: response.updated_at
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// Raw Data Methods (for database viewing)
|
|
// ============================================
|
|
|
|
/**
|
|
* Gets raw wiki and game data for a character
|
|
* This data is fetched separately to avoid bloating regular entity responses
|
|
* Note: BaseAdapter.request() automatically transforms snake_case to camelCase
|
|
*/
|
|
async getCharacterRawData(id: string): Promise<EntityRawData> {
|
|
// Response keys are already camelCase after BaseAdapter.transformResponse()
|
|
const response = await this.request<EntityRawData>(`/characters/${id}/raw`, {
|
|
method: 'GET'
|
|
})
|
|
|
|
return response
|
|
}
|
|
|
|
/**
|
|
* Gets raw wiki and game data for a weapon
|
|
* This data is fetched separately to avoid bloating regular entity responses
|
|
* Note: BaseAdapter.request() automatically transforms snake_case to camelCase
|
|
*/
|
|
async getWeaponRawData(id: string): Promise<EntityRawData> {
|
|
// Response keys are already camelCase after BaseAdapter.transformResponse()
|
|
const response = await this.request<EntityRawData>(`/weapons/${id}/raw`, {
|
|
method: 'GET'
|
|
})
|
|
|
|
return response
|
|
}
|
|
|
|
/**
|
|
* Gets raw wiki and game data for a summon
|
|
* This data is fetched separately to avoid bloating regular entity responses
|
|
* Note: BaseAdapter.request() automatically transforms snake_case to camelCase
|
|
*/
|
|
async getSummonRawData(id: string): Promise<EntityRawData> {
|
|
// Response keys are already camelCase after BaseAdapter.transformResponse()
|
|
const response = await this.request<EntityRawData>(`/summons/${id}/raw`, {
|
|
method: 'GET'
|
|
})
|
|
|
|
return response
|
|
}
|
|
|
|
// ============================================
|
|
// Wiki Fetch Methods (editor-only)
|
|
// ============================================
|
|
|
|
/**
|
|
* Fetches and stores wiki data for a character
|
|
* Requires editor role (>= 7)
|
|
*/
|
|
async fetchCharacterWiki(id: string): Promise<EntityRawData> {
|
|
return this.request<EntityRawData>(`/characters/${id}/fetch_wiki`, {
|
|
method: 'POST'
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Fetches and stores wiki data for a weapon
|
|
* Requires editor role (>= 7)
|
|
*/
|
|
async fetchWeaponWiki(id: string): Promise<EntityRawData> {
|
|
return this.request<EntityRawData>(`/weapons/${id}/fetch_wiki`, {
|
|
method: 'POST'
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Fetches and stores wiki data for a summon
|
|
* Requires editor role (>= 7)
|
|
*/
|
|
async fetchSummonWiki(id: string): Promise<EntityRawData> {
|
|
return this.request<EntityRawData>(`/summons/${id}/fetch_wiki`, {
|
|
method: 'POST'
|
|
})
|
|
}
|
|
|
|
// ============================================
|
|
// Batch Preview Methods (for batch import)
|
|
// ============================================
|
|
|
|
/**
|
|
* Fetches wiki data and suggestions for multiple character wiki pages
|
|
* Requires editor role (>= 7)
|
|
* @param wikiPages - Array of wiki page names (max 10)
|
|
*/
|
|
async batchPreviewCharacters(
|
|
wikiPages: string[]
|
|
): Promise<BatchPreviewResponse<CharacterSuggestions>> {
|
|
return this.request<BatchPreviewResponse<CharacterSuggestions>>('/characters/batch_preview', {
|
|
method: 'POST',
|
|
body: { wiki_pages: wikiPages }
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Fetches wiki data and suggestions for multiple weapon wiki pages
|
|
* Requires editor role (>= 7)
|
|
* @param wikiPages - Array of wiki page names (max 10)
|
|
*/
|
|
async batchPreviewWeapons(
|
|
wikiPages: string[]
|
|
): Promise<BatchPreviewResponse<WeaponSuggestions>> {
|
|
return this.request<BatchPreviewResponse<WeaponSuggestions>>('/weapons/batch_preview', {
|
|
method: 'POST',
|
|
body: { wiki_pages: wikiPages }
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Fetches wiki data and suggestions for multiple summon wiki pages
|
|
* Requires editor role (>= 7)
|
|
* @param wikiPages - Array of wiki page names (max 10)
|
|
*/
|
|
async batchPreviewSummons(
|
|
wikiPages: string[]
|
|
): Promise<BatchPreviewResponse<SummonSuggestions>> {
|
|
return this.request<BatchPreviewResponse<SummonSuggestions>>('/summons/batch_preview', {
|
|
method: 'POST',
|
|
body: { wiki_pages: wikiPages }
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Default entity adapter instance
|
|
*/
|
|
export const entityAdapter = new EntityAdapter(DEFAULT_ADAPTER_CONFIG)
|