Files
equicord-plugins/badgesSelector/index.tsx
2025-12-27 17:29:54 +02:00

605 lines
21 KiB
TypeScript

/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import * as DataStore from "@api/DataStore";
import definePlugin from "@utils/types";
import { findStoreLazy } from "@webpack";
import { Menu, Toasts } from "@webpack/common";
const UserProfileStore = findStoreLazy("UserProfileStore");
const userBadgesMap = new Map<string, any[]>();
const removedBadgesMap = new Map<string, Set<string>>();
function getNitroSinceDate(months: number): string {
const currentDate = new Date();
const sinceDate = new Date(currentDate);
sinceDate.setMonth(currentDate.getMonth() - months);
const month = sinceDate.getMonth() + 1;
const day = sinceDate.getDate();
const year = sinceDate.getFullYear();
return `${month}/${day}/${year.toString().slice(-2)}`;
}
function getBoostSinceDate(months: number): string {
const currentDate = new Date();
const sinceDate = new Date(currentDate);
sinceDate.setMonth(currentDate.getMonth() - months);
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
return `${monthNames[sinceDate.getMonth()]} ${sinceDate.getDate()}, ${sinceDate.getFullYear()}`;
}
const availableBadges = [
{
id: "staff",
title: "Discord Staff",
description: "Discord Staff",
icon: "5e74e9b61934fc1f67c65515d1f7e60d",
link: "https://discord.com/company"
},
{
id: "premium",
title: "Nitro Subscriber",
description: "Subscriber since Dec 22, 2016",
icon: "2ba85e8026a8614b640c2837bcdfe21b",
link: "https://discord.com/settings/premium"
},
{
id: "premium_tenure_1_month_v2",
title: "Nitro Bronze (1 month)",
description: `Subscriber since ${getNitroSinceDate(1)}`,
icon: "4f33c4a9c64ce221936bd256c356f91f",
link: "https://discord.com/nitro"
},
{
id: "premium_tenure_3_month_v2",
title: "Nitro Silver (3 months)",
description: `Subscriber since ${getNitroSinceDate(3)}`,
icon: "4514fab914bdbfb4ad2fa23df76121a6",
link: "https://discord.com/nitro"
},
{
id: "premium_tenure_6_month_v2",
title: "Nitro Gold (6 months)",
description: `Subscriber since ${getNitroSinceDate(6)}`,
icon: "2895086c18d5531d499862e41d1155a6",
link: "https://discord.com/nitro"
},
{
id: "premium_tenure_12_month_v2",
title: "Nitro Platinum (1 year)",
description: `Subscriber since ${getNitroSinceDate(12)}`,
icon: "0334688279c8359120922938dcb1d6f8",
link: "https://discord.com/nitro"
},
{
id: "premium_tenure_24_month_v2",
title: "Nitro Diamond (2 years)",
description: `Subscriber since ${getNitroSinceDate(24)}`,
icon: "0d61871f72bb9a33a7ae568c1fb4f20a",
link: "https://discord.com/nitro"
},
{
id: "premium_tenure_36_month_v2",
title: "Nitro Emerald (3 years)",
description: `Subscriber since ${getNitroSinceDate(36)}`,
icon: "11e2d339068b55d3a506cff34d3780f3",
link: "https://discord.com/nitro"
},
{
id: "premium_tenure_60_month_v2",
title: "Nitro Ruby (5 years)",
description: `Subscriber since ${getNitroSinceDate(60)}`,
icon: "cd5e2cfd9d7f27a8cdcd3e8a8d5dc9f4",
link: "https://discord.com/nitro"
},
{
id: "premium_tenure_72_month_v2",
title: "Nitro Opal (6+ years)",
description: `Subscriber since ${getNitroSinceDate(72)}`,
icon: "5b154df19c53dce2af92c9b61e6be5e2",
link: "https://discord.com/nitro"
},
{
id: "partner",
title: "Partnered Server Owner",
description: "Partnered Server Owner",
icon: "3f9748e53446a137a052f3454e2de41e",
link: "https://discord.com/partners"
},
{
id: "certified_moderator",
title: "Moderator Programs Alumni",
description: "Moderator Programs Alumni",
icon: "fee1624003e2fee35cb398e125dc479b",
link: "https://discord.com/safety"
},
{
id: "hypesquad",
title: "HypeSquad Events",
description: "HypeSquad Events",
icon: "bf01d1073931f921909045f3a39fd264",
link: "https://discord.com/hypesquad"
},
{
id: "hypesquad_house_1",
title: "HypeSquad Bravery",
description: "HypeSquad Bravery",
icon: "8a88d63823d8a71cd5e390baa45efa02",
link: "https://discord.com/settings/hypesquad-online"
},
{
id: "hypesquad_house_2",
title: "HypeSquad Brilliance",
description: "HypeSquad Brilliance",
icon: "011940fd013da3f7fb926e4a1cd2e618",
link: "https://discord.com/settings/hypesquad-online"
},
{
id: "hypesquad_house_3",
title: "HypeSquad Balance",
description: "HypeSquad Balance",
icon: "3aa41de486fa12454c3761e8e223442e",
link: "https://discord.com/settings/hypesquad-online"
},
{
id: "bug_hunter_level_1",
title: "Discord Bug Hunter",
description: "Discord Bug Hunter",
icon: "2717692c7dca7289b35297368a940dd0",
link: "https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs"
},
{
id: "bug_hunter_level_2",
title: "Discord Bug Hunter Gold",
description: "Discord Bug Hunter",
icon: "848f79194d4be5ff5f81505cbd0ce1e6",
link: "https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs"
},
{
id: "active_developer",
title: "Active Developer",
description: "Active Developer",
icon: "6bdc42827a38498929a4920da12695d9",
link: "https://support-dev.discord.com/hc/en-us/articles/10113997751447?ref=badge"
},
{
id: "verified_developer",
title: "Early Verified Bot Developer",
description: "Early Verified Bot Developer",
icon: "6df5892e0f35b051f8b61eace34f4967",
link: "https://discord.com/developers"
},
{
id: "early_supporter",
title: "Early Supporter",
description: "Early Supporter",
icon: "7060786766c9c840eb3019e725d2b358",
link: "https://discord.com/settings/premium"
},
{
id: "guild_booster_lvl1",
title: "Server Boost 1 Month",
description: `Server boosting since ${getBoostSinceDate(1)}`,
icon: "51040c70d4f20a921ad6674ff86fc95c",
link: "https://discord.com/settings/premium"
},
{
id: "guild_booster_lvl2",
title: "Server Boost 2 Months",
description: `Server boosting since ${getBoostSinceDate(2)}`,
icon: "0e4080d1d333bc7ad29ef6528b6f2fb7",
link: "https://discord.com/settings/premium"
},
{
id: "guild_booster_lvl3",
title: "Server Boost 3 Months",
description: `Server boosting since ${getBoostSinceDate(3)}`,
icon: "72bed924410c304dbe3d00a6e593ff59",
link: "https://discord.com/settings/premium"
},
{
id: "guild_booster_lvl4",
title: "Server Boost 6 Months",
description: `Server boosting since ${getBoostSinceDate(6)}`,
icon: "df199d2050d3ed4ebf84d64ae83989f8",
link: "https://discord.com/settings/premium"
},
{
id: "guild_booster_lvl5",
title: "Server Boost 9 Months",
description: `Server boosting since ${getBoostSinceDate(9)}`,
icon: "996b3e870e8a22ce519b3a50e6bdd52f",
link: "https://discord.com/settings/premium"
},
{
id: "guild_booster_lvl6",
title: "Server Boost 12 Months",
description: `Server boosting since ${getBoostSinceDate(12)}`,
icon: "991c9f39ee33d7537d9f408c3e53141e",
link: "https://discord.com/settings/premium"
},
{
id: "guild_booster_lvl7",
title: "Server Boost 15 Months",
description: `Server boosting since ${getBoostSinceDate(15)}`,
icon: "cb3ae83c15e970e8f3d410bc62cb8b99",
link: "https://discord.com/settings/premium"
},
{
id: "guild_booster_lvl8",
title: "Server Boost 18 Months",
description: `Server boosting since ${getBoostSinceDate(18)}`,
icon: "7142225d31238f6387d9f09efaa02759",
link: "https://discord.com/settings/premium"
},
{
id: "guild_booster_lvl9",
title: "Server Boost 24 Months",
description: `Server boosting since ${getBoostSinceDate(24)}`,
icon: "ec92202290b48d0879b7413d2dde3bab",
link: "https://discord.com/settings/premium"
},
{
id: "legacy_username",
title: "Legacy Username",
description: "Originally known as their old username",
icon: "6de6d34650760ba5551a79732e98ed60",
link: "https://discord.com"
},
{
id: "quest_completed",
title: "Quest Completer",
description: "Completed a Quest",
icon: "7d9ae358c8c5e118768335dbe68b4fb8",
link: "https://discord.com/settings/inventory"
},
{
id: "bot_commands",
title: "Supports Commands",
description: "Supports Commands",
icon: "6f9e37f9029ff57aef81db857890005e",
link: "https://discord.com/blog/welcome-to-the-new-era-of-discord-apps?ref=badge"
},
{
id: "automod",
title: "Uses AutoMod",
description: "Uses automod",
icon: "f2459b691ac7453ed6039bbcfaccbfcd",
link: "https://discord.com"
},
{
id: "application_guild_subscription",
title: "Server Subscription",
description: "Monetized server",
icon: "d2010c413a8da2208b7e4311042a4b9d",
link: "https://discord.com"
},
{
id: "orb_profile_badge",
title: "Orbs",
description: "Collected the Orb Profile Badge",
icon: "83d8a1eb09a8d64e59233eec5d4d5c2d",
link: "https://discord.com"
}
];
async function loadData() {
const savedUserBadges = await DataStore.get("userBadges");
if (savedUserBadges) {
Object.entries(savedUserBadges).forEach(([userId, badges]) => {
const updatedBadges = (badges as any[]).map(badge => {
const freshBadge = availableBadges.find(b => b.id === badge.id);
if (freshBadge && (badge.id.startsWith("premium_tenure_") || badge.id.startsWith("guild_booster_"))) {
return { ...badge, description: freshBadge.description };
}
return badge;
});
userBadgesMap.set(userId, updatedBadges);
});
}
const savedRemovedBadges = await DataStore.get("removedBadges");
if (savedRemovedBadges) {
Object.entries(savedRemovedBadges).forEach(([userId, badgeIds]) => {
removedBadgesMap.set(userId, new Set(badgeIds as string[]));
});
}
}
async function saveData() {
const userBadgesObject = Object.fromEntries(userBadgesMap);
await DataStore.set("userBadges", userBadgesObject);
const removedBadgesObject = Object.fromEntries(
Array.from(removedBadgesMap.entries()).map(([userId, badgeIds]) => [userId, Array.from(badgeIds)])
);
await DataStore.set("removedBadges", removedBadgesObject);
}
function addBadgesToUser(userId: string, badges: any[]) {
const existingBadges = userBadgesMap.get(userId) || [];
badges.forEach(badge => {
const existingIndex = existingBadges.findIndex(b => b.id === badge.id);
if (existingIndex === -1) {
existingBadges.push(badge);
}
});
userBadgesMap.set(userId, existingBadges);
saveData();
}
function removeBadgeFromUser(userId: string, badgeId: string) {
const existingBadges = userBadgesMap.get(userId) || [];
const updatedBadges = existingBadges.filter(b => b.id !== badgeId);
if (updatedBadges.length > 0) {
userBadgesMap.set(userId, updatedBadges);
} else {
userBadgesMap.delete(userId);
}
saveData();
}
function getUserBadges(userId: string): any[] {
return userBadgesMap.get(userId) || [];
}
function hideRealBadge(userId: string, badgeId: string) {
if (!removedBadgesMap.has(userId)) {
removedBadgesMap.set(userId, new Set());
}
removedBadgesMap.get(userId)!.add(badgeId);
saveData();
}
function unhideRealBadge(userId: string, badgeId: string) {
if (removedBadgesMap.has(userId)) {
removedBadgesMap.get(userId)!.delete(badgeId);
if (removedBadgesMap.get(userId)!.size === 0) {
removedBadgesMap.delete(userId);
}
saveData();
}
}
function isRealBadgeHidden(userId: string, badgeId: string): boolean {
return removedBadgesMap.has(userId) && removedBadgesMap.get(userId)!.has(badgeId);
}
function toggleBadge(userId: string, badge: any, hasActualBadge: boolean) {
const hasUserBadge = getUserBadges(userId).find(b => b.id === badge.id);
const isHidden = isRealBadgeHidden(userId, badge.id);
if (hasActualBadge) {
if (isHidden) {
unhideRealBadge(userId, badge.id);
Toasts.show({
message: `Restored ${badge.title}`,
type: Toasts.Type.SUCCESS,
id: Toasts.genId()
});
} else {
hideRealBadge(userId, badge.id);
Toasts.show({
message: `Removed ${badge.title}`,
type: Toasts.Type.SUCCESS,
id: Toasts.genId()
});
}
} else {
if (hasUserBadge) {
removeBadgeFromUser(userId, badge.id);
Toasts.show({
message: `Removed ${badge.title}`,
type: Toasts.Type.SUCCESS,
id: Toasts.genId()
});
} else {
addBadgesToUser(userId, [badge]);
Toasts.show({
message: `Added ${badge.title}`,
type: Toasts.Type.SUCCESS,
id: Toasts.genId()
});
}
}
}
function giveAllBadges(userId: string) {
addBadgesToUser(userId, availableBadges);
Toasts.show({
message: "Added all badges",
type: Toasts.Type.SUCCESS,
id: Toasts.genId()
});
}
function addUserBadge(userId: string) {
const badgeId = prompt("Enter badge ID:");
if (!badgeId) return;
const description = prompt("Enter badge description:");
if (!description) return;
const icon = prompt("Enter badge icon hash:");
if (!icon) return;
const link = prompt("Enter badge link (optional):", "https://discord.com");
const userBadge = {
id: badgeId,
description: description,
icon: icon,
link: link || "https://discord.com"
};
addBadgesToUser(userId, [userBadge]);
Toasts.show({
message: `Added badge: ${badgeId}`,
type: Toasts.Type.SUCCESS,
id: Toasts.genId()
});
}
let originalGetUserProfile: any;
export default definePlugin({
name: "BadgeSelector",
description: "Customize user badges - add, remove, or hide any Discord badge",
authors: [
{ id: 1263457746829705310n, name: ".q1" },
{ id: 1147940825330876538n, name: "Jelly" },
{ id: 1403404140461297816n, name: "Sami" },
],
async start() {
await loadData();
originalGetUserProfile = UserProfileStore.getUserProfile;
UserProfileStore.getUserProfile = function (userId: string) {
const userProfile = originalGetUserProfile.call(this, userId);
if (!userProfile) return userProfile;
let newBadges = [...(userProfile.badges || [])];
if (removedBadgesMap.has(userId)) {
const hiddenBadges = removedBadgesMap.get(userId)!;
newBadges = newBadges.filter(b => !hiddenBadges.has(b.id));
}
if (userBadgesMap.has(userId)) {
const userBadges = userBadgesMap.get(userId)!;
userBadges.forEach(badgeObj => {
const { id, description, icon, link } = badgeObj;
const badgeIndex = newBadges.findIndex(b => b.id === id);
if (badgeIndex === -1) {
newBadges.push({ id, description, icon, link });
}
});
}
newBadges.sort((a, b) => {
const orderA = availableBadges.findIndex(badge => badge.id === a.id);
const orderB = availableBadges.findIndex(badge => badge.id === b.id);
const finalOrderA = orderA === -1 ? 9999 : orderA;
const finalOrderB = orderB === -1 ? 9999 : orderB;
return finalOrderA - finalOrderB;
});
return {
...userProfile,
badges: newBadges
};
};
},
stop() {
if (originalGetUserProfile) {
UserProfileStore.getUserProfile = originalGetUserProfile;
}
userBadgesMap.clear();
removedBadgesMap.clear();
},
contextMenus: {
"user-context"(children, { user }) {
if (!user) return;
const currentUserBadges = getUserBadges(user.id);
const userProfile = originalGetUserProfile ? originalGetUserProfile.call(UserProfileStore, user.id) : UserProfileStore.getUserProfile(user.id);
const actualBadges = userProfile?.badges || [];
children.push(
<Menu.MenuItem
label="Add Badge"
key="add-custom-badge"
id="user-context-add-custom"
color="brand"
action={() => addUserBadge(user.id)}
/>,
<Menu.MenuItem
label="Manage Badges"
key="custom-badges"
id="user-context-custom-badges"
>
{availableBadges.map(badge => {
const hasUserBadge = currentUserBadges.find(b => b.id === badge.id);
const hasActualBadge = actualBadges.find(b => b.id === badge.id);
const isHidden = isRealBadgeHidden(user.id, badge.id);
const isChecked = !!hasUserBadge || (!!hasActualBadge && !isHidden);
return (
<Menu.MenuCheckboxItem
key={badge.id}
id={`badge-${badge.id}`}
// edited here to fix some error
// label={
// <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
// <img
// src={`https://cdn.discordapp.com/badge-icons/${badge.icon}.png`}
// alt=""
// width="18"
// height="18"
// style={{
// borderRadius: "3px",
// imageRendering: "crisp-edges"
// }}
// />
// <span>{badge.title}</span>
// </div>
// }
label={badge.title}
checked={isChecked}
action={() => toggleBadge(user.id, badge, !!hasActualBadge)}
/>
);
})}
<Menu.MenuSeparator />
<Menu.MenuItem
label="Give All Badges"
id="give-all-badges"
color="brand"
action={() => giveAllBadges(user.id)}
/>
<Menu.MenuItem
label="Remove All Badges"
id="remove-all-badges"
color="danger"
action={() => {
actualBadges.forEach(badge => {
hideRealBadge(user.id, badge.id);
});
userBadgesMap.delete(user.id);
Toasts.show({
message: "Removed all badges",
type: Toasts.Type.SUCCESS,
id: Toasts.genId()
});
}}
/>
<Menu.MenuItem
label="Reset to Original"
id="reset-badges"
color="default"
action={() => {
userBadgesMap.delete(user.id);
removedBadgesMap.delete(user.id);
Toasts.show({
message: "Reset badges to original state",
type: Toasts.Type.SUCCESS,
id: Toasts.genId()
});
}}
/>
</Menu.MenuItem>
);
}
}
});