move globalbadges api (#429)

* move globalbadges api

* Update API URL for badge images

* bleh

* bleh

* bleh: the reckoning

* bleh: the final remorse

---------

Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com>
This commit is contained in:
creations 2025-10-01 10:39:10 -04:00 • committed by GitHub
parent 628893f4d2
commit 02e9bf6b19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 301 additions and 199 deletions

View file

@ -0,0 +1,73 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { React, Tooltip, UserStore } from "@webpack/common";
import { JSX } from "react";
import { BadgeModalComponent, openBadgeModal } from "./badgeModal";
import { settings } from "./settings";
import { BadgeCache } from "./types";
import { fetchBadges, serviceMap } from "./utils";
export let badgeImages;
export const BadgeComponent = ({ name, img }: { name: string, img: string; }) => {
return (
<Tooltip text={name} >
{(tooltipProps: any) => (
<img
{...tooltipProps}
src={img}
style={{
width: "20px",
height: "20px",
transform: name.includes("Replugged") ? "scale(0.9)" : null
}}
/>
)}
</Tooltip>
);
};
export const GlobalBadges = ({ userId }: { userId: string; }) => {
const [badges, setBadges] = React.useState<BadgeCache["badges"]>({});
React.useEffect(() => setBadges(fetchBadges(userId) ?? {}), [userId]);
if (!badges) return null;
const globalBadges: JSX.Element[] = [];
const badgeModal: JSX.Element[] = [];
Object.keys(badges).forEach(mod => {
if (!badges[mod] || !Array.isArray(badges[mod]) || badges[mod].length === 0) return;
badges[mod].forEach(badge => {
if (!badge || !badge.tooltip || !badge.badge) return;
const modDisplay = serviceMap[mod.toLowerCase()] || mod;
const prefix = settings.store.showPrefix ? `(${modDisplay})` : "";
const suffix = settings.store.showSuffix ? `(${modDisplay})` : "";
const displayName = `${prefix} ${badge.tooltip} ${suffix}`;
if (mod.toLowerCase() === "badgevault") {
badge.custom = true;
}
globalBadges.push(<BadgeComponent name={displayName} img={badge.badge} />);
badgeModal.push(<BadgeModalComponent name={displayName} img={badge.badge} />);
});
});
badgeImages = badgeModal;
return (
<div
className="vc-global-badges"
style={{ alignItems: "center", display: "flex" }}
onClick={_ => openBadgeModal(UserStore.getUser(userId))}
>
{globalBadges}
</div>
);
};

View file

@ -0,0 +1,68 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import ErrorBoundary from "@components/ErrorBoundary";
import { ModalContent, ModalRoot, openModal } from "@utils/modal";
import { User } from "@vencord/discord-types";
import { Forms, React, Tooltip } from "@webpack/common";
import { badgeImages } from "./badgeComponent";
import { cl } from "./utils";
export const BadgeModalComponent = ({ name, img }: { name: string, img: string; }) => {
return (
<Tooltip text={name} >
{(tooltipProps: any) => (
<img
{...tooltipProps}
src={img}
style={{ width: "50px", height: "50px", margin: "2px 2px" }}
/>
)}
</Tooltip>
);
};
export function BadgeModal({ user }: { user: User; }) {
return (
<>
<div className={cl("header")}>
<img
className={cl("avatar")}
src={user.getAvatarURL(void 0, 512, true)}
alt=""
/>
<Forms.FormTitle tag="h2" className={cl("name")}>{user.username}</Forms.FormTitle>
</div>
{badgeImages.length ? (
<Forms.FormText>
{user.username} has {badgeImages.length} global badges.
</Forms.FormText>
) : (
<Forms.FormText>
{user.username} has no global badges.
</Forms.FormText>
)}
{!!badgeImages.length && (
<div className={cl("badges")}>
{badgeImages}
</div>
)}
</>
);
}
export function openBadgeModal(user: User) {
openModal(modalprops =>
<ModalRoot {...modalprops}>
<ErrorBoundary>
<ModalContent className={cl("root")}>
<BadgeModal user={user} />
</ModalContent>
</ErrorBoundary>
</ModalRoot>
);
}

View file

@ -1,126 +1,17 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { addProfileBadge, BadgePosition, ProfileBadge, removeProfileBadge } from "@api/Badges";
import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs, EquicordDevs } from "@utils/constants";
import { ModalContent, ModalRoot, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { User } from "@vencord/discord-types";
import { Forms, React, Tooltip, UserStore } from "@webpack/common";
import { JSX } from "react";
import definePlugin from "@utils/types";
import { React } from "@webpack/common";
type CustomBadge = string | {
name: string;
badge: string;
custom?: boolean;
};
interface BadgeCache {
badges: { [mod: string]: CustomBadge[]; };
expires: number;
}
let badgeImages;
// const API_URL = "https://clientmodbadges-api.herokuapp.com/";
const API_URL = "https://globalbadges.equicord.org/";
const cache = new Map<string, BadgeCache>();
const EXPIRES = 1000 * 60 * 15;
const fetchBadges = (id: string): BadgeCache["badges"] | undefined => {
const cachedValue = cache.get(id);
if (!cache.has(id) || (cachedValue && cachedValue.expires < Date.now())) {
fetch(`${API_URL}users/${id}`)
.then(res => res.json() as Promise<BadgeCache["badges"]>)
.then(body => {
cache.set(id, { badges: body, expires: Date.now() + EXPIRES });
return body;
})
.catch(() => null);
} else if (cachedValue) {
return cachedValue.badges;
}
};
const BadgeComponent = ({ name, img }: { name: string, img: string; }) => {
return (
<Tooltip text={name} >
{(tooltipProps: any) => (
<img
{...tooltipProps}
src={img}
style={{
width: "20px",
height: "20px",
transform: name.includes("Replugged") ? "scale(0.9)" : null
}}
/>
)}
</Tooltip>
);
};
const GlobalBadges = ({ userId }: { userId: string; }) => {
const [badges, setBadges] = React.useState<BadgeCache["badges"]>({});
React.useEffect(() => setBadges(fetchBadges(userId) ?? {}), [userId]);
if (!badges) return null;
const globalBadges: JSX.Element[] = [];
const badgeModal: JSX.Element[] = [];
Object.keys(badges).forEach(mod => {
if (mod.toLowerCase() === "vencord") return;
if (mod.toLowerCase() === "equicord") return;
if (mod.toLowerCase() === "badgevault" && !settings.store.showCustom) return;
badges[mod].forEach(badge => {
if (typeof badge === "string") {
const fullNames = { "hunter": "Bug Hunter", "early": "Early User" };
badge = {
name: fullNames[badge as string] ? fullNames[badge as string] : badge,
badge: `${API_URL}badges/${mod}/${(badge as string).replace(mod, "").trim().split(" ")[0]}`
};
} else if (typeof badge === "object") badge.custom = true;
const cleanName = badge.name.replace(mod, "").trim();
const prefix = settings.store.showPrefix ? mod : "";
if (!badge.custom) badge.name = `${prefix} ${cleanName.charAt(0).toUpperCase() + cleanName.slice(1)}`;
if (badge.custom) {
if (cleanName.toLowerCase().includes(mod)) return;
else if (prefix) badge.name = `${cleanName} (${prefix})`.replaceAll(" ()", "");
}
globalBadges.push(<BadgeComponent name={badge.name} img={badge.badge} />);
badgeModal.push(<BadgeModalComponent name={badge.name} img={badge.badge} />);
});
});
badgeImages = badgeModal;
return (
<div
className="vc-global-badges"
style={{ alignItems: "center", display: "flex" }}
onClick={_ => openBadgeModal(UserStore.getUser(userId))}
>
{globalBadges}
</div>
);
};
import { GlobalBadges } from "./badgeComponent";
import { settings } from "./settings";
import { fetchBadges } from "./utils";
const Badge: ProfileBadge = {
component: b => <GlobalBadges {...b} />,
@ -129,21 +20,6 @@ const Badge: ProfileBadge = {
key: "GlobalBadges"
};
const settings = definePluginSettings({
showPrefix: {
type: OptionType.BOOLEAN,
description: "Shows the Mod as Prefix",
default: true,
restartNeeded: false
},
showCustom: {
type: OptionType.BOOLEAN,
description: "Show Custom Badges",
default: true,
restartNeeded: false
}
});
export default definePlugin({
name: "GlobalBadges",
description: "Adds global badges from other client mods",
@ -152,69 +28,3 @@ export default definePlugin({
start: () => addProfileBadge(Badge),
stop: () => removeProfileBadge(Badge),
});
/*
Badge duping fix for modal lines below
L39 the value for everything below
L81 for not reusing globalbadges const
L100 for the size of the badges
L103 actual dupe fix
L109 is when clicking the badge open the modal
Everything below is related to the badge modal
*/
const cl = classNameFactory("vc-author-modal-");
const BadgeModalComponent = ({ name, img }: { name: string, img: string; }) => {
return (
<Tooltip text={name} >
{(tooltipProps: any) => (
<img
{...tooltipProps}
src={img}
style={{ width: "50px", height: "50px", margin: "2px 2px" }}
/>
)}
</Tooltip>
);
};
function BadgeModal({ user }: { user: User; }) {
return (
<>
<div className={cl("header")}>
<img
className={cl("avatar")}
src={user.getAvatarURL(void 0, 512, true)}
alt=""
/>
<Forms.FormTitle tag="h2" className={cl("name")}>{user.username}</Forms.FormTitle>
</div>
{badgeImages.length ? (
<Forms.FormText>
{user.username} has {badgeImages.length} global badges.
</Forms.FormText>
) : (
<Forms.FormText>
{user.username} has no global badges.
</Forms.FormText>
)}
{!!badgeImages.length && (
<div className={cl("badges")}>
{badgeImages}
</div>
)}
</>
);
}
function openBadgeModal(user: User) {
openModal(modalprops =>
<ModalRoot {...modalprops}>
<ErrorBoundary>
<ModalContent className={cl("root")}>
<BadgeModal user={user} />
</ModalContent>
</ErrorBoundary>
</ModalRoot>
);
}

View file

@ -0,0 +1,77 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { definePluginSettings } from "@api/Settings";
import { OptionType } from "@utils/types";
export const settings = definePluginSettings({
showPrefix: {
type: OptionType.BOOLEAN,
description: "Shows the Mod as Prefix",
default: true,
restartNeeded: false
},
showSuffix: {
type: OptionType.BOOLEAN,
description: "Shows the Mod as Suffix",
default: false,
restartNeeded: false
},
showCustom: {
type: OptionType.BOOLEAN,
description: "Show Custom Badges",
default: true,
restartNeeded: false
},
showNekocord: {
type: OptionType.BOOLEAN,
description: "Show Nekocord Badges",
default: true,
restartNeeded: false
},
showReviewDB: {
type: OptionType.BOOLEAN,
description: "Show ReviewDB Badges",
default: true,
restartNeeded: false
},
showAero: {
type: OptionType.BOOLEAN,
description: "Show Aero Badges",
default: true,
restartNeeded: false
},
showAliucord: {
type: OptionType.BOOLEAN,
description: "Show Aliucord Badges",
default: true,
restartNeeded: false
},
showRa1ncord: {
type: OptionType.BOOLEAN,
description: "Show Ra1ncord Badges",
default: true,
restartNeeded: false
},
showVelocity: {
type: OptionType.BOOLEAN,
description: "Show Velocity Badges",
default: true,
restartNeeded: false
},
showEnmity: {
type: OptionType.BOOLEAN,
description: "Show Enmity Badges",
default: true,
restartNeeded: false
},
showReplugged: {
type: OptionType.BOOLEAN,
description: "Show Replugged Badges",
default: true,
restartNeeded: false
}
});

View file

@ -0,0 +1,16 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
export type CustomBadge = {
tooltip: string;
badge: string;
custom?: boolean;
};
export interface BadgeCache {
badges: { [mod: string]: CustomBadge[]; };
expires: number;
}

View file

@ -0,0 +1,58 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { classNameFactory } from "@api/Styles";
import { settings } from "./settings";
import { BadgeCache } from "./types";
const API_URL = "https://badges.equicord.org/";
const cache = new Map<string, BadgeCache>();
const EXPIRES = 1000 * 60 * 15;
export const cl = classNameFactory("vc-author-modal-");
export const serviceMap = {
"nekocord": "Nekocord",
"reviewdb": "ReviewDB",
"aero": "Aero",
"aliucord": "Aliucord",
"ra1ncord": "Ra1ncord",
"velocity": "Velocity",
"enmity": "Enmity",
"replugged": "Replugged",
"badgevault": "BadgeVault"
};
export const fetchBadges = (id: string): BadgeCache["badges"] | undefined => {
const cachedValue = cache.get(id);
if (!cache.has(id) || (cachedValue && cachedValue.expires < Date.now())) {
const services: string[] = [];
if (settings.store.showNekocord) services.push("nekocord");
if (settings.store.showReviewDB) services.push("reviewdb");
if (settings.store.showAero) services.push("aero");
if (settings.store.showAliucord) services.push("aliucord");
if (settings.store.showRa1ncord) services.push("ra1ncord");
if (settings.store.showVelocity) services.push("velocity");
if (settings.store.showEnmity) services.push("enmity");
if (settings.store.showReplugged) services.push("replugged");
if (settings.store.showCustom) services.push("badgevault");
if (services.length === 0) {
cache.set(id, { badges: {}, expires: Date.now() + EXPIRES });
return {};
}
fetch(`${API_URL}${id}?seperated=true&services=${services.join(",")}`)
.then(res => res.json() as Promise<{ status: number; badges: BadgeCache["badges"]; }>)
.then(body => {
cache.set(id, { badges: body.badges, expires: Date.now() + EXPIRES });
return body.badges;
})
.catch(() => null);
} else if (cachedValue) {
return cachedValue.badges;
}
};