server/laf-cloud/functions/wucaiMigration.ts

218 lines
6.8 KiB
TypeScript

export const WUCAI_TYPE_NONE = -1;
export const WUCAI_TYPE_AUTO_MIGRATED = 0;
export const WUCAI_TYPE_PENDING_CHOICE = 1;
export const WUCAI_TYPE_CHOSE_WUCAI = 2;
export const WUCAI_TYPE_CHOSE_CURRENT = 3;
export const WUCAI_REWARD = {
coinAmount: 2000,
freezeAmount: 3,
hammerAmount: 3,
magicAmount: 3,
};
export default async function main() {
return { code: 1, data: null, msg: "wucaiMigration ready" };
}
const DEFAULT_HEAD_ARR = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17];
const DEFAULT_CAT_ARR = [1, 2, 3];
export async function fetchWucaiUser(unionid: string): Promise<any | null> {
if (!unionid) {
return null;
}
try {
const response = await fetch("https://laf.nika4games.com/getUserInfoByUnionid", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ unionid }),
});
if (response.ok === false) {
return null;
}
const result = await response.json();
const user = result?.data;
if (!user || typeof user !== "object"
|| !Object.prototype.hasOwnProperty.call(user, "levelAmount")) {
return null;
}
return user;
} catch (error) {
console.error("Failed to read Wucai user", error);
return null;
}
}
export function normalizeWucaiUser(source: any): any {
return {
levelAmount: numberOr(source?.levelAmount, 0),
coinAmount: numberOr(source?.coinAmount, 0),
healthAmount: numberOr(source?.healthAmount, 5),
healthtimestamp: numberOr(source?.healthtimestamp, 0),
freezeAmount: numberOr(source?.freezeAmount, 3),
hammerAmount: numberOr(source?.hammerAmount, 3),
magicAmount: numberOr(source?.magicAmount, 3),
userPowerTime: numberOr(source?.userPowerTime, 0),
monthCardReward: numberOr(source?.monthCardReward, 0),
monthCardTime: numberOr(source?.monthCardTime, 0),
pay_user: booleanOrFalse(source?.pay_user),
starter_pack: numberOr(source?.starter_pack, 0),
starter_packState: numberOr(source?.starter_packState, 0),
winStreak: numberOr(source?.winStreak, 0),
passCheck: cloneSerializable(source?.passCheck ?? null),
endLevelNum: numberOr(source?.endLevelNum, 0),
totalLevel: numberOr(source?.totalLevel, 0),
userLevel: numberOr(source?.userLevel, 0),
film: numberOr(source?.film, 0),
headArr: numberArrayOr(source?.headArr, DEFAULT_HEAD_ARR),
catArr: numberArrayOr(source?.catArr, DEFAULT_CAT_ARR),
};
}
export function buildWucaiPreview(source: any, nowSeconds = Math.floor(Date.now() / 1000)): any {
const user = normalizeWucaiUser(source);
return {
levelAmount: user.levelAmount,
coinAmount: user.coinAmount,
healthAmount: user.healthAmount,
freezeAmount: user.freezeAmount,
hammerAmount: user.hammerAmount,
magicAmount: user.magicAmount,
film: user.film,
winStreak: user.winStreak,
headCount: user.headArr.length,
catCount: user.catArr.length,
monthCardActive: user.monthCardTime > nowSeconds,
passCheckActive: source?.passCheckV2 ? isV2PassActive(source.passCheckV2, nowSeconds * 1000) : isPassCheckActive(user.passCheck),
};
}
export function addWucaiReward(user: any): any {
return {
coinAmount: numberOr(user?.coinAmount, 0) + WUCAI_REWARD.coinAmount,
freezeAmount: numberOr(user?.freezeAmount, 0) + WUCAI_REWARD.freezeAmount,
hammerAmount: numberOr(user?.hammerAmount, 0) + WUCAI_REWARD.hammerAmount,
magicAmount: numberOr(user?.magicAmount, 0) + WUCAI_REWARD.magicAmount,
};
}
export function isPristineWucaiTarget(user: any, hasOrder: boolean): boolean {
if (!user || hasOrder || user.passCheckV2) {
return false;
}
const expectedNumbers: Record<string, number> = {
levelAmount: 0,
coinAmount: 0,
healthAmount: 5,
healthtimestamp: 0,
freezeAmount: 3,
hammerAmount: 3,
magicAmount: 3,
userPowerTime: 0,
monthCardReward: 0,
monthCardTime: 0,
rebornGiftCount: 0,
rebornGiftTime: 0,
starter_pack: 0,
starter_packState: 0,
winStreak: 0,
endLevelNum: 0,
totalLevel: 0,
userLevel: 0,
film: 0,
careerAmount: 0,
addLevel: 0,
};
for (const key of Object.keys(expectedNumbers)) {
if (numberOr(user[key], expectedNumbers[key]) !== expectedNumbers[key]) {
return false;
}
}
if (booleanOrFalse(user.pay_user)) {
return false;
}
if (user.passCheck != null && user.passCheck !== "null" && user.passCheck !== "") {
return false;
}
if (hasMeaningfulJungleState(user.jungleTreasureState)) {
return false;
}
return arraysEqual(numberArrayOr(user.headArr, DEFAULT_HEAD_ARR), DEFAULT_HEAD_ARR)
&& arraysEqual(numberArrayOr(user.catArr, DEFAULT_CAT_ARR), DEFAULT_CAT_ARR);
}
export function isValidWucaiType(value: any): boolean {
const type = Number(value);
return Number.isInteger(type) && type >= WUCAI_TYPE_NONE && type <= WUCAI_TYPE_CHOSE_CURRENT;
}
function numberOr(value: any, fallback: number): number {
if (value === null || value === undefined || value === "") {
return fallback;
}
const number = Number(value);
return Number.isFinite(number) ? number : fallback;
}
function booleanOrFalse(value: any): boolean {
return value === true || value === 1 || value === "1" || value === "true";
}
function numberArrayOr(value: any, fallback: number[]): number[] {
if (!Array.isArray(value)) {
return [...fallback];
}
return value.map(item => Number(item)).filter(item => Number.isFinite(item));
}
function cloneSerializable(value: any): any {
if (value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
return value;
}
return JSON.parse(JSON.stringify(value));
}
function isPassCheckActive(value: any): boolean {
let passCheck = value;
if (typeof passCheck === "string") {
try {
passCheck = JSON.parse(passCheck);
} catch {
return false;
}
}
if (Array.isArray(passCheck)) {
return passCheck.some(item => item?.activate === true || item?.activate === 1);
}
return passCheck?.activate === true || passCheck?.activate === 1;
}
function hasMeaningfulJungleState(value: any): boolean {
if (value == null || value === "" || value === "null") {
return false;
}
let state = value;
if (typeof state === "string") {
try {
state = JSON.parse(state);
} catch {
return true;
}
}
if (!state || !Array.isArray(state.tierStates)) {
return true;
}
return state.tierStates.some(item => Number(item) > 0);
}
function arraysEqual(left: number[], right: number[]): boolean {
return left.length === right.length && left.every((item, index) => item === right[index]);
}
function isV2PassActive(raw: any, now: number): boolean {
try { const s = typeof raw === 'string' ? JSON.parse(raw) : raw; return s.seasons.some((p: any) => p.tier >= 2 && p.claimUntil > now); } catch { return false; }
}