WorldStateSync/index.ts
2025-07-01 00:32:48 +08:00

134 lines
4.4 KiB
TypeScript

import { IPlugin } from "@/src/types/pluginTypes";
import { logger } from "@/src/utils/logger";
import staticWorldState from "@/static/fixed_responses/worldState/worldState.json";
import { config } from "@/src/services/configService";
/* Add this to your config.json worldState section to enable world state sync:
"sync":{
"enabled": true,
"url": "https://content.warframe.com/dynamic/worldState.php",
"fields": { // adjust these fields to your needs
"Events": "merge",
"InGameMarket": "replace",
"SyndicateMissions": "replace",
"ActiveMissions": "replace",
"VoidTraders": "replace",
"PrimeVaultTraders": "replace",
"DailyDeals": "replace",
"Goals": "replace"
},
"interval": 3000
}
*/
type AnyObj = { [key: string]: object | Array<object> | string | number | boolean };
const staticWorldStateBackup: AnyObj = structuredClone(staticWorldState);
let syncWorldStateTimer: NodeJS.Timeout | null = null;
interface myConfig {
worldState?: {
sync?: {
enabled: boolean;
url: string;
fields: { [key: string]: string };
interval?: number;
};
};
}
export const syncWorldState = async (): Promise<void> => {
if (syncWorldStateTimer) {
clearTimeout(syncWorldStateTimer);
syncWorldStateTimer = null;
}
const config_ = config as myConfig;
if (!config_.worldState?.sync) {
logger.info("World state sync is disabled, skipping");
return;
}
const { enabled, url, fields, interval } = config_.worldState.sync;
if (!enabled || !url || !fields) {
logger.info("World state sync is not enabled or misconfigured, skipping");
return;
}
const res = await fetch(url, { method: "GET" });
if (!res.ok) {
logger.error("Failed to fetch remote world state, will retry in 5 min", {
status: res.status,
statusText: res.statusText
});
syncWorldStateTimer = setTimeout(
() => {
void syncWorldState();
},
5 * 60 * 1000
);
return;
}
const data = await res.json();
if (!data || typeof data !== "object") {
logger.error("Invalid world state sync response", { data });
return;
}
const staticWorldState_ = staticWorldState as AnyObj;
const data_ = data as AnyObj;
for (const [name, action_] of Object.entries(fields)) {
if (!(name in data)) {
logger.warn(`Field ${name} not found in world state sync response`, { data });
continue;
}
const action = action_ as string;
if (action === "replace") {
staticWorldState_[name] = data_[name];
continue;
}
if (action === "merge") {
switch (name) {
case "Events":
{
const remoteValue = data_[name] as Array<object>;
const localValue = staticWorldStateBackup[name] as Array<object>;
if (Array.isArray(remoteValue) && Array.isArray(localValue)) {
staticWorldState_[name] = localValue.concat(remoteValue);
}
}
break;
default:
logger.warn(`Not supported merge action for field ${name} in world state sync`);
}
continue;
}
logger.warn(`Unknown action ${action} for field ${name} in world state sync`);
continue;
}
if (interval && interval > 0) {
logger.info(`Next world state sync in ${interval} seconds`);
syncWorldStateTimer = setTimeout(() => {
void syncWorldState();
}, interval * 1000);
} else {
logger.info("No next world state sync scheduled");
}
};
export default class WorldStateSync implements IPlugin {
public name = "WorldStateSync";
public version = "1.0.0";
public description = "WorldStateSync plugin for Warframe Emulator";
async initialize(): Promise<void> {
logger.info(`[${this.name}] Plugin initialized successfully!`);
await syncWorldState();
}
async cleanup(): Promise<void> {
logger.info(`[${this.name}] Plugin cleanup completed`);
if (syncWorldStateTimer) {
clearTimeout(syncWorldStateTimer);
syncWorldStateTimer = null;
}
}
}