diff --git a/README.md b/README.md index c37f6e6..f35e1c8 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,12 @@ A custom sheilds.io badge maker for Modrinth projects To get the badge in your markdown file do ```md -[![Supported versions](https://modrinth-shields.imgalvin.me/badge/)](https://modrinth.com/mod/) +[![Supported versions](https://modrinth-shields.imgalvin.me/badge/versions/)](https://modrinth.com/mod/) ``` Like this: -[![Supported versions](https://modrinth-shields.imgalvin.me/badge/restrictedflying)](https://modrinth.com/mod/restrictedflying) +[![Supported versions](https://modrinth-shields.imgalvin.me/badge/versions/restrictedflying)](https://modrinth.com/mod/restrictedflying) # Docs diff --git a/bun.lock b/bun.lock index 5fb6f4f..6f8f3e8 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "modrinth-shields", diff --git a/src/index.ts b/src/index.ts index e5d0bae..618ba74 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import { siModrinth } from 'simple-icons'; import { Elysia, t } from 'elysia'; import { cors } from '@elysiajs/cors'; import { openapi } from '@elysiajs/openapi'; -import type { ModrinthAPIResponse } from './types'; +import type { ModrinthAPIResponse, ModrinthAPIVersionsResponse } from './types'; import { getMinecraftVersions } from './getMinecraftVersions'; import { EnvManager } from 'managedenv'; @@ -22,13 +22,37 @@ const envs = new EnvManager() const port = envs.env.PORT; app.use(cors()); -app.use(openapi()); +app.use(openapi({ + documentation: { + info: { + title: 'Modrinth Badges API', + description: 'An API to generate badges for Modrinth projects, such as supported Minecraft versions. Not affiliated with Modrinth, Shields.io or Mojang in any way.', + version: '1.1.0' + } + } +})); // Preload cache getMinecraftVersions(); app.get( '/', + ({ set }) => { + set.status = 307; + set.headers['Location'] = '/openapi'; + return 'Redirecting...'; + }, + { + detail: { + tags: ['General'], + summary: 'Redirect to OpenAPI documentation', + description: 'Redirects to the OpenAPI documentation for this API. Please do not make any requests to this endpoint.' + } + } +); + +app.get( + '/source', ({ set }) => { set.status = 307; set.headers['Location'] = 'https://github.com/GalvinPython/modrinth-badge'; @@ -49,10 +73,13 @@ app.get( const { projectId } = params; const optionIgnoreSnapshots = query.ignoresnapshots === 'true'; + // Set appropriate headers + set.headers['Deprecation'] = '@1763942400' + set.headers['Content-Type'] = 'image/svg+xml'; + const httpResponse = await fetch(`https://api.modrinth.com/v2/project/${projectId}`); if (!httpResponse.ok) { set.status = 404; - set.headers['Content-Type'] = 'image/svg+xml'; return makeBadge({ label: 'Error', message: 'Project not found', @@ -68,15 +95,84 @@ app.get( if (optionIgnoreSnapshots) { const mcVersionsResponse = await getMinecraftVersions(); - if (mcVersionsResponse instanceof Error) return; - - versions = versions.filter(version => { - const isSnapshot = mcVersionsResponse.versions.find(v => v.id === version)?.type === 'snapshot'; - return !isSnapshot; + if (mcVersionsResponse instanceof Error) return makeBadge({ + label: 'Error', + message: 'Could not fetch Minecraft versions', + color: '#ff0000', + style: 'flat', + labelColor: '#fff', + logoBase64: `data:image/svg+xml;base64,${btoa(siModrinth.svg)}`, }); } + return makeBadge({ + label: 'Supported Versions', + message: versions.join(' | '), + color: '#00AF5C', + style: 'flat', + labelColor: '#fff', + logoBase64: `data:image/svg+xml;base64,${btoa(siModrinth.svg)}`, + }); + }, + { + params: t.Object({ + projectId: t.String({ description: 'The Modrinth project slug or ID' }) + }), + query: t.Object({ + ignoresnapshots: t.Optional( + t.String({ + enum: ['false', 'true'], + description: 'Ignore snapshot versions (true/false). Defaults to false.' + }) + ), + }), + detail: { + deprecated: true, + tags: ['Badges'], + summary: 'Generate a supported Minecraft versions badge for a Modrinth project', + description: + '**Deprecated - Use `/badge/versions/:projectId` instead.**' + } + } +); + +app.get( + '/badge/versions/:projectId', + async ({ params, query, set }) => { + const { projectId } = params; + const optionIgnoreSnapshots = query.ignoresnapshots === 'true'; + + // Set appropriate headers set.headers['Content-Type'] = 'image/svg+xml'; + + const httpResponse = await fetch(`https://api.modrinth.com/v2/project/${projectId}`); + if (!httpResponse.ok) { + set.status = 404; + return makeBadge({ + label: 'Error', + message: 'Project not found', + color: '#ff0000', + style: 'flat', + labelColor: '#fff', + logoBase64: `data:image/svg+xml;base64,${btoa(siModrinth.svg)}`, + }); + } + + const projectData = (await httpResponse.json()) as ModrinthAPIResponse; + let versions = projectData.game_versions; + + if (optionIgnoreSnapshots) { + const mcVersionsResponse = await getMinecraftVersions(); + if (mcVersionsResponse instanceof Error) return makeBadge({ + label: 'Error', + message: 'Could not fetch Minecraft versions (Server issue)', + color: '#ff0000', + style: 'flat', + labelColor: '#fff', + logoBase64: `data:image/svg+xml;base64,${btoa(siModrinth.svg)}`, + }); + } + return makeBadge({ label: 'Supported Versions', message: versions.join(' | '), @@ -97,18 +193,84 @@ app.get( description: 'Ignore snapshot versions (true/false). Defaults to false.' }) ), - // useloadersinstead: t.Optional( - // t.String({ - // enum: ['false', 'true'], - // description: 'Use loaders instead of game versions (true/false). Defaults to false.' - // }) - // ) }), detail: { tags: ['Badges'], summary: 'Generate a supported Minecraft versions badge for a Modrinth project', description: - 'Returns an SVG badge showing supported Minecraft versions. Optionally ignores snapshot versions if `ignoresnapshots=true`.' + 'Returns an SVG badge showing supported Minecraft versions. Optionally ignores snapshot versions if `?ignoresnapshots=true`.' + } + } +); + +app.get( + '/badge/loaders/:projectId', + async ({ params, query, set }) => { + const { projectId } = params; + const optionIgnoreSnapshots = query.ignoresnapshots === 'true'; + + // Set appropriate headers + set.headers['Content-Type'] = 'image/svg+xml'; + + const httpResponse = await fetch(`https://api.modrinth.com/v2/project/${projectId}/version`); + if (!httpResponse.ok) { + set.status = 404; + return makeBadge({ + label: 'Error', + message: 'Project not found', + color: '#ff0000', + style: 'flat', + labelColor: '#fff', + logoBase64: `data:image/svg+xml;base64,${btoa(siModrinth.svg)}`, + }); + } + + const projectData = (await httpResponse.json()) as ModrinthAPIVersionsResponse; + + if (!projectData || projectData.length === 0) { + set.status = 404; + return makeBadge({ + label: 'Error', + message: 'Project not found', + color: '#ff0000', + style: 'flat', + labelColor: '#fff', + logoBase64: `data:image/svg+xml;base64,${btoa(siModrinth.svg)}`, + }); + } + + const loaders = [...new Set(projectData.flatMap(v => v.loaders ?? []))]; + + if (optionIgnoreSnapshots) { + const mcVersionsResponse = await getMinecraftVersions(); + if (mcVersionsResponse instanceof Error) return makeBadge({ + label: 'Error', + message: 'Could not fetch Minecraft versions (Server issue)', + color: '#ff0000', + style: 'flat', + labelColor: '#fff', + logoBase64: `data:image/svg+xml;base64,${btoa(siModrinth.svg)}`, + }); + } + + return makeBadge({ + label: 'Supported Loaders', + message: loaders.join(' | '), + color: '#00AF5C', + style: 'flat', + labelColor: '#fff', + logoBase64: `data:image/svg+xml;base64,${btoa(siModrinth.svg)}`, + }); + }, + { + params: t.Object({ + projectId: t.String({ description: 'The Modrinth project slug or ID' }) + }), + detail: { + tags: ['Badges'], + summary: 'Generate a supported Minecraft loaders badge for a Modrinth project', + description: + 'Returns an SVG badge showing supported Minecraft loaders.' } } ); diff --git a/src/types.d.ts b/src/types.d.ts index e26441e..2476218 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -49,6 +49,43 @@ export interface ModrinthAPIResponse { monetization_status: string } +export interface ModrinthAPIVersion { + game_versions: Array + loaders: Array + id: string + project_id: string + author_id: string + featured: boolean + name: string + version_number: string + changelog: string + changelog_url: any + date_published: string + downloads: number + version_type: string + status: string + requested_status: any + files: Array<{ + hashes: { + sha1: string + sha512: string + } + url: string + filename: string + primary: boolean + size: number + file_type: any + }> + dependencies: Array<{ + version_id: any + project_id: string + file_name: any + dependency_type: string + }> +} + +export type ModrinthAPIVersionsResponse = Array + export interface MinecraftVersionsResponse { latest: { release: string