feat: content tab rewrite for worlds (#5136)
* feat: base content card component * fix: tooltips + colors * feat: fix orgs * feat: base content tab internals rewrite * feat: fix invalidmodal * feat: add ContentModpackCard * fix: extract types * draft: layout * feat: unlink modal * feat: impl content tab * fix: lint * fix: toggling * temp: disable updating stuff * feat: selection v-model * feat: bulk selection * feat: mods tab rough draft * feat: use fuse.js * feat: add project combobox * clean up project combobox * feat: start install to play modal * fix: events * feat: use v-on * feat: bulk actions + fix floating action bar width * feat: figma alignments * feat: migrate toggle to tailwind * fix: row borders * feat: disabled state * feat: virtual list impl for card table based on window scroll * fix: lint * feat: virtualization + smaller contentcard items * feat: use ContentCardTable + ContentCardItems * feat: fix gap + border issues on last elm * feat: cleanup + use proper searching * fix: use TeleportOverflowMenu * fix: fallback to svg if src is invalid on avatar component * fix: storybook * feat: start on updater modal * feat: finish content updater modal * feat: i18n pass * feat: impl modal * feat(app): backend changes for content tab refactor (#5237) * feat: include_changelog=false for updater modal * fix: hash overrides * feat: update checking for modpack * feat: qa * feat: modpack content modal * fix: padding in table to match modals + tightness * fix: lint * feat: delete modal * feat: fix toggle bugs * fix: prepr * fix: duplicate messages * qa: full width search * qa: use bg-surface-1.5 * qa: animation for filter pills * qa: standardize hover colors * fix: border-[1px] is border * qa: mass de-select actually mass selecting * qa: match figma designs for floating action bar * qa: modal fixes * q: modal fixes x2 * fix: table border * qa: confirm modals * qa: modal alignment * qa: re-add stuck heading + dedupe logic * qa: dedupe virtual scrolling + remove dead components * qa: responsiveness for content table + link fixes * qa: version column link, tooltips + lint fixes * qa: instance busy protections * fix: installation freeze bug * chore: remove old mods page * refactor: deduplicate layout * chore: delete old content page(s) * qa * qa * qa * feat: sort btn - to iterate * fix: ml * feat: date added * fix: lint * fix: formatting.ts removal * feat: get_dependencies_as_content_items * qa: final QA changes * refactor: deduplicate + polish content.rs * feat: hook up content.vue with v1 * feat: hide v1 content api behind frontend feature flag * fix: query keys + copy on empty state * chore: i18n pass * feat: reimpl unlink + upload endpoint * feat: use bulk endpoints v1 * fix: lint * fix: flags * fix: responsiveness via container queries * fix: lint * qa: 1 * qa: fixes * qa: fix ssr issues with browse content * qa: header page divider * qa: modals * fix: prepr * fix: issues * fix: lint * fix: toggle v1 ff * qa: 5 * qa: delete modal copy * feat: creation flow modals (#5383) * refactor: delete content v0 usages + impl * feat: qa + fixes * feat: installing banner using state event * feat: fix modpack card bugs + filtering issues * refactor: delete backups v0 api module * feat: v1 servers GET endpoint * fix: backups * feat: swap to kyros upload v1 addon * fix: use tanstack for loader.vue * feat: finish install from discovery modal * qa: bug fixes * feat: set up installation settings * fix: lint * fix: typos * fix: bugs * fix: disable inline content * feat: content tab improvements — upload UX, installation settings, and client-only indicators Upload cancellation and navigation guard: - Add ConfirmLeaveModal that prompts when navigating away during upload - Cancel in-flight XHR uploads when user confirms leaving the page - Add beforeunload handler to warn on browser/tab close during upload - Track uploadedBytes/totalBytes in UploadState for progress display - Replace Collapsible with Transition for upload progress admonition - Show byte progress and percentage in upload banner - Clamp upload progress to prevent exceeding 100% Installation settings (server.properties): - Add KnownPropertiesFields and PropertiesFields types to Archon types - Add buildProperties() to creation flow context to collect gamemode, difficulty, seed, world type, structures, and generator settings - Pass properties through installContent on onboarding, discovery, and ServerSetupModal flows Server setup and discovery flow improvements: - Migrate ServerSetupModal from servers_v0.reinstall to content_v1.installContent - Replace loaderApiNames lookup with toApiLoader() helper - Remove eraseDataOnInstall toggle — always use soft_override: false - Simplify modpack install on discovery page to use first available version and route through creation flow modal for both onboarding and non-onboarding - Differentiate post-install navigation: content page for onboarding, loader options for existing servers Modpack update flow: - Replace updateModpack() call with installContent() using soft_override: true to support version selection in the content updater modal Client-only mod indicators: - Add environment field to AddonVersion (reuses Labrinth.Projects.v3.Environment) - Add environment to ContentItem and isClientOnly to ContentCardTableItem - Show orange TriangleAlertIcon with tooltip on client-only mods in content table - Add "Client-only" filter pill to content filtering (controlled via showClientOnlyFilter on ContentManagerContext) - Apply client-only indicators in both ContentPageLayout and ModpackContentModal Misc: - Add CLAUDE.md note about using prepr commands for lint checks - Export ConfirmLeaveModal from instances barrel * fix: piping * fix: switch content disable for linked server instances * feat: client only filter * fix: prepr * feat: hasUpdate shape update * feat: bulk update endpoint impl for content in panel * feat: websocket state impl again with new phases * fix: ws * fix: use timeout fn for sync admon + fix content card layout scroll for browsers with overflow anchor bug * fix: qa bugs * fix: lint, a11y and i18n * refactor: set up layouts folder properly * fix: linked data cache stuff + lint * feat: move installationsettings to shared layout * fix: lint * fix: issues * feat: temp fuck staging up * fix: lockfile * fix: data sync issues on loader.vue * fix: lint * Hide shader configuration files from content list (#5499) * feat: workaround search problem + split out reset * fix: qa * fix: changelog not showing on first open * fix: qa + optimistic updating improvements * fix: prepr+lint * fix: qa * feat: qa * fix: lint * fix: lint * fix: build * fix: build * fix: type errors * fix: fade and JAVA_HOME passthrough * feat: qa * feat: impl diff shit * fix: qa * fix: app qa * feat: update diff modal * fix: endpoint * fix: qa * fix: qa * fix: use bulk in modpack modal * feat: abort signal impl + fix issues * fix: diff modal trunc * feat: qa * fix: qa * feat: tooltip content tab * fix: prepr * fix: dismiss on settings btn * feat: qa * feat: dont clear handlers on disconnect * fix: lint * fix: wrangler + introduce staging-archon env file --------- Signed-off-by: Calum H. <calum@modrinth.com> Co-authored-by: tdgao <mr.trumgao@gmail.com> Co-authored-by: Artyom Ezri <61311568+Artezon@users.noreply.github.com>
This commit is contained in:
@@ -8,6 +8,8 @@ import type { RequestContext } from '../types/request'
|
||||
export interface NodeAuth {
|
||||
/** Node instance URL (e.g., "node-xyz.modrinth.com/modrinth/v0/fs") */
|
||||
url: string
|
||||
/** Base URL without path suffix (e.g., "node-xyz.modrinth.com") — used when available */
|
||||
baseUrl?: string
|
||||
/** JWT token */
|
||||
token: string
|
||||
}
|
||||
@@ -105,7 +107,7 @@ export class NodeAuthFeature extends AbstractFeature {
|
||||
}
|
||||
|
||||
private applyAuth(context: RequestContext, auth: NodeAuth): void {
|
||||
const baseUrl = `https://${auth.url.replace('v0/fs', '')}`
|
||||
const baseUrl = `https://${auth.url.replace(/\/modrinth\/v\d+\/fs\/?$/, '')}`
|
||||
context.url = this.buildUrl(context.path, baseUrl, context.options.version)
|
||||
|
||||
context.options.headers = {
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
import { AbstractModule } from '../../../core/abstract-module'
|
||||
import type { Archon } from '../types'
|
||||
|
||||
export class ArchonBackupsV0Module extends AbstractModule {
|
||||
public getModuleID(): string {
|
||||
return 'archon_backups_v0'
|
||||
}
|
||||
|
||||
/** GET /modrinth/v0/servers/:server_id/backups */
|
||||
public async list(serverId: string): Promise<Archon.Backups.v1.Backup[]> {
|
||||
return this.client.request<Archon.Backups.v1.Backup[]>(`/servers/${serverId}/backups`, {
|
||||
api: 'archon',
|
||||
version: 'modrinth/v0',
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
/** GET /modrinth/v0/servers/:server_id/backups/:backup_id */
|
||||
public async get(serverId: string, backupId: string): Promise<Archon.Backups.v1.Backup> {
|
||||
return this.client.request<Archon.Backups.v1.Backup>(
|
||||
`/servers/${serverId}/backups/${backupId}`,
|
||||
{ api: 'archon', version: 'modrinth/v0', method: 'GET' },
|
||||
)
|
||||
}
|
||||
|
||||
/** POST /modrinth/v0/servers/:server_id/backups */
|
||||
public async create(
|
||||
serverId: string,
|
||||
request: Archon.Backups.v1.BackupRequest,
|
||||
): Promise<Archon.Backups.v1.PostBackupResponse> {
|
||||
return this.client.request<Archon.Backups.v1.PostBackupResponse>(
|
||||
`/servers/${serverId}/backups`,
|
||||
{ api: 'archon', version: 'modrinth/v0', method: 'POST', body: request },
|
||||
)
|
||||
}
|
||||
|
||||
/** POST /modrinth/v0/servers/:server_id/backups/:backup_id/restore */
|
||||
public async restore(serverId: string, backupId: string): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/backups/${backupId}/restore`, {
|
||||
api: 'archon',
|
||||
version: 'modrinth/v0',
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
/** DELETE /modrinth/v0/servers/:server_id/backups/:backup_id */
|
||||
public async delete(serverId: string, backupId: string): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/backups/${backupId}`, {
|
||||
api: 'archon',
|
||||
version: 'modrinth/v0',
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /modrinth/v0/servers/:server_id/backups/:backup_id/retry */
|
||||
public async retry(serverId: string, backupId: string): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/backups/${backupId}/retry`, {
|
||||
api: 'archon',
|
||||
version: 'modrinth/v0',
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
/** PATCH /modrinth/v0/servers/:server_id/backups/:backup_id */
|
||||
public async rename(
|
||||
serverId: string,
|
||||
backupId: string,
|
||||
request: Archon.Backups.v1.PatchBackup,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/backups/${backupId}`, {
|
||||
api: 'archon',
|
||||
version: 'modrinth/v0',
|
||||
method: 'PATCH',
|
||||
body: request,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,102 +1,84 @@
|
||||
import { AbstractModule } from '../../../core/abstract-module'
|
||||
import type { Archon } from '../types'
|
||||
|
||||
/**
|
||||
* Default world ID - Uuid::nil() which the backend treats as "first/active world"
|
||||
* See: apps/archon/src/routes/v1/servers/worlds/mod.rs - world_id_nullish()
|
||||
* TODO:
|
||||
* - Make sure world ID is being passed before we ship worlds.
|
||||
* - The schema will change when Backups v4 (routes stay as v1) so remember to do that.
|
||||
*/
|
||||
const DEFAULT_WORLD_ID: string = '00000000-0000-0000-0000-000000000000' as const
|
||||
|
||||
export class ArchonBackupsV1Module extends AbstractModule {
|
||||
public getModuleID(): string {
|
||||
return 'archon_backups_v1'
|
||||
}
|
||||
|
||||
/** GET /v1/:server_id/worlds/:world_id/backups */
|
||||
public async list(
|
||||
serverId: string,
|
||||
worldId: string = DEFAULT_WORLD_ID,
|
||||
): Promise<Archon.Backups.v1.Backup[]> {
|
||||
/** GET /v1/servers/:server_id/worlds/:world_id/backups */
|
||||
public async list(serverId: string, worldId: string): Promise<Archon.Backups.v1.Backup[]> {
|
||||
return this.client.request<Archon.Backups.v1.Backup[]>(
|
||||
`/${serverId}/worlds/${worldId}/backups`,
|
||||
`/servers/${serverId}/worlds/${worldId}/backups`,
|
||||
{ api: 'archon', version: 1, method: 'GET' },
|
||||
)
|
||||
}
|
||||
|
||||
/** GET /v1/:server_id/worlds/:world_id/backups/:backup_id */
|
||||
/** GET /v1/servers/:server_id/worlds/:world_id/backups/:backup_id */
|
||||
public async get(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
backupId: string,
|
||||
worldId: string = DEFAULT_WORLD_ID,
|
||||
): Promise<Archon.Backups.v1.Backup> {
|
||||
return this.client.request<Archon.Backups.v1.Backup>(
|
||||
`/${serverId}/worlds/${worldId}/backups/${backupId}`,
|
||||
`/servers/${serverId}/worlds/${worldId}/backups/${backupId}`,
|
||||
{ api: 'archon', version: 1, method: 'GET' },
|
||||
)
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/backups */
|
||||
/** POST /v1/servers/:server_id/worlds/:world_id/backups */
|
||||
public async create(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
request: Archon.Backups.v1.BackupRequest,
|
||||
worldId: string = DEFAULT_WORLD_ID,
|
||||
): Promise<Archon.Backups.v1.PostBackupResponse> {
|
||||
return this.client.request<Archon.Backups.v1.PostBackupResponse>(
|
||||
`/${serverId}/worlds/${worldId}/backups`,
|
||||
`/servers/${serverId}/worlds/${worldId}/backups`,
|
||||
{ api: 'archon', version: 1, method: 'POST', body: request },
|
||||
)
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/backups/:backup_id/restore */
|
||||
public async restore(
|
||||
serverId: string,
|
||||
backupId: string,
|
||||
worldId: string = DEFAULT_WORLD_ID,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/${serverId}/worlds/${worldId}/backups/${backupId}/restore`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
})
|
||||
/** POST /v1/servers/:server_id/worlds/:world_id/backups/:backup_id/restore */
|
||||
public async restore(serverId: string, worldId: string, backupId: string): Promise<void> {
|
||||
await this.client.request<void>(
|
||||
`/servers/${serverId}/worlds/${worldId}/backups/${backupId}/restore`,
|
||||
{
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/** DELETE /v1/:server_id/worlds/:world_id/backups/:backup_id */
|
||||
public async delete(
|
||||
serverId: string,
|
||||
backupId: string,
|
||||
worldId: string = DEFAULT_WORLD_ID,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/${serverId}/worlds/${worldId}/backups/${backupId}`, {
|
||||
/** DELETE /v1/servers/:server_id/worlds/:world_id/backups/:backup_id */
|
||||
public async delete(serverId: string, worldId: string, backupId: string): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/worlds/${worldId}/backups/${backupId}`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/backups/:backup_id/retry */
|
||||
public async retry(
|
||||
serverId: string,
|
||||
backupId: string,
|
||||
worldId: string = DEFAULT_WORLD_ID,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/${serverId}/worlds/${worldId}/backups/${backupId}/retry`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
})
|
||||
/** POST /v1/servers/:server_id/worlds/:world_id/backups/:backup_id/retry */
|
||||
public async retry(serverId: string, worldId: string, backupId: string): Promise<void> {
|
||||
await this.client.request<void>(
|
||||
`/servers/${serverId}/worlds/${worldId}/backups/${backupId}/retry`,
|
||||
{
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/** PATCH /v1/:server_id/worlds/:world_id/backups/:backup_id */
|
||||
/** PATCH /v1/servers/:server_id/worlds/:world_id/backups/:backup_id */
|
||||
public async rename(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
backupId: string,
|
||||
request: Archon.Backups.v1.PatchBackup,
|
||||
worldId: string = DEFAULT_WORLD_ID,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/${serverId}/worlds/${worldId}/backups/${backupId}`, {
|
||||
await this.client.request<void>(`/servers/${serverId}/worlds/${worldId}/backups/${backupId}`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'PATCH',
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import { AbstractModule } from '../../../core/abstract-module'
|
||||
import type { Archon } from '../types'
|
||||
|
||||
export class ArchonContentV0Module extends AbstractModule {
|
||||
public getModuleID(): string {
|
||||
return 'archon_content_v0'
|
||||
}
|
||||
|
||||
/** GET /modrinth/v0/servers/:server_id/mods */
|
||||
public async list(serverId: string): Promise<Archon.Content.v0.Mod[]> {
|
||||
return this.client.request<Archon.Content.v0.Mod[]>(`/servers/${serverId}/mods`, {
|
||||
api: 'archon',
|
||||
version: 'modrinth/v0',
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /modrinth/v0/servers/:server_id/mods */
|
||||
public async install(
|
||||
serverId: string,
|
||||
request: Archon.Content.v0.InstallModRequest,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/mods`, {
|
||||
api: 'archon',
|
||||
version: 'modrinth/v0',
|
||||
method: 'POST',
|
||||
body: request,
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /modrinth/v0/servers/:server_id/deleteMod */
|
||||
public async delete(
|
||||
serverId: string,
|
||||
request: Archon.Content.v0.DeleteModRequest,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/deleteMod`, {
|
||||
api: 'archon',
|
||||
version: 'modrinth/v0',
|
||||
method: 'POST',
|
||||
body: request,
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /modrinth/v0/servers/:server_id/mods/update */
|
||||
public async update(
|
||||
serverId: string,
|
||||
request: Archon.Content.v0.UpdateModRequest,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/mods/update`, {
|
||||
api: 'archon',
|
||||
version: 'modrinth/v0',
|
||||
method: 'POST',
|
||||
body: request,
|
||||
})
|
||||
}
|
||||
}
|
||||
276
packages/api-client/src/modules/archon/content/v1.ts
Normal file
276
packages/api-client/src/modules/archon/content/v1.ts
Normal file
@@ -0,0 +1,276 @@
|
||||
import { AbstractModule } from '../../../core/abstract-module'
|
||||
import type { Archon } from '../types'
|
||||
|
||||
export class ArchonContentV1Module extends AbstractModule {
|
||||
public getModuleID(): string {
|
||||
return 'archon_content_v1'
|
||||
}
|
||||
|
||||
/** GET /v1/:server_id/worlds/:world_id/addons */
|
||||
public async getAddons(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
options?: {
|
||||
from_modpack?: boolean
|
||||
disabled?: boolean
|
||||
addons?: boolean
|
||||
updates?: boolean
|
||||
},
|
||||
): Promise<Archon.Content.v1.Addons> {
|
||||
const params = new URLSearchParams()
|
||||
if (options?.from_modpack !== undefined)
|
||||
params.set('from_modpack', String(options.from_modpack))
|
||||
if (options?.disabled !== undefined) params.set('disabled', String(options.disabled))
|
||||
if (options?.addons !== undefined) params.set('addons', String(options.addons))
|
||||
if (options?.updates !== undefined) params.set('updates', String(options.updates))
|
||||
const query = params.toString()
|
||||
|
||||
return this.client.request<Archon.Content.v1.Addons>(
|
||||
`/servers/${serverId}/worlds/${worldId}/addons${query ? `?${query}` : ''}`,
|
||||
{
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'GET',
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/addons */
|
||||
public async addAddon(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
request: Archon.Content.v1.AddAddonRequest,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/worlds/${worldId}/addons`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
body: request,
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/addons/delete */
|
||||
public async deleteAddon(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
request: Archon.Content.v1.RemoveAddonRequest,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/worlds/${worldId}/addons/delete`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
body: request,
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/addons/disable */
|
||||
public async disableAddon(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
request: Archon.Content.v1.RemoveAddonRequest,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/worlds/${worldId}/addons/disable`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
body: request,
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/addons/enable */
|
||||
public async enableAddon(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
request: Archon.Content.v1.RemoveAddonRequest,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/worlds/${worldId}/addons/enable`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
body: request,
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/addons/delete-many */
|
||||
public async deleteAddons(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
items: Archon.Content.v1.RemoveAddonRequest[],
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/worlds/${worldId}/addons/delete-many`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
body: { items },
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/addons/disable-many */
|
||||
public async disableAddons(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
items: Archon.Content.v1.RemoveAddonRequest[],
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/worlds/${worldId}/addons/disable-many`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
body: { items },
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/addons/enable-many */
|
||||
public async enableAddons(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
items: Archon.Content.v1.RemoveAddonRequest[],
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/worlds/${worldId}/addons/enable-many`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
body: { items },
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/content */
|
||||
public async installContent(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
request: Archon.Content.v1.InstallWorldContent,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/worlds/${worldId}/content`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
body: request,
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/content/repair */
|
||||
public async repair(serverId: string, worldId: string): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/worlds/${worldId}/content/repair`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/content/unlink-modpack */
|
||||
public async unlinkModpack(serverId: string, worldId: string): Promise<void> {
|
||||
await this.client.request<void>(
|
||||
`/servers/${serverId}/worlds/${worldId}/content/unlink-modpack`,
|
||||
{
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/** GET /v1/:server_id/worlds/:world_id/addons/update?filename=... */
|
||||
public async getAddonUpdate(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
filename: string,
|
||||
): Promise<Archon.Content.v1.Addon> {
|
||||
return this.client.request<Archon.Content.v1.Addon>(
|
||||
`/servers/${serverId}/worlds/${worldId}/addons/update?filename=${encodeURIComponent(filename)}`,
|
||||
{
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'GET',
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/addons/update */
|
||||
public async updateAddon(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
request: Archon.Content.v1.UpdateAddonRequest,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/worlds/${worldId}/addons/update`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
body: request,
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/addons/update-many */
|
||||
public async updateAddons(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
addons: Archon.Content.v1.UpdateAddonRequest[],
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/worlds/${worldId}/addons/update-many`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
body: { addons },
|
||||
})
|
||||
}
|
||||
|
||||
/** GET /v1/:server_id/worlds/:world_id/content/modpack/update */
|
||||
public async getModpackUpdate(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
): Promise<Archon.Content.v1.ModpackFields> {
|
||||
return this.client.request<Archon.Content.v1.ModpackFields>(
|
||||
`/servers/${serverId}/worlds/${worldId}/content/modpack/update`,
|
||||
{
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'GET',
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/content/modpack/update */
|
||||
public async updateModpack(serverId: string, worldId: string): Promise<void> {
|
||||
await this.client.request<void>(
|
||||
`/servers/${serverId}/worlds/${worldId}/content/modpack/update`,
|
||||
{
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/** GET /v1/:server_id/worlds/:world_id/content/update-game-version?game_version=... */
|
||||
public async getUpdateGameVersionPreview(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
gameVersion: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<Archon.Content.v1.UpdateGameVersionPreview> {
|
||||
return this.client.request<Archon.Content.v1.UpdateGameVersionPreview>(
|
||||
`/servers/${serverId}/worlds/${worldId}/content/update-game-version?game_version=${encodeURIComponent(gameVersion)}`,
|
||||
{
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'GET',
|
||||
timeout: 1000 * 1000,
|
||||
signal,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/content/update-game-version?game_version=... */
|
||||
public async applyGameVersionUpdate(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
gameVersion: string,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(
|
||||
`/servers/${serverId}/worlds/${worldId}/content/update-game-version?game_version=${encodeURIComponent(gameVersion)}`,
|
||||
{
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
export * from './backups/v0'
|
||||
export * from './backups/v1'
|
||||
export * from './content/v0'
|
||||
export * from './content/v1'
|
||||
export * from './properties/v1'
|
||||
export * from './servers/v0'
|
||||
export * from './servers/v1'
|
||||
export * from './types'
|
||||
|
||||
37
packages/api-client/src/modules/archon/options/v1.ts
Normal file
37
packages/api-client/src/modules/archon/options/v1.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { AbstractModule } from '../../../core/abstract-module'
|
||||
import type { Archon } from '../types'
|
||||
|
||||
export class ArchonOptionsV1Module extends AbstractModule {
|
||||
public getModuleID(): string {
|
||||
return 'archon_options_v1'
|
||||
}
|
||||
|
||||
/** GET /v1/servers/:server_id/worlds/:world_id/options/startup */
|
||||
public async getStartup(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
): Promise<Archon.Content.v1.RuntimeOptions> {
|
||||
return this.client.request<Archon.Content.v1.RuntimeOptions>(
|
||||
`/servers/${serverId}/worlds/${worldId}/options/startup`,
|
||||
{
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'GET',
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/** PATCH /v1/servers/:server_id/worlds/:world_id/options/startup */
|
||||
public async patchStartup(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
body: Archon.Content.v1.PatchRuntimeOptions,
|
||||
): Promise<void> {
|
||||
await this.client.request(`/servers/${serverId}/worlds/${worldId}/options/startup`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'PATCH',
|
||||
body,
|
||||
})
|
||||
}
|
||||
}
|
||||
40
packages/api-client/src/modules/archon/properties/v1.ts
Normal file
40
packages/api-client/src/modules/archon/properties/v1.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { AbstractModule } from '../../../core/abstract-module'
|
||||
import type { Archon } from '../types'
|
||||
|
||||
export class ArchonPropertiesV1Module extends AbstractModule {
|
||||
public getModuleID(): string {
|
||||
return 'archon_properties_v1'
|
||||
}
|
||||
|
||||
/** GET /v1/servers/:server_id/worlds/:world_id/properties */
|
||||
public async getProperties(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
): Promise<Archon.Content.v1.PropertiesFields> {
|
||||
return this.client.request<Archon.Content.v1.PropertiesFields>(
|
||||
`/servers/${serverId}/worlds/${worldId}/properties`,
|
||||
{
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'GET',
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/** PATCH /v1/servers/:server_id/worlds/:world_id/properties */
|
||||
public async patchProperties(
|
||||
serverId: string,
|
||||
worldId: string,
|
||||
body: Archon.Content.v1.PatchPropertiesFields,
|
||||
): Promise<Archon.Content.v1.PropertiesFields> {
|
||||
return this.client.request<Archon.Content.v1.PropertiesFields>(
|
||||
`/servers/${serverId}/worlds/${worldId}/properties`,
|
||||
{
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'PATCH',
|
||||
body,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AbstractModule } from '../../../core/abstract-module'
|
||||
import type { UploadHandle, UploadProgress } from '../../../types/upload'
|
||||
import type { Archon } from '../types'
|
||||
|
||||
export class ArchonServersV0Module extends AbstractModule {
|
||||
@@ -94,4 +95,210 @@ export class ArchonServersV0Module extends AbstractModule {
|
||||
body: { action },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinstall a server with a new loader or modpack
|
||||
* POST /modrinth/v0/servers/:id/reinstall
|
||||
*/
|
||||
public async reinstall(
|
||||
serverId: string,
|
||||
request: Archon.Servers.v0.ReinstallRequest,
|
||||
hardReset: boolean = false,
|
||||
): Promise<void> {
|
||||
await this.client.request(`/servers/${serverId}/reinstall`, {
|
||||
api: 'archon',
|
||||
method: 'POST',
|
||||
version: 'modrinth/v0',
|
||||
params: { hard: String(hardReset) },
|
||||
body: request,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authentication credentials for .mrpack file upload
|
||||
* GET /modrinth/v0/servers/:id/reinstallFromMrpack
|
||||
*/
|
||||
public async getReinstallMrpackAuth(
|
||||
serverId: string,
|
||||
): Promise<Archon.Servers.v0.MrpackReinstallAuth> {
|
||||
return this.client.request<Archon.Servers.v0.MrpackReinstallAuth>(
|
||||
`/servers/${serverId}/reinstallFromMrpack`,
|
||||
{
|
||||
api: 'archon',
|
||||
version: 'modrinth/v0',
|
||||
method: 'GET',
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinstall a server from a .mrpack file with progress tracking
|
||||
*
|
||||
* Two-step flow: fetches upload auth, then uploads the .mrpack file to the node.
|
||||
*
|
||||
* @param serverId - Server ID
|
||||
* @param file - .mrpack file to upload
|
||||
* @param hardReset - Whether to erase all server data
|
||||
* @param options - Optional progress callback
|
||||
* @returns Promise resolving to an UploadHandle with progress tracking and cancellation
|
||||
*/
|
||||
public async reinstallFromMrpack(
|
||||
serverId: string,
|
||||
file: File,
|
||||
hardReset: boolean = false,
|
||||
options?: {
|
||||
onProgress?: (progress: UploadProgress) => void
|
||||
},
|
||||
): Promise<UploadHandle<void>> {
|
||||
const auth = await this.getReinstallMrpackAuth(serverId)
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
return this.client.upload<void>('', {
|
||||
api: `https://${auth.url}`,
|
||||
version: 'reinstallMrpackMultiparted',
|
||||
formData,
|
||||
params: { hard: String(hardReset) },
|
||||
headers: { Authorization: `Bearer ${auth.token}` },
|
||||
skipAuth: true,
|
||||
onProgress: options?.onProgress,
|
||||
retry: false,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a server's name
|
||||
* POST /modrinth/v0/servers/:id/name
|
||||
*/
|
||||
public async updateName(serverId: string, name: string): Promise<void> {
|
||||
await this.client.request(`/servers/${serverId}/name`, {
|
||||
api: 'archon',
|
||||
method: 'POST',
|
||||
version: 'modrinth/v0',
|
||||
body: { name },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allocations for a server
|
||||
* GET /modrinth/v0/servers/:id/allocations
|
||||
*/
|
||||
public async getAllocations(serverId: string): Promise<Archon.Servers.v0.Allocation[]> {
|
||||
return this.client.request<Archon.Servers.v0.Allocation[]>(`/servers/${serverId}/allocations`, {
|
||||
api: 'archon',
|
||||
method: 'GET',
|
||||
version: 'modrinth/v0',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Reserve a new allocation for a server
|
||||
* POST /modrinth/v0/servers/:id/allocations?name=...
|
||||
*/
|
||||
public async reserveAllocation(
|
||||
serverId: string,
|
||||
name: string,
|
||||
): Promise<Archon.Servers.v0.Allocation> {
|
||||
return this.client.request<Archon.Servers.v0.Allocation>(`/servers/${serverId}/allocations`, {
|
||||
api: 'archon',
|
||||
method: 'POST',
|
||||
version: 'modrinth/v0',
|
||||
params: { name },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an allocation's name
|
||||
* PUT /modrinth/v0/servers/:id/allocations/:port?name=...
|
||||
*/
|
||||
public async updateAllocation(serverId: string, port: number, name: string): Promise<void> {
|
||||
await this.client.request(`/servers/${serverId}/allocations/${port}`, {
|
||||
api: 'archon',
|
||||
method: 'PUT',
|
||||
version: 'modrinth/v0',
|
||||
params: { name },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an allocation
|
||||
* DELETE /modrinth/v0/servers/:id/allocations/:port
|
||||
*/
|
||||
public async deleteAllocation(serverId: string, port: number): Promise<void> {
|
||||
await this.client.request(`/servers/${serverId}/allocations/${port}`, {
|
||||
api: 'archon',
|
||||
method: 'DELETE',
|
||||
version: 'modrinth/v0',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a subdomain is available
|
||||
* GET /modrinth/v0/subdomains/:subdomain/isavailable
|
||||
*/
|
||||
public async checkSubdomainAvailability(subdomain: string): Promise<{ available: boolean }> {
|
||||
return this.client.request<{ available: boolean }>(`/subdomains/${subdomain}/isavailable`, {
|
||||
api: 'archon',
|
||||
method: 'GET',
|
||||
version: 'modrinth/v0',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Change a server's subdomain
|
||||
* POST /modrinth/v0/servers/:id/subdomain
|
||||
*/
|
||||
public async changeSubdomain(serverId: string, subdomain: string): Promise<void> {
|
||||
await this.client.request(`/servers/${serverId}/subdomain`, {
|
||||
api: 'archon',
|
||||
method: 'POST',
|
||||
version: 'modrinth/v0',
|
||||
body: { subdomain },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get startup configuration for a server
|
||||
* GET /modrinth/v0/servers/:id/startup
|
||||
*/
|
||||
public async getStartupConfig(serverId: string): Promise<Archon.Servers.v0.StartupConfig> {
|
||||
return this.client.request<Archon.Servers.v0.StartupConfig>(`/servers/${serverId}/startup`, {
|
||||
api: 'archon',
|
||||
method: 'GET',
|
||||
version: 'modrinth/v0',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update startup configuration for a server
|
||||
* POST /modrinth/v0/servers/:id/startup
|
||||
*/
|
||||
public async updateStartupConfig(
|
||||
serverId: string,
|
||||
config: {
|
||||
invocation: string | null
|
||||
jdk_version: string | null
|
||||
jdk_build: string | null
|
||||
},
|
||||
): Promise<void> {
|
||||
await this.client.request(`/servers/${serverId}/startup`, {
|
||||
api: 'archon',
|
||||
method: 'POST',
|
||||
version: 'modrinth/v0',
|
||||
body: config,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss a server notice
|
||||
* POST /modrinth/v0/servers/:id/notices/:noticeId/dismiss
|
||||
*/
|
||||
public async dismissNotice(serverId: string, noticeId: number): Promise<void> {
|
||||
await this.client.request(`/servers/${serverId}/notices/${noticeId}/dismiss`, {
|
||||
api: 'archon',
|
||||
method: 'POST',
|
||||
version: 'modrinth/v0',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,30 @@ export class ArchonServersV1Module extends AbstractModule {
|
||||
return 'archon_servers_v1'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of servers for the authenticated user
|
||||
* GET /v1/servers
|
||||
*/
|
||||
public async list(): Promise<Archon.Servers.v1.ServerFull[]> {
|
||||
return this.client.request<Archon.Servers.v1.ServerFull[]>('/servers', {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full server details including worlds, backups, and content
|
||||
* GET /v1/servers/:server_id
|
||||
*/
|
||||
public async get(serverId: string): Promise<Archon.Servers.v1.ServerFull> {
|
||||
return this.client.request<Archon.Servers.v1.ServerFull>(`/servers/${serverId}`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available regions
|
||||
* GET /v1/regions
|
||||
@@ -17,4 +41,16 @@ export class ArchonServersV1Module extends AbstractModule {
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* End the intro flow for a server
|
||||
* DELETE /v1/servers/:id/flows/intro
|
||||
*/
|
||||
public async endIntro(serverId: string): Promise<void> {
|
||||
await this.client.request(`/servers/${serverId}/flows/intro`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,211 @@
|
||||
import type { Labrinth } from '../labrinth/types'
|
||||
|
||||
export namespace Archon {
|
||||
export namespace Content {
|
||||
export namespace v0 {
|
||||
export type ContentKind = 'mod' | 'plugin'
|
||||
export namespace v1 {
|
||||
export type AddonKind = 'mod' | 'plugin' | 'datapack' | 'shader' | 'resourcepack'
|
||||
|
||||
export type Mod = {
|
||||
export type ContentOwnerType = 'user' | 'organization'
|
||||
|
||||
export type ContentOwner = {
|
||||
id: string
|
||||
name: string
|
||||
type: ContentOwnerType
|
||||
icon_url: string | null
|
||||
}
|
||||
|
||||
export type AddonVersion = {
|
||||
id: string
|
||||
name: string | null
|
||||
environment?: Labrinth.Projects.v3.Environment | null
|
||||
}
|
||||
|
||||
export type Addon = {
|
||||
id: string
|
||||
filename: string
|
||||
project_id: string | undefined
|
||||
version_id: string | undefined
|
||||
name: string | undefined
|
||||
version_number: string | undefined
|
||||
icon_url: string | undefined
|
||||
owner: string | undefined
|
||||
filesize: number
|
||||
disabled: boolean
|
||||
installing: boolean
|
||||
kind: AddonKind
|
||||
from_modpack: boolean
|
||||
has_update: string | null
|
||||
name: string | null
|
||||
project_id: string | null
|
||||
version: AddonVersion | null
|
||||
owner: ContentOwner | null
|
||||
icon_url: string | null
|
||||
}
|
||||
|
||||
export type InstallModRequest = {
|
||||
rinth_ids: {
|
||||
project_id: string
|
||||
version_id: string
|
||||
}
|
||||
install_as: ContentKind
|
||||
export type Addons = {
|
||||
modloader: string | null
|
||||
modloader_version: string | null
|
||||
game_version: string | null
|
||||
modpack: ModpackFields | null
|
||||
addons: Addon[] | null
|
||||
}
|
||||
|
||||
export type DeleteModRequest = {
|
||||
path: string
|
||||
export type AddAddonRequest = {
|
||||
project_id: string
|
||||
version_id?: string
|
||||
kind?: AddonKind
|
||||
}
|
||||
|
||||
export type UpdateModRequest = {
|
||||
replace: string
|
||||
export type RemoveAddonRequest = {
|
||||
kind: AddonKind
|
||||
filename: string
|
||||
}
|
||||
|
||||
export type UpdateAddonRequest = {
|
||||
filename: string
|
||||
version_id?: string | null
|
||||
}
|
||||
|
||||
export type Modloader =
|
||||
| 'forge'
|
||||
| 'neo_forge'
|
||||
| 'fabric'
|
||||
| 'quilt'
|
||||
| 'paper'
|
||||
| 'purpur'
|
||||
| 'vanilla'
|
||||
|
||||
export type ModpackSpec = {
|
||||
platform: 'modrinth'
|
||||
project_id: string
|
||||
version_id: string
|
||||
}
|
||||
|
||||
export type ModpackOwner = {
|
||||
id: string
|
||||
name: string
|
||||
type: 'user' | 'organization'
|
||||
icon_url: string | null
|
||||
}
|
||||
|
||||
export type ModpackFields = {
|
||||
spec: ModpackSpec
|
||||
has_update: string | null
|
||||
title: string | null
|
||||
description: string | null
|
||||
icon_url: string | null
|
||||
owner: ModpackOwner | null
|
||||
version_number: string | null
|
||||
date_published: string | null
|
||||
downloads: number | null
|
||||
followers: number | null
|
||||
}
|
||||
|
||||
export type KnownPropertiesFields = {
|
||||
allow_cheats?: string | null
|
||||
allow_flight?: string | null
|
||||
difficulty?: string | null
|
||||
enforce_whitelist?: string | null
|
||||
force_gamemode?: string | null
|
||||
gamemode?: string | null
|
||||
generate_structures?: string | null
|
||||
generator_settings?: string | null
|
||||
hardcore?: string | null
|
||||
level_seed?: string | null
|
||||
level_type?: string | null
|
||||
max_players?: string | null
|
||||
max_tick_time?: string | null
|
||||
motd?: string | null
|
||||
pause_when_empty_seconds?: string | null
|
||||
player_idle_timeout?: string | null
|
||||
require_resource_pack?: string | null
|
||||
resource_pack?: string | null
|
||||
resource_pack_id?: string | null
|
||||
resource_pack_sha1?: string | null
|
||||
simulation_distance?: string | null
|
||||
spawn_protection?: string | null
|
||||
sync_chunk_writes?: string | null
|
||||
view_distance?: string | null
|
||||
white_list?: string | null
|
||||
}
|
||||
|
||||
export type PropertiesFields = {
|
||||
known: KnownPropertiesFields
|
||||
custom?: Record<string, string>
|
||||
}
|
||||
|
||||
export type PatchPropertiesFields = {
|
||||
known?: KnownPropertiesFields
|
||||
custom?: Record<string, string | null>
|
||||
}
|
||||
|
||||
export type JreVendor = 'temurin' | 'corretto' | 'graal'
|
||||
|
||||
export type RuntimeOptions = {
|
||||
java_version: number | null
|
||||
jre_vendor: JreVendor | null
|
||||
original_invocation: string | null
|
||||
startup_command: string | null
|
||||
}
|
||||
|
||||
export type PatchRuntimeOptions = {
|
||||
java_version?: number | null
|
||||
jre_vendor?: JreVendor | null
|
||||
startup_command?: string | null
|
||||
}
|
||||
|
||||
export type InstallWorldContent =
|
||||
| {
|
||||
content_variant: 'modpack'
|
||||
spec: ModpackSpec
|
||||
soft_override: boolean
|
||||
properties?: PropertiesFields | null
|
||||
}
|
||||
| {
|
||||
content_variant: 'bare'
|
||||
loader: Modloader
|
||||
version: string
|
||||
game_version?: string
|
||||
soft_override: boolean
|
||||
properties?: PropertiesFields | null
|
||||
}
|
||||
|
||||
export type AddonDiffVersion = {
|
||||
id: string
|
||||
version_number: string
|
||||
}
|
||||
|
||||
export type AddonDiffProject = {
|
||||
id: string
|
||||
title: string
|
||||
icon_url: string | null
|
||||
slug: string
|
||||
}
|
||||
|
||||
export type AddonBaseDiffInfo = {
|
||||
current_version: AddonDiffVersion | null
|
||||
new_version: AddonDiffVersion | null
|
||||
file_name: string | null
|
||||
project_id: string | null
|
||||
project: AddonDiffProject | null
|
||||
}
|
||||
|
||||
export type AddonDiffAdded = AddonBaseDiffInfo & {
|
||||
type: 'added'
|
||||
new_version_id: string
|
||||
}
|
||||
|
||||
export type AddonDiffRemoved = AddonBaseDiffInfo & {
|
||||
type: 'removed'
|
||||
}
|
||||
|
||||
export type AddonDiffUpdated = AddonBaseDiffInfo & {
|
||||
type: 'updated'
|
||||
current_version_id: string
|
||||
new_version_id: string
|
||||
}
|
||||
|
||||
export type AddonDiff = AddonDiffAdded | AddonDiffRemoved | AddonDiffUpdated
|
||||
|
||||
export type UpdateGameVersionPreview = {
|
||||
addon_changes: AddonDiff[]
|
||||
new_game_version: string
|
||||
new_loader_version: string
|
||||
has_unknown_content: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,9 +322,95 @@ export namespace Archon {
|
||||
url: string // e.g., "node-xyz.modrinth.com/modrinth/v0/fs"
|
||||
token: string // JWT token for filesystem access
|
||||
}
|
||||
|
||||
export type ReinstallLoaderRequest = {
|
||||
loader: string
|
||||
loader_version?: string
|
||||
game_version?: string
|
||||
}
|
||||
|
||||
export type ReinstallModpackRequest = {
|
||||
project_id: string
|
||||
version_id?: string
|
||||
}
|
||||
|
||||
export type ReinstallRequest = ReinstallLoaderRequest | ReinstallModpackRequest
|
||||
|
||||
export type MrpackReinstallAuth = {
|
||||
url: string
|
||||
token: string
|
||||
}
|
||||
|
||||
export type Allocation = {
|
||||
port: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export type StartupConfig = {
|
||||
invocation: string
|
||||
original_invocation: string
|
||||
jdk_version: 'lts8' | 'lts11' | 'lts17' | 'lts21'
|
||||
jdk_build: 'corretto' | 'temurin' | 'graal'
|
||||
}
|
||||
}
|
||||
|
||||
export namespace v1 {
|
||||
export type ServerFull = {
|
||||
id: string
|
||||
name: string
|
||||
subdomain: string
|
||||
specs: ServerResources
|
||||
sftp_username: string
|
||||
sftp_password: string
|
||||
tags: string[]
|
||||
location: ServerLocation
|
||||
worlds: WorldFull[]
|
||||
}
|
||||
|
||||
export type ServerResources = {
|
||||
cpu: number
|
||||
memory_mb: number
|
||||
storage_mb: number
|
||||
swap_mb: number
|
||||
}
|
||||
|
||||
export type ServerLocation =
|
||||
| {
|
||||
status: 'assigned'
|
||||
location_metadata: {
|
||||
region: string
|
||||
region_should_be_user_displayed: boolean
|
||||
hostname: string
|
||||
is_decommissioned_node: boolean
|
||||
}
|
||||
}
|
||||
| {
|
||||
status: 'unassigned'
|
||||
}
|
||||
|
||||
export type WorldFull = {
|
||||
id: string
|
||||
name: string
|
||||
created_at: string
|
||||
is_active: boolean
|
||||
backups: Archon.Backups.v1.Backup[]
|
||||
content: WorldContentInfo | null
|
||||
readiness: WorldReadiness
|
||||
}
|
||||
|
||||
export type WorldReadiness = {
|
||||
data_synchronized_fetched: boolean
|
||||
}
|
||||
|
||||
export type WorldContentInfo = {
|
||||
modloader: string
|
||||
modloader_version: string
|
||||
game_version: string
|
||||
java_version: number
|
||||
invocation: string
|
||||
original_invocation: string
|
||||
}
|
||||
|
||||
export type Region = {
|
||||
shortcode: string
|
||||
country_code: string
|
||||
@@ -174,19 +434,18 @@ export namespace Archon {
|
||||
|
||||
export type Backup = {
|
||||
id: string
|
||||
physical_id: string
|
||||
name: string
|
||||
created_at: string
|
||||
automated: boolean
|
||||
interrupted: boolean
|
||||
ongoing: boolean
|
||||
locked: boolean
|
||||
task?: {
|
||||
file?: BackupTaskProgress
|
||||
create?: BackupTaskProgress
|
||||
restore?: BackupTaskProgress
|
||||
}
|
||||
// TODO: Uncomment when API supports these fields
|
||||
// size?: number // bytes
|
||||
// creator_id?: string // user ID, or 'auto' for automated backups
|
||||
}
|
||||
|
||||
export type BackupRequest = {
|
||||
@@ -319,6 +578,37 @@ export namespace Archon {
|
||||
all: FilesystemOperation[]
|
||||
}
|
||||
|
||||
export type ReadinessState =
|
||||
| 'deprovisioned'
|
||||
| 'waiting_active_world'
|
||||
| 'waiting_world_spec_details_for_progress'
|
||||
| 'pulling_world_data'
|
||||
| 'migration_zfs'
|
||||
| 'sync_content'
|
||||
| 'container_readying'
|
||||
| 'ready'
|
||||
|
||||
export type FlattenedPowerState = 'not_ready' | 'starting' | 'running' | 'stopping' | 'idle'
|
||||
|
||||
export type SyncInstallPhase = 'Analyzing' | 'InstallingPack' | 'InstallingLoader' | 'Addons'
|
||||
|
||||
export type SyncContentProgress = {
|
||||
started_at: string
|
||||
phase: SyncInstallPhase
|
||||
percent: number
|
||||
}
|
||||
|
||||
export type WSStateEvent = {
|
||||
event: 'state'
|
||||
debug: string
|
||||
power_variant: FlattenedPowerState
|
||||
exit_code?: number | null
|
||||
was_oom?: boolean
|
||||
target: 'start' | 'stop' | 'restart' | null
|
||||
uptime: number
|
||||
progress: SyncContentProgress | null
|
||||
}
|
||||
|
||||
// Outgoing messages (client -> server)
|
||||
export type WSOutgoingMessage = WSAuthMessage | WSCommandMessage
|
||||
|
||||
@@ -337,6 +627,7 @@ export namespace Archon {
|
||||
| WSLogEvent
|
||||
| WSStatsEvent
|
||||
| WSPowerStateEvent
|
||||
| WSStateEvent
|
||||
| WSAuthExpiringEvent
|
||||
| WSAuthIncorrectEvent
|
||||
| WSAuthOkEvent
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import type { AbstractModrinthClient } from '../core/abstract-client'
|
||||
import type { AbstractModule } from '../core/abstract-module'
|
||||
import { ArchonBackupsV0Module } from './archon/backups/v0'
|
||||
import { ArchonBackupsV1Module } from './archon/backups/v1'
|
||||
import { ArchonContentV0Module } from './archon/content/v0'
|
||||
import { ArchonContentV1Module } from './archon/content/v1'
|
||||
import { ArchonOptionsV1Module } from './archon/options/v1'
|
||||
import { ArchonPropertiesV1Module } from './archon/properties/v1'
|
||||
import { ArchonServersV0Module } from './archon/servers/v0'
|
||||
import { ArchonServersV1Module } from './archon/servers/v1'
|
||||
import { ISO3166Module } from './iso3166'
|
||||
import { KyrosContentV1Module } from './kyros/content/v1'
|
||||
import { KyrosFilesV0Module } from './kyros/files/v0'
|
||||
import { LabrinthVersionsV3Module } from './labrinth'
|
||||
import { LabrinthVersionsV2Module, LabrinthVersionsV3Module } from './labrinth'
|
||||
import { LabrinthBillingInternalModule } from './labrinth/billing/internal'
|
||||
import { LabrinthCollectionsModule } from './labrinth/collections'
|
||||
import { LabrinthProjectsV2Module } from './labrinth/projects/v2'
|
||||
@@ -17,6 +19,9 @@ import { LabrinthStateModule } from './labrinth/state'
|
||||
import { LabrinthTechReviewInternalModule } from './labrinth/tech-review/internal'
|
||||
import { LabrinthThreadsV3Module } from './labrinth/threads/v3'
|
||||
import { LabrinthUsersV2Module } from './labrinth/users/v2'
|
||||
import { LauncherMetaManifestV0Module } from './launcher-meta/v0'
|
||||
import { PaperVersionsV3Module } from './paper/v3'
|
||||
import { PurpurVersionsV2Module } from './purpur/v2'
|
||||
|
||||
type ModuleConstructor = new (client: AbstractModrinthClient) => AbstractModule
|
||||
|
||||
@@ -30,12 +35,15 @@ type ModuleConstructor = new (client: AbstractModrinthClient) => AbstractModule
|
||||
* TODO: Better way? Probably not
|
||||
*/
|
||||
export const MODULE_REGISTRY = {
|
||||
archon_backups_v0: ArchonBackupsV0Module,
|
||||
archon_backups_v1: ArchonBackupsV1Module,
|
||||
archon_content_v0: ArchonContentV0Module,
|
||||
archon_content_v1: ArchonContentV1Module,
|
||||
archon_options_v1: ArchonOptionsV1Module,
|
||||
archon_properties_v1: ArchonPropertiesV1Module,
|
||||
archon_servers_v0: ArchonServersV0Module,
|
||||
archon_servers_v1: ArchonServersV1Module,
|
||||
iso3166_data: ISO3166Module,
|
||||
launchermeta_manifest_v0: LauncherMetaManifestV0Module,
|
||||
kyros_content_v1: KyrosContentV1Module,
|
||||
kyros_files_v0: KyrosFilesV0Module,
|
||||
labrinth_billing_internal: LabrinthBillingInternalModule,
|
||||
labrinth_collections: LabrinthCollectionsModule,
|
||||
@@ -46,7 +54,10 @@ export const MODULE_REGISTRY = {
|
||||
labrinth_tech_review_internal: LabrinthTechReviewInternalModule,
|
||||
labrinth_threads_v3: LabrinthThreadsV3Module,
|
||||
labrinth_users_v2: LabrinthUsersV2Module,
|
||||
labrinth_versions_v2: LabrinthVersionsV2Module,
|
||||
labrinth_versions_v3: LabrinthVersionsV3Module,
|
||||
paper_versions_v3: PaperVersionsV3Module,
|
||||
purpur_versions_v2: PurpurVersionsV2Module,
|
||||
} as const satisfies Record<string, ModuleConstructor>
|
||||
|
||||
export type ModuleID = keyof typeof MODULE_REGISTRY
|
||||
|
||||
65
packages/api-client/src/modules/kyros/content/v1.ts
Normal file
65
packages/api-client/src/modules/kyros/content/v1.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { AbstractModule } from '../../../core/abstract-module'
|
||||
import type { UploadHandle, UploadProgress } from '../../../types/upload'
|
||||
import type { Archon } from '../../archon/types'
|
||||
|
||||
export class KyrosContentV1Module extends AbstractModule {
|
||||
public getModuleID(): string {
|
||||
return 'kyros_content_v1'
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload addon files to a world via multipart form data
|
||||
*
|
||||
* @param worldId - World UUID
|
||||
* @param files - Files to upload as addons
|
||||
* @param options - Optional progress callback
|
||||
* @returns UploadHandle with promise, onProgress, and cancel
|
||||
*/
|
||||
public uploadAddonFile(
|
||||
worldId: string,
|
||||
files: (File | Blob)[],
|
||||
options?: {
|
||||
onProgress?: (progress: UploadProgress) => void
|
||||
},
|
||||
): UploadHandle<void> {
|
||||
const formData = new FormData()
|
||||
for (const file of files) {
|
||||
formData.append('file', file, file instanceof File ? file.name : 'file')
|
||||
}
|
||||
|
||||
return this.client.upload<void>(`/worlds/${worldId}/content/upload-addon-file`, {
|
||||
api: '',
|
||||
version: 'v1',
|
||||
formData,
|
||||
onProgress: options?.onProgress,
|
||||
useNodeAuth: true,
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /v1/worlds/:world_id/content/upload-modpack-file */
|
||||
public uploadModpackFile(
|
||||
worldId: string,
|
||||
file: File | Blob,
|
||||
properties: Archon.Content.v1.PropertiesFields,
|
||||
options?: {
|
||||
softOverride?: boolean
|
||||
onProgress?: (progress: UploadProgress) => void
|
||||
},
|
||||
): UploadHandle<void> {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file, file instanceof File ? file.name : 'file')
|
||||
formData.append('properties', JSON.stringify(properties))
|
||||
|
||||
return this.client.upload<void>(`/worlds/${worldId}/content/upload-modpack-file`, {
|
||||
api: '',
|
||||
version: 'v1',
|
||||
formData,
|
||||
params:
|
||||
options?.softOverride !== undefined
|
||||
? { soft_override: String(options.softOverride) }
|
||||
: undefined,
|
||||
onProgress: options?.onProgress,
|
||||
useNodeAuth: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export class KyrosFilesV0Module extends AbstractModule {
|
||||
): Promise<Kyros.Files.v0.DirectoryResponse> {
|
||||
return this.client.request<Kyros.Files.v0.DirectoryResponse>('/fs/list', {
|
||||
api: '',
|
||||
version: 'v0',
|
||||
version: 'modrinth/v0',
|
||||
method: 'GET',
|
||||
params: { path, page, page_size: pageSize },
|
||||
useNodeAuth: true,
|
||||
@@ -38,7 +38,7 @@ export class KyrosFilesV0Module extends AbstractModule {
|
||||
public async createFileOrFolder(path: string, type: 'file' | 'directory'): Promise<void> {
|
||||
return this.client.request<void>('/fs/create', {
|
||||
api: '',
|
||||
version: 'v0',
|
||||
version: 'modrinth/v0',
|
||||
method: 'POST',
|
||||
params: { path, type },
|
||||
headers: { 'Content-Type': 'application/octet-stream' },
|
||||
@@ -55,7 +55,7 @@ export class KyrosFilesV0Module extends AbstractModule {
|
||||
public async downloadFile(path: string): Promise<Blob> {
|
||||
return this.client.request<Blob>('/fs/download', {
|
||||
api: '',
|
||||
version: 'v0',
|
||||
version: 'modrinth/v0',
|
||||
method: 'GET',
|
||||
params: { path },
|
||||
useNodeAuth: true,
|
||||
@@ -80,7 +80,7 @@ export class KyrosFilesV0Module extends AbstractModule {
|
||||
): UploadHandle<void> {
|
||||
return this.client.upload<void>('/fs/create', {
|
||||
api: '',
|
||||
version: 'v0',
|
||||
version: 'modrinth/v0',
|
||||
file,
|
||||
params: { path, type: 'file' },
|
||||
onProgress: options?.onProgress,
|
||||
@@ -100,7 +100,7 @@ export class KyrosFilesV0Module extends AbstractModule {
|
||||
|
||||
return this.client.request<void>('/fs/update', {
|
||||
api: '',
|
||||
version: 'v0',
|
||||
version: 'modrinth/v0',
|
||||
method: 'PUT',
|
||||
params: { path },
|
||||
body: blob,
|
||||
@@ -118,7 +118,7 @@ export class KyrosFilesV0Module extends AbstractModule {
|
||||
public async moveFileOrFolder(sourcePath: string, destPath: string): Promise<void> {
|
||||
return this.client.request<void>('/fs/move', {
|
||||
api: '',
|
||||
version: 'v0',
|
||||
version: 'modrinth/v0',
|
||||
method: 'POST',
|
||||
body: { source: sourcePath, destination: destPath },
|
||||
useNodeAuth: true,
|
||||
@@ -145,7 +145,7 @@ export class KyrosFilesV0Module extends AbstractModule {
|
||||
public async deleteFileOrFolder(path: string, recursive: boolean): Promise<void> {
|
||||
return this.client.request<void>('/fs/delete', {
|
||||
api: '',
|
||||
version: 'v0',
|
||||
version: 'modrinth/v0',
|
||||
method: 'DELETE',
|
||||
params: { path, recursive },
|
||||
useNodeAuth: true,
|
||||
|
||||
@@ -7,4 +7,5 @@ export * from './state'
|
||||
export * from './tech-review/internal'
|
||||
export * from './threads/v3'
|
||||
export * from './users/v2'
|
||||
export * from './versions/v2'
|
||||
export * from './versions/v3'
|
||||
|
||||
@@ -617,6 +617,14 @@ export namespace Labrinth {
|
||||
game_versions: string[]
|
||||
loaders: string[]
|
||||
}
|
||||
|
||||
export interface GetProjectVersionsParams {
|
||||
game_versions?: string[]
|
||||
loaders?: string[]
|
||||
include_changelog?: boolean
|
||||
limit?: number
|
||||
offset?: number
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: consolidate duplicated types between v2 and v3 versions
|
||||
@@ -632,7 +640,8 @@ export namespace Labrinth {
|
||||
game_versions?: string[]
|
||||
loaders?: string[]
|
||||
include_changelog?: boolean
|
||||
apiVersion?: 2 | 3
|
||||
limit?: number
|
||||
offset?: number
|
||||
}
|
||||
|
||||
export type VersionChannel = 'release' | 'beta' | 'alpha'
|
||||
|
||||
141
packages/api-client/src/modules/labrinth/versions/v2.ts
Normal file
141
packages/api-client/src/modules/labrinth/versions/v2.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { AbstractModule } from '../../../core/abstract-module'
|
||||
import type { Labrinth } from '../types'
|
||||
|
||||
export class LabrinthVersionsV2Module extends AbstractModule {
|
||||
public getModuleID(): string {
|
||||
return 'labrinth_versions_v2'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get versions for a project (v2)
|
||||
*
|
||||
* @param id - Project ID or slug (e.g., 'sodium' or 'AANobbMI')
|
||||
* @param options - Optional query parameters to filter versions
|
||||
* @returns Promise resolving to an array of v2 versions
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const versions = await client.labrinth.versions_v2.getProjectVersions('sodium')
|
||||
* const filteredVersions = await client.labrinth.versions_v2.getProjectVersions('sodium', {
|
||||
* game_versions: ['1.20.1'],
|
||||
* loaders: ['fabric'],
|
||||
* include_changelog: false
|
||||
* })
|
||||
* console.log(versions[0].version_number)
|
||||
* ```
|
||||
*/
|
||||
public async getProjectVersions(
|
||||
id: string,
|
||||
options?: Labrinth.Versions.v2.GetProjectVersionsParams,
|
||||
): Promise<Labrinth.Versions.v2.Version[]> {
|
||||
const params: Record<string, string> = {}
|
||||
if (options?.game_versions?.length) {
|
||||
params.game_versions = JSON.stringify(options.game_versions)
|
||||
}
|
||||
if (options?.loaders?.length) {
|
||||
params.loaders = JSON.stringify(options.loaders)
|
||||
}
|
||||
if (options?.include_changelog === false) {
|
||||
params.include_changelog = 'false'
|
||||
}
|
||||
if (options?.limit != null) {
|
||||
params.limit = String(options.limit)
|
||||
}
|
||||
if (options?.offset != null) {
|
||||
params.offset = String(options.offset)
|
||||
}
|
||||
|
||||
return this.client.request<Labrinth.Versions.v2.Version[]>(`/project/${id}/version`, {
|
||||
api: 'labrinth',
|
||||
version: 2,
|
||||
method: 'GET',
|
||||
params: Object.keys(params).length > 0 ? params : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific version by ID (v2)
|
||||
*
|
||||
* @param id - Version ID
|
||||
* @returns Promise resolving to the v2 version data
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const version = await client.labrinth.versions_v2.getVersion('DXtmvS8i')
|
||||
* console.log(version.version_number)
|
||||
* ```
|
||||
*/
|
||||
public async getVersion(id: string): Promise<Labrinth.Versions.v2.Version> {
|
||||
return this.client.request<Labrinth.Versions.v2.Version>(`/version/${id}`, {
|
||||
api: 'labrinth',
|
||||
version: 2,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple versions by IDs (v2)
|
||||
*
|
||||
* @param ids - Array of version IDs
|
||||
* @returns Promise resolving to an array of v2 versions
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const versions = await client.labrinth.versions_v2.getVersions(['DXtmvS8i', 'abc123'])
|
||||
* console.log(versions[0].version_number)
|
||||
* ```
|
||||
*/
|
||||
public async getVersions(ids: string[]): Promise<Labrinth.Versions.v2.Version[]> {
|
||||
return this.client.request<Labrinth.Versions.v2.Version[]>(`/versions`, {
|
||||
api: 'labrinth',
|
||||
version: 2,
|
||||
method: 'GET',
|
||||
params: { ids: JSON.stringify(ids) },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a version from a project by version ID or number (v2)
|
||||
*
|
||||
* @param projectId - Project ID or slug
|
||||
* @param versionId - Version ID or version number
|
||||
* @returns Promise resolving to the v2 version data
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const version = await client.labrinth.versions_v2.getVersionFromIdOrNumber('sodium', 'DXtmvS8i')
|
||||
* const versionByNumber = await client.labrinth.versions_v2.getVersionFromIdOrNumber('sodium', '0.4.12')
|
||||
* ```
|
||||
*/
|
||||
public async getVersionFromIdOrNumber(
|
||||
projectId: string,
|
||||
versionId: string,
|
||||
): Promise<Labrinth.Versions.v2.Version> {
|
||||
return this.client.request<Labrinth.Versions.v2.Version>(
|
||||
`/project/${projectId}/version/${versionId}`,
|
||||
{
|
||||
api: 'labrinth',
|
||||
version: 2,
|
||||
method: 'GET',
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a version by ID (v2)
|
||||
*
|
||||
* @param versionId - Version ID
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* await client.labrinth.versions_v2.deleteVersion('DXtmvS8i')
|
||||
* ```
|
||||
*/
|
||||
public async deleteVersion(versionId: string): Promise<void> {
|
||||
return this.client.request(`/version/${versionId}`, {
|
||||
api: 'labrinth',
|
||||
version: 2,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -35,8 +35,14 @@ export class LabrinthVersionsV3Module extends AbstractModule {
|
||||
if (options?.loaders?.length) {
|
||||
params.loaders = JSON.stringify(options.loaders)
|
||||
}
|
||||
if (options?.include_changelog !== undefined) {
|
||||
params.include_changelog = options.include_changelog
|
||||
if (options?.include_changelog === false) {
|
||||
params.include_changelog = 'false'
|
||||
}
|
||||
if (options?.limit != null) {
|
||||
params.limit = String(options.limit)
|
||||
}
|
||||
if (options?.offset != null) {
|
||||
params.offset = String(options.offset)
|
||||
}
|
||||
|
||||
return this.client.request<Labrinth.Versions.v3.Version[]>(`/project/${id}/version`, {
|
||||
|
||||
19
packages/api-client/src/modules/launcher-meta/types.ts
Normal file
19
packages/api-client/src/modules/launcher-meta/types.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export namespace LauncherMeta {
|
||||
export namespace Manifest {
|
||||
export namespace v0 {
|
||||
export type LoaderVersion = {
|
||||
id: string
|
||||
stable: boolean
|
||||
}
|
||||
|
||||
export type GameVersionEntry = {
|
||||
id: string
|
||||
loaders: LoaderVersion[]
|
||||
}
|
||||
|
||||
export type Manifest = {
|
||||
gameVersions: GameVersionEntry[]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
packages/api-client/src/modules/launcher-meta/v0.ts
Normal file
23
packages/api-client/src/modules/launcher-meta/v0.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { $fetch } from 'ofetch'
|
||||
|
||||
import { AbstractModule } from '../../core/abstract-module'
|
||||
import type { LauncherMeta } from './types'
|
||||
|
||||
export type { LauncherMeta } from './types'
|
||||
|
||||
const BASE_URL = 'https://launcher-meta.modrinth.com'
|
||||
|
||||
export class LauncherMetaManifestV0Module extends AbstractModule {
|
||||
public getModuleID(): string {
|
||||
return 'launchermeta_manifest_v0'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the loader manifest for a given loader platform.
|
||||
*
|
||||
* @param loader - Loader platform (fabric, forge, quilt, neo)
|
||||
*/
|
||||
public async getManifest(loader: string): Promise<LauncherMeta.Manifest.v0.Manifest> {
|
||||
return $fetch<LauncherMeta.Manifest.v0.Manifest>(`${BASE_URL}/${loader}/v0/manifest.json`)
|
||||
}
|
||||
}
|
||||
9
packages/api-client/src/modules/paper/types.ts
Normal file
9
packages/api-client/src/modules/paper/types.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export namespace Paper {
|
||||
export namespace Versions {
|
||||
export namespace v3 {
|
||||
export type VersionBuilds = {
|
||||
builds: number[]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
packages/api-client/src/modules/paper/v3.ts
Normal file
25
packages/api-client/src/modules/paper/v3.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { $fetch } from 'ofetch'
|
||||
|
||||
import { AbstractModule } from '../../core/abstract-module'
|
||||
import type { Paper } from './types'
|
||||
|
||||
export type { Paper } from './types'
|
||||
|
||||
const BASE_URL = 'https://fill.papermc.io/v3'
|
||||
|
||||
export class PaperVersionsV3Module extends AbstractModule {
|
||||
public getModuleID(): string {
|
||||
return 'paper_versions_v3'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available Paper builds for a Minecraft version.
|
||||
*
|
||||
* @param mcVersion - Minecraft version (e.g. "1.21.4")
|
||||
*/
|
||||
public async getBuilds(mcVersion: string): Promise<Paper.Versions.v3.VersionBuilds> {
|
||||
return $fetch<Paper.Versions.v3.VersionBuilds>(
|
||||
`${BASE_URL}/projects/paper/versions/${mcVersion}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
11
packages/api-client/src/modules/purpur/types.ts
Normal file
11
packages/api-client/src/modules/purpur/types.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export namespace Purpur {
|
||||
export namespace Versions {
|
||||
export namespace v2 {
|
||||
export type VersionBuilds = {
|
||||
builds: {
|
||||
all: string[]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
packages/api-client/src/modules/purpur/v2.ts
Normal file
23
packages/api-client/src/modules/purpur/v2.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { $fetch } from 'ofetch'
|
||||
|
||||
import { AbstractModule } from '../../core/abstract-module'
|
||||
import type { Purpur } from './types'
|
||||
|
||||
export type { Purpur } from './types'
|
||||
|
||||
const BASE_URL = 'https://api.purpurmc.org/v2'
|
||||
|
||||
export class PurpurVersionsV2Module extends AbstractModule {
|
||||
public getModuleID(): string {
|
||||
return 'purpur_versions_v2'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available Purpur builds for a Minecraft version.
|
||||
*
|
||||
* @param mcVersion - Minecraft version (e.g. "1.21.4")
|
||||
*/
|
||||
public async getBuilds(mcVersion: string): Promise<Purpur.Versions.v2.VersionBuilds> {
|
||||
return $fetch<Purpur.Versions.v2.VersionBuilds>(`${BASE_URL}/purpur/${mcVersion}`)
|
||||
}
|
||||
}
|
||||
@@ -2,3 +2,6 @@ export * from './archon/types'
|
||||
export * from './iso3166/types'
|
||||
export * from './kyros/types'
|
||||
export * from './labrinth/types'
|
||||
export * from './launcher-meta/types'
|
||||
export * from './paper/types'
|
||||
export * from './purpur/types'
|
||||
|
||||
@@ -13,27 +13,32 @@ import { XHRUploadClient } from './xhr-upload-client'
|
||||
*
|
||||
* This provides cross-request persistence in SSR while also working in client-side.
|
||||
* State is shared between requests in the same Nuxt context.
|
||||
*
|
||||
* Note: useState must be called during initialization (in setup context) and cached,
|
||||
* as it won't work during async operations when the Nuxt context may be lost.
|
||||
*/
|
||||
export class NuxtCircuitBreakerStorage implements CircuitBreakerStorage {
|
||||
private getState(): Map<string, CircuitBreakerState> {
|
||||
private state: Map<string, CircuitBreakerState>
|
||||
|
||||
constructor() {
|
||||
// @ts-expect-error - useState is provided by Nuxt runtime
|
||||
const state = useState<Map<string, CircuitBreakerState>>(
|
||||
const stateRef = useState<Map<string, CircuitBreakerState>>(
|
||||
'circuit-breaker-state',
|
||||
() => new Map(),
|
||||
)
|
||||
return state.value
|
||||
this.state = stateRef.value
|
||||
}
|
||||
|
||||
get(key: string): CircuitBreakerState | undefined {
|
||||
return this.getState().get(key)
|
||||
return this.state.get(key)
|
||||
}
|
||||
|
||||
set(key: string, state: CircuitBreakerState): void {
|
||||
this.getState().set(key, state)
|
||||
this.state.set(key, state)
|
||||
}
|
||||
|
||||
clear(key: string): void {
|
||||
this.getState().delete(key)
|
||||
this.state.delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,8 +69,16 @@ export class TauriModrinthClient extends XHRUploadClient {
|
||||
|
||||
let fullUrl = url
|
||||
if (options.params) {
|
||||
const queryParams = new URLSearchParams(options.params as Record<string, string>).toString()
|
||||
fullUrl = `${url}?${queryParams}`
|
||||
const filteredParams: Record<string, string> = {}
|
||||
for (const [key, value] of Object.entries(options.params)) {
|
||||
if (value !== undefined && value !== null) {
|
||||
filteredParams[key] = String(value)
|
||||
}
|
||||
}
|
||||
const queryString = new URLSearchParams(filteredParams).toString()
|
||||
if (queryString) {
|
||||
fullUrl = `${url}?${queryString}`
|
||||
}
|
||||
}
|
||||
|
||||
const response = await tauriFetch(fullUrl, {
|
||||
|
||||
@@ -14,7 +14,7 @@ export class GenericWebSocketClient extends AbstractWebSocketClient {
|
||||
|
||||
async connect(serverId: string, auth: Archon.Websocket.v0.WSAuth): Promise<void> {
|
||||
if (this.connections.has(serverId)) {
|
||||
this.disconnect(serverId)
|
||||
this.closeConnection(serverId)
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -57,14 +57,30 @@ export class GenericWebSocketClient extends AbstractWebSocketClient {
|
||||
}
|
||||
|
||||
ws.onclose = (event) => {
|
||||
console.debug(`[WebSocket] Closed for server ${serverId}:`, {
|
||||
code: event.code,
|
||||
reason: event.reason,
|
||||
wasClean: event.wasClean,
|
||||
})
|
||||
if (event.code !== NORMAL_CLOSURE) {
|
||||
this.scheduleReconnect(serverId, auth)
|
||||
}
|
||||
}
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error(`[WebSocket] Error for server ${serverId}:`, error)
|
||||
reject(new Error(`WebSocket connection failed for server ${serverId}`))
|
||||
ws.onerror = (event) => {
|
||||
const url = ws.url
|
||||
const readyState = ws.readyState
|
||||
console.error(`[WebSocket] Error for server ${serverId}:`, {
|
||||
url,
|
||||
readyState,
|
||||
readyStateLabel: ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'][readyState],
|
||||
type: (event as Event).type,
|
||||
})
|
||||
reject(
|
||||
new Error(
|
||||
`WebSocket connection failed for server ${serverId} (readyState: ${readyState})`,
|
||||
),
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
@@ -73,6 +89,16 @@ export class GenericWebSocketClient extends AbstractWebSocketClient {
|
||||
}
|
||||
|
||||
disconnect(serverId: string): void {
|
||||
this.closeConnection(serverId)
|
||||
|
||||
this.emitter.all.forEach((_handlers, type) => {
|
||||
if (type.toString().startsWith(`${serverId}:`)) {
|
||||
this.emitter.all.delete(type)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private closeConnection(serverId: string): void {
|
||||
const connection = this.connections.get(serverId)
|
||||
if (!connection) return
|
||||
|
||||
@@ -88,12 +114,6 @@ export class GenericWebSocketClient extends AbstractWebSocketClient {
|
||||
connection.socket.close(NORMAL_CLOSURE, 'Client disconnecting')
|
||||
}
|
||||
|
||||
this.emitter.all.forEach((_handlers, type) => {
|
||||
if (type.toString().startsWith(`${serverId}:`)) {
|
||||
this.emitter.all.delete(type)
|
||||
}
|
||||
})
|
||||
|
||||
this.connections.delete(serverId)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user