Redo ChannelBadges

This commit is contained in:
thororen1234 2025-08-22 18:41:52 -04:00
parent 03e9c2b501
commit 708bce9a91
No known key found for this signature in database
4 changed files with 143 additions and 195 deletions

View file

@ -1,140 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./style.css";
import { EquicordDevs } from "@utils/constants";
import { getCurrentGuild } from "@utils/discord";
import definePlugin from "@utils/types";
import { Channel, Guild } from "@vencord/discord-types";
import { ChannelStore, SelectedGuildStore } from "@webpack/common";
import { isEnabled, returnChannelBadge, settings } from "./settings";
let observer: MutationObserver | null = null;
let currentGuild: Guild | undefined | null = null;
function addBadgesToChannel(element: HTMLElement, channelId: string) {
const parentContainer: HTMLElement | null = element.querySelector('[class*="linkTop"]');
if (!parentContainer) return;
const channel: Channel | undefined = ChannelStore.getChannel(channelId);
if (!channel || !isEnabled(channel.type)) return;
const { type, nsfw, threadMetadata } = channel;
const isPrivate = channel.isPrivate() || threadMetadata?.locked || channel.isArchivedThread();
const isNSFW = nsfw || channel.isNSFW();
let badgeContainer: HTMLElement | null = parentContainer.querySelector(".badge-container");
if (!badgeContainer) {
badgeContainer = document.createElement("div");
badgeContainer.classList.add("badge-container");
parentContainer.appendChild(badgeContainer);
}
const badgeConditions = [
{ id: 6101, condition: isPrivate, title: "This channel is locked." },
{ id: 6100, condition: isNSFW, title: "This channel is marked as NSFW." },
{ id: 6102, condition: currentGuild?.rulesChannelId === channel.id, title: "This channel is the server rules channel." },
];
badgeConditions.forEach(({ id, condition, title }) => {
if (condition && isEnabled(id)) addBadge(badgeContainer!, id, title);
});
addBadge(badgeContainer!, type, returnChannelBadge(type).label);
}
function addBadge(container: HTMLElement, id: number, title: string) {
const { css, label, color } = returnChannelBadge(id);
const badge = document.createElement("div");
badge.classList.add("channel-badge", `channel-badge-${css}`);
badge.textContent = label;
badge.title = title;
if (color && color !== "") {
badge.style.backgroundColor = color;
}
container.appendChild(badge);
}
function deleteAllBadges() {
document.querySelectorAll(".channel-badge").forEach(badge => badge.remove());
document.querySelectorAll('[data-list-item-id^="channels___"][data-scanned]').forEach(element => {
element.removeAttribute("data-scanned");
});
}
export function reloadBadges() {
deleteAllBadges();
document.querySelectorAll('[data-list-item-id^="channels___"]').forEach(element => {
const channelId = element.getAttribute("data-list-item-id")?.split("___")[1];
if (channelId && /^\d+$/.test(channelId)) {
addBadgesToChannel(element as HTMLElement, channelId);
element.setAttribute("data-scanned", "true");
}
});
}
function observeDomChanges() {
if (observer) observer.disconnect();
const handleMutations = (mutations: MutationRecord[]) => {
const addedElements = new Set<Element>();
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
const element = node as Element;
element.querySelectorAll('[data-list-item-id^="channels___"]:not([data-scanned])').forEach(child => {
addedElements.add(child);
});
}
});
});
addedElements.forEach(element => {
const channelId = element.getAttribute("data-list-item-id")?.split("___")[1];
if (channelId && /^\d+$/.test(channelId)) {
addBadgesToChannel(element as HTMLElement, channelId);
element.setAttribute("data-scanned", "true");
}
});
};
observer = new MutationObserver(handleMutations);
observer.observe(document.body, { childList: true, subtree: true });
}
function onGuildChange() {
const newGuild: Guild | undefined | null = getCurrentGuild();
if (newGuild !== currentGuild) {
currentGuild = newGuild;
}
}
export default definePlugin({
name: "ChannelBadges",
description: "Adds badges to channels based on their type",
authors: [EquicordDevs.creations],
settings,
async start() {
currentGuild = getCurrentGuild();
observeDomChanges();
reloadBadges();
SelectedGuildStore.addChangeListener(onGuildChange);
},
stop() {
if (observer) {
observer.disconnect();
observer = null;
}
deleteAllBadges();
SelectedGuildStore.removeChangeListener(onGuildChange);
}
});

View file

