Plugin

USRBG

Displays user banners from USRBG, allowing anyone to get a banner without Nitro

Appearance Customisation
index.tsx
Download

Source

src/plugins/usrbg/index.tsx
1/*
2 * FiveCord — a Discord client mod
3 * Copyright (c) 2025 FiveCord
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
7import { definePluginSettings } from "@api/Settings";
8import { LinkButton } from "@components/Button";
9import { Devs } from "@utils/constants";
10import definePlugin, { OptionType } from "@utils/types";
11
12const API_URL = "https:class="ts-cmt">//usrbg.is-hardly.online/users";
13
14interface UsrbgApiReturn {
15 endpoint: string;
16 bucket: string;
17 prefix: string;
18 users: Record<string, string>;
19}
20
21const settings = definePluginSettings({
22 nitroFirst: {
23 description: "Banner to use if both Nitro and USRBG banners are present",
24 type: OptionType.SELECT,
25 options: [
26 { label: "Nitro banner", value: true, default: true },
27 { label: "USRBG banner", value: false },
28 ]
29 },
30 voiceBackground: {
31 description: "Use USRBG banners as voice chat backgrounds",
32 type: OptionType.BOOLEAN,
33 default: true,
34 restartNeeded: true
35 }
36});
37
38export default definePlugin({
39 name: "USRBG",
40 description: "Displays user banners from USRBG, allowing anyone to get a banner without Nitro",
41 tags: ["Appearance", "Customisation"],
42 authors: [Devs.AutumnVN, Devs.katlyn, Devs.pylix, Devs.TheKodeToad],
43 settings,
44 patches: [
45 {
46 find: &#039;:"SHOULD_LOAD");&#039;,
47 replacement: {
48 match: /\i(?:\?)?.getPreviewBanner\(\i,\i,\i\)(?=.{0,100}"COMPLETE")/,
49 replace: "$self.patchBannerUrl(arguments[0])||$&"
50
51 }
52 },
53 {
54 find: "\"data-selenium-video-tile\":",
55 predicate: () => settings.store.voiceBackground,
56 replacement: [
57 {
58 match: /(?<=function\((\i),\i\)\{)(?=let.{20,40},style:)/,
59 replace: "$1.style=$self.getVoiceBackgroundStyles($1);"
60 }
61 ]
62 },
63 {
64 find: &#039;"VideoBackground-web"&#039;,
65 predicate: () => settings.store.voiceBackground,
66 replacement: {
67 match: /backgroundColor:.{0,25},\{style:(?=\i\?)/,
68 replace: "$&$self.userHasBackground(arguments[0]?.userId)?null:",
69 }
70 }
71 ],
72
73 data: null as UsrbgApiReturn | null,
74
75 settingsAboutComponent: () => {
76 return (
77 <LinkButton href="https:class="ts-cmt">//github.com/AutumnVN/usrbg#how-to-request-your-own-usrbg-banner" variant="primary">
78 Get your own USRBG banner
79 </LinkButton>
80 );
81 },
82
83 getVoiceBackgroundStyles({ className, participantUserId }: any) {
84 if (className.includes("tile")) {
85 if (this.userHasBackground(participantUserId)) {
86 return {
87 backgroundImage: `url(${this.getImageUrl(participantUserId)})`,
88 backgroundSize: "cover",
89 backgroundPosition: "center",
90 backgroundRepeat: "no-repeat"
91 };
92 }
93 }
94 },
95
96 patchBannerUrl({ displayProfile }: any) {
97 if (displayProfile?.banner && settings.store.nitroFirst) return;
98 if (this.userHasBackground(displayProfile?.userId)) return this.getImageUrl(displayProfile?.userId);
99 },
100
101 userHasBackground(userId: string) {
102 return !!this.data?.users[userId];
103 },
104
105 getImageUrl(userId: string): string | null {
106 if (!this.userHasBackground(userId)) return null;
107
108 // We can assert that data exists because userHasBackground returned true
109 const { endpoint, bucket, prefix, users: { [userId]: etag } } = this.data!;
110 return `${endpoint}/${bucket}/${prefix}${userId}?${etag}`;
111 },
112
113 async start() {
114 const res = await fetch(API_URL);
115 if (res.ok) {
116 this.data = await res.json();
117 }
118 }
119});
120