Для мод-менеджеров
Эта страница для авторов мод-менеджеров: как подключить Glitchmug как источник. Свой менеджер мы не делаем, потому что хорошие уже есть.
Начать можно без ключа. Каталог публичный, это те же данные, что видит любой посетитель сайта, поэтому для чтения и скачивания файлов ключ не нужен. Ключи только снимают лимиты.
Эндпоинты
| Шаг | Запрос |
|---|---|
| Игры | GET /games, затем GET /games/{slug}/categories |
| Каталог и поиск | GET /mods?game=deadlock с q, category, tag, sort |
| Что изменилось | GET /mods?game=deadlock&updatedSince=… |
| Один мод | GET /mods/{id} |
| Версии и списки изменений | GET /mods/{id}/versions, сначала новые |
| Файлы версии | GET /versions/{id}/files: name, size, sha256 |
| Что внутри архива | GET /files/{id}/entries |
| Скачивание | GET /files/{id}/download |
Полная схема в справочнике API.
Проверка обновлений
Храните id и currentVersion каждого установленного мода. При синхронизации запрашивайте изменения с прошлого раза и сравнивайте версии: updatedAt сдвигается и при обновлении счётчиков, так что новая дата сама по себе не значит новый релиз.
- curl
- TypeScript
- Python
#!/usr/bin/env bash
set -euo pipefail
BASE_URL="${GLITCHMUG_API:-https://api.glitchmug.com/v1}"
UA="ExampleManager/1.0 (+https://example.com)"
# Everything changed since your last sync; compare currentVersion with what you stored
curl -fsS -A "$UA" "$BASE_URL/mods?game=deadlock&updatedSince=2026-10-01T00:00:00Z&sort=updated&limit=100"
const baseUrl = process.env.GLITCHMUG_API ?? 'https://api.glitchmug.com/v1';
const headers = { 'User-Agent': 'ExampleManager/1.0 (+https://example.com)' };
type Page = {
data: { id: number; title: string; currentVersion: string | null; updatedAt: string }[];
page: { nextCursor: string | null; hasMore: boolean };
};
// What the manager stored on the last sync: mod id -> version
const known = new Map<number, string>();
const since = '2026-10-01T00:00:00Z';
// Walk the pages of what changed (stop after 3 here); updatedAt also moves on counters, so compare versions
let cursor: string | null = null;
for (let i = 0; i < 3; i++) {
const q = new URLSearchParams({ game: 'deadlock', updatedSince: since, sort: 'updated', limit: '100' });
if (cursor) q.set('cursor', cursor);
const res = await fetch(`${baseUrl}/mods?${q}`, { headers });
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
const body = (await res.json()) as Page;
for (const m of body.data) {
if (m.currentVersion && known.get(m.id) !== m.currentVersion) console.log('update:', m.id, m.title, m.currentVersion);
}
if (!body.page.hasMore) break;
cursor = body.page.nextCursor;
}
import json
import os
import urllib.parse
import urllib.request
base_url = os.environ.get("GLITCHMUG_API", "https://api.glitchmug.com/v1")
headers = {"User-Agent": "ExampleManager/1.0 (+https://example.com)"}
# What the manager stored on the last sync: mod id -> version
known = {}
since = "2026-10-01T00:00:00Z"
# Walk the pages of what changed (stop after 3 here); updatedAt also moves on counters, so compare versions
cursor = None
for _ in range(3):
query = {"game": "deadlock", "updatedSince": since, "sort": "updated", "limit": 100}
if cursor:
query["cursor"] = cursor
req = urllib.request.Request(f"{base_url}/mods?{urllib.parse.urlencode(query)}", headers=headers)
with urllib.request.urlopen(req) as res:
body = json.load(res)
for mod in body["data"]:
if mod["currentVersion"] and known.get(mod["id"]) != mod["currentVersion"]:
print("update:", mod["id"], mod["title"], mod["currentVersion"])
if not body["page"]["hasMore"]:
break
cursor = body["page"]["nextCursor"]
Недоступный мод отвечает 404. isWip отмечает незаконченный мод, а в GET /mods/{id} есть ещё isObsolete для устаревшего. Моды 18+ не попадают в списки без nsfw=include, их отмечает isNsfw: держите их за своей проверкой возраста.
Вебхуки сообщают только об изменениях ваших собственных модов, поэтому менеджер опрашивает API. Раза в несколько минут более чем достаточно.
Скачивание
GET /files/{id}/download засчитывает скачивание автору и отвечает 302 на подписанную ссылку на https://dl.glitchmug.com. Пройдите по редиректу и сверьте файл с sha256.
- curl
- TypeScript
- Python
#!/usr/bin/env bash
set -euo pipefail
BASE_URL="${GLITCHMUG_API:-https://api.glitchmug.com/v1}"
UA="ExampleManager/1.0 (+https://example.com)"
VERSION_ID="${VERSION_ID:-twl7516oswn1}"
FILE_ID="${FILE_ID:-5ann2e2oz3y6}"
# The files of a version, with name, size and sha256
curl -fsS -A "$UA" "$BASE_URL/versions/$VERSION_ID/files"
# -L follows the 302 to the signed link on dl.glitchmug.com
out="$(mktemp)"
curl -fsSL -A "$UA" -o "$out" "$BASE_URL/files/$FILE_ID/download"
# Compare with the sha256 from the list above; on a mismatch discard the file
sha256sum "$out"
rm -f "$out"
import { createHash } from 'node:crypto';
const baseUrl = process.env.GLITCHMUG_API ?? 'https://api.glitchmug.com/v1';
const headers = { 'User-Agent': 'ExampleManager/1.0 (+https://example.com)' };
const versionId = process.env.VERSION_ID ?? 'twl7516oswn1';
type Files = { data: { id: string; name: string; size: number; sha256: string; scanStatus: string }[] };
const list = await fetch(`${baseUrl}/versions/${versionId}/files`, { headers });
if (!list.ok) throw new Error(`HTTP ${list.status}: ${await list.text()}`);
const { data } = (await list.json()) as Files;
for (const file of data) {
// fetch follows the 302 to the signed link on dl.glitchmug.com
const res = await fetch(`${baseUrl}/files/${file.id}/download`, { headers });
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
const bytes = Buffer.from(await res.arrayBuffer());
const hash = createHash('sha256').update(bytes).digest('hex');
console.log(file.name, hash === file.sha256 ? 'ok' : 'sha256 mismatch: discard the file');
}
import hashlib
import json
import os
import urllib.request
base_url = os.environ.get("GLITCHMUG_API", "https://api.glitchmug.com/v1")
headers = {"User-Agent": "ExampleManager/1.0 (+https://example.com)"}
version_id = os.environ.get("VERSION_ID", "twl7516oswn1")
def get(path):
return urllib.request.urlopen(urllib.request.Request(f"{base_url}{path}", headers=headers))
with get(f"/versions/{version_id}/files") as res:
files = json.load(res)["data"]
for file in files:
# urllib follows the 302 to the signed link on dl.glitchmug.com
with get(f"/files/{file['id']}/download") as res:
digest = hashlib.sha256(res.read()).hexdigest()
print(file["name"], "ok" if digest == file["sha256"] else "sha256 mismatch: discard the file")
- Всегда начинайте с этого эндпоинта. Подписанная ссылка живёт 6 часов: не храните её и не размещайте копии файлов, иначе авторы теряют счётчик скачиваний и возможность удалить свою работу.
- В списке только файлы, прошедшие проверку на вирусы.
- Без ключа скорость скачивания ограничена 2 МБ/с. Клиентский ключ снимает это ограничение.
Лимиты и ключи
Обращаться к API можно тремя способами:
| Без ключа | Клиентский ключ | API-ключ | |
|---|---|---|---|
| Для чего | Попробовать, приложение с небольшим числом скачиваний | Ваше приложение на компьютерах пользователей | Ваш бэкенд |
| Запросы | 60 в минуту на IP | 60 в минуту на IP | 600 в минуту на ключ |
| Скорость скачивания | 2 МБ/с | Без ограничения | Без ограничения |
| Как передавать | Ничего не нужно | X-Glitchmug-Client: <ключ> при скачивании | Authorization: Bearer <ключ> |
| Как получить | Бесплатно, напишите на contact@glitchmug.com | Настройки → API-ключи |
- Клиентский ключ снимает только ограничение скорости, поэтому его можно класть в сборку приложения.
- API-ключ действует от имени вашего аккаунта и может менять ваши моды, поэтому в сборку приложения его класть нельзя.
- 60 запросов в минуту на IP хватит десктопному приложению, которое синхронизируется через
updatedSince, а не листает весь каталог. См. Лимиты запросов. - API не отдаёт заголовки CORS, поэтому обращайтесь к нему из нативного кода или с бэкенда, а не со страницы в webview. В Tauri и Electron это нативная часть, а не фронтенд.
Правила хорошего клиента
- Передавайте
User-Agentс названием приложения, версией и ссылкой, как в примерах: так мы сможем связаться с вами раньше, чем проблема дойдёт до блокировки. - Показывайте автора мода и ссылку на его страницу:
pathесть в каждом ответе, доменhttps://glitchmug.com. Для русских страниц добавьте/ruв начало. - Показывайте
licenseизGET /mods/{id}. Там указано, что разрешает автор, в том числе про перезалив. - Соблюдайте
Retry-Afterпри429.
Стабильность
Новое появляется в /v1. Несовместимое изменение вышло бы как /v2, а v1 работал бы ещё какое-то время для перехода. Все изменения есть в журнале. До открытой беты v1 может меняться, такие изменения помечены Несовместимо.
Сотрудничество
Если вы ведёте менеджер, напишите на contact@glitchmug.com. Пришлём клиентский ключ и можем обсудить:
- кнопку «Установить через …» на страницах модов, если у менеджера есть URL-протокол;
- чего вам не хватает в API.