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:
parent
628893f4d2
commit
02e9bf6b19
6 changed files with 301 additions and 199 deletions
73
src/equicordplugins/globalBadges/badgeComponent.tsx
Normal file
73
src/equicordplugins/globalBadges/badgeComponent.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
68
src/equicordplugins/globalBadges/badgeModal.tsx
Normal file
68
src/equicordplugins/globalBadges/badgeModal.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
77
src/equicordplugins/globalBadges/settings.ts
Normal file
77
src/equicordplugins/globalBadges/settings.ts
Normal 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
|
||||
}
|
||||
});
|
||||
16
src/equicordplugins/globalBadges/types.ts
Normal file
16
src/equicordplugins/globalBadges/types.ts
Normal 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;
|
||||
}
|
||||
58
src/equicordplugins/globalBadges/utils.ts
Normal file
58
src/equicordplugins/globalBadges/utils.ts
Normal 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;
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue