import type { JWTAuth, PowerAction, Project, ServerGeneral } from '@modrinth/utils' import { $fetch } from 'ofetch' import { useServersFetch } from '../servers-fetch.ts' import { ServerModule } from './base.ts' export class GeneralModule extends ServerModule implements ServerGeneral { server_id!: string name!: string owner_id!: string net!: { ip: string; port: number; domain: string } game!: string backup_quota!: number used_backup_quota!: number status!: string suspension_reason!: string loader!: string loader_version!: string mc_version!: string upstream!: { kind: 'modpack' | 'mod' | 'resourcepack' version_id: string project_id: string } | null motd?: string image?: string project?: Project sftp_username!: string sftp_password!: string sftp_host!: string datacenter?: string notices?: any[] node!: { token: string; instance: string } flows?: { intro?: boolean } is_medal?: boolean async fetch(): Promise { const data = await useServersFetch(`servers/${this.serverId}`, {}, 'general') if (data.upstream?.project_id) { const project = await $fetch( `https://api.modrinth.com/v2/project/${data.upstream.project_id}`, ) data.project = project as Project } if (import.meta.client) { data.image = (await this.server.processImage(data.project?.icon_url)) ?? undefined } // Copy data to this module Object.assign(this, data) } async updateName(newName: string): Promise { await useServersFetch(`servers/${this.serverId}/name`, { method: 'POST', body: { name: newName }, }) } async power(action: PowerAction): Promise { await useServersFetch(`servers/${this.serverId}/power`, { method: 'POST', body: { action }, }) await new Promise((resolve) => setTimeout(resolve, 1000)) await this.fetch() // Refresh this module } async reinstall( loader: boolean, projectId: string, versionId?: string, loaderVersionId?: string, hardReset: boolean = false, ): Promise { const hardResetParam = hardReset ? 'true' : 'false' if (loader) { if (projectId.toLowerCase() === 'neoforge') { projectId = 'NeoForge' } await useServersFetch(`servers/${this.serverId}/reinstall?hard=${hardResetParam}`, { method: 'POST', body: { loader: projectId, loader_version: loaderVersionId, game_version: versionId, }, }) } else { await useServersFetch(`servers/${this.serverId}/reinstall?hard=${hardResetParam}`, { method: 'POST', body: { project_id: projectId, version_id: versionId }, }) } } reinstallFromMrpack( mrpack: File, hardReset: boolean = false, ): { promise: Promise onProgress: (cb: (p: { loaded: number; total: number; progress: number }) => void) => void } { const hardResetParam = hardReset ? 'true' : 'false' const progressSubject = new EventTarget() const uploadPromise = (async () => { try { const auth = await useServersFetch(`servers/${this.serverId}/reinstallFromMrpack`) await new Promise((resolve, reject) => { const xhr = new XMLHttpRequest() xhr.upload.addEventListener('progress', (e) => { if (e.lengthComputable) { progressSubject.dispatchEvent( new CustomEvent('progress', { detail: { loaded: e.loaded, total: e.total, progress: (e.loaded / e.total) * 100, }, }), ) } }) xhr.onload = () => xhr.status >= 200 && xhr.status < 300 ? resolve() : reject(new Error(`[pyroservers] XHR error status: ${xhr.status}`)) xhr.onerror = () => reject(new Error('[pyroservers] .mrpack upload failed')) xhr.onabort = () => reject(new Error('[pyroservers] .mrpack upload cancelled')) xhr.ontimeout = () => reject(new Error('[pyroservers] .mrpack upload timed out')) xhr.timeout = 30 * 60 * 1000 xhr.open('POST', `https://${auth.url}/reinstallMrpackMultiparted?hard=${hardResetParam}`) xhr.setRequestHeader('Authorization', `Bearer ${auth.token}`) const formData = new FormData() formData.append('file', mrpack) xhr.send(formData) }) } catch (err) { console.error('Error reinstalling from mrpack:', err) throw err } })() return { promise: uploadPromise, onProgress: (cb: (p: { loaded: number; total: number; progress: number }) => void) => progressSubject.addEventListener('progress', ((e: CustomEvent) => cb(e.detail)) as EventListener), } } async suspend(status: boolean): Promise { await useServersFetch(`servers/${this.serverId}/suspend`, { method: 'POST', body: { suspended: status }, }) } async endIntro(): Promise { await useServersFetch(`servers/${this.serverId}/flows/intro`, { method: 'DELETE', version: 1, }) await this.fetch() // Refresh this module } async setMotd(motd: string): Promise { try { const props = (await this.server.fetchConfigFile('ServerProperties')) as any if (props) { props.motd = motd const newProps = this.server.constructServerProperties(props) const octetStream = new Blob([newProps], { type: 'application/octet-stream' }) const auth = await useServersFetch(`servers/${this.serverId}/fs`) await useServersFetch(`/update?path=/server.properties`, { method: 'PUT', contentType: 'application/octet-stream', body: octetStream, override: auth, }) } } catch { console.error( '[Modrinth Hosting] [General] Failed to set MOTD due to lack of server properties file.', ) } } }