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:
parent
e0ec995068
commit
be1102119a
5 changed files with 153 additions and 1 deletions
|
|
@ -24,7 +24,7 @@ const jwtSecret = process.env.JWT_SECRET || 'your-secret-key'
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
scope: scopes,
|
scope: scopes,
|
||||||
sub: 'test-client',
|
sub: 'starkow',
|
||||||
iat: Math.floor(Date.now() / 1000),
|
iat: Math.floor(Date.now() / 1000),
|
||||||
exp: Math.floor(Date.now() / 1000) + (expirationHours * 3600),
|
exp: Math.floor(Date.now() / 1000) + (expirationHours * 3600),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { discordForwarderFactory } from './modules/discord-forwarder/index.js'
|
||||||
import { hytaleDownloaderFactory } from './modules/hytale-downloader/index.js'
|
import { hytaleDownloaderFactory } from './modules/hytale-downloader/index.js'
|
||||||
import { hytaleLauncherFactory } from './modules/hytale-launcher/index.js'
|
import { hytaleLauncherFactory } from './modules/hytale-launcher/index.js'
|
||||||
import { hytalePatchesFactory } from './modules/hytale-patches/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'
|
import { hytaleServerFactory } from './modules/hytale-server/index.js'
|
||||||
|
|
||||||
const MODULE_FACTORIES: Record<string, (config: unknown, deps: import('./core/module.js').ModuleDependencies) => Module> = {
|
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-launcher': hytaleLauncherFactory,
|
||||||
'hytale-patches': hytalePatchesFactory,
|
'hytale-patches': hytalePatchesFactory,
|
||||||
'hytale-downloader': hytaleDownloaderFactory,
|
'hytale-downloader': hytaleDownloaderFactory,
|
||||||
|
'hytale-presskit': hytalePresskitFactory,
|
||||||
'hytale-server': hytaleServerFactory,
|
'hytale-server': hytaleServerFactory,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
31
src/modules/hytale-presskit/formatter.ts
Normal file
31
src/modules/hytale-presskit/formatter.ts
Normal 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
|
||||||
|
}
|
||||||
73
src/modules/hytale-presskit/index.ts
Normal file
73
src/modules/hytale-presskit/index.ts
Normal 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)
|
||||||
|
}
|
||||||
46
src/modules/hytale-presskit/tracker.ts
Normal file
46
src/modules/hytale-presskit/tracker.ts
Normal 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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue