import axios, { AxiosInstance, AxiosResponseHeaders, RawAxiosResponseHeaders, } from 'axios'; export type ServerMeta = { key: string; open: boolean; }; export type Tribe = { id: number; name: string; tag: string; numMembers: number; numVillages: number; points: number; allPoints: number; rank: number; dominance: number; rankAtt: number; scoreAtt: number; rankDef: number; scoreDef: number; rankTotal: number; scoreTotal: number; profileUrl: string; bestRank: number; bestRankAt: string; mostPoints: number; mostPointsAt: string; mostVillages: number; mostVillagesAt: string; createdAt: string; deletedAt: string | null; }; export type TribeMeta = { id: number; name: string; tag: string; profileUrl: string; }; export type Player = { id: number; points: number; rank: number; numVillages: number; scoreAtt: number; rankAtt: number; scoreDef: number; rankDef: number; scoreSup: number; rankSup: number; scoreTotal: number; rankTotal: number; bestRank: number; bestRankAt: string; mostPoints: number; mostPointsAt: string; mostVillages: number; mostVillagesAt: string; lastActivityAt: string; createdAt: string; tribe: TribeMeta | null; }; export type PlayerWithServer = Player & { server: ServerMeta; }; export type PlayerMeta = { id: number; name: string; profileUrl: string; tribe: TribeMeta | null; }; export type VillageMeta = { id: number; fullName: string; profileUrl: string; x: number; y: number; continent: string; player: PlayerMeta | null; }; export type TribeChange = { id: number; newTribe: TribeMeta | null; player: PlayerMeta; createdAt: string; }; export type Ennoblement = { id: number; points: number; newOwner: PlayerMeta | null; village: VillageMeta; createdAt: string; }; export type TribeSnapshot = { id: number; numMembers: number; numVillages: number; points: number; allPoints: number; rank: number; dominance: number; rankAtt: number; scoreAtt: number; rankDef: number; scoreDef: number; rankTotal: number; scoreTotal: number; date: string; }; export type PlayerSnapshot = { id: number; tribe: TribeMeta | null; points: number; numVillages: number; rank: number; rankAtt: number; rankDef: number; rankSup: number; rankTotal: number; scoreAtt: number; scoreDef: number; scoreSup: number; scoreTotal: number; date: string; }; export type ServerConfig = { speed: number; unitSpeed: number; snob: { maxDist: number; }; }; export type Unit = { speed: number; }; export type UnitInfo = { archer: Unit; axe: Unit; catapult: Unit; heavy: Unit; knight: Unit; light: Unit; marcher: Unit; militia: Unit; ram: Unit; snob: Unit; spear: Unit; spy: Unit; sword: Unit; }; export type ListResult = { data: T[]; total: number; }; export type ListTribeChangesQueryParams = { offset?: number; limit?: number; sort?: string[]; }; export type ListEnnoblementsParams = { offset?: number; limit?: number; sort?: string[]; }; export type ListTribeMembers = { offset?: number; limit?: number; sort?: string[]; }; export type ListSnapshotsParams = { offset?: number; limit?: number; sort?: string[]; }; export type ListPlayerOtherServersParams = { offset?: number; limit?: number; }; export class TWHelpClient { client: AxiosInstance; constructor(url: string, timeout = 10000) { this.client = axios.create({ baseURL: url, timeout, }); } public async serverConfig( version: string, server: string ): Promise { const resp = await this.client.get( `/api/v1/versions/${version}/servers/${server}/config` ); return resp.data.data; } public async unitInfo(version: string, server: string): Promise { const resp = await this.client.get( `/api/v1/versions/${version}/servers/${server}/unit-info` ); return resp.data.data; } public async tribe( version: string, server: string, id: number ): Promise { const resp = await this.client.get( `/api/v1/versions/${version}/servers/${server}/tribes/${id}` ); return resp.data.data; } public async tribeHistory( version: string, server: string, id: number, queryParams?: ListSnapshotsParams ): Promise> { const queryString = queryParams ? this.buildQueryString(queryParams) : ''; const resp = await this.client.get( `/api/v1/versions/${version}/servers/${server}/tribes/${id}/history?${queryString}` ); return { data: resp.data.data, total: this.parseTotal(resp.headers), }; } public async tribeTribeChanges( version: string, server: string, id: number, queryParams?: ListTribeChangesQueryParams ): Promise> { const queryString = queryParams ? this.buildQueryString(queryParams) : ''; const resp = await this.client.get( `/api/v1/versions/${version}/servers/${server}/tribes/${id}/tribe-changes?${queryString}` ); return { data: resp.data.data, total: this.parseTotal(resp.headers), }; } public async tribeEnnoblements( version: string, server: string, id: number, queryParams?: ListEnnoblementsParams ): Promise> { const queryString = queryParams ? this.buildQueryString(queryParams) : ''; const resp = await this.client.get( `/api/v1/versions/${version}/servers/${server}/tribes/${id}/ennoblements?${queryString}` ); return { data: resp.data.data, total: this.parseTotal(resp.headers), }; } public async tribeMembers( version: string, server: string, id: number, queryParams?: ListTribeMembers ): Promise> { const queryString = queryParams ? this.buildQueryString(queryParams) : ''; const resp = await this.client.get( `/api/v1/versions/${version}/servers/${server}/tribes/${id}/members?${queryString}` ); return { data: resp.data.data, total: this.parseTotal(resp.headers), }; } public async player( version: string, server: string, id: number ): Promise { const resp = await this.client.get( `/api/v1/versions/${version}/servers/${server}/players/${id}` ); return resp.data.data; } public async playerTribeChanges( version: string, server: string, id: number, queryParams?: ListTribeChangesQueryParams ): Promise> { const queryString = queryParams ? this.buildQueryString(queryParams) : ''; const resp = await this.client.get( `/api/v1/versions/${version}/servers/${server}/players/${id}/tribe-changes?${queryString}` ); return { data: resp.data.data, total: this.parseTotal(resp.headers), }; } public async playerEnnoblements( version: string, server: string, id: number, queryParams?: ListEnnoblementsParams ): Promise> { const queryString = queryParams ? this.buildQueryString(queryParams) : ''; const resp = await this.client.get( `/api/v1/versions/${version}/servers/${server}/players/${id}/ennoblements?${queryString}` ); return { data: resp.data.data, total: this.parseTotal(resp.headers), }; } public async playerHistory( version: string, server: string, id: number, queryParams?: ListSnapshotsParams ): Promise> { const queryString = queryParams ? this.buildQueryString(queryParams) : ''; const resp = await this.client.get( `/api/v1/versions/${version}/servers/${server}/players/${id}/history?${queryString}` ); return { data: resp.data.data, total: this.parseTotal(resp.headers), }; } public async playerOtherServers( version: string, server: string, id: number, queryParams?: ListPlayerOtherServersParams ): Promise> { const queryString = queryParams ? this.buildQueryString(queryParams) : ''; const resp = await this.client.get( `/api/v1/versions/${version}/servers/${server}/players/${id}/other-servers?${queryString}` ); return { data: resp.data.data, total: this.parseTotal(resp.headers), }; } public async villageEnnoblements( version: string, server: string, id: number, queryParams?: ListEnnoblementsParams ): Promise> { const queryString = queryParams ? this.buildQueryString(queryParams) : ''; const resp = await this.client.get( `/api/v1/versions/${version}/servers/${server}/villages/${id}/ennoblements?${queryString}` ); return { data: resp.data.data, total: this.parseTotal(resp.headers), }; } private buildQueryString( queryParams: Record ): string { const params = new URLSearchParams(); for (const [name, val] of Object.entries(queryParams)) { if (Array.isArray(val)) { val.forEach((s) => { params.append(name, s); }); continue; } if (val instanceof Date) { params.set(name, val.toISOString()); continue; } params.set(name, val.toString()); } return params.toString(); } private parseTotal( headers: RawAxiosResponseHeaders | AxiosResponseHeaders ): number { return parseInt(headers['x-total-count'] ?? '0'); } }