refactor: convert to modular system with Hytale update trackers

- Core infrastructure: module interface, Telegram wrapper, OAuth token manager, state store
- Migrate Discord forwarder to modules/discord-forwarder/
- Add Hytale update trackers: launcher, patches, downloader, server
- Support multiple Telegram chats with per-chat topic IDs
- Unified config with legacy migration and env var fallbacks
- Auto-refresh OAuth tokens (5min buffer)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
devilreef 2026-01-16 01:18:15 +06:00
parent 369a37903f
commit a7d4df6986
29 changed files with 1368 additions and 121 deletions

84
src/core/state-store.ts Normal file
View file

@ -0,0 +1,84 @@
import fs from 'node:fs/promises'
const STATE_FILE = './state.json'
const DEBOUNCE_MS = 5000
export class StateStore {
private state = new Map<string, unknown>()
private dirty = new Set<string>()
private persistTimer: NodeJS.Timeout | null = null
private loaded = false
async load(): Promise<void> {
if (this.loaded) {
return
}
try {
const content = await fs.readFile(STATE_FILE, 'utf-8')
const data = JSON.parse(content) as Record<string, unknown>
for (const [key, value] of Object.entries(data)) {
this.state.set(key, value)
}
}
catch (err) {
if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
console.warn('[StateStore] Failed to load state file:', err)
}
}
this.loaded = true
}
get<T>(key: string): T | undefined {
return this.state.get(key) as T
}
set<T>(key: string, value: T): void {
this.state.set(key, value)
this.dirty.add(key)
this.schedulePersist()
}
private schedulePersist(): void {
if (this.persistTimer) {
return
}
this.persistTimer = setTimeout(() => {
this.persist().catch((err) => {
console.error('[StateStore] Failed to persist state:', err)
})
}, DEBOUNCE_MS)
}
private async persist(): Promise<void> {
this.persistTimer = null
if (this.dirty.size === 0) {
return
}
const toSave: Record<string, unknown> = {}
for (const key of this.dirty) {
toSave[key] = this.state.get(key)
}
try {
await fs.writeFile(STATE_FILE, JSON.stringify(toSave, null, 2))
this.dirty.clear()
}
catch (err) {
console.error('[StateStore] Failed to write state file:', err)
}
}
async flush(): Promise<void> {
if (this.persistTimer) {
clearTimeout(this.persistTimer)
this.persistTimer = null
}
await this.persist()
}
}