Merge remote-tracking branch 'origin/main' into cal/hosting-server-instances

# Conflicts:
#	apps/app-frontend/src/pages/Browse.vue
#	apps/app-frontend/src/pages/hosting/manage/Index.vue
#	apps/app-frontend/src/pages/instance/Index.vue
#	apps/app-frontend/src/pages/project/Index.vue
#	apps/app-frontend/src/routes.js
#	apps/frontend/src/locales/en-US/index.json
#	apps/frontend/src/middleware/project.global.ts
#	apps/frontend/src/pages/[type]/[project].vue
#	apps/frontend/src/pages/discover/[type]/index.vue
#	apps/frontend/src/pages/organization/[organization].vue
#	apps/frontend/src/pages/user/[user].vue
#	packages/ui/src/components/base/FloatingActionBar.vue
#	packages/ui/src/components/base/PageHeader.vue
#	packages/ui/src/components/base/StackedAdmonitions.vue
#	packages/ui/src/components/base/TeleportOverflowMenu.vue
#	packages/ui/src/components/base/index.ts
#	packages/ui/src/components/project/ProjectHeader.vue
#	packages/ui/src/components/servers/server-header/ServerManageHeader.vue
#	packages/ui/src/components/servers/server-header/index.ts
#	packages/ui/src/composables/index.ts
#	packages/ui/src/layouts/shared/browse-tab/composables/use-browse-search.ts
#	packages/ui/src/layouts/shared/browse-tab/header.vue
#	packages/ui/src/layouts/shared/browse-tab/layout.vue
#	packages/ui/src/layouts/shared/content-tab/components/ContentModpackCard.vue
#	packages/ui/src/layouts/shared/content-tab/layout.vue
#	packages/ui/src/layouts/shared/installation-settings/components/ContentDiffModal.vue
#	packages/ui/src/layouts/shared/installation-settings/providers/installation-settings.ts
#	packages/ui/src/layouts/wrapped/hosting/manage/[id]/index.vue
#	packages/ui/src/locales/cs-CZ/index.json
#	packages/ui/src/locales/de-CH/index.json
#	packages/ui/src/locales/de-DE/index.json
#	packages/ui/src/locales/es-419/index.json
#	packages/ui/src/locales/es-ES/index.json
#	packages/ui/src/locales/fr-FR/index.json
#	packages/ui/src/locales/hu-HU/index.json
#	packages/ui/src/locales/id-ID/index.json
#	packages/ui/src/locales/it-IT/index.json
#	packages/ui/src/locales/ja-JP/index.json
#	packages/ui/src/locales/ko-KR/index.json
#	packages/ui/src/locales/ms-MY/index.json
#	packages/ui/src/locales/nl-NL/index.json
#	packages/ui/src/locales/pl-PL/index.json
#	packages/ui/src/locales/pt-BR/index.json
#	packages/ui/src/locales/ru-RU/index.json
#	packages/ui/src/locales/sv-SE/index.json
#	packages/ui/src/locales/tr-TR/index.json
#	packages/ui/src/locales/uk-UA/index.json
#	packages/ui/src/locales/vi-VN/index.json
#	packages/ui/src/locales/zh-CN/index.json
#	packages/ui/src/locales/zh-TW/index.json
#	packages/ui/src/stories/base/PageHeader.stories.ts
#	packages/ui/src/utils/file-extensions.ts
This commit is contained in:
Calum H. (IMB11)
2026-08-03 18:39:37 +01:00
1294 changed files with 151547 additions and 35420 deletions
+24
View File
@@ -0,0 +1,24 @@
import { build } from 'esbuild'
import dts from 'unplugin-dts/esbuild'
await build({
entryPoints: ['src/index.ts'],
outdir: 'dist',
entryNames: '[name]',
bundle: true,
format: 'esm',
platform: 'neutral',
target: 'es2020',
minify: true,
sourcemap: true,
legalComments: 'none',
external: ['ofetch', 'mitt', '@tauri-apps/plugin-http'],
plugins: [
dts({
bundleTypes: true,
outDirs: 'dist',
processor: 'ts',
tsconfigPath: './tsconfig.build.json',
}),
],
})
+4 -2
View File
@@ -34,7 +34,7 @@
},
"scripts": {
"clean": "node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
"build": "pnpm run clean && esbuild src/index.ts --bundle --format=esm --platform=neutral --target=es2020 --minify --legal-comments=none --outfile=dist/index.js --external:ofetch --external:mitt --external:@tauri-apps/plugin-http && tsc -p tsconfig.build.json",
"build": "pnpm run clean && node esbuild.config.mjs",
"prepare": "pnpm run build",
"lint": "eslint . && prettier --check .",
"fix": "eslint . --fix && prettier --write ."
@@ -44,10 +44,12 @@
"ofetch": "^1.4.1"
},
"devDependencies": {
"@microsoft/api-extractor": "^7.58.9",
"@modrinth/tooling-config": "workspace:*",
"@tauri-apps/plugin-http": "^2.0.0",
"esbuild": "0.27.2",
"typescript": "^5.9.3"
"typescript": "^5.9.3",
"unplugin-dts": "^1.0.3"
},
"peerDependencies": {
"@tauri-apps/plugin-http": "^2.0.0"
@@ -43,6 +43,7 @@ export abstract class AbstractModrinthClient extends AbstractUploadClient {
public readonly launchermeta!: InferredClientModules['launchermeta']
public readonly paper!: InferredClientModules['paper']
public readonly purpur!: InferredClientModules['purpur']
public readonly sharedinstances!: InferredClientModules['sharedinstances']
constructor(config: ClientConfig) {
super()
@@ -50,6 +51,7 @@ export abstract class AbstractModrinthClient extends AbstractUploadClient {
timeout: 10000,
labrinthBaseUrl: 'https://api.modrinth.com',
archonBaseUrl: 'https://archon.modrinth.com',
sharedInstancesBaseUrl: 'https://shared-instances.modrinth.com',
...config,
}
this.features = config.features ?? []
@@ -123,6 +125,8 @@ export abstract class AbstractModrinthClient extends AbstractUploadClient {
baseUrl = this.resolveBaseUrl(this.config.labrinthBaseUrl!)
} else if (options.api === 'archon') {
baseUrl = this.resolveBaseUrl(this.config.archonBaseUrl!)
} else if (options.api === 'sharedinstances') {
baseUrl = this.resolveBaseUrl(this.config.sharedInstancesBaseUrl!)
} else {
baseUrl = options.api
}
@@ -170,6 +174,8 @@ export abstract class AbstractModrinthClient extends AbstractUploadClient {
baseUrl = this.resolveBaseUrl(this.config.labrinthBaseUrl!)
} else if (options.api === 'archon') {
baseUrl = this.resolveBaseUrl(this.config.archonBaseUrl!)
} else if (options.api === 'sharedinstances') {
baseUrl = this.resolveBaseUrl(this.config.sharedInstancesBaseUrl!)
} else {
baseUrl = options.api
}
+14
View File
@@ -19,11 +19,14 @@ import { KyrosLogsV1Module } from './kyros/logs/v1'
import { KyrosUploadSessionsV1Module } from './kyros/upload-sessions/v1'
import { LabrinthVersionsV2Module, LabrinthVersionsV3Module } from './labrinth'
import { LabrinthAffiliateInternalModule } from './labrinth/affiliate/internal'
import { LabrinthAnalyticsInternalModule } from './labrinth/analytics/internal'
import { LabrinthAnalyticsV3Module } from './labrinth/analytics/v3'
import { LabrinthAttributionInternalModule } from './labrinth/attribution/internal'
import { LabrinthAuthInternalModule } from './labrinth/auth/internal'
import { LabrinthAuthV2Module } from './labrinth/auth/v2'
import { LabrinthBillingInternalModule } from './labrinth/billing/internal'
import { LabrinthBlockedUsersInternalModule } from './labrinth/blocked-users/internal'
import { LabrinthBlockedUsersV3Module } from './labrinth/blocked-users/v3'
import { LabrinthCampaignInternalModule } from './labrinth/campaign/internal'
import { LabrinthCollectionsModule } from './labrinth/collections'
import { LabrinthContentV3Module } from './labrinth/content/v3'
@@ -57,6 +60,10 @@ import { MclogsInsightsV1Module } from './mclogs/insights/v1'
import { MclogsLogsV1Module } from './mclogs/logs/v1'
import { PaperVersionsV3Module } from './paper/v3'
import { PurpurVersionsV2Module } from './purpur/v2'
import { SharedInstancesInstancesV1Module } from './shared-instances/instances/v1'
import { SharedInstancesInvitesV1Module } from './shared-instances/invites/v1'
import { SharedInstancesModerationV1Module } from './shared-instances/moderation/v1'
import { SharedInstancesUsersV1Module } from './shared-instances/users/v1'
type ModuleConstructor = new (client: AbstractModrinthClient) => AbstractModule
@@ -91,11 +98,14 @@ export const MODULE_REGISTRY = {
kyros_logs_v1: KyrosLogsV1Module,
kyros_upload_sessions_v1: KyrosUploadSessionsV1Module,
labrinth_affiliate_internal: LabrinthAffiliateInternalModule,
labrinth_analytics_internal: LabrinthAnalyticsInternalModule,
labrinth_analytics_v3: LabrinthAnalyticsV3Module,
labrinth_auth_internal: LabrinthAuthInternalModule,
labrinth_auth_v2: LabrinthAuthV2Module,
labrinth_attribution_internal: LabrinthAttributionInternalModule,
labrinth_billing_internal: LabrinthBillingInternalModule,
labrinth_blocked_users_internal: LabrinthBlockedUsersInternalModule,
labrinth_blocked_users_v3: LabrinthBlockedUsersV3Module,
labrinth_campaign_internal: LabrinthCampaignInternalModule,
labrinth_collections: LabrinthCollectionsModule,
labrinth_content_v3: LabrinthContentV3Module,
@@ -128,6 +138,10 @@ export const MODULE_REGISTRY = {
labrinth_versions_v3: LabrinthVersionsV3Module,
paper_versions_v3: PaperVersionsV3Module,
purpur_versions_v2: PurpurVersionsV2Module,
sharedinstances_invites_v1: SharedInstancesInvitesV1Module,
sharedinstances_instances_v1: SharedInstancesInstancesV1Module,
sharedinstances_moderation_v1: SharedInstancesModerationV1Module,
sharedinstances_users_v1: SharedInstancesUsersV1Module,
} as const satisfies Record<string, ModuleConstructor>
export type ModuleID = keyof typeof MODULE_REGISTRY
@@ -0,0 +1,51 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Labrinth } from '../types'
export class LabrinthAnalyticsInternalModule extends AbstractModule {
public getModuleID(): string {
return 'labrinth_analytics_internal'
}
/**
* Create an analytics event.
* POST /_internal/analytics-event
*/
public async createEvent(
data: Labrinth.Analytics.Internal.AnalyticsEventUpsert,
): Promise<Labrinth.Analytics.v3.AnalyticsEvent> {
return this.client.request<Labrinth.Analytics.v3.AnalyticsEvent>('/analytics-event', {
api: 'labrinth',
version: 'internal',
method: 'POST',
body: data,
})
}
/**
* Edit an analytics event.
* PATCH /_internal/analytics-event/{id}
*/
public async editEvent(
id: Labrinth.Analytics.v3.AnalyticsEventId,
data: Labrinth.Analytics.Internal.AnalyticsEventUpsert,
): Promise<Labrinth.Analytics.v3.AnalyticsEvent> {
return this.client.request<Labrinth.Analytics.v3.AnalyticsEvent>(`/analytics-event/${id}`, {
api: 'labrinth',
version: 'internal',
method: 'PATCH',
body: data,
})
}
/**
* Delete an analytics event.
* DELETE /_internal/analytics-event/{id}
*/
public async deleteEvent(id: Labrinth.Analytics.v3.AnalyticsEventId): Promise<void> {
return this.client.request<void>(`/analytics-event/${id}`, {
api: 'labrinth',
version: 'internal',
method: 'DELETE',
})
}
}
@@ -70,47 +70,4 @@ export class LabrinthAnalyticsV3Module extends AbstractModule {
method: 'GET',
})
}
/**
* Create an analytics event.
* POST /v3/analytics-event
*/
public async createEvent(
data: Labrinth.Analytics.v3.AnalyticsEventUpsert,
): Promise<Labrinth.Analytics.v3.AnalyticsEvent> {
return this.client.request<Labrinth.Analytics.v3.AnalyticsEvent>('/analytics-event', {
api: 'labrinth',
version: 3,
method: 'POST',
body: data,
})
}
/**
* Edit an analytics event.
* PATCH /v3/analytics-event/{id}
*/
public async editEvent(
id: Labrinth.Analytics.v3.AnalyticsEventId,
data: Labrinth.Analytics.v3.AnalyticsEventUpsert,
): Promise<Labrinth.Analytics.v3.AnalyticsEvent> {
return this.client.request<Labrinth.Analytics.v3.AnalyticsEvent>(`/analytics-event/${id}`, {
api: 'labrinth',
version: 3,
method: 'PATCH',
body: data,
})
}
/**
* Delete an analytics event.
* DELETE /v3/analytics-event/{id}
*/
public async deleteEvent(id: Labrinth.Analytics.v3.AnalyticsEventId): Promise<void> {
return this.client.request<void>(`/analytics-event/${id}`, {
api: 'labrinth',
version: 3,
method: 'DELETE',
})
}
}
@@ -64,6 +64,38 @@ export class LabrinthAttributionInternalModule extends AbstractModule {
})
}
/**
* Delete attribution groups and all files inside them.
* DELETE /_internal/attribution/group
*
* @param groupIds - The base62 attribution group ids (as returned from listProjectAttribution).
*/
public async deleteGroups(groupIds: string[]): Promise<void> {
const body: Labrinth.Attribution.Internal.DeleteGroupsRequest = { groups: groupIds }
return this.client.request<void>('/attribution/group', {
api: 'labrinth',
version: 'internal',
method: 'DELETE',
body,
})
}
/**
* Delete all attribution groups and files for a project.
* DELETE /_internal/attribution/all-groups
*/
public async deleteAllGroups(projectId: string): Promise<void> {
const body: Labrinth.Attribution.Internal.DeleteAllGroupsRequest = {
project_id: projectId,
}
return this.client.request<void>('/attribution/all-groups', {
api: 'labrinth',
version: 'internal',
method: 'DELETE',
body,
})
}
/**
* Reassign a file (by sha1) to another attribution group within the same project.
* POST /_internal/attribution/assign
@@ -100,4 +132,21 @@ export class LabrinthAttributionInternalModule extends AbstractModule {
body,
})
}
/**
* Scan a file for attribution information.
* POST /_internal/attribution/file/{file_id}/scan
*
* @param fileId - The file ID to scan.
*/
public async scanFile(fileId: string): Promise<Labrinth.Attribution.Internal.FileScanResponse> {
return this.client.request<Labrinth.Attribution.Internal.FileScanResponse>(
`/attribution/file/${fileId}/scan`,
{
api: 'labrinth',
version: 'internal',
method: 'POST',
},
)
}
}
@@ -0,0 +1,25 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Labrinth } from '../types'
export class LabrinthBlockedUsersInternalModule extends AbstractModule {
public getModuleID(): string {
return 'labrinth_blocked_users_internal'
}
/**
* Check whether one user has blocked another.
*/
public async getStatus(
userId: string,
targetId: string,
): Promise<Labrinth.BlockedUsers.Internal.BlockStatus> {
return this.client.request<Labrinth.BlockedUsers.Internal.BlockStatus>(
`/block/${encodeURIComponent(userId)}/${encodeURIComponent(targetId)}`,
{
api: 'labrinth',
version: 'internal',
method: 'GET',
},
)
}
}
@@ -0,0 +1,45 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Labrinth } from '../types'
export class LabrinthBlockedUsersV3Module extends AbstractModule {
public getModuleID(): string {
return 'labrinth_blocked_users_v3'
}
/**
* List the users blocked by the authenticated user.
*/
public async list(): Promise<Labrinth.BlockedUsers.v3.BlockedUserId[]> {
return this.client.request<Labrinth.BlockedUsers.v3.BlockedUserId[]>('/blocks', {
api: 'labrinth',
version: 3,
method: 'GET',
})
}
/**
* Block a user.
*
* @param idOrUsername - The target user's ID or username
*/
public async block(idOrUsername: string): Promise<void> {
return this.client.request(`/block/${encodeURIComponent(idOrUsername)}`, {
api: 'labrinth',
version: 3,
method: 'POST',
})
}
/**
* Unblock a user.
*
* @param idOrUsername - The target user's ID or username
*/
public async unblock(idOrUsername: string): Promise<void> {
return this.client.request(`/block/${encodeURIComponent(idOrUsername)}`, {
api: 'labrinth',
version: 3,
method: 'DELETE',
})
}
}
@@ -21,7 +21,7 @@ function buildImageQueryParams(
params.thread_message_id = target.thread_message_id
break
case 'report':
params.report_id = target.report_id
if (target.report_id) params.report_id = target.report_id
break
}
return params
@@ -1,3 +1,4 @@
export * from './analytics/internal'
export * from './analytics/v3'
export * from './attribution/internal'
export * from './auth/internal'
@@ -6,6 +6,34 @@ export class LabrinthModerationInternalModule extends AbstractModule {
return 'labrinth_moderation_internal'
}
public async getProjects(
params: Labrinth.Moderation.Internal.ProjectsRequest = {},
): Promise<Labrinth.Moderation.Internal.ProjectsResponse> {
return this.client.request<Labrinth.Moderation.Internal.ProjectsResponse>(
'/moderation/projects',
{
api: 'labrinth',
version: 'internal',
method: 'GET',
params,
},
)
}
public async getProjectIds(
params: Omit<Labrinth.Moderation.Internal.ProjectsRequest, 'count' | 'offset'> = {},
): Promise<Labrinth.Moderation.Internal.ProjectIdsResponse> {
return this.client.request<Labrinth.Moderation.Internal.ProjectIdsResponse>(
'/moderation/projects/ids',
{
api: 'labrinth',
version: 'internal',
method: 'GET',
params,
},
)
}
public async acquireLock(
projectId: string,
): Promise<Labrinth.Moderation.Internal.LockAcquireResponse> {
@@ -1,5 +1,4 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { UploadHandle } from '../../../types/upload'
import type { Labrinth } from '../types'
export class LabrinthOAuthInternalModule extends AbstractModule {
@@ -45,14 +44,15 @@ export class LabrinthOAuthInternalModule extends AbstractModule {
* @returns Promise resolving to an array of OAuth clients
*/
public async getApps(ids: string[]): Promise<Labrinth.OAuth.Internal.OAuthClient[]> {
return this.client.request<Labrinth.OAuth.Internal.OAuthClient[]>(
`/oauth/apps?ids=${encodeURIComponent(JSON.stringify(ids))}`,
{
api: 'labrinth',
version: 'internal',
method: 'GET',
},
)
if (ids.length === 0) {
return []
}
// bulk `/oauth/apps` is broken on backend, fetch by id instead
// TODO: Remove this once the backend is fixed
const results = await Promise.all(ids.map((id) => this.getApp(id).catch(() => null)))
return results.filter((app): app is Labrinth.OAuth.Internal.OAuthClient => app !== null)
}
/**
@@ -109,14 +109,14 @@ export class LabrinthOAuthInternalModule extends AbstractModule {
* @param id - The OAuth client ID
* @param file - The icon file
* @param ext - The file extension (e.g. 'png', 'jpeg')
* @returns UploadHandle for progress tracking and cancellation
*/
public uploadAppIcon(id: string, file: File | Blob, ext: string): UploadHandle<void> {
return this.client.upload<void>(`/oauth/app/${id}/icon`, {
public async uploadAppIcon(id: string, file: File | Blob, ext: string): Promise<void> {
return this.client.request(`/oauth/app/${id}/icon`, {
api: 'labrinth',
version: 'internal',
file,
method: 'PATCH',
params: { ext },
body: file,
})
}
@@ -73,6 +73,30 @@ export class LabrinthProjectsV3Module extends AbstractModule {
})
}
/**
* Search projects (v3)
*
* @param params - Search parameters
* @returns Promise resolving to v3 search results
*/
public async search(
params: Labrinth.Search.SearchParams,
options?: {
headers?: Record<string, string>
},
): Promise<Labrinth.Search.v3.SearchResults> {
return this.client.request<Labrinth.Search.v3.SearchResults>(`/search`, {
api: 'labrinth',
version: 3,
method: 'GET',
headers: options?.headers,
params: {
...params,
facets: params.facets ? JSON.stringify(params.facets) : undefined,
},
})
}
/**
* Edit a project (v3)
*
@@ -100,9 +100,15 @@ export class LabrinthTechReviewInternalModule extends AbstractModule {
*/
public async updateIssueDetail(
detailId: string,
data: Labrinth.TechReview.Internal.UpdateIssueRequest,
data: Labrinth.TechReview.Internal.UpdateIssueDetailRequest,
): Promise<void> {
return this.client.request<void>(`/moderation/tech-review/issue-detail/${detailId}`, {
return this.updateIssueDetails([{ detail_id: detailId, verdict: data.verdict }])
}
public async updateIssueDetails(
data: Labrinth.TechReview.Internal.UpdateIssueRequest[],
): Promise<void> {
return this.client.request<void>('/moderation/tech-review/issue-detail', {
api: 'labrinth',
version: 'internal',
method: 'PATCH',
@@ -110,6 +116,45 @@ export class LabrinthTechReviewInternalModule extends AbstractModule {
})
}
public async updateGlobalIssueDetails(
data: Labrinth.TechReview.Internal.UpdateGlobalIssueRequest[],
): Promise<void> {
return this.client.request<void>('/moderation/tech-review/global-issue-detail', {
api: 'labrinth',
version: 'internal',
method: 'POST',
body: data,
})
}
public async searchGlobalIssueDetails(
params: Labrinth.TechReview.Internal.SearchGlobalIssueDetailsRequest,
): Promise<Labrinth.TechReview.Internal.SearchGlobalIssueDetailsResponse> {
return this.client.request<Labrinth.TechReview.Internal.SearchGlobalIssueDetailsResponse>(
'/moderation/tech-review/global-issue-detail/search',
{
api: 'labrinth',
version: 'internal',
method: 'POST',
body: params,
},
)
}
public async getGlobalIssueDetail(
params: Labrinth.TechReview.Internal.GetGlobalIssueDetailRequest,
): Promise<Labrinth.TechReview.Internal.GetGlobalIssueDetailResponse> {
return this.client.request<Labrinth.TechReview.Internal.GetGlobalIssueDetailResponse>(
'/moderation/tech-review/global-issue-detail/local-traces',
{
api: 'labrinth',
version: 'internal',
method: 'POST',
body: params,
},
)
}
public async submitProject(
projectId: string,
data: Labrinth.TechReview.Internal.SubmitProjectRequest,
+209 -13
View File
@@ -77,6 +77,7 @@ export namespace Labrinth {
status: SubscriptionStatus
created: string
metadata?: SubscriptionMetadata
next_charge_tax_amount?: number | null
}
export type SubscriptionMetadata =
@@ -373,6 +374,14 @@ export namespace Labrinth {
date_created: string
}
export type OverrideFileOnPlatform = {
file_path: string
sha1: string
version_id: string
platform_version_id: string
platform_project_id: string
}
export type AttributionGroup = {
id: string
flame_project: FlameProject | null
@@ -381,12 +390,21 @@ export namespace Labrinth {
attributed_by: string | null
files: AttributionFile[]
versions: AttributionVersionInfo[]
override_files_on_platform: OverrideFileOnPlatform[]
}
export type UpdateGroupRequest = {
attribution: AttributionResolution
}
export type DeleteGroupsRequest = {
groups: string[]
}
export type DeleteAllGroupsRequest = {
project_id: string
}
export type AssignRequest = {
sha1: string
target_group_id: number
@@ -397,6 +415,12 @@ export namespace Labrinth {
sha1: string
project_id: string
}
export type FileScanResponse = {
new_attribution_groups: number
new_attribution_files: number
scanned_file_names: string[]
}
}
}
@@ -418,7 +442,7 @@ export namespace Labrinth {
| { context: 'project'; project_id: string }
| { context: 'version'; version_id: string }
| { context: 'thread_message'; thread_message_id: string }
| { context: 'report'; report_id: string }
| { context: 'report'; report_id: string | null }
)
export type UploadedImageFor<C extends ImageUploadContext> = Extract<
@@ -434,11 +458,21 @@ export namespace Labrinth {
| { context: 'project'; project_id: string }
| { context: 'version'; version_id: string }
| { context: 'thread_message'; thread_message_id: string }
| { context: 'report'; report_id: string }
| { context: 'report'; report_id?: string }
}
}
export namespace Analytics {
export namespace Internal {
export type AnalyticsEventUpsert = {
announcement_url: string | null
for_metric_kind: v3.AnalyticsEventMetricKind[] | null
title: string
ends: string
starts: string
}
}
export namespace v3 {
export type AnalyticsEventId = number
export type AnalyticsEventMetricKind = 'views' | 'revenue' | 'downloads' | 'playtime'
@@ -452,14 +486,6 @@ export namespace Labrinth {
starts: string
}
export type AnalyticsEventUpsert = {
announcement_url: string | null
for_metric_kind: AnalyticsEventMetricKind[] | null
title: string
ends: string
starts: string
}
export type FetchRequest = {
time_range: TimeRange
return_metrics: ReturnMetrics
@@ -740,6 +766,7 @@ export namespace Labrinth {
email: string
challenge: string
sign_up_newsletter?: boolean
account_consent?: boolean
}
export type CreateAccountResponse = {
@@ -757,6 +784,7 @@ export namespace Labrinth {
state: string
challenge: string
sign_up_newsletter: boolean
account_consent?: boolean
}
export type CreateOAuthAccountResponse = {
@@ -1333,6 +1361,7 @@ export namespace Labrinth {
}
export type VersionFile = {
id?: string
hashes: VersionFileHash
url: string
filename: string
@@ -1454,6 +1483,7 @@ export namespace Labrinth {
}
export interface VersionFile {
id?: string
hashes: VersionFileHash
url: string
filename: string
@@ -1635,6 +1665,8 @@ export namespace Labrinth {
allow_friend_requests?: boolean
moderation_notes?: Common.ModerationNote | null
github_id?: number
discord_id?: string
steam_id?: string
}
export type SearchUser = {
@@ -1661,6 +1693,18 @@ export namespace Labrinth {
}
}
export namespace BlockedUsers {
export namespace Internal {
export type BlockStatus = {
blocked: boolean
}
}
export namespace v3 {
export type BlockedUserId = string
}
}
export namespace ServerPing {
export namespace Internal {
export type MinecraftJavaPingRequest = {
@@ -1730,10 +1774,22 @@ export namespace Labrinth {
}
export namespace Search {
export type SearchParams = {
query?: string
offset?: string | number
index?: string
limit?: string | number
new_filters?: string
facets?: string[][]
filters?: string
version?: string
}
export namespace v2 {
export interface ResultSearchProject {
project_id: string
project_type: string
all_project_types: string[]
slug: string | null
author: string
author_id: string | null
@@ -1771,6 +1827,7 @@ export namespace Labrinth {
version_id: string
project_id: string
project_types: string[]
all_project_types: string[]
slug: string | null
author: string
author_id: string | null
@@ -1790,7 +1847,9 @@ export namespace Labrinth {
featured_gallery: string | null
color: number | null
loaders: string[]
project_loader_fields?: Record<string, unknown[]>
project_loader_fields?: Record<string, unknown[]> & {
environment?: Projects.v3.Environment[]
}
minecraft_server?: Projects.v3.MinecraftServer | null
minecraft_java_server?: Projects.v3.MinecraftJavaServer | null
minecraft_bedrock_server?: Projects.v3.MinecraftBedrockServer | null
@@ -1823,6 +1882,19 @@ export namespace Labrinth {
new_status: Projects.v2.ProjectStatus
old_status: Projects.v2.ProjectStatus
}
| {
type: 'tech_review'
verdict: 'safe' | 'unsafe'
}
| {
type: 'tech_review_entered'
}
| {
type: 'tech_review_exited'
}
| {
type: 'tech_review_exit_file_deleted'
}
| {
type: 'thread_closure'
}
@@ -1869,13 +1941,14 @@ export namespace Labrinth {
export namespace Reports {
export namespace v3 {
export type ItemType = 'project' | 'version' | 'user' | 'unknown'
export type ItemType = 'project' | 'version' | 'user' | 'shared-instance' | 'unknown'
export type Report = {
id: string
report_type: string
item_id: string
item_type: ItemType
shared_instance_version_id?: number
reporter: string
body: string
created: string
@@ -1906,6 +1979,57 @@ export namespace Labrinth {
export namespace Moderation {
export namespace Internal {
export type Ownership =
| {
kind: 'user'
id: string
name: string
icon_url: string | null
}
| {
kind: 'organization'
id: string
name: string
icon_url: string | null
}
export type ProjectsSort = 'oldest' | 'newest' | 'most_external_deps' | 'least_external_deps'
export type ProjectsRequest = {
count?: number
offset?: number
has_external_dependencies?: boolean
exclude_technical_review?: boolean
query?: string
project_type?: string
sort?: ProjectsSort
}
export type QueueProject = {
id: string
slug: string | null
name: string
summary: string
icon_url: string | null
status: Projects.v2.ProjectStatus
requested_status: Projects.v2.ProjectStatus | null
queued: string | null
published: string
updated: string
project_types: string[]
ownership: Ownership
external_dependencies_count: number
}
export type ProjectsResponse = {
total: number
projects: QueueProject[]
}
export type ProjectIdsResponse = {
ids: string[]
}
export type LockedByUser = {
id: string
username: string
@@ -2163,7 +2287,64 @@ export namespace Labrinth {
| 'severity_desc'
export type UpdateIssueRequest = {
verdict: 'safe' | 'unsafe'
detail_id: string
verdict: DelphiReportIssueStatus
}
export type UpdateIssueDetailRequest = {
verdict: DelphiReportIssueStatus
}
export type UpdateGlobalIssueRequest = {
detail_key: string
verdict: DelphiReportIssueStatus
}
export type SearchGlobalIssueDetailsRequest = {
limit?: number
page?: number
query?: string | null
}
export type SearchGlobalIssueDetailsResponse = {
total: number
traces: GlobalIssueDetail[]
}
export type GetGlobalIssueDetailRequest = {
detail_key: string
limit?: number
after_detail_id?: string | null
}
export type GetGlobalIssueDetailResponse = {
trace: GlobalIssueDetail
next_after_detail_id: string | null
}
export type GlobalIssueDetail = {
detail_key: string
verdict: DelphiReportIssueStatus
local_trace_count: number
local_traces: GlobalIssueDetailTrace[]
}
export type GlobalIssueDetailTrace = {
detail_id: string
issue_id: string
issue_type: string
project_id: string
project_slug: string | null
project_name: string
version_id: string
version_number: string
file_id: string
file_name: string
jar: string | null
file_path: string
severity: DelphiSeverity
local_status: DelphiReportIssueStatus
effective_status: DelphiReportIssueStatus
}
export type SubmitProjectRequest = {
@@ -2225,6 +2406,8 @@ export namespace Labrinth {
decompiled_source: string | null
data: Record<string, unknown>
severity: DelphiSeverity
local_status: DelphiReportIssueStatus | null
global_status: DelphiReportIssueStatus | null
status: DelphiReportIssueStatus
}
@@ -2273,6 +2456,19 @@ export namespace Labrinth {
new_status: Projects.v2.ProjectStatus
old_status: Projects.v2.ProjectStatus
}
| {
type: 'tech_review'
verdict: 'safe' | 'unsafe'
}
| {
type: 'tech_review_entered'
}
| {
type: 'tech_review_exited'
}
| {
type: 'tech_review_exit_file_deleted'
}
| {
type: 'thread_closure'
}
@@ -168,7 +168,7 @@ export class LabrinthUsersV2Module extends AbstractModule {
*/
public async patch(
idOrUsername: string,
data: Partial<Pick<Labrinth.Users.v2.User, 'badges' | 'role'>>,
data: Partial<Pick<Labrinth.Users.v2.User, 'badges' | 'bio' | 'role' | 'username'>>,
): Promise<void> {
return this.client.request(`/user/${idOrUsername}`, {
api: 'labrinth',
@@ -177,4 +177,34 @@ export class LabrinthUsersV2Module extends AbstractModule {
body: data,
})
}
/**
* Change a user's avatar.
*
* @param idOrUsername - The user's ID or username
* @param file - Image file to upload
* @param ext - File extension (e.g., 'png', 'jpeg', 'gif', 'webp')
*/
public async changeIcon(idOrUsername: string, file: Blob, ext: string): Promise<void> {
return this.client.request(`/user/${idOrUsername}/icon`, {
api: 'labrinth',
version: 2,
method: 'PATCH',
params: { ext },
body: file,
})
}
/**
* Delete a user's avatar.
*
* @param idOrUsername - The user's ID or username
*/
public async deleteIcon(idOrUsername: string): Promise<void> {
return this.client.request(`/user/${idOrUsername}/icon`, {
api: 'labrinth',
version: 2,
method: 'DELETE',
})
}
}
@@ -73,6 +73,18 @@ export class LabrinthVersionsV2Module extends AbstractModule {
})
}
public async getVersionFromFileHash(
hash: string,
algorithm: keyof Labrinth.Versions.v2.VersionFileHash,
): Promise<Labrinth.Versions.v2.Version> {
return this.client.request<Labrinth.Versions.v2.Version>(`/version_file/${hash}`, {
api: 'labrinth',
version: 2,
method: 'GET',
params: { algorithm },
})
}
/**
* Get multiple versions by IDs (v2)
*
@@ -3,16 +3,25 @@ export namespace LauncherMeta {
export namespace v0 {
export type LoaderVersion = {
id: string
url: string
stable: boolean
}
export type GameVersionEntry = {
id: string
stable: boolean
versionGroup?: string
loaders: LoaderVersion[]
}
export type VersionGroup = {
id: string
loaders: LoaderVersion[]
}
export type Manifest = {
gameVersions: GameVersionEntry[]
versionGroups?: VersionGroup[]
}
}
}
@@ -20,10 +20,13 @@ export class LauncherMetaManifestV0Module extends AbstractModule {
*
* @param loader - Loader platform (fabric, forge, quilt, neo)
*/
public async getManifest(loader: string): Promise<LauncherMeta.Manifest.v0.Manifest> {
public async getManifest(
loader: string,
formatVersion = 0,
): Promise<LauncherMeta.Manifest.v0.Manifest> {
return this.client.request<LauncherMeta.Manifest.v0.Manifest>('/manifest.json', {
api: LAUNCHER_META_BASE_URL,
version: `${loader}/v0`,
version: `${loader}/v${formatVersion}`,
method: 'GET',
skipAuth: true,
headers: { 'Content-Type': '' },
@@ -0,0 +1,66 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { SharedInstances } from '../types'
export class SharedInstancesInstancesV1Module extends AbstractModule {
public getModuleID(): string {
return 'sharedinstances_instances_v1'
}
public async get(instanceId: string): Promise<SharedInstances.Instances.v1.Instance> {
return this.client.request<SharedInstances.Instances.v1.Instance>(
`/instances/${encodeURIComponent(instanceId)}`,
{
api: 'sharedinstances',
version: 1,
method: 'GET',
},
)
}
public async getForUser(userId: string): Promise<string[]> {
return this.client.request<string[]>('/instances', {
api: 'sharedinstances',
version: 1,
method: 'GET',
params: { user: userId },
})
}
public async getUsers(instanceId: string): Promise<SharedInstances.Instances.v1.InstanceUsers> {
return this.client.request<SharedInstances.Instances.v1.InstanceUsers>(
`/instances/${encodeURIComponent(instanceId)}/users`,
{
api: 'sharedinstances',
version: 1,
method: 'GET',
},
)
}
public async getLatestVersion(
instanceId: string,
): Promise<SharedInstances.Instances.v1.InstanceVersion> {
return this.client.request<SharedInstances.Instances.v1.InstanceVersion>(
`/instances/${encodeURIComponent(instanceId)}/versions`,
{
api: 'sharedinstances',
version: 1,
method: 'GET',
},
)
}
public async getVersion(
instanceId: string,
version: number,
): Promise<SharedInstances.Instances.v1.InstanceVersion> {
return this.client.request<SharedInstances.Instances.v1.InstanceVersion>(
`/instances/${encodeURIComponent(instanceId)}/versions/${version}`,
{
api: 'sharedinstances',
version: 1,
method: 'GET',
},
)
}
}
@@ -0,0 +1,21 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { SharedInstances } from '../types'
export class SharedInstancesInvitesV1Module extends AbstractModule {
public getModuleID(): string {
return 'sharedinstances_invites_v1'
}
public async get(inviteId: string): Promise<SharedInstances.Invites.v1.Invite> {
return this.client.request<SharedInstances.Invites.v1.Invite>(
`/invites/${encodeURIComponent(inviteId)}`,
{
api: 'sharedinstances',
version: 1,
method: 'GET',
skipAuth: true,
retry: false,
},
)
}
}
@@ -0,0 +1,41 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { SharedInstances } from '../types'
export class SharedInstancesModerationV1Module extends AbstractModule {
public getModuleID(): string {
return 'sharedinstances_moderation_v1'
}
public async blacklistUsers(
request: SharedInstances.Moderation.v1.BlacklistUserRequest,
): Promise<void> {
return this.client.request('/moderation/blacklist', {
api: 'sharedinstances',
version: 1,
method: 'POST',
body: request,
})
}
public async unblacklistUsers(
request: SharedInstances.Moderation.v1.BlacklistUserRequest,
): Promise<void> {
return this.client.request('/moderation/blacklist', {
api: 'sharedinstances',
version: 1,
method: 'DELETE',
body: request,
})
}
public async deleteFile(instanceId: string, version: number, fileName: string): Promise<void> {
return this.client.request(
`/moderation/instances/${encodeURIComponent(instanceId)}/versions/${version}/files/${encodeURIComponent(fileName)}`,
{
api: 'sharedinstances',
version: 1,
method: 'DELETE',
},
)
}
}
@@ -0,0 +1,101 @@
export namespace SharedInstances {
export namespace Invites {
export namespace v1 {
export type UserManager = {
id: string
name: string
type: 'user'
avatar?: string | null
}
export type ServerManager = {
name: string
type: 'server'
icon?: string | null
}
export type Manager = UserManager | ServerManager
export type InviteUser = {
id: string
name: string
avatar?: string | null
joined_at: string | null
}
export type Invite = {
instance_id: string
instance_name: string
instance_icon?: string | null
game_version: string
loader_version: string
managers: Manager[]
instance_users?: InviteUser[]
}
}
}
export namespace Instances {
export namespace v1 {
export type Instance = {
name: string
icon: string | null
quarantine: boolean
}
export type JoinType = 'owner' | 'invite' | 'link'
export type InstanceUser = {
id: string
joined_at: string | null
join_type: JoinType
last_played: string | null
}
export type InstanceUsers = {
users: InstanceUser[]
tokens: number
}
export type FileMetadata = {
path: string
hash: string
}
export type ExternalFile = {
file_name: string
file_type: string
url: string
file_size?: number
metadata?: FileMetadata[]
}
export type InstanceVersion = {
version: number
modrinth_ids?: string[]
ready: boolean
external_files: ExternalFile[]
modpack_id: string | null
game_version: string
loader: string
loader_version: string
}
}
}
export namespace Moderation {
export namespace v1 {
export type BlacklistUserRequest = {
user_ids: string[]
}
}
}
export namespace Users {
export namespace v1 {
export type BlacklistStatus = {
blacklisted: boolean
}
}
}
}
@@ -0,0 +1,21 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { SharedInstances } from '../types'
export class SharedInstancesUsersV1Module extends AbstractModule {
public getModuleID(): string {
return 'sharedinstances_users_v1'
}
public async getBlacklistStatus(
userId: string,
): Promise<SharedInstances.Users.v1.BlacklistStatus> {
return this.client.request<SharedInstances.Users.v1.BlacklistStatus>(
`/blacklist/${encodeURIComponent(userId)}`,
{
api: 'sharedinstances',
version: 1,
method: 'GET',
},
)
}
}
+1
View File
@@ -6,3 +6,4 @@ export * from './launcher-meta/types'
export * from './mclogs/types'
export * from './paper/types'
export * from './purpur/types'
export * from './shared-instances/types'
@@ -21,6 +21,8 @@ export abstract class XHRUploadClient extends AbstractModrinthClient {
baseUrl = this.resolveBaseUrl(this.config.labrinthBaseUrl!)
} else if (options.api === 'archon') {
baseUrl = this.resolveBaseUrl(this.config.archonBaseUrl!)
} else if (options.api === 'sharedinstances') {
baseUrl = this.resolveBaseUrl(this.config.sharedInstancesBaseUrl!)
} else {
baseUrl = options.api
}
+6
View File
@@ -50,6 +50,12 @@ export interface ClientConfig {
*/
archonBaseUrl?: BaseUrlConfig
/**
* Base URL for the Shared Instances API
* @default 'https://shared-instances.modrinth.com'
*/
sharedInstancesBaseUrl?: BaseUrlConfig
/**
* Default request timeout in milliseconds
* @default 10000
+1
View File
@@ -11,6 +11,7 @@ export type RequestOptions = {
* API to use for this request
* - 'labrinth': Main Modrinth API (resolves to labrinthBaseUrl)
* - 'archon': Modrinth Hosting API (resolves to archonBaseUrl)
* - 'sharedinstances': Shared Instances API (resolves to sharedInstancesBaseUrl)
* - string: Custom base URL (e.g., 'https://custom-api.com')
*/
api: 'labrinth' | 'archon' | string