Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(v2): add new v2 endpoints #809

Merged
merged 4 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Torn Open API JSON
openapi.json

# Logs
logs
*.log
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,8 @@ if (TornAPI.isError(myBattleStats)) {
1. Clone this repo, `git clone https://github.com/jgolla/torn-api`
1. Install the dependencies, `npm install`
1. Build the library, `npm run build`

## Importing TornOpenAPI types

1. Download https://www.torn.com/swagger/openapi.json locally to the main directory
1. Run 'npm run gentypes'
49 changes: 49 additions & 0 deletions lib/Forum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { TornAPIBase } from './TornAPIBase';
import { Errorable, ForumCategories, ForumPosts, ForumThreadBase, ForumThreadExtended, PostContentType, Sort } from './Interfaces';

export class Forum extends TornAPIBase {
constructor(apiKey: string, comment: string) {
super(apiKey, comment);
}

/**
* It is possible to get threads from returned public categories
* @returns ForumCategories[]
*/
async categories(): Promise<Errorable<ForumCategories[]>> {
return this.apiQueryV2({ route: 'forum', selection: 'categories' });
}

/**
* Returns 20 posts per page for the specific thread.
* @param id Thread id
* @param offset
* @param cat PostContentType, plain | raw
* @returns ForumPosts
*/
async posts(id: number, offset: number, cat: PostContentType): Promise<Errorable<ForumPosts>> {
return this.apiQueryV2({ route: 'forum', selection: 'posts', id: id, cat: cat, offset: offset });
}

/**
* Return the details of a thread including topic content and poll (if any).
* @param id Thread id
* @returns ForumThreadExtended
*/
async thread(id: number): Promise<Errorable<ForumThreadExtended>> {
return this.apiQueryV2({ route: 'forum', selection: 'thread', id: id });
}

/**
* Returns a list of threads for the chosen forum category (or categories)
* @param ids The forum ID or a list of forum IDs
* @param limit Limits the number of returned results
* @param from Returns threads created after this timestamp
* @param to Returns threads created before this timestamp
* @param sort Sorted by the greatest of first_post_time and last_post_time timestamps, ASC | DESC
* @returns ForumThreadBase[]
*/
async threads(ids?: number[], limit?: number, from?: number, to?: number, sort?: Sort): Promise<Errorable<ForumThreadBase[]>> {
return this.apiQueryV2({ route: 'forum', selection: 'threads', ids: ids, limit: limit, to: to, from: from, sort: sort });
}
}
59 changes: 49 additions & 10 deletions lib/Interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { components } from './TornOpenAPI';

export enum ApiErrorCodes {
/**
* Unhandled error, should not occur.
Expand Down Expand Up @@ -578,16 +580,16 @@ export interface ICrimes {
}

export interface ICriminalRecord {
vandalism: number
theft: number
counterfeiting: number
fraud: number
illicitservices: number
cybercrime: number
extortion: number
illegalproduction: number
total: number
}
vandalism: number;
theft: number;
counterfeiting: number;
fraud: number;
illicitservices: number;
cybercrime: number;
extortion: number;
illegalproduction: number;
total: number;
}

export interface IDiscord {
userID: number;
Expand Down Expand Up @@ -1864,3 +1866,40 @@ export interface IAPIKeyInfo {
user: IAPIUserSelection[];
};
}

// V2
type ComponentBase = components['schemas'];
export type Sort = 'DESC' | 'ASC';

// forum
export type ForumThreadBase = ComponentBase['ForumThreadBase'];
export type ForumCategories = {
id?: ComponentBase['ForumId'];
title?: string;
acronym?: string;
threads?: number;
};
export type ForumThreadExtended = ComponentBase['ForumThreadExtended'];
export type PostContentType = 'raw' | 'plain';
export type ForumPosts = {
posts?: ComponentBase['ForumPost'][];
_links?: ComponentBase['RequestLinks'];
};

// racing
export type RaceCategory = 'official' | 'custom';
export type Race = ComponentBase['Race'];
export type RaceClassEnum = ComponentBase['RaceClassEnum'];
export type RaceRecord = ComponentBase['RaceRecord'];
export type RaceCar = ComponentBase['RaceCar'];
export type RaceCarUpgrade = ComponentBase['RaceCarUpgrade'];
export type RaceTrack = ComponentBase['RaceTrack'];
export type UserRaceCarDetails = ComponentBase['UserRaceCarDetails'];

// torn
export type Calendar = {
competitions?: ComponentBase['TornCalendarActivity'][];
events?: ComponentBase['TornCalendarActivity'][];
};
export type TornCrime = ComponentBase['TornCrime'];
export type TornSubcrime = ComponentBase['TornSubcrime'];
64 changes: 64 additions & 0 deletions lib/Racing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { TornAPIBase } from './TornAPIBase';
import { Errorable, Race, RaceCar, RaceCarUpgrade, RaceCategory, RaceClassEnum, RaceRecord, RaceTrack, Sort } from './Interfaces';

export class Racing extends TornAPIBase {
constructor(apiKey: string, comment: string) {
super(apiKey, comment);
}

/**
* Return the stat details about racing cars.
* @returns RaceCar[]
*/
async cars(): Promise<Errorable<RaceCar[]>> {
return this.apiQueryV2({ route: 'racing', selection: 'cars' });
}

/**
* Return the details about all possible car upgrades.
* @returns RaceCarUpgrade[]
*/
async carupgrades(): Promise<Errorable<RaceCarUpgrade[]>> {
return this.apiQueryV2({ route: 'racing', selection: 'carupgrades' });
}

/**
* Returns a list of races, ordered by race start timestamp
* @param limit
* @param from Timestamp after when started races are returned (scheduled.start)
* @param to Timestamp until when started races are returned (schedule.start)
* @param sort Sorted by schedule.start field, ASC | DESC
* @param cat Category of races returned, official | custom
* @returns Race[]
*/
async races(limit?: number, from?: number, to?: number, sort?: Sort, cat?: RaceCategory): Promise<Errorable<Race[]>> {
return this.apiQueryV2({ route: 'racing', selection: 'races', limit: limit, to: to, from: from, sort: sort, cat: cat });
}

/**
* Return the details of a race.
* @param id Race id
* @returns Race
*/
async race(id: number): Promise<Errorable<Race>> {
return this.apiQueryV2({ route: 'racing', selection: 'race', id: id });
}

/**
* Returns a list of 10 best lap records for the chosen track and car class. Results are cached globally 1 hour
* @param id Track id
* @param cat Car class, A | B | C | D | E
* @returns RaceRecord[]
*/
async records(id: number, cat: RaceClassEnum): Promise<Errorable<RaceRecord[]>> {
return this.apiQueryV2({ route: 'racing', selection: 'records', id: id, cat: cat });
}

/**
* Return the details about racing tracks.
* @returns RaceTrack[]
*/
async tracks(): Promise<Errorable<RaceTrack[]>> {
return this.apiQueryV2({ route: 'racing', selection: 'tracks' });
}
}
33 changes: 29 additions & 4 deletions lib/Torn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ import {
IItemDetails,
ISearchForCashCrimeStatus,
IShopliftingCrimeStatus,
ITornDirtyBomb
ITornDirtyBomb,
Calendar,
TornCrime,
TornSubcrime
} from './Interfaces';
import { TornAPIBase } from './TornAPIBase';

Expand All @@ -48,6 +51,14 @@ export class Torn extends TornAPIBase {
return this.apiQuery({ route: 'torn', selection: 'bank' });
}

/**
* Get the details about competitions & events in the running year.
* @returns Calendar
*/
async calendar(): Promise<Errorable<Calendar>> {
return this.apiQueryV2({ route: 'torn', selection: 'calendar' });
}

async cards(): Promise<Errorable<ICard[]>> {
return this.apiQueryToArray({ route: 'torn', selection: 'cards' }, 'id');
}
Expand Down Expand Up @@ -114,6 +125,14 @@ export class Torn extends TornAPIBase {
}
}

/**
* Return the details about released crimes.
* @returns TornCrime[]
*/
async crimes(): Promise<Errorable<TornCrime[]>> {
return this.apiQueryV2({ route: 'torn', selection: 'crimes' });
}

async dirtybombs(): Promise<Errorable<ITornDirtyBomb[]>> {
return this.apiQueryToArray({ route: 'torn', selection: 'dirtybombs' });
}
Expand Down Expand Up @@ -267,7 +286,6 @@ export class Torn extends TornAPIBase {

/**
* Gets the Search For Cash crime status
*
* @returns ISearchForCashCrimeStatus object
*/
async searchforcash(): Promise<Errorable<ISearchForCashCrimeStatus>> {
Expand All @@ -276,7 +294,6 @@ export class Torn extends TornAPIBase {

/**
* Gets the Shoplifting crime status
*
* @returns IShopliftingCrimeStatus object
*/
async shoplifting(): Promise<Errorable<IShopliftingCrimeStatus>> {
Expand Down Expand Up @@ -308,13 +325,21 @@ export class Torn extends TornAPIBase {
}
}

/**
* Return the details about possible actions for a specific crime.
* @param id Crime id
* @returns TornSubcrime[]
*/
async subcrimes(id: number): Promise<Errorable<TornSubcrime[]>> {
return this.apiQueryV2({ route: 'torn', selection: 'subcrimes', id: id });
}

async timestamp(): Promise<Errorable<number>> {
return this.apiQuery({ route: 'torn', selection: 'timestamp' });
}

/**
* Gets an array of ITerritoryDetail for the specified input territory list.
*
* @param terriorties Comma separated list of territories to get the details for. Max 50
* @returns An array of ITerritoryDetail
*/
Expand Down
73 changes: 72 additions & 1 deletion lib/TornAPIBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,33 @@ export abstract class TornAPIBase {
return TornAPIBase.GenericAPIError;
}

protected async apiQueryV2<T>(params: QueryParams): Promise<T | ITornApiError> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const response = await axios.get<any>(this.buildUriV2(params));
if (response instanceof Error) {
return { code: 0, error: response.message };
} else {
if (response.data && response.data.error) {
return response.data.error;
} else if (response.data) {
let jsonSelection = response.data;
if (params.jsonOverride) {
jsonSelection = response.data[params.jsonOverride];
} else if (params.selection) {
jsonSelection = response.data[params.selection];
}

if (jsonSelection) {
return jsonSelection;
} else {
return response.data;
}
}
}

return TornAPIBase.GenericAPIError;
}

protected async apiQueryToArray<T>(params: QueryParams, keyField?: string): Promise<T[] | ITornApiError> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const response = await axios.get<any>(this.buildUri(params));
Expand Down Expand Up @@ -148,6 +175,46 @@ export abstract class TornAPIBase {
return url.toString();
}

protected buildUriV2(params: QueryParams): string {
const url = new URL(`v2/${params.route}/`, `https://api.torn.com`);
url.searchParams.set('selections', params.selection);
url.searchParams.set('key', this.apiKey);

if (params.id) {
url.searchParams.set('id', params.id.toString());
}

if (params.ids) {
params.ids.forEach((id) => {
url.searchParams.append('id', id.toString());
});
}

if (params.additionalSelections) {
for (const key in params.additionalSelections) {
url.searchParams.set(key, params.additionalSelections[key]);
}
}

if (params.from) {
url.searchParams.set('from', params.from.toString());
}

if (params.to) {
url.searchParams.set('to', params.to.toString());
}

if (params.limit) {
url.searchParams.set('limit', params.limit.toString());
}

if (params.timestamp) {
url.searchParams.set('timestamp', params.timestamp.toString());
}

return url.toString();
}

protected async multiQuery<T>(route: string, endpoints: string[], id?: string): Promise<ITornApiError | Record<string, T>> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const response = await axios.get<any>(this.buildUri({ route: route, selection: endpoints.join(','), id: id }));
Expand All @@ -168,11 +235,15 @@ export abstract class TornAPIBase {
interface QueryParams {
route: string;
selection: string;
id?: string;
id?: string | number;
ids?: number[];
jsonOverride?: string;
from?: number;
to?: number;
limit?: number;
timestamp?: number;
additionalSelections?: Record<string, string>;
sort?: string;
cat?: string;
offset?: number;
}
Loading