chunithm: Initial implementation code drop

No cleaner development history than this exists. It's difficult to
methodically design something when you don't yet know what it is
you're designing.
This commit is contained in:
Tau
2019-11-06 17:29:07 -05:00
parent beb9144e9b
commit d1d63e1ef9
169 changed files with 3903 additions and 242 deletions
+13
View File
@@ -0,0 +1,13 @@
// Make the names explicit so that we don't get the order mixed up
interface Params {
maxCount: number;
nextIndex: number;
}
export function paginationCookie<T>(items: T[], params: Params) {
if (items.length < params.maxCount) {
return "-1";
} else {
return (params.nextIndex + params.maxCount).toString();
}
}
+10
View File
@@ -0,0 +1,10 @@
import { Repositories } from "../repo";
import { GameLoginRequest } from "../request/gameLogin";
import { GameLoginResponse } from "../response/gameLogin";
export default async function gameLogin(
rep: Repositories,
req: GameLoginRequest
): Promise<GameLoginResponse> {
return { returnCode: "1" };
}
+10
View File
@@ -0,0 +1,10 @@
import { Repositories } from "../repo";
import { GameLogoutRequest } from "../request/gameLogout";
import { GameLogoutResponse } from "../response/gameLogout";
export default async function gameLogout(
rep: Repositories,
req: GameLogoutRequest
): Promise<GameLogoutResponse> {
return { returnCode: "1" };
}
+13
View File
@@ -0,0 +1,13 @@
import { Repositories } from "../repo";
import { GetGameChargeRequest } from "../request/getGameCharge";
import { GetGameChargeResponse } from "../response/getGameCharge";
export default async function getGameCharge(
rep: Repositories,
req: GetGameChargeRequest
): Promise<GetGameChargeResponse> {
return {
length: "0",
gameChargeList: [],
};
}
+14
View File
@@ -0,0 +1,14 @@
import { Repositories } from "../repo";
import { GetGameEventRequest } from "../request/getGameEvent";
import { GetGameEventResponse } from "../response/getGameEvent";
export default async function getGameEvent(
rep: Repositories,
req: GetGameEventRequest
): Promise<GetGameEventResponse> {
return {
type: req.type,
length: "0",
gameEventList: [],
};
}
+14
View File
@@ -0,0 +1,14 @@
import { Repositories } from "../repo";
import { GetGameIdlistRequest } from "../request/getGameIdlist";
import { GetGameIdlistResponse } from "../response/getGameIdlist";
export default async function getGameIdlist(
rep: Repositories,
req: GetGameIdlistRequest
): Promise<GetGameIdlistResponse> {
return {
type: req.type,
length: "0",
gameIdlistList: [],
};
}
+14
View File
@@ -0,0 +1,14 @@
import { Repositories } from "../repo";
import { GetGameMessageRequest } from "../request/getGameMessage";
import { GetGameMessageResponse } from "../response/getGameMessage";
export default async function getGameMessage(
rep: Repositories,
req: GetGameMessageRequest
): Promise<GetGameMessageResponse> {
return {
type: req.type,
length: "0",
gameMessageList: [],
};
}
+13
View File
@@ -0,0 +1,13 @@
import { Repositories } from "../repo";
import { GetGameRankingRequest } from "../request/getGameRanking";
import { GetGameRankingResponse } from "../response/getGameRanking";
export default async function getGameRanking(
rep: Repositories,
req: GetGameRankingRequest
): Promise<GetGameRankingResponse> {
return {
type: req.type,
gameRankingList: [],
};
}
+14
View File
@@ -0,0 +1,14 @@
import { Repositories } from "../repo";
import { GetGameSaleRequest } from "../request/getGameSale";
import { GetGameSaleResponse } from "../response/getGameSale";
export default async function getGameSale(
rep: Repositories,
req: GetGameSaleRequest
): Promise<GetGameSaleResponse> {
return {
type: req.type,
length: "0",
gameSaleList: [],
};
}
+24
View File
@@ -0,0 +1,24 @@
import { Repositories } from "../repo";
import { GetGameSettingRequest } from "../request/getGameSetting";
import { GetGameSettingResponse } from "../response/getGameSetting";
export default async function getGameSetting(
rep: Repositories,
req: GetGameSettingRequest
): Promise<GetGameSettingResponse> {
return {
gameSetting: {
dataVersion: "1",
isMaintenance: "false",
requestInterval: "10",
rebootStartTime: "0",
rebootEndTime: "0",
isBackgroundDistribute: "false",
maxCountCharacter: "999",
maxCountItem: "999",
maxCountMusic: "999",
},
isDumpUpload: "false",
isAou: "false",
};
}
+23
View File
@@ -0,0 +1,23 @@
import { readAimeId } from "../proto/base";
import { writeUserActivity } from "../proto/userActivity";
import { Repositories } from "../repo";
import { GetUserActivityRequest } from "../request/getUserActivity";
import { GetUserActivityResponse } from "../response/getUserActivity";
export default async function getUserActivity(
rep: Repositories,
req: GetUserActivityRequest
): Promise<GetUserActivityResponse> {
const aimeId = readAimeId(req.userId);
const kind = parseInt(req.kind);
const profileId = await rep.userData().lookup(aimeId);
const items = await rep.userActivity().load(profileId, kind);
return {
userId: req.userId,
length: items.length.toString(),
kind: kind.toString(),
userActivityList: items.map(writeUserActivity),
};
}
+27
View File
@@ -0,0 +1,27 @@
import { paginationCookie } from "./_util";
import { readAimeId } from "../proto/base";
import { writeUserCharacter } from "../proto/userCharacter";
import { Repositories } from "../repo";
import { GetUserCharacterRequest } from "../request/getUserCharacter";
import { GetUserCharacterResponse } from "../response/getUserCharacter";
export default async function getUserCharacter(
rep: Repositories,
req: GetUserCharacterRequest
): Promise<GetUserCharacterResponse> {
const aimeId = readAimeId(req.userId);
const maxCount = parseInt(req.maxCount);
const nextIndex = parseInt(req.nextIndex);
const profileId = await rep.userData().lookup(aimeId);
const items = await rep
.userCharacter()
.load(profileId, { limit: maxCount, offset: nextIndex });
return {
userId: req.userId,
length: items.length.toString(),
nextIndex: paginationCookie(items, { maxCount, nextIndex }),
userCharacterList: items.map(writeUserCharacter),
};
}
+14
View File
@@ -0,0 +1,14 @@
import { Repositories } from "../repo";
import { GetUserChargeRequest } from "../request/getUserCharge";
import { GetUserChargeResponse } from "../response/getUserCharge";
export default async function getUserCharge(
rep: Repositories,
req: GetUserChargeRequest
): Promise<GetUserChargeResponse> {
return {
userId: req.userId,
length: "0",
userChargeList: [],
};
}
+17
View File
@@ -0,0 +1,17 @@
import { Repositories } from "../repo";
import { GetUserCourseRequest } from "../request/getUserCourse";
import { GetUserCourseResponse } from "../response/getUserCourse";
export default async function getUserCourse(
rep: Repositories,
req: GetUserCourseRequest
): Promise<GetUserCourseResponse> {
return {
userId: req.userId,
length: "0",
nextIndex: "-1",
userCourseList: [
// This will get saved at some point I think
],
};
}
+20
View File
@@ -0,0 +1,20 @@
import { readAimeId } from "../proto/base";
import { writeUserData } from "../proto/userData";
import { Repositories } from "../repo";
import { GetUserDataRequest } from "../request/getUserData";
import { GetUserDataResponse } from "../response/getUserData";
export default async function getUserData(
rep: Repositories,
req: GetUserDataRequest
): Promise<GetUserDataResponse> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().lookup(aimeId);
const userData = await rep.userData().load(profileId);
return {
userId: req.userId,
userData: writeUserData(userData),
};
}
+20
View File
@@ -0,0 +1,20 @@
import { readAimeId } from "../proto/base";
import { writeUserDataEx } from "../proto/userDataEx";
import { Repositories } from "../repo";
import { GetUserDataExRequest } from "../request/getUserDataEx";
import { GetUserDataExResponse } from "../response/getUserDataEx";
export default async function getUserDataEx(
rep: Repositories,
req: GetUserDataExRequest
): Promise<GetUserDataExResponse> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().lookup(aimeId);
const userDataEx = await rep.userDataEx().load(profileId);
return {
userId: req.userId,
userDataEx: writeUserDataEx(userDataEx),
};
}
+14
View File
@@ -0,0 +1,14 @@
import { Repositories } from "../repo";
import { GetUserDuelRequest } from "../request/getUserDuel";
import { GetUserDuelResponse } from "../response/getUserDuel";
export default async function getUserDuel(
rep: Repositories,
req: GetUserDuelRequest
): Promise<GetUserDuelResponse> {
return {
userId: req.userId,
length: "0",
userDuelList: [],
};
}
+62
View File
@@ -0,0 +1,62 @@
import { readAimeId } from "../proto/base";
import { writeUserItem } from "../proto/userItem";
import { Repositories } from "../repo";
import { GetUserItemRequest } from "../request/getUserItem";
import { GetUserItemResponse } from "../response/getUserItem";
import { UserItemItem } from "../model/userItem";
// I'm not sure why they went to all this trouble just to avoid passing an
// explicit `itemKind` parameter in the request...
const itemKindMul = 10000000000n;
export default async function getUserItem(
rep: Repositories,
req: GetUserItemRequest
): Promise<GetUserItemResponse> {
// Split the "nextIndex" parameter
const compoundIn = BigInt(req.nextIndex);
const itemKindN = compoundIn / itemKindMul;
const nextIndexN = compoundIn % itemKindMul;
// Now unpack the entire request
const aimeId = readAimeId(req.userId);
const maxCount = parseInt(req.maxCount);
const itemKind = parseInt(itemKindN as any);
const nextIndex = parseInt(nextIndexN as any);
// Hit DB
// This gets called for not-yet-created profiles for some reason (probably
// so that the server can force some bonus items into a new profile's
// inventory)? So we need to gracefully handle that condition.
// (maybe some sort of anchor row is created on first GameLogin...)
const profileId = await rep.userData().tryLookup(aimeId);
let items: UserItemItem[];
if (profileId !== undefined) {
items = await rep
.userItem()
.load(profileId, itemKind, { limit: maxCount, offset: nextIndex });
} else {
items = [];
}
// Pack the next pagination cookie into bigint compound form
const compoundOut =
itemKindN * itemKindMul + BigInt(maxCount + items.length);
// Done:
return {
userId: req.userId,
length: items.length.toString(),
nextIndex: items.length < maxCount ? "-1" : compoundOut.toString(),
itemKind: itemKind.toString(),
userItemList: items.map(writeUserItem),
};
}
+21
View File
@@ -0,0 +1,21 @@
import { readAimeId } from "../proto/base";
import { writeUserMap } from "../proto/userMap";
import { Repositories } from "../repo";
import { GetUserMapRequest } from "../request/getUserMap";
import { GetUserMapResponse } from "../response/getUserMap";
export default async function getUserMap(
rep: Repositories,
req: GetUserMapRequest
): Promise<GetUserMapResponse> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().lookup(aimeId);
const items = await rep.userMap().load(profileId);
return {
userId: req.userId,
length: items.length.toString(),
userMapList: items.map(writeUserMap),
};
}
+50
View File
@@ -0,0 +1,50 @@
import { writeUserMusicDetail, UserMusicJson } from "../proto/userMusic";
import { Repositories } from "../repo";
import { GetUserMusicRequest } from "../request/getUserMusic";
import { GetUserMusicResponse } from "../response/getUserMusic";
import { readAimeId } from "../proto/base";
import { UserMusicDetailItem } from "../model/userMusic";
import { paginationCookie } from "./_util";
export default async function getUserMusic(
rep: Repositories,
req: GetUserMusicRequest
): Promise<GetUserMusicResponse> {
const aimeId = readAimeId(req.userId);
const maxCount = parseInt(req.maxCount);
const nextIndex = parseInt(req.nextIndex);
const profileId = await rep.userData().lookup(aimeId);
const flat = await rep
.userMusic()
.load(profileId, { limit: maxCount, offset: nextIndex });
const groupIdx = new Map<number, UserMusicDetailItem[]>();
for (const item of flat) {
const { musicId } = item;
const existing = groupIdx.get(musicId);
if (existing !== undefined) {
existing.push(item);
} else {
groupIdx.set(musicId, [item]);
}
}
const userMusicList = new Array<UserMusicJson>();
for (const group of groupIdx.values()) {
userMusicList.push({
length: group.length.toString(),
userMusicDetailList: group.map(writeUserMusicDetail),
});
}
return {
userId: req.userId,
length: userMusicList.length.toString(),
nextIndex: paginationCookie(flat, { maxCount, nextIndex }),
userMusicList,
};
}
+20
View File
@@ -0,0 +1,20 @@
import { readAimeId } from "../proto/base";
import { writeUserGameOption } from "../proto/userGameOption";
import { Repositories } from "../repo";
import { GetUserOptionRequest } from "../request/getUserOption";
import { GetUserOptionResponse } from "../response/getUserOption";
export default async function getUserOption(
rep: Repositories,
req: GetUserOptionRequest
): Promise<GetUserOptionResponse> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().lookup(aimeId);
const userGameOption = await rep.userGameOption().load(profileId);
return {
userId: req.userId,
userGameOption: writeUserGameOption(userGameOption),
};
}
+20
View File
@@ -0,0 +1,20 @@
import { readAimeId } from "../proto/base";
import { writeUserGameOptionEx } from "../proto/userGameOptionEx";
import { Repositories } from "../repo";
import { GetUserOptionExRequest } from "../request/getUserOptionEx";
import { GetUserOptionExResponse } from "../response/getUserOptionEx";
export default async function getUserOptionEx(
rep: Repositories,
req: GetUserOptionExRequest
): Promise<GetUserOptionExResponse> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().lookup(aimeId);
const userGameOptionEx = await rep.userGameOptionEx().load(profileId);
return {
userId: req.userId,
userGameOptionEx: writeUserGameOptionEx(userGameOptionEx),
};
}
+58
View File
@@ -0,0 +1,58 @@
import { readAimeId, writeDate } from "../proto/base";
import { writeUserCharacter } from "../proto/userCharacter";
import { Repositories } from "../repo";
import { GetUserPreviewRequest } from "../request/getUserPreview";
import { GetUserPreviewResponse } from "../response/getUserPreview";
export default async function getUserPreview(
rep: Repositories,
req: GetUserPreviewRequest
): Promise<GetUserPreviewResponse | null> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().tryLookup(aimeId);
// Return an empty JSON object if the player lacks a profile; this will
// start the new profile registration flow in the client.
if (profileId === undefined) {
return null;
}
const userData = await rep.userData().load(profileId);
const userCharacter = await rep
.userCharacter()
.loadOne(profileId, userData.characterId);
const userGameOption = await rep.userGameOption().load(profileId);
return {
userId: "1",
// Current login (i.e. profile lock) status
isLogin: "false",
lastLoginDate: "1970-01-01 09:00:00",
// UserData:
userName: userData.userName,
reincarnationNum: userData.reincarnationNum.toString(),
level: userData.level.toString(),
exp: userData.exp.toString(),
playerRating: userData.playerRating.toString(),
lastGameId: userData.lastGameId,
lastRomVersion: userData.lastRomVersion,
lastDataVersion: userData.lastDataVersion,
lastPlayDate: writeDate(userData.lastPlayDate),
trophyId: userData.trophyId.toString(),
// Currently selected UserCharacter:
userCharacter: writeUserCharacter(userCharacter),
// UserGameOption:
playerLevel: userGameOption.playerLevel.toString(),
rating: userGameOption.rating.toString(),
headphone: userGameOption.headphone.toString(),
};
}
@@ -0,0 +1,16 @@
import { Repositories } from "../repo";
import { GetUserRecentRatingRequest } from "../request/getUserRecentRating";
import { GetUserRecentRatingResponse } from "../response/getUserRecentRating";
export default async function getUserRecentRating(
rep: Repositories,
req: GetUserRecentRatingRequest
): Promise<GetUserRecentRatingResponse> {
// I don't really know what subset of data to return here.
return {
userId: req.userId,
length: "0",
userRecentRatingList: [],
};
}
+16
View File
@@ -0,0 +1,16 @@
import { Repositories } from "../repo";
import { GetUserRegionRequest } from "../request/getUserRegion";
import { GetUserRegionResponse } from "../response/getUserRegion";
export default async function getUserRegion(
rep: Repositories,
req: GetUserRegionRequest
): Promise<GetUserRegionResponse> {
return {
userId: req.userId,
length: "0",
userRegionList: [
// Doesn't get saved
],
};
}
+126
View File
@@ -0,0 +1,126 @@
import compression from "compression";
import express, { NextFunction, Request, Response } from "express";
import logger from "debug";
import gameLogin from "./gameLogin";
import gameLogout from "./gameLogout";
import getGameCharge from "./getGameCharge";
import getGameEvent from "./getGameEvent";
import getGameIdlist from "./getGameIdlist";
import getGameMessage from "./getGameMessage";
import getGameRanking from "./getGameRanking";
import getGameSale from "./getGameSale";
import getGameSetting from "./getGameSetting";
import getUserActivity from "./getUserActivity";
import getUserCharacter from "./getUserCharacter";
import getUserCharge from "./getUserCharge";
import getUserCourse from "./getUserCourse";
import getUserData from "./getUserData";
import getUserDataEx from "./getUserDataEx";
import getUserDuel from "./getUserDuel";
import getUserItem from "./getUserItem";
import getUserMap from "./getUserMap";
import getUserMusic from "./getUserMusic";
import getUserOption from "./getUserOption";
import getUserOptionEx from "./getUserOptionEx";
import getUserPreview from "./getUserPreview";
import getUserRecentRating from "./getUserRecentRating";
import getUserRegion from "./getUserRegion";
import upsertClientBookkeeping from "./upsertClientBookkeeping";
import upsertClientDevelop from "./upsertClientDevelop";
import upsertClientError from "./upsertClientError";
import upsertClientSetting from "./upsertClientSetting";
import upsertClientTestmode from "./upsertClientTestmode";
import upsertUserAll from "./upsertUserAll";
import createSqlWrapper from "../sql";
import { DataSource } from "../../sql";
const debug = logger("app:chuni:io");
// Thankfully we can use standard middleware for JSON I/O. We have to use a
// zlib middleware as well because compression is non-optional: the client will
// attempt to inflate whatever response we send to it whether there's a
// Transfer-Encoding header or not.
function quirks(req: Request, res: Response, next: NextFunction) {
// Chunithm client does not follow the HTTP spec: user agents must indicate
// that they are willing to accept a particular Transfer-Encoding for that
// T-E to be used (unless the T-E in question is "chunked" but w/e), and this
// indication is advisory anyway, the server is permitted to ignore it.
//
// Force this header into the request, since it is not present normally.
// ... unless, that is, the client is actually somebody debugging with curl
// and not the real game client, in which case this forced deflate can be
// annoying. Pass an "X-Debug" request header to suppress forced compression.
if (req.headers["x-debug"] === undefined) {
req.headers["accept-encoding"] = "deflate";
}
return next();
}
function trace(req: Request, res: Response, next: NextFunction) {
debug("\n--- Chunithm %s ---\n", req.url);
debug("Request: %j", req.body);
const prevJson = res.json;
res.json = function(obj) {
debug("Response: %j", obj);
res.json = prevJson;
res.json.apply(this, arguments);
return res;
};
return next();
}
export default function chunithm(db: DataSource) {
const wrapper = createSqlWrapper(db);
wrapper.rpc("/GameLoginApi", gameLogin);
wrapper.rpc("/GameLogoutApi", gameLogout);
wrapper.rpc("/GetGameChargeApi", getGameCharge);
wrapper.rpc("/GetGameEventApi", getGameEvent);
wrapper.rpc("/GetGameIdlistApi", getGameIdlist);
wrapper.rpc("/GetGameMessageApi", getGameMessage);
wrapper.rpc("/GetGameRankingApi", getGameRanking);
wrapper.rpc("/GetGameSaleApi", getGameSale);
wrapper.rpc("/GetGameSettingApi", getGameSetting);
wrapper.rpc("/GetUserActivityApi", getUserActivity);
wrapper.rpc("/GetUserCharacterApi", getUserCharacter);
wrapper.rpc("/GetUserChargeApi", getUserCharge);
wrapper.rpc("/GetUserCourseAPi", getUserCourse);
wrapper.rpc("/GetUserDataApi", getUserData);
wrapper.rpc("/GetUserDataExApi", getUserDataEx);
wrapper.rpc("/GetUserDuelApi", getUserDuel);
wrapper.rpc("/GetUserItemApi", getUserItem);
wrapper.rpc("/GetUserMapApi", getUserMap);
wrapper.rpc("/GetUserMusicApi", getUserMusic);
wrapper.rpc("/GetUserOptionApi", getUserOption);
wrapper.rpc("/GetUserOptionExApi", getUserOptionEx);
wrapper.rpc("/GetUserPreviewApi", getUserPreview);
wrapper.rpc("/GetUserRecentRatingApi", getUserRecentRating);
wrapper.rpc("/GetUserRegionApi", getUserRegion);
wrapper.rpc("/UpsertClientBookkeepingApi", upsertClientBookkeeping);
wrapper.rpc("/UpsertClientDevelopApi", upsertClientDevelop);
wrapper.rpc("/UpsertClientErrorApi", upsertClientError);
wrapper.rpc("/UpsertClientSettingApi", upsertClientSetting);
wrapper.rpc("/UpsertClientTestmodeApi", upsertClientTestmode);
wrapper.rpc("/UpsertUserAllApi", upsertUserAll);
const app = express();
app.use(quirks);
app.use(compression({ threshold: 0 }));
app.use(express.json());
app.use(trace);
app.use("/ChuniServlet", wrapper);
return app;
}
@@ -0,0 +1,12 @@
import { Repositories } from "../repo";
import { UpsertClientBookkeepingRequest } from "../request/upsertClientBookkeeping";
import { UpsertClientBookkeepingResponse } from "../response/upsertClientBookkeeping";
export default async function upsertClientBookkeeping(
rep: Repositories,
req: UpsertClientBookkeepingRequest
): Promise<UpsertClientBookkeepingResponse> {
return {
returnCode: "1",
};
}
@@ -0,0 +1,12 @@
import { Repositories } from "../repo";
import { UpsertClientDevelopRequest } from "../request/upsertClientDevelop";
import { UpsertClientDevelopResponse } from "../response/upsertClientDevelop";
export default async function upsertClientDevelop(
rep: Repositories,
req: UpsertClientDevelopRequest
): Promise<UpsertClientDevelopResponse> {
return {
returnCode: "1",
};
}
+12
View File
@@ -0,0 +1,12 @@
import { Repositories } from "../repo";
import { UpsertClientErrorRequest } from "../request/upsertClientError";
import { UpsertClientErrorResponse } from "../response/upsertClientError";
export default async function upsertClientError(
rep: Repositories,
req: UpsertClientErrorRequest
): Promise<UpsertClientErrorResponse> {
return {
returnCode: "1",
};
}
@@ -0,0 +1,12 @@
import { Repositories } from "../repo";
import { UpsertClientSettingRequest } from "../request/upsertClientSetting";
import { UpsertClientSettingResponse } from "../response/upsertClientSetting";
export default async function upsertClientSetting(
rep: Repositories,
req: UpsertClientSettingRequest
): Promise<UpsertClientSettingResponse> {
return {
returnCode: "1",
};
}
@@ -0,0 +1,12 @@
import { Repositories } from "../repo";
import { UpsertClientTestmodeRequest } from "../request/upsertClientTestmode";
import { UpsertClientTestmodeResponse } from "../response/upsertClientTestmode";
export default async function upsertClientTestmode(
rep: Repositories,
req: UpsertClientTestmodeRequest
): Promise<UpsertClientTestmodeResponse> {
return {
returnCode: "1",
};
}
+73
View File
@@ -0,0 +1,73 @@
import { Repositories } from "../repo";
import { readAimeId } from "../proto/base";
import { readUserCharacter } from "../proto/userCharacter";
import { readUserData } from "../proto/userData";
import { readUserGameOption } from "../proto/userGameOption";
import { UpsertUserAllRequest } from "../request/upsertUserAll";
import { UpsertUserAllResponse } from "../response/upsertUserAll";
import { readUserGameOptionEx } from "../proto/userGameOptionEx";
import { readUserMap } from "../proto/userMap";
import { readUserItem } from "../proto/userItem";
import { readUserMusicDetail } from "../proto/userMusic";
import { readUserActivity } from "../proto/userActivity";
import { readUserDataEx } from "../proto/userDataEx";
import { readUserPlaylog } from "../proto/userPlaylog";
// It shouldn't need to be said really, but seeing as this message (A) requires
// fairly lengthy processing and (B) is only sent right at the end of a credit,
// you should probably copy the JSON from the server's debug log and then
// validate this implementation using curl if you're planning to make any
// modifications to how things get saved.
export default async function upsertUserAll(
rep: Repositories,
req: UpsertUserAllRequest
): Promise<UpsertUserAllResponse> {
const aimeId = readAimeId(req.userId);
const payload = req.upsertUserAll;
const profile = readUserData(req.upsertUserAll.userData[0]);
const profileId = await rep.userData().save(aimeId, profile);
// Empty lists don't get sent at all. Not sure which are potentially empty
// so let's just assume the answer is "all of them". Hence the use of || []
// everywhere.
for (const item of payload.userGameOption || []) {
await rep.userGameOption().save(profileId, readUserGameOption(item));
}
for (const item of payload.userGameOptionEx || []) {
await rep.userGameOptionEx().save(profileId, readUserGameOptionEx(item));
}
for (const item of payload.userMapList || []) {
await rep.userMap().save(profileId, readUserMap(item));
}
for (const item of payload.userCharacterList || []) {
await rep.userCharacter().save(profileId, readUserCharacter(item));
}
for (const item of payload.userItemList || []) {
await rep.userItem().save(profileId, readUserItem(item));
}
for (const item of payload.userMusicDetailList || []) {
await rep.userMusic().save(profileId, readUserMusicDetail(item));
}
for (const item of payload.userActivityList || []) {
await rep.userActivity().save(profileId, readUserActivity(item));
}
for (const item of payload.userPlaylogList || []) {
await rep.userPlaylog().save(profileId, readUserPlaylog(item));
}
for (const item of payload.userDataEx || []) {
await rep.userDataEx().save(profileId, readUserDataEx(item));
}
return { returnCode: "1" };
}