feat: add hytale-presskit module for Creator Presskit tracking

Adds new module to track CreatorPressKit.zip file size changes via HEAD requests.
Alerts with old/new size and diff. Also updates sign-token.ts with correct account ID.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
devilreef 2026-01-17 00:41:21 +06:00
parent e0ec995068
commit be1102119a
5 changed files with 153 additions and 1 deletions

View file

@ -24,7 +24,7 @@ const jwtSecret = process.env.JWT_SECRET || 'your-secret-key'
const payload = {
scope: scopes,
sub: 'test-client',
sub: 'starkow',
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + (expirationHours * 3600),
}

View file

@ -11,6 +11,7 @@ import { discordForwarderFactory } from './modules/discord-forwarder/index.js'
import { hytaleDownloaderFactory } from './modules/hytale-downloader/index.js'
import { hytaleLauncherFactory } from './modules/hytale-launcher/index.js'
import { hytalePatchesFactory } from './modules/hytale-patches/index.js'
import { hytalePresskitFactory } from './modules/hytale-presskit/index.js'
import { hytaleServerFactory } from './modules/hytale-server/index.js'
const MODULE_FACTORIES: Record<string, (config: unknown, deps: import('./core/module.js').ModuleDependencies) => Module> = {
@ -18,6 +19,7 @@ const MODULE_FACTORIES: Record<string, (config: unknown, deps: import('./core/mo
'hytale-launcher': hytaleLauncherFactory,
'hytale-patches': hytalePatchesFactory,
'hytale-downloader': hytaleDownloaderFactory,
'hytale-presskit': hytalePresskitFactory,
'hytale-server': hytaleServerFactory,
}

View file

@ -0,0 +1,31 @@
import type { PresskitUpdate } from './tracker.js'
function formatBytes(bytes: number): string {
if (bytes === 0)
return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`
}
export function formatPresskitUpdate(update: PresskitUpdate): string {
const { size, previousSize, sizeDiff, date } = update
let message = `<b>📦 Hytale Creator Presskit Update</b>
<b>New size:</b> <code>${formatBytes(size)}</code>`
if (previousSize !== null) {
const diffStr = sizeDiff > 0 ? `+${formatBytes(sizeDiff)}` : formatBytes(sizeDiff)
const diffSign = sizeDiff > 0 ? '📈' : '📉'
message += `
<b>Previous size:</b> <code>${formatBytes(previousSize)}</code>
<b>Difference:</b> <code>${diffStr}</code> ${diffSign}`
}
message += `
<b>Date:</b> <code>${date}</code>`
return message
}

View file

@ -0,0 +1,73 @@
import type { Module, ModuleDependencies } from '../../core/module.js'
import type { HytaleTrackerConfig } from '../../types.js'
import { formatPresskitUpdate } from './formatter.js'
import { checkPresskitUpdate } from './tracker.js'
export function hytalePresskitFactory(
moduleConfig: unknown,
deps: ModuleDependencies,
): Module {
const config = moduleConfig as HytaleTrackerConfig
class HytalePresskitModule implements Module {
readonly name = 'hytale-presskit'
private dependencies: ModuleDependencies
private config: HytaleTrackerConfig
private running = false
private intervalId: NodeJS.Timeout | null = null
constructor(cfg: HytaleTrackerConfig, deps: ModuleDependencies) {
this.config = cfg
this.dependencies = deps
}
isRunning(): boolean {
return this.running
}
async start(): Promise<void> {
if (this.running)
return
this.dependencies.logger(this.name, `Starting (poll interval: ${this.config.pollIntervalMinutes}min)`)
// Initial check
await this.check()
// Start polling
this.intervalId = setInterval(() => {
this.check().catch((err) => {
this.dependencies.logger(this.name, `Check failed: ${err}`)
})
}, this.config.pollIntervalMinutes * 60 * 1000)
this.running = true
}
async stop(): Promise<void> {
if (!this.running)
return
if (this.intervalId) {
clearInterval(this.intervalId)
this.intervalId = null
}
this.running = false
}
private async check(): Promise<void> {
const update = await checkPresskitUpdate(this.dependencies.stateStore)
if (update) {
const message = formatPresskitUpdate(update)
await this.dependencies.telegram.sendMessage({
text: message,
chatIds: this.config.chatIds,
disableLinkPreview: true,
})
this.dependencies.logger(this.name, `Update detected: ${update.size} bytes`)
}
}
}
return new HytalePresskitModule(config, deps)
}

View file

@ -0,0 +1,46 @@
import type { StateStore } from '../../core/state-store.js'
export interface PresskitUpdate {
size: number
previousSize: number | null
sizeDiff: number
date: string
}
const ENDPOINT = 'https://cdn.hytale.com/CreatorPressKit.zip'
const STATE_KEY = 'hytale-presskit'
export async function checkPresskitUpdate(stateStore: StateStore): Promise<PresskitUpdate | null> {
const response = await fetch(ENDPOINT, { method: 'HEAD' })
if (!response.ok) {
throw new Error(`Presskit endpoint returned ${response.status}`)
}
const contentLength = response.headers.get('content-length')
const date = response.headers.get('date')
if (!contentLength) {
throw new Error('Content-Length header missing')
}
const size = parseInt(contentLength, 10)
const state = stateStore.get<{ lastSize?: number, lastDate?: string }>(STATE_KEY)
const lastSize = state?.lastSize ?? null
if (lastSize === size) {
// No update, just update check time
stateStore.set(STATE_KEY, { lastSize: size, lastDate: date || new Date().toISOString() })
return null
}
// Update detected
stateStore.set(STATE_KEY, { lastSize: size, lastDate: date || new Date().toISOString() })
return {
size,
previousSize: lastSize,
sizeDiff: lastSize !== null ? size - lastSize : 0,
date: date || new Date().toISOString(),
}
}