525 lines
13 KiB
TypeScript
525 lines
13 KiB
TypeScript
import { GetByCardIdAndVersion } from "../utils";
|
|
import { ChunithmUserCharacter, ChunithmUserItem } from "external/db/entity/chunithm/item";
|
|
import {
|
|
ChunithmUserActivity,
|
|
ChunithmUserData,
|
|
ChunithmUserDataEx,
|
|
ChunithmUserOption,
|
|
ChunithmUserOptionEx,
|
|
} from "external/db/entity/chunithm/profile";
|
|
import { ChunithmUserRecentRating } from "external/db/entity/chunithm/score";
|
|
import { ChunithmStaticCharge, ChunithmStaticEvent } from "external/db/entity/chunithm/static";
|
|
import CreateLogCtx from "lib/logger/logger";
|
|
import { Config } from "lib/setup/config";
|
|
import { DateTime } from "luxon";
|
|
import { BaseTitle } from "servers/titles/types/titles";
|
|
import { ParseStrInt } from "servers/titles/utils/string-checks";
|
|
import { LessThanOrEqual } from "typeorm";
|
|
import type { Request, Response } from "express";
|
|
|
|
const logger = CreateLogCtx(__filename);
|
|
|
|
export class Chunithm extends BaseTitle {
|
|
private readonly dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
|
|
|
|
constructor(gameCode?: string, version?: string, servletName?: string) {
|
|
super(gameCode ?? "SDBT", version ?? "100", servletName ?? "ChuniServlet");
|
|
}
|
|
|
|
protected createGetGameSettingsApiResponse(_req: Request, _res: Response) {
|
|
// HACK: This is not the way to go if you're somehow hosting an arcade server
|
|
// from my code, and wants your machines to actually restart sometimes.
|
|
const rebootStartTime = DateTime.now().minus({ hours: 4 }).toFormat(this.dateTimeFormat);
|
|
const rebootEndTime = DateTime.now().minus({ hours: 3 }).toFormat(this.dateTimeFormat);
|
|
|
|
return {
|
|
gameSetting: {
|
|
dataVersion: "1.00.00",
|
|
isMaintenance: "false",
|
|
requestInterval: "10",
|
|
rebootStartTime,
|
|
rebootEndTime,
|
|
isBackgroundDistribute: "false",
|
|
maxCountCharacter: "300",
|
|
maxCountItem: "300",
|
|
maxCountMusic: "300",
|
|
},
|
|
isDumpUpload: "false",
|
|
isAou: "false",
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @note override {@link createGetGameSettingsApiResponse} in order to modify this
|
|
* method's response
|
|
* @since CHUNITHM
|
|
*/
|
|
handle_GetGameSettingsApi(req: Request, res: Response) {
|
|
const resp = this.createGetGameSettingsApiResponse(req, res);
|
|
|
|
return res.status(200).send(resp);
|
|
}
|
|
|
|
handle_UpsertClientSettingApi(req: Request, res: Response) {
|
|
return res.status(200).send({ returnCode: "1" });
|
|
}
|
|
|
|
handle_UpsertClientTestmodeApi(req: Request, res: Response) {
|
|
return res.status(200).send({ returnCode: "1" });
|
|
}
|
|
|
|
handle_GetGameIdlistApi(req: Request, res: Response) {
|
|
return res.status(200).send({ type: req.safeBody.type, length: "0", gameIdlistList: [] });
|
|
}
|
|
|
|
async handle_GetGameEventApi(req: Request, res: Response) {
|
|
const events = await ChunithmStaticEvent.find({
|
|
where: {
|
|
enabled: true,
|
|
version: this.numericVersion,
|
|
},
|
|
});
|
|
|
|
if (events.length === 0) {
|
|
logger.warn("No events are enabled.", {
|
|
gameCode: this.gameCode,
|
|
version: this.version,
|
|
});
|
|
}
|
|
|
|
// HACK: We may want to rotate events...
|
|
return res.send({
|
|
type: req.safeBody.type,
|
|
length: events.length,
|
|
gameEventList: events.map((e) => ({
|
|
id: e.eventId,
|
|
type: e.type,
|
|
startDate: DateTime.fromJSDate(e.startDate).toFormat(this.dateTimeFormat),
|
|
endDate: "2099-12-31 00:00:00",
|
|
})),
|
|
});
|
|
}
|
|
|
|
handle_GetGameRankingApi(req: Request, res: Response) {
|
|
// TODO: Get most played songs from playlog
|
|
return res.send({
|
|
type: req.safeBody.type,
|
|
length: "0",
|
|
gameRankingList: [],
|
|
});
|
|
}
|
|
|
|
async handle_GetGameChargeApi(req: Request, res: Response) {
|
|
const charges = await ChunithmStaticCharge.find({
|
|
where: {
|
|
enabled: true,
|
|
version: this.numericVersion,
|
|
},
|
|
});
|
|
|
|
if (charges.length === 0) {
|
|
logger.warn("No charges (tickets) are enabled.", {
|
|
gameCode: this.gameCode,
|
|
version: this.version,
|
|
});
|
|
}
|
|
|
|
// HACK
|
|
return res.send({
|
|
length: charges.length,
|
|
gameChargeList: charges.map((c, i) => ({
|
|
orderId: i,
|
|
chargeId: c.chargeId,
|
|
price: "1",
|
|
startDate: "2017-12-05 07:00:00.0",
|
|
endDate: "2099-12-31 00:00:00.0",
|
|
salePrice: "1",
|
|
saleStartDate: "2017-12-05 07:00:00.0",
|
|
saleEndDate: "2099-12-31 00:00:00.0",
|
|
})),
|
|
});
|
|
}
|
|
|
|
async handle_GetUserPreviewApi(req: Request, res: Response) {
|
|
const userId = ParseStrInt(req.safeBody.userId);
|
|
|
|
if (!userId) {
|
|
logger.error("Invalid request: received userId is not a number.", {
|
|
userId: req.safeBody.userId,
|
|
});
|
|
|
|
return res.send({ returnCode: "1" });
|
|
}
|
|
|
|
const profile = await GetByCardIdAndVersion(ChunithmUserData, userId, this.numericVersion);
|
|
|
|
if (!profile) {
|
|
logger.error(`No users found.`, {
|
|
userId,
|
|
version: this.numericVersion,
|
|
});
|
|
return res.send({ returnCode: "1" });
|
|
}
|
|
|
|
const options = await GetByCardIdAndVersion(
|
|
ChunithmUserOption,
|
|
userId,
|
|
this.numericVersion
|
|
);
|
|
|
|
const character = await ChunithmUserCharacter.findOne({
|
|
where: {
|
|
characterId: profile.characterId,
|
|
card: {
|
|
id: profile.id,
|
|
},
|
|
},
|
|
});
|
|
|
|
const lastPlayDate = DateTime.fromJSDate(profile.lastPlayDate ?? new Date(0)).toFormat(
|
|
this.dateTimeFormat
|
|
);
|
|
|
|
return res.send({
|
|
userId,
|
|
|
|
// TODO: This should probably be false if the user is having a session somewhere else.
|
|
isLogin: false,
|
|
|
|
lastLoginDate: lastPlayDate,
|
|
userName: profile.userName,
|
|
reincarnationNum: profile.reincarnationNum,
|
|
level: profile.level,
|
|
exp: profile.exp,
|
|
playerRating: profile.playerRating,
|
|
lastGameId: profile.lastGameId,
|
|
lastRomVersion: profile.lastRomVersion,
|
|
lastDataVersion: profile.lastDataVersion,
|
|
lastPlayDate,
|
|
trophyId: profile.trophyId,
|
|
nameplateId: profile.nameplateId,
|
|
playerLevel: profile.level,
|
|
rating: profile.playerRating,
|
|
headphone: options?.headphone ?? 0,
|
|
chargeState: 1,
|
|
userNameEx: profile.userNameEx,
|
|
userCharacter: character ? { ...character, id: undefined, profile: undefined } : {},
|
|
});
|
|
}
|
|
|
|
handle_GameLoginApi(req: Request, res: Response) {
|
|
if (!Config.CHUNITHM_CONFIG.MODS.USE_LOGIN_BONUS) {
|
|
return res.send({ returnCode: "1" });
|
|
}
|
|
|
|
// TODO: Add login bonuses
|
|
return res.send({ returnCode: "1" });
|
|
}
|
|
|
|
async handle_GetUserDataApi(req: Request, res: Response) {
|
|
const userId = ParseStrInt(req.safeBody.userId);
|
|
|
|
if (!userId) {
|
|
logger.error("Invalid request: received userId is not a number.", {
|
|
userId: req.safeBody.userId,
|
|
});
|
|
|
|
return res.send({ returnCode: "1" });
|
|
}
|
|
|
|
const profile = await GetByCardIdAndVersion(ChunithmUserData, userId, this.numericVersion);
|
|
|
|
if (!profile) {
|
|
logger.error(`No users found.`, {
|
|
userId,
|
|
version: this.numericVersion,
|
|
});
|
|
return res.send({ returnCode: "1" });
|
|
}
|
|
|
|
return res.send({
|
|
userId,
|
|
userData: { ...profile, id: undefined, card: undefined, version: undefined },
|
|
});
|
|
}
|
|
|
|
async handle_GetUserDataExApi(req: Request, res: Response) {
|
|
const userId = ParseStrInt(req.safeBody.userId);
|
|
|
|
if (!userId) {
|
|
logger.error("Invalid request: received userId is not a number.", {
|
|
userId: req.safeBody.userId,
|
|
});
|
|
|
|
return res.send({ returnCode: "1" });
|
|
}
|
|
|
|
const profile = await GetByCardIdAndVersion(
|
|
ChunithmUserDataEx,
|
|
userId,
|
|
this.numericVersion
|
|
);
|
|
|
|
if (!profile) {
|
|
logger.error(`No users found.`, {
|
|
userId,
|
|
version: this.numericVersion,
|
|
});
|
|
return res.send({ returnCode: "1" });
|
|
}
|
|
|
|
return res.send({
|
|
userId,
|
|
userDataEx: { ...profile, id: undefined, card: undefined, version: undefined },
|
|
});
|
|
}
|
|
|
|
async handle_GetUserOptionApi(req: Request, res: Response) {
|
|
const userId = ParseStrInt(req.safeBody.userId);
|
|
|
|
if (!userId) {
|
|
logger.error("Invalid request: received userId is not a number.", {
|
|
userId: req.safeBody.userId,
|
|
});
|
|
|
|
return res.send({ returnCode: "1" });
|
|
}
|
|
|
|
const options = await GetByCardIdAndVersion(
|
|
ChunithmUserOption,
|
|
userId,
|
|
this.numericVersion
|
|
);
|
|
|
|
if (!options) {
|
|
logger.error(`No options found for requested user.`, {
|
|
userId,
|
|
version: this.numericVersion,
|
|
});
|
|
return res.send({ returnCode: "1" });
|
|
}
|
|
|
|
return res.send({
|
|
userId,
|
|
userGameOption: { ...options, id: undefined, card: undefined, version: undefined },
|
|
});
|
|
}
|
|
|
|
async handle_GetUserOptionExApi(req: Request, res: Response) {
|
|
const userId = ParseStrInt(req.safeBody.userId);
|
|
|
|
if (!userId) {
|
|
logger.error("Invalid request: received userId is not a number.", {
|
|
userId: req.safeBody.userId,
|
|
});
|
|
|
|
return res.send({ returnCode: "1" });
|
|
}
|
|
|
|
const options = await GetByCardIdAndVersion(
|
|
ChunithmUserOptionEx,
|
|
userId,
|
|
this.numericVersion
|
|
);
|
|
|
|
if (!options) {
|
|
logger.error(`No options found for requested user.`, {
|
|
userId,
|
|
version: this.numericVersion,
|
|
});
|
|
return res.send({ returnCode: "1" });
|
|
}
|
|
|
|
return res.send({
|
|
userId,
|
|
userGameOption: { ...options, id: undefined, card: undefined, version: undefined },
|
|
});
|
|
}
|
|
|
|
async handle_GetUserCharacterApi(req: Request, res: Response) {
|
|
const userId = ParseStrInt(req.safeBody.userId);
|
|
const nextIndex = ParseStrInt(req.safeBody.nextIndex);
|
|
const maxCount = ParseStrInt(req.safeBody.maxCount);
|
|
|
|
if (!userId || !nextIndex || !maxCount) {
|
|
logger.error("Invalid request: received userId/nextIndex/maxCount is not a number.", {
|
|
userId: req.safeBody.userId,
|
|
nextIndex: req.safeBody.nextIndex,
|
|
maxCount: req.safeBody.maxCount,
|
|
});
|
|
|
|
return res.send({ returnCode: "1" });
|
|
}
|
|
|
|
const [characters, characterCount] = await ChunithmUserCharacter.findAndCount({
|
|
where: {
|
|
card: {
|
|
id: userId,
|
|
},
|
|
},
|
|
take: maxCount,
|
|
skip: nextIndex,
|
|
});
|
|
|
|
const nextNextIndex = nextIndex + maxCount;
|
|
|
|
return res.send({
|
|
userId,
|
|
length: characters.length,
|
|
nextIndex: nextNextIndex < characterCount ? nextNextIndex : -1,
|
|
userCharacterList: characters.map((c) => ({ ...c, id: undefined, card: undefined })),
|
|
});
|
|
}
|
|
|
|
async handle_GetUserActivityApi(req: Request, res: Response) {
|
|
const userId = ParseStrInt(req.safeBody.userId);
|
|
const kind = ParseStrInt(req.safeBody.kind);
|
|
|
|
if (!userId || !kind) {
|
|
logger.error("Invalid request: received userId/kind is not a number.", {
|
|
userId: req.safeBody.userId,
|
|
kind: req.safeBody.kind,
|
|
});
|
|
|
|
return res.send({ returnCode: "1" });
|
|
}
|
|
|
|
const activities = await ChunithmUserActivity.find({
|
|
where: {
|
|
card: {
|
|
id: userId,
|
|
},
|
|
kind,
|
|
},
|
|
});
|
|
|
|
return res.send({
|
|
userId,
|
|
length: activities.length,
|
|
kind,
|
|
userActivityList: activities.map((a) => ({
|
|
...a,
|
|
id: a.activityId,
|
|
activityId: undefined,
|
|
})),
|
|
});
|
|
}
|
|
|
|
async handle_GetUserItemApi(req: Request, res: Response) {
|
|
const userId = ParseStrInt(req.safeBody.userId);
|
|
const nextIndex = ParseStrInt(req.safeBody.nextIndex);
|
|
const maxCount = ParseStrInt(req.safeBody.maxCount);
|
|
|
|
if (!userId || !nextIndex || !maxCount) {
|
|
logger.error("Invalid request: received userId/nextIndex/maxCount is not a number.", {
|
|
userId: req.safeBody.userId,
|
|
nextIndex: req.safeBody.nextIndex,
|
|
maxCount: req.safeBody.maxCount,
|
|
});
|
|
|
|
return res.send({ returnCode: "1" });
|
|
}
|
|
|
|
const itemKind = Math.trunc(nextIndex / 1e10);
|
|
const skip = nextIndex % 1e10;
|
|
|
|
const [items, itemsCount] = await ChunithmUserItem.findAndCount({
|
|
where: {
|
|
card: {
|
|
id: userId,
|
|
},
|
|
itemKind,
|
|
},
|
|
take: maxCount,
|
|
skip,
|
|
});
|
|
|
|
const nextIndexWithinKind = skip + maxCount;
|
|
|
|
return res.send({
|
|
userId,
|
|
nextIndex: nextIndexWithinKind < itemsCount ? itemKind * 1e10 + nextIndexWithinKind : 0,
|
|
itemKind,
|
|
length: items.length,
|
|
userItemList: items.map((i) => ({ ...i, id: undefined, card: undefined })),
|
|
});
|
|
}
|
|
|
|
async handle_GetUserRecentRatingApi(req: Request, res: Response) {
|
|
const userId = ParseStrInt(req.safeBody.userId);
|
|
|
|
if (!userId) {
|
|
logger.error("Invalid request: received userId is not a number.", {
|
|
userId: req.safeBody.userId,
|
|
});
|
|
|
|
return res.send({ returnCode: "1" });
|
|
}
|
|
|
|
const recentRatings = await ChunithmUserRecentRating.find({
|
|
where: {
|
|
card: {
|
|
id: userId,
|
|
},
|
|
version: LessThanOrEqual(this.numericVersion),
|
|
},
|
|
order: {
|
|
order: "asc",
|
|
},
|
|
});
|
|
|
|
const maxVersion = Math.max(...recentRatings.map((r) => r.version));
|
|
const userRecentRatingList = recentRatings
|
|
.filter((r) => r.version === maxVersion)
|
|
.map((r) => ({
|
|
...r,
|
|
id: undefined,
|
|
card: undefined,
|
|
version: undefined,
|
|
order: undefined,
|
|
}));
|
|
|
|
return res.send({
|
|
userId,
|
|
length: userRecentRatingList.length,
|
|
userRecentRatingList,
|
|
});
|
|
}
|
|
|
|
handle_GetUserMusicApi(req: Request, res: Response) {
|
|
throw new Error("Unimplemented");
|
|
}
|
|
|
|
handle_GetUserRegionApi(req: Request, res: Response) {
|
|
// TODO: What the hell is a region anyways?
|
|
return res.send({
|
|
userId: req.safeBody.userId,
|
|
length: 0,
|
|
userRegionList: [],
|
|
});
|
|
}
|
|
|
|
handle_GetUserLoginBonusApi(req: Request, res: Response) {
|
|
throw new Error("Unimplemented");
|
|
}
|
|
|
|
handle_GetUserMapAreaApi(req: Request, res: Response) {
|
|
throw new Error("Unimplemented");
|
|
}
|
|
|
|
handle_GetUserSymbolChatSettingApi(req: Request, res: Response) {
|
|
throw new Error("Unimplemented");
|
|
}
|
|
|
|
/**
|
|
* @since NEW
|
|
*/
|
|
handle_GetUserNetBattleDataApi(req: Request, res: Response) {
|
|
throw new Error("Unimplemented");
|
|
}
|
|
|
|
handle_GameLogoutApi(req: Request, res: Response) {
|
|
return res.status(200).send({ returnCode: "1" });
|
|
}
|
|
}
|