mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-07 06:18:03 +03:00
Merge pull request #272 from zkldi:zkldi/issue-246-Kai-API-dan-retrieving
Kai-API dan retrieving (and MIN support)
This commit is contained in:
Vendored
+1
@@ -8,6 +8,7 @@
|
||||
SESSION_SECRET: "unused",
|
||||
FLO_API_URL: "https://flo.example.com",
|
||||
EAG_API_URL: "https://eag.example.com",
|
||||
MIN_API_URL: "https://min.example.com",
|
||||
ARC_API_URL: "https://arc.example.com",
|
||||
ARC_AUTH_TOKEN: "unused",
|
||||
CDN_FILE_ROOT: "./local-cdn",
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@
|
||||
"redis": "3.1.2",
|
||||
"rimraf": "3.0.2",
|
||||
"safe-json-stringify": "1.2.0",
|
||||
"tachi-common": "^0.1.5",
|
||||
"tachi-common": "^0.1.6",
|
||||
"typescript": "4.3.4",
|
||||
"winston": "3.3.3"
|
||||
},
|
||||
|
||||
Generated
+4
-4
@@ -51,7 +51,7 @@ specifiers:
|
||||
rimraf: 3.0.2
|
||||
safe-json-stringify: 1.2.0
|
||||
supertest: 6.1.3
|
||||
tachi-common: ^0.1.5
|
||||
tachi-common: ^0.1.6
|
||||
tap: 15.0.9
|
||||
ts-node: 10.0.0
|
||||
tsconfig-paths: 3.10.1
|
||||
@@ -84,7 +84,7 @@ dependencies:
|
||||
redis: 3.1.2
|
||||
rimraf: 3.0.2
|
||||
safe-json-stringify: 1.2.0
|
||||
tachi-common: 0.1.5_ts-node@10.0.0+typescript@4.3.4
|
||||
tachi-common: 0.1.6_ts-node@10.0.0+typescript@4.3.4
|
||||
typescript: 4.3.4
|
||||
winston: 3.3.3
|
||||
|
||||
@@ -3774,8 +3774,8 @@ packages:
|
||||
strip-ansi: 6.0.0
|
||||
dev: true
|
||||
|
||||
/tachi-common/0.1.5_ts-node@10.0.0+typescript@4.3.4:
|
||||
resolution: {integrity: sha512-caqTQ43TqTb861lenL27cIFxU9enZJCulSBU6kk1db2/XJNmNsLNtoo5xDwbgYs4eSwxkENGBZ1THbeBG0B5RQ==}
|
||||
/tachi-common/0.1.6_ts-node@10.0.0+typescript@4.3.4:
|
||||
resolution: {integrity: sha512-H2kjAIUXyvNGuPlOgvWc25KmujETee4XqRRSG/BQIall0FSiO7yWApZQMelQZITebdlKwzpd976P2NSejTv0Yg==}
|
||||
dependencies:
|
||||
monk: 7.3.4
|
||||
tap: 15.0.9_ts-node@10.0.0+typescript@4.3.4
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export enum IIDXClasses {
|
||||
export enum IIDXDans {
|
||||
KYU_7,
|
||||
KYU_6,
|
||||
KYU_5,
|
||||
|
||||
@@ -2,7 +2,7 @@ import t from "tap";
|
||||
import { CloseAllConnections } from "test-utils/close-connections";
|
||||
import { MockJSONFetch } from "test-utils/mock-fetch";
|
||||
import ResetDBState from "test-utils/resets";
|
||||
import { IIDXClasses } from "lib/constants/classes";
|
||||
import { IIDXDans } from "lib/constants/classes";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { CreateArcIIDXClassHandler } from "./class-handler";
|
||||
|
||||
@@ -34,10 +34,10 @@ t.test("#CreateArcIIDXClassHandler", (t) => {
|
||||
// its not async but typescript complains
|
||||
const res = await fn("iidx", "SP", 1, {}, logger);
|
||||
|
||||
t.equal(res!.dan, IIDXClasses.CHUUDEN);
|
||||
t.equal(res!.dan, IIDXDans.CHUUDEN);
|
||||
|
||||
const res2 = await fn("iidx", "DP", 1, {}, logger);
|
||||
t.equal(res2!.dan, IIDXClasses.DAN_2);
|
||||
t.equal(res2!.dan, IIDXDans.DAN_2);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import nodeFetch from "utils/fetch";
|
||||
import { HasOwnProperty } from "utils/misc";
|
||||
import { IIDXClasses } from "lib/constants/classes";
|
||||
import { IIDXDans } from "lib/constants/classes";
|
||||
import { ClassHandler } from "../../../framework/user-game-stats/types";
|
||||
import { ServerConfig } from "lib/setup/config";
|
||||
|
||||
@@ -72,24 +72,24 @@ export async function CreateArcIIDXClassHandler(
|
||||
}
|
||||
|
||||
const ARCClasses = {
|
||||
皆伝: IIDXClasses.KAIDEN,
|
||||
中伝: IIDXClasses.CHUUDEN,
|
||||
十段: IIDXClasses.DAN_10,
|
||||
九段: IIDXClasses.DAN_9,
|
||||
八段: IIDXClasses.DAN_8,
|
||||
七段: IIDXClasses.DAN_7,
|
||||
六段: IIDXClasses.DAN_6,
|
||||
五段: IIDXClasses.DAN_5,
|
||||
四段: IIDXClasses.DAN_4,
|
||||
三段: IIDXClasses.DAN_3,
|
||||
二段: IIDXClasses.DAN_2, // These two look very similar but they aren't
|
||||
ニ段: IIDXClasses.DAN_2, // and ARC uses both, from what I can tell.
|
||||
初段: IIDXClasses.DAN_1,
|
||||
一級: IIDXClasses.KYU_1,
|
||||
二級: IIDXClasses.KYU_2,
|
||||
三級: IIDXClasses.KYU_3,
|
||||
四級: IIDXClasses.KYU_4,
|
||||
五級: IIDXClasses.KYU_5,
|
||||
六級: IIDXClasses.KYU_6,
|
||||
七級: IIDXClasses.KYU_7,
|
||||
皆伝: IIDXDans.KAIDEN,
|
||||
中伝: IIDXDans.CHUUDEN,
|
||||
十段: IIDXDans.DAN_10,
|
||||
九段: IIDXDans.DAN_9,
|
||||
八段: IIDXDans.DAN_8,
|
||||
七段: IIDXDans.DAN_7,
|
||||
六段: IIDXDans.DAN_6,
|
||||
五段: IIDXDans.DAN_5,
|
||||
四段: IIDXDans.DAN_4,
|
||||
三段: IIDXDans.DAN_3,
|
||||
二段: IIDXDans.DAN_2, // These two look very similar but they aren't
|
||||
ニ段: IIDXDans.DAN_2, // and ARC uses both, from what I can tell.
|
||||
初段: IIDXDans.DAN_1,
|
||||
一級: IIDXDans.KYU_1,
|
||||
二級: IIDXDans.KYU_2,
|
||||
三級: IIDXDans.KYU_3,
|
||||
四級: IIDXDans.KYU_4,
|
||||
五級: IIDXDans.KYU_5,
|
||||
六級: IIDXDans.KYU_6,
|
||||
七級: IIDXDans.KYU_7,
|
||||
};
|
||||
|
||||
@@ -21,7 +21,11 @@ export async function ParseArcIIDX(
|
||||
fetch
|
||||
),
|
||||
context: {},
|
||||
classHandler: await CreateArcIIDXClassHandler(arcProfileID, ServerConfig.ARC_AUTH_TOKEN),
|
||||
classHandler: await CreateArcIIDXClassHandler(
|
||||
arcProfileID,
|
||||
ServerConfig.ARC_AUTH_TOKEN,
|
||||
fetch
|
||||
),
|
||||
game: "iidx",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { KtLogger } from "lib/logger/logger";
|
||||
import { KaiAuthDocument } from "tachi-common";
|
||||
import { ParseKaiSDVX } from "../../common/api-kai/sdvx/parser";
|
||||
|
||||
export function ParseMinSDVX(authDoc: KaiAuthDocument, logger: KtLogger) {
|
||||
return ParseKaiSDVX("MIN", authDoc, logger);
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
import { IIDXDans } from "lib/constants/classes";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import t from "tap";
|
||||
import { CloseAllConnections } from "test-utils/close-connections";
|
||||
import { MockBasicFetch, MockJSONFetch } from "test-utils/mock-fetch";
|
||||
import ResetDBState from "test-utils/resets";
|
||||
import { KaiTypeToBaseURL } from "utils/misc";
|
||||
import { CreateKaiIIDXClassHandler } from "./class-handler";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
t.test("#CreateKaiIIDXClassHandler", async (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
const fn = await CreateKaiIIDXClassHandler(
|
||||
"FLO",
|
||||
"token",
|
||||
MockJSONFetch({
|
||||
[`${KaiTypeToBaseURL("FLO")}/api/iidx/v2/player_profile`]: {
|
||||
_links: {},
|
||||
iidx_id: 12345678,
|
||||
dj_name: "SOMEONE",
|
||||
sp: 18,
|
||||
dp: 4,
|
||||
access_time: "2021-08-08T18:50:40Z",
|
||||
register_time: "2019-01-19T12:53:50Z",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
t.test("Should return a function with arity 5.", (t) => {
|
||||
t.equal(fn.length, 5);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should call the provided URL with the authentication token", (t) => {
|
||||
const res = fn("iidx", "SP", 1, {}, logger);
|
||||
|
||||
t.strictSame(res, { dan: 18 });
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should return nothing if dan is not a number", async (t) => {
|
||||
const fn = await CreateKaiIIDXClassHandler(
|
||||
"FLO",
|
||||
"token",
|
||||
MockJSONFetch({
|
||||
[`${KaiTypeToBaseURL("FLO")}/api/iidx/v2/player_profile`]: {
|
||||
_links: {},
|
||||
iidx_id: 12345678,
|
||||
dj_name: "SOMEONE",
|
||||
sp: "NOT A NUMBER",
|
||||
dp: 4,
|
||||
access_time: "2021-08-08T18:50:40Z",
|
||||
register_time: "2019-01-19T12:53:50Z",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const res = fn("iidx", "SP", 1, {}, logger);
|
||||
|
||||
t.strictSame(res, {});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should return nothing if dan is too great", async (t) => {
|
||||
const fn = await CreateKaiIIDXClassHandler(
|
||||
"FLO",
|
||||
"token",
|
||||
MockJSONFetch({
|
||||
[`${KaiTypeToBaseURL("FLO")}/api/iidx/v2/player_profile`]: {
|
||||
_links: {},
|
||||
iidx_id: 12345678,
|
||||
dj_name: "SOMEONE",
|
||||
sp: IIDXDans.KAIDEN + 1,
|
||||
dp: 4,
|
||||
access_time: "2021-08-08T18:50:40Z",
|
||||
register_time: "2019-01-19T12:53:50Z",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const res = fn("iidx", "SP", 1, {}, logger);
|
||||
|
||||
t.strictSame(res, {});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should return nothing if dan is negative", async (t) => {
|
||||
const fn = await CreateKaiIIDXClassHandler(
|
||||
"FLO",
|
||||
"token",
|
||||
MockJSONFetch({
|
||||
[`${KaiTypeToBaseURL("FLO")}/api/iidx/v2/player_profile`]: {
|
||||
_links: {},
|
||||
iidx_id: 12345678,
|
||||
dj_name: "SOMEONE",
|
||||
sp: IIDXDans.KYU_7 - 1,
|
||||
dp: 4,
|
||||
access_time: "2021-08-08T18:50:40Z",
|
||||
register_time: "2019-01-19T12:53:50Z",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const res = fn("iidx", "SP", 1, {}, logger);
|
||||
|
||||
t.strictSame(res, {});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should gracefully handle negative API responses", async (t) => {
|
||||
const fn = await CreateKaiIIDXClassHandler("FLO", "token", MockBasicFetch({ status: 500 }));
|
||||
|
||||
const res = fn("iidx", "SP", 1, {}, logger);
|
||||
|
||||
t.strictSame(res, {});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should ignore null dans", async (t) => {
|
||||
const fn = await CreateKaiIIDXClassHandler(
|
||||
"FLO",
|
||||
"token",
|
||||
MockJSONFetch({
|
||||
[`${KaiTypeToBaseURL("FLO")}/api/iidx/v2/player_profile`]: {
|
||||
_links: {},
|
||||
iidx_id: 12345678,
|
||||
dj_name: "SOMEONE",
|
||||
sp: 1,
|
||||
dp: null,
|
||||
access_time: "2021-08-08T18:50:40Z",
|
||||
register_time: "2019-01-19T12:53:50Z",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const res = fn("iidx", "DP", 1, {}, logger);
|
||||
|
||||
t.strictSame(res, {});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should handle invalid playtypes", async (t) => {
|
||||
const fn = await CreateKaiIIDXClassHandler(
|
||||
"FLO",
|
||||
"token",
|
||||
MockJSONFetch({
|
||||
[`${KaiTypeToBaseURL("FLO")}/api/iidx/v2/player_profile`]: {
|
||||
_links: {},
|
||||
iidx_id: 12345678,
|
||||
dj_name: "SOMEONE",
|
||||
sp: 1,
|
||||
dp: 1,
|
||||
access_time: "2021-08-08T18:50:40Z",
|
||||
register_time: "2019-01-19T12:53:50Z",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const res = fn("iidx", "14K", 1, {}, logger);
|
||||
|
||||
t.strictSame(res, {});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.teardown(CloseAllConnections);
|
||||
@@ -0,0 +1,74 @@
|
||||
import { IIDXDans } from "lib/constants/classes";
|
||||
import { ClassHandler } from "lib/score-import/framework/user-game-stats/types";
|
||||
import nodeFetch from "utils/fetch";
|
||||
import { KaiTypeToBaseURL } from "utils/misc";
|
||||
|
||||
export async function CreateKaiIIDXClassHandler(
|
||||
kaiType: "FLO" | "EAG",
|
||||
token: string,
|
||||
fetch = nodeFetch
|
||||
): Promise<ClassHandler> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let json: any;
|
||||
let err: unknown;
|
||||
const baseUrl = KaiTypeToBaseURL(kaiType);
|
||||
|
||||
// SP and DP dans are located in the same place,
|
||||
// fetch once, then return a function that traverses this data.
|
||||
try {
|
||||
const res = await fetch(`${baseUrl}/api/iidx/v2/player_profile`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
json = await res.json();
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
|
||||
return (game, playtype, userID, ratings, logger) => {
|
||||
if (err) {
|
||||
logger.error(`An error occured while updating classes for ${baseUrl}.`, { err });
|
||||
return {};
|
||||
}
|
||||
|
||||
let iidxDan: number | null;
|
||||
|
||||
if (playtype === "SP") {
|
||||
iidxDan = json.sp;
|
||||
} else if (playtype === "DP") {
|
||||
iidxDan = json.dp;
|
||||
} else {
|
||||
logger.warn(`KAIIIDXClassUpdater called with invalid playtype of ${playtype}.`);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (iidxDan === null) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!Number.isInteger(iidxDan)) {
|
||||
logger.warn(`${baseUrl} returned a dan of ${iidxDan}, which was not a number.`);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (iidxDan > IIDXDans.KAIDEN) {
|
||||
logger.warn(
|
||||
`${baseUrl} returned a dan of ${iidxDan}, which was greater than KAIDEN (${IIDXDans.KAIDEN}.)`
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (iidxDan < IIDXDans.KYU_7) {
|
||||
logger.warn(
|
||||
`${baseUrl} returned a dan of ${iidxDan}, which was less than KYU_7 (${IIDXDans.KYU_7}.)`
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
dan: iidxDan,
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -33,10 +33,9 @@ t.test("#ParseKaiIIDX", (t) => {
|
||||
},
|
||||
});
|
||||
|
||||
const res = ParseKaiIIDX("FLO", fakeAuth, logger, mockFloAPI);
|
||||
const res = await ParseKaiIIDX("FLO", fakeAuth, logger, mockFloAPI);
|
||||
|
||||
t.equal(res.game, "iidx");
|
||||
t.equal(res.classHandler, null);
|
||||
t.strictSame(res.context, { service: "FLO" });
|
||||
|
||||
const iter = [];
|
||||
@@ -66,10 +65,9 @@ t.test("#ParseKaiIIDX", (t) => {
|
||||
},
|
||||
});
|
||||
|
||||
const res = ParseKaiIIDX("EAG", fakeAuth, logger, mockEagAPI);
|
||||
const res = await ParseKaiIIDX("EAG", fakeAuth, logger, mockEagAPI);
|
||||
|
||||
t.equal(res.game, "iidx");
|
||||
t.equal(res.classHandler, null);
|
||||
t.strictSame(res.context, { service: "EAG" });
|
||||
|
||||
const iter = [];
|
||||
|
||||
@@ -5,14 +5,16 @@ import { KaiContext } from "../types";
|
||||
import { TraverseKaiAPI } from "../traverse-api";
|
||||
import { ParserFunctionReturns } from "../../types";
|
||||
import { ServerConfig } from "lib/setup/config";
|
||||
import { KaiTypeToBaseURL } from "utils/misc";
|
||||
import { CreateKaiIIDXClassHandler } from "./class-handler";
|
||||
|
||||
export function ParseKaiIIDX(
|
||||
export async function ParseKaiIIDX(
|
||||
service: "FLO" | "EAG",
|
||||
authDoc: KaiAuthDocument,
|
||||
logger: KtLogger,
|
||||
fetch = nodeFetch
|
||||
): ParserFunctionReturns<unknown, KaiContext> {
|
||||
const baseUrl = service === "FLO" ? ServerConfig.FLO_API_URL : ServerConfig.EAG_API_URL;
|
||||
): Promise<ParserFunctionReturns<unknown, KaiContext>> {
|
||||
const baseUrl = KaiTypeToBaseURL(service);
|
||||
|
||||
return {
|
||||
iterable: TraverseKaiAPI(
|
||||
@@ -25,7 +27,7 @@ export function ParseKaiIIDX(
|
||||
context: {
|
||||
service,
|
||||
},
|
||||
classHandler: null,
|
||||
classHandler: await CreateKaiIIDXClassHandler(service, authDoc.token, fetch),
|
||||
game: "iidx",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
import { SDVXDans } from "lib/constants/classes";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import t from "tap";
|
||||
import { CloseAllConnections } from "test-utils/close-connections";
|
||||
import { MockBasicFetch, MockJSONFetch } from "test-utils/mock-fetch";
|
||||
import ResetDBState from "test-utils/resets";
|
||||
import { KaiTypeToBaseURL } from "utils/misc";
|
||||
import { CreateKaiSDVXClassHandler } from "./class-handler";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
t.test("#CreateKaiSDVXClassHandler", async (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
const fn = await CreateKaiSDVXClassHandler(
|
||||
"FLO",
|
||||
"token",
|
||||
MockJSONFetch({
|
||||
[`${KaiTypeToBaseURL("FLO")}/api/sdvx/v1/player_profile`]: {
|
||||
_links: {},
|
||||
sdvx_id: 12345678,
|
||||
name: "SOMEONE",
|
||||
skill_level: 10,
|
||||
access_time: "2019-08-26T18:22:36Z",
|
||||
register_time: "2019-08-26T18:22:36Z",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
t.test("Should return a function with arity 5.", (t) => {
|
||||
t.equal(fn.length, 5);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should call the provided URL with the authentication token", (t) => {
|
||||
const res = fn("sdvx", "Single", 1, {}, logger);
|
||||
|
||||
t.strictSame(res, { dan: 10 });
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should return nothing if dan is not a number", async (t) => {
|
||||
const fn = await CreateKaiSDVXClassHandler(
|
||||
"FLO",
|
||||
"token",
|
||||
MockJSONFetch({
|
||||
[`${KaiTypeToBaseURL("FLO")}/api/sdvx/v1/player_profile`]: {
|
||||
_links: {},
|
||||
sdvx_id: 12345678,
|
||||
name: "SOMEONE",
|
||||
skill_level: "NOT A NUMBER",
|
||||
access_time: "2019-08-26T18:22:36Z",
|
||||
register_time: "2019-08-26T18:22:36Z",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const res = fn("sdvx", "Single", 1, {}, logger);
|
||||
|
||||
t.strictSame(res, {});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should return nothing if dan is too great", async (t) => {
|
||||
const fn = await CreateKaiSDVXClassHandler(
|
||||
"FLO",
|
||||
"token",
|
||||
MockJSONFetch({
|
||||
[`${KaiTypeToBaseURL("FLO")}/api/sdvx/v1/player_profile`]: {
|
||||
_links: {},
|
||||
sdvx_id: 12345678,
|
||||
name: "SOMEONE",
|
||||
skill_level: SDVXDans.INF + 1,
|
||||
access_time: "2019-08-26T18:22:36Z",
|
||||
register_time: "2019-08-26T18:22:36Z",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const res = fn("sdvx", "Single", 1, {}, logger);
|
||||
|
||||
t.strictSame(res, {});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should return nothing if dan is negative", async (t) => {
|
||||
const fn = await CreateKaiSDVXClassHandler(
|
||||
"FLO",
|
||||
"token",
|
||||
MockJSONFetch({
|
||||
[`${KaiTypeToBaseURL("FLO")}/api/sdvx/v1/player_profile`]: {
|
||||
_links: {},
|
||||
sdvx_id: 12345678,
|
||||
name: "SOMEONE",
|
||||
skill_level: -1,
|
||||
access_time: "2019-08-26T18:22:36Z",
|
||||
register_time: "2019-08-26T18:22:36Z",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const res = fn("sdvx", "Single", 1, {}, logger);
|
||||
|
||||
t.strictSame(res, {});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should gracefully handle negative API responses", async (t) => {
|
||||
const fn = await CreateKaiSDVXClassHandler("FLO", "token", MockBasicFetch({ status: 500 }));
|
||||
|
||||
const res = fn("sdvx", "Single", 1, {}, logger);
|
||||
|
||||
t.strictSame(res, {});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should ignore null dans", async (t) => {
|
||||
const fn = await CreateKaiSDVXClassHandler(
|
||||
"FLO",
|
||||
"token",
|
||||
MockJSONFetch({
|
||||
[`${KaiTypeToBaseURL("FLO")}/api/sdvx/v1/player_profile`]: {
|
||||
_links: {},
|
||||
sdvx_id: 12345678,
|
||||
name: "SOMEONE",
|
||||
skill_level: null,
|
||||
access_time: "2019-08-26T18:22:36Z",
|
||||
register_time: "2019-08-26T18:22:36Z",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const res = fn("sdvx", "Single", 1, {}, logger);
|
||||
|
||||
t.strictSame(res, {});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.teardown(CloseAllConnections);
|
||||
@@ -0,0 +1,65 @@
|
||||
import { SDVXDans } from "lib/constants/classes";
|
||||
import { ClassHandler } from "lib/score-import/framework/user-game-stats/types";
|
||||
import nodeFetch from "utils/fetch";
|
||||
import { KaiTypeToBaseURL } from "utils/misc";
|
||||
|
||||
export async function CreateKaiSDVXClassHandler(
|
||||
kaiType: "FLO" | "EAG" | "MIN",
|
||||
token: string,
|
||||
fetch = nodeFetch
|
||||
): Promise<ClassHandler> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let json: any;
|
||||
let err: unknown;
|
||||
const baseUrl = KaiTypeToBaseURL(kaiType);
|
||||
|
||||
// SP and DP dans are located in the same place,
|
||||
// fetch once, then return a function that traverses this data.
|
||||
try {
|
||||
const res = await fetch(`${baseUrl}/api/sdvx/v1/player_profile`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
json = await res.json();
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
|
||||
return (game, playtype, userID, ratings, logger) => {
|
||||
if (err) {
|
||||
logger.error(`An error occured while updating classes for ${baseUrl}.`, { err });
|
||||
return {};
|
||||
}
|
||||
|
||||
const sdvxDan: number | null = json.skill_level;
|
||||
|
||||
if (sdvxDan === null) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!Number.isInteger(sdvxDan)) {
|
||||
logger.warn(`${baseUrl} returned a dan of ${sdvxDan}, which was not a number.`);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (sdvxDan > SDVXDans.INF) {
|
||||
logger.warn(
|
||||
`${baseUrl} returned a dan of ${sdvxDan}, which was greater than INF (${SDVXDans.INF}.)`
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (sdvxDan < SDVXDans.DAN_1) {
|
||||
logger.warn(
|
||||
`${baseUrl} returned a dan of ${sdvxDan}, which was less than DAN_1 (${SDVXDans.DAN_1}.)`
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
dan: sdvxDan,
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -33,10 +33,9 @@ t.test("#ParseKaiSDVX", (t) => {
|
||||
},
|
||||
});
|
||||
|
||||
const res = ParseKaiSDVX("FLO", fakeAuth, logger, mockFloAPI);
|
||||
const res = await ParseKaiSDVX("FLO", fakeAuth, logger, mockFloAPI);
|
||||
|
||||
t.equal(res.game, "sdvx");
|
||||
t.equal(res.classHandler, null);
|
||||
t.strictSame(res.context, { service: "FLO" });
|
||||
|
||||
const iter = [];
|
||||
@@ -66,10 +65,9 @@ t.test("#ParseKaiSDVX", (t) => {
|
||||
},
|
||||
});
|
||||
|
||||
const res = ParseKaiSDVX("EAG", fakeAuth, logger, mockEagAPI);
|
||||
const res = await ParseKaiSDVX("EAG", fakeAuth, logger, mockEagAPI);
|
||||
|
||||
t.equal(res.game, "sdvx");
|
||||
t.equal(res.classHandler, null);
|
||||
t.strictSame(res.context, { service: "EAG" });
|
||||
|
||||
const iter = [];
|
||||
|
||||
@@ -5,14 +5,16 @@ import { KaiContext } from "../types";
|
||||
import { TraverseKaiAPI } from "../traverse-api";
|
||||
import { ParserFunctionReturns } from "../../types";
|
||||
import { ServerConfig } from "lib/setup/config";
|
||||
import { KaiTypeToBaseURL } from "utils/misc";
|
||||
import { CreateKaiSDVXClassHandler } from "./class-handler";
|
||||
|
||||
export function ParseKaiSDVX(
|
||||
service: "FLO" | "EAG",
|
||||
export async function ParseKaiSDVX(
|
||||
service: "FLO" | "EAG" | "MIN",
|
||||
authDoc: KaiAuthDocument,
|
||||
logger: KtLogger,
|
||||
fetch = nodeFetch
|
||||
): ParserFunctionReturns<unknown, KaiContext> {
|
||||
const baseUrl = service === "FLO" ? ServerConfig.FLO_API_URL : ServerConfig.EAG_API_URL;
|
||||
): Promise<ParserFunctionReturns<unknown, KaiContext>> {
|
||||
const baseUrl = KaiTypeToBaseURL(service);
|
||||
|
||||
return {
|
||||
iterable: TraverseKaiAPI(
|
||||
@@ -25,7 +27,7 @@ export function ParseKaiSDVX(
|
||||
context: {
|
||||
service,
|
||||
},
|
||||
classHandler: null,
|
||||
classHandler: await CreateKaiSDVXClassHandler(service, authDoc.token, fetch),
|
||||
game: "sdvx",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,5 +31,5 @@ export interface KaiSDVXScore {
|
||||
}
|
||||
|
||||
export interface KaiContext {
|
||||
service: "FLO" | "EAG";
|
||||
service: "FLO" | "EAG" | "MIN";
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface ImportTypeDataMap {
|
||||
"api/arc-ddr": unknown;
|
||||
"api/flo-iidx": unknown;
|
||||
"api/flo-sdvx": unknown;
|
||||
"api/min-sdvx": unknown;
|
||||
"api/eag-iidx": unknown;
|
||||
"api/eag-sdvx": unknown;
|
||||
}
|
||||
@@ -68,6 +69,7 @@ export interface ImportTypeContextMap {
|
||||
"api/arc-ddr": EmptyObject;
|
||||
"api/flo-iidx": KaiContext;
|
||||
"api/flo-sdvx": KaiContext;
|
||||
"api/min-sdvx": KaiContext;
|
||||
"api/eag-iidx": KaiContext;
|
||||
"api/eag-sdvx": KaiContext;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ export const Converters: ConverterMap = {
|
||||
"api/eag-sdvx": ConvertAPIKaiSDVX,
|
||||
"api/flo-iidx": ConvertAPIKaiIIDX,
|
||||
"api/flo-sdvx": ConvertAPIKaiSDVX,
|
||||
"api/min-sdvx": ConvertAPIKaiSDVX,
|
||||
"api/arc-iidx": ConvertAPIArcIIDX,
|
||||
"api/arc-ddr": ConvertAPIArcDDR,
|
||||
"api/arc-sdvx": ConvertAPIArcSDVX,
|
||||
|
||||
@@ -45,6 +45,7 @@ export interface TachiConfig {
|
||||
SESSION_SECRET: string;
|
||||
FLO_API_URL: string;
|
||||
EAG_API_URL: string;
|
||||
MIN_API_URL: string;
|
||||
ARC_API_URL: string;
|
||||
ARC_AUTH_TOKEN: string;
|
||||
CDN_FILE_ROOT: string;
|
||||
@@ -64,6 +65,7 @@ const err = p(config, {
|
||||
SESSION_SECRET: "string",
|
||||
FLO_API_URL: isValidURL,
|
||||
EAG_API_URL: isValidURL,
|
||||
MIN_API_URL: isValidURL,
|
||||
ARC_API_URL: isValidURL,
|
||||
ARC_AUTH_TOKEN: "string",
|
||||
CDN_FILE_ROOT: "string",
|
||||
|
||||
@@ -22,6 +22,10 @@ export function MockJSONFetch(urlDataMap: Record<string, unknown>) {
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected url ${url} - No Data Present?`);
|
||||
throw new Error(
|
||||
`Unexpected url ${url} - No Data Present? Valid urls are ${Object.keys(urlDataMap).join(
|
||||
", "
|
||||
)}`
|
||||
);
|
||||
}) as NodeFetch;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import crypto from "crypto";
|
||||
import { ServerTypeInfo } from "lib/setup/config";
|
||||
import { ServerConfig, ServerTypeInfo } from "lib/setup/config";
|
||||
import { Game, Playtypes, GetGameConfig, AnySongDocument, AnyChartDocument } from "tachi-common";
|
||||
|
||||
// https://github.com/sindresorhus/escape-string-regexp/blob/main/index.js
|
||||
@@ -76,3 +76,15 @@ export function IsString(val: unknown): val is string {
|
||||
export function DedupeArr<T>(arr: T[]): T[] {
|
||||
return [...new Set(arr)];
|
||||
}
|
||||
|
||||
export function KaiTypeToBaseURL(kaiType: "FLO" | "EAG" | "MIN") {
|
||||
if (kaiType === "FLO") {
|
||||
return ServerConfig.FLO_API_URL;
|
||||
} else if (kaiType === "EAG") {
|
||||
return ServerConfig.EAG_API_URL;
|
||||
} else if (kaiType === "MIN") {
|
||||
return ServerConfig.MIN_API_URL;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid Kai Type ${kaiType} provided.`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user