diff --git a/src/api/Badges.ts b/src/api/Badges.ts new file mode 100644 index 00000000..542f78d8 --- /dev/null +++ b/src/api/Badges.ts @@ -0,0 +1,100 @@ +/* + * 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 . +*/ + +import { User } from "discord-types/general"; +import { HTMLProps } from "react"; + +export enum BadgePosition { + START, + END +} + +export interface ProfileBadge { + /** The tooltip to show on hover */ + tooltip: string; + /** The custom image to use */ + image?: string; + /** Action to perform when you click the badge */ + onClick?(): void; + /** Should the user display this badge? */ + shouldShow?(userInfo: BadgeUserArgs): boolean; + /** Optional props (e.g. style) for the badge */ + props?: HTMLProps; + /** Insert at start or end? */ + position?: BadgePosition; + + /** The badge name to display. Discord uses this, but we don't. */ + key?: string; +} + +const Badges = new Set(); + +/** + * Register a new badge with the Badges API + * @param badge The badge to register + */ +export function addBadge(badge: ProfileBadge) { + Badges.add(badge); +} + +/** + * Unregister a badge from the Badges API + * @param badge The badge to remove + */ +export function removeBadge(badge: ProfileBadge) { + return Badges.delete(badge); +} + +/** + * Inject badges into the profile badges array. + * You probably don't need to use this. + */ +export function inject(badgeArray: ProfileBadge[], args: BadgeUserArgs) { + for (const badge of Badges) { + if (!badge.shouldShow || badge.shouldShow(args)) { + badge.position === BadgePosition.START + ? badgeArray.unshift(badge) + : badgeArray.push(badge); + } + } + return badgeArray; +} + +export interface BadgeUserArgs { + user: User; + profile: Profile; + premiumSince: Date; + premiumGuildSince?: Date; +} + +interface ConnectedAccount { + type: string; + id: string; + name: string; + verified: boolean; +} + +interface Profile { + connectedAccounts: ConnectedAccount[]; + premiumType: number; + premiumSince: string; + premiumGuildSince?: any; + lastFetched: number; + profileFetchFailed: boolean; + application?: any; +} diff --git a/src/api/index.ts b/src/api/index.ts index 73dafc93..bde4b3e7 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import * as $Badges from "./Badges"; import * as $Commands from "./Commands"; import * as $DataStore from "./DataStore"; import * as $MessageAccessories from "./MessageAccessories"; @@ -57,5 +58,9 @@ const DataStore = $DataStore; * An API allowing you to add custom components as message accessories */ const MessageAccessories = $MessageAccessories; +/** + * An API allowing you to add badges to user profiles + */ +const Badges = $Badges; -export { Commands,DataStore, MessageAccessories, MessageEvents, Notices }; +export { Badges, Commands, DataStore, MessageAccessories, MessageEvents, Notices }; diff --git a/src/plugins/apiBadges.tsx b/src/plugins/apiBadges.tsx new file mode 100644 index 00000000..e26a7a1b --- /dev/null +++ b/src/plugins/apiBadges.tsx @@ -0,0 +1,70 @@ +/* + * 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 . +*/ + +import { BadgePosition, ProfileBadge } from "../api/Badges"; +import { Devs } from "../utils/constants"; +import IpcEvents from "../utils/IpcEvents"; +import definePlugin from "../utils/types"; + +const CONTRIBUTOR_BADGE = "https://media.discordapp.net/stickers/1026517526106087454.webp"; + +/** List of vencord contributor IDs */ +const contributorIds: string[] = Object.values(Devs).map(d => d.id.toString()); + +const ContributorBadge: ProfileBadge = { + tooltip: "Vencord Contributor", + image: CONTRIBUTOR_BADGE, + position: BadgePosition.START, + props: { + style: { + borderRadius: "50%", + transform: "scale(0.9)" // The image is a bit too big compared to default badges + } + }, + shouldShow: ({ user }) => contributorIds.includes(user.id), + onClick: () => VencordNative.ipc.invoke(IpcEvents.OPEN_EXTERNAL, "https://github.com/Vendicated/Vencord") +}; + +export default definePlugin({ + name: "BadgeAPI", + description: "API to add badges to users.", + authors: [Devs.Megu], + required: true, + patches: [ + /* Patch the badges array */ + { + find: "PREMIUM_GUILD_SUBSCRIPTION_TOOLTIP.format({date:", + replacement: { + match: /&&((\w{1,3})\.push\({tooltip:\w{1,3}\.\w{1,3}\.Messages\.PREMIUM_GUILD_SUBSCRIPTION_TOOLTIP\.format.+?;)(?:return\s\w{1,3};?})/, + replace: (_, m, badgeArray) => `&&${m} return Vencord.Api.Badges.inject(${badgeArray}, arguments[0]);}`, + } + }, + /* Patch the badge list component on user profiles */ + { + find: "Messages.PROFILE_USER_BADGES,role:", + replacement: { + match: /src:(\w{1,3})\[(\w{1,3})\.key\],/, + // + replace: (_, imageMap, badge) => `src: ${badge}.image ?? ${imageMap}[${badge}.key], ...${badge}.props,` + } + } + ], + start() { + Vencord.Api.Badges.addBadge(ContributorBadge); + }, +}); diff --git a/src/plugins/banger.ts b/src/plugins/banger.ts index 59d9b421..62632248 100644 --- a/src/plugins/banger.ts +++ b/src/plugins/banger.ts @@ -22,13 +22,7 @@ import definePlugin, { OptionType } from "../utils/types"; export default definePlugin({ name: "BANger", description: "Replaces the GIF in the ban dialogue with a custom one.", - authors: [ - { - name: "Xinto", - id: 423915768191647755n - }, - Devs.Glitch - ], + authors: [Devs.Xinto, Devs.Glitch], patches: [ { find: "BAN_CONFIRM_TITLE.", diff --git a/src/plugins/clearURLs/index.ts b/src/plugins/clearURLs/index.ts index be0b8029..b01faedb 100644 --- a/src/plugins/clearURLs/index.ts +++ b/src/plugins/clearURLs/index.ts @@ -23,6 +23,7 @@ import { removePreEditListener, removePreSendListener, } from "../../api/MessageEvents"; +import { Devs } from "../../utils/constants"; import definePlugin from "../../utils/types"; import { defaultRules } from "./defaultRules"; @@ -33,12 +34,7 @@ const reHasRegExpChar = RegExp(reRegExpChar.source); export default definePlugin({ name: "clearURLs", description: "Removes tracking garbage from URLs", - authors: [ - { - name: "adryd", - id: 0n, - }, - ], + authors: [Devs.adryd], dependencies: ["MessageEventsAPI"], escapeRegExp(str: string) { diff --git a/src/plugins/experiments.tsx b/src/plugins/experiments.tsx index 861bb051..4f83c5a2 100644 --- a/src/plugins/experiments.tsx +++ b/src/plugins/experiments.tsx @@ -31,7 +31,7 @@ export default definePlugin({ Devs.Megu, Devs.Ven, Devs.Nickyux, - { name: "BanTheNons", id: 460478012794863637n }, + Devs.BanTheNons ], description: "Enable Access to Experiments in Discord!", patches: [{ diff --git a/src/plugins/iLoveSpam.ts b/src/plugins/iLoveSpam.ts index 17753269..7f390f34 100644 --- a/src/plugins/iLoveSpam.ts +++ b/src/plugins/iLoveSpam.ts @@ -22,10 +22,7 @@ import definePlugin from "../utils/types"; export default definePlugin({ name: "iLoveSpam", description: "Do not hide messages from 'likely spammers'", - authors: [ - Devs.botato, - Devs.Animal, - ], + authors: [Devs.botato, Devs.Animal], patches: [ { find: "),{hasFlag:", diff --git a/src/plugins/moarKaomojis.ts b/src/plugins/moarKaomojis.ts index 839be6a9..8b3c0c58 100644 --- a/src/plugins/moarKaomojis.ts +++ b/src/plugins/moarKaomojis.ts @@ -16,18 +16,14 @@ * along with this program. If not, see . */ -import { findOption,OptionalMessageOption } from "../api/Commands"; +import { findOption, OptionalMessageOption } from "../api/Commands"; +import { Devs } from "../utils/constants"; import definePlugin from "../utils/types"; export default definePlugin({ name: "moarKaomojis", description: "Adds more Kaomojis to discord. ヽ(´▽`)/", - authors: [ - { - name: "Jacob.Tm", - id: 302872992097107991n - } - ], + authors: [Devs.JacobTm], dependencies: ["CommandsAPI"], commands: [ { name: "dissatisfaction", description: " >﹏<" }, diff --git a/src/plugins/moreCommands.ts b/src/plugins/moreCommands.ts index 074e7159..0a23aafb 100644 --- a/src/plugins/moreCommands.ts +++ b/src/plugins/moreCommands.ts @@ -32,14 +32,7 @@ function mock(input: string): string { export default definePlugin({ name: "MoreCommands", description: "echo, lenny, mock", - authors: [ - Devs.Arjix, - Devs.echo, - { - name: "ICodeInAssembly", - id: 702973430449832038n - } - ], + authors: [Devs.Arjix, Devs.echo, Devs.Samu], dependencies: ["CommandsAPI"], commands: [ { diff --git a/src/plugins/nitroBypass.ts b/src/plugins/nitroBypass.ts index 937b8d05..dfdfe9d2 100644 --- a/src/plugins/nitroBypass.ts +++ b/src/plugins/nitroBypass.ts @@ -52,11 +52,7 @@ interface StickerPack { export default definePlugin({ name: "NitroBypass", - authors: [ - Devs.Arjix, - Devs.D3SOX, - Devs.Ven - ], + authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven], description: "Allows you to stream in nitro quality and send fake emojis/stickers.", dependencies: ["MessageEventsAPI"], diff --git a/src/plugins/noCanaryMessageLinks.ts b/src/plugins/noCanaryMessageLinks.ts index 807c8643..67c91762 100644 --- a/src/plugins/noCanaryMessageLinks.ts +++ b/src/plugins/noCanaryMessageLinks.ts @@ -24,10 +24,7 @@ import { Settings } from "../Vencord"; export default definePlugin({ name: "NoCanaryMessageLinks", description: "Allows you to change/remove the subdomain of discord message and channel links", - authors: [ - Devs.Samu, - Devs.nea, - ], + authors: [Devs.Samu, Devs.nea], options: { linkPrefix: { description: "The subdomain for your discord message links", diff --git a/src/plugins/noReplyMention.ts b/src/plugins/noReplyMention.ts index d516450a..2e5e9695 100644 --- a/src/plugins/noReplyMention.ts +++ b/src/plugins/noReplyMention.ts @@ -16,15 +16,13 @@ * along with this program. If not, see . */ +import { Devs } from "../utils/constants"; import definePlugin from "../utils/types"; export default definePlugin({ name: "NoReplyMention", description: "Disables reply pings by default", - authors: [{ - name: "DustyAngel47", - id: 714583473804935238n - }], + authors: [Devs.DustyAngel47], patches: [ { find: "CREATE_PENDING_REPLY:function", diff --git a/src/plugins/pronoundb/index.ts b/src/plugins/pronoundb/index.ts index 802ede9c..39637e2e 100644 --- a/src/plugins/pronoundb/index.ts +++ b/src/plugins/pronoundb/index.ts @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import { Devs } from "../../utils/constants"; import definePlugin, { OptionType } from "../../utils/types"; import PronounsAboutComponent from "./components/PronounsAboutComponent"; import PronounsChatComponent from "./components/PronounsChatComponent"; @@ -28,10 +29,7 @@ export enum PronounsFormat { export default definePlugin({ name: "PronounDB", - authors: [{ - name: "Tyman", - id: 487443883127472129n - }], + authors: [Devs.Tyman], description: "Adds pronouns to user messages using pronoundb", patches: [ // Patch the chat timestamp element diff --git a/src/plugins/showHiddenChannels.tsx b/src/plugins/showHiddenChannels.tsx index 7f2c4b80..8dc92bfe 100644 --- a/src/plugins/showHiddenChannels.tsx +++ b/src/plugins/showHiddenChannels.tsx @@ -34,17 +34,7 @@ waitFor(m => m.can && m.initialize, m => ({ can } = m)); export default definePlugin({ name: "ShowHiddenChannels", description: "Show hidden channels", - authors: [ - { - name: "BigDuck", - id: 1024588272623681609n - }, - { - name: "Average React Enjoyer", - id: 1004904120056029256n - }, - Devs.D3SOX, - ], + authors: [Devs.BigDuck, Devs.AverageReactEnjoyer, Devs.D3SOX], options: { hideUnreads: { description: "Hide unreads", diff --git a/src/plugins/spotifyControls/index.tsx b/src/plugins/spotifyControls/index.tsx index 6bf66253..e8b53702 100644 --- a/src/plugins/spotifyControls/index.tsx +++ b/src/plugins/spotifyControls/index.tsx @@ -23,17 +23,7 @@ import { Player } from "./PlayerComponent"; export default definePlugin({ name: "SpotifyControls", description: "Spotify Controls", - authors: [ - Devs.Ven, - { - id: 420043923822608384n, - name: "afn", - }, - { - id: 379304073515499530n, - name: "KraXen72" - } - ], + authors: [Devs.Ven, Devs.afn, Devs.KraXen72], dependencies: ["MenuItemDeobfuscatorApi"], patches: [ { diff --git a/src/plugins/uwuify.ts b/src/plugins/uwuify.ts index 9f4ac7e7..804c7417 100644 --- a/src/plugins/uwuify.ts +++ b/src/plugins/uwuify.ts @@ -17,6 +17,7 @@ */ import { findOption, RequiredMessageOption } from "../api/Commands"; +import { Devs } from "../utils/constants"; import definePlugin from "../utils/types"; const endings = [ @@ -109,10 +110,7 @@ function uwuify(message: string): string { export default definePlugin({ name: "UwUifier", description: "Simply uwuify commands", - authors: [{ - name: "ECHO", - id: 712639419785412668n - }], + authors: [Devs.echo], dependencies: ["CommandsAPI"], commands: [ diff --git a/src/plugins/vcDoubleClick.ts b/src/plugins/vcDoubleClick.ts index 554b31a2..b1a05581 100644 --- a/src/plugins/vcDoubleClick.ts +++ b/src/plugins/vcDoubleClick.ts @@ -28,10 +28,7 @@ const timers = {} as Record