1
0
Fork 0
forked from creations/badgeAPI

Compare commits

..

17 commits
main ... main

15 changed files with 265 additions and 405 deletions

View file

@ -5,4 +5,5 @@ PORT=8080
REDIS_URL=redis://username:password@localhost:6379
REDIS_TTL=3600 # seconds
DISCORD_TOKEN=discord_bot_token
# if you wish to get discord badges
DISCORD_TOKEN=discord_bot_token

41
LICENSE
View file

@ -1,21 +1,28 @@
MIT License
BSD 3-Clause License
Copyright (c) 2025 [fullname]
Copyright (c) 2025, creations.works
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -2,6 +2,9 @@
A fast Discord badge aggregation API built with [Bun](https://bun.sh) and Redis caching.
# Preview
https://badges.creations.works
## Features
- Aggregates custom badge data from multiple sources (e.g. Vencord, Nekocord, Equicord, etc.)
@ -37,6 +40,9 @@ REDIS_URL=redis://username:password@localhost:6379
# Value is in seconds
REDIS_TTL=3600
#only use this if you want to show discord badges
DISCORD_TOKEN=discord_bot_token
```
## Endpoint
@ -53,11 +59,11 @@ GET /:userId
### Query Parameters
| Name | Description |
|--------------|--------------------------------------------------------------------------|
| `services` | A comma or space separated list of services to fetch badges from |
| `cache` | Set to `true` or `false` (default: `true`). `false` bypasses Redis |
| `seperated` | Set to `true` to return results grouped by service, else merged array |
| Name | Description |
|--------------|---------------------------------------------------------------------------------------------------|
| `services` | A comma or space separated list of services to fetch badges from, if this is empty it fetches all |
| `cache` | Set to `true` or `false` (default: `true`). `false` bypasses Redis |
| `seperated` | Set to `true` to return results grouped by service, else merged array |
### Supported Services
@ -65,6 +71,8 @@ GET /:userId
- Equicord
- Nekocord
- ReviewDb
- Enmity
- Discord ( some )
### Example
@ -72,20 +80,12 @@ GET /:userId
GET /209830981060788225?seperated=true&cache=true&services=equicord
```
## Development
Run formatting and linting with BiomeJS:
```bash
bun run lint
bun run lint:fix
```
## Start the Server
```bash
bun i
bun run start
```
## License
[MIT](LICENSE)
[BSD 3](LICENSE)

88
config/discordBadges.ts Normal file
View file

@ -0,0 +1,88 @@
export const discordBadges = {
// User badges
STAFF: 1 << 0,
PARTNER: 1 << 1,
HYPESQUAD: 1 << 2,
BUG_HUNTER_LEVEL_1: 1 << 3,
HYPESQUAD_ONLINE_HOUSE_1: 1 << 6,
HYPESQUAD_ONLINE_HOUSE_2: 1 << 7,
HYPESQUAD_ONLINE_HOUSE_3: 1 << 8,
PREMIUM_EARLY_SUPPORTER: 1 << 9,
TEAM_USER: 1 << 10,
SYSTEM: 1 << 12,
BUG_HUNTER_LEVEL_2: 1 << 14,
VERIFIED_DEVELOPER: 1 << 17,
CERTIFIED_MODERATOR: 1 << 18,
SPAMMER: 1 << 20,
ACTIVE_DEVELOPER: 1 << 22,
// Bot badges
VERIFIED_BOT: 1 << 16,
BOT_HTTP_INTERACTIONS: 1 << 19,
SUPPORTS_COMMANDS: 1 << 23,
USES_AUTOMOD: 1 << 24,
};
export const discordBadgeDetails = {
HYPESQUAD: {
tooltip: "HypeSquad Events",
icon: "/public/badges/discord/HYPESQUAD.svg",
},
HYPESQUAD_ONLINE_HOUSE_1: {
tooltip: "HypeSquad Bravery",
icon: "/public/badges/discord/HYPESQUAD_ONLINE_HOUSE_1.svg",
},
HYPESQUAD_ONLINE_HOUSE_2: {
tooltip: "HypeSquad Brilliance",
icon: "/public/badges/discord/HYPESQUAD_ONLINE_HOUSE_2.svg",
},
HYPESQUAD_ONLINE_HOUSE_3: {
tooltip: "HypeSquad Balance",
icon: "/public/badges/discord/HYPESQUAD_ONLINE_HOUSE_3.svg",
},
STAFF: {
tooltip: "Discord Staff",
icon: "/public/badges/discord/STAFF.svg",
},
PARTNER: {
tooltip: "Discord Partner",
icon: "/public/badges/discord/PARTNER.svg",
},
CERTIFIED_MODERATOR: {
tooltip: "Certified Moderator",
icon: "/public/badges/discord/CERTIFIED_MODERATOR.svg",
},
VERIFIED_DEVELOPER: {
tooltip: "Verified Bot Developer",
icon: "/public/badges/discord/VERIFIED_DEVELOPER.svg",
},
ACTIVE_DEVELOPER: {
tooltip: "Active Developer",
icon: "/public/badges/discord/ACTIVE_DEVELOPER.svg",
},
PREMIUM_EARLY_SUPPORTER: {
tooltip: "Premium Early Supporter",
icon: "/public/badges/discord/PREMIUM_EARLY_SUPPORTER.svg",
},
BUG_HUNTER_LEVEL_1: {
tooltip: "Bug Hunter (Level 1)",
icon: "/public/badges/discord/BUG_HUNTER_LEVEL_1.svg",
},
BUG_HUNTER_LEVEL_2: {
tooltip: "Bug Hunter (Level 2)",
icon: "/public/badges/discord/BUG_HUNTER_LEVEL_2.svg",
},
SUPPORTS_COMMANDS: {
tooltip: "Supports Commands",
icon: "/public/badges/discord/SUPPORTS_COMMANDS.svg",
},
USES_AUTOMOD: {
tooltip: "Uses AutoMod",
icon: "/public/badges/discord/USES_AUTOMOD.svg",
},
};

View file

@ -9,39 +9,35 @@ export const redisTtl: number = process.env.REDIS_TTL
? Number.parseInt(process.env.REDIS_TTL, 10)
: 60 * 60 * 1; // 1 hour
// not sure the point ?
// function getClientModBadgesUrl(userId: string): string {
// return `https://cdn.jsdelivr.net/gh/Equicord/ClientModBadges-API@main/users/${userId}.json`;
// }
export const badgeServices: badgeURLMap[] = [
{
service: "Vencord",
url: "https://badges.vencord.dev/badges.json",
authType: "none"
},
{
service: "Equicord", // Ekwekord ! WOOP
url: "https://raw.githubusercontent.com/Equicord/Equibored/refs/heads/main/badges.json",
authType: "none"
},
{
service: "Nekocord",
url: "https://nekocord.dev/assets/badges.json",
authType: "none"
},
{
service: "ReviewDb",
url: "https://manti.vendicated.dev/api/reviewdb/badges",
authType: "none"
},
// {
// service: "ClientMods",
// url: getClientModBadgesUrl,
// }
{
service: "Enmity",
url: (userId: string) => ({
user: `https://raw.githubusercontent.com/enmity-mod/badges/main/${userId}.json`,
badge: (id: string) =>
`https://raw.githubusercontent.com/enmity-mod/badges/main/data/${id}.json`,
}),
},
{
service: "Discord",
url: (id) => `https://discord.com/api/v10/users${id}`,
authType: "DISCORD"
}
url: (userId: string) => `https://discord.com/api/v10/users/${userId}`,
},
];
export const botToken: string | undefined = process.env.DISCORD_TOKEN;

View file

@ -7,7 +7,7 @@
"dev": "bun run --hot src/index.ts --dev",
"lint": "bunx biome check",
"lint:fix": "bunx biome check --fix",
"cleanup": "rm -rf logs node_modules bun.lockdb"
"cleanup": "rm -rf logs node_modules bun.lock"
},
"devDependencies": {
"@types/bun": "^1.2.9",
@ -19,6 +19,7 @@
"typescript": "^5.8.3"
},
"dependencies": {
"@creations.works/logger": "^1.0.3",
"ejs": "^3.1.10"
}
}

View file

@ -1,98 +1,19 @@
import { badgeServices, redisTtl } from "@config/environment";
import { discordBadgeDetails, discordBadges } from "@config/discordBadges";
import { badgeServices, botToken, redisTtl } from "@config/environment";
import { fetch, redis } from "bun";
const DISCORD_BADGES = {
// User badges
HYPESQUAD: 2 << 2,
HYPESQUAD_ONLINE_HOUSE_1: 2 << 6,
HYPESQUAD_ONLINE_HOUSE_2: 2 << 7,
HYPESQUAD_ONLINE_HOUSE_3: 2 << 8,
STAFF: 2 << 0,
PARTNER: 2 << 1,
CERTIFIED_MODERATOR: 2 << 18,
VERIFIED_DEVELOPER: 2 << 17,
ACTIVE_DEVELOPER: 2 << 22,
PREMIUM_EARLY_SUPPORTER: 2 << 9,
BUG_HUNTER_LEVEL_1: 2 << 3,
BUG_HUNTER_LEVEL_2: 2 << 14,
// Bot badges
SUPPORTS_COMMANDS: 2 << 23,
USES_AUTOMOD: 2 << 24,
};
const DISCORD_BADGE_DETAILS = {
HYPESQUAD: {
tooltip: "HypeSquad Events",
icon: "/public/badges/discord/HYPESQUAD.svg",
},
HYPESQUAD_ONLINE_HOUSE_1: {
tooltip: "HypeSquad Bravery",
icon: "/public/badges/discord/HYPESQUAD_ONLINE_HOUSE_1.svg",
},
HYPESQUAD_ONLINE_HOUSE_2: {
tooltip: "HypeSquad Brilliance",
icon: "/public/badges/discord/HYPESQUAD_ONLINE_HOUSE_2.svg",
},
HYPESQUAD_ONLINE_HOUSE_3: {
tooltip: "HypeSquad Balance",
icon: "/public/badges/discord/HYPESQUAD_ONLINE_HOUSE_3.svg",
},
STAFF: {
tooltip: "Discord Staff",
icon: "/public/badges/discord/STAFF.svg",
},
PARTNER: {
tooltip: "Discord Partner",
icon: "/public/badges/discord/PARTNER.svg",
},
CERTIFIED_MODERATOR: {
tooltip: "Certified Moderator",
icon: "/public/badges/discord/CERTIFIED_MODERATOR.svg",
},
VERIFIED_DEVELOPER: {
tooltip: "Verified Bot Developer",
icon: "/public/badges/discord/VERIFIED_DEVELOPER.svg",
},
ACTIVE_DEVELOPER: {
tooltip: "Active Developer",
icon: "/public/badges/discord/ACTIVE_DEVELOPER.svg",
},
PREMIUM_EARLY_SUPPORTER: {
tooltip: "Premium Early Supporter",
icon: "/public/badges/discord/PREMIUM_EARLY_SUPPORTER.svg",
},
BUG_HUNTER_LEVEL_1: {
tooltip: "Bug Hunter (Level 1)",
icon: "/public/badges/discord/BUG_HUNTER_LEVEL_1.svg",
},
BUG_HUNTER_LEVEL_2: {
tooltip: "Bug Hunter (Level 2)",
icon: "/public/badges/discord/BUG_HUNTER_LEVEL_2.svg",
},
SUPPORTS_COMMANDS: {
tooltip: "Supports Commands",
icon: "/public/badges/discord/SUPPORTS_COMMANDS.svg",
},
USES_AUTOMOD: {
tooltip: "Uses AutoMod",
icon: "/public/badges/discord/USES_AUTOMOD.svg",
},
function getRequestOrigin(request: Request): string {
const headers = request.headers;
const forwardedProto = headers.get("X-Forwarded-Proto") || "http";
const host = headers.get("Host") || new URL(request.url).host;
return `${forwardedProto}://${host}`;
}
export async function fetchBadges(
userId: string,
services: string[],
options?: FetchBadgesOptions,
request?: Request,
): Promise<BadgeResult> {
const { nocache = false, separated = false } = options ?? {};
const results: Record<string, Badge[]> = {};
@ -120,50 +41,23 @@ export async function fetchBadges(
}
}
let url: string;
let headers: Record<string, string> = {};
if (typeof entry.url === "function") {
url = entry.url(userId);
} else {
url = entry.url;
}
if (entry.authType === "DISCORD") {
headers = {
Authorization: `Bot ${process.env.DISCORD_TOKEN}`
};
}
const result: Badge[] = [];
try {
const res = await fetch(url, { headers });
if (!res.ok) return;
const data = await res.json();
const result: Badge[] = [];
let url: string | { user: string; badge: (id: string) => string };
if (typeof entry.url === "function") {
url = entry.url(userId);
} else {
url = entry.url;
}
switch (serviceKey) {
case "discord": {
if (data.avatar.startsWith("a_")) {
result.push({
tooltip: "Discord Nitro",
badge: "/public/badges/discord/NITRO.svg",
});
}
for (const [flag, bitwise] of Object.entries(DISCORD_BADGES)) {
if (data.flags & bitwise) {
const badge = DISCORD_BADGE_DETAILS[flag as keyof typeof DISCORD_BADGE_DETAILS];
result.push({
tooltip: badge.tooltip,
badge: badge.icon,
});
}
}
break;
}
case "vencord":
case "equicord": {
const res = await fetch(url as string);
if (!res.ok) break;
const data = await res.json();
const userBadges = data[userId];
if (Array.isArray(userBadges)) {
for (const b of userBadges) {
@ -177,6 +71,10 @@ export async function fetchBadges(
}
case "nekocord": {
const res = await fetch(url as string);
if (!res.ok) break;
const data = await res.json();
const userBadgeIds = data.users?.[userId]?.badges;
if (Array.isArray(userBadgeIds)) {
for (const id of userBadgeIds) {
@ -193,6 +91,10 @@ export async function fetchBadges(
}
case "reviewdb": {
const res = await fetch(url as string);
if (!res.ok) break;
const data = await res.json();
for (const b of data) {
if (b.discordID === userId) {
result.push({
@ -203,6 +105,70 @@ export async function fetchBadges(
}
break;
}
case "enmity": {
if (
typeof url !== "object" ||
typeof url.user !== "string" ||
typeof url.badge !== "function"
)
break;
const userRes = await fetch(url.user);
if (!userRes.ok) break;
const badgeIds: string[] = await userRes.json();
if (!Array.isArray(badgeIds)) break;
await Promise.all(
badgeIds.map(async (id) => {
const badgeRes = await fetch(url.badge(id));
if (!badgeRes.ok) return;
const badge = await badgeRes.json();
if (!badge?.name || !badge?.url?.dark) return;
result.push({
tooltip: badge.name,
badge: badge.url.dark,
});
}),
);
break;
}
case "discord": {
if (!botToken) break;
const res = await fetch(url as string, {
headers: {
Authorization: `Bot ${botToken}`,
},
});
if (!res.ok) break;
const data = await res.json();
const origin = request ? getRequestOrigin(request) : "";
if (data.avatar.startsWith("a_")) {
result.push({
tooltip: "Discord Nitro",
badge: `${origin}/public/badges/discord/NITRO.svg`,
});
}
for (const [flag, bitwise] of Object.entries(discordBadges)) {
if (data.flags & bitwise) {
const badge =
discordBadgeDetails[flag as keyof typeof discordBadgeDetails];
result.push({
tooltip: badge.tooltip,
badge: `${origin}${badge.icon}`,
});
}
}
break;
}
}
if (result.length > 0) {
@ -212,7 +178,7 @@ export async function fetchBadges(
await redis.expire(cacheKey, redisTtl);
}
}
} catch (_) { }
} catch (_) {}
}),
);

View file

@ -1,10 +1,3 @@
export function timestampToReadable(timestamp?: number): string {
const date: Date =
timestamp && !Number.isNaN(timestamp) ? new Date(timestamp) : new Date();
if (Number.isNaN(date.getTime())) return "Invalid Date";
return date.toISOString().replace("T", " ").replace("Z", "");
}
export function validateID(id: string): boolean {
if (!id) return false;

View file

@ -1,205 +0,0 @@
import type { Stats } from "node:fs";
import {
type WriteStream,
createWriteStream,
existsSync,
mkdirSync,
statSync,
} from "node:fs";
import { EOL } from "node:os";
import { basename, join } from "node:path";
import { environment } from "@config/environment";
import { timestampToReadable } from "@helpers/char";
class Logger {
private static instance: Logger;
private static log: string = join(__dirname, "../../logs");
public static getInstance(): Logger {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
private writeToLog(logMessage: string): void {
if (environment.development) return;
const date: Date = new Date();
const logDir: string = Logger.log;
const logFile: string = join(
logDir,
`${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}.log`,
);
if (!existsSync(logDir)) {
mkdirSync(logDir, { recursive: true });
}
let addSeparator = false;
if (existsSync(logFile)) {
const fileStats: Stats = statSync(logFile);
if (fileStats.size > 0) {
const lastModified: Date = new Date(fileStats.mtime);
if (
lastModified.getFullYear() === date.getFullYear() &&
lastModified.getMonth() === date.getMonth() &&
lastModified.getDate() === date.getDate() &&
lastModified.getHours() !== date.getHours()
) {
addSeparator = true;
}
}
}
const stream: WriteStream = createWriteStream(logFile, { flags: "a" });
if (addSeparator) {
stream.write(`${EOL}${date.toISOString()}${EOL}`);
}
stream.write(`${logMessage}${EOL}`);
stream.close();
}
private extractFileName(stack: string): string {
const stackLines: string[] = stack.split("\n");
let callerFile = "";
for (let i = 2; i < stackLines.length; i++) {
const line: string = stackLines[i].trim();
if (line && !line.includes("Logger.") && line.includes("(")) {
callerFile = line.split("(")[1]?.split(")")[0] || "";
break;
}
}
return basename(callerFile);
}
private getCallerInfo(stack: unknown): {
filename: string;
timestamp: string;
} {
const filename: string =
typeof stack === "string" ? this.extractFileName(stack) : "unknown";
const readableTimestamp: string = timestampToReadable();
return { filename, timestamp: readableTimestamp };
}
public info(message: string | string[], breakLine = false): void {
const stack: string = new Error().stack || "";
const { filename, timestamp } = this.getCallerInfo(stack);
const joinedMessage: string = Array.isArray(message)
? message.join(" ")
: message;
const logMessageParts: ILogMessageParts = {
readableTimestamp: { value: timestamp, color: "90" },
level: { value: "[INFO]", color: "32" },
filename: { value: `(${filename})`, color: "36" },
message: { value: joinedMessage, color: "0" },
};
this.writeToLog(`${timestamp} [INFO] (${filename}) ${joinedMessage}`);
this.writeConsoleMessageColored(logMessageParts, breakLine);
}
public warn(message: string | string[], breakLine = false): void {
const stack: string = new Error().stack || "";
const { filename, timestamp } = this.getCallerInfo(stack);
const joinedMessage: string = Array.isArray(message)
? message.join(" ")
: message;
const logMessageParts: ILogMessageParts = {
readableTimestamp: { value: timestamp, color: "90" },
level: { value: "[WARN]", color: "33" },
filename: { value: `(${filename})`, color: "36" },
message: { value: joinedMessage, color: "0" },
};
this.writeToLog(`${timestamp} [WARN] (${filename}) ${joinedMessage}`);
this.writeConsoleMessageColored(logMessageParts, breakLine);
}
public error(
message: string | Error | (string | Error)[],
breakLine = false,
): void {
const stack: string = new Error().stack || "";
const { filename, timestamp } = this.getCallerInfo(stack);
const messages: (string | Error)[] = Array.isArray(message)
? message
: [message];
const joinedMessage: string = messages
.map((msg: string | Error): string =>
typeof msg === "string" ? msg : msg.message,
)
.join(" ");
const logMessageParts: ILogMessageParts = {
readableTimestamp: { value: timestamp, color: "90" },
level: { value: "[ERROR]", color: "31" },
filename: { value: `(${filename})`, color: "36" },
message: { value: joinedMessage, color: "0" },
};
this.writeToLog(`${timestamp} [ERROR] (${filename}) ${joinedMessage}`);
this.writeConsoleMessageColored(logMessageParts, breakLine);
}
public custom(
bracketMessage: string,
bracketMessage2: string,
message: string | string[],
color: string,
breakLine = false,
): void {
const stack: string = new Error().stack || "";
const { timestamp } = this.getCallerInfo(stack);
const joinedMessage: string = Array.isArray(message)
? message.join(" ")
: message;
const logMessageParts: ILogMessageParts = {
readableTimestamp: { value: timestamp, color: "90" },
level: { value: bracketMessage, color },
filename: { value: `${bracketMessage2}`, color: "36" },
message: { value: joinedMessage, color: "0" },
};
this.writeToLog(
`${timestamp} ${bracketMessage} (${bracketMessage2}) ${joinedMessage}`,
);
this.writeConsoleMessageColored(logMessageParts, breakLine);
}
public space(): void {
console.log();
}
private writeConsoleMessageColored(
logMessageParts: ILogMessageParts,
breakLine = false,
): void {
const logMessage: string = Object.keys(logMessageParts)
.map((key: string) => {
const part: ILogMessagePart = logMessageParts[key];
return `\x1b[${part.color}m${part.value}\x1b[0m`;
})
.join(" ");
console.log(logMessage + (breakLine ? EOL : ""));
}
}
const logger: Logger = Logger.getInstance();
export { logger };

View file

@ -1,4 +1,4 @@
import { logger } from "@helpers/logger";
import { logger } from "@creations.works/logger";
import { serverHandler } from "@/server";
@ -10,3 +10,7 @@ main().catch((error: Error) => {
logger.error(["Error initializing the server:", error]);
process.exit(1);
});
if (process.env.IN_PTERODACTYL === "true") {
console.log("Server Started");
}

View file

@ -58,10 +58,15 @@ async function handler(request: ExtendedRequest): Promise<Response> {
validServices = badgeServices.map((b) => b.service);
}
const badges: BadgeResult = await fetchBadges(userId, validServices, {
nocache: cache !== "true",
separated: seperated === "true",
});
const badges: BadgeResult = await fetchBadges(
userId,
validServices,
{
nocache: cache !== "true",
separated: seperated === "true",
},
request,
);
if (badges instanceof Error) {
return Response.json(

View file

@ -1,6 +1,6 @@
import { resolve } from "node:path";
import { environment } from "@config/environment";
import { logger } from "@helpers/logger";
import { logger } from "@creations.works/logger";
import {
type BunFile,
FileSystemRouter,
@ -37,10 +37,9 @@ class ServerHandler {
},
});
logger.info(
`Server running at http://${server.hostname}:${server.port}`,
true,
);
logger.info(`Server running at http://${server.hostname}:${server.port}`, {
breakLine: true,
});
this.logRoutes();
}

View file

@ -1,4 +1,4 @@
import { logger } from "@helpers/logger";
import { logger } from "@creations.works/logger";
import type { ServerWebSocket } from "bun";
class WebSocketHandler {

11
types/badge.d.ts vendored
View file

@ -9,3 +9,14 @@ interface FetchBadgesOptions {
nocache?: boolean;
separated?: boolean;
}
type badgeURLMap = {
service: string;
url:
| string
| ((userId: string) => string)
| ((userId: string) => {
user: string;
badge: (id: string) => string;
});
};

6
types/config.d.ts vendored
View file

@ -3,9 +3,3 @@ type Environment = {
host: string;
development: boolean;
};
type badgeURLMap = {
service: string;
url: string | ((userId: string) => string);
authType: "none" | "DISCORD";
};