break up library, move bots to their own repositories
This commit is contained in:
23
packages/eve/README.md
Normal file
23
packages/eve/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# tsdown-starter
|
||||
|
||||
A starter for creating a TypeScript package.
|
||||
|
||||
## Development
|
||||
|
||||
- Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
- Run the unit tests:
|
||||
|
||||
```bash
|
||||
npm run test
|
||||
```
|
||||
|
||||
- Build the library:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
29
packages/eve/fixtures/markdown/test-data-colors.json
Normal file
29
packages/eve/fixtures/markdown/test-data-colors.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"colors": ["red", "blue", "green", "yellow"],
|
||||
"testText": {
|
||||
"simple": "Hello World",
|
||||
"multiline": "Line 1\nLine 2\nLine 3",
|
||||
"withSpecialChars": "Text with !@#$%^&*()_+-=[]{}|;':\",./<>?",
|
||||
"empty": "",
|
||||
"unicode": "Unicode: 🌟 ❤️ 🔥",
|
||||
"code": "function test() { return 'hello'; }"
|
||||
},
|
||||
"expected": {
|
||||
"red": {
|
||||
"simple": "```ansi\n\u001b[2;31mHello World\u001b[0m```\n",
|
||||
"empty": "```ansi\n\u001b[2;31m\u001b[0m```\n"
|
||||
},
|
||||
"blue": {
|
||||
"simple": "```ansi\n\u001b[2;32m\u001b[2;36m\u001b[2;34mHello World\u001b[0m\u001b[2;36m\u001b[0m\u001b[2;32m\u001b[0m```\n",
|
||||
"empty": "```ansi\n\u001b[2;32m\u001b[2;36m\u001b[2;34m\u001b[0m\u001b[2;36m\u001b[0m\u001b[2;32m\u001b[0m```\n"
|
||||
},
|
||||
"green": {
|
||||
"simple": "```ansi\n\u001b[2;36mHello World\u001b[0m```\n",
|
||||
"empty": "```ansi\n\u001b[2;36m\u001b[0m```\n"
|
||||
},
|
||||
"yellow": {
|
||||
"simple": "```ansi\n\u001b[2;33mHello World\u001b[0m```\n",
|
||||
"empty": "```ansi\n\u001b[2;33m\u001b[0m```\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
41
packages/eve/fixtures/markdown/test-data-markup.json
Normal file
41
packages/eve/fixtures/markdown/test-data-markup.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"boldMarkup": {
|
||||
"complete": "<b>bold text</b>",
|
||||
"openOnly": "<b>bold text",
|
||||
"closeOnly": "bold text</b>",
|
||||
"nested": "<b>outer <b>inner</b> text</b>",
|
||||
"empty": "<b></b>",
|
||||
"multiple": "<b>first</b> and <b>second</b>",
|
||||
"mixed": "<b>bold</b> with <i>italic</i> text"
|
||||
},
|
||||
"italicMarkup": {
|
||||
"complete": "<i>italic text</i>",
|
||||
"openOnly": "<i>italic text",
|
||||
"closeOnly": "italic text</i>",
|
||||
"nested": "<i>outer <i>inner</i> text</i>",
|
||||
"empty": "<i></i>",
|
||||
"multiple": "<i>first</i> and <i>second</i>",
|
||||
"mixed": "<i>italic</i> with <b>bold</b> text"
|
||||
},
|
||||
"colorTags": {
|
||||
"hex6": "<color=0xFF5733>colored text</color>",
|
||||
"hex8": "<color=0xFF5733AA>colored text</color>",
|
||||
"hexWithoutPrefix": "<color=FF5733>colored text</color>",
|
||||
"namedColor": "<color=red>colored text</color>",
|
||||
"nested": "<color=blue>outer <color=red>inner</color> text</color>",
|
||||
"empty": "<color=green></color>",
|
||||
"multiple": "<color=red>first</color> and <color=blue>second</color>"
|
||||
},
|
||||
"eveLinks": {
|
||||
"simple": "<a href=showinfo:587>Rifter</a>",
|
||||
"withSpaces": "<a href=showinfo:12345>Ship Name With Spaces</a>",
|
||||
"multiple": "<a href=showinfo:587>Rifter</a> and <a href=showinfo:588>Merlin</a>",
|
||||
"nested": "Check out <a href=showinfo:587>Rifter</a> for PvP",
|
||||
"empty": "<a href=showinfo:587></a>"
|
||||
},
|
||||
"combined": {
|
||||
"allMarkup": "<b>Bold</b> <i>italic</i> <color=red>colored</color> <a href=showinfo:587>linked</a>",
|
||||
"nestedComplex": "<b><color=blue><a href=showinfo:587>Bold Blue Rifter</a></color></b>",
|
||||
"realWorldExample": "The <b><color=0xFF5733>Rifter</color></b> is a <i>fast</i> <a href=showinfo:587>frigate</a> used in PvP."
|
||||
}
|
||||
}
|
||||
34
packages/eve/fixtures/markdown/test-data-time.json
Normal file
34
packages/eve/fixtures/markdown/test-data-time.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"milliseconds": {
|
||||
"zero": 0,
|
||||
"oneSecond": 1000,
|
||||
"oneMinute": 60000,
|
||||
"oneHour": 3600000,
|
||||
"complex": 3661500,
|
||||
"daysWorthMs": 86400000,
|
||||
"fractionalSeconds": 1500,
|
||||
"smallFraction": 100
|
||||
},
|
||||
"seconds": {
|
||||
"zero": 0,
|
||||
"oneSecond": 1,
|
||||
"oneMinute": 60,
|
||||
"oneHour": 3600,
|
||||
"complex": 3661,
|
||||
"daysWorthSec": 86400,
|
||||
"fractionalInput": 3661.5
|
||||
},
|
||||
"expected": {
|
||||
"zero": "0.0s",
|
||||
"oneSecond": "1.0s",
|
||||
"oneMinute": "1m",
|
||||
"oneHour": "1h",
|
||||
"complexMs": "1h 1m 1.5s",
|
||||
"complexSec": "1h 1m 1s",
|
||||
"daysMs": "24h",
|
||||
"daysSec": "24h",
|
||||
"fractionalSeconds": "1.5s",
|
||||
"smallFraction": "0.1s",
|
||||
"fractionalInputSec": "1h 1m 1s"
|
||||
}
|
||||
}
|
||||
115
packages/eve/package.json
Normal file
115
packages/eve/package.json
Normal file
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"name": "@star-kitten/eve",
|
||||
"version": "0.0.1",
|
||||
"description": "Star Kitten EVE Library.",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://git.f302.me/jb/star-kitten#readme",
|
||||
"bugs": {
|
||||
"url": "https://git.f302.me/jb/star-kitten/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://git.f302.me/jb/star-kitten.git"
|
||||
},
|
||||
"author": "JB <j-b-3.deviate267@passmail.net>",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index*.d.ts"
|
||||
},
|
||||
"./esi": {
|
||||
"import": "./dist/esi/index.js",
|
||||
"types": "./dist/esi/index*.d.ts",
|
||||
"require": "./dist/esi/index.js"
|
||||
},
|
||||
"./db": {
|
||||
"import": "./dist/db/index.js",
|
||||
"types": "./dist/db/index*.d.ts",
|
||||
"require": "./dist/db/index.js"
|
||||
},
|
||||
"./ref": {
|
||||
"import": "./dist/ref/index.js",
|
||||
"types": "./dist/ref/index*.d.ts",
|
||||
"require": "./dist/ref/index.js"
|
||||
},
|
||||
"./third-party/janice.js": {
|
||||
"import": "./dist/third-party/janice.js",
|
||||
"types": "./dist/types/third-party/janice.d.ts",
|
||||
"require": "./dist/third-party/janice.js"
|
||||
},
|
||||
"./models": {
|
||||
"import": "./dist/models/index.js",
|
||||
"types": "./dist/models/index*.d.ts",
|
||||
"require": "./dist/models/index.js"
|
||||
},
|
||||
"./data/*": "./data/*",
|
||||
"./discord": {
|
||||
"import": "./dist/discord/index.js",
|
||||
"require": "./dist/discord/index.js",
|
||||
"types": "./dist/types/discord/index.d.ts"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.5",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/jwk-to-pem": "^2.0.3",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/node": "^22.15.17",
|
||||
"@types/node-cache": "^4.2.5",
|
||||
"@types/stream-chain": "^2.1.0",
|
||||
"@types/stream-json": "^1.7.8",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"bumpp": "^10.1.0",
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"openapi-fetch": "^0.15.0",
|
||||
"openapi-typescript": "^7.10.1",
|
||||
"prettier-plugin-multiline-arrays": "^4.0.3",
|
||||
"tsdown": "^0.14.2",
|
||||
"typescript": "beta"
|
||||
},
|
||||
"dependencies": {
|
||||
"@star-kitten/util": "link:@star-kitten/util",
|
||||
"@orama/orama": "^3.1.13",
|
||||
"@oslojs/encoding": "^1.1.0",
|
||||
"cron-parser": "^5.3.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"drizzle-orm": "^0.44.5",
|
||||
"elysia": "^1.4.20",
|
||||
"fp-filters": "^0.5.4",
|
||||
"html-dom-parser": "^5.1.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jwk-to-pem": "^2.0.7",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"node-cache": "^5.1.2",
|
||||
"stream-chain": "^3.4.0",
|
||||
"stream-json": "^1.9.1",
|
||||
"winston": "^3.17.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
"dev": "tsdown --watch",
|
||||
"link": "bun link",
|
||||
"test": "bun test",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"release": "bumpp && npm publish",
|
||||
"generate-migrations": "bunx drizzle-kit generate --dialect sqlite --schema ./src/db/schema.ts",
|
||||
"migrate": "bun run ./src/db/migrate.ts",
|
||||
"everef-api": "bunx openapi-typescript https://raw.githubusercontent.com/autonomouslogic/eve-ref/refs/heads/main/spec/eve-ref-api.yaml -o src/eve/everef/schema.d.ts",
|
||||
"get-data": "bun run refresh:reference-data && bun run refresh:hoboleaks",
|
||||
"refresh:reference-data": "bun run scripts/download-and-extract.ts https://data.everef.net/reference-data/reference-data-latest.tar.xz ./data/reference-data",
|
||||
"refresh:hoboleaks": "bun run scripts/download-and-extract.ts https://data.everef.net/hoboleaks-sde/hoboleaks-sde-latest.tar.xz ./data/hoboleaks",
|
||||
"static-export": "bun run scripts/export-solar-systems.ts"
|
||||
}
|
||||
}
|
||||
59
packages/eve/src/esi/alliance.ts
Normal file
59
packages/eve/src/esi/alliance.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* EVE Swagger Interface - Alliance Endpoints
|
||||
* https://developers.eveonline.com/api-explorer#/operations/GetAlliances
|
||||
*/
|
||||
import { esiFetch, type PublicEsiOptions } from './util/fetch';
|
||||
|
||||
/**
|
||||
* List all active player alliances
|
||||
* - This route is cached for an hour
|
||||
* @returns {number[]} - An array of all active player alliance ids
|
||||
*/
|
||||
export async function listAlliances(options?: PublicEsiOptions): Promise<number[]> {
|
||||
return await esiFetch<number[]>('/alliances/', options);
|
||||
}
|
||||
|
||||
interface AllianceInfo {
|
||||
creator_corporation_id: number;
|
||||
creator_id: number;
|
||||
date_founded: string;
|
||||
executor_corporation_id: number;
|
||||
faction_id: number;
|
||||
name: string;
|
||||
ticker: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about a specific alliance
|
||||
* - This route is cached for an hour
|
||||
* @param alliance_id Alliance id
|
||||
* @returns {AllianceInfo}
|
||||
*/
|
||||
export async function getAllianceInformation(alliance_id: number, options?: PublicEsiOptions): Promise<Partial<AllianceInfo>> {
|
||||
return await esiFetch<Partial<AllianceInfo>>(`/alliances/${alliance_id}/`, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all corporations in an alliance
|
||||
* - This route is cached for an hour
|
||||
* @param alliance_id Alliance id
|
||||
* @returns {number[]} - Array of corporation ids
|
||||
*/
|
||||
export async function listAllianceCorporations(alliance_id: number, options?: PublicEsiOptions): Promise<number[]> {
|
||||
return await esiFetch<number[]>(`/alliances/${alliance_id}/corporations/`, options);
|
||||
}
|
||||
|
||||
interface AllianceIcon {
|
||||
px128x128: string;
|
||||
px64x64: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get alliance icon
|
||||
* - This route is cached for an hour
|
||||
* @param alliance_id Alliance id
|
||||
* @returns {AllianceIcon}
|
||||
*/
|
||||
export async function getAllianceIcon(alliance_id: number, options?: PublicEsiOptions): Promise<Partial<AllianceIcon>> {
|
||||
return await esiFetch<Partial<AllianceIcon>>(`/alliances/${alliance_id}/icons/`, options);
|
||||
}
|
||||
132
packages/eve/src/esi/assets.ts
Normal file
132
packages/eve/src/esi/assets.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* EVE ESI Assets API
|
||||
* https://developers.eveonline.com/api-explorer#/operations/GetCharactersCharacterIdAssets
|
||||
*/
|
||||
import { ESI_SCOPE } from '../oauth';
|
||||
import { ESI_RATE_LIMIT_GROUP } from './util/rate-limits';
|
||||
import { checkScopesAndGetCharacterId, esiFetch, type EsiOptions } from './util/fetch';
|
||||
import type { LocationFlag } from './types/location-flag';
|
||||
|
||||
export enum AssetLocationType {
|
||||
STATION = 'station',
|
||||
SOLAR_SYSTEM = 'solar_system',
|
||||
ITEM = 'item',
|
||||
OTHER = 'other',
|
||||
}
|
||||
|
||||
export interface Asset {
|
||||
is_blueprint_copy: boolean;
|
||||
is_singleton: boolean;
|
||||
item_id: number;
|
||||
location_flag: LocationFlag;
|
||||
location_id: number;
|
||||
location_type: AssetLocationType;
|
||||
quantity: number;
|
||||
type_id: number;
|
||||
}
|
||||
|
||||
export function getCharacterAssets(options: EsiOptions, page: number = 1): Promise<Partial<Asset>[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-assets.read_assets.v1']);
|
||||
return esiFetch<Partial<Asset>[]>(`/characters/${character_id}/assets/?page=${page}`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_ASSET,
|
||||
});
|
||||
}
|
||||
|
||||
export interface AssetLocation {
|
||||
item_id: number;
|
||||
position: {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
};
|
||||
}
|
||||
|
||||
export function getCharacterAssetLocations(options: EsiOptions, ids: number[]): any[] | Promise<Partial<AssetLocation>[]> {
|
||||
if (ids.length === 0) return [];
|
||||
if (ids.length > 1000) throw 'Maximum of 1000 IDs can be requested at once';
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-assets.read_assets.v1']);
|
||||
|
||||
return esiFetch<Partial<AssetLocation>[]>(`/characters/${character_id}/assets/locations/`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(ids),
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_ASSET,
|
||||
});
|
||||
}
|
||||
|
||||
export interface AssetNames {
|
||||
item_id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function getCharacterAssetNames(options: EsiOptions, ids: number[]): any[] | Promise<Partial<AssetNames>[]> {
|
||||
if (ids.length === 0) return [];
|
||||
if (ids.length > 1000) throw 'Maximum of 1000 IDs can be requested at once';
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-assets.read_assets.v1']);
|
||||
return esiFetch<Partial<AssetNames>[]>(`/characters/${character_id}/assets/names/`, {
|
||||
...options,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(ids),
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_ASSET,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CorpAsset {
|
||||
is_blueprint_copy: boolean;
|
||||
is_singleton: boolean;
|
||||
item_id: number;
|
||||
location_flag: LocationFlag;
|
||||
location_id: number;
|
||||
location_type: 'station' | 'solar_system' | 'item' | 'other';
|
||||
quantity: number;
|
||||
type_id: number;
|
||||
}
|
||||
|
||||
export async function getCorporationAssets(options: EsiOptions, corporation_id: number, page: number = 1): Promise<Partial<CorpAsset>[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-assets.read_corporation_assets.v1']);
|
||||
return await esiFetch<Partial<CorpAsset>[]>(`/corporations/${corporation_id}/assets/?page=${page}`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_ASSET,
|
||||
});
|
||||
}
|
||||
|
||||
export interface AssetLocation {
|
||||
item_id: number;
|
||||
position: {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
};
|
||||
}
|
||||
|
||||
export async function getCorporationAssetLocations(
|
||||
options: EsiOptions,
|
||||
corporation_id: number,
|
||||
ids: number[],
|
||||
): Promise<Partial<AssetLocation>[]> {
|
||||
if (ids.length === 0) return [];
|
||||
if (ids.length > 1000) throw 'Maximum of 1000 IDs can be requested at once';
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-assets.read_corporation_assets.v1']);
|
||||
return await esiFetch<Partial<AssetLocation>[]>(`/corporations/${corporation_id}/assets/locations/`, {
|
||||
...options,
|
||||
method: 'POST',
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_ASSET,
|
||||
});
|
||||
}
|
||||
|
||||
export interface AssetNames {
|
||||
item_id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export async function getCorporationAssetNames(options: EsiOptions, id: number, ids: number[]): Promise<Partial<AssetNames>[]> {
|
||||
if (ids.length === 0) return [];
|
||||
if (ids.length > 1000) throw 'Maximum of 1000 IDs can be requested at once';
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-assets.read_corporation_assets.v1']);
|
||||
return await esiFetch<Partial<AssetNames>[]>(`/corporations/${id}/assets/names/`, {
|
||||
...options,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(ids),
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_ASSET,
|
||||
});
|
||||
}
|
||||
117
packages/eve/src/esi/calendar.ts
Normal file
117
packages/eve/src/esi/calendar.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* EVE ESI Caldendar Module
|
||||
*
|
||||
* This module provides functions to interact with EVE Online's ESI Calendar API,
|
||||
* allowing retrieval and management of calendar events for characters.
|
||||
*
|
||||
* ref: https://developers.eveonline.com/api-explorer#/operations/GetCharactersCharacterIdCalendar
|
||||
*/
|
||||
import { checkScopesAndGetCharacterId, esiFetch, type EsiOptions } from './util/fetch';
|
||||
import { tokenHasScopes } from '../oauth/eve-auth';
|
||||
import { ESI_SCOPE } from '../oauth';
|
||||
import { ESI_RATE_LIMIT_GROUP } from './util/rate-limits';
|
||||
|
||||
export interface CalendarEvent {
|
||||
event_date: string; // date-time string
|
||||
event_id: number;
|
||||
event_response: 'accepted' | 'declined' | 'tentative' | 'no_response';
|
||||
importance: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* List calendar event summaries for a character.
|
||||
*
|
||||
* Get 50 event summaries from the calendar. If no from_event ID is given, the resource will
|
||||
* return the next 50 chronological event summaries from now. If a from_event ID is
|
||||
* specified, it will return the next 50 chronological event summaries from after that event
|
||||
* - cached for 5 seconds
|
||||
*
|
||||
* @param options EsiOptions
|
||||
* @param from_event Event from which to get the next 50 chronological event summaries
|
||||
* @returns {Partial<CalendarEvent>[]}
|
||||
*/
|
||||
export async function listCalendarEventSummaries(options: EsiOptions, from_event?: number): Promise<Partial<CalendarEvent>[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-calendar.read_calendar_events.v1']);
|
||||
return await esiFetch<Partial<CalendarEvent>[]>(`/characters/${character_id}/calendar/${from_event ?? '?from_event=' + from_event}`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_SOCIAL,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CalendarEventDetails {
|
||||
date: string; // date-time string
|
||||
duration: number; // in minutes
|
||||
event_id: number;
|
||||
importance: number;
|
||||
owner_id: number;
|
||||
owner_name: string;
|
||||
owner_type: 'eve_server' | 'corporation' | 'faction' | 'alliance' | 'character';
|
||||
response: 'accepted' | 'declined' | 'tentative' | 'no_response';
|
||||
text: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an event's details by its ID.
|
||||
*
|
||||
* Get all the information for a specific event.
|
||||
* - cached for 5 seconds
|
||||
*
|
||||
* @param options EsiOptions
|
||||
* @param event_id Event Id
|
||||
* @returns {Partial<CalendarEventDetails>}
|
||||
*/
|
||||
export async function getEventDetails(options: EsiOptions, event_id: number): Promise<Partial<CalendarEventDetails>> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-calendar.read_calendar_events.v1']);
|
||||
return await esiFetch<Partial<CalendarEventDetails>>(`/characters/${character_id}/calendar/${event_id}/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_SOCIAL,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to a calendar event.
|
||||
*
|
||||
* Accept, decline, or tentatively accept an event invitation.
|
||||
*
|
||||
* @param options EsiOptions
|
||||
* @param event_id Event Id
|
||||
* @param response Response: 'accepted' | 'declined' | 'tentative'
|
||||
*/
|
||||
export async function respondToEvent(
|
||||
options: EsiOptions,
|
||||
event_id: number,
|
||||
response: 'accepted' | 'declined' | 'tentative',
|
||||
): Promise<void> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-calendar.respond_calendar_events.v1']);
|
||||
return await esiFetch<void>(`/characters/${character_id}/calendar/${event_id}/`, {
|
||||
...options,
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ response }),
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_SOCIAL,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CalendarEventAttendee {
|
||||
character_id: number;
|
||||
event_response: 'accepted' | 'declined' | 'tentative' | 'no_response';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attendees of a calendar event.
|
||||
*
|
||||
* Get the list of attendees for a specific event.
|
||||
* - cached for 5 seconds
|
||||
*
|
||||
* @param options EsiOptions
|
||||
* @param event_id Event Id
|
||||
* @returns {Partial<CalendarEventAttendee>[]}
|
||||
*/
|
||||
export async function getEventAttendees(options: EsiOptions, event_id: number): Promise<Partial<CalendarEventAttendee>[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-calendar.read_calendar_events.v1']);
|
||||
return await esiFetch<Partial<CalendarEventAttendee>[]>(`/characters/${character_id}/calendar/${event_id}/attendees/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_SOCIAL,
|
||||
});
|
||||
}
|
||||
223
packages/eve/src/esi/character.ts
Normal file
223
packages/eve/src/esi/character.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
import { ESI_SCOPE } from '../oauth';
|
||||
import { ESI_RATE_LIMIT_GROUP } from './util/rate-limits';
|
||||
import type { NotificationType } from './types/notification-type';
|
||||
import { checkScopesAndGetCharacterId, esiFetch, type EsiOptions, type PublicEsiOptions } from './util/fetch';
|
||||
import type { Blueprint } from './types/shared';
|
||||
export interface CharacterAffiliations {
|
||||
character_id: number;
|
||||
corporation_id: number;
|
||||
alliance_id?: number;
|
||||
faction_id?: number;
|
||||
}
|
||||
export function getCharacterAffiliations(
|
||||
character_ids: number[],
|
||||
options?: PublicEsiOptions,
|
||||
): any[] | Promise<Partial<CharacterAffiliations>[]> {
|
||||
if (character_ids.length === 0) return [];
|
||||
if (character_ids.length > 1000) throw 'Maximum of 1000 character IDs can be requested at once';
|
||||
return esiFetch<Partial<CharacterAffiliations>[]>(`/characters/affiliation/`, {
|
||||
...options,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(character_ids),
|
||||
});
|
||||
}
|
||||
|
||||
export interface CharacterData {
|
||||
alliance_id: number;
|
||||
birthday: string;
|
||||
bloodline_id: number;
|
||||
corporation_id: number;
|
||||
description: string;
|
||||
faction_id: number;
|
||||
gender: 'male' | 'female';
|
||||
name: string;
|
||||
race_id: number;
|
||||
security_status: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export function getCharacterPublicData(id: number, options?: PublicEsiOptions): Promise<Partial<CharacterData>> {
|
||||
return esiFetch<Partial<CharacterData>>(`/characters/${id}/`, options);
|
||||
}
|
||||
|
||||
export interface AgentResearch {
|
||||
agent_id: number;
|
||||
points_per_day: number;
|
||||
remainder_points: number;
|
||||
skill_type_id: number;
|
||||
started_at: string;
|
||||
}
|
||||
|
||||
// required scope: esi-characters.read_agents_research.v1
|
||||
export function getCharacterAgentResearch(options: EsiOptions): Promise<Partial<AgentResearch>[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-characters.read_agents_research.v1']);
|
||||
return esiFetch<Partial<AgentResearch>[]>(`/characters/${character_id}/agents_research/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_INDUSTRY,
|
||||
});
|
||||
}
|
||||
|
||||
// required scope: esi-characters.read_blueprints.v1
|
||||
export function getCharacterBlueprints(options: EsiOptions, page: number = 1): Promise<Partial<Blueprint>[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-characters.read_blueprints.v1']);
|
||||
return esiFetch<Partial<Blueprint>[]>(`/characters/${character_id}/blueprints/?page=${page}`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_INDUSTRY,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CharacterCorporationHistory {
|
||||
corporation_id: number;
|
||||
is_deleted: boolean;
|
||||
record_id: number; // An incrementing ID that can be used to order records where start_date is ambiguous
|
||||
start_date: string;
|
||||
}
|
||||
|
||||
export function getCharacterCorporationHistory(options: EsiOptions): Promise<Partial<CharacterCorporationHistory>[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-characters.read_corporation_roles.v1']);
|
||||
return esiFetch<Partial<CharacterCorporationHistory>[]>(`/characters/${character_id}/corporationhistory/`, options);
|
||||
}
|
||||
|
||||
export function calculateCSPAChargeCost(options: EsiOptions, target_character_ids: number[]): Promise<number[]> {
|
||||
if (target_character_ids.length === 0) return null;
|
||||
if (target_character_ids.length > 100) throw 'Maximum of 100 target character IDs can be requested at once';
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-characters.read_cspa.v1']);
|
||||
return esiFetch<number[]>(`/characters/${character_id}/cspa/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_DETAIL,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(target_character_ids),
|
||||
});
|
||||
}
|
||||
|
||||
export interface JumpFatigue {
|
||||
jump_fatigue_expire_date: string;
|
||||
last_jump_date: string;
|
||||
last_update_date: string;
|
||||
}
|
||||
|
||||
export function getCharacterJumpFatigue(options: EsiOptions): Promise<Partial<JumpFatigue>> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-characters.read_fatigue.v1']);
|
||||
return esiFetch<Partial<JumpFatigue>>(`/characters/${character_id}/fatigue/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_DETAIL,
|
||||
});
|
||||
}
|
||||
|
||||
export interface Medals {
|
||||
corporation_id: number;
|
||||
date: string;
|
||||
description: string;
|
||||
graphics: {
|
||||
color: number;
|
||||
graphic: number;
|
||||
layer: number;
|
||||
part: number;
|
||||
}[];
|
||||
issuer_id: number;
|
||||
medal_id: number;
|
||||
reason: string;
|
||||
status: 'private' | 'public';
|
||||
title: string;
|
||||
}
|
||||
|
||||
export function getCharacterMedals(options: EsiOptions): Promise<Partial<Medals>[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-characters.read_medals.v1']);
|
||||
return esiFetch<Partial<Medals>[]>(`/characters/${character_id}/medals/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_DETAIL,
|
||||
});
|
||||
}
|
||||
|
||||
export interface Notification {
|
||||
is_read: boolean;
|
||||
notification_id: number;
|
||||
sender_id: number;
|
||||
sender_type: 'character' | 'corporation' | 'alliance' | 'faction' | 'other';
|
||||
text: string;
|
||||
timestamp: string;
|
||||
type: NotificationType;
|
||||
}
|
||||
|
||||
export function getCharacterNotifications(options: EsiOptions): Promise<Partial<Notification>[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-characters.read_notifications.v1']);
|
||||
return esiFetch<Partial<Notification>[]>(`/characters/${character_id}/notifications/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_NOTIFICATION,
|
||||
});
|
||||
}
|
||||
|
||||
export interface ContactNotification {
|
||||
message: string;
|
||||
notification_id: number;
|
||||
send_date: string;
|
||||
sender_character_id: number;
|
||||
standing_level: -10 | -5 | 0 | 5 | 10;
|
||||
}
|
||||
|
||||
export function getCharacterContactNotifications(options: EsiOptions): Promise<Partial<ContactNotification>[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-characters.read_notifications.v1']);
|
||||
return esiFetch<Partial<ContactNotification>[]>(`/characters/${character_id}/notifications/contacts`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_SOCIAL,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CharacterPortraits {
|
||||
px128x128: string;
|
||||
px256x256: string;
|
||||
px512x512: string;
|
||||
px64x64: string;
|
||||
}
|
||||
|
||||
export function getCharacterPortraits(character_id: number, options?: PublicEsiOptions): Promise<Partial<CharacterPortraits>> {
|
||||
return esiFetch<Partial<CharacterPortraits>>(`/characters/${character_id}/portrait/`, options);
|
||||
}
|
||||
|
||||
export function getPortraitURL(character_id: number) {
|
||||
return `https://images.evetech.net/characters/${character_id}/portrait`;
|
||||
}
|
||||
|
||||
export interface CharacterCorporationRoles {
|
||||
roles: string[];
|
||||
roles_at_base: string[];
|
||||
roles_at_hq: string[];
|
||||
roles_at_other: string[];
|
||||
}
|
||||
|
||||
export function getCharacterCorporationRoles(options: EsiOptions): Promise<Partial<CharacterCorporationRoles>> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-characters.read_corporation_roles.v1']);
|
||||
return esiFetch<Partial<CharacterCorporationRoles>>(`/characters/${character_id}/roles`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_DETAIL,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CharacterStandings {
|
||||
from_id: number;
|
||||
from_type: 'agent' | 'npc_corp' | 'faction';
|
||||
standing: number;
|
||||
}
|
||||
|
||||
export function getCharacterStandings(options: EsiOptions): Promise<Partial<CharacterStandings>[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-characters.read_standings.v1']);
|
||||
return esiFetch<Partial<CharacterStandings>[]>(`/characters/${character_id}/standings`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_SOCIAL,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CharacterTitles {
|
||||
titles: {
|
||||
name: string;
|
||||
title_id: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
export function getCharacterTitles(options: EsiOptions): Promise<Partial<CharacterTitles>> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-characters.read_titles.v1']);
|
||||
return esiFetch<Partial<CharacterTitles>>(`/characters/${character_id}/titles`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_DETAIL,
|
||||
});
|
||||
}
|
||||
35
packages/eve/src/esi/clones.ts
Normal file
35
packages/eve/src/esi/clones.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { ESI_SCOPE } from '../oauth/auth.types';
|
||||
import { checkScopesAndGetCharacterId, esiFetch, type EsiOptions } from './util/fetch';
|
||||
import { ESI_RATE_LIMIT_GROUP } from './util/rate-limits';
|
||||
|
||||
export interface CharacterClones {
|
||||
home_location: {
|
||||
location_id: number;
|
||||
location_type: 'station' | 'structure';
|
||||
};
|
||||
jump_clones: {
|
||||
implants: number[];
|
||||
jump_clone_id: number;
|
||||
location_id: number;
|
||||
location_type: 'station' | 'structure';
|
||||
name: string;
|
||||
}[];
|
||||
last_clone_jump_date: string;
|
||||
last_station_change_date: string;
|
||||
}
|
||||
|
||||
export function getCharacterClones(options: EsiOptions): Promise<Partial<CharacterClones>> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-clones.read_clones.v1']);
|
||||
return esiFetch<Partial<CharacterClones>>(`/characters/${character_id}/clones`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_LOCATION,
|
||||
});
|
||||
}
|
||||
|
||||
export function getCharacterActiveImplants(options: EsiOptions): Promise<number[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-clones.read_implants.v1']);
|
||||
return esiFetch<number[]>(`/characters/${character_id}/implants`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_DETAIL,
|
||||
});
|
||||
}
|
||||
133
packages/eve/src/esi/contacts.ts
Normal file
133
packages/eve/src/esi/contacts.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { ESI_SCOPE } from '../oauth/auth.types';
|
||||
import type { STANDING } from './types/shared';
|
||||
import { checkScopesAndGetCharacterId, esiFetch, type EsiOptions } from './util/fetch';
|
||||
import { ESI_RATE_LIMIT_GROUP } from './util/rate-limits';
|
||||
|
||||
export interface Contact {
|
||||
contact_id: number;
|
||||
contact_type: 'character' | 'corporation' | 'alliance' | 'faction';
|
||||
label_ids?: number[];
|
||||
standing: STANDING;
|
||||
}
|
||||
|
||||
export function getAllianceContacts(options: EsiOptions, alliance_id: number, page: number = 1): Promise<Contact[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-alliances.read_contacts.v1']);
|
||||
return esiFetch<Contact[]>(`/alliances/${alliance_id}/contacts/?page=${page}`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.ALLIANCE_SOCIAL,
|
||||
});
|
||||
}
|
||||
|
||||
export interface ContactLabel {
|
||||
label_id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function getAllianceContactLabels(options: EsiOptions, alliance_id: number): Promise<ContactLabel[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-alliances.read_contacts.v1']);
|
||||
return esiFetch<ContactLabel[]>(`/alliances/${alliance_id}/contacts/labels/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.ALLIANCE_SOCIAL,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteCharacterContacts(options: EsiOptions, character_ids: number[]): Promise<void> {
|
||||
if (character_ids.length === 0) return;
|
||||
if (character_ids.length > 20) throw 'Maximum of 20 IDs can be deleted at once';
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-characters.write_contacts.v1']);
|
||||
return esiFetch<void>(`/characters/${character_id}/contacts/`, {
|
||||
method: 'DELETE',
|
||||
body: JSON.stringify({ contact_ids: character_ids }),
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_SOCIAL,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CharacterContact extends Contact {
|
||||
is_blocked?: boolean;
|
||||
is_watched?: boolean;
|
||||
}
|
||||
|
||||
export function getCharacterContacts(options: EsiOptions, page: number = 1): Promise<CharacterContact[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-characters.read_contacts.v1']);
|
||||
return esiFetch<CharacterContact[]>(`/characters/${character_id}/contacts/?page=${page}`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_SOCIAL,
|
||||
});
|
||||
}
|
||||
|
||||
export function addCharacterContacts(
|
||||
options: EsiOptions,
|
||||
character_ids: number[],
|
||||
standing: STANDING,
|
||||
label_ids?: number[],
|
||||
watched?: boolean,
|
||||
): Promise<void> {
|
||||
if (character_ids.length === 0) return;
|
||||
if (character_ids.length > 100) throw 'Maximum of 100 IDs can be added at once';
|
||||
if (label_ids && label_ids.length > 63) throw 'Maximum of 63 label IDs can be assigned at once';
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-characters.write_contacts.v1']);
|
||||
return esiFetch<void>(
|
||||
`/characters/${character_id}/contacts?standing=${standing}${watched ? '&watched=true' : ''}${
|
||||
label_ids ? `&label_ids=${label_ids.join(',')}` : ''
|
||||
}`,
|
||||
{
|
||||
...options,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(character_ids),
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_SOCIAL,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function editCharacterContacts(
|
||||
options: EsiOptions,
|
||||
character_ids: number[],
|
||||
standing: STANDING,
|
||||
label_ids?: number[],
|
||||
watched?: boolean,
|
||||
): Promise<void> {
|
||||
if (character_ids.length === 0) return;
|
||||
if (character_ids.length > 100) throw 'Maximum of 100 IDs can be edited at once';
|
||||
if (label_ids && label_ids.length > 63) throw 'Maximum of 63 label IDs can be assigned at once';
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-characters.write_contacts.v1']);
|
||||
return esiFetch<void>(
|
||||
`/characters/${character_id}/contacts?standing=${standing}${watched ? '&watched=true' : ''}${
|
||||
label_ids ? `&label_ids=${label_ids.join(',')}` : ''
|
||||
}`,
|
||||
{
|
||||
...options,
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(character_ids),
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_SOCIAL,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function getCharacterContactLabels(options: EsiOptions): Promise<Partial<ContactLabel>[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-characters.read_contacts.v1']);
|
||||
return esiFetch<Partial<ContactLabel>[]>(`/characters/${character_id}/contacts/labels/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_SOCIAL,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CorporationContact extends Contact {
|
||||
is_watched?: boolean;
|
||||
}
|
||||
|
||||
export function getCorporationContacts(options: EsiOptions, corporation_id: number, page: number = 1): Promise<CorporationContact[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.read_contacts.v1']);
|
||||
return esiFetch<CorporationContact[]>(`/corporations/${corporation_id}/contacts/?page=${page}`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_SOCIAL,
|
||||
});
|
||||
}
|
||||
|
||||
export function getCorporationContactLabels(options: EsiOptions, corporation_id: number): Promise<ContactLabel[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.read_contacts.v1']);
|
||||
return esiFetch<ContactLabel[]>(`/corporations/${corporation_id}/contacts/labels/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_SOCIAL,
|
||||
});
|
||||
}
|
||||
161
packages/eve/src/esi/contracts.ts
Normal file
161
packages/eve/src/esi/contracts.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { ESI_SCOPE } from '../oauth/auth.types';
|
||||
import { ESI_RATE_LIMIT_GROUP } from './util/rate-limits';
|
||||
import { checkScopesAndGetCharacterId, esiFetch, type EsiOptions, type PublicEsiOptions } from './util/fetch';
|
||||
|
||||
export enum ContractType {
|
||||
ITEM_EXCHANGE = 'item_exchange',
|
||||
AUCTION = 'auction',
|
||||
COURIER = 'courier',
|
||||
LOAN = 'loan',
|
||||
}
|
||||
|
||||
export interface PublicContract {
|
||||
buyout?: number;
|
||||
collateral?: number;
|
||||
contract_id: number;
|
||||
date_expired: string;
|
||||
date_issued: string;
|
||||
days_to_complete?: number;
|
||||
end_location_id?: number;
|
||||
for_corporation: boolean;
|
||||
issuer_corporation_id: number;
|
||||
issuer_id: number;
|
||||
price?: number;
|
||||
reward?: number;
|
||||
start_location_id?: number;
|
||||
title?: string;
|
||||
type: 'item_exchange' | 'auction' | 'courier' | 'loan';
|
||||
volume?: number;
|
||||
}
|
||||
|
||||
export enum ContractAvailability {
|
||||
PUBLIC = 'public',
|
||||
PERSONAL = 'personal',
|
||||
CORPORATION = 'corporation',
|
||||
ALLIANCE = 'alliance',
|
||||
}
|
||||
|
||||
export enum ContractStatus {
|
||||
OUTSTANDING = 'outstanding',
|
||||
IN_PROGRESS = 'in_progress',
|
||||
FINISHED_ISSUER = 'finished_issuer',
|
||||
FINISHED_CONTRACTOR = 'finished_contractor',
|
||||
CANCELLED = 'cancelled',
|
||||
REJECTED = 'rejected',
|
||||
FAILED = 'failed',
|
||||
DELETED = 'deleted',
|
||||
REVERSED = 'reversed',
|
||||
}
|
||||
|
||||
export interface Contract extends PublicContract {
|
||||
acceptor_id: number;
|
||||
assignee_id: number;
|
||||
availability: ContractAvailability;
|
||||
date_accepted?: string;
|
||||
date_completed?: string;
|
||||
status:
|
||||
| 'outstanding'
|
||||
| 'in_progress'
|
||||
| 'finished_issuer'
|
||||
| 'finished_contractor'
|
||||
| 'cancelled'
|
||||
| 'rejected'
|
||||
| 'failed'
|
||||
| 'deleted'
|
||||
| 'reversed';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns contracts available to a character, only if the character is issuer, acceptor or assignee.
|
||||
* Only returns contracts no older than 30 days, or if the status is "in_progress".
|
||||
*/
|
||||
export function getCharacterContracts(options: EsiOptions, page: number = 1): Promise<Contract[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-contracts.read_character_contracts.v1']);
|
||||
return esiFetch<Contract[]>(`/characters/${character_id}/contracts/?page=${page}`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_CONTRACT,
|
||||
});
|
||||
}
|
||||
|
||||
export interface PublicContractBid {
|
||||
amount: number;
|
||||
bid_id: number;
|
||||
date_bid: string;
|
||||
}
|
||||
|
||||
export interface ContractBid extends PublicContractBid {
|
||||
bidder_id: number;
|
||||
}
|
||||
|
||||
export function getContractBids(options: EsiOptions, contract_id: number): Promise<ContractBid[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-contracts.read_character_contracts.v1']);
|
||||
return esiFetch<ContractBid[]>(`/characters/${character_id}/contracts/${contract_id}/bids/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_CONTRACT,
|
||||
});
|
||||
}
|
||||
|
||||
export interface ContractItem {
|
||||
is_included: boolean; // true if the item is included in the contract, false if it is being requested
|
||||
is_singleton: boolean;
|
||||
quantity: number; // number of items (for stackable items)
|
||||
raw_quantity?: number; // -1 indicates that the item is a singleton (non-stackable). If the item happens to be a Blueprint, -1 is an Original and -2 is a Blueprint Copy
|
||||
record_id: number; // unique ID for this item in the contract
|
||||
type_id: number; // type ID of the item
|
||||
}
|
||||
|
||||
export function getContractItems(options: EsiOptions, contract_id: number): Promise<ContractItem[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-contracts.read_character_contracts.v1']);
|
||||
return esiFetch<ContractItem[]>(`/characters/${character_id}/contracts/${contract_id}/items/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CHAR_CONTRACT,
|
||||
});
|
||||
}
|
||||
|
||||
export function getPublicContractBids(contract_id: number, page: number = 1, options?: PublicEsiOptions): Promise<PublicContractBid[]> {
|
||||
return esiFetch<PublicContractBid[]>(`/contracts/public/bids/${contract_id}?page=${page}`, options);
|
||||
}
|
||||
|
||||
export interface PublicContractItem {
|
||||
is_blueprint_copy?: boolean;
|
||||
is_included: boolean; // true if the item is included in the contract, false if it is being requested
|
||||
item_id: number;
|
||||
material_efficiency?: number; // Material efficiency level of the blueprint
|
||||
quantity: number;
|
||||
record_id: number; // unique ID for this item in the contract
|
||||
runs?: number; // Number of runs for the blueprint
|
||||
time_efficiency?: number; // Time efficiency level of the blueprint
|
||||
type_id: number; // type ID of the item
|
||||
}
|
||||
|
||||
export function getPublicContractItems(contract_id: number, page: number = 1, options?: PublicEsiOptions): Promise<PublicContractItem[]> {
|
||||
return esiFetch<PublicContractItem[]>(`/contracts/public/items/${contract_id}?page=${page}`, options);
|
||||
}
|
||||
|
||||
export function getPublicContracts(region_id: number, page: number = 1, options?: PublicEsiOptions): Promise<PublicContract[]> {
|
||||
return esiFetch<PublicContract[]>(`/contracts/public/${region_id}?page=${page}`, options);
|
||||
}
|
||||
|
||||
export function getCorporationContracts(options: EsiOptions, corporation_id: number, page: number = 1): Promise<Contract[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-contracts.read_corporation_contracts.v1']);
|
||||
return esiFetch<Contract[]>(`/corporations/${corporation_id}/contracts/?page=${page}`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_CONTRACT,
|
||||
});
|
||||
}
|
||||
|
||||
export function getCorporationContractBids(options: EsiOptions, corporation_id: number, contract_id: number): Promise<ContractBid[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-contracts.read_corporation_contracts.v1']);
|
||||
return esiFetch<ContractBid[]>(`/corporations/${corporation_id}/contracts/${contract_id}/bids/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_CONTRACT,
|
||||
});
|
||||
}
|
||||
|
||||
export function getCorporationContractItems(options: EsiOptions, corporation_id: number, contract_id: number): Promise<ContractItem[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-contracts.read_corporation_contracts.v1']);
|
||||
return esiFetch<ContractItem[]>(`/corporations/${corporation_id}/contracts/${contract_id}/items/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_CONTRACT,
|
||||
});
|
||||
}
|
||||
67
packages/eve/src/esi/corporation-projects.ts
Normal file
67
packages/eve/src/esi/corporation-projects.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Eve Corporation Projects ESI endpoints
|
||||
* ref: https://developers.eveonline.com/api-explorer
|
||||
*/
|
||||
import { ESI_SCOPE } from '../oauth/auth.types';
|
||||
import { ESI_RATE_LIMIT_GROUP } from './util/rate-limits';
|
||||
import { checkScopesAndGetCharacterId, esiFetch, type EsiOptions, type PublicEsiOptions } from './util/fetch';
|
||||
import type { Blueprint, Icons, STANDING } from './types/shared';
|
||||
import type { CorporationRoles } from './types/corporation';
|
||||
|
||||
export interface CorporationProject {
|
||||
id: string; // uuid
|
||||
last_modified: string;
|
||||
name: string;
|
||||
progress: {
|
||||
current: number;
|
||||
desired: number;
|
||||
};
|
||||
reward: {
|
||||
initial: number; // original ISK amount reserved for the project
|
||||
remaining: number; // ISK amount still remaining for the project
|
||||
};
|
||||
state: 'Unspecified' | 'Active' | 'Closed' | 'Completed' | 'Expired' | 'Deleted';
|
||||
/**
|
||||
* State:
|
||||
* Unspecified - An unspecified state
|
||||
* Active - Active and accepting contributions
|
||||
* Closed - Closed by the corporation
|
||||
* Completed - Completed
|
||||
* Expired - Expired
|
||||
* Deleted - Deleted and the details are no longer available
|
||||
*/
|
||||
}
|
||||
|
||||
export interface CorporationProjectResponse {
|
||||
cursor?: {
|
||||
after?: string; // cursor to use as 'after' in your next request, to continue walking fowards in time
|
||||
before?: string; // cursor to use as 'before' in your next request, to continue walking backwards in time
|
||||
};
|
||||
|
||||
projects: CorporationProject[];
|
||||
}
|
||||
|
||||
export function listCorporationProjects(
|
||||
options: EsiOptions,
|
||||
corporation_id: number,
|
||||
filters: {
|
||||
after?: string;
|
||||
before?: string;
|
||||
limit?: number;
|
||||
state?: 'Unspecified' | 'Active' | 'Closed' | 'Completed' | 'Expired' | 'Deleted';
|
||||
} = {},
|
||||
): Promise<CorporationProjectResponse> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.read_projects.v1']);
|
||||
const queryParams = new URLSearchParams();
|
||||
if (filters.after) queryParams.append('after', filters.after);
|
||||
if (filters.before) queryParams.append('before', filters.before);
|
||||
if (filters.limit) queryParams.append('limit', filters.limit.toString());
|
||||
if (filters.state) queryParams.append('state', filters.state);
|
||||
|
||||
return esiFetch<CorporationProjectResponse>(`/corporations/${corporation_id}/projects/?${queryParams.toString()}`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_PROJECT,
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: finish the remaining endpoints later
|
||||
393
packages/eve/src/esi/corporation.ts
Normal file
393
packages/eve/src/esi/corporation.ts
Normal file
@@ -0,0 +1,393 @@
|
||||
/**
|
||||
* Eve Corporation ESI endpoints
|
||||
* ref: https://developers.eveonline.com/api-explorer#/operations/GetCorporationsNpccorps
|
||||
*/
|
||||
import { ESI_SCOPE } from '../oauth/auth.types';
|
||||
import { ESI_RATE_LIMIT_GROUP } from './util/rate-limits';
|
||||
import { checkScopesAndGetCharacterId, esiFetch, type EsiOptions, type PublicEsiOptions } from './util/fetch';
|
||||
import type { Blueprint, Icons, STANDING } from './types/shared';
|
||||
import type { CorporationRoles } from './types/corporation';
|
||||
|
||||
export async function getNpcCorporations(options?: PublicEsiOptions): Promise<number[]> {
|
||||
return await esiFetch<number[]>('/corporations/npccorps', options);
|
||||
}
|
||||
|
||||
interface CorporationInfo {
|
||||
alliance_id?: number;
|
||||
ceo_id: number;
|
||||
creator_id: number;
|
||||
date_founded?: string;
|
||||
description?: string;
|
||||
faction_id?: number;
|
||||
home_station_id?: number;
|
||||
member_count: number;
|
||||
name: string;
|
||||
shares?: number;
|
||||
tax_rate: number;
|
||||
ticker: string;
|
||||
url?: string;
|
||||
war_eligible?: boolean;
|
||||
}
|
||||
|
||||
export async function getCorporationData(corporation_id: number, options?: PublicEsiOptions): Promise<CorporationInfo> {
|
||||
return await esiFetch<CorporationInfo>(`/corporations/${corporation_id}/`, options);
|
||||
}
|
||||
|
||||
interface AllianceHistory {
|
||||
alliance_id?: number;
|
||||
is_deleted?: boolean;
|
||||
record_id: number;
|
||||
start_date: string;
|
||||
}
|
||||
|
||||
export async function getCorporationAllianceHistory(corporation_id: number, options?: PublicEsiOptions): Promise<AllianceHistory[]> {
|
||||
return await esiFetch<AllianceHistory[]>(`/corporations/${corporation_id}/alliancehistory/`, options);
|
||||
}
|
||||
|
||||
export async function getCorporationBlueprints(
|
||||
options: EsiOptions,
|
||||
corporation_id: number,
|
||||
page: number = 1,
|
||||
): Promise<Partial<Blueprint>[]> {
|
||||
checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.read_blueprints.v1']);
|
||||
return await esiFetch<Partial<Blueprint>[]>(`/corporations/${corporation_id}/blueprints/?page=${page}`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_INDUSTRY,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAllCorporationALSCLogs(options: EsiOptions, corporation_id: number, page: number = 1): Promise<any[]> {
|
||||
checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.read_container_logs.v1']);
|
||||
return await esiFetch<any[]>(`/corporations/${corporation_id}/containers/logs/?page=${page}`, {
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CorporationDivisions {
|
||||
hangar: {
|
||||
division: number;
|
||||
name: string;
|
||||
}[];
|
||||
wallet: {
|
||||
division: number;
|
||||
name: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export async function getCorporationDivisions(options: EsiOptions, corporation_id: number): Promise<CorporationDivisions> {
|
||||
checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.read_divisions.v1']);
|
||||
return await esiFetch<CorporationDivisions>(`/corporations/${corporation_id}/divisions/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_WALLET,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CorporationFacility {
|
||||
facility_id: number;
|
||||
system_id: number;
|
||||
type_id: number;
|
||||
}
|
||||
|
||||
export async function getCorporationFacilities(options: EsiOptions, corporation_id: number): Promise<CorporationFacility[]> {
|
||||
checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.read_facilities.v1']);
|
||||
return await esiFetch<CorporationFacility[]>(`/corporations/${corporation_id}/facilities/`, {
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getCorporationIcons(corporation_id: number, options?: PublicEsiOptions): Promise<Icons> {
|
||||
return await esiFetch<Icons>(`/corporations/${corporation_id}/icons/`, options);
|
||||
}
|
||||
|
||||
export interface CorporationMedal {
|
||||
created_at: string;
|
||||
createor_id: number;
|
||||
description: string;
|
||||
medal_id: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export async function getCorporationMedals(options: EsiOptions, corporation_id: number, page: number = 1): Promise<CorporationMedal[]> {
|
||||
checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.read_medals.v1']);
|
||||
return await esiFetch<CorporationMedal[]>(`/corporations/${corporation_id}/medals/?page=${page}`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_DETAIL,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CorporationIssuedMedal {
|
||||
character_id: number;
|
||||
issued_at: string;
|
||||
issuer_id: number;
|
||||
medal_id: number;
|
||||
reason: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export async function getCorporationIssuedMedals(
|
||||
options: EsiOptions,
|
||||
corporation_id: number,
|
||||
page: number = 1,
|
||||
): Promise<CorporationIssuedMedal[]> {
|
||||
checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.read_medals.v1']);
|
||||
return await esiFetch<CorporationIssuedMedal[]>(`/corporations/${corporation_id}/medals/issued/?page=${page}`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_DETAIL,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getCorporationMembers(options: EsiOptions, corporation_id: number): Promise<number[]> {
|
||||
checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.read_corporation_membership.v1']);
|
||||
return await esiFetch<number[]>(`/corporations/${corporation_id}/members/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_MEMBER,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getCorporationMemberLimit(options: EsiOptions, corporation_id: number): Promise<number> {
|
||||
checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.track_members.v1']);
|
||||
return await esiFetch<number>(`/corporations/${corporation_id}/members/limit/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_MEMBER,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CorporationMemberTitles {
|
||||
character_id: number;
|
||||
titles: number[];
|
||||
}
|
||||
|
||||
export async function getCorporationMemberTitles(options: EsiOptions, corporation_id: number): Promise<CorporationMemberTitles[]> {
|
||||
checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.read_titles.v1']);
|
||||
return await esiFetch<CorporationMemberTitles[]>(`/corporations/${corporation_id}/members/titles/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_MEMBER,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CorporationMemberTracking {
|
||||
base_id?: number;
|
||||
character_id: number;
|
||||
location_id?: number;
|
||||
logoff_date?: string;
|
||||
logon_date?: string;
|
||||
ship_type_id?: number;
|
||||
start_date?: string;
|
||||
}
|
||||
|
||||
export async function getCorporationMemberTracking(options: EsiOptions, corporation_id: number): Promise<CorporationMemberTracking[]> {
|
||||
checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.track_members.v1']);
|
||||
return await esiFetch<CorporationMemberTracking[]>(`/corporations/${corporation_id}/membertracking/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_MEMBER,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CorporationMemberRole {
|
||||
character_id: number;
|
||||
grantable_roles: CorporationRoles[];
|
||||
grantable_roles_at_base: CorporationRoles[];
|
||||
grantable_roles_at_hq: CorporationRoles[];
|
||||
grantable_roles_at_other: CorporationRoles[];
|
||||
roles: CorporationRoles[];
|
||||
roles_at_base: CorporationRoles[];
|
||||
roles_at_hq: CorporationRoles[];
|
||||
roles_at_other: CorporationRoles[];
|
||||
}
|
||||
|
||||
export async function getCorporationMemberRoles(options: EsiOptions, corporation_id: number): Promise<CorporationMemberRole[]> {
|
||||
checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.read_corporation_membership.v1']);
|
||||
return await esiFetch<CorporationMemberRole[]>(`/corporations/${corporation_id}/members/roles/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_MEMBER,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CorporationMemberRoleHistory {
|
||||
changed_at: string;
|
||||
character_id: number;
|
||||
issuer_id: number;
|
||||
new_roles: CorporationRoles[];
|
||||
old_roles: CorporationRoles[];
|
||||
role_type:
|
||||
| 'grantable_roles'
|
||||
| 'grantable_roles_at_base'
|
||||
| 'grantable_roles_at_hq'
|
||||
| 'grantable_roles_at_other'
|
||||
| 'roles'
|
||||
| 'roles_at_base'
|
||||
| 'roles_at_hq'
|
||||
| 'roles_at_other';
|
||||
}
|
||||
|
||||
export async function getCorporationMemberRoleHistory(
|
||||
options: EsiOptions,
|
||||
corporation_id: number,
|
||||
page: number = 1,
|
||||
): Promise<CorporationMemberRoleHistory[]> {
|
||||
checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.read_corporation_membership.v1']);
|
||||
return await esiFetch<CorporationMemberRoleHistory[]>(`/corporations/${corporation_id}/roles/history/?page=${page}`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_MEMBER,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CorporationShareholder {
|
||||
share_count: number;
|
||||
shareholder_id: number;
|
||||
shareholder_type: 'character' | 'corporation';
|
||||
}
|
||||
|
||||
export async function getCorporationShareholders(
|
||||
options: EsiOptions,
|
||||
corporation_id: number,
|
||||
page: number = 1,
|
||||
): Promise<CorporationShareholder[]> {
|
||||
checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-wallet.read_corporation_wallets.v1']);
|
||||
return await esiFetch<CorporationShareholder[]>(`/corporations/${corporation_id}/shareholders/?page=${page}`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_DETAIL,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CorporationStanding {
|
||||
from_id: number;
|
||||
from_type: 'agent' | 'npc_corp' | 'faction';
|
||||
standing: STANDING;
|
||||
}
|
||||
|
||||
export async function getCorporationStandings(
|
||||
options: EsiOptions,
|
||||
corporation_id: number,
|
||||
page: number = 1,
|
||||
): Promise<CorporationStanding[]> {
|
||||
checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.read_standings.v1']);
|
||||
return await esiFetch<CorporationStanding[]>(`/corporations/${corporation_id}/standings/?page=${page}`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_MEMBER,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CorporationStarbase {
|
||||
moon_id?: number;
|
||||
onlined_since?: string;
|
||||
reinforced_until?: string;
|
||||
starbase_id: number;
|
||||
state?: 'offline' | 'online' | 'onlining' | 'reinforced' | 'unanchoring';
|
||||
system_id: number;
|
||||
type_id: number;
|
||||
unanchor_at?: string;
|
||||
}
|
||||
|
||||
export async function getCorporationStarbases(
|
||||
options: EsiOptions,
|
||||
corporation_id: number,
|
||||
page: number = 1,
|
||||
): Promise<CorporationStarbase[]> {
|
||||
checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.read_starbases.v1']);
|
||||
return await esiFetch<CorporationStarbase[]>(`/corporations/${corporation_id}/starbases/?page=${page}`, {
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export type StarbaseRole = 'alliance_member' | 'config_starbase_equipment_role' | 'corporation_member' | 'starbase_fuel_technician_role';
|
||||
|
||||
export interface StarbaseDetail {
|
||||
allow_alliance_members: boolean;
|
||||
allow_corporation_members: boolean;
|
||||
anchor: StarbaseRole;
|
||||
attack_if_at_war: boolean;
|
||||
attach_if_other_security_status_dropping: boolean;
|
||||
attack_security_status_threshold?: number;
|
||||
attack_standing_threshold?: STANDING;
|
||||
fuel_bay_take: StarbaseRole;
|
||||
fuel_bay_view: StarbaseRole;
|
||||
fuels?: {
|
||||
type_id: number;
|
||||
quantity: number;
|
||||
}[];
|
||||
offline: StarbaseRole;
|
||||
online: StarbaseRole;
|
||||
unanchor: StarbaseRole;
|
||||
use_alliance_standings: boolean;
|
||||
}
|
||||
|
||||
export async function getCorporationStarbaseDetail(
|
||||
options: EsiOptions,
|
||||
corporation_id: number,
|
||||
starbase_id: number,
|
||||
system_id: number,
|
||||
): Promise<StarbaseDetail> {
|
||||
checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.read_starbases.v1']);
|
||||
return await esiFetch<StarbaseDetail>(`/corporations/${corporation_id}/starbases/${starbase_id}/?system_id=${system_id}`, {
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export interface StationService {
|
||||
name: string;
|
||||
state: 'online' | 'offline' | 'cleanup';
|
||||
}
|
||||
|
||||
export interface CorporationStructure {
|
||||
corporation_id: number;
|
||||
fuel_expires: string;
|
||||
name: string;
|
||||
next_reinforce_apply: string;
|
||||
next_reinforce_hour: number;
|
||||
profile_id: number;
|
||||
reinforce_hour: number;
|
||||
services: StationService[];
|
||||
state:
|
||||
| 'anchor_vulnerable'
|
||||
| 'anchoring'
|
||||
| 'armor_reinforce'
|
||||
| 'armor_vulnerable'
|
||||
| 'deploy_vulnerable'
|
||||
| 'fitting_invulnerable'
|
||||
| 'hull_reinforce'
|
||||
| 'hull_vulnerable'
|
||||
| 'online_depreceated'
|
||||
| 'onlining_vulnerable'
|
||||
| 'shield_vulnerable'
|
||||
| 'unanchored'
|
||||
| 'unknown';
|
||||
state_timer_end: string; // date at which the structure will move to it's next state
|
||||
state_timer_start: string; // date at which the structue entered it's current state
|
||||
structure_id: number;
|
||||
system_id: number;
|
||||
type_id: number;
|
||||
unanchors_at: string;
|
||||
}
|
||||
|
||||
export async function getCorporationStructures(
|
||||
options: EsiOptions,
|
||||
corporation_id: number,
|
||||
page: number = 1,
|
||||
): Promise<CorporationStructure[]> {
|
||||
checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.read_structures.v1']);
|
||||
return await esiFetch<CorporationStructure[]>(`/corporations/${corporation_id}/structures/?page=${page}`, {
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CorporationTitle {
|
||||
grantable_roles: CorporationRoles[];
|
||||
grantable_roles_at_base: CorporationRoles[];
|
||||
grantable_roles_at_hq: CorporationRoles[];
|
||||
grantable_roles_at_other: CorporationRoles[];
|
||||
name: string;
|
||||
roles: CorporationRoles[];
|
||||
roles_at_base: CorporationRoles[];
|
||||
roles_at_hq: CorporationRoles[];
|
||||
roles_at_other: CorporationRoles[];
|
||||
title_id: number;
|
||||
}
|
||||
|
||||
export async function getCorporationTitles(options: EsiOptions, corporation_id: number): Promise<CorporationTitle[]> {
|
||||
checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-corporations.read_titles.v1']);
|
||||
return await esiFetch<CorporationTitle[]>(`/corporations/${corporation_id}/titles/`, {
|
||||
...options,
|
||||
rateLimitGroup: ESI_RATE_LIMIT_GROUP.CORP_DETAIL,
|
||||
});
|
||||
}
|
||||
1
packages/eve/src/esi/dogma.ts
Normal file
1
packages/eve/src/esi/dogma.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
1
packages/eve/src/esi/faction-warfare.ts
Normal file
1
packages/eve/src/esi/faction-warfare.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
1
packages/eve/src/esi/fittings.ts
Normal file
1
packages/eve/src/esi/fittings.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
1
packages/eve/src/esi/fleets.ts
Normal file
1
packages/eve/src/esi/fleets.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
1
packages/eve/src/esi/freelance-jobs.ts
Normal file
1
packages/eve/src/esi/freelance-jobs.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
1
packages/eve/src/esi/incursions.ts
Normal file
1
packages/eve/src/esi/incursions.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
18
packages/eve/src/esi/index.ts
Normal file
18
packages/eve/src/esi/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export * as CharacterAPI from './character';
|
||||
export * as CorporationAPI from './corporation';
|
||||
export * as AllianceAPI from './alliance';
|
||||
export * from './util/fetch';
|
||||
export * from './skills';
|
||||
export * from './util/options';
|
||||
export * from './mail';
|
||||
export * from './character';
|
||||
export * from './alliance';
|
||||
export * from './contracts';
|
||||
|
||||
import * as alliance from './alliance';
|
||||
import * as assets from './assets';
|
||||
import * as character from './character';
|
||||
import * as corporation from './corporation';
|
||||
import * as contracts from './contracts';
|
||||
|
||||
export { alliance, assets, character, corporation, contracts };
|
||||
1
packages/eve/src/esi/industry.ts
Normal file
1
packages/eve/src/esi/industry.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
1
packages/eve/src/esi/insurance.ts
Normal file
1
packages/eve/src/esi/insurance.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
1
packages/eve/src/esi/killmails.ts
Normal file
1
packages/eve/src/esi/killmails.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
39
packages/eve/src/esi/location.ts
Normal file
39
packages/eve/src/esi/location.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { checkScopesAndGetCharacterId, esiFetch, type EsiOptions } from './util/fetch';
|
||||
|
||||
import { ESI_SCOPE } from '../oauth';
|
||||
export interface Location {
|
||||
solar_system_id: number;
|
||||
station_id: number;
|
||||
structure_id: number;
|
||||
}
|
||||
|
||||
// required scope: esi-location.read_location.v1
|
||||
export function getCharacterLocation(options: EsiOptions): Promise<Partial<Location>> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-location.read_location.v1']);
|
||||
return esiFetch<Partial<Location>>(`/characters/${character_id}/location/`, options);
|
||||
}
|
||||
|
||||
export interface Online {
|
||||
last_login: string;
|
||||
last_logout: string;
|
||||
logins: number;
|
||||
online: boolean;
|
||||
}
|
||||
|
||||
// required scope: esi-location.read_online.v1
|
||||
export function getCharacterOnline(options: EsiOptions): Promise<Partial<Online>> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-location.read_online.v1']);
|
||||
return esiFetch<Partial<Online>>(`/characters/${character_id}/online/`, options);
|
||||
}
|
||||
|
||||
export interface CurrentShip {
|
||||
ship_item_id: number;
|
||||
ship_type_id: number;
|
||||
ship_name: string;
|
||||
}
|
||||
|
||||
// required scope: esi-location.read_ship_type.v1
|
||||
export function getCharacterCurrentShip(options: EsiOptions): Promise<Partial<CurrentShip>> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-location.read_ship_type.v1']);
|
||||
return esiFetch<Partial<CurrentShip>>(`/characters/${character_id}/ship/`, options);
|
||||
}
|
||||
1
packages/eve/src/esi/loyalty.ts
Normal file
1
packages/eve/src/esi/loyalty.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
144
packages/eve/src/esi/mail.ts
Normal file
144
packages/eve/src/esi/mail.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { ESI_SCOPE } from '../oauth';
|
||||
import { esiFetch, type EsiOptions, checkScopesAndGetCharacterId } from './util/fetch';
|
||||
import { CharacterHelper, type Character } from '@/db';
|
||||
|
||||
export interface MailHeader {
|
||||
from: number; // From whom the mail was sent
|
||||
is_read: boolean; // is_read boolean
|
||||
labels: string[]; // maxItems: 25, minimum: 0, title: get_characters_character_id_mail_labels, uniqueItems: true, labels array
|
||||
mail_id: number; // mail_id integer
|
||||
recipients: {
|
||||
recipient_id: number; // recipient_id integer
|
||||
recipient_type: 'alliance' | 'character' | 'corporation' | 'mailing_list'; // recipient_type enum
|
||||
}[]; // maxItems: 52, minimum: 0, title: get_characters_character_id_mail_recipients, uniqueItems: true, recipients of the mail
|
||||
subject: string; // Mail subject
|
||||
timestamp: string; // When the mail was sent
|
||||
}
|
||||
|
||||
// requires scope: esi-mail.read_mail.v1
|
||||
export function getMailHeaders(options: EsiOptions): Promise<MailHeader[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-mail.read_mail.v1']);
|
||||
return esiFetch<MailHeader[]>(`/characters/${character_id}/mail/`, {
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export interface SendMail {
|
||||
approved_cost?: number; // approved_cost number
|
||||
body: string; // body string; max length 10000
|
||||
recipients: {
|
||||
recipient_id: number; // recipient_id integer
|
||||
recipient_type: 'alliance' | 'character' | 'corporation' | 'mailing_list'; // recipient_type enum
|
||||
}[]; // maxItems: 50, minimum: 1, title: post_characters_character_id_mail, recipients of the mail
|
||||
subject: string; // subject string; max length 1000
|
||||
}
|
||||
|
||||
// requires scope: esi-mail.send_mail.v1
|
||||
export function sendMail(options: EsiOptions, mail: SendMail): Promise<any> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-mail.send_mail.v1']);
|
||||
return esiFetch(`/characters/${character_id}/mail/`, {
|
||||
...options,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(mail),
|
||||
});
|
||||
}
|
||||
|
||||
// requires scope: esi-mail.read_mail.v1
|
||||
export function deleteMail(options: EsiOptions, mailID: number): Promise<any> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-mail.organize_mail.v1']);
|
||||
return esiFetch(`/characters/${character_id}/mail/${mailID}/`, {
|
||||
...options,
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
export interface Mail {
|
||||
body: string; // body string
|
||||
from: number; // from integer
|
||||
labels: string[]; // labels array
|
||||
read: boolean; // read boolean
|
||||
subject: string; // subject string
|
||||
timestamp: string; // timestamp string
|
||||
recipients: {
|
||||
recipient_id: number; // recipient_id integer
|
||||
recipient_type: 'alliance' | 'character' | 'corporation' | 'mailing_list'; // recipient_type enum
|
||||
}[]; // recipients array
|
||||
}
|
||||
|
||||
// requires scope: esi-mail.read_mail.v1
|
||||
export function getMail(options: EsiOptions, mailID: number): Promise<Mail> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-mail.read_mail.v1']);
|
||||
return esiFetch<Mail>(`/characters/${character_id}/mail/${mailID}/`, {
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export interface MailMetadata {
|
||||
labels: string[]; // labels array
|
||||
read: boolean; // read boolean
|
||||
}
|
||||
|
||||
// requires scope: esi-mail.organize_mail.v1
|
||||
export function updateMailMetadata(options: EsiOptions, mailID: number, metadata: MailMetadata): Promise<any> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-mail.organize_mail.v1']);
|
||||
return esiFetch(`/characters/${character_id}/mail/${mailID}/`, {
|
||||
...options,
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(metadata),
|
||||
});
|
||||
}
|
||||
|
||||
export interface MailLabels {
|
||||
labels: {
|
||||
color: number; // color integer
|
||||
label_id: number; // label_id integer
|
||||
name: string; // name string
|
||||
unread_count: number; // unread_count integer
|
||||
}[]; // labels array
|
||||
total_unread_count: number; // total_unread_count integer
|
||||
}
|
||||
|
||||
// requires scope: esi-mail.read_mail.v1
|
||||
export function getMailLabels(options: EsiOptions): Promise<MailLabels> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-mail.read_mail.v1']);
|
||||
return esiFetch<MailLabels>(`/characters/${character_id}/mail/labels/`, {
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CreateMailLabel {
|
||||
color: number; // color integer
|
||||
name: string; // name string
|
||||
}
|
||||
|
||||
// requires scope: esi-mail.organize_mail.v1
|
||||
export function createMailLabel(options: EsiOptions, label: CreateMailLabel): Promise<any> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-mail.organize_mail.v1']);
|
||||
return esiFetch(`/characters/${character_id}/mail/labels/`, {
|
||||
...options,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(label),
|
||||
});
|
||||
}
|
||||
|
||||
// requires scope: esi-mail.organize_mail.v1
|
||||
export function deleteMailLabel(options: EsiOptions, labelID: number): Promise<any> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-mail.organize_mail.v1']);
|
||||
return esiFetch(`/characters/${character_id}/mail/labels/${labelID}/`, {
|
||||
...options,
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
export interface MailingList {
|
||||
mailing_list_id: number; // mailing_list_id integer
|
||||
name: string; // name string
|
||||
}
|
||||
|
||||
// requires scope: esi-mail.read_mail.v1
|
||||
export function getMailingLists(options: EsiOptions): Promise<MailingList[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-mail.read_mail.v1']);
|
||||
return esiFetch<MailingList[]>(`/characters/${character_id}/mail/lists/`, {
|
||||
...options,
|
||||
});
|
||||
}
|
||||
1
packages/eve/src/esi/market.ts
Normal file
1
packages/eve/src/esi/market.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
1
packages/eve/src/esi/meta.ts
Normal file
1
packages/eve/src/esi/meta.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
1
packages/eve/src/esi/planetary-interaction.ts
Normal file
1
packages/eve/src/esi/planetary-interaction.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
1
packages/eve/src/esi/routes.ts
Normal file
1
packages/eve/src/esi/routes.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
1
packages/eve/src/esi/search.ts
Normal file
1
packages/eve/src/esi/search.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
72
packages/eve/src/esi/skills.ts
Normal file
72
packages/eve/src/esi/skills.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { ESI_SCOPE } from '../oauth';
|
||||
import { esiFetch, checkScopesAndGetCharacterId, type EsiOptions } from './util/fetch';
|
||||
|
||||
export interface CharacterAttributes {
|
||||
charisma: number;
|
||||
intelligence: number;
|
||||
memory: number;
|
||||
perception: number;
|
||||
willpower: number;
|
||||
last_remap_date?: string;
|
||||
bonus_remaps?: number;
|
||||
accrued_remap_cooldown_date?: string;
|
||||
}
|
||||
|
||||
// required scope: esi-skills.read_skills.v1
|
||||
export function getCharacterAttributes(options: EsiOptions): Promise<CharacterAttributes> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-skills.read_skills.v1']);
|
||||
return esiFetch<CharacterAttributes>(`/characters/${character_id}/attributes`, {
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export interface SkillQueueItem {
|
||||
finish_date?: string;
|
||||
finished_level: number;
|
||||
level_end_sp?: number;
|
||||
level_start_sp?: number;
|
||||
queue_position: number;
|
||||
skill_id: number;
|
||||
start_date?: string;
|
||||
training_start_sp?: number;
|
||||
}
|
||||
|
||||
// required scope: esi-skills.read_skillqueue.v1
|
||||
export function getCharacterSkillQueue(options: EsiOptions): Promise<SkillQueueItem[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-skills.read_skillqueue.v1']);
|
||||
return esiFetch<SkillQueueItem[]>(`/characters/${character_id}/skillqueue`, {
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export interface APISkill {
|
||||
active_skill_level: number;
|
||||
skill_id: number;
|
||||
skillpoints_in_skill: number;
|
||||
trained_skill_level: number;
|
||||
}
|
||||
|
||||
export interface CharacterSkills {
|
||||
skills: APISkill[];
|
||||
total_sp: number;
|
||||
unallocated_sp?: number;
|
||||
}
|
||||
|
||||
// required scope: esi-skills.read_skills.v1
|
||||
export function getCharacterSkills(options: EsiOptions): Promise<CharacterSkills> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-skills.read_skills.v1']);
|
||||
return esiFetch<CharacterSkills>(`/characters/${character_id}/skills`, {
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export function calculateTrainingPercentage(queuedSkill: SkillQueueItem): number {
|
||||
// percentage in when training started
|
||||
const trainingStartPosition = (queuedSkill.training_start_sp! - queuedSkill.level_start_sp!) / queuedSkill.level_end_sp!;
|
||||
// percentage completed between start and now
|
||||
const timePosition =
|
||||
(new Date().getTime() - new Date(queuedSkill.start_date!).getTime()) /
|
||||
(new Date(queuedSkill.finish_date!).getTime() - new Date(queuedSkill.start_date!).getTime());
|
||||
// percentage completed
|
||||
return trainingStartPosition + (1 - trainingStartPosition) * timePosition;
|
||||
}
|
||||
1
packages/eve/src/esi/sovereignty.ts
Normal file
1
packages/eve/src/esi/sovereignty.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
1
packages/eve/src/esi/status.ts
Normal file
1
packages/eve/src/esi/status.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
56
packages/eve/src/esi/types/corporation.ts
Normal file
56
packages/eve/src/esi/types/corporation.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
export enum CorporationRoles {
|
||||
ACCOUNT_TAKE_1 = 'Account_Take_1',
|
||||
ACCOUNT_TAKE_2 = 'Account_Take_2',
|
||||
ACCOUNT_TAKE_3 = 'Account_Take_3',
|
||||
ACCOUNT_TAKE_4 = 'Account_Take_4',
|
||||
ACCOUNT_TAKE_5 = 'Account_Take_5',
|
||||
ACCOUNT_TAKE_6 = 'Account_Take_6',
|
||||
ACCOUNT_TAKE_7 = 'Account_Take_7',
|
||||
ACCOUNTANT = 'Accountant',
|
||||
AUDITOR = 'Auditor',
|
||||
BRAND_MANAGER = 'Brand_Manager',
|
||||
COMMUNICATIONS_OFFICER = 'Communications_Officer',
|
||||
CONFIG_EQUIPMENT = 'Config_Equipment',
|
||||
CONFIG_STARBASE_EQUIPMENT = 'Config_Starbase_Equipment',
|
||||
CONTAINER_TAKE_1 = 'Container_Take_1',
|
||||
CONTAINER_TAKE_2 = 'Container_Take_2',
|
||||
CONTAINER_TAKE_3 = 'Container_Take_3',
|
||||
CONTAINER_TAKE_4 = 'Container_Take_4',
|
||||
CONTAINER_TAKE_5 = 'Container_Take_5',
|
||||
CONTAINER_TAKE_6 = 'Container_Take_6',
|
||||
CONTAINER_TAKE_7 = 'Container_Take_7',
|
||||
CONTRACT_MANAGER = 'Contract_Manager',
|
||||
DELIVERIES_CONTAINER_TAKE = 'Deliveries_Container_Take',
|
||||
DELIVERIES_QUERY = 'Deliveries_Query',
|
||||
DELIVERIES_TAKE = 'Deliveries_Take',
|
||||
DIPLOMAT = 'Diplomat',
|
||||
DIRECTOR = 'Director',
|
||||
FACTORY_MANAGER = 'Factory_Manager',
|
||||
FITTING_MANAGER = 'Fitting_Manager',
|
||||
HANGAR_QUERY_1 = 'Hangar_Query_1',
|
||||
HANGAR_QUERY_2 = 'Hangar_Query_2',
|
||||
HANGAR_QUERY_3 = 'Hangar_Query_3',
|
||||
HANGAR_QUERY_4 = 'Hangar_Query_4',
|
||||
HANGAR_QUERY_5 = 'Hangar_Query_5',
|
||||
HANGAR_QUERY_6 = 'Hangar_Query_6',
|
||||
HANGAR_QUERY_7 = 'Hangar_Query_7',
|
||||
HANGAR_TAKE_1 = 'Hangar_Take_1',
|
||||
HANGAR_TAKE_2 = 'Hangar_Take_2',
|
||||
HANGAR_TAKE_3 = 'Hangar_Take_3',
|
||||
HANGAR_TAKE_4 = 'Hangar_Take_4',
|
||||
HANGAR_TAKE_5 = 'Hangar_Take_5',
|
||||
HANGAR_TAKE_6 = 'Hangar_Take_6',
|
||||
HANGAR_TAKE_7 = 'Hangar_Take_7',
|
||||
JUNIOR_ACCOUNTANT = 'Junior_Accountant',
|
||||
PERSONNEL_MANAGER = 'Personnel_Manager',
|
||||
PROJECT_MANAGER = 'Project_Manager',
|
||||
RENT_FACTORY_FACILITY = 'Rent_Factory_Facility',
|
||||
RENT_OFFICE = 'Rent_Office',
|
||||
RENT_RESEARCH_FACILITY = 'Rent_Research_Facility',
|
||||
SECURITY_OFFICER = 'Security_Officer',
|
||||
SKILL_PLAN_MANAGER = 'Skill_Plan_Manager',
|
||||
STARBASE_DEFENSE_OPERATOR = 'Starbase_Defense_Operator',
|
||||
STARBASE_FUEL_TECHNICIAN = 'Starbase_Fuel_Technician',
|
||||
STATION_MANAGER = 'Station_Manager',
|
||||
TRADER = 'Trader',
|
||||
}
|
||||
128
packages/eve/src/esi/types/location-flag.ts
Normal file
128
packages/eve/src/esi/types/location-flag.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
export enum LocationFlag {
|
||||
ASSET_SAFETY = 'AssetSafety',
|
||||
AUTOFIT = 'AutoFit',
|
||||
BONUS = 'Bonus',
|
||||
BOOSTER = 'Booster',
|
||||
BOOSTER_BAY = 'BoosterBay',
|
||||
CAPSULE = 'Capsule',
|
||||
CAPSULEER_DELIVERIES = 'CapsuleerDeliveries',
|
||||
CARGO = 'Cargo',
|
||||
CORP_DELIVERIES = 'CorpDeliveries',
|
||||
CORP_SAG1 = 'CorpSAG1',
|
||||
CORP_SAG2 = 'CorpSAG2',
|
||||
CORP_SAG3 = 'CorpSAG3',
|
||||
CORP_SAG4 = 'CorpSAG4',
|
||||
CORP_SAG5 = 'CorpSAG5',
|
||||
CORP_SAG6 = 'CorpSAG6',
|
||||
CORP_SAG7 = 'CorpSAG7',
|
||||
CORPORATION_GOAL_DELIVERIES = 'CorporationGoalDeliveries',
|
||||
CORPSE_BAY = 'CorpseBay',
|
||||
CRATE_LOOT = 'CrateLoot',
|
||||
DELIVERIES = 'Deliveries',
|
||||
DRONE_BAY = 'DroneBay',
|
||||
DUST_BATTLE = 'DustBattle',
|
||||
DUST_DATABANK = 'DustDatabank',
|
||||
EXPEDITION_HOLD = 'ExpeditionHold',
|
||||
FIGHTER_BAY = 'FighterBay',
|
||||
FIGHTER_TUBE_0 = 'FighterTube0',
|
||||
FIGHTER_TUBE_1 = 'FighterTube1',
|
||||
FIGHTER_TUBE_2 = 'FighterTube2',
|
||||
FIGHTER_TUBE_3 = 'FighterTube3',
|
||||
FIGHTER_TUBE_4 = 'FighterTube4',
|
||||
FLEET_HANGAR = 'FleetHangar',
|
||||
FRIGATE_ESCAPE_BAY = 'FrigateEscapeBay',
|
||||
HANGAR = 'Hangar',
|
||||
HANGAR_ALL = 'HangarAll',
|
||||
HI_SLOT_0 = 'HiSlot0',
|
||||
HI_SLOT_1 = 'HiSlot1',
|
||||
HI_SLOT_2 = 'HiSlot2',
|
||||
HI_SLOT_3 = 'HiSlot3',
|
||||
HI_SLOT_4 = 'HiSlot4',
|
||||
HI_SLOT_5 = 'HiSlot5',
|
||||
HI_SLOT_6 = 'HiSlot6',
|
||||
HI_SLOT_7 = 'HiSlot7',
|
||||
HIDDEN_MODIFIERS = 'HiddenModifiers',
|
||||
IMPLANT = 'Implant',
|
||||
IMPOUNDED = 'Impounded',
|
||||
INFRASTRUCTURE_HANGAR = 'InfrastructureHangar',
|
||||
JUNKYARD_REPROCESSED = 'JunkyardReprocessed',
|
||||
JUNKYARD_TRASHED = 'JunkyardTrashed',
|
||||
LO_SLOT_0 = 'LoSlot0',
|
||||
LO_SLOT_1 = 'LoSlot1',
|
||||
LO_SLOT_2 = 'LoSlot2',
|
||||
LO_SLOT_3 = 'LoSlot3',
|
||||
LO_SLOT_4 = 'LoSlot4',
|
||||
LO_SLOT_5 = 'LoSlot5',
|
||||
LO_SLOT_6 = 'LoSlot6',
|
||||
LO_SLOT_7 = 'LoSlot7',
|
||||
LOCKED = 'Locked',
|
||||
MED_SLOT_0 = 'MedSlot0',
|
||||
MED_SLOT_1 = 'MedSlot1',
|
||||
MED_SLOT_2 = 'MedSlot2',
|
||||
MED_SLOT_3 = 'MedSlot3',
|
||||
MED_SLOT_4 = 'MedSlot4',
|
||||
MED_SLOT_5 = 'MedSlot5',
|
||||
MED_SLOT_6 = 'MedSlot6',
|
||||
MED_SLOT_7 = 'MedSlot7',
|
||||
MOBILE_DEPOT_HOLD = 'MobileDepotHold',
|
||||
MOON_MATERIAL_BAY = 'MoonMaterialBay',
|
||||
OFFICE_FOLDER = 'OfficeFolder',
|
||||
PILOT = 'Pilot',
|
||||
PLANET_SURFACE = 'PlanetSurface',
|
||||
QUAFE_BAY = 'QuafeBay',
|
||||
QUANTUM_CORE_ROOM = 'QuantumCoreRoom',
|
||||
REWARD = 'Reward',
|
||||
RIG_SLOT_0 = 'RigSlot0',
|
||||
RIG_SLOT_1 = 'RigSlot1',
|
||||
RIG_SLOT_2 = 'RigSlot2',
|
||||
RIG_SLOT_3 = 'RigSlot3',
|
||||
RIG_SLOT_4 = 'RigSlot4',
|
||||
RIG_SLOT_5 = 'RigSlot5',
|
||||
RIG_SLOT_6 = 'RigSlot6',
|
||||
RIG_SLOT_7 = 'RigSlot7',
|
||||
SECONDARY_STORAGE = 'SecondaryStorage',
|
||||
SERVICE_SLOT_0 = 'ServiceSlot0',
|
||||
SERVICE_SLOT_1 = 'ServiceSlot1',
|
||||
SERVICE_SLOT_2 = 'ServiceSlot2',
|
||||
SERVICE_SLOT_3 = 'ServiceSlot3',
|
||||
SERVICE_SLOT_4 = 'ServiceSlot4',
|
||||
SERVICE_SLOT_5 = 'ServiceSlot5',
|
||||
SERVICE_SLOT_6 = 'ServiceSlot6',
|
||||
SERVICE_SLOT_7 = 'ServiceSlot7',
|
||||
SHIP_HANGAR = 'ShipHangar',
|
||||
SHIP_OFFLINE = 'ShipOffline',
|
||||
SKILL = 'Skill',
|
||||
SKILL_IN_TRAINING = 'SkillInTraining',
|
||||
SPECIALIZED_AMMO_HOLD = 'SpecializedAmmoHold',
|
||||
SPECIALIZED_ASTEROID_HOLD = 'SpecializedAsteroidHold',
|
||||
SPECIALIZED_COMMAND_CENTER_HOLD = 'SpecializedCommandCenterHold',
|
||||
SPECIALIZED_FUEL_BAY = 'SpecializedFuelBay',
|
||||
SPECIALIZED_GAS_HOLD = 'SpecializedGasHold',
|
||||
SPECIALIZED_ICE_HOLD = 'SpecializedIceHold',
|
||||
SPECIALIZED_INDUSTRIAL_SHIP_HOLD = 'SpecializedIndustrialShipHold',
|
||||
SPECIALIZED_LARGE_SHIP_HOLD = 'SpecializedLargeShipHold',
|
||||
SPECIALIZED_MATERIAL_BAY = 'SpecializedMaterialBay',
|
||||
SPECIALIZED_MEDIUM_SHIP_HOLD = 'SpecializedMediumShipHold',
|
||||
SPECIALIZED_MINERAL_HOLD = 'SpecializedMineralHold',
|
||||
SPECIALIZED_ORE_HOLD = 'SpecializedOreHold',
|
||||
SPECIALIZED_PLANETARY_COMMODITIES_HOLD = 'SpecializedPlanetaryCommoditiesHold',
|
||||
SPECIALIZED_SALVAGE_HOLD = 'SpecializedSalvageHold',
|
||||
SPECIALIZED_SHIP_HOLD = 'SpecializedShipHold',
|
||||
SPECIALIZED_SMALL_SHIP_HOLD = 'SpecializedSmallShipHold',
|
||||
STRUCTURE_ACTIVE = 'StructureActive',
|
||||
STRUCTURE_FUEL = 'StructureFuel',
|
||||
STRUCTURE_INACTIVE = 'StructureInactive',
|
||||
STRUCTURE_OFFLINE = 'StructureOffline',
|
||||
SUB_SYSTEM_BAY = 'SubSystemBay',
|
||||
SUB_SYSTEM_SLOT_0 = 'SubSystemSlot0',
|
||||
SUB_SYSTEM_SLOT_1 = 'SubSystemSlot1',
|
||||
SUB_SYSTEM_SLOT_2 = 'SubSystemSlot2',
|
||||
SUB_SYSTEM_SLOT_3 = 'SubSystemSlot3',
|
||||
SUB_SYSTEM_SLOT_4 = 'SubSystemSlot4',
|
||||
SUB_SYSTEM_SLOT_5 = 'SubSystemSlot5',
|
||||
SUB_SYSTEM_SLOT_6 = 'SubSystemSlot6',
|
||||
SUB_SYSTEM_SLOT_7 = 'SubSystemSlot7',
|
||||
UNLOCKED = 'Unlocked',
|
||||
WALLET = 'Wallet',
|
||||
WARDROBE = 'Wardrobe',
|
||||
}
|
||||
249
packages/eve/src/esi/types/notification-type.ts
Normal file
249
packages/eve/src/esi/types/notification-type.ts
Normal file
@@ -0,0 +1,249 @@
|
||||
export enum NotificationType {
|
||||
ACCEPTED_ALLY = 'AcceptedAlly',
|
||||
ACCEPTED_SURRENDER = 'AcceptedSurrender',
|
||||
AGENT_RETIRED_TRIGRAVIAN = 'AgentRetiredTrigravian',
|
||||
ALL_ANCHORING_MSG = 'AllAnchoringMsg',
|
||||
ALL_MAINTENANCE_BILL_MSG = 'AllMaintenanceBillMsg',
|
||||
ALL_STRUC_INVULNERABLE_MSG = 'AllStrucInvulnerableMsg',
|
||||
ALL_STRUCT_VULNERABLE_MSG = 'AllStructVulnerableMsg',
|
||||
ALL_WAR_CORP_JOINED_ALLIANCE_MSG = 'AllWarCorpJoinedAllianceMsg',
|
||||
ALL_WAR_DECLARED_MSG = 'AllWarDeclaredMsg',
|
||||
ALL_WAR_INVALIDATED_MSG = 'AllWarInvalidatedMsg',
|
||||
ALL_WAR_RETRACTED_MSG = 'AllWarRetractedMsg',
|
||||
ALL_WAR_SURRENDER_MSG = 'AllWarSurrenderMsg',
|
||||
ALLIANCE_CAPITAL_CHANGED = 'AllianceCapitalChanged',
|
||||
ALLIANCE_WAR_DECLARED_V2 = 'AllianceWarDeclaredV2',
|
||||
ALLY_CONTRACT_CANCELLED = 'AllyContractCancelled',
|
||||
ALLY_JOINED_WAR_AGGRESSOR_MSG = 'AllyJoinedWarAggressorMsg',
|
||||
ALLY_JOINED_WAR_ALLY_MSG = 'AllyJoinedWarAllyMsg',
|
||||
ALLY_JOINED_WAR_DEFENDER_MSG = 'AllyJoinedWarDefenderMsg',
|
||||
BATTLE_PUNISH_FRIENDLY_FIRE = 'BattlePunishFriendlyFire',
|
||||
BILL_OUT_OF_MONEY_MSG = 'BillOutOfMoneyMsg',
|
||||
BILL_PAID_CORP_ALL_MSG = 'BillPaidCorpAllMsg',
|
||||
BOUNTY_CLAIM_MSG = 'BountyClaimMsg',
|
||||
BOUNTY_ESS_SHARED = 'BountyESSShared',
|
||||
BOUNTY_ESS_TAKEN = 'BountyESSTaken',
|
||||
BOUNTY_PLACED_ALLIANCE = 'BountyPlacedAlliance',
|
||||
BOUNTY_PLACED_CHAR = 'BountyPlacedChar',
|
||||
BOUNTY_PLACED_CORP = 'BountyPlacedCorp',
|
||||
BOUNTY_YOUR_BOUNTY_CLAIMED = 'BountyYourBountyClaimed',
|
||||
BUDDY_CONNECT_CONTACT_ADD = 'BuddyConnectContactAdd',
|
||||
CHAR_APP_ACCEPT_MSG = 'CharAppAcceptMsg',
|
||||
CHAR_APP_REJECT_MSG = 'CharAppRejectMsg',
|
||||
CHAR_APP_WITHDRAW_MSG = 'CharAppWithdrawMsg',
|
||||
CHAR_LEFT_CORP_MSG = 'CharLeftCorpMsg',
|
||||
CHAR_MEDAL_MSG = 'CharMedalMsg',
|
||||
CHAR_TERMINATION_MSG = 'CharTerminationMsg',
|
||||
CLONE_ACTIVATION_MSG = 'CloneActivationMsg',
|
||||
CLONE_ACTIVATION_MSG2 = 'CloneActivationMsg2',
|
||||
CLONE_MOVED_MSG = 'CloneMovedMsg',
|
||||
CLONE_REVOKED_MSG1 = 'CloneRevokedMsg1',
|
||||
CLONE_REVOKED_MSG2 = 'CloneRevokedMsg2',
|
||||
COMBAT_OPERATION_FINISHED = 'CombatOperationFinished',
|
||||
CONTACT_ADD = 'ContactAdd',
|
||||
CONTACT_EDIT = 'ContactEdit',
|
||||
CONTAINER_PASSWORD_MSG = 'ContainerPasswordMsg',
|
||||
CONTRACT_REGION_CHANGED_TO_POCHVEN = 'ContractRegionChangedToPochven',
|
||||
CORP_ALL_BILL_MSG = 'CorpAllBillMsg',
|
||||
CORP_APP_ACCEPT_MSG = 'CorpAppAcceptMsg',
|
||||
CORP_APP_INVITED_MSG = 'CorpAppInvitedMsg',
|
||||
CORP_APP_NEW_MSG = 'CorpAppNewMsg',
|
||||
CORP_APP_REJECT_CUSTOM_MSG = 'CorpAppRejectCustomMsg',
|
||||
CORP_APP_REJECT_MSG = 'CorpAppRejectMsg',
|
||||
CORP_BECAME_WAR_ELIGIBLE = 'CorpBecameWarEligible',
|
||||
CORP_DIVIDEND_MSG = 'CorpDividendMsg',
|
||||
CORP_FRIENDLY_FIRE_DISABLE_TIMER_COMPLETED = 'CorpFriendlyFireDisableTimerCompleted',
|
||||
CORP_FRIENDLY_FIRE_DISABLE_TIMER_STARTED = 'CorpFriendlyFireDisableTimerStarted',
|
||||
CORP_FRIENDLY_FIRE_ENABLE_TIMER_COMPLETED = 'CorpFriendlyFireEnableTimerCompleted',
|
||||
CORP_FRIENDLY_FIRE_ENABLE_TIMER_STARTED = 'CorpFriendlyFireEnableTimerStarted',
|
||||
CORP_KICKED = 'CorpKicked',
|
||||
CORP_LIQUIDATION_MSG = 'CorpLiquidationMsg',
|
||||
CORP_NEW_CEO_MSG = 'CorpNewCEOMsg',
|
||||
CORP_NEWS_MSG = 'CorpNewsMsg',
|
||||
CORP_NO_LONGER_WAR_ELIGIBLE = 'CorpNoLongerWarEligible',
|
||||
CORP_OFFICE_EXPIRATION_MSG = 'CorpOfficeExpirationMsg',
|
||||
CORP_STRUCT_LOST_MSG = 'CorpStructLostMsg',
|
||||
CORP_TAX_CHANGE_MSG = 'CorpTaxChangeMsg',
|
||||
CORP_VOTE_CEO_REVOKED_MSG = 'CorpVoteCEORevokedMsg',
|
||||
CORP_VOTE_MSG = 'CorpVoteMsg',
|
||||
CORP_WAR_DECLARED_MSG = 'CorpWarDeclaredMsg',
|
||||
CORP_WAR_DECLARED_V2 = 'CorpWarDeclaredV2',
|
||||
CORP_WAR_FIGHTING_LEGAL_MSG = 'CorpWarFightingLegalMsg',
|
||||
CORP_WAR_INVALIDATED_MSG = 'CorpWarInvalidatedMsg',
|
||||
CORP_WAR_RETRACTED_MSG = 'CorpWarRetractedMsg',
|
||||
CORP_WAR_SURRENDER_MSG = 'CorpWarSurrenderMsg',
|
||||
CORPORATION_GOAL_CLOSED = 'CorporationGoalClosed',
|
||||
CORPORATION_GOAL_COMPLETED = 'CorporationGoalCompleted',
|
||||
CORPORATION_GOAL_CREATED = 'CorporationGoalCreated',
|
||||
CORPORATION_GOAL_EXPIRED = 'CorporationGoalExpired',
|
||||
CORPORATION_GOAL_LIMIT_REACHED = 'CorporationGoalLimitReached',
|
||||
CORPORATION_GOAL_NAME_CHANGE = 'CorporationGoalNameChange',
|
||||
CORPORATION_LEFT = 'CorporationLeft',
|
||||
CUSTOMS_MSG = 'CustomsMsg',
|
||||
DAILY_ITEM_REWARD_AUTO_CLAIMED = 'DailyItemRewardAutoClaimed',
|
||||
DECLARE_WAR = 'DeclareWar',
|
||||
DISTRICT_ATTACKED = 'DistrictAttacked',
|
||||
DUST_APP_ACCEPTED_MSG = 'DustAppAcceptedMsg',
|
||||
ESS_MAIN_BANK_LINK = 'ESSMainBankLink',
|
||||
ENTOSIS_CAPTURE_STARTED = 'EntosisCaptureStarted',
|
||||
EXPERT_SYSTEM_EXPIRED = 'ExpertSystemExpired',
|
||||
EXPERT_SYSTEM_EXPIRY_IMMINENT = 'ExpertSystemExpiryImminent',
|
||||
FW_ALLIANCE_KICK_MSG = 'FWAllianceKickMsg',
|
||||
FW_ALLIANCE_WARNING_MSG = 'FWAllianceWarningMsg',
|
||||
FW_CHAR_KICK_MSG = 'FWCharKickMsg',
|
||||
FW_CHAR_RANK_GAIN_MSG = 'FWCharRankGainMsg',
|
||||
FW_CHAR_RANK_LOSS_MSG = 'FWCharRankLossMsg',
|
||||
FW_CHAR_WARNING_MSG = 'FWCharWarningMsg',
|
||||
FW_CORP_JOIN_MSG = 'FWCorpJoinMsg',
|
||||
FW_CORP_KICK_MSG = 'FWCorpKickMsg',
|
||||
FW_CORP_LEAVE_MSG = 'FWCorpLeaveMsg',
|
||||
FW_CORP_WARNING_MSG = 'FWCorpWarningMsg',
|
||||
FAC_WAR_CORP_JOIN_REQUEST_MSG = 'FacWarCorpJoinRequestMsg',
|
||||
FAC_WAR_CORP_JOIN_WITHDRAW_MSG = 'FacWarCorpJoinWithdrawMsg',
|
||||
FAC_WAR_CORP_LEAVE_REQUEST_MSG = 'FacWarCorpLeaveRequestMsg',
|
||||
FAC_WAR_CORP_LEAVE_WITHDRAW_MSG = 'FacWarCorpLeaveWithdrawMsg',
|
||||
FAC_WAR_DIRECT_ENLISTMENT_REVOKED = 'FacWarDirectEnlistmentRevoked',
|
||||
FAC_WAR_LP_DISQUALIFIED_EVENT = 'FacWarLPDisqualifiedEvent',
|
||||
FAC_WAR_LP_DISQUALIFIED_KILL = 'FacWarLPDisqualifiedKill',
|
||||
FAC_WAR_LP_PAYOUT_EVENT = 'FacWarLPPayoutEvent',
|
||||
FAC_WAR_LP_PAYOUT_KILL = 'FacWarLPPayoutKill',
|
||||
FREELANCE_PROJECT_CLOSED = 'FreelanceProjectClosed',
|
||||
FREELANCE_PROJECT_COMPLETED = 'FreelanceProjectCompleted',
|
||||
FREELANCE_PROJECT_CREATED = 'FreelanceProjectCreated',
|
||||
FREELANCE_PROJECT_EXPIRED = 'FreelanceProjectExpired',
|
||||
FREELANCE_PROJECT_LIMIT_REACHED = 'FreelanceProjectLimitReached',
|
||||
FREELANCE_PROJECT_PARTICIPANT_KICKED = 'FreelanceProjectParticipantKicked',
|
||||
GAME_TIME_ADDED = 'GameTimeAdded',
|
||||
GAME_TIME_RECEIVED = 'GameTimeReceived',
|
||||
GAME_TIME_SENT = 'GameTimeSent',
|
||||
GIFT_RECEIVED = 'GiftReceived',
|
||||
I_HUB_DESTROYED_BY_BILL_FAILURE = 'IHubDestroyedByBillFailure',
|
||||
INCURSION_COMPLETED_MSG = 'IncursionCompletedMsg',
|
||||
INDUSTRY_OPERATION_FINISHED = 'IndustryOperationFinished',
|
||||
INDUSTRY_TEAM_AUCTION_LOST = 'IndustryTeamAuctionLost',
|
||||
INDUSTRY_TEAM_AUCTION_WON = 'IndustryTeamAuctionWon',
|
||||
INFRASTRUCTURE_HUB_BILL_ABOUT_TO_EXPIRE = 'InfrastructureHubBillAboutToExpire',
|
||||
INSURANCE_EXPIRATION_MSG = 'InsuranceExpirationMsg',
|
||||
INSURANCE_FIRST_SHIP_MSG = 'InsuranceFirstShipMsg',
|
||||
INSURANCE_INVALIDATED_MSG = 'InsuranceInvalidatedMsg',
|
||||
INSURANCE_ISSUED_MSG = 'InsuranceIssuedMsg',
|
||||
INSURANCE_PAYOUT_MSG = 'InsurancePayoutMsg',
|
||||
INVASION_COMPLETED_MSG = 'InvasionCompletedMsg',
|
||||
INVASION_SYSTEM_LOGIN = 'InvasionSystemLogin',
|
||||
INVASION_SYSTEM_START = 'InvasionSystemStart',
|
||||
JUMP_CLONE_DELETED_MSG1 = 'JumpCloneDeletedMsg1',
|
||||
JUMP_CLONE_DELETED_MSG2 = 'JumpCloneDeletedMsg2',
|
||||
KILL_REPORT_FINAL_BLOW = 'KillReportFinalBlow',
|
||||
KILL_REPORT_VICTIM = 'KillReportVictim',
|
||||
KILL_RIGHT_AVAILABLE = 'KillRightAvailable',
|
||||
KILL_RIGHT_AVAILABLE_OPEN = 'KillRightAvailableOpen',
|
||||
KILL_RIGHT_EARNED = 'KillRightEarned',
|
||||
KILL_RIGHT_UNAVAILABLE = 'KillRightUnavailable',
|
||||
KILL_RIGHT_UNAVAILABLE_OPEN = 'KillRightUnavailableOpen',
|
||||
KILL_RIGHT_USED = 'KillRightUsed',
|
||||
LP_AUTO_REDEEMED = 'LPAutoRedeemed',
|
||||
LOCATE_CHAR_MSG = 'LocateCharMsg',
|
||||
MADE_WAR_MUTUAL = 'MadeWarMutual',
|
||||
MERC_OFFER_RETRACTED_MSG = 'MercOfferRetractedMsg',
|
||||
MERC_OFFERED_NEGOTIATION_MSG = 'MercOfferedNegotiationMsg',
|
||||
MERCENARY_DEN_ATTACKED = 'MercenaryDenAttacked',
|
||||
MERCENARY_DEN_NEW_MTO = 'MercenaryDenNewMTO',
|
||||
MERCENARY_DEN_REINFORCED = 'MercenaryDenReinforced',
|
||||
MISSION_CANCELED_TRIGLAVIAN = 'MissionCanceledTriglavian',
|
||||
MISSION_OFFER_EXPIRATION_MSG = 'MissionOfferExpirationMsg',
|
||||
MISSION_TIMEOUT_MSG = 'MissionTimeoutMsg',
|
||||
MOONMINING_AUTOMATIC_FRACTURE = 'MoonminingAutomaticFracture',
|
||||
MOONMINING_EXTRACTION_CANCELLED = 'MoonminingExtractionCancelled',
|
||||
MOONMINING_EXTRACTION_FINISHED = 'MoonminingExtractionFinished',
|
||||
MOONMINING_EXTRACTION_STARTED = 'MoonminingExtractionStarted',
|
||||
MOONMINING_LASER_FIRED = 'MoonminingLaserFired',
|
||||
MUTUAL_WAR_EXPIRED = 'MutualWarExpired',
|
||||
MUTUAL_WAR_INVITE_ACCEPTED = 'MutualWarInviteAccepted',
|
||||
MUTUAL_WAR_INVITE_REJECTED = 'MutualWarInviteRejected',
|
||||
MUTUAL_WAR_INVITE_SENT = 'MutualWarInviteSent',
|
||||
NPC_STANDINGS_GAINED = 'NPCStandingsGained',
|
||||
NPC_STANDINGS_LOST = 'NPCStandingsLost',
|
||||
OFFER_TO_ALLY_RETRACTED = 'OfferToAllyRetracted',
|
||||
OFFERED_SURRENDER = 'OfferedSurrender',
|
||||
OFFERED_TO_ALLY = 'OfferedToAlly',
|
||||
OFFICE_LEASE_CANCELED_INSUFFICIENT_STANDINGS = 'OfficeLeaseCanceledInsufficientStandings',
|
||||
OLD_LSC_MESSAGES = 'OldLscMessages',
|
||||
OPERATION_FINISHED = 'OperationFinished',
|
||||
ORBITAL_ATTACKED = 'OrbitalAttacked',
|
||||
ORBITAL_REINFORCED = 'OrbitalReinforced',
|
||||
OWNERSHIP_TRANSFERRED = 'OwnershipTransferred',
|
||||
RAFFLE_CREATED = 'RaffleCreated',
|
||||
RAFFLE_EXPIRED = 'RaffleExpired',
|
||||
RAFFLE_FINISHED = 'RaffleFinished',
|
||||
REIMBURSEMENT_MSG = 'ReimbursementMsg',
|
||||
RESEARCH_MISSION_AVAILABLE_MSG = 'ResearchMissionAvailableMsg',
|
||||
RETRACTS_WAR = 'RetractsWar',
|
||||
SP_AUTO_REDEEMED = 'SPAutoRedeemed',
|
||||
SEASONAL_CHALLENGE_COMPLETED = 'SeasonalChallengeCompleted',
|
||||
SKIN_SEQUENCING_COMPLETED = 'SkinSequencingCompleted',
|
||||
SKYHOOK_DEPLOYED = 'SkyhookDeployed',
|
||||
SKYHOOK_DESTROYED = 'SkyhookDestroyed',
|
||||
SKYHOOK_LOST_SHIELDS = 'SkyhookLostShields',
|
||||
SKYHOOK_ONLINE = 'SkyhookOnline',
|
||||
SKYHOOK_UNDER_ATTACK = 'SkyhookUnderAttack',
|
||||
SOV_ALL_CLAIM_ACQUIRED_MSG = 'SovAllClaimAquiredMsg',
|
||||
SOV_ALL_CLAIM_LOST_MSG = 'SovAllClaimLostMsg',
|
||||
SOV_COMMAND_NODE_EVENT_STARTED = 'SovCommandNodeEventStarted',
|
||||
SOV_CORP_BILL_LATE_MSG = 'SovCorpBillLateMsg',
|
||||
SOV_CORP_CLAIM_FAIL_MSG = 'SovCorpClaimFailMsg',
|
||||
SOV_DISRUPTOR_MSG = 'SovDisruptorMsg',
|
||||
SOV_STATION_ENTERED_FREEPORT = 'SovStationEnteredFreeport',
|
||||
SOV_STRUCTURE_DESTROYED = 'SovStructureDestroyed',
|
||||
SOV_STRUCTURE_REINFORCED = 'SovStructureReinforced',
|
||||
SOV_STRUCTURE_SELF_DESTRUCT_CANCEL = 'SovStructureSelfDestructCancel',
|
||||
SOV_STRUCTURE_SELF_DESTRUCT_FINISHED = 'SovStructureSelfDestructFinished',
|
||||
SOV_STRUCTURE_SELF_DESTRUCT_REQUESTED = 'SovStructureSelfDestructRequested',
|
||||
SOVEREIGNTY_IH_DAMAGE_MSG = 'SovereigntyIHDamageMsg',
|
||||
SOVEREIGNTY_SBU_DAMAGE_MSG = 'SovereigntySBUDamageMsg',
|
||||
SOVEREIGNTY_TCU_DAMAGE_MSG = 'SovereigntyTCUDamageMsg',
|
||||
STATION_AGGRESSION_MSG1 = 'StationAggressionMsg1',
|
||||
STATION_AGGRESSION_MSG2 = 'StationAggressionMsg2',
|
||||
STATION_CONQUER_MSG = 'StationConquerMsg',
|
||||
STATION_SERVICE_DISABLED = 'StationServiceDisabled',
|
||||
STATION_SERVICE_ENABLED = 'StationServiceEnabled',
|
||||
STATION_STATE_CHANGE_MSG = 'StationStateChangeMsg',
|
||||
STORY_LINE_MISSION_AVAILABLE_MSG = 'StoryLineMissionAvailableMsg',
|
||||
STRUCTURE_ANCHORING = 'StructureAnchoring',
|
||||
STRUCTURE_COURIER_CONTRACT_CHANGED = 'StructureCourierContractChanged',
|
||||
STRUCTURE_DESTROYED = 'StructureDestroyed',
|
||||
STRUCTURE_FUEL_ALERT = 'StructureFuelAlert',
|
||||
STRUCTURE_IMPENDING_ABANDONMENT_ASSETS_AT_RISK = 'StructureImpendingAbandonmentAssetsAtRisk',
|
||||
STRUCTURE_ITEMS_DELIVERED = 'StructureItemsDelivered',
|
||||
STRUCTURE_ITEMS_MOVED_TO_SAFETY = 'StructureItemsMovedToSafety',
|
||||
STRUCTURE_LOST_ARMOR = 'StructureLostArmor',
|
||||
STRUCTURE_LOST_SHIELDS = 'StructureLostShields',
|
||||
STRUCTURE_LOW_REAGENTS_ALERT = 'StructureLowReagentsAlert',
|
||||
STRUCTURE_NO_REAGENTS_ALERT = 'StructureNoReagentsAlert',
|
||||
STRUCTURE_ONLINE = 'StructureOnline',
|
||||
STRUCTURE_PAINT_PURCHASED = 'StructurePaintPurchased',
|
||||
STRUCTURE_SERVICES_OFFLINE = 'StructureServicesOffline',
|
||||
STRUCTURE_UNANCHORING = 'StructureUnanchoring',
|
||||
STRUCTURE_UNDER_ATTACK = 'StructureUnderAttack',
|
||||
STRUCTURE_WENT_HIGH_POWER = 'StructureWentHighPower',
|
||||
STRUCTURE_WENT_LOW_POWER = 'StructureWentLowPower',
|
||||
STRUCTURES_JOBS_CANCELLED = 'StructuresJobsCancelled',
|
||||
STRUCTURES_JOBS_PAUSED = 'StructuresJobsPaused',
|
||||
STRUCTURES_REINFORCEMENT_CHANGED = 'StructuresReinforcementChanged',
|
||||
TOWER_ALERT_MSG = 'TowerAlertMsg',
|
||||
TOWER_RESOURCE_ALERT_MSG = 'TowerResourceAlertMsg',
|
||||
TRANSACTION_REVERSAL_MSG = 'TransactionReversalMsg',
|
||||
TUTORIAL_MSG = 'TutorialMsg',
|
||||
WAR_ADOPTED = 'WarAdopted',
|
||||
WAR_ALLY_INHERITED = 'WarAllyInherited',
|
||||
WAR_ALLY_OFFER_DECLINED_MSG = 'WarAllyOfferDeclinedMsg',
|
||||
WAR_CONCORD_INVALIDATES = 'WarConcordInvalidates',
|
||||
WAR_DECLARED = 'WarDeclared',
|
||||
WAR_ENDED_HQ_SECURITY_DROP = 'WarEndedHqSecurityDrop',
|
||||
WAR_HQ_REMOVED_FROM_SPACE = 'WarHQRemovedFromSpace',
|
||||
WAR_INHERITED = 'WarInherited',
|
||||
WAR_INVALID = 'WarInvalid',
|
||||
WAR_RETRACTED = 'WarRetracted',
|
||||
WAR_RETRACTED_BY_CONCORD = 'WarRetractedByConcord',
|
||||
WAR_SURRENDER_DECLINED_MSG = 'WarSurrenderDeclinedMsg',
|
||||
WAR_SURRENDER_OFFER_MSG = 'WarSurrenderOfferMsg',
|
||||
}
|
||||
26
packages/eve/src/esi/types/shared.ts
Normal file
26
packages/eve/src/esi/types/shared.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { LocationFlag } from './location-flag';
|
||||
|
||||
export enum STANDING {
|
||||
HOSTILE = -10,
|
||||
UNFRIENDLY = -5,
|
||||
NEUTRAL = 0,
|
||||
FRIENDLY = 5,
|
||||
ALLY = 10,
|
||||
}
|
||||
|
||||
export interface Blueprint {
|
||||
item_id: number;
|
||||
location_flag: LocationFlag;
|
||||
location_id: number;
|
||||
material_efficiency: number;
|
||||
quantity: number;
|
||||
runs: number;
|
||||
time_efficiency: number;
|
||||
type_id: number;
|
||||
}
|
||||
|
||||
export interface Icons {
|
||||
px256x256: string;
|
||||
px128x128: string;
|
||||
px64x64: string;
|
||||
}
|
||||
1
packages/eve/src/esi/universe.ts
Normal file
1
packages/eve/src/esi/universe.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
15
packages/eve/src/esi/util/cache.ts
Normal file
15
packages/eve/src/esi/util/cache.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Simple caching for ESI fetches using bun sqlite key-value store.
|
||||
*/
|
||||
import * as kv from '@star-kitten/util/kv';
|
||||
|
||||
export const defaultCacheDuration = 1000 * 60 * 30; // 30 minutes
|
||||
|
||||
export function set(key: string, data: any, expires: number = Date.now() + defaultCacheDuration) {
|
||||
kv.setExact(`esi_cache_${key}`, data, expires);
|
||||
}
|
||||
|
||||
export function get<T>(key: string): T | undefined {
|
||||
const item = kv.get<T>(`esi_cache_${key}`);
|
||||
return undefined;
|
||||
}
|
||||
115
packages/eve/src/esi/util/fetch.ts
Normal file
115
packages/eve/src/esi/util/fetch.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import * as cache from './cache';
|
||||
import { options } from './options';
|
||||
import { ESI_SCOPE, extractCharacterInfoFromToken, isValidToken, tokenHasScopes } from '@/oauth';
|
||||
import { ESI_RATE_LIMIT_GROUP, rateLimitedFetch } from './rate-limits';
|
||||
|
||||
export const ESI_BASE_URL = 'https://esi.evetech.net';
|
||||
const compatibility_date = '2026-01-04';
|
||||
|
||||
export enum Language {
|
||||
EN = 'en',
|
||||
DE = 'de',
|
||||
FR = 'fr',
|
||||
JA = 'ja',
|
||||
RU = 'ru',
|
||||
ZH = 'zh',
|
||||
KO = 'ko',
|
||||
ES = 'es',
|
||||
}
|
||||
|
||||
export interface PublicEsiOptions {
|
||||
language?: Language;
|
||||
noCache?: boolean;
|
||||
cacheDuration?: number; // default 30 minutes
|
||||
}
|
||||
|
||||
export interface EsiOptions extends PublicEsiOptions {
|
||||
access_token?: string;
|
||||
}
|
||||
|
||||
interface RequestOptions extends Partial<EsiOptions & RequestInit> {
|
||||
rateLimitGroup?: ESI_RATE_LIMIT_GROUP;
|
||||
}
|
||||
|
||||
export function checkScopesAndGetCharacterId(options: EsiOptions, scope: ESI_SCOPE): number {
|
||||
if (!tokenHasScopes(options, scope)) {
|
||||
throw `Required scope '${scope}' not present in token`;
|
||||
}
|
||||
const character_id = extractCharacterInfoFromToken(options)?.id;
|
||||
if (!character_id) {
|
||||
throw 'Character ID could not be determined from access token';
|
||||
}
|
||||
return character_id;
|
||||
}
|
||||
|
||||
export async function esiFetch<T = any>(
|
||||
path: string,
|
||||
{
|
||||
access_token,
|
||||
method = 'GET',
|
||||
body,
|
||||
noCache = false,
|
||||
cacheDuration,
|
||||
rateLimitGroup = ESI_RATE_LIMIT_GROUP.DEFAULT,
|
||||
language = Language.EN,
|
||||
}: Partial<RequestOptions> = {},
|
||||
) {
|
||||
try {
|
||||
const headers = {
|
||||
'User-Agent': options.user_agent,
|
||||
'Accept': 'application/json',
|
||||
'Accept-Language': language,
|
||||
'X-Compatibility-Date': compatibility_date,
|
||||
};
|
||||
|
||||
if (access_token && !isValidToken({ access_token })) {
|
||||
throw 'Invalid or expired EVE tokens provided for ESI request';
|
||||
}
|
||||
|
||||
let character_id: number | undefined;
|
||||
if (access_token) {
|
||||
headers['Authorization'] = `Bearer ${access_token}`;
|
||||
character_id = extractCharacterInfoFromToken({ access_token }).id;
|
||||
}
|
||||
|
||||
const init: RequestInit = {
|
||||
headers,
|
||||
method: method,
|
||||
body: body,
|
||||
};
|
||||
|
||||
const url = new URL(`${ESI_BASE_URL}${path.startsWith('/') ? path : '/' + path}`);
|
||||
url.searchParams.set('datasource', 'tranquility');
|
||||
|
||||
if (!noCache && init.method === 'GET') {
|
||||
const cached = cache.get<T>(url.href);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
|
||||
const res = await rateLimitedFetch(() => fetch(url, init), rateLimitGroup, character_id);
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
console.error(`ESI request failure at ${path} | ${res.status}:${res.statusText} => ${JSON.stringify(data)}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (init.method === 'GET') {
|
||||
cache.set(
|
||||
url.href,
|
||||
data,
|
||||
Math.max(
|
||||
(res.headers.get('expires') && new Date(Number(res.headers.get('expires') || '')).getTime()) || 0,
|
||||
Date.now() + cacheDuration,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return data as T;
|
||||
} catch (err) {
|
||||
console.error(`ESI request failure at ${path} | ${JSON.stringify(err)}`, err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
32
packages/eve/src/esi/util/options.ts
Normal file
32
packages/eve/src/esi/util/options.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* EVE Online ESI Authentication Options Module
|
||||
*
|
||||
* This module defines and initializes the configuration options required
|
||||
* for authenticating with EVE Online's ESI API. It reads from environment
|
||||
* variables by default and allows for custom overrides.
|
||||
*/
|
||||
export interface EveAuthOptions {
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
callback_url: string;
|
||||
user_agent: string;
|
||||
}
|
||||
|
||||
const CLIENT_ID = process.env.EVE_CLIENT_ID || '';
|
||||
const CLIENT_SECRET = process.env.EVE_CLIENT_SECRET || '';
|
||||
const CALLBACK_URL = process.env.EVE_CALLBACK_URL || '';
|
||||
const USER_AGENT = process.env.ESI_USER_AGENT || '';
|
||||
|
||||
export const options: EveAuthOptions = {
|
||||
client_id: CLIENT_ID,
|
||||
client_secret: CLIENT_SECRET,
|
||||
callback_url: CALLBACK_URL,
|
||||
user_agent: USER_AGENT,
|
||||
};
|
||||
|
||||
export function initESI(customOptions: Partial<EveAuthOptions>) {
|
||||
options.client_id = customOptions.client_id || options.client_id;
|
||||
options.client_secret = customOptions.client_secret || options.client_secret;
|
||||
options.callback_url = customOptions.callback_url || options.callback_url;
|
||||
options.user_agent = customOptions.user_agent || options.user_agent;
|
||||
}
|
||||
251
packages/eve/src/esi/util/rate-limits.ts
Normal file
251
packages/eve/src/esi/util/rate-limits.ts
Normal file
@@ -0,0 +1,251 @@
|
||||
/**
|
||||
* ESI Rate Limiting Module
|
||||
*
|
||||
* This module implements rate limiting for EVE Online's ESI API based on predefined
|
||||
* rate limit groups. Each group has its own limits and windows, and requests are
|
||||
* tracked to ensure compliance with ESI's rate limiting policies.
|
||||
*
|
||||
* ref: https://developers.eveonline.com/docs/services/esi/rate-limiting/
|
||||
*/
|
||||
export enum ESI_RATE_LIMIT_GROUP {
|
||||
UNAUTHENTICATED = 'unauthenticated',
|
||||
DEFAULT = 'default',
|
||||
CHAR_DETAIL = 'char-detail',
|
||||
CHAR_ASSET = 'char-asset',
|
||||
CHAR_SOCIAL = 'char-social',
|
||||
CHAR_CONTRACT = 'char-contract',
|
||||
CHAR_INDUSTRY = 'char-industry',
|
||||
CHAR_LOCATION = 'char-location',
|
||||
CHAR_NOTIFICATION = 'char-notification',
|
||||
CORP_ASSET = 'corp-asset',
|
||||
CORP_SOCIAL = 'corp-social',
|
||||
CORP_CONTRACT = 'corp-contract',
|
||||
CORP_INDUSTRY = 'corp-industry',
|
||||
CORP_WALLET = 'corp-wallet',
|
||||
CORP_DETAIL = 'corp-detail',
|
||||
CORP_MEMBER = 'corp-member',
|
||||
CORP_PROJECT = 'corp-project',
|
||||
ALLIANCE_SOCIAL = 'alliance-social',
|
||||
}
|
||||
|
||||
interface RateLimitGroup {
|
||||
type: ESI_RATE_LIMIT_GROUP;
|
||||
limit: number; // max tokens per window
|
||||
window: number; // in seconds
|
||||
countOnlyErrors?: boolean; // whether to count only non 2xx/3xx responses
|
||||
}
|
||||
|
||||
const ESI_GROUP_LIMITS: Record<ESI_RATE_LIMIT_GROUP, RateLimitGroup> = {
|
||||
[ESI_RATE_LIMIT_GROUP.UNAUTHENTICATED]: {
|
||||
type: ESI_RATE_LIMIT_GROUP.UNAUTHENTICATED,
|
||||
limit: 100, // only counts non 2xx/3xx responses
|
||||
window: 60 * 1, // 1 minute
|
||||
countOnlyErrors: true,
|
||||
},
|
||||
[ESI_RATE_LIMIT_GROUP.DEFAULT]: {
|
||||
type: ESI_RATE_LIMIT_GROUP.DEFAULT,
|
||||
limit: 100, // only counts non 2xx/3xx responses
|
||||
window: 60 * 1, // 1 minute
|
||||
countOnlyErrors: true,
|
||||
},
|
||||
[ESI_RATE_LIMIT_GROUP.CHAR_DETAIL]: {
|
||||
type: ESI_RATE_LIMIT_GROUP.CHAR_DETAIL,
|
||||
limit: 600,
|
||||
window: 60 * 15, // 15 minutes
|
||||
},
|
||||
[ESI_RATE_LIMIT_GROUP.CHAR_ASSET]: {
|
||||
type: ESI_RATE_LIMIT_GROUP.CHAR_ASSET,
|
||||
limit: 1800,
|
||||
window: 60 * 15, // 15 minutes
|
||||
},
|
||||
[ESI_RATE_LIMIT_GROUP.CORP_ASSET]: {
|
||||
type: ESI_RATE_LIMIT_GROUP.CORP_ASSET,
|
||||
limit: 1800,
|
||||
window: 60 * 15, // 15 minutes
|
||||
},
|
||||
[ESI_RATE_LIMIT_GROUP.CHAR_SOCIAL]: {
|
||||
type: ESI_RATE_LIMIT_GROUP.CHAR_SOCIAL,
|
||||
limit: 600,
|
||||
window: 60 * 15, // 15 minutes
|
||||
},
|
||||
[ESI_RATE_LIMIT_GROUP.CHAR_INDUSTRY]: {
|
||||
type: ESI_RATE_LIMIT_GROUP.CHAR_INDUSTRY,
|
||||
limit: 600,
|
||||
window: 60 * 15, // 15 minutes
|
||||
},
|
||||
[ESI_RATE_LIMIT_GROUP.CHAR_LOCATION]: {
|
||||
type: ESI_RATE_LIMIT_GROUP.CHAR_LOCATION,
|
||||
limit: 1200,
|
||||
window: 60 * 15, // 15 minutes
|
||||
},
|
||||
[ESI_RATE_LIMIT_GROUP.CHAR_NOTIFICATION]: {
|
||||
type: ESI_RATE_LIMIT_GROUP.CHAR_NOTIFICATION,
|
||||
limit: 15,
|
||||
window: 60 * 15, // 15 minutes
|
||||
},
|
||||
[ESI_RATE_LIMIT_GROUP.ALLIANCE_SOCIAL]: {
|
||||
type: ESI_RATE_LIMIT_GROUP.ALLIANCE_SOCIAL,
|
||||
limit: 300,
|
||||
window: 60 * 15, // 15 minutes
|
||||
},
|
||||
[ESI_RATE_LIMIT_GROUP.CORP_SOCIAL]: {
|
||||
type: ESI_RATE_LIMIT_GROUP.CORP_SOCIAL,
|
||||
limit: 300,
|
||||
window: 60 * 15, // 15 minutes
|
||||
},
|
||||
[ESI_RATE_LIMIT_GROUP.CHAR_CONTRACT]: {
|
||||
type: ESI_RATE_LIMIT_GROUP.CHAR_CONTRACT,
|
||||
limit: 600,
|
||||
window: 60 * 15, // 15 minutes
|
||||
},
|
||||
[ESI_RATE_LIMIT_GROUP.CORP_CONTRACT]: {
|
||||
type: ESI_RATE_LIMIT_GROUP.CORP_CONTRACT,
|
||||
limit: 600,
|
||||
window: 60 * 15, // 15 minutes
|
||||
},
|
||||
[ESI_RATE_LIMIT_GROUP.CORP_INDUSTRY]: {
|
||||
type: ESI_RATE_LIMIT_GROUP.CORP_INDUSTRY,
|
||||
limit: 600,
|
||||
window: 60 * 15, // 15 minutes
|
||||
},
|
||||
[ESI_RATE_LIMIT_GROUP.CORP_WALLET]: {
|
||||
type: ESI_RATE_LIMIT_GROUP.CORP_WALLET,
|
||||
limit: 300,
|
||||
window: 60 * 15, // 15 minutes
|
||||
},
|
||||
[ESI_RATE_LIMIT_GROUP.CORP_DETAIL]: {
|
||||
type: ESI_RATE_LIMIT_GROUP.CORP_DETAIL,
|
||||
limit: 300,
|
||||
window: 60 * 15, // 15 minutes
|
||||
},
|
||||
[ESI_RATE_LIMIT_GROUP.CORP_MEMBER]: {
|
||||
type: ESI_RATE_LIMIT_GROUP.CORP_MEMBER,
|
||||
limit: 300,
|
||||
window: 60 * 15, // 15 minutes
|
||||
},
|
||||
[ESI_RATE_LIMIT_GROUP.CORP_PROJECT]: {
|
||||
type: ESI_RATE_LIMIT_GROUP.CORP_PROJECT,
|
||||
limit: 600,
|
||||
window: 60 * 15, // 15 minutes
|
||||
},
|
||||
};
|
||||
|
||||
const TOKEN_COST = {
|
||||
['2XX']: 2,
|
||||
['3XX']: 1,
|
||||
['4XX']: 5,
|
||||
['5XX']: 0,
|
||||
};
|
||||
|
||||
interface LimitBucket {
|
||||
tokens: number;
|
||||
uses: { number; timestamp: number }[];
|
||||
}
|
||||
|
||||
// Rate limit buckets to track request timestamps
|
||||
// keys:
|
||||
// authentcated routes: ESI_RATE_LIMIT_GROUP:applicationId:characterId
|
||||
// unauthenticated routes based on the ip address or ip and application id, but we'll just use `unauthenticated` for simplicity
|
||||
const rateLimitBuckets: Record<string, LimitBucket> = {
|
||||
[ESI_RATE_LIMIT_GROUP.UNAUTHENTICATED]: {
|
||||
tokens: 0,
|
||||
uses: [],
|
||||
},
|
||||
};
|
||||
|
||||
function canMakeRequest(group: ESI_RATE_LIMIT_GROUP, characterId?: number): boolean {
|
||||
const now = Date.now();
|
||||
const limit = ESI_GROUP_LIMITS[group];
|
||||
const bucketKey = `${group}${characterId ? `:${characterId}` : ''}`;
|
||||
const bucket = rateLimitBuckets[bucketKey] || rateLimitBuckets[ESI_RATE_LIMIT_GROUP.UNAUTHENTICATED];
|
||||
if (!bucket) {
|
||||
rateLimitBuckets[bucketKey] = {
|
||||
tokens: 0,
|
||||
uses: [],
|
||||
};
|
||||
return true; // no requests made yet
|
||||
}
|
||||
|
||||
// If we have enough tokens, allow the request without further checks
|
||||
if (bucket.tokens < limit.limit - 5) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the most recent request is outside the window
|
||||
if (bucket.uses.at(-1)?.timestamp < now - limit.window) {
|
||||
bucket.uses = [];
|
||||
bucket.tokens = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove timestamps outside the window
|
||||
while (bucket.uses.length > 0 && bucket.uses[0].timestamp < now - limit.window) {
|
||||
bucket.uses.shift();
|
||||
}
|
||||
|
||||
bucket.tokens = 0;
|
||||
bucket.tokens = bucket.uses.reduce((acc, use) => acc + use.number, 0);
|
||||
return bucket.tokens < limit.limit - 5; // keep a small buffer
|
||||
}
|
||||
|
||||
export async function rateLimitedFetch(
|
||||
fetchFn: () => Promise<Response>,
|
||||
group: ESI_RATE_LIMIT_GROUP = ESI_RATE_LIMIT_GROUP.DEFAULT,
|
||||
characterId?: number,
|
||||
): Promise<Response> {
|
||||
if (!canMakeRequest(group, characterId)) {
|
||||
return new Response('Rate limit exceeded', { status: 429 });
|
||||
}
|
||||
|
||||
const response = await fetchFn();
|
||||
|
||||
const limit = ESI_GROUP_LIMITS[group];
|
||||
const bucket =
|
||||
rateLimitBuckets[`${group}${characterId ? `:${characterId}` : ''}`] || rateLimitBuckets[ESI_RATE_LIMIT_GROUP.UNAUTHENTICATED];
|
||||
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
// 2XX
|
||||
var cost = TOKEN_COST['2XX'];
|
||||
if (limit.countOnlyErrors) {
|
||||
return response;
|
||||
}
|
||||
bucket.tokens += cost;
|
||||
bucket.uses.push({ number: cost, timestamp: Date.now() });
|
||||
} else if (response.status >= 300 && response.status < 400) {
|
||||
// 3XX
|
||||
var cost = TOKEN_COST['3XX'];
|
||||
if (limit.countOnlyErrors) {
|
||||
return response;
|
||||
}
|
||||
bucket.tokens += cost;
|
||||
bucket.uses.push({ number: cost, timestamp: Date.now() });
|
||||
} else if (response.status >= 400 && response.status < 500) {
|
||||
// 4XX
|
||||
var cost = limit.countOnlyErrors ? 1 : TOKEN_COST['4XX'];
|
||||
bucket.tokens += cost;
|
||||
bucket.uses.push({ number: cost, timestamp: Date.now() });
|
||||
} else {
|
||||
// 5XX
|
||||
var cost = limit.countOnlyErrors ? 1 : TOKEN_COST['5XX'];
|
||||
bucket.tokens += cost;
|
||||
bucket.uses.push({ number: cost, timestamp: Date.now() });
|
||||
}
|
||||
|
||||
// verify our tokens count matches headers and update accordingly
|
||||
const remaining = response.headers.get('X-RateLimit-Remaining');
|
||||
if (remaining) {
|
||||
const expectedTokens = limit.limit - parseInt(remaining);
|
||||
if (expectedTokens !== bucket.tokens) {
|
||||
console.warn(
|
||||
`Rate limit token count mismatch for group ${group} (characterId: ${characterId || 'N/A'}). Expected ${expectedTokens}, but have ${
|
||||
bucket.tokens
|
||||
}. Adjusting.`,
|
||||
);
|
||||
const diff = expectedTokens - bucket.tokens;
|
||||
bucket.tokens = expectedTokens;
|
||||
bucket.uses.push({ number: diff, timestamp: Date.now() });
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
57
packages/eve/src/esi/wallet.ts
Normal file
57
packages/eve/src/esi/wallet.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { ESI_SCOPE } from '../oauth';
|
||||
import { checkScopesAndGetCharacterId, esiFetch, type EsiOptions } from './util/fetch';
|
||||
|
||||
// required scope: esi-wallet.read_character_wallet.v1
|
||||
export function getCharacterWallet(options: EsiOptions): Promise<number> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-wallet.read_character_wallet.v1']);
|
||||
return esiFetch<number>(`/characters/${character_id}/wallet/`, {
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export interface WalletTransaction {
|
||||
client_id: number;
|
||||
date: string;
|
||||
is_buy: boolean;
|
||||
is_personal: boolean;
|
||||
journal_ref_id: number;
|
||||
location_id: number;
|
||||
quantity: number;
|
||||
transaction_id: number;
|
||||
type_id: number;
|
||||
unit_price: number;
|
||||
}
|
||||
|
||||
// required scope: esi-wallet.read_character_wallet.v1
|
||||
export function getCharacterWalletTransactions(options: EsiOptions, fromId: number): Promise<Partial<WalletTransaction>[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-wallet.read_character_wallet.v1']);
|
||||
return esiFetch<Partial<WalletTransaction>[]>(`/characters/${character_id}/wallet/transactions/`, {
|
||||
...options,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(fromId),
|
||||
});
|
||||
}
|
||||
|
||||
export interface WalletJournalEntry {
|
||||
amount: number; // The amount of ISK given or taken from the wallet as a result of the given transaction. Positive when ISK is deposited into the wallet and negative when ISK is withdrawn
|
||||
balance: number; // Wallet balance after transaction occurred
|
||||
context_id: number; // And ID that gives extra context to the particualr transaction. Because of legacy reasons the context is completely different per ref_type and means different things. It is also possible to not have a context_id
|
||||
context_id_type: 'character' | 'corporation' | 'alliance' | 'faction'; // The type of the given context_id if present
|
||||
date: string; // Date and time of transaction
|
||||
description: string;
|
||||
first_party_id: number;
|
||||
id: number;
|
||||
reason: string;
|
||||
ref_type: 'agent' | 'assetSafety' | 'bounty' | 'bountyPrizes' | 'contract' | 'dividend' | 'marketTransaction' | 'other';
|
||||
second_party_id: number;
|
||||
tax: number;
|
||||
tax_receiver_id: number;
|
||||
}
|
||||
|
||||
// required scope: esi-wallet.read_character_wallet.v1
|
||||
export function getCharacterWalletJournal(options: EsiOptions, page: number = 1): Promise<Partial<WalletJournalEntry>[]> {
|
||||
const character_id = checkScopesAndGetCharacterId(options, ESI_SCOPE['esi-wallet.read_character_wallet.v1']);
|
||||
return esiFetch<Partial<WalletJournalEntry>[]>(`/characters/${character_id}/wallet/journal/?page=${page}`, {
|
||||
...options,
|
||||
});
|
||||
}
|
||||
1
packages/eve/src/esi/wars.ts
Normal file
1
packages/eve/src/esi/wars.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
10
packages/eve/src/everef/everef.api.http
Normal file
10
packages/eve/src/everef/everef.api.http
Normal file
@@ -0,0 +1,10 @@
|
||||
// Get info for a blueprint
|
||||
|
||||
GET https://api.everef.net/v1/industry/cost
|
||||
?blueprint_id=21028
|
||||
&runs=40
|
||||
&structure_type_id=35825
|
||||
&security=NULL_SEC
|
||||
&rig_id=43891
|
||||
&rig_id=43892
|
||||
©ing_cost=0.0168
|
||||
94
packages/eve/src/everef/index.ts
Normal file
94
packages/eve/src/everef/index.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import createClient, { type FetchOptions, type FetchResponse } from 'openapi-fetch';
|
||||
import type { components, paths } from './schema.d';
|
||||
|
||||
export * as schema from './schema.d';
|
||||
|
||||
const client = createClient<paths>({
|
||||
baseUrl: 'https://api.everef.net',
|
||||
});
|
||||
|
||||
export function getBlueprintCosts(blueprintTypeId: number): Promise<
|
||||
FetchResponse<
|
||||
{
|
||||
parameters: {
|
||||
query?: {
|
||||
input?: components['schemas']['IndustryCostInput'];
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
'application/json': components['schemas']['IndustryCost'];
|
||||
};
|
||||
};
|
||||
400: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
'application/json': components['schemas']['ApiError'];
|
||||
};
|
||||
};
|
||||
500: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
'application/json': components['schemas']['ApiError'];
|
||||
};
|
||||
};
|
||||
};
|
||||
},
|
||||
FetchOptions<{
|
||||
parameters: {
|
||||
query?: {
|
||||
input?: components['schemas']['IndustryCostInput'];
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
'application/json': components['schemas']['IndustryCost'];
|
||||
};
|
||||
};
|
||||
400: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
'application/json': components['schemas']['ApiError'];
|
||||
};
|
||||
};
|
||||
500: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
'application/json': components['schemas']['ApiError'];
|
||||
};
|
||||
};
|
||||
};
|
||||
}>,
|
||||
`${string}/${string}`
|
||||
>
|
||||
> {
|
||||
return client.GET('/v1/industry/cost', {
|
||||
query: {
|
||||
blueprint_type_id: blueprintTypeId,
|
||||
},
|
||||
});
|
||||
}
|
||||
702
packages/eve/src/everef/schema.d.ts
vendored
Normal file
702
packages/eve/src/everef/schema.d.ts
vendored
Normal file
@@ -0,0 +1,702 @@
|
||||
/**
|
||||
* This file was auto-generated by openapi-typescript.
|
||||
* Do not make direct changes to the file.
|
||||
*/
|
||||
|
||||
export interface paths {
|
||||
"/v1/industry/cost": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get: operations["industryCost"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/v1/search": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/**
|
||||
* Search for inventory types
|
||||
* @description Search for EVE Online inventory types by name
|
||||
*/
|
||||
get: operations["search"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
}
|
||||
export type webhooks = Record<string, never>;
|
||||
export interface components {
|
||||
schemas: {
|
||||
ApiError: {
|
||||
message?: string;
|
||||
};
|
||||
CopyingCost: {
|
||||
/** @description The alpha clone tax amount */
|
||||
alpha_clone_tax?: number;
|
||||
/** @description The estimated item value (EIV). This may not be completely accurate. */
|
||||
estimated_item_value?: number;
|
||||
/** @description The facility amount */
|
||||
facility_tax?: number;
|
||||
job_cost_base?: number;
|
||||
materials?: {
|
||||
[key: string]: components["schemas"]["MaterialCost"];
|
||||
};
|
||||
materials_volume?: number;
|
||||
/** Format: int64 */
|
||||
product_id?: number;
|
||||
product_volume?: number;
|
||||
/**
|
||||
* Format: double
|
||||
* @description The number of runs
|
||||
*/
|
||||
runs?: number;
|
||||
/** @description The SCC surcharge amount */
|
||||
scc_surcharge?: number;
|
||||
/** @description Bonuses to system cost from structures, rigs, etc. */
|
||||
system_cost_bonuses?: number;
|
||||
/**
|
||||
* @description The system cost index amount.
|
||||
* Note that this will always be a slightly off, as the ESI does not report the full precision of the system cost index rates.
|
||||
* See https://github.com/esi/esi-issues/issues/1411
|
||||
*/
|
||||
system_cost_index?: number;
|
||||
time?: string;
|
||||
total_cost?: number;
|
||||
total_cost_per_run?: number;
|
||||
/** @description The total amount of ISK required to start the job */
|
||||
total_job_cost?: number;
|
||||
total_material_cost?: number;
|
||||
};
|
||||
IndustryCost: {
|
||||
copying?: {
|
||||
[key: string]: components["schemas"]["CopyingCost"];
|
||||
};
|
||||
input?: components["schemas"]["IndustryCostInput"];
|
||||
invention?: {
|
||||
[key: string]: components["schemas"]["InventionCost"];
|
||||
};
|
||||
manufacturing?: {
|
||||
[key: string]: components["schemas"]["ProductionCost"];
|
||||
};
|
||||
reaction?: {
|
||||
[key: string]: components["schemas"]["ProductionCost"];
|
||||
};
|
||||
};
|
||||
IndustryCostInput: {
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Advanced Capital Ship Construction skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
advanced_capital_ship_construction: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Advanced Industrial Ship Construction skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
advanced_industrial_ship_construction: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Advanced Industry skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
advanced_industry: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Advanced Large Ship Construction skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
advanced_large_ship_construction: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Advanced Medium Ship Construction skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
advanced_medium_ship_construction: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Advanced Small Ship Construction skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
advanced_small_ship_construction: number;
|
||||
/**
|
||||
* @description Whether installing character is an alpha clone or not
|
||||
* @default false
|
||||
*/
|
||||
alpha: boolean;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Amarr Encryption Methods skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
amarr_encryption_methods: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Amarr Starship Engineering skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
amarr_starship_engineering: number;
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The blueprint ID to calculate
|
||||
*/
|
||||
blueprint_id?: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Caldari Encryption Methods skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
caldari_encryption_methods: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Caldari Starship Engineering skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
caldari_starship_engineering: number;
|
||||
/** @description The copying cost index of the system where the job is installed */
|
||||
copying_cost?: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Core Subsystem Technology skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
core_subsystem_technology: number;
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The decryptor type ID to use
|
||||
*/
|
||||
decryptor_id?: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Defensive Subsystem Technology skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
defensive_subsystem_technology: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Electromagnetic Physics skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
electromagnetic_physics: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Electronic Engineering skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
electronic_engineering: number;
|
||||
/**
|
||||
* @description The facility tax rate of the station or structure where the job is installed
|
||||
* @default 0
|
||||
*/
|
||||
facility_tax: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Gallente Encryption Methods skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
gallente_encryption_methods: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Gallente Starship Engineering skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
gallente_starship_engineering: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Graviton Physics skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
graviton_physics: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The High Energy Physics skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
high_energy_physics: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Hydromagnetic Physics skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
hydromagnetic_physics: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Industry skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
industry: number;
|
||||
/** @description The invention cost index of the system where the job is installed */
|
||||
invention_cost?: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Laser Physics skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
laser_physics: number;
|
||||
/** @description The manufacturing cost index of the system where the job is installed */
|
||||
manufacturing_cost?: number;
|
||||
/**
|
||||
* @description Where to get material prices from
|
||||
* @default ESI_AVG
|
||||
* @enum {string}
|
||||
*/
|
||||
material_prices: "ESI_AVG" | "FUZZWORK_JITA_SELL_MIN" | "FUZZWORK_JITA_SELL_AVG" | "FUZZWORK_JITA_BUY_MAX" | "FUZZWORK_JITA_BUY_AVG";
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The material efficiency of the blueprint. Defaults to 10 for T1 products or invention output ME to T2 products
|
||||
*/
|
||||
me?: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Mechanical Engineering skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
mechanical_engineering: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Metallurgy skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
metallurgy: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Minmatar Encryption Methods skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
minmatar_encryption_methods: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Minmatar Starship Engineering skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
minmatar_starship_engineering: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Molecular Engineering skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
molecular_engineering: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Mutagenic Stabilization skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
mutagenic_stabilization: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Nanite Engineering skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
nanite_engineering: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Nuclear Physics skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
nuclear_physics: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Offensive Subsystem Technology skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
offensive_subsystem_technology: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Outpost Construction skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
outpost_construction: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Plasma Physics skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
plasma_physics: number;
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The desired product type ID
|
||||
*/
|
||||
product_id?: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Propulsion Subsystem Technology skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
propulsion_subsystem_technology: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Quantum Physics skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
quantum_physics: number;
|
||||
/** @description The reaction cost index of the system where the job is installed */
|
||||
reaction_cost?: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Reactions skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
reactions: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Research skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
research: number;
|
||||
/** @description The researching material efficiency cost index of the system where the job is installed */
|
||||
researching_me_cost?: number;
|
||||
/** @description The researching time efficiency cost index of the system where the job is installed */
|
||||
researching_te_cost?: number;
|
||||
/** @description The type IDs of the rigs installed on the sture structure where the job is installed */
|
||||
rig_id?: number[];
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Rocket Science skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
rocket_science: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The number of runs
|
||||
* @default 1
|
||||
*/
|
||||
runs: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Science skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
science: number;
|
||||
/**
|
||||
* @description The security class of the system where the job is installed. If neither security nor system is supplied, high sec is assumed
|
||||
* @enum {string}
|
||||
*/
|
||||
security?: "HIGH_SEC" | "LOW_SEC" | "NULL_SEC";
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Sleeper Encryption Methods skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
sleeper_encryption_methods: number;
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The type ID of the structure where the job is installed. If not set, an NPC station is assumed.
|
||||
*/
|
||||
structure_type_id?: number;
|
||||
/**
|
||||
* @description Bonus to apply to system cost, such as the faction warfare bonus
|
||||
* @default 0
|
||||
* @example -0.5
|
||||
*/
|
||||
system_cost_bonus: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The ID of the system where the job is installed. This will resolve security class and cost indices. If neither security nor system is supplied, high sec is assumed
|
||||
*/
|
||||
system_id?: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The time efficiency of the blueprint. Defaults to 20 for T1 products or invention output TE to T2 products
|
||||
*/
|
||||
te?: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Triglavian Encryption Methods skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
triglavian_encryption_methods: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Triglavian Quantum Engineering skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
triglavian_quantum_engineering: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Upwell Encryption Methods skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
upwell_encryption_methods: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The Upwell Starship Engineering skill level the installing character
|
||||
* @default 5
|
||||
*/
|
||||
upwell_starship_engineering: number;
|
||||
};
|
||||
InventionCost: {
|
||||
/** @description The alpha clone tax amount */
|
||||
alpha_clone_tax?: number;
|
||||
avg_cost_per_copy?: number;
|
||||
avg_cost_per_run?: number;
|
||||
avg_cost_per_unit?: number;
|
||||
avg_time_per_copy?: string;
|
||||
avg_time_per_run?: string;
|
||||
avg_time_per_unit?: string;
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The source blueprint of the invention
|
||||
*/
|
||||
blueprint_id?: number;
|
||||
/** @description The estimated item value (EIV). This may not be completely accurate. */
|
||||
estimated_item_value?: number;
|
||||
/** Format: double */
|
||||
expected_copies?: number;
|
||||
/** Format: double */
|
||||
expected_runs?: number;
|
||||
/** Format: double */
|
||||
expected_units?: number;
|
||||
/** @description The facility amount */
|
||||
facility_tax?: number;
|
||||
job_cost_base?: number;
|
||||
materials?: {
|
||||
[key: string]: components["schemas"]["MaterialCost"];
|
||||
};
|
||||
materials_volume?: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The material efficiency of the invented blueprint
|
||||
*/
|
||||
me?: number;
|
||||
/** Format: double */
|
||||
probability?: number;
|
||||
/** Format: int64 */
|
||||
product_id?: number;
|
||||
product_volume?: number;
|
||||
/**
|
||||
* Format: double
|
||||
* @description The number of runs
|
||||
*/
|
||||
runs?: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The number of runs on each successfully invented copy
|
||||
*/
|
||||
runs_per_copy?: number;
|
||||
/** @description The SCC surcharge amount */
|
||||
scc_surcharge?: number;
|
||||
/** @description Bonuses to system cost from structures, rigs, etc. */
|
||||
system_cost_bonuses?: number;
|
||||
/**
|
||||
* @description The system cost index amount.
|
||||
* Note that this will always be a slightly off, as the ESI does not report the full precision of the system cost index rates.
|
||||
* See https://github.com/esi/esi-issues/issues/1411
|
||||
*/
|
||||
system_cost_index?: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The time efficiency of the invented blueprint
|
||||
*/
|
||||
te?: number;
|
||||
time?: string;
|
||||
total_cost?: number;
|
||||
/** @description The total amount of ISK required to start the job */
|
||||
total_job_cost?: number;
|
||||
total_material_cost?: number;
|
||||
/** Format: int32 */
|
||||
units_per_run?: number;
|
||||
};
|
||||
MaterialCost: {
|
||||
cost?: number;
|
||||
cost_per_unit?: number;
|
||||
/** Format: double */
|
||||
quantity?: number;
|
||||
/** Format: int64 */
|
||||
type_id?: number;
|
||||
volume?: number;
|
||||
volume_per_unit?: number;
|
||||
};
|
||||
ProductionCost: {
|
||||
/** @description The alpha clone tax amount */
|
||||
alpha_clone_tax?: number;
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The source blueprint of the manufacture
|
||||
*/
|
||||
blueprint_id?: number;
|
||||
/** @description The estimated item value (EIV). This may not be completely accurate. */
|
||||
estimated_item_value?: number;
|
||||
/** @description The facility amount */
|
||||
facility_tax?: number;
|
||||
materials?: {
|
||||
[key: string]: components["schemas"]["MaterialCost"];
|
||||
};
|
||||
materials_volume?: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The material efficiency used
|
||||
*/
|
||||
me?: number;
|
||||
/** Format: int64 */
|
||||
product_id?: number;
|
||||
product_volume?: number;
|
||||
/**
|
||||
* Format: double
|
||||
* @description The number of runs
|
||||
*/
|
||||
runs?: number;
|
||||
/** @description The SCC surcharge amount */
|
||||
scc_surcharge?: number;
|
||||
/** @description Bonuses to system cost from structures, rigs, etc. */
|
||||
system_cost_bonuses?: number;
|
||||
/**
|
||||
* @description The system cost index amount.
|
||||
* Note that this will always be a slightly off, as the ESI does not report the full precision of the system cost index rates.
|
||||
* See https://github.com/esi/esi-issues/issues/1411
|
||||
*/
|
||||
system_cost_index?: number;
|
||||
/**
|
||||
* Format: int32
|
||||
* @description The time efficiency used
|
||||
*/
|
||||
te?: number;
|
||||
time?: string;
|
||||
time_per_run?: string;
|
||||
time_per_unit?: string;
|
||||
total_cost?: number;
|
||||
total_cost_per_run?: number;
|
||||
total_cost_per_unit?: number;
|
||||
/** @description The total amount of ISK required to start the job */
|
||||
total_job_cost?: number;
|
||||
total_material_cost?: number;
|
||||
/**
|
||||
* Format: int64
|
||||
* @description Total number of item produced
|
||||
*/
|
||||
units?: number;
|
||||
/**
|
||||
* Format: int64
|
||||
* @description Total number of item produced
|
||||
*/
|
||||
units_per_run?: number;
|
||||
};
|
||||
SearchEntry: {
|
||||
/** Format: int64 */
|
||||
id?: number;
|
||||
language?: string;
|
||||
/**
|
||||
* Format: int64
|
||||
* @description Relevance score of the search result. Lower is better.
|
||||
*/
|
||||
relevance?: number;
|
||||
title?: string;
|
||||
/** @enum {string} */
|
||||
type?: "inventory_type" | "market_group" | "category" | "group";
|
||||
type_name?: string;
|
||||
urls?: components["schemas"]["SearchEntryUrls"];
|
||||
};
|
||||
SearchEntryUrls: {
|
||||
everef?: string;
|
||||
reference_data?: string;
|
||||
};
|
||||
SearchResult: {
|
||||
entries?: components["schemas"]["SearchEntry"][];
|
||||
};
|
||||
};
|
||||
responses: never;
|
||||
parameters: never;
|
||||
requestBodies: never;
|
||||
headers: never;
|
||||
pathItems: never;
|
||||
}
|
||||
export type $defs = Record<string, never>;
|
||||
export interface operations {
|
||||
industryCost: {
|
||||
parameters: {
|
||||
query?: {
|
||||
input?: components["schemas"]["IndustryCostInput"];
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Success */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["IndustryCost"];
|
||||
};
|
||||
};
|
||||
/** @description Client error */
|
||||
400: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["ApiError"];
|
||||
};
|
||||
};
|
||||
/** @description Server error */
|
||||
500: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["ApiError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
search: {
|
||||
parameters: {
|
||||
query: {
|
||||
/** @description Search query (minimum 3 characters) */
|
||||
q: string;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Success */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["SearchResult"];
|
||||
};
|
||||
};
|
||||
/** @description Client error */
|
||||
400: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["ApiError"];
|
||||
};
|
||||
};
|
||||
/** @description Server error */
|
||||
500: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["ApiError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
6
packages/eve/src/index.ts
Normal file
6
packages/eve/src/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from './esi';
|
||||
export * from './db';
|
||||
export * from './ref';
|
||||
export * from './third-party';
|
||||
export * as locales from './locales';
|
||||
export * from './utils';
|
||||
0
packages/eve/src/industry/api.ts
Normal file
0
packages/eve/src/industry/api.ts
Normal file
0
packages/eve/src/industry/index.ts
Normal file
0
packages/eve/src/industry/index.ts
Normal file
19
packages/eve/src/industry/models/IndustryConfig.ts
Normal file
19
packages/eve/src/industry/models/IndustryConfig.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { schema } from '@/everef';
|
||||
|
||||
export type IndustryCostInput = schema.components['schemas']['IndustryCostInput'];
|
||||
|
||||
export type GeneralOptions = Pick<IndustryCostInput, 'alpha' | 'runs' | 'product_id' | 'material_prices'>;
|
||||
|
||||
export type LocationOptions = Pick<
|
||||
IndustryCostInput,
|
||||
'system_id' | 'structure_type_id' | 'rig_id' | 'system_cost_bonus' | 'security' | 'facility_tax'
|
||||
>;
|
||||
|
||||
export type IndexOptions = Pick<
|
||||
IndustryCostInput,
|
||||
'researching_me_cost' | 'researching_te_cost' | 'manufacturing_cost' | 'copying_cost' | 'invention_cost' | 'reaction_cost'
|
||||
>;
|
||||
|
||||
export type BlueprintOptions = Pick<IndustryCostInput, 'blueprint_id' | 'copying_cost' | 'me' | 'te' | 'decryptor_id'>;
|
||||
|
||||
export type Skills = Omit<IndustryCostInput, keyof BlueprintOptions | keyof IndexOptions | keyof LocationOptions | keyof GeneralOptions>;
|
||||
35
packages/eve/src/locales.ts
Normal file
35
packages/eve/src/locales.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export type Locales = 'en' | 'ru' | 'de' | 'fr' | 'ja' | 'es' | 'zh' | 'ko';
|
||||
export const ALL_LOCALES: Locales[] = ['en', 'ru', 'de', 'fr', 'ja', 'es', 'zh', 'ko'];
|
||||
export const DEFAULT_LOCALE: Locales = 'en';
|
||||
export const LOCALE_NAMES: { [key in Locales]: string } = {
|
||||
en: 'English',
|
||||
ru: 'Русский',
|
||||
de: 'Deutsch',
|
||||
fr: 'Français',
|
||||
ja: '日本語',
|
||||
es: 'Español',
|
||||
zh: '中文',
|
||||
ko: '한국어',
|
||||
};
|
||||
export function toDiscordLocale(locale: Locales): string {
|
||||
switch (locale) {
|
||||
case 'en':
|
||||
return 'en-US';
|
||||
case 'ru':
|
||||
return 'ru';
|
||||
case 'de':
|
||||
return 'de';
|
||||
case 'fr':
|
||||
return 'fr';
|
||||
case 'ja':
|
||||
return 'ja';
|
||||
case 'es':
|
||||
return 'es-ES';
|
||||
case 'zh':
|
||||
return 'zh-CN';
|
||||
case 'ko':
|
||||
return 'ko';
|
||||
default:
|
||||
return 'en-US';
|
||||
}
|
||||
}
|
||||
130
packages/eve/src/models/attribute.ts
Normal file
130
packages/eve/src/models/attribute.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import type { LocalizedString } from './shared-types';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export interface Attribute {
|
||||
readonly attribute_id: number;
|
||||
readonly category_id: number;
|
||||
readonly data_type: number;
|
||||
readonly default_value: number;
|
||||
readonly description: LocalizedString;
|
||||
readonly high_is_good: boolean;
|
||||
readonly icon_id?: number;
|
||||
readonly name: string;
|
||||
readonly published: boolean;
|
||||
readonly stackable: boolean;
|
||||
readonly unit_id?: number;
|
||||
readonly display_name: LocalizedString;
|
||||
readonly tooltip_title?: LocalizedString;
|
||||
readonly tooltip_description?: LocalizedString;
|
||||
}
|
||||
|
||||
export const getAttribute = (id: number): Attribute => {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.dogma_attributes[String(id)];
|
||||
if (!data) throw new Error(`Attribute ID ${id} not found in reference data`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export enum CommonAttribute {
|
||||
// Structure
|
||||
StructureHitpoints = 9,
|
||||
CargoCapacity = 38,
|
||||
DroneCapacity = 283,
|
||||
DroneBandwidth = 1271,
|
||||
Mass = 4,
|
||||
Volume = 161,
|
||||
InertiaModifier = 70,
|
||||
StructureEMResistance = 113,
|
||||
StructureThermalResistance = 110,
|
||||
StructureKineticResistance = 109,
|
||||
StructureExplosiveResistance = 111,
|
||||
|
||||
// Armor
|
||||
ArmorHitpoints = 265,
|
||||
ArmorEMResistance = 267,
|
||||
ArmorThermalResistance = 270,
|
||||
ArmorKineticResistance = 269,
|
||||
ArmorExplosiveResistance = 268,
|
||||
|
||||
// Shield
|
||||
ShieldCapacity = 263,
|
||||
ShieldRechargeTime = 479,
|
||||
ShieldEMResistance = 271,
|
||||
ShieldThermalResistance = 274,
|
||||
ShieldKineticResistance = 273,
|
||||
ShieldExplosiveResistance = 272,
|
||||
|
||||
// Electronic Resistances
|
||||
CapacitorWarfareResistance = 2045,
|
||||
StasisWebifierResistance = 2115,
|
||||
WeaponDisruptionResistance = 2113,
|
||||
|
||||
// Capacitor
|
||||
CapacitorCapacity = 482,
|
||||
CapacitorRechargeTime = 55,
|
||||
|
||||
// Targeting
|
||||
MaxTargetRange = 76,
|
||||
MaxLockedTargets = 192,
|
||||
SignatureRadius = 552,
|
||||
ScanResolution = 564,
|
||||
RadarSensorStrength = 208,
|
||||
MagnetometricSensorStrength = 210,
|
||||
GravimetricSensorStrength = 211,
|
||||
LadarSensorStrength = 209,
|
||||
|
||||
// Jump Drive Systems
|
||||
HasJumpDrive = 861,
|
||||
JumpDriveCapacitorNeed = 898,
|
||||
MaxJumpRange = 867,
|
||||
JumpDriveFuelNeed = 866,
|
||||
JumpDriveConsumptionAmount = 868,
|
||||
FuelBayCapacity = 1549,
|
||||
ConduitJumpConsumptionAmount = 3131,
|
||||
COnduitJumpPassengerCapacity = 3133,
|
||||
|
||||
// Propulsion
|
||||
MaxVelocity = 37,
|
||||
WarpSpeed = 600,
|
||||
|
||||
// FITTING
|
||||
|
||||
// Slots
|
||||
HighSlots = 14,
|
||||
MediumSlots = 13,
|
||||
LowSlots = 12,
|
||||
|
||||
// Stats
|
||||
PowergridOutput = 11,
|
||||
CPUOutput = 48,
|
||||
TurretHardpoints = 102,
|
||||
LauncherHardpoints = 101,
|
||||
|
||||
// Rigging
|
||||
RigSlots = 1137,
|
||||
RigSize = 1547,
|
||||
Calibration = 1132,
|
||||
|
||||
// Module
|
||||
CPUUsage = 50,
|
||||
PowergridUsage = 30,
|
||||
ActivationCost = 6,
|
||||
|
||||
// EWAR
|
||||
MaxVelocityBonus = 20,
|
||||
WarpScrambleStrength = 105,
|
||||
WarpDisruptionStrength = 2425,
|
||||
WarpDisruptionRange = 103,
|
||||
|
||||
// Weapon
|
||||
DamageMultiplier = 64,
|
||||
AccuracyFalloff = 158,
|
||||
OptimalRange = 54,
|
||||
RateOfFire = 51,
|
||||
TrackingSpeed = 160,
|
||||
ReloadTime = 1795,
|
||||
ActivationTime = 73,
|
||||
UsedWithCharge1 = 604,
|
||||
UsedWithCharge2 = 605,
|
||||
ChargeSize = 128,
|
||||
}
|
||||
140
packages/eve/src/models/blueprint.ts
Normal file
140
packages/eve/src/models/blueprint.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { ActivityType, type TypeIDQuantity } from './shared-types';
|
||||
import type { Type } from './type';
|
||||
import { getType } from './type';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export interface Activity {
|
||||
time: number;
|
||||
}
|
||||
|
||||
export interface ManufacturingActivity extends Activity {
|
||||
time: number;
|
||||
materials: { [type_id: string]: TypeIDQuantity };
|
||||
products: { [type_id: string]: TypeIDQuantity };
|
||||
}
|
||||
|
||||
export interface InventionActivity extends Activity {
|
||||
time: number;
|
||||
materials: { [type_id: string]: TypeIDQuantity };
|
||||
products: { [type_id: string]: TypeIDQuantity };
|
||||
skills: { [skill_type_id: string]: number }; // skill_type_id : level
|
||||
}
|
||||
|
||||
export interface TypeQuantity {
|
||||
type: Type;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
export interface Blueprint {
|
||||
readonly blueprint_type_id: number;
|
||||
readonly max_production_limit: number;
|
||||
readonly activities: {
|
||||
[ActivityType.MANUFACTURING]?: ManufacturingActivity;
|
||||
[ActivityType.RESEARCH_MATERIAL]?: Activity;
|
||||
[ActivityType.RESEARCH_TIME]?: Activity;
|
||||
[ActivityType.COPYING]?: Activity;
|
||||
[ActivityType.INVENTION]?: InventionActivity;
|
||||
};
|
||||
}
|
||||
|
||||
export function getBlueprint(blueprint_type_id: number): Blueprint {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.blueprints[String(blueprint_type_id)];
|
||||
if (!data) throw new Error(`Blueprint Type ID ${blueprint_type_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function getManufacturingMaterials(blueprint: Blueprint):
|
||||
| any[]
|
||||
| Promise<
|
||||
{
|
||||
type: Type;
|
||||
quantity: number;
|
||||
}[]
|
||||
> {
|
||||
const manufacturing = blueprint.activities[ActivityType.MANUFACTURING];
|
||||
if (!manufacturing) return [];
|
||||
|
||||
return Promise.all(
|
||||
Object.entries(manufacturing.materials).map(([type_id, { quantity }]) => ({
|
||||
type: getType(parseInt(type_id)),
|
||||
quantity,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
export function getManufacturingProducts(blueprint: Blueprint):
|
||||
| any[]
|
||||
| Promise<
|
||||
{
|
||||
type: Type;
|
||||
quantity: number;
|
||||
}[]
|
||||
> {
|
||||
const manufacturing = blueprint.activities[ActivityType.MANUFACTURING];
|
||||
if (!manufacturing) return [];
|
||||
|
||||
return Promise.all(
|
||||
Object.entries(manufacturing.products).map(([type_id, { quantity }]) => ({
|
||||
type: getType(parseInt(type_id)),
|
||||
quantity,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
export function getInventionMaterials(blueprint: Blueprint):
|
||||
| any[]
|
||||
| Promise<
|
||||
{
|
||||
type: Type;
|
||||
quantity: number;
|
||||
}[]
|
||||
> {
|
||||
const invention = blueprint.activities[ActivityType.INVENTION];
|
||||
if (!invention) return [];
|
||||
|
||||
return Promise.all(
|
||||
Object.entries(invention.materials).map(([type_id, { quantity }]) => ({
|
||||
type: getType(parseInt(type_id)),
|
||||
quantity,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
export function getInventionProducts(blueprint: Blueprint):
|
||||
| any[]
|
||||
| Promise<
|
||||
{
|
||||
type: Type;
|
||||
quantity: number;
|
||||
}[]
|
||||
> {
|
||||
const invention = blueprint.activities[ActivityType.INVENTION];
|
||||
if (!invention) return [];
|
||||
|
||||
return Promise.all(
|
||||
Object.entries(invention.products).map(([type_id, { quantity }]) => ({
|
||||
type: getType(parseInt(type_id)),
|
||||
quantity,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
export function getInventionSkills(blueprint: Blueprint):
|
||||
| any[]
|
||||
| Promise<
|
||||
{
|
||||
type: Type;
|
||||
level: number;
|
||||
}[]
|
||||
> {
|
||||
const invention = blueprint.activities[ActivityType.INVENTION];
|
||||
if (!invention) return [];
|
||||
|
||||
return Promise.all(
|
||||
Object.entries(invention.skills).map(([skill_type_id, level]) => ({
|
||||
type: getType(parseInt(skill_type_id)),
|
||||
level,
|
||||
})),
|
||||
);
|
||||
}
|
||||
35
packages/eve/src/models/category.ts
Normal file
35
packages/eve/src/models/category.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { LocalizedString } from './shared-types';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export enum CommonCategory {
|
||||
CARGO = 5,
|
||||
SHIP = 6,
|
||||
MODULE = 7,
|
||||
CHARGE = 8,
|
||||
BLUEPRINT = 9,
|
||||
SKILL = 16,
|
||||
DRONE = 18,
|
||||
IMPLANT = 20,
|
||||
APPAREL = 30,
|
||||
DEPLOYABLE = 22,
|
||||
REACTION = 24,
|
||||
SUBSYSTEM = 32,
|
||||
STRUCTURE = 65,
|
||||
STRUCTURE_MODULE = 66,
|
||||
FIGHTER = 87,
|
||||
}
|
||||
|
||||
export interface Category {
|
||||
readonly category_id: number;
|
||||
readonly name: LocalizedString;
|
||||
readonly published: boolean;
|
||||
readonly group_ids: number[];
|
||||
readonly icon_id?: number;
|
||||
}
|
||||
|
||||
export function getCategory(category_id: number): Category {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.categories[String(category_id)];
|
||||
if (!data) throw new Error(`Category ID ${category_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
65
packages/eve/src/models/effect.ts
Normal file
65
packages/eve/src/models/effect.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { getAttribute, type Attribute } from './attribute';
|
||||
import type { LocalizedString } from './shared-types';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
interface Modifier {
|
||||
domain: number;
|
||||
func: number;
|
||||
group_id?: number;
|
||||
modified_attribute_id: number;
|
||||
modifying_attribute_id: number;
|
||||
skill_type_id?: number;
|
||||
operator: number;
|
||||
}
|
||||
|
||||
export interface Effect {
|
||||
readonly effect_id: number;
|
||||
readonly disallow_auto_repeat: boolean;
|
||||
readonly discharge_attribute_id?: number;
|
||||
readonly distribution?: number;
|
||||
readonly duration_attribute_id?: number;
|
||||
readonly effect_category: number;
|
||||
readonly effect_name: string;
|
||||
readonly electronic_chance: boolean;
|
||||
readonly falloff_attribute_id?: number;
|
||||
readonly guid: string;
|
||||
readonly is_assistance: boolean;
|
||||
readonly is_offensive: boolean;
|
||||
readonly is_warp_safe: boolean;
|
||||
readonly propulsion_chance: boolean;
|
||||
readonly published: boolean;
|
||||
readonly range_attribute_id?: number;
|
||||
readonly range_chance: boolean;
|
||||
readonly modifiers: Modifier[];
|
||||
readonly tracking_speed_attribute_id?: number;
|
||||
readonly description: LocalizedString;
|
||||
readonly display_name: LocalizedString;
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
export function getEffect(effect_id: number): Effect {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.dogma_effects[String(effect_id)];
|
||||
if (!data) throw new Error(`Effect ID ${effect_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function getDischargeAttribute(effect: Effect): Attribute {
|
||||
return effect.discharge_attribute_id && getAttribute(effect.discharge_attribute_id);
|
||||
}
|
||||
|
||||
export function getFalloffAttribute(effect: Effect): Attribute {
|
||||
return effect.falloff_attribute_id && getAttribute(effect.falloff_attribute_id);
|
||||
}
|
||||
|
||||
export function getDurationAttribute(effect: Effect): Attribute {
|
||||
return effect.duration_attribute_id && getAttribute(effect.duration_attribute_id);
|
||||
}
|
||||
|
||||
export function getRangeAttribute(effect: Effect): Attribute {
|
||||
return effect.range_attribute_id && getAttribute(effect.range_attribute_id);
|
||||
}
|
||||
|
||||
export function getTrackingSpeedAttribute(effect: Effect): Attribute {
|
||||
return effect.tracking_speed_attribute_id && getAttribute(effect.tracking_speed_attribute_id);
|
||||
}
|
||||
29
packages/eve/src/models/group.ts
Normal file
29
packages/eve/src/models/group.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { LocalizedString } from './shared-types';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export interface Group {
|
||||
readonly group_id: number;
|
||||
readonly category_id: number;
|
||||
readonly name: LocalizedString;
|
||||
readonly published: boolean;
|
||||
readonly icon_id?: number;
|
||||
readonly anchorable: boolean;
|
||||
readonly anchored: boolean;
|
||||
readonly fittable_non_singleton: boolean;
|
||||
readonly use_base_price: boolean;
|
||||
readonly type_ids?: number[];
|
||||
}
|
||||
export function getGroup(group_id: number): Group {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.groups[String(group_id)];
|
||||
if (!data) throw new Error(`Group ID ${group_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function groupEveRefLink(group_id: number) {
|
||||
return `https://everef.net/groups/${group_id}`;
|
||||
}
|
||||
|
||||
export function renderGroupEveRefLink(group: Group, locale: string = 'en') {
|
||||
return `[${group.name[locale] ?? group.name.en}](${groupEveRefLink(group.group_id)})`;
|
||||
}
|
||||
37
packages/eve/src/models/icon.ts
Normal file
37
packages/eve/src/models/icon.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export enum IconSize {
|
||||
SIZE_32 = 32,
|
||||
SIZE_64 = 64,
|
||||
SIZE_128 = 128,
|
||||
SIZE_256 = 256,
|
||||
SIZE_512 = 512,
|
||||
}
|
||||
|
||||
export interface Icon {
|
||||
readonly icon_id: number;
|
||||
readonly description: string;
|
||||
readonly file: string;
|
||||
}
|
||||
|
||||
export function getIcon(icon_id: number): Icon {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.icons[String(icon_id)];
|
||||
if (!data) throw new Error(`Icon ID ${icon_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function getIconUrl(
|
||||
icon_id: Icon,
|
||||
{
|
||||
size = IconSize.SIZE_64,
|
||||
isBp = false,
|
||||
isBpc = false,
|
||||
}: {
|
||||
size?: IconSize;
|
||||
isBp?: boolean;
|
||||
isBpc?: boolean;
|
||||
} = {},
|
||||
): string {
|
||||
return `https://images.evetech.net/types/${icon_id}/icon${isBp ? '/bp' : isBpc ? '/bpc' : ''}?size=${size}`;
|
||||
}
|
||||
14
packages/eve/src/models/index.ts
Normal file
14
packages/eve/src/models/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export * from './attribute';
|
||||
export * from './blueprint';
|
||||
export * from './category';
|
||||
export * from './effect';
|
||||
export * from './group';
|
||||
export * from './icon';
|
||||
export * from './market-group';
|
||||
export * from './meta-group';
|
||||
export * from './region';
|
||||
export * from './schematic';
|
||||
export * from './skill';
|
||||
export * from './solar-system';
|
||||
export * from './type';
|
||||
export * from './unit';
|
||||
53
packages/eve/src/models/loadModels.ts
Normal file
53
packages/eve/src/models/loadModels.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import fs from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import type { Unit } from './unit';
|
||||
import type { SolarSystem } from './solar-system';
|
||||
import type { Attribute } from './attribute';
|
||||
import type { Blueprint } from './blueprint';
|
||||
import type { Category } from './category';
|
||||
import type { Effect } from './effect';
|
||||
import type { Group } from './group';
|
||||
import type { Icon } from './icon';
|
||||
import type { MarketGroup } from './market-group';
|
||||
import type { MetaGroup } from './meta-group';
|
||||
import type { Region } from './region';
|
||||
import type { Schematic } from './schematic';
|
||||
import type { Skill } from './skill';
|
||||
import type { Type } from './type';
|
||||
|
||||
const dataSets = {
|
||||
loaded: false,
|
||||
dogma_attributes: {} as Record<string, Attribute>,
|
||||
blueprints: {} as Record<string, Blueprint>,
|
||||
categories: {} as Record<string, Category>,
|
||||
dogma_effects: {} as Record<string, Effect>,
|
||||
groups: {} as Record<string, Group>,
|
||||
icons: {} as Record<string, Icon>,
|
||||
market_groups: {} as Record<string, MarketGroup>,
|
||||
meta_groups: {} as Record<string, MetaGroup>,
|
||||
regions: {} as Record<string, Region>,
|
||||
schematics: {} as Record<string, Schematic>,
|
||||
skills: {} as Record<string, Skill>,
|
||||
solar_systems: {} as Record<string, SolarSystem>,
|
||||
types: {} as Record<string, Type>,
|
||||
units: {} as Record<string, Unit>,
|
||||
};
|
||||
export async function loadModels(): Promise<void> {
|
||||
dataSets.dogma_attributes = JSON.parse(fs.readFileSync(join(__dirname, './data/reference-data/dogma_attributes.json')).toString());
|
||||
dataSets.blueprints = JSON.parse(fs.readFileSync(join(__dirname, './data/reference-data/blueprints.json')).toString());
|
||||
dataSets.categories = JSON.parse(fs.readFileSync(join(__dirname, './data/reference-data/categories.json')).toString());
|
||||
dataSets.dogma_effects = JSON.parse(fs.readFileSync(join(__dirname, './data/reference-data/dogma_effects.json')).toString());
|
||||
dataSets.groups = JSON.parse(fs.readFileSync(join(__dirname, './data/reference-data/groups.json')).toString());
|
||||
dataSets.icons = JSON.parse(fs.readFileSync(join(__dirname, './data/reference-data/icons.json')).toString());
|
||||
dataSets.market_groups = JSON.parse(fs.readFileSync(join(__dirname, './data/reference-data/market_groups.json')).toString());
|
||||
dataSets.meta_groups = JSON.parse(fs.readFileSync(join(__dirname, './data/reference-data/meta_groups.json')).toString());
|
||||
dataSets.regions = JSON.parse(fs.readFileSync(join(__dirname, './data/reference-data/regions.json')).toString());
|
||||
dataSets.schematics = JSON.parse(fs.readFileSync(join(__dirname, './data/reference-data/schematics.json')).toString());
|
||||
dataSets.skills = JSON.parse(fs.readFileSync(join(__dirname, './data/reference-data/skills.json')).toString());
|
||||
dataSets.solar_systems = JSON.parse(fs.readFileSync(join(__dirname, './data/reference-data/solar_systems.json')).toString());
|
||||
dataSets.types = JSON.parse(fs.readFileSync(join(__dirname, './data/reference-data/types.json')).toString());
|
||||
dataSets.units = JSON.parse(fs.readFileSync(join(__dirname, './data/reference-data/units.json')).toString());
|
||||
dataSets.loaded = true;
|
||||
}
|
||||
|
||||
export { dataSets };
|
||||
24
packages/eve/src/models/market-group.ts
Normal file
24
packages/eve/src/models/market-group.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { LocalizedString } from './shared-types';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export interface MarketGroup {
|
||||
readonly market_group_id: number;
|
||||
readonly parent_group_id: number;
|
||||
readonly name: LocalizedString;
|
||||
readonly description: LocalizedString;
|
||||
readonly child_market_group_ids: number[];
|
||||
readonly icon_id: number;
|
||||
readonly has_types: boolean;
|
||||
}
|
||||
|
||||
export function getMarketGroup(market_group_id: number): MarketGroup {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.market_groups[String(market_group_id)];
|
||||
if (!data) throw new Error(`Market group ID ${market_group_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getAllChildMarketGroups(marketGroup: MarketGroup): Promise<MarketGroup[]> {
|
||||
const children = await Promise.all(marketGroup.child_market_group_ids.map((id) => getMarketGroup(id)));
|
||||
return children.concat(...(await Promise.all(children.map((child) => getAllChildMarketGroups(child)))));
|
||||
}
|
||||
17
packages/eve/src/models/meta-group.ts
Normal file
17
packages/eve/src/models/meta-group.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { LocalizedString } from './shared-types';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export interface MetaGroup {
|
||||
readonly meta_group_id: number;
|
||||
readonly name: LocalizedString;
|
||||
readonly type_ids: number[];
|
||||
readonly icon_id?: number;
|
||||
readonly icon_suffix?: string;
|
||||
}
|
||||
|
||||
export function getMetaGroup(meta_group_id: number): MetaGroup {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.meta_groups[String(meta_group_id)];
|
||||
if (!data) throw new Error(`Meta group ID ${meta_group_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
24
packages/eve/src/models/region.ts
Normal file
24
packages/eve/src/models/region.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { LocalizedString, Position } from './shared-types';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export interface Region {
|
||||
readonly region_id: number;
|
||||
readonly center: Position;
|
||||
readonly description_id: number;
|
||||
readonly faction_id: number;
|
||||
readonly max: Position;
|
||||
readonly min: Position;
|
||||
readonly name_id: number;
|
||||
readonly wormhole_class_id?: number;
|
||||
readonly nebula_id?: number;
|
||||
readonly universe_id: string;
|
||||
readonly description: LocalizedString;
|
||||
readonly name: LocalizedString;
|
||||
}
|
||||
|
||||
export function getRegion(region_id: number): Region {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.regions[String(region_id)];
|
||||
if (!data) throw new Error(`Region ID ${region_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
43
packages/eve/src/models/schematic.ts
Normal file
43
packages/eve/src/models/schematic.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { LocalizedString, TypeIDQuantity } from './shared-types';
|
||||
import { getType, type Type } from './type';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export interface Schematic {
|
||||
readonly schematic_id: number;
|
||||
readonly cycle_time: number;
|
||||
readonly name: LocalizedString;
|
||||
readonly materials: { [type_id: string]: TypeIDQuantity };
|
||||
readonly products: { [type_id: string]: TypeIDQuantity };
|
||||
readonly pin_type_ids: number[];
|
||||
}
|
||||
|
||||
export function getSchematic(schematic_id: number): Schematic {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.schematics[String(schematic_id)];
|
||||
if (!data) throw new Error(`Schematic ID ${schematic_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function getMaterialQuantities(schematic: Schematic): {
|
||||
type: Type;
|
||||
quantity: number;
|
||||
}[] {
|
||||
return Object.entries(schematic.materials).map(([type_id, { quantity }]) => ({
|
||||
type: getType(Number(type_id)),
|
||||
quantity,
|
||||
}));
|
||||
}
|
||||
|
||||
export function getProductQuantities(schematic: Schematic): {
|
||||
type: Type;
|
||||
quantity: number;
|
||||
}[] {
|
||||
return Object.entries(schematic.products).map(([type_id, { quantity }]) => ({
|
||||
type: getType(Number(type_id)),
|
||||
quantity,
|
||||
}));
|
||||
}
|
||||
|
||||
export function getPinTypes(schematic: Schematic): Type[] {
|
||||
return schematic.pin_type_ids.map(getType);
|
||||
}
|
||||
56
packages/eve/src/models/shared-types.ts
Normal file
56
packages/eve/src/models/shared-types.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { Type } from './type';
|
||||
|
||||
export interface LocalizedString {
|
||||
de?: string;
|
||||
en?: string;
|
||||
es?: string;
|
||||
fr?: string;
|
||||
ja?: string;
|
||||
ko?: string;
|
||||
ru?: string;
|
||||
zh?: string;
|
||||
}
|
||||
|
||||
export interface TypeIDQuantity {
|
||||
type_id: number;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
export interface TypeQuantity {
|
||||
type: Type;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
export interface AttributeIDValue {
|
||||
attribute_id: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface EffectIDDefault {
|
||||
effect_id: number;
|
||||
is_default: boolean;
|
||||
}
|
||||
|
||||
export interface MaterialIDQuantity {
|
||||
material_type_id: number;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
export interface BlueprintTypeIDActivity {
|
||||
blueprint_type_id: number;
|
||||
blueprint_activity: ActivityType;
|
||||
}
|
||||
|
||||
export enum ActivityType {
|
||||
MANUFACTURING = 'manufacturing',
|
||||
RESEARCH_MATERIAL = 'research_material',
|
||||
RESEARCH_TIME = 'research_time',
|
||||
COPYING = 'copying',
|
||||
INVENTION = 'invention',
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
}
|
||||
47
packages/eve/src/models/skill.ts
Normal file
47
packages/eve/src/models/skill.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { type Attribute, getAttribute } from './attribute';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export interface Skill {
|
||||
readonly type_id: number;
|
||||
readonly primary_dogma_attribute_id: number;
|
||||
readonly secondary_dogma_attribute_id: number;
|
||||
readonly primary_character_attribute_id: number;
|
||||
readonly secondary_character_attribute_id: number;
|
||||
readonly training_time_multiplier: number;
|
||||
readonly required_skills?: { [skill_type_id: string]: number }; // skill_type_id : level
|
||||
}
|
||||
|
||||
export function getSkill(type_id: number): Skill {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.skills[String(type_id)];
|
||||
if (!data) throw new Error(`Skill ID ${type_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function getPrimaryDogmaAttribute(skill: Skill): Attribute {
|
||||
return getAttribute(skill.primary_dogma_attribute_id);
|
||||
}
|
||||
|
||||
export function getSecondaryDogmaAttribute(skill: Skill): Attribute {
|
||||
return getAttribute(skill.secondary_dogma_attribute_id);
|
||||
}
|
||||
|
||||
export function getPrimaryCharacterAttribute(skill: Skill): Attribute {
|
||||
return getAttribute(skill.primary_character_attribute_id);
|
||||
}
|
||||
|
||||
export function getSecondaryCharacterAttribute(skill: Skill): Attribute {
|
||||
return getAttribute(skill.secondary_character_attribute_id);
|
||||
}
|
||||
|
||||
export function getPrerequisites(skill: Skill): { skill: Skill; level: number }[] {
|
||||
if (!skill.required_skills) return [];
|
||||
return Object.entries(skill.required_skills).map(([skill_type_id, level]) => ({
|
||||
skill: getSkill(parseInt(skill_type_id)),
|
||||
level,
|
||||
}));
|
||||
}
|
||||
|
||||
export function skillpointsAtLevel(skill: Skill, level: number): number {
|
||||
return Math.pow(2, 2.5 * (level - 1)) * 250 * skill.training_time_multiplier;
|
||||
}
|
||||
42
packages/eve/src/models/solar-system.ts
Normal file
42
packages/eve/src/models/solar-system.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export interface SolarSystem {
|
||||
readonly regionID: number;
|
||||
readonly constellationID: number;
|
||||
readonly solarSystemID: number;
|
||||
readonly solarSystemName: string;
|
||||
readonly x: number;
|
||||
readonly y: number;
|
||||
readonly z: number;
|
||||
readonly xMin: number;
|
||||
readonly xMax: number;
|
||||
readonly yMin: number;
|
||||
readonly yMax: number;
|
||||
readonly zMin: number;
|
||||
readonly zMax: number;
|
||||
readonly luminosity: number;
|
||||
readonly border: boolean;
|
||||
readonly fringe: boolean;
|
||||
readonly corridor: boolean;
|
||||
readonly hub: boolean;
|
||||
readonly international: boolean;
|
||||
readonly regional: boolean;
|
||||
readonly security: number;
|
||||
readonly factionID: number;
|
||||
readonly radius: number;
|
||||
readonly sunTypeID: number;
|
||||
readonly securityClass: string;
|
||||
}
|
||||
|
||||
export function getSolarSystem(solarSystemID: number): SolarSystem {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.solar_systems[String(solarSystemID)] as any;
|
||||
if (!data) throw new Error(`Solar System ID ${solarSystemID} not found in reference data`);
|
||||
return {
|
||||
...data,
|
||||
security: parseFloat(data.security),
|
||||
radius: parseFloat(data.radius),
|
||||
sunTypeID: parseInt(data.sun_type_id, 10),
|
||||
securityClass: data.security_class ?? 'nullsec', // Default to 'nullsec' if security_class is not present
|
||||
};
|
||||
}
|
||||
224
packages/eve/src/models/type.ts
Normal file
224
packages/eve/src/models/type.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
import type {
|
||||
ActivityType,
|
||||
AttributeIDValue,
|
||||
BlueprintTypeIDActivity,
|
||||
EffectIDDefault,
|
||||
LocalizedString,
|
||||
MaterialIDQuantity,
|
||||
} from './shared-types';
|
||||
import { IconSize } from './icon';
|
||||
import { getUnit, type Unit } from './unit';
|
||||
import { CommonAttribute, getAttribute, type Attribute } from './attribute';
|
||||
import { getGroup, type Group } from './group';
|
||||
import { getMetaGroup, type MetaGroup } from './meta-group';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
interface Masteries {
|
||||
'0': number[];
|
||||
'1': number[];
|
||||
'2': number[];
|
||||
'3': number[];
|
||||
'4': number[];
|
||||
}
|
||||
|
||||
interface Bonus {
|
||||
bonus: number;
|
||||
bonus_text: LocalizedString;
|
||||
importance: number;
|
||||
unit_id: number;
|
||||
}
|
||||
|
||||
interface Traits {
|
||||
misc_bonuses: { [level: string]: Bonus };
|
||||
role_bonuses: { [level: string]: Bonus };
|
||||
types: { [skill_type_id: string]: { [order: string]: Bonus } };
|
||||
}
|
||||
|
||||
export interface Type {
|
||||
readonly type_id: number;
|
||||
readonly name: LocalizedString;
|
||||
readonly description: LocalizedString;
|
||||
readonly published: boolean;
|
||||
readonly group_id?: number;
|
||||
readonly base_price?: number;
|
||||
readonly capacity?: number;
|
||||
readonly faction_id?: number;
|
||||
readonly graphic_id?: number;
|
||||
readonly market_group_id?: number;
|
||||
readonly mass?: number;
|
||||
readonly masteries?: Masteries;
|
||||
readonly meta_group_id?: number;
|
||||
readonly portion_size?: number;
|
||||
readonly race_id?: number;
|
||||
readonly radius?: number;
|
||||
readonly sof_faction_name?: string;
|
||||
readonly sound_id?: number;
|
||||
readonly traits?: Traits;
|
||||
readonly volume?: number;
|
||||
readonly dogma_attributes?: {
|
||||
[attribute_id: string]: AttributeIDValue;
|
||||
};
|
||||
readonly dogma_effects?: { [effect_id: string]: EffectIDDefault };
|
||||
readonly packaged_volume?: number;
|
||||
readonly type_materials?: { [type_id: string]: MaterialIDQuantity };
|
||||
readonly required_skills?: { [skill_type_id: string]: number }; // skill_type_id : level
|
||||
readonly type_variations?: { [meta_group_id: string]: number[] }; // meta_group_id : type_ids[]
|
||||
readonly produced_by_blueprints?: {
|
||||
[blueprint_type_id: string]: BlueprintTypeIDActivity;
|
||||
}; // blueprint_type_id : blueprint_activity
|
||||
readonly buildable_pin_type_ids?: number[];
|
||||
readonly is_ore?: boolean;
|
||||
readonly ore_variations?: { [variant: string]: number }; // variant : type_id
|
||||
readonly produced_by_schematic_ids?: number[];
|
||||
readonly used_by_schematic_ids?: number[];
|
||||
readonly is_blueprint?: boolean;
|
||||
}
|
||||
|
||||
export function getType(type_id: number): Type {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.types[String(type_id)];
|
||||
if (!data) throw new Error(`Type ID ${type_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function getTypeIconUrl(type: Type, size: IconSize = IconSize.SIZE_64) {
|
||||
return `https://images.evetech.net/types/${type.type_id}/icon${type.is_blueprint ? '/bp' : ''}?size=${size}`;
|
||||
}
|
||||
|
||||
export function getSkillBonuses(type: Type): {
|
||||
skill: Type;
|
||||
bonuses: {
|
||||
bonus: number;
|
||||
bonus_text: LocalizedString;
|
||||
importance: number;
|
||||
unit: Unit;
|
||||
}[];
|
||||
}[] {
|
||||
if (!type.traits) return [];
|
||||
const skillBonuses: {
|
||||
skill: Type;
|
||||
bonuses: {
|
||||
bonus: number;
|
||||
bonus_text: LocalizedString;
|
||||
importance: number;
|
||||
unit: Unit;
|
||||
}[];
|
||||
}[] = [];
|
||||
for (const skill_type_id in type.traits.types) {
|
||||
skillBonuses.push({
|
||||
skill: getType(Number(skill_type_id)),
|
||||
bonuses: Object.keys(type.traits.types[skill_type_id]).map((order) => {
|
||||
const bonus = type.traits!.types[skill_type_id][order];
|
||||
return {
|
||||
bonus: bonus.bonus,
|
||||
bonus_text: bonus.bonus_text,
|
||||
importance: bonus.importance,
|
||||
unit: getUnit(bonus.unit_id),
|
||||
};
|
||||
}),
|
||||
});
|
||||
}
|
||||
return skillBonuses;
|
||||
}
|
||||
|
||||
export function getRoleBonuses(type: Type): {
|
||||
bonus: number;
|
||||
bonus_text: LocalizedString;
|
||||
importance: number;
|
||||
unit: Unit;
|
||||
}[] {
|
||||
if (!type.traits || !type.traits.role_bonuses) return [];
|
||||
return Object.values(type.traits.role_bonuses).map((bonus) => ({
|
||||
bonus: bonus.bonus,
|
||||
bonus_text: bonus.bonus_text,
|
||||
importance: bonus.importance,
|
||||
unit: bonus.unit_id ? getUnit(bonus.unit_id) : undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
export function eveRefLink(type_id: number) {
|
||||
return `https://everef.net/types/${type_id}`;
|
||||
}
|
||||
|
||||
export function renderTypeEveRefLink(type: Type, locale: string = 'en') {
|
||||
return `[${type.name[locale] ?? type.name.en}](${eveRefLink(type.type_id)})`;
|
||||
}
|
||||
|
||||
export function eveTycoonLink(type_id: number) {
|
||||
return `https://evetycoon.com/market/${type_id}`;
|
||||
}
|
||||
|
||||
export function getTypeAttributes(type: Type): any[] {
|
||||
if (!type.dogma_attributes) return [];
|
||||
Object.keys(type.dogma_attributes).map((attribute_id) => ({
|
||||
attribute: getAttribute(Number(attribute_id)),
|
||||
value: type.dogma_attributes![attribute_id].value,
|
||||
}));
|
||||
}
|
||||
|
||||
export function typeHasAnyAttribute(type: Type, attribute_ids: CommonAttribute[]): boolean {
|
||||
if (!type.dogma_attributes) return false;
|
||||
for (const attribute_id of attribute_ids) {
|
||||
if (type.dogma_attributes[attribute_id]) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getTypeSkills(type: Type): {
|
||||
skill: Type;
|
||||
level: number;
|
||||
}[] {
|
||||
if (!type.required_skills) return [];
|
||||
return Object.keys(type.required_skills).map((skill_type_id) => ({
|
||||
skill: getType(Number(skill_type_id)),
|
||||
level: type.required_skills![skill_type_id],
|
||||
}));
|
||||
}
|
||||
|
||||
export function typeGetAttribute(
|
||||
type: Type,
|
||||
attribute_id: number,
|
||||
): {
|
||||
attribute: Attribute;
|
||||
value: number;
|
||||
} {
|
||||
if (!type.dogma_attributes || !type.dogma_attributes[attribute_id]) return null;
|
||||
return {
|
||||
attribute: getAttribute(attribute_id),
|
||||
value: type.dogma_attributes[attribute_id].value,
|
||||
};
|
||||
}
|
||||
|
||||
export function getTypeBlueprints(type: Type): {
|
||||
blueprint: Type;
|
||||
activity: ActivityType;
|
||||
}[] {
|
||||
if (!type.produced_by_blueprints) return [];
|
||||
return Object.values(type.produced_by_blueprints).map((blueprint) => ({
|
||||
blueprint: getType(blueprint.blueprint_type_id),
|
||||
activity: blueprint.blueprint_activity,
|
||||
}));
|
||||
}
|
||||
|
||||
export function getTypeSchematics(type: Type): Type[] {
|
||||
return type.produced_by_schematic_ids?.map((schematic_id) => getType(schematic_id)) ?? [];
|
||||
}
|
||||
|
||||
export function getTypeGroup(type: Type): Group {
|
||||
if (!type.group_id) return null;
|
||||
return getGroup(type.group_id);
|
||||
}
|
||||
|
||||
export function getTypeVariants(type: Type): {
|
||||
metaGroup: MetaGroup;
|
||||
types: Type[];
|
||||
}[] {
|
||||
return Object.entries(type.type_variations || {}).map(([meta_group_id, variant_ids]) => ({
|
||||
metaGroup: getMetaGroup(Number(meta_group_id)),
|
||||
types: variant_ids.map((type_id) => getType(type_id)),
|
||||
}));
|
||||
}
|
||||
|
||||
export function typeHasAttributes(type: Type): boolean {
|
||||
return type.dogma_attributes && Object.keys(type.dogma_attributes).length > 0;
|
||||
}
|
||||
66
packages/eve/src/models/unit.ts
Normal file
66
packages/eve/src/models/unit.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { convertMillisecondsToTimeString, convertSecondsToTimeString } from '@/utils/markdown';
|
||||
import { getGroup, renderGroupEveRefLink } from './group';
|
||||
import type { LocalizedString } from './shared-types';
|
||||
import { getType, renderTypeEveRefLink } from './type';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
const sizeMap = {
|
||||
1: 'Small',
|
||||
2: 'Medium',
|
||||
3: 'Large',
|
||||
4: 'X-Large',
|
||||
};
|
||||
|
||||
export interface Unit {
|
||||
readonly unit_id: number;
|
||||
readonly display_name: string;
|
||||
readonly description: LocalizedString;
|
||||
readonly name: LocalizedString;
|
||||
}
|
||||
|
||||
export function getUnit(unit_id: number): Unit {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const unit = dataSets.units[String(unit_id)];
|
||||
if (!unit) throw new Error(`Unit ID ${unit_id} not found in reference data`);
|
||||
return unit;
|
||||
}
|
||||
|
||||
export function renderUnit(unit: Unit, value: number, locale: string = 'en'): string {
|
||||
switch (unit.unit_id) {
|
||||
case 108: // inverse percentage
|
||||
case 111: // Inverse percentage
|
||||
return [(1 - value).toFixed(2), unit.display_name ?? ''].join(' ');
|
||||
case 3: // seconds
|
||||
return `${convertSecondsToTimeString(value)}`;
|
||||
case 101: // milliseconds
|
||||
return `${convertMillisecondsToTimeString(value)}`;
|
||||
case 117: // size class
|
||||
return sizeMap[value] ?? 'Unknown';
|
||||
case 141: // hardpoints
|
||||
return value + '';
|
||||
case 120: // calibration
|
||||
return value + ' pts';
|
||||
case 116: // typeID
|
||||
return getType(value).name[locale] ?? 'Unknown';
|
||||
case 10: // m/s
|
||||
return `${value} m/s`;
|
||||
case 11: // meters per second squared
|
||||
return `${value} m/s²`;
|
||||
case 9: // cubic meters
|
||||
return `${value} m³`;
|
||||
case 8: // square meters
|
||||
return `${value} m²`;
|
||||
case 12: // reciprocal meters
|
||||
return `${value} m⁻¹`;
|
||||
case 128: // megabits per second
|
||||
return `${value} Mbps`;
|
||||
case 115: // groupID
|
||||
return renderGroupEveRefLink(getGroup(value), locale) ?? 'Unknown';
|
||||
default:
|
||||
return [value, unit.display_name ?? ''].join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
export function isUnitInversePercentage(unit: Unit): boolean {
|
||||
return unit.unit_id == 108 || unit.unit_id == 111;
|
||||
}
|
||||
240
packages/eve/src/oauth/api.ts
Normal file
240
packages/eve/src/oauth/api.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
import { Elysia, redirect } from 'elysia';
|
||||
import { eveAuthPlugin, extractCharacterInfoFromToken, extractScopesFromToken, refresh } from '@/oauth';
|
||||
import { ESI_SCOPE, type eveAuthDb } from './auth.types';
|
||||
|
||||
export interface EVEAuthAPIOptions {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
callbackUrl: string;
|
||||
database: eveAuthDb;
|
||||
basePath?: string;
|
||||
defaultScopes?: ESI_SCOPE | ESI_SCOPE[];
|
||||
scopeSets?: { [key: string]: ESI_SCOPE[] };
|
||||
responsePage?: {
|
||||
success: string;
|
||||
error: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* EVE Online OAuth API for Elysia framework.
|
||||
* Provides endpoints to handle OAuth flow and manage character scopes.
|
||||
*/
|
||||
export function eveAuthAPI(options: EVEAuthAPIOptions) {
|
||||
const { clientId, clientSecret, callbackUrl, database, basePath = '/auth', scopeSets } = options;
|
||||
|
||||
const successPage = options.responsePage?.success || 'EVE OAuth successful! You can close this window.';
|
||||
const errorPage = options.responsePage?.error || 'EVE OAuth failed. Please try again.';
|
||||
|
||||
const defaultScopes = options.defaultScopes
|
||||
? Array.isArray(options.defaultScopes)
|
||||
? options.defaultScopes
|
||||
: [options.defaultScopes]
|
||||
: [ESI_SCOPE.publicData];
|
||||
|
||||
return (
|
||||
new Elysia()
|
||||
.use(eveAuthPlugin({ clientId, clientSecret, callbackUrl }))
|
||||
// Display success page
|
||||
.get(`${basePath}/success`, () => successPage)
|
||||
// Display error page
|
||||
.get(`${basePath}/error`, () => errorPage)
|
||||
// OAuth callback
|
||||
.get(`${basePath}/callback`, async ({ cookie, redirect, eveAuthValidateRequest }) => {
|
||||
try {
|
||||
const tokens = await eveAuthValidateRequest();
|
||||
const { id: characterId, name: characterName } = extractCharacterInfoFromToken(tokens);
|
||||
|
||||
if (cookie.characterId && cookie.characterId.value !== characterId) {
|
||||
throw 'Character ID mismatch';
|
||||
}
|
||||
|
||||
const clientId = cookie.clientId?.value as string;
|
||||
if (!clientId) {
|
||||
throw 'Missing Client ID cookie';
|
||||
}
|
||||
|
||||
let user = await database.getUserByClientId(clientId);
|
||||
if (!user) {
|
||||
// create user if not exists
|
||||
if (!(user = await database.createUser(clientId))) {
|
||||
throw 'Failed to create user';
|
||||
}
|
||||
}
|
||||
|
||||
let character = await database.getCharacterByUserAndCharacterId(user.id, characterId);
|
||||
if (!character) {
|
||||
// create character if not exists
|
||||
if (!(character = await database.createCharacter(user.id, characterId, characterName, tokens))) {
|
||||
throw 'Failed to create character';
|
||||
}
|
||||
} else {
|
||||
// update tokens if character exists
|
||||
if (!(await database.updateCharacterTokens(character.id, tokens))) {
|
||||
throw 'Failed to update character tokens';
|
||||
}
|
||||
}
|
||||
|
||||
if (!user.main_character_id) {
|
||||
// set main character if not set, but do not error if it fails
|
||||
await database.setCharacterAsMain(user.id, characterId);
|
||||
}
|
||||
|
||||
cookie.userId.set({ value: user.id, maxAge: 30 * 24 * 60 * 60, path: '/' }); // 30 days
|
||||
return redirect(`${basePath}/success`, 302);
|
||||
} catch (error) {
|
||||
console.error(`EVE Discord OAuth callback failed`, error);
|
||||
return redirect(`${basePath}/error`, 302);
|
||||
} finally {
|
||||
cookie.characterId?.remove();
|
||||
cookie.clientId?.remove();
|
||||
}
|
||||
})
|
||||
// Initiates OAuth with default scopes
|
||||
.get(`${basePath}/clientId/:clientId`, async ({ params, eveAuthRedirect, cookie }) => {
|
||||
const clientId = params.clientId;
|
||||
cookie.clientId.set({ value: clientId, maxAge: 600, path: '/' }); // 10 minutes
|
||||
return eveAuthRedirect(defaultScopes);
|
||||
})
|
||||
// Adds scopes by merging existing scopes with the specified scopes
|
||||
.get(`${basePath}/clientId/:clientId/addScopes/:characterId/:scopes`, async ({ params, eveAuthRedirect, cookie }) => {
|
||||
const clientId = params.clientId;
|
||||
const characterId = parseInt(params.characterId);
|
||||
const scopes = params.scopes.split(',');
|
||||
|
||||
const user = await database.getUserByClientId(clientId);
|
||||
if (!user) {
|
||||
throw 'User not found';
|
||||
}
|
||||
|
||||
const character = await database.getCharacterByUserAndCharacterId(user.id, characterId);
|
||||
if (!character) {
|
||||
throw 'Character not found';
|
||||
}
|
||||
|
||||
const existingScopes = extractScopesFromToken({ access_token: character.access_token });
|
||||
const mergedScopes = Array.from(new Set([...existingScopes, ...scopes])) as ESI_SCOPE[];
|
||||
|
||||
cookie.clientId.set({ value: clientId, maxAge: 600, path: '/' }); // 10 minutes
|
||||
cookie.characterId.set({ value: characterId, maxAge: 600, path: '/' }); // 10 minutes
|
||||
return eveAuthRedirect(mergedScopes);
|
||||
})
|
||||
// Add scope set to character by merging existing scopes with the set
|
||||
.get(`${basePath}/clientId/:clientId/addSet/:characterId/:set`, async ({ params, eveAuthRedirect, cookie }) => {
|
||||
const clientId = params.clientId;
|
||||
const characterId = parseInt(params.characterId);
|
||||
const setName = params.set;
|
||||
const scopes = scopeSets?.[setName];
|
||||
if (!scopes) {
|
||||
throw `Scope set "${setName}" not found`;
|
||||
}
|
||||
|
||||
const user = await database.getUserByClientId(clientId);
|
||||
if (!user) {
|
||||
throw 'User not found';
|
||||
}
|
||||
|
||||
const character = await database.getCharacterByUserAndCharacterId(user.id, characterId);
|
||||
if (!character) {
|
||||
throw 'Character not found';
|
||||
}
|
||||
|
||||
const existingScopes = extractScopesFromToken({ access_token: character.access_token });
|
||||
const mergedScopes = Array.from(new Set([...existingScopes, ...scopes]));
|
||||
|
||||
cookie.clientId.set({ value: clientId, maxAge: 600, path: '/' }); // 10 minutes
|
||||
cookie.characterId.set({ value: characterId, maxAge: 600, path: '/' }); // 10 minutes
|
||||
return eveAuthRedirect(mergedScopes);
|
||||
})
|
||||
// Replaces scopes by requesting only the specified scopes
|
||||
.get(`${basePath}/clientId/:clientId/setScopes/:characterId/:scopes`, async ({ params, eveAuthRedirect, cookie }) => {
|
||||
const clientId = params.clientId;
|
||||
const characterId = parseInt(params.characterId);
|
||||
const scopes = params.scopes.split(',') as ESI_SCOPE[];
|
||||
|
||||
cookie.clientId.set({ value: clientId, maxAge: 600, path: '/' }); // 10 minutes
|
||||
cookie.characterId.set({ value: characterId, maxAge: 600, path: '/' }); // 10 minutes
|
||||
return eveAuthRedirect(scopes);
|
||||
})
|
||||
// remove scopes by refreshing tokens without the specified scopes
|
||||
.get(`${basePath}/clientId/:clientId/remScopes/:characterId/:scopes`, async ({ params, eveAuthRedirect, cookie }) => {
|
||||
const clientId = params.clientId;
|
||||
const characterId = parseInt(params.characterId);
|
||||
const scopes = params.scopes.split(',');
|
||||
|
||||
const user = await database.getUserByClientId(clientId);
|
||||
if (!user) {
|
||||
throw 'User not found';
|
||||
}
|
||||
|
||||
const character = await database.getCharacterByUserAndCharacterId(user.id, characterId);
|
||||
if (!character) {
|
||||
throw 'Character not found';
|
||||
}
|
||||
|
||||
const existingScopes = extractScopesFromToken({ access_token: character.access_token });
|
||||
const mergedScopes = existingScopes.filter((scope) => !scopes.includes(scope));
|
||||
|
||||
const refreshedTokens = await refresh(
|
||||
{ refresh_token: character.refresh_token },
|
||||
{
|
||||
clientId,
|
||||
clientSecret,
|
||||
scopes: mergedScopes,
|
||||
},
|
||||
);
|
||||
|
||||
if (!refreshedTokens) {
|
||||
throw 'Failed to refresh tokens with removed scopes';
|
||||
}
|
||||
|
||||
if (!(await database.updateCharacterTokens(character.id, refreshedTokens))) {
|
||||
throw 'Failed to update character tokens after removing scopes';
|
||||
}
|
||||
|
||||
return redirect(`${basePath}/success`, 302);
|
||||
})
|
||||
// Remove scope set from character by refreshing tokens without the specified scopes
|
||||
.get(`${basePath}/clientId/:clientId/remSet/:characterId/:set`, async ({ params, eveAuthRedirect, cookie }) => {
|
||||
const clientId = params.clientId;
|
||||
const characterId = parseInt(params.characterId);
|
||||
const setName = params.set;
|
||||
const scopes = scopeSets?.[setName];
|
||||
if (!scopes) {
|
||||
throw `Scope set "${setName}" not found`;
|
||||
}
|
||||
|
||||
const user = await database.getUserByClientId(clientId);
|
||||
if (!user) {
|
||||
throw 'User not found';
|
||||
}
|
||||
|
||||
const character = await database.getCharacterByUserAndCharacterId(user.id, characterId);
|
||||
if (!character) {
|
||||
throw 'Character not found';
|
||||
}
|
||||
|
||||
const existingScopes = extractScopesFromToken({ access_token: character.access_token });
|
||||
const mergedScopes = existingScopes.filter((scope) => !scopes.includes(scope));
|
||||
|
||||
const refreshedTokens = await refresh(
|
||||
{ refresh_token: character.refresh_token },
|
||||
{
|
||||
clientId,
|
||||
clientSecret,
|
||||
scopes: mergedScopes,
|
||||
},
|
||||
);
|
||||
|
||||
if (!refreshedTokens) {
|
||||
throw 'Failed to refresh tokens with removed scopes';
|
||||
}
|
||||
|
||||
if (!(await database.updateCharacterTokens(character.id, refreshedTokens))) {
|
||||
throw 'Failed to update character tokens after removing scopes';
|
||||
}
|
||||
|
||||
return redirect(`${basePath}/success`, 302);
|
||||
})
|
||||
);
|
||||
}
|
||||
126
packages/eve/src/oauth/auth.types.ts
Normal file
126
packages/eve/src/oauth/auth.types.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Minimal database interface for EVE Online OAuth integration.
|
||||
* Implement this interface to manage users and their associated characters.
|
||||
*/
|
||||
|
||||
export const EVE_JWKS_URL = 'https://login.eveonline.com/oauth/jwks';
|
||||
export const EVE_ISSUER = 'login.eveonline.com';
|
||||
export const EVE_AUDIENCE = 'eveonline';
|
||||
export const DATA_SOURCE = 'tranquility';
|
||||
|
||||
export interface EveTokens {
|
||||
access_token: string;
|
||||
expires_in: number;
|
||||
refresh_token: string;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
main_character_id?: number; // references Character.id
|
||||
}
|
||||
|
||||
export interface Character {
|
||||
id: number;
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
}
|
||||
|
||||
export interface eveAuthDb {
|
||||
createUser(client_id: string): Promise<User>;
|
||||
createCharacter(userId: number, characterId: number, characterName: string, tokens: EveTokens): Promise<Character>;
|
||||
updateCharacterTokens(id: number, tokens: EveTokens): Promise<boolean>;
|
||||
getUserByClientId(client_id: string): Promise<User | null>;
|
||||
getCharacterByUserAndCharacterId(userId: number, characterId: number): Promise<Character | null>;
|
||||
setCharacterAsMain(userId: number, characterId: number): Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface PluginOptions {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
callbackUrl: string;
|
||||
}
|
||||
|
||||
export interface AuthOptions extends PluginOptions {
|
||||
scopes?: ESI_SCOPE | ESI_SCOPE[];
|
||||
}
|
||||
|
||||
export interface ApiOptions extends PluginOptions {
|
||||
database: eveAuthDb;
|
||||
basePath?: string;
|
||||
defaultScopes?: ESI_SCOPE | ESI_SCOPE[];
|
||||
scopeSets?: { [key: string]: ESI_SCOPE[] };
|
||||
responsePage?: {
|
||||
success: string;
|
||||
error: string;
|
||||
};
|
||||
}
|
||||
|
||||
export enum ESI_SCOPE {
|
||||
'publicData' = 'publicData',
|
||||
'esi-alliances.read_contacts.v1' = 'esi-alliances.read_contacts.v1',
|
||||
'esi-assets.read_assets.v1' = 'esi-assets.read_assets.v1',
|
||||
'esi-assets.read_corporation_assets.v1' = 'esi-assets.read_corporation_assets.v1',
|
||||
'esi-calendar.read_calendar_events.v1' = 'esi-calendar.read_calendar_events.v1',
|
||||
'esi-calendar.respond_calendar_events.v1' = 'esi-calendar.respond_calendar_events.v1',
|
||||
'esi-characters.read_agents_research.v1' = 'esi-characters.read_agents_research.v1',
|
||||
'esi-characters.read_blueprints.v1' = 'esi-characters.read_blueprints.v1',
|
||||
'esi-characters.read_contacts.v1' = 'esi-characters.read_contacts.v1',
|
||||
'esi-characters.read_corporation_roles.v1' = 'esi-characters.read_corporation_roles.v1',
|
||||
'esi-characters.read_fatigue.v1' = 'esi-characters.read_fatigue.v1',
|
||||
'esi-characters.read_freelance_jobs.v1' = 'esi-characters.read_freelance_jobs.v1',
|
||||
'esi-characters.read_fw_stats.v1' = 'esi-characters.read_fw_stats.v1',
|
||||
'esi-characters.read_loyalty.v1' = 'esi-characters.read_loyalty.v1',
|
||||
'esi-characters.read_medals.v1' = 'esi-characters.read_medals.v1',
|
||||
'esi-characters.read_notifications.v1' = 'esi-characters.read_notifications.v1',
|
||||
'esi-characters.read_standings.v1' = 'esi-characters.read_standings.v1',
|
||||
'esi-characters.read_titles.v1' = 'esi-characters.read_titles.v1',
|
||||
'esi-characters.write_contacts.v1' = 'esi-characters.write_contacts.v1',
|
||||
'esi-clones.read_clones.v1' = 'esi-clones.read_clones.v1',
|
||||
'esi-clones.read_implants.v1' = 'esi-clones.read_implants.v1',
|
||||
'esi-contracts.read_character_contracts.v1' = 'esi-contracts.read_character_contracts.v1',
|
||||
'esi-contracts.read_corporation_contracts.v1' = 'esi-contracts.read_corporation_contracts.v1',
|
||||
'esi-corporations.read_blueprints.v1' = 'esi-corporations.read_blueprints.v1',
|
||||
'esi-corporations.read_contacts.v1' = 'esi-corporations.read_contacts.v1',
|
||||
'esi-corporations.read_container_logs.v1' = 'esi-corporations.read_container_logs.v1',
|
||||
'esi-corporations.read_corporation_membership.v1' = 'esi-corporations.read_corporation_membership.v1',
|
||||
'esi-corporations.read_divisions.v1' = 'esi-corporations.read_divisions.v1',
|
||||
'esi-corporations.read_facilities.v1' = 'esi-corporations.read_facilities.v1',
|
||||
'esi-corporations.read_freelance_jobs.v1' = 'esi-corporations.read_freelance_jobs.v1',
|
||||
'esi-corporations.read_fw_stats.v1' = 'esi-corporations.read_fw_stats.v1',
|
||||
'esi-corporations.read_medals.v1' = 'esi-corporations.read_medals.v1',
|
||||
'esi-corporations.read_projects.v1' = 'esi-corporations.read_projects.v1',
|
||||
'esi-corporations.read_standings.v1' = 'esi-corporations.read_standings.v1',
|
||||
'esi-corporations.read_starbases.v1' = 'esi-corporations.read_starbases.v1',
|
||||
'esi-corporations.read_structures.v1' = 'esi-corporations.read_structures.v1',
|
||||
'esi-corporations.read_titles.v1' = 'esi-corporations.read_titles.v1',
|
||||
'esi-corporations.track_members.v1' = 'esi-corporations.track_members.v1',
|
||||
'esi-fittings.read_fittings.v1' = 'esi-fittings.read_fittings.v1',
|
||||
'esi-fittings.write_fittings.v1' = 'esi-fittings.write_fittings.v1',
|
||||
'esi-fleets.read_fleet.v1' = 'esi-fleets.read_fleet.v1',
|
||||
'esi-fleets.write_fleet.v1' = 'esi-fleets.write_fleet.v1',
|
||||
'esi-industry.read_character_jobs.v1' = 'esi-industry.read_character_jobs.v1',
|
||||
'esi-industry.read_character_mining.v1' = 'esi-industry.read_character_mining.v1',
|
||||
'esi-industry.read_corporation_jobs.v1' = 'esi-industry.read_corporation_jobs.v1',
|
||||
'esi-industry.read_corporation_mining.v1' = 'esi-industry.read_corporation_mining.v1',
|
||||
'esi-killmails.read_corporation_killmails.v1' = 'esi-killmails.read_corporation_killmails.v1',
|
||||
'esi-killmails.read_killmails.v1' = 'esi-killmails.read_killmails.v1',
|
||||
'esi-location.read_location.v1' = 'esi-location.read_location.v1',
|
||||
'esi-location.read_online.v1' = 'esi-location.read_online.v1',
|
||||
'esi-location.read_ship_type.v1' = 'esi-location.read_ship_type.v1',
|
||||
'esi-mail.organize_mail.v1' = 'esi-mail.organize_mail.v1',
|
||||
'esi-mail.read_mail.v1' = 'esi-mail.read_mail.v1',
|
||||
'esi-mail.send_mail.v1' = 'esi-mail.send_mail.v1',
|
||||
'esi-markets.read_character_orders.v1' = 'esi-markets.read_character_orders.v1',
|
||||
'esi-markets.read_corporation_orders.v1' = 'esi-markets.read_corporation_orders.v1',
|
||||
'esi-markets.structure_markets.v1' = 'esi-markets.structure_markets.v1',
|
||||
'esi-planets.manage_planets.v1' = 'esi-planets.manage_planets.v1',
|
||||
'esi-planets.read_customs_offices.v1' = 'esi-planets.read_customs_offices.v1',
|
||||
'esi-search.search_structures.v1' = 'esi-search.search_structures.v1',
|
||||
'esi-skills.read_skillqueue.v1' = 'esi-skills.read_skillqueue.v1',
|
||||
'esi-skills.read_skills.v1' = 'esi-skills.read_skills.v1',
|
||||
'esi-ui.open_window.v1' = 'esi-ui.open_window.v1',
|
||||
'esi-ui.write_waypoint.v1' = 'esi-ui.write_waypoint.v1',
|
||||
'esi-universe.read_structures.v1' = 'esi-universe.read_structures.v1',
|
||||
'esi-wallet.read_character_wallet.v1' = 'esi-wallet.read_character_wallet.v1',
|
||||
'esi-wallet.read_corporation_wallets.v1' = 'esi-wallet.read_corporation_wallets.v1',
|
||||
}
|
||||
96
packages/eve/src/oauth/eve-auth.ts
Normal file
96
packages/eve/src/oauth/eve-auth.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import * as oauth from '@star-kitten/util/oauth';
|
||||
import jwkToPem from 'jwk-to-pem';
|
||||
import { jwtDecode } from 'jwt-decode';
|
||||
import type { AuthOptions, ESI_SCOPE, EveTokens } from './auth.types';
|
||||
|
||||
const EVE_OAUTH_BASE_URL = 'https://login.eveonline.com';
|
||||
|
||||
export async function createAuthorizationURL(options: AuthOptions) {
|
||||
return oauth.createAuthorizationURL(
|
||||
`${EVE_OAUTH_BASE_URL}/v2/oauth/authorize/`,
|
||||
options.callbackUrl,
|
||||
options.clientId,
|
||||
options.scopes || 'publicData',
|
||||
);
|
||||
}
|
||||
|
||||
export async function validateCode(clientId: string, clientSecret: string, code: string): Promise<EveTokens> {
|
||||
try {
|
||||
return oauth.validateCode<EveTokens>(code, {
|
||||
clientId: clientId,
|
||||
url: `${EVE_OAUTH_BASE_URL}/v2/oauth/token`,
|
||||
auth: {
|
||||
clientSecret: clientSecret,
|
||||
type: 'basic',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`failed to validate EVE authorization code`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
let eveAuthPublicKey: any; // cache the public key for EVE Online's OAuth2 provider
|
||||
export async function verify(token: string) {
|
||||
if (!eveAuthPublicKey) {
|
||||
try {
|
||||
const eveJWKS = (await (await fetch(`${EVE_OAUTH_BASE_URL}/oauth/jwks`)).json()) as { keys: any[] };
|
||||
eveAuthPublicKey = jwkToPem(eveJWKS.keys[0]);
|
||||
} catch (err) {
|
||||
console.error(`failed to get EVE Auth public keys`, err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return oauth.verify(token, eveAuthPublicKey);
|
||||
}
|
||||
|
||||
export async function refresh(
|
||||
{ refresh_token }: { refresh_token: string },
|
||||
{ clientId, clientSecret, scopes }: { clientId: string; clientSecret: string; scopes?: ESI_SCOPE | ESI_SCOPE[] },
|
||||
): Promise<EveTokens> {
|
||||
try {
|
||||
const options = {
|
||||
clientId: clientId,
|
||||
url: `${EVE_OAUTH_BASE_URL}/v2/oauth/token`,
|
||||
scope: scopes,
|
||||
auth: {
|
||||
clientSecret: clientSecret,
|
||||
type: 'basic' as const,
|
||||
},
|
||||
};
|
||||
|
||||
return oauth.refresh<EveTokens>(refresh_token, options);
|
||||
} catch (error) {
|
||||
console.error(`failed to refresh EVE tokens`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function extractCharacterInfoFromToken({ access_token }: { access_token?: string }) {
|
||||
if (!access_token) return undefined;
|
||||
const payload = jwtDecode<any>(access_token);
|
||||
return {
|
||||
id: parseInt(payload.sub!.split(':')[2]),
|
||||
name: payload.name,
|
||||
};
|
||||
}
|
||||
|
||||
export function extractScopesFromToken({ access_token }: { access_token?: string }) {
|
||||
if (!access_token) return [];
|
||||
const payload = jwtDecode<any>(access_token);
|
||||
return payload.scp as ESI_SCOPE[];
|
||||
}
|
||||
|
||||
export function isValidToken({ access_token }: { access_token?: string }) {
|
||||
if (!access_token) return false;
|
||||
const payload = jwtDecode<any>(access_token);
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
return payload.exp > now;
|
||||
}
|
||||
|
||||
export function tokenHasScopes({ access_token }: { access_token?: string }, scopes: ESI_SCOPE | ESI_SCOPE[]) {
|
||||
if (!access_token) return false;
|
||||
const tokenScopes = extractScopesFromToken({ access_token });
|
||||
const requiredScopes = Array.isArray(scopes) ? scopes : [scopes];
|
||||
return requiredScopes.every((scope) => tokenScopes.includes(scope));
|
||||
}
|
||||
4
packages/eve/src/oauth/index.ts
Normal file
4
packages/eve/src/oauth/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './eve-auth';
|
||||
export * from './plugin';
|
||||
export * from './api';
|
||||
export * from './auth.types';
|
||||
52
packages/eve/src/oauth/plugin.ts
Normal file
52
packages/eve/src/oauth/plugin.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Elysia } from 'elysia';
|
||||
import { createAuthorizationURL, validateCode, verify } from './eve-auth';
|
||||
import { ESI_SCOPE, type PluginOptions } from './auth.types';
|
||||
|
||||
/**
|
||||
* EVE Online OAuth plugin for Elysia framework.
|
||||
* Injects methods into the Elysia context to redirect users to EVE's
|
||||
* OAuth authorization page and to validate OAuth callback requests.
|
||||
*/
|
||||
export function eveAuthPlugin({ clientId, clientSecret, callbackUrl }: PluginOptions) {
|
||||
return new Elysia().derive({ as: 'global' }, ({ cookie, redirect, query, status }) => {
|
||||
const eveAuthRedirect = async (scope?: ESI_SCOPE | ESI_SCOPE[]) => {
|
||||
const { url, state } = await createAuthorizationURL({
|
||||
clientId,
|
||||
clientSecret,
|
||||
callbackUrl,
|
||||
scopes: scope || ESI_SCOPE.publicData,
|
||||
});
|
||||
cookie.state.set({ value: state, maxAge: 600, path: '/' });
|
||||
return redirect(url.href, 302);
|
||||
};
|
||||
|
||||
const eveAuthValidateRequest = async () => {
|
||||
const code = query.code as string;
|
||||
const state = query.state as string;
|
||||
const cookieState = cookie.state?.value;
|
||||
|
||||
if (!code || !state || !cookieState || state !== cookieState) {
|
||||
throw status(401);
|
||||
}
|
||||
|
||||
try {
|
||||
const tokens = await validateCode(clientId, clientSecret, code);
|
||||
const decoded = await verify(tokens.access_token);
|
||||
if (!decoded) {
|
||||
throw 'Invalid token';
|
||||
}
|
||||
return tokens;
|
||||
} catch (error) {
|
||||
console.error(`EVE OAuth validation failed`, error);
|
||||
throw status(401);
|
||||
} finally {
|
||||
cookie.state?.remove();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
eveAuthRedirect,
|
||||
eveAuthValidateRequest,
|
||||
};
|
||||
});
|
||||
}
|
||||
7
packages/eve/src/ref/index.ts
Normal file
7
packages/eve/src/ref/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import attributeOrders from '@data/hoboleaks-sde/attributeorders.json';
|
||||
|
||||
export const attributeOrdering = {
|
||||
'11': attributeOrders['11'],
|
||||
'87': attributeOrders['87'],
|
||||
'default': attributeOrders.default,
|
||||
};
|
||||
25
packages/eve/src/third-party/evetycoon.ts
vendored
Normal file
25
packages/eve/src/third-party/evetycoon.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
const base_url = 'https://evetycoon.com/api/v1';
|
||||
|
||||
export interface Price {
|
||||
buyVolume: number;
|
||||
sellVolume: number;
|
||||
buyOrders: number;
|
||||
sellOrders: number;
|
||||
buyOutliers: number;
|
||||
sellOutliers: number;
|
||||
buyThreshold: number;
|
||||
sellThreshold: number;
|
||||
buyAvgFivePercent: number;
|
||||
sellAvgFivePercent: number;
|
||||
maxBuy: number;
|
||||
minSell: number;
|
||||
}
|
||||
|
||||
enum Region {
|
||||
TheForge = 10000002,
|
||||
}
|
||||
|
||||
export const fetchPrice = async (type_id: number, region_id: number = Region.TheForge): Promise<Price> => {
|
||||
const response = await fetch(`${base_url}/market/stats/${region_id}/${type_id}`);
|
||||
return (await response.json()) as Price;
|
||||
};
|
||||
2
packages/eve/src/third-party/index.ts
vendored
Normal file
2
packages/eve/src/third-party/index.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export * as evetycoon from './evetycoon';
|
||||
export * as janice from './janice';
|
||||
407
packages/eve/src/third-party/janice.ts
vendored
Normal file
407
packages/eve/src/third-party/janice.ts
vendored
Normal file
@@ -0,0 +1,407 @@
|
||||
/**
|
||||
* Janice API integration for EVE Online market appraisals and pricing.
|
||||
* This module provides interfaces and functions to interact with the Janice API.
|
||||
*/
|
||||
|
||||
const BASE_URL = 'https://janice.e-351.com/api/rest/v2';
|
||||
|
||||
/**
|
||||
* Represents an appraisal from the Janice API.
|
||||
*/
|
||||
export interface Appraisal {
|
||||
/** Unique identifier for the appraisal */
|
||||
id: number;
|
||||
/** Creation timestamp */
|
||||
created: string;
|
||||
/** Expiration timestamp */
|
||||
expires: string;
|
||||
/** Dataset timestamp */
|
||||
datasetTime: string;
|
||||
/** Appraisal code */
|
||||
code: string;
|
||||
/** Designation type */
|
||||
designation: AppraisalDesignation;
|
||||
/** Pricing strategy */
|
||||
pricing: AppraisalPricing;
|
||||
/** Pricing variant */
|
||||
pricingVariant: AppraisalPricingVariant;
|
||||
/** Price percentage */
|
||||
pricePercentage: number;
|
||||
/** Whether the appraisal is compactized */
|
||||
isCompactized: boolean;
|
||||
/** Failure messages */
|
||||
failures: string;
|
||||
/** Market information */
|
||||
market: PricerMarket;
|
||||
/** Total volume */
|
||||
totalVolume: number;
|
||||
/** Total packaged volume */
|
||||
totalPackagedVolume: number;
|
||||
/** Effective prices */
|
||||
effectivePrices: AppraisalValues;
|
||||
/** Immediate prices */
|
||||
immediatePrices: AppraisalValues;
|
||||
/** Top 5 average prices */
|
||||
top5AveragePrices: AppraisalValues;
|
||||
/** List of items in the appraisal */
|
||||
items: AppraisalItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Price values for an appraisal.
|
||||
*/
|
||||
export interface AppraisalValues {
|
||||
/** Total buy price */
|
||||
totalBuyPrice: number;
|
||||
/** Total split price */
|
||||
totalSplitPrice: number;
|
||||
/** Total sell price */
|
||||
totalSellPrice: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an item in an appraisal.
|
||||
*/
|
||||
export interface AppraisalItem {
|
||||
/** Item ID */
|
||||
id: number;
|
||||
/** Amount of the item */
|
||||
amount: number;
|
||||
/** Number of buy orders */
|
||||
buyOrderCount: number;
|
||||
/** Buy volume */
|
||||
buyVolume: number;
|
||||
/** Number of sell orders */
|
||||
sellOrderCount: number;
|
||||
/** Sell volume */
|
||||
sellVolume: number;
|
||||
/** Effective prices for the item */
|
||||
effectivePrices: AppraisalItemValues;
|
||||
/** Immediate prices for the item */
|
||||
immediatePrices: AppraisalItemValues;
|
||||
/** Top 5 average prices for the item */
|
||||
top5AveragePrices: AppraisalItemValues;
|
||||
/** Total volume */
|
||||
totalVolume: number;
|
||||
/** Total packaged volume */
|
||||
totalPackagedVolume: number;
|
||||
/** Item type information */
|
||||
itemType: ItemType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a pricer item from the API.
|
||||
*/
|
||||
export interface PricerItem {
|
||||
/** Date of the price data */
|
||||
date: string;
|
||||
/** Market information */
|
||||
market: PricerMarket;
|
||||
/** Number of buy orders */
|
||||
buyOrderCount: number;
|
||||
/** Buy volume */
|
||||
buyVolume: number;
|
||||
/** Number of sell orders */
|
||||
sellOrderCount: number;
|
||||
/** Sell volume */
|
||||
sellVolume: number;
|
||||
/** Immediate prices */
|
||||
immediatePrices: PricerItemValues;
|
||||
/** Top 5 average prices */
|
||||
top5AveragePrices: PricerItemValues;
|
||||
/** Item type information */
|
||||
itemType: ItemType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Price values for a pricer item.
|
||||
*/
|
||||
export interface PricerItemValues {
|
||||
/** Buy price */
|
||||
buyPrice: number;
|
||||
/** Split price */
|
||||
splitPrice: number;
|
||||
/** Sell price */
|
||||
sellPrice: number;
|
||||
/** 5-day median buy price */
|
||||
buyPrice5DayMedian: number;
|
||||
/** 5-day median split price */
|
||||
splitPrice5DayMedian: number;
|
||||
/** 5-day median sell price */
|
||||
sellPrice5DayMedian: number;
|
||||
/** 30-day median buy price */
|
||||
buyPrice30DayMedian: number;
|
||||
/** 30-day median split price */
|
||||
splitPrice30DayMedian: number;
|
||||
/** 30-day median sell price */
|
||||
sellPrice30DayMedian: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended price values for appraisal items.
|
||||
*/
|
||||
export interface AppraisalItemValues extends PricerItemValues {
|
||||
/** Total buy price */
|
||||
buyPriceTotal: number;
|
||||
/** Total split price */
|
||||
splitPriceTotal: number;
|
||||
/** Total sell price */
|
||||
sellPriceTotal: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an item type.
|
||||
*/
|
||||
export interface ItemType {
|
||||
/** EVE item ID */
|
||||
eid: number;
|
||||
/** Item name (optional) */
|
||||
name?: string;
|
||||
/** Item volume */
|
||||
volume: number;
|
||||
/** Packaged volume */
|
||||
packagedVolume: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration for appraisal designations.
|
||||
*/
|
||||
export enum AppraisalDesignation {
|
||||
Appraisal = 'appraisal',
|
||||
WantToBuy = 'wtb',
|
||||
WantToSell = 'wts',
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration for appraisal pricing strategies.
|
||||
*/
|
||||
export enum AppraisalPricing {
|
||||
Buy = 'buy',
|
||||
Split = 'split',
|
||||
Sell = 'sell',
|
||||
Purchase = 'purchase',
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration for appraisal pricing variants.
|
||||
*/
|
||||
export enum AppraisalPricingVariant {
|
||||
Immediate = 'immediate',
|
||||
Top5Percent = 'top5percent',
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a market in the pricer system.
|
||||
*/
|
||||
export interface PricerMarket {
|
||||
/** Market ID */
|
||||
id: number;
|
||||
/** Market name */
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Predefined list of available markets.
|
||||
*/
|
||||
export const markets: PricerMarket[] = [
|
||||
{ id: 2, name: 'Jita 4-4' },
|
||||
{ id: 3, name: 'R1O-GN' },
|
||||
{ id: 6, name: 'NPC' },
|
||||
{ id: 114, name: 'MJ-5F9' },
|
||||
{ id: 115, name: 'Amarr' },
|
||||
{ id: 116, name: 'Rens' },
|
||||
{ id: 117, name: 'Dodixie' },
|
||||
{ id: 118, name: 'Hek' },
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Simple cache for API responses to improve performance.
|
||||
*/
|
||||
const cache = new Map<string, { data: any; timestamp: number }>();
|
||||
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
/**
|
||||
* Clears the internal cache. Useful for testing.
|
||||
*/
|
||||
export function clearCache(): void {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if a value is a positive number.
|
||||
*/
|
||||
export function isPositiveNumber(value: any): value is number {
|
||||
return typeof value === 'number' && value > 0 && isFinite(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if a value is a non-empty string.
|
||||
*/
|
||||
export function isNonEmptyString(value: any): value is string {
|
||||
return typeof value === 'string' && value.trim().length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches price data for a single item type.
|
||||
* @param type_id - The EVE item type ID
|
||||
* @param market_id - The market ID (default: 2 for Jita)
|
||||
* @returns Promise resolving to PricerItem
|
||||
* @throws Error if API call fails or validation fails
|
||||
*/
|
||||
export const fetchPrice = async (type_id: number, market_id: number = 2): Promise<PricerItem> => {
|
||||
if (!isPositiveNumber(type_id)) {
|
||||
throw new Error('Invalid type_id: must be a positive number');
|
||||
}
|
||||
if (!isPositiveNumber(market_id)) {
|
||||
throw new Error('Invalid market_id: must be a positive number');
|
||||
}
|
||||
|
||||
const cacheKey = `price_${type_id}_${market_id}`;
|
||||
const cached = cache.get(cacheKey);
|
||||
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/pricer/${type_id}?market=${market_id}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-ApiKey': process.env.JANICE_KEY || '',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
cache.set(cacheKey, { data, timestamp: Date.now() });
|
||||
return data as PricerItem;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching price for type_id ${type_id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches price data for multiple item types.
|
||||
* @param type_ids - Array of EVE item type IDs
|
||||
* @param market_id - The market ID (default: 2 for Jita)
|
||||
* @returns Promise resolving to array of PricerItem
|
||||
* @throws Error if API call fails or validation fails
|
||||
*/
|
||||
export const fetchPrices = async (type_ids: number[], market_id: number = 2): Promise<PricerItem[]> => {
|
||||
if (!Array.isArray(type_ids) || type_ids.length === 0 || !type_ids.every(isPositiveNumber)) {
|
||||
throw new Error('Invalid type_ids: must be a non-empty array of positive numbers');
|
||||
}
|
||||
if (!isPositiveNumber(market_id)) {
|
||||
throw new Error('Invalid market_id: must be a positive number');
|
||||
}
|
||||
|
||||
const cacheKey = `prices_${type_ids.sort().join('_')}_${market_id}`;
|
||||
const cached = cache.get(cacheKey);
|
||||
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/pricer?market=${market_id}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
'X-ApiKey': process.env.JANICE_KEY || '',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: type_ids.join('\n'),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
cache.set(cacheKey, { data, timestamp: Date.now() });
|
||||
return data as PricerItem[];
|
||||
} catch (error) {
|
||||
console.error(`Error fetching prices for type_ids ${type_ids}:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches an appraisal by its code.
|
||||
* @param code - The appraisal code
|
||||
* @returns Promise resolving to Appraisal
|
||||
* @throws Error if API call fails or validation fails
|
||||
*/
|
||||
export const fetchAppraisal = async (code: string): Promise<Appraisal> => {
|
||||
if (!isNonEmptyString(code)) {
|
||||
throw new Error('Invalid code: must be a non-empty string');
|
||||
}
|
||||
|
||||
const cacheKey = `appraisal_${code}`;
|
||||
const cached = cache.get(cacheKey);
|
||||
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/appraisal/${code}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-ApiKey': process.env.JANICE_KEY || '',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
cache.set(cacheKey, { data, timestamp: Date.now() });
|
||||
return data as Appraisal;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching appraisal for code ${code}:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Appraises items from text input.
|
||||
* @param text - The text containing items to appraise
|
||||
* @param market_id - The market ID (default: 2 for Jita)
|
||||
* @returns Promise resolving to Appraisal
|
||||
* @throws Error if API call fails or validation fails
|
||||
*/
|
||||
export const appraiseItems = async (text: string, market_id: number = 2, apiKey: string | undefined = process.env.JANICE_KEY): Promise<Appraisal> => {
|
||||
if (!isNonEmptyString(text)) {
|
||||
throw new Error('Invalid text: must be a non-empty string');
|
||||
}
|
||||
if (!isPositiveNumber(market_id)) {
|
||||
throw new Error('Invalid market_id: must be a positive number');
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/appraisal?market=${market_id}&persist=true&compactize=true&pricePercentage=1`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
'X-ApiKey': apiKey || '',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: text,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data as Appraisal;
|
||||
} catch (error) {
|
||||
console.error('Error appraising items:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
2
packages/eve/src/utils/index.ts
Normal file
2
packages/eve/src/utils/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './markdown';
|
||||
export * from './typeSearch';
|
||||
403
packages/eve/src/utils/markdown.test.ts
Normal file
403
packages/eve/src/utils/markdown.test.ts
Normal file
@@ -0,0 +1,403 @@
|
||||
import { describe, it, expect, beforeAll } from 'bun:test';
|
||||
import { cleanText, convertMillisecondsToTimeString, convertSecondsToTimeString, coloredTextCodeBlock } from './markdown';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
// Load test fixtures
|
||||
const basePath = path.join(__dirname, '../../fixtures/markdown');
|
||||
const markupFixturesPath = path.join(basePath, 'test-data-markup.json');
|
||||
const timeFixturesPath = path.join(basePath, 'test-data-time.json');
|
||||
const colorFixturesPath = path.join(basePath, 'test-data-colors.json');
|
||||
|
||||
let markupFixtures: any;
|
||||
let timeFixtures: any;
|
||||
let colorFixtures: any;
|
||||
|
||||
describe('cleanText', () => {
|
||||
beforeAll(() => {
|
||||
// Fixtures are already loaded above
|
||||
markupFixtures = JSON.parse(fs.readFileSync(markupFixturesPath, 'utf8'));
|
||||
timeFixtures = JSON.parse(fs.readFileSync(timeFixturesPath, 'utf8'));
|
||||
colorFixtures = JSON.parse(fs.readFileSync(colorFixturesPath, 'utf8'));
|
||||
});
|
||||
it('should handle basic bold markup', () => {
|
||||
const input = markupFixtures.boldMarkup.complete;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('**bold text**');
|
||||
});
|
||||
|
||||
it('should handle incomplete bold markup - open only', () => {
|
||||
const input = markupFixtures.boldMarkup.openOnly;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('**bold text**');
|
||||
});
|
||||
|
||||
it('should handle incomplete bold markup - close only', () => {
|
||||
const input = markupFixtures.boldMarkup.closeOnly;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('**bold text**');
|
||||
});
|
||||
|
||||
it('should handle basic italic markup', () => {
|
||||
const input = markupFixtures.italicMarkup.complete;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('*italic text*');
|
||||
});
|
||||
|
||||
it('should handle incomplete italic markup - open only', () => {
|
||||
const input = markupFixtures.italicMarkup.openOnly;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('*italic text*');
|
||||
});
|
||||
|
||||
it('should handle incomplete italic markup - close only', () => {
|
||||
const input = markupFixtures.italicMarkup.closeOnly;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('*italic text*');
|
||||
});
|
||||
|
||||
it('should remove color tags with hex colors', () => {
|
||||
const input = markupFixtures.colorTags.hex6;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('colored text');
|
||||
});
|
||||
|
||||
it('should remove color tags with hex colors (8-digit)', () => {
|
||||
const input = markupFixtures.colorTags.hex8;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('colored text');
|
||||
});
|
||||
|
||||
it('should remove color tags with named colors', () => {
|
||||
const input = markupFixtures.colorTags.namedColor;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('colored text');
|
||||
});
|
||||
|
||||
it('should respect max length parameter', () => {
|
||||
const longText = 'a'.repeat(100);
|
||||
const input = `<b>${longText}</b>`;
|
||||
const result = cleanText(input, 50);
|
||||
expect(result.length).toBeLessThanOrEqual(53); // Account for ** markup + truncation
|
||||
expect(result).toContain('**');
|
||||
});
|
||||
|
||||
it('should use default max length when not specified', () => {
|
||||
const veryLongText = 'a'.repeat(2000);
|
||||
const input = `<b>${veryLongText}</b>`;
|
||||
const result = cleanText(input);
|
||||
expect(result.length).toBeLessThanOrEqual(1003); // Account for ** markup
|
||||
});
|
||||
|
||||
it('should handle empty input', () => {
|
||||
const result = cleanText('');
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should handle whitespace-only input', () => {
|
||||
const result = cleanText(' \n\t ');
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should trim whitespace from input', () => {
|
||||
const input = ' <b>text</b> ';
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('**text**');
|
||||
});
|
||||
|
||||
it('should handle multiple bold tags', () => {
|
||||
const input = markupFixtures.boldMarkup.multiple;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('**first** and **second**');
|
||||
});
|
||||
|
||||
it('should handle multiple italic tags', () => {
|
||||
const input = markupFixtures.italicMarkup.multiple;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('*first* and *second*');
|
||||
});
|
||||
|
||||
it('should handle multiple color tags', () => {
|
||||
const input = markupFixtures.colorTags.multiple;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('first and second');
|
||||
});
|
||||
|
||||
it('should handle nested markup', () => {
|
||||
const input = markupFixtures.boldMarkup.nested;
|
||||
const result = cleanText(input);
|
||||
expect(result).toContain('**');
|
||||
});
|
||||
|
||||
it('should handle empty tags', () => {
|
||||
const result1 = cleanText(markupFixtures.boldMarkup.empty);
|
||||
const result2 = cleanText(markupFixtures.italicMarkup.empty);
|
||||
const result3 = cleanText(markupFixtures.colorTags.empty);
|
||||
|
||||
// The regex doesn't match empty content between tags
|
||||
expect(result1).toBe('<b></b>'); // No match, so unchanged
|
||||
expect(result2).toBe('<i></i>'); // No match, so unchanged
|
||||
expect(result3).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertMillisecondsToTimeString', () => {
|
||||
beforeAll(() => {
|
||||
// Fixtures are already loaded above
|
||||
markupFixtures = JSON.parse(fs.readFileSync(markupFixturesPath, 'utf8'));
|
||||
timeFixtures = JSON.parse(fs.readFileSync(timeFixturesPath, 'utf8'));
|
||||
colorFixtures = JSON.parse(fs.readFileSync(colorFixturesPath, 'utf8'));
|
||||
});
|
||||
it('should handle zero milliseconds', () => {
|
||||
const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.zero);
|
||||
expect(result).toBe(timeFixtures.expected.zero);
|
||||
});
|
||||
|
||||
it('should handle one second', () => {
|
||||
const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.oneSecond);
|
||||
expect(result).toBe(timeFixtures.expected.oneSecond);
|
||||
});
|
||||
|
||||
it('should handle one minute', () => {
|
||||
const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.oneMinute);
|
||||
expect(result).toBe(timeFixtures.expected.oneMinute);
|
||||
});
|
||||
|
||||
it('should handle one hour', () => {
|
||||
const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.oneHour);
|
||||
expect(result).toBe(timeFixtures.expected.oneHour);
|
||||
});
|
||||
|
||||
it('should handle complex time (1h 1m 1.5s)', () => {
|
||||
const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.complex);
|
||||
expect(result).toBe(timeFixtures.expected.complexMs);
|
||||
});
|
||||
|
||||
it('should handle large time values (24h)', () => {
|
||||
const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.daysWorthMs);
|
||||
expect(result).toBe(timeFixtures.expected.daysMs);
|
||||
});
|
||||
|
||||
it('should handle fractional seconds', () => {
|
||||
const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.fractionalSeconds);
|
||||
expect(result).toBe(timeFixtures.expected.fractionalSeconds);
|
||||
});
|
||||
|
||||
it('should handle small fractions', () => {
|
||||
const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.smallFraction);
|
||||
expect(result).toBe(timeFixtures.expected.smallFraction);
|
||||
});
|
||||
|
||||
it('should handle negative values', () => {
|
||||
const result = convertMillisecondsToTimeString(-1000);
|
||||
expect(result).toBe('-1.0s');
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertSecondsToTimeString', () => {
|
||||
beforeAll(() => {
|
||||
// Fixtures are already loaded above
|
||||
markupFixtures = JSON.parse(fs.readFileSync(markupFixturesPath, 'utf8'));
|
||||
timeFixtures = JSON.parse(fs.readFileSync(timeFixturesPath, 'utf8'));
|
||||
colorFixtures = JSON.parse(fs.readFileSync(colorFixturesPath, 'utf8'));
|
||||
});
|
||||
it('should handle zero seconds', () => {
|
||||
const result = convertSecondsToTimeString(timeFixtures.seconds.zero);
|
||||
expect(result).toBe('0s');
|
||||
});
|
||||
|
||||
it('should handle one second', () => {
|
||||
const result = convertSecondsToTimeString(timeFixtures.seconds.oneSecond);
|
||||
expect(result).toBe('1s');
|
||||
});
|
||||
|
||||
it('should handle one minute', () => {
|
||||
const result = convertSecondsToTimeString(timeFixtures.seconds.oneMinute);
|
||||
expect(result).toBe('1m');
|
||||
});
|
||||
|
||||
it('should handle one hour', () => {
|
||||
const result = convertSecondsToTimeString(timeFixtures.seconds.oneHour);
|
||||
expect(result).toBe('1h');
|
||||
});
|
||||
|
||||
it('should handle complex time (1h 1m 1s)', () => {
|
||||
const result = convertSecondsToTimeString(timeFixtures.seconds.complex);
|
||||
expect(result).toBe(timeFixtures.expected.complexSec);
|
||||
});
|
||||
|
||||
it('should handle large time values (24h)', () => {
|
||||
const result = convertSecondsToTimeString(timeFixtures.seconds.daysWorthSec);
|
||||
expect(result).toBe(timeFixtures.expected.daysSec);
|
||||
});
|
||||
|
||||
it('should handle fractional input (should floor to integer seconds)', () => {
|
||||
const result = convertSecondsToTimeString(timeFixtures.seconds.fractionalInput);
|
||||
expect(result).toBe('1h 1m 1.5s'); // Function doesn't actually floor, it preserves fractional seconds
|
||||
});
|
||||
|
||||
it('should handle negative values', () => {
|
||||
const result = convertSecondsToTimeString(-60);
|
||||
expect(result).toBe('0s'); // Current implementation doesn't handle negatives properly
|
||||
});
|
||||
|
||||
it('should not include seconds when there are hours and minutes but no remainder seconds', () => {
|
||||
const result = convertSecondsToTimeString(3660); // 1h 1m 0s
|
||||
expect(result).toBe('1h 1m');
|
||||
});
|
||||
});
|
||||
|
||||
describe('coloredTextCodeBlock', () => {
|
||||
beforeAll(() => {
|
||||
// Fixtures are already loaded above
|
||||
markupFixtures = JSON.parse(fs.readFileSync(markupFixturesPath, 'utf8'));
|
||||
timeFixtures = JSON.parse(fs.readFileSync(timeFixturesPath, 'utf8'));
|
||||
colorFixtures = JSON.parse(fs.readFileSync(colorFixturesPath, 'utf8'));
|
||||
});
|
||||
it('should create red colored text block', () => {
|
||||
const input = colorFixtures.testText.simple;
|
||||
const result = coloredTextCodeBlock(input, 'red');
|
||||
expect(result).toBe(colorFixtures.expected.red.simple);
|
||||
});
|
||||
|
||||
it('should create blue colored text block', () => {
|
||||
const input = colorFixtures.testText.simple;
|
||||
const result = coloredTextCodeBlock(input, 'blue');
|
||||
expect(result).toBe(colorFixtures.expected.blue.simple);
|
||||
});
|
||||
|
||||
it('should create green colored text block', () => {
|
||||
const input = colorFixtures.testText.simple;
|
||||
const result = coloredTextCodeBlock(input, 'green');
|
||||
expect(result).toBe(colorFixtures.expected.green.simple);
|
||||
});
|
||||
|
||||
it('should create yellow colored text block', () => {
|
||||
const input = colorFixtures.testText.simple;
|
||||
const result = coloredTextCodeBlock(input, 'yellow');
|
||||
expect(result).toBe(colorFixtures.expected.yellow.simple);
|
||||
});
|
||||
|
||||
it('should handle empty text with red color', () => {
|
||||
const result = coloredTextCodeBlock('', 'red');
|
||||
expect(result).toBe(colorFixtures.expected.red.empty);
|
||||
});
|
||||
|
||||
it('should handle empty text with blue color', () => {
|
||||
const result = coloredTextCodeBlock('', 'blue');
|
||||
expect(result).toBe(colorFixtures.expected.blue.empty);
|
||||
});
|
||||
|
||||
it('should handle empty text with green color', () => {
|
||||
const result = coloredTextCodeBlock('', 'green');
|
||||
expect(result).toBe(colorFixtures.expected.green.empty);
|
||||
});
|
||||
|
||||
it('should handle empty text with yellow color', () => {
|
||||
const result = coloredTextCodeBlock('', 'yellow');
|
||||
expect(result).toBe(colorFixtures.expected.yellow.empty);
|
||||
});
|
||||
|
||||
it('should handle multiline text', () => {
|
||||
const input = colorFixtures.testText.multiline;
|
||||
const result = coloredTextCodeBlock(input, 'red');
|
||||
expect(result).toContain('Line 1\nLine 2\nLine 3');
|
||||
expect(result).toContain('```ansi');
|
||||
expect(result).toContain('\u001B[2;31m');
|
||||
});
|
||||
|
||||
it('should handle special characters', () => {
|
||||
const input = colorFixtures.testText.withSpecialChars;
|
||||
const result = coloredTextCodeBlock(input, 'green');
|
||||
expect(result).toContain(input);
|
||||
expect(result).toContain('```ansi');
|
||||
expect(result).toContain('\u001B[2;36m');
|
||||
});
|
||||
|
||||
it('should handle unicode characters', () => {
|
||||
const input = colorFixtures.testText.unicode;
|
||||
const result = coloredTextCodeBlock(input, 'yellow');
|
||||
expect(result).toContain(input);
|
||||
expect(result).toContain('```ansi');
|
||||
expect(result).toContain('\u001B[2;33m');
|
||||
});
|
||||
|
||||
it('should handle code-like text', () => {
|
||||
const input = colorFixtures.testText.code;
|
||||
const result = coloredTextCodeBlock(input, 'blue');
|
||||
expect(result).toContain(input);
|
||||
expect(result).toContain('```ansi');
|
||||
expect(result).toContain('\u001B[2;32m\u001B[2;36m\u001B[2;34m');
|
||||
});
|
||||
|
||||
it('should return original text for invalid color', () => {
|
||||
const input = 'test text';
|
||||
// TypeScript should prevent this, but testing runtime behavior
|
||||
const result = coloredTextCodeBlock(input, 'invalid' as any);
|
||||
expect(result).toBe(input);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases and error handling', () => {
|
||||
beforeAll(() => {
|
||||
// Fixtures are already loaded above
|
||||
markupFixtures = JSON.parse(fs.readFileSync(markupFixturesPath, 'utf8'));
|
||||
timeFixtures = JSON.parse(fs.readFileSync(timeFixturesPath, 'utf8'));
|
||||
colorFixtures = JSON.parse(fs.readFileSync(colorFixturesPath, 'utf8'));
|
||||
});
|
||||
it('should handle malformed markup gracefully', () => {
|
||||
const input = '<b>unclosed bold <i>nested italic</b> text</i>';
|
||||
const result = cleanText(input);
|
||||
expect(result).toContain('**');
|
||||
expect(result).toContain('*');
|
||||
});
|
||||
|
||||
it('should handle deeply nested markup', () => {
|
||||
const input = '<b><i><color=red><a href=showinfo:587>Deep Nesting</a></color></i></b>';
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('***Deep Nesting***');
|
||||
});
|
||||
|
||||
it('should handle very large time values', () => {
|
||||
const largeValue = 1000 * 60 * 60 * 24 * 365; // 1 year in ms
|
||||
const result = convertMillisecondsToTimeString(largeValue);
|
||||
expect(result).toContain('h');
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should handle very small time values', () => {
|
||||
const result = convertMillisecondsToTimeString(1);
|
||||
expect(result).toBe('0.0s');
|
||||
});
|
||||
|
||||
it('should handle text with no markup', () => {
|
||||
const input = 'Plain text with no markup';
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe(input);
|
||||
});
|
||||
|
||||
it('should handle only whitespace in markup', () => {
|
||||
const input = '<b> </b>';
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('** **');
|
||||
});
|
||||
|
||||
it('should handle EVE links with very large IDs', () => {
|
||||
const input = '<a href=showinfo:999999999>Large ID Item</a>';
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('Large ID Item');
|
||||
});
|
||||
|
||||
it('should handle color tags with various hex formats', () => {
|
||||
const testCases = [
|
||||
{ input: '<color=0xABC123>hex with 0x</color>', expected: 'hex with 0x' },
|
||||
{ input: '<color=ABC123>hex without 0x</color>', expected: 'hex without 0x' },
|
||||
{ input: '<color=0xABC12345>8-digit hex</color>', expected: '8-digit hex' },
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, expected }) => {
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
105
packages/eve/src/utils/markdown.ts
Normal file
105
packages/eve/src/utils/markdown.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { truncateText } from '@star-kitten/util/text.js';
|
||||
|
||||
export function cleanText(input: string, maxLength: number = 1000): string {
|
||||
return truncateText(replaceBoldTextMarkup(replaceItalicTextMarkup(removeColorTags(removeLinks(input.trim())))), maxLength);
|
||||
}
|
||||
|
||||
function replaceBoldTextMarkup(input: string): string {
|
||||
// replace all <b>name</b>, <b>name, and name</b> with **name** using regex
|
||||
const regex = /<b>([^<]*)<\/b>|<b>([^<]*)|([^<]*)<\/b>/g;
|
||||
return input.replace(regex, (match, p1, p2, p3) => {
|
||||
if (p1) return `**${p1}**`;
|
||||
if (p2) return `**${p2}**`;
|
||||
if (p3) return `**${p3}**`;
|
||||
return match;
|
||||
});
|
||||
}
|
||||
|
||||
function replaceItalicTextMarkup(input: string): string {
|
||||
// replace all <i>name</i>, <i>name, and name</i> with *name* using regex
|
||||
const regex = /<i>([^<]*)<\/i>|<i>([^<]*)|([^<]*)<\/i>/g;
|
||||
return input.replace(regex, (match, p1, p2, p3) => {
|
||||
if (p1) return `*${p1}*`;
|
||||
if (p2) return `*${p2}*`;
|
||||
if (p3) return `*${p3}*`;
|
||||
return match;
|
||||
});
|
||||
}
|
||||
|
||||
function removeColorTags(input: string): string {
|
||||
const regex = /<color=(?:0x)?([0-9a-fA-F]{6,8}|[a-zA-Z]+)>(.*?)<\/color>/g;
|
||||
return input.replace(regex, '$2');
|
||||
}
|
||||
|
||||
function convertToDiscordLinks(input: string): string {
|
||||
const regex = /<a href=showinfo:(\d+)>(.*?)<\/a>/g;
|
||||
return input.replace(regex, (match, number, text) => {
|
||||
const eveRefLink = `https://everef.net/types/${number}`;
|
||||
return `[${text}](${eveRefLink})`;
|
||||
});
|
||||
}
|
||||
|
||||
function removeLinks(input: string): string {
|
||||
const regex = /<a href=showinfo:(\d+)>(.*?)<\/a>/g;
|
||||
return input.replace(regex, (match, number, text) => {
|
||||
return text;
|
||||
});
|
||||
}
|
||||
|
||||
export function convertMillisecondsToTimeString(milliseconds: number): string {
|
||||
const totalSeconds = milliseconds / 1000;
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const secs = totalSeconds % 60;
|
||||
|
||||
const parts: string[] = [];
|
||||
|
||||
if (hours > 0) {
|
||||
parts.push(`${hours}h`);
|
||||
}
|
||||
if (minutes > 0) {
|
||||
parts.push(`${minutes}m`);
|
||||
}
|
||||
if (secs > 0 || parts.length === 0) {
|
||||
// Include seconds if it's the only part
|
||||
parts.push(`${secs.toFixed(1)}s`);
|
||||
}
|
||||
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
export function convertSecondsToTimeString(seconds: number): string {
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const secs = seconds % 60;
|
||||
|
||||
const parts: string[] = [];
|
||||
|
||||
if (hours > 0) {
|
||||
parts.push(`${hours}h`);
|
||||
}
|
||||
if (minutes > 0) {
|
||||
parts.push(`${minutes}m`);
|
||||
}
|
||||
if (secs > 0 || parts.length === 0) {
|
||||
// Include seconds if it's the only part
|
||||
parts.push(`${secs}s`);
|
||||
}
|
||||
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
export function coloredTextCodeBlock(text: string, color: 'green' | 'blue' | 'red' | 'yellow'): string {
|
||||
switch (color) {
|
||||
case 'red':
|
||||
return '```ansi\n[2;31m' + text + '[0m```\n';
|
||||
case 'blue':
|
||||
return '```ansi\n[2;32m[2;36m[2;34m' + text + '[0m[2;36m[0m[2;32m[0m```\n';
|
||||
case 'yellow':
|
||||
return '```ansi\n[2;33m' + text + '[0m```\n';
|
||||
case 'green':
|
||||
return '```ansi\n[2;36m' + text + '[0m```\n';
|
||||
default:
|
||||
return text;
|
||||
}
|
||||
}
|
||||
73
packages/eve/src/utils/typeSearch.ts
Normal file
73
packages/eve/src/utils/typeSearch.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import fs from 'node:fs';
|
||||
import { chain } from 'stream-chain';
|
||||
import { parser } from 'stream-json';
|
||||
import { streamObject } from 'stream-json/streamers/StreamObject';
|
||||
import { create, insert, search } from '@orama/orama';
|
||||
import { normalize } from '@star-kitten/util/text';
|
||||
import { getType, type Type } from '@/models/type';
|
||||
|
||||
const db = create({
|
||||
schema: {
|
||||
type_id: 'number',
|
||||
name: {
|
||||
en: 'string',
|
||||
de: 'string',
|
||||
fr: 'string',
|
||||
ru: 'string',
|
||||
ja: 'string',
|
||||
zh: 'string',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export async function initializeTypeSearch() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const pipeline = chain([fs.createReadStream('./data/reference-data/types.json'), parser(), streamObject(), (data) => data]);
|
||||
|
||||
pipeline.on('data', async ({ value }) => {
|
||||
if (value && value.market_group_id && value.published) {
|
||||
try {
|
||||
await addType(value);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
pipeline.on('error', reject);
|
||||
pipeline.on('end', resolve);
|
||||
});
|
||||
}
|
||||
|
||||
const addType = async (type: Type) =>
|
||||
await insert(db, {
|
||||
type_id: type.type_id,
|
||||
name: type.name,
|
||||
});
|
||||
|
||||
export async function typeSearch(name: string) {
|
||||
let now = Date.now();
|
||||
const normalizedName = normalize(name);
|
||||
if (normalizedName.length > 100) return null;
|
||||
const results = await search(db, {
|
||||
term: normalizedName,
|
||||
limit: 1,
|
||||
tolerance: 0,
|
||||
});
|
||||
if (!results || results.count === 0) return null;
|
||||
now = Date.now();
|
||||
const type = await getType(results.hits[0].document.type_id);
|
||||
return type;
|
||||
}
|
||||
|
||||
export async function typeSearchAutoComplete(name: string) {
|
||||
const normalizedName = normalize(name);
|
||||
if (normalizedName.length > 100) return null;
|
||||
const results = await search(db, {
|
||||
term: normalizedName,
|
||||
});
|
||||
if (!results || results.count === 0) return null;
|
||||
return results.hits.map((hit) => ({
|
||||
name: hit.document.name.en,
|
||||
value: hit.document.name.en,
|
||||
}));
|
||||
}
|
||||
24
packages/eve/tsconfig.json
Normal file
24
packages/eve/tsconfig.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"strict": false,
|
||||
"noImplicitAny": false,
|
||||
"skipLibCheck": true,
|
||||
"lib": ["ESNext"],
|
||||
"typeRoots": ["src/types", "./node_modules/@types"],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@data/*": ["./data/*"],
|
||||
},
|
||||
"emitDeclarationOnly": true,
|
||||
"noEmit": false,
|
||||
"noEmitOnError": false,
|
||||
"declaration": true,
|
||||
"outDir": "dist/types",
|
||||
"rootDir": ".",
|
||||
"allowImportingTsExtensions": true
|
||||
},
|
||||
"include": ["src", "fixtures"],
|
||||
"exclude": ["node_modules", "build", "**/*.test.ts"]
|
||||
}
|
||||
16
packages/eve/tsdown.config.ts
Normal file
16
packages/eve/tsdown.config.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { defineConfig } from 'tsdown';
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
entry: [
|
||||
'./src/**/*.ts',
|
||||
'!./src/**/*.test.ts',
|
||||
],
|
||||
platform: 'node',
|
||||
dts: true,
|
||||
minify: false,
|
||||
sourcemap: true,
|
||||
unbundle: true,
|
||||
external: ['bun:sqlite', 'bun'],
|
||||
},
|
||||
]);
|
||||
Reference in New Issue
Block a user