@ -0,0 +1,91 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./style.css";
import { EquicordDevs } from "@utils/constants";
import definePlugin from "@utils/types";
import { Channel } from "@vencord/discord-types";
import { GuildStore, React, SelectedGuildStore } from "@webpack/common";
import { JSX } from "react";
import { isEnabled, returnChannelBadge, settings } from "./settings";
function renderBadge(id: number, title: string) {
const { css, label, color } = returnChannelBadge(id);
return (
<div
key={id}
className={`channel-badge channel-badge-${css}`}
style={color ? { backgroundColor: color } : undefined}
title={title}
>
{label}
</div>
);
}
export default definePlugin({
name: "ChannelBadges",
description: "Adds badges to channels based on their type",
authors: [EquicordDevs.creations],
settings,
patches: [
// TY TypingIndicator
// Normal Channels
{
find: "UNREAD_IMPORTANT:",
replacement: {
match: /\.Children\.count.+?:null(?<=,channel:(\i).+?)/,
replace: "$&,$self.renderChannelBadges($1)"
}
},
// Threads
{
find: "spineWithGuildIcon]:",
replacement: {
match: /mentionsCount:\i.+?null(?<=channel:(\i).+?)/,
replace: "$&,$self.renderChannelBadges($1)"
}
}
],
renderChannelBadges(channel: Channel) {
if (!channel || !isEnabled(channel.type)) return null;
const { type, nsfw, threadMetadata } = channel;
const isPrivate = channel.isPrivate() || threadMetadata?.locked || channel.isArchivedThread();
const isNSFW = nsfw || channel.isNSFW();
const selectedGuildId = SelectedGuildStore.getGuildId();
const guild = selectedGuildId ? GuildStore.getGuild(selectedGuildId) : null;
const badgeConditions = [
{ id: 6101, condition: isPrivate, title: "This channel is locked." },
{ id: 6100, condition: isNSFW, title: "This channel is marked as NSFW." },
{ id: 6102, condition: guild?.rulesChannelId === channel.id, title: "This channel is the server rules channel." },
];
let badges: JSX.Element[] = [];
if (settings.store.oneBadgePerChannel) {
const first = badgeConditions.find(({ id, condition }) => condition && isEnabled(id));
if (first) {
badges.push(renderBadge(first.id, first.title));
} else {
badges.push(renderBadge(type, returnChannelBadge(type).label));
}
} else {
badges = badgeConditions
.filter(({ id, condition }) => condition && isEnabled(id))
.map(({ id, title }) => renderBadge(id, title));
badges.push(renderBadge(type, returnChannelBadge(type).label));
}
return <div className="badge-container">{badges}</div>;
}
});

View file

@ -7,276 +7,273 @@
import { definePluginSettings } from "@api/Settings";
import { OptionType } from "@utils/types";
import { reloadBadges } from "./index";
const settings = definePluginSettings({
export const settings = definePluginSettings({
oneBadgePerChannel: {
type: OptionType.BOOLEAN,
default: false,
description: "Show only one badge per channel",
onChange: reloadBadges,
restartNeeded: true,
},
showTextBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Text badge",
onChange: reloadBadges,
restartNeeded: true,
},
showVoiceBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Voice badge",
onChange: reloadBadges,
restartNeeded: true,
},
showCategoryBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Category badge",
onChange: reloadBadges,
restartNeeded: true,
},
showDirectoryBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Directory badge",
onChange: reloadBadges,
restartNeeded: true,
},
showAnnouncementThreadBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Announcement Thread badge",
onChange: reloadBadges,
restartNeeded: true,
},
showPublicThreadBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Public Thread badge",
onChange: reloadBadges,
restartNeeded: true,
},
showPrivateThreadBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Private Thread badge",
onChange: reloadBadges,
restartNeeded: true,
},
showStageBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Stage badge",
onChange: reloadBadges,
restartNeeded: true,
},
showAnnouncementBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Announcement badge",
onChange: reloadBadges,
restartNeeded: true,
},
showForumBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Forum badge",
onChange: reloadBadges,
restartNeeded: true,
},
showMediaBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Media badge",
onChange: reloadBadges,
restartNeeded: true,
},
showNSFWBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show NSFW badge",
onChange: reloadBadges,
restartNeeded: true,
},
showLockedBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Locked badge",
onChange: reloadBadges,
restartNeeded: true,
},
showRulesBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Rules badge",
onChange: reloadBadges,
restartNeeded: true,
},
showUnknownBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Unknown badge",
onChange: reloadBadges,
restartNeeded: true,
},
textBadgeLabel: {
type: OptionType.STRING,
default: "Text",
description: "Text badge label",
onChange: reloadBadges,
restartNeeded: true,
},
voiceBadgeLabel: {
type: OptionType.STRING,
default: "Voice",
description: "Voice badge label",
onChange: reloadBadges,
restartNeeded: true,
},
categoryBadgeLabel: {
type: OptionType.STRING,
default: "Category",
description: "Category badge label",
onChange: reloadBadges,
restartNeeded: true,
},
announcementBadgeLabel: {
type: OptionType.STRING,
default: "News",
description: "Announcement badge label",
onChange: reloadBadges,
restartNeeded: true,
},
announcementThreadBadgeLabel: {
type: OptionType.STRING,
default: "News Thread",
description: "Announcement Thread badge label",
onChange: reloadBadges,
restartNeeded: true,
},
publicThreadBadgeLabel: {
type: OptionType.STRING,
default: "Thread",
description: "Public Thread badge label",
onChange: reloadBadges,
restartNeeded: true,
},
privateThreadBadgeLabel: {
type: OptionType.STRING,
default: "Private Thread",
description: "Private Thread badge label",
onChange: reloadBadges,
restartNeeded: true,
},
stageBadgeLabel: {
type: OptionType.STRING,
default: "Stage",
description: "Stage badge label",
onChange: reloadBadges,
restartNeeded: true,
},
directoryBadgeLabel: {
type: OptionType.STRING,
default: "Directory",
description: "Directory badge label",
onChange: reloadBadges,
restartNeeded: true,
},
forumBadgeLabel: {
type: OptionType.STRING,
default: "Forum",
description: "Forum badge label",
onChange: reloadBadges,
restartNeeded: true,
},
mediaBadgeLabel: {
type: OptionType.STRING,
default: "Media",
description: "Media badge label",
onChange: reloadBadges,
restartNeeded: true,
},
nsfwBadgeLabel: {
type: OptionType.STRING,
default: "NSFW",
description: "NSFW badge label",
onChange: reloadBadges,
restartNeeded: true,
},
lockedBadgeLabel: {
type: OptionType.STRING,
default: "Locked",
description: "Locked badge label",
onChange: reloadBadges,
restartNeeded: true,
},
rulesBadgeLabel: {
type: OptionType.STRING,
default: "Rules",
description: "Rules badge label",
onChange: reloadBadges,
restartNeeded: true,
},
unknownBadgeLabel: {
type: OptionType.STRING,
default: "Unknown",
description: "Unknown badge label",
onChange: reloadBadges,
restartNeeded: true,
},
textBadgeColor: {
type: OptionType.STRING,
description: "Text badge color",
onChange: reloadBadges,
restartNeeded: true,
},
voiceBadgeColor: {
type: OptionType.STRING,
description: "Voice badge color",
onChange: reloadBadges,
restartNeeded: true,
},
categoryBadgeColor: {
type: OptionType.STRING,
description: "Category badge color",
onChange: reloadBadges,
restartNeeded: true,
},
announcementBadgeColor: {
type: OptionType.STRING,
description: "Announcement badge color",
onChange: reloadBadges,
restartNeeded: true,
},
announcementThreadBadgeColor: {
type: OptionType.STRING,
description: "Announcement Thread badge color",
onChange: reloadBadges,
restartNeeded: true,
},
publicThreadBadgeColor: {
type: OptionType.STRING,
description: "Public Thread badge color",
onChange: reloadBadges,
restartNeeded: true,
},
privateThreadBadgeColor: {
type: OptionType.STRING,
description: "Private Thread badge color",
onChange: reloadBadges,
restartNeeded: true,
},
stageBadgeColor: {
type: OptionType.STRING,
description: "Stage badge color",
onChange: reloadBadges,
restartNeeded: true,
},
directoryBadgeColor: {
type: OptionType.STRING,
description: "Directory badge color",
onChange: reloadBadges,
restartNeeded: true,
},
forumBadgeColor: {
type: OptionType.STRING,
description: "Forum badge color",
onChange: reloadBadges,
restartNeeded: true,
},
mediaBadgeColor: {
type: OptionType.STRING,
description: "Media badge color",
onChange: reloadBadges,
restartNeeded: true,
},
nsfwBadgeColor: {
type: OptionType.STRING,
description: "NSFW badge color",
onChange: reloadBadges,
restartNeeded: true,
},
lockedBadgeColor: {
type: OptionType.STRING,
description: "Locked badge color",
onChange: reloadBadges,
restartNeeded: true,
},
rulesBadgeColor: {
type: OptionType.STRING,
description: "Rules badge color",
onChange: reloadBadges,
restartNeeded: true,
},
unknownBadgeColor: {
type: OptionType.STRING,
description: "Unknown badge color",
onChange: reloadBadges,
restartNeeded: true,
},
});
const defaultValues = {
export const defaultValues = {
showTextBadge: true,
showVoiceBadge: true,
showCategoryBadge: true,
@ -314,7 +311,7 @@ const defaultValues = {
nsfwBadgeTooltip: "This channel is marked as NSFW.",
};
function isEnabled(type: number) {
export function isEnabled(type: number) {
const fromValues = settings.store;
switch (type) {
@ -351,7 +348,7 @@ function isEnabled(type: number) {
}
}
function returnChannelBadge(type: number) {
export function returnChannelBadge(type: number) {
switch (type) {
case 0:
return { css: "text", label: settings.store.textBadgeLabel, color: settings.store.textBadgeColor };
@ -385,5 +382,3 @@ function returnChannelBadge(type: number) {
return { css: "unknown", label: settings.store.unknownBadgeLabel, color: settings.store.unknownBadgeColor };
}
}
export { defaultValues, isEnabled, returnChannelBadge, settings };

View file

@ -78,6 +78,7 @@ export default definePlugin({
settings,
patches: [
// TY TypingIndicator
// Normal Channels
{
find: "UNREAD_IMPORTANT:",
replacement: {
@ -85,6 +86,7 @@ export default definePlugin({
replace: "$&,$self.TrashIcon($1)"
}
},
// Threads
{
find: "spineWithGuildIcon]:",
replacement: {