Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<projectid>)](https://modrinth.com/mod/<projectid>)
[![Supported versions](https://modrinth-shields.imgalvin.me/badge/versions/<projectid>)](https://modrinth.com/mod/<projectid>)
```

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

Expand Down
1 change: 1 addition & 0 deletions bun.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "modrinth-shields",
Expand Down
192 changes: 177 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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';
Expand All @@ -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',
Expand All @@ -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(' | '),
Expand All @@ -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.'
}
}
);
Expand Down
37 changes: 37 additions & 0 deletions src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,43 @@ export interface ModrinthAPIResponse {
monetization_status: string
}

export interface ModrinthAPIVersion {
game_versions: Array<string>
loaders: Array<string>
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<ModrinthAPIVersion>

export interface MinecraftVersionsResponse {
latest: {
release: string
Expand Down