diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 792fa40f..680f8375 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -65,7 +65,7 @@ Also pay attention to the following:
- Match any but a guaranteed terminating character: `[^;]+`, for example to match the entire assigned value in `var a=b||c||func();`,
`var .{1,2}=([^;]+);`
- If you don't care about that part, just match a bunch of chars: `.{0,50}`, for example to extract the variable "b" in `createElement("div",{a:"foo",c:"bar"},b)`, `createElement\("div".{0,30},(.{1,2})\),`. Note the `.{0,30}`, this is essentially the same as `.+`, but safer as you can't end up accidently eating thousands of characters
-- Additionally, as you might have noticed, all of the appove approaches use regex groups (`(...)`) to capture the variable name. You can then use those groups in your replacement to access those variables dynamically
+- Additionally, as you might have noticed, all of the above approaches use regex groups (`(...)`) to capture the variable name. You can then use those groups in your replacement to access those variables dynamically
#### "replace"
diff --git a/docs/1_INSTALLING.md b/docs/1_INSTALLING.md
index 6ea71607..d57e64e5 100644
--- a/docs/1_INSTALLING.md
+++ b/docs/1_INSTALLING.md
@@ -63,7 +63,7 @@ Then fully close Discord from your taskbar or task manager, and restart it. Venc
If you're using Discord already, go into the `Updater` tab in settings.
-Sometimes it may be neccessary to manually update if the GUI updater fails.
+Sometimes it may be necessary to manually update if the GUI updater fails.
To pull latest changes:
diff --git a/package.json b/package.json
index 033d907f..4df9ba4e 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
- "version": "1.4.5",
+ "version": "1.4.6",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index 906be225..68e3c808 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -171,7 +171,7 @@ page.on("console", async e => {
plugin,
type,
id,
- match: regex,
+ match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"),
error: cause
});
break;
diff --git a/src/components/CodeBlock.tsx b/src/components/CodeBlock.tsx
new file mode 100644
index 00000000..41c5ef0c
--- /dev/null
+++ b/src/components/CodeBlock.tsx
@@ -0,0 +1,21 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2023 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { findByPropsLazy } from "@webpack";
+import { Parser } from "@webpack/common";
+
+const CodeContainerClasses = findByPropsLazy("markup", "codeContainer");
+
+/**
+ * Renders code in a Discord codeblock
+ */
+export function CodeBlock(props: { content?: string, lang: string; }) {
+ return (
+
+ {Parser.defaultRules.codeBlock.react(props, null, {})}
+
+ );
+}
diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx
index f4cab54c..f30cedee 100644
--- a/src/components/PluginSettings/PluginModal.tsx
+++ b/src/components/PluginSettings/PluginModal.tsx
@@ -23,7 +23,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { proxyLazy } from "@utils/lazy";
import { Margins } from "@utils/margins";
-import { classes } from "@utils/misc";
+import { classes, isObjectEmpty } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
import { LazyComponent } from "@utils/react";
import { OptionType, Plugin } from "@utils/types";
@@ -89,7 +89,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
const canSubmit = () => Object.values(errors).every(e => !e);
- const hasSettings = Boolean(pluginSettings && plugin.options);
+ const hasSettings = Boolean(pluginSettings && plugin.options && !isObjectEmpty(plugin.options));
React.useEffect(() => {
enableStyle(hideBotTagStyle);
diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx
index 12487c6d..f19d3264 100644
--- a/src/components/PluginSettings/index.tsx
+++ b/src/components/PluginSettings/index.tsx
@@ -28,7 +28,7 @@ import { SettingsTab } from "@components/VencordSettings/shared";
import { ChangeList } from "@utils/ChangeList";
import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
-import { classes } from "@utils/misc";
+import { classes, isObjectEmpty } from "@utils/misc";
import { openModalLazy } from "@utils/modal";
import { LazyComponent, useAwaiter } from "@utils/react";
import { Plugin } from "@utils/types";
@@ -161,7 +161,7 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe
onMouseLeave={onMouseLeave}
infoButton={
openModal()} className={classes(ButtonClasses.button, cl("info-button"))}>
- {plugin.options
+ {plugin.options && !isObjectEmpty(plugin.options)
?
: }
diff --git a/src/components/VencordSettings/PatchHelperTab.tsx b/src/components/VencordSettings/PatchHelperTab.tsx
index d5bd94ce..0b869a51 100644
--- a/src/components/VencordSettings/PatchHelperTab.tsx
+++ b/src/components/VencordSettings/PatchHelperTab.tsx
@@ -17,6 +17,7 @@
*/
import { CheckedTextInput } from "@components/CheckedTextInput";
+import { CodeBlock } from "@components/CodeBlock";
import { debounce } from "@utils/debounce";
import { Margins } from "@utils/margins";
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
@@ -299,7 +300,7 @@ function PatchHelper() {
{!!(find && match && replacement) && (
<>
Code
- {Parser.parse(makeCodeblock(code, "ts"))}
+
Clipboard.copy(code)}>Copy to Clipboard
>
)}
diff --git a/src/plugins/clearURLs/defaultRules.ts b/src/plugins/clearURLs/defaultRules.ts
index c59cef9d..644d2cf2 100644
--- a/src/plugins/clearURLs/defaultRules.ts
+++ b/src/plugins/clearURLs/defaultRules.ts
@@ -136,4 +136,5 @@ export const defaultRules = [
"utm_term",
"si@open.spotify.com",
"igshid",
+ "share_id@reddit.com",
];
diff --git a/src/plugins/hideAttachments.tsx b/src/plugins/hideAttachments.tsx
index f608e05a..fe7f4ab9 100644
--- a/src/plugins/hideAttachments.tsx
+++ b/src/plugins/hideAttachments.tsx
@@ -49,7 +49,7 @@ export default definePlugin({
await this.buildCss();
addButton("HideAttachments", msg => {
- if (!msg.attachments.length && !msg.embeds.length) return null;
+ if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length) return null;
const isHidden = hiddenMessages.has(msg.id);
@@ -72,7 +72,7 @@ export default definePlugin({
async buildCss() {
const elements = [...hiddenMessages].map(id => `#message-accessories-${id}`).join(",");
style.textContent = `
- :is(${elements}) [class*="embedWrapper"] {
+ :is(${elements}) :is([class*="embedWrapper"], [class*="clickableSticker"]) {
/* important is not necessary, but add it to make sure bad themes won't break it */
display: none !important;
}
diff --git a/src/plugins/loadingQuotes.ts b/src/plugins/loadingQuotes.ts
index 7be6f305..963705b6 100644
--- a/src/plugins/loadingQuotes.ts
+++ b/src/plugins/loadingQuotes.ts
@@ -56,7 +56,8 @@ const quotes = [
"hd{b${",
"<;vqkijbq33271:56<3799?24944:",
"Thof$lu'ofdn,!qsefc'az*bnrcma+&Om{o+iu\"`khct$)bnrd\"bcdoi&",
- "snofplkb{)c'r\"lod'|f*aurv#cpno`abchijklmno"
+ "snofplkb{)c'r\"lod'|f*aurv#cpno`abchijklmno",
+ "Wdn`khc'|f*eghl{%"
];
export default definePlugin({
diff --git a/src/plugins/memberCount.tsx b/src/plugins/memberCount.tsx
index 7f8b868e..ecdb8afb 100644
--- a/src/plugins/memberCount.tsx
+++ b/src/plugins/memberCount.tsx
@@ -30,6 +30,9 @@ const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & {
getProps(guildId: string, channelId: string): { groups: { count: number; id: string; }[]; };
};
+const sharedIntlNumberFormat = new Intl.NumberFormat();
+const numberFormat = (value: number) => sharedIntlNumberFormat.format(value);
+
function MemberCount() {
const { id: channelId, guild_id: guildId } = useStateFromStores([SelectedChannelStore], () => getCurrentChannel());
const { groups } = useStateFromStores(
@@ -57,7 +60,7 @@ function MemberCount() {
alignContent: "center",
gap: 0
}}>
-
+
{props => (
- {online}
+ {numberFormat(online)}
)}
-
+
{props => (
- {total}
+ {numberFormat(total)}
)}
diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx
index 0e6f1187..9c665607 100644
--- a/src/plugins/messageLogger/index.tsx
+++ b/src/plugins/messageLogger/index.tsx
@@ -169,21 +169,14 @@ export default definePlugin({
try {
if (cache == null || (!isBulk && !cache.has(data.id))) return cache;
- const { ignoreBots, ignoreSelf, ignoreUsers, ignoreChannels, ignoreGuilds } = Settings.plugins.MessageLogger;
- const myId = UserStore.getCurrentUser().id;
-
- function mutate(id: string) {
+ const mutate = (id: string) => {
const msg = cache.get(id);
if (!msg) return;
const EPHEMERAL = 64;
const shouldIgnore = data.mlDeleted ||
(msg.flags & EPHEMERAL) === EPHEMERAL ||
- ignoreBots && msg.author?.bot ||
- ignoreSelf && msg.author?.id === myId ||
- ignoreUsers.includes(msg.author?.id) ||
- ignoreChannels.includes(msg.channel_id) ||
- ignoreGuilds.includes(ChannelStore.getChannel(msg.channel_id)?.guild_id);
+ this.shouldIgnore(msg);
if (shouldIgnore) {
cache = cache.remove(id);
@@ -192,7 +185,7 @@ export default definePlugin({
.set("deleted", true)
.set("attachments", m.attachments.map(a => (a.deleted = true, a))));
}
- }
+ };
if (isBulk) {
data.ids.forEach(mutate);
@@ -205,6 +198,18 @@ export default definePlugin({
return cache;
},
+ shouldIgnore(message: any) {
+ const { ignoreBots, ignoreSelf, ignoreUsers, ignoreChannels, ignoreGuilds } = Settings.plugins.MessageLogger;
+ const myId = UserStore.getCurrentUser().id;
+
+ return ignoreBots && message.author?.bot ||
+ ignoreSelf && message.author?.id === myId ||
+ ignoreUsers.includes(message.author?.id) ||
+ ignoreChannels.includes(message.channel_id) ||
+ ignoreChannels.includes(ChannelStore.getChannel(message.channel_id)?.parent_id) ||
+ ignoreGuilds.includes(ChannelStore.getChannel(message.channel_id)?.guild_id);
+ },
+
// Based on canary 9ab8626bcebceaea6da570b9c586172d02b9c996
patches: [
{
@@ -237,7 +242,7 @@ export default definePlugin({
match: /(MESSAGE_UPDATE:function\((\w)\).+?)\.update\((\w)/,
replace: "$1" +
".update($3,m =>" +
- " (($2.message.flags & 64) === 64 || (Vencord.Settings.plugins.MessageLogger.ignoreBots && $2.message.author?.bot) || (Vencord.Settings.plugins.MessageLogger.ignoreSelf && $2.message.author?.id === Vencord.Webpack.Common.UserStore.getCurrentUser().id)) ? m :" +
+ " (($2.message.flags & 64) === 64 || $self.shouldIgnore($2.message)) ? m :" +
" $2.message.content !== m.editHistory?.[0]?.content && $2.message.content !== m.content ?" +
" m.set('editHistory',[...(m.editHistory || []), $self.makeEdit($2.message, m)]) :" +
" m" +
diff --git a/src/plugins/permissionsViewer/index.tsx b/src/plugins/permissionsViewer/index.tsx
index 480efc15..7de29b35 100644
--- a/src/plugins/permissionsViewer/index.tsx
+++ b/src/plugins/permissionsViewer/index.tsx
@@ -178,12 +178,12 @@ export default definePlugin({
start() {
addContextMenuPatch("user-context", this.userContextMenuPatch);
addContextMenuPatch("channel-context", this.channelContextMenuPatch);
- addContextMenuPatch("guild-context", this.guildContextMenuPatch);
+ addContextMenuPatch(["guild-context", "guild-header-popout"], this.guildContextMenuPatch);
},
stop() {
removeContextMenuPatch("user-context", this.userContextMenuPatch);
removeContextMenuPatch("channel-context", this.channelContextMenuPatch);
- removeContextMenuPatch("guild-context", this.guildContextMenuPatch);
+ removeContextMenuPatch(["guild-context", "guild-header-popout"], this.guildContextMenuPatch);
},
});
diff --git a/src/plugins/pinDms/index.tsx b/src/plugins/pinDms/index.tsx
index 249dcd27..02fe332d 100644
--- a/src/plugins/pinDms/index.tsx
+++ b/src/plugins/pinDms/index.tsx
@@ -122,6 +122,14 @@ export default definePlugin({
// ....concat(pins).concat(toArray(channelIds).filter(c => !isPinned(c)))
replace: ".concat($self.getSnapshot()).concat($2.filter(c=>!$self.isPinned(c)))"
}
- }
+ },
+ // fix alt+shift+up/down
+ {
+ find: '"alt+shift+down"',
+ replacement: {
+ match: /(?<=return \i===\i\.ME\?)\i\.\i\.getPrivateChannelIds\(\)/,
+ replace: "$self.getSnapshot().concat($&.filter(c=>!$self.isPinned(c)))"
+ }
+ },
]
});
diff --git a/src/plugins/previewMessage.tsx b/src/plugins/previewMessage.tsx
index 26533185..9bea221d 100644
--- a/src/plugins/previewMessage.tsx
+++ b/src/plugins/previewMessage.tsx
@@ -20,7 +20,7 @@ import { sendBotMessage } from "@api/Commands";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
-import { Button, ButtonLooks, ButtonWrapperClasses, DraftStore, DraftType, SelectedChannelStore, Tooltip, UserStore } from "@webpack/common";
+import { Button, ButtonLooks, ButtonWrapperClasses, DraftStore, DraftType, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
interface Props {
type: {
@@ -31,10 +31,9 @@ interface Props {
const getDraft = (channelId: string) => DraftStore.getDraft(channelId, DraftType.ChannelMessage);
export function PreviewButton(chatBoxProps: Props) {
- if (chatBoxProps.type.analyticsName !== "normal") return null;
const channelId = SelectedChannelStore.getChannelId();
- const draft = getDraft(channelId);
-
+ const draft = useStateFromStores([DraftStore], () => getDraft(channelId));
+ if (chatBoxProps.type.analyticsName !== "normal") return null;
if (!draft) return null;
return (
diff --git a/src/plugins/rnnoise.web/index.tsx b/src/plugins/rnnoise.web/index.tsx
index 7117ca29..8de6557e 100644
--- a/src/plugins/rnnoise.web/index.tsx
+++ b/src/plugins/rnnoise.web/index.tsx
@@ -145,7 +145,7 @@ export default definePlugin({
find: "window.webkitAudioContext",
replacement: {
match: /(?<=\i\.acquire=function\((\i)\)\{return )navigator\.mediaDevices\.getUserMedia\(\1\)(?=\})/,
- replace: m => `${m}.then(stream => $self.connectRnnoise(stream))`
+ replace: "$&.then(stream => $self.connectRnnoise(stream, $1.audio))"
},
},
{
@@ -182,7 +182,8 @@ export default definePlugin({
setEnabled,
isEnabled: () => settings.store.isEnabled,
- async connectRnnoise(stream: MediaStream): Promise {
+ async connectRnnoise(stream: MediaStream, isAudio: boolean): Promise {
+ if (!isAudio) return stream;
if (!settings.store.isEnabled) return stream;
const audioCtx = new AudioContext();
diff --git a/src/plugins/serverProfile/GuildProfileModal.tsx b/src/plugins/serverProfile/GuildProfileModal.tsx
new file mode 100644
index 00000000..79b38777
--- /dev/null
+++ b/src/plugins/serverProfile/GuildProfileModal.tsx
@@ -0,0 +1,247 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2023 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import "./styles.css";
+
+import { classNameFactory } from "@api/Styles";
+import { openImageModal, openUserProfile } from "@utils/discord";
+import { classes } from "@utils/misc";
+import { ModalRoot, ModalSize, openModal } from "@utils/modal";
+import { LazyComponent, useAwaiter } from "@utils/react";
+import { findByCode, findByPropsLazy } from "@webpack";
+import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, moment, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common";
+import { Guild, User } from "discord-types/general";
+
+const IconUtils = findByPropsLazy("getGuildBannerURL");
+const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper");
+const UserRow = LazyComponent(() => findByCode(".listDiscriminator"));
+
+const cl = classNameFactory("vc-gp-");
+
+export function openGuildProfileModal(guild: Guild) {
+ openModal(props =>
+
+
+
+ );
+}
+
+const enum Tabs {
+ ServerInfo,
+ Friends,
+ BlockedUsers
+}
+
+interface GuildProps {
+ guild: Guild;
+}
+
+interface RelationshipProps extends GuildProps {
+ setCount(count: number): void;
+}
+
+const fetched = {
+ friends: false,
+ blocked: false
+};
+
+function renderTimestamp(timestamp: number) {
+ return (
+
+ );
+}
+
+function GuildProfileModal({ guild }: GuildProps) {
+ const [friendCount, setFriendCount] = useState();
+ const [blockedCount, setBlockedCount] = useState();
+
+ useEffect(() => {
+ fetched.friends = false;
+ fetched.blocked = false;
+ }, []);
+
+ const [currentTab, setCurrentTab] = useState(Tabs.ServerInfo);
+
+ const bannerUrl = guild.banner && IconUtils.getGuildBannerURL({
+ id: guild.id,
+ banner: guild.banner
+ }, true).replace(/\?size=\d+$/, "?size=1024");
+
+ const iconUrl = guild.icon && IconUtils.getGuildIconURL({
+ id: guild.id,
+ icon: guild.icon,
+ canAnimate: true,
+ size: 512
+ });
+
+ return (
+
+ {bannerUrl && currentTab === Tabs.ServerInfo && (
+
openImageModal(bannerUrl)}
+ />
+ )}
+
+
+ {guild.icon
+ ?
openImageModal(iconUrl)}
+ />
+ :
{guild.acronym}
+ }
+
+
+ {guild.name}
+ {guild.description && {guild.description} }
+
+
+
+
+
+ Server Info
+
+
+ Friends{friendCount !== undefined ? ` (${friendCount})` : ""}
+
+
+ Blocked Users{blockedCount !== undefined ? ` (${blockedCount})` : ""}
+
+
+
+
+ {currentTab === Tabs.ServerInfo && }
+ {currentTab === Tabs.Friends && }
+ {currentTab === Tabs.BlockedUsers && }
+
+
+ );
+}
+
+
+function Owner(guildId: string, owner: User) {
+ const guildAvatar = GuildMemberStore.getMember(guildId, owner.id)?.avatar;
+ const ownerAvatarUrl =
+ guildAvatar
+ ? IconUtils.getGuildMemberAvatarURLSimple({
+ userId: owner!.id,
+ avatar: guildAvatar,
+ guildId,
+ canAnimate: true
+ }, true)
+ : IconUtils.getUserAvatarURL(owner, true);
+
+ return (
+
+
openImageModal(ownerAvatarUrl)} />
+ {Parser.parse(`<@${owner.id}>`)}
+
+ );
+}
+
+function ServerInfoTab({ guild }: GuildProps) {
+ const [owner] = useAwaiter(() => UserUtils.fetchUser(guild.ownerId), {
+ deps: [guild.ownerId],
+ fallbackValue: null
+ });
+
+ const Fields = {
+ "Server Owner": owner ? Owner(guild.id, owner) : "Loading...",
+ "Created At": renderTimestamp(SnowflakeUtils.extractTimestamp(guild.id)),
+ "Joined At": renderTimestamp(guild.joinedAt.getTime()),
+ "Vanity Link": guild.vanityURLCode ? ({`discord.gg/${guild.vanityURLCode}`} ) : "-", // Making the anchor href valid would cause Discord to reload
+ "Preferred Locale": guild.preferredLocale || "-",
+ "Verification Level": ["None", "Low", "Medium", "High", "Highest"][guild.verificationLevel] || "?",
+ "Nitro Boosts": `${guild.premiumSubscriberCount ?? 0} (Level ${guild.premiumTier ?? 0})`,
+ "Channels": GuildChannelStore.getChannels(guild.id)?.count - 1 || "?", // - null category
+ "Roles": Object.keys(guild.roles).length - 1, // - @everyone
+ };
+
+ return (
+
+ {Object.entries(Fields).map(([name, node]) =>
+
+ {name}
+ {typeof node === "string" ? {node} : node}
+
+ )}
+
+ );
+}
+
+function FriendsTab({ guild, setCount }: RelationshipProps) {
+ return UserList("friends", guild, RelationshipStore.getFriendIDs(), setCount);
+}
+
+function BlockedUsersTab({ guild, setCount }: RelationshipProps) {
+ const blockedIds = Object.keys(RelationshipStore.getRelationships()).filter(id => RelationshipStore.isBlocked(id));
+ return UserList("blocked", guild, blockedIds, setCount);
+}
+
+function UserList(type: "friends" | "blocked", guild: Guild, ids: string[], setCount: (count: number) => void) {
+ const missing = [] as string[];
+ const members = [] as string[];
+
+ for (const id of ids) {
+ if (GuildMemberStore.isMember(guild.id, id))
+ members.push(id);
+ else
+ missing.push(id);
+ }
+
+ // Used for side effects (rerender on member request success)
+ useStateFromStores(
+ [GuildMemberStore],
+ () => GuildMemberStore.getMemberIds(guild.id),
+ null,
+ (old, curr) => old.length === curr.length
+ );
+
+ useEffect(() => {
+ if (!fetched[type] && missing.length) {
+ fetched[type] = true;
+ FluxDispatcher.dispatch({
+ type: "GUILD_MEMBERS_REQUEST",
+ guildIds: [guild.id],
+ userIds: missing
+ });
+ }
+ }, []);
+
+ useEffect(() => setCount(members.length), [members.length]);
+
+ return (
+
+ {members.map(id =>
+ openUserProfile(id)}
+ onContextMenu={() => { }}
+ />
+ )}
+
+ );
+}
diff --git a/src/plugins/serverProfile/README.md b/src/plugins/serverProfile/README.md
new file mode 100644
index 00000000..9da70e74
--- /dev/null
+++ b/src/plugins/serverProfile/README.md
@@ -0,0 +1,7 @@
+# ServerProfile
+
+Allows you to view info about servers and see friends and blocked users
+
+![image](https://github.com/Vendicated/Vencord/assets/45497981/a49783b5-e8fc-41d8-968f-58600e9f6580)
+![image](https://github.com/Vendicated/Vencord/assets/45497981/5efc158a-e671-4196-a15a-77edf79a2630)
+![image](https://github.com/Vendicated/Vencord/assets/45497981/f43be943-6dc4-4232-9709-fbeb382d8e54)
diff --git a/src/plugins/serverProfile/index.tsx b/src/plugins/serverProfile/index.tsx
new file mode 100644
index 00000000..c27f8cd5
--- /dev/null
+++ b/src/plugins/serverProfile/index.tsx
@@ -0,0 +1,40 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2023 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
+import { Devs } from "@utils/constants";
+import definePlugin from "@utils/types";
+import { Menu } from "@webpack/common";
+import { Guild } from "discord-types/general";
+
+import { openGuildProfileModal } from "./GuildProfileModal";
+
+const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild; }) => () => {
+ const group = findGroupChildrenByChildId("privacy", children);
+
+ group?.push(
+ openGuildProfileModal(guild)}
+ />
+ );
+};
+
+export default definePlugin({
+ name: "ServerProfile",
+ description: "Allows you to view info about a server by right clicking it in the server list",
+ authors: [Devs.Ven, Devs.Nuckyz],
+ tags: ["guild", "info"],
+
+ start() {
+ addContextMenuPatch(["guild-context", "guild-header-popout"], Patch);
+ },
+
+ stop() {
+ removeContextMenuPatch(["guild-context", "guild-header-popout"], Patch);
+ }
+});
diff --git a/src/plugins/serverProfile/styles.css b/src/plugins/serverProfile/styles.css
new file mode 100644
index 00000000..87487ecb
--- /dev/null
+++ b/src/plugins/serverProfile/styles.css
@@ -0,0 +1,97 @@
+.vc-gp-root {
+ height: 100%;
+ user-select: text;
+}
+
+.vc-gp-banner {
+ width: 100%;
+ cursor: pointer;
+}
+
+.vc-gp-header {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 0.5em;
+ margin: 0.5em;
+}
+
+.vc-gp-header img {
+ width: 48px;
+ height: 48px;
+ cursor: pointer;
+}
+
+.vc-gp-name-and-description {
+ display: flex;
+ flex-direction: column;
+ gap: 0.2em;
+}
+
+.vc-gp-name {
+ margin: 0;
+}
+
+.vc-gp-tab-bar {
+ border-bottom: 2px solid var(--background-modifier-accent);
+ margin: 20px 12px 0;
+ display: flex;
+ gap: 40px;
+ align-items: stretch;
+ flex-direction: row;
+}
+
+.vc-gp-tab {
+ border-bottom: 2px solid transparent;
+ color: var(--interactive-normal);
+ cursor: pointer;
+ height: 39px;
+ line-height: 14px;
+}
+
+.vc-gp-tab-content {
+ margin: 1em;
+}
+
+.vc-gp-tab:where(.vc-gp-selected, :hover, :focus) {
+ border-bottom-color: var(--interactive-active);
+}
+
+.vc-gp-info {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 1em;
+}
+
+.vc-gp-server-info-pair {
+ color: var(--text-normal);
+}
+
+.vc-gp-server-info-pair [class^="timestamp"] {
+ margin-left: 0;
+}
+
+.vc-gp-owner {
+ display: flex;
+ align-items: center;
+ gap: 0.2em;
+}
+
+.vc-gp-owner img {
+ height: 20px;
+ border-radius: 50%;
+ cursor: pointer;
+}
+
+.vc-gp-scroller {
+ width: 100%;
+ max-height: 500px;
+}
+
+.vc-gp-scroller [class^="listRow"] {
+ margin: 1px 0;
+}
+
+.vc-gp-scroller [class^="listRow"]:hover {
+ background-color: var(--background-modifier-hover);
+}
diff --git a/src/plugins/showTimeouts.ts b/src/plugins/showTimeouts.ts
new file mode 100644
index 00000000..b0774bed
--- /dev/null
+++ b/src/plugins/showTimeouts.ts
@@ -0,0 +1,35 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2023 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 { Devs } from "@utils/constants";
+import definePlugin from "@utils/types";
+
+export default definePlugin({
+ name: "ShowTimeouts",
+ description: "Display member timeout icons in chat regardless of permissions.",
+ authors: [Devs.Dolfies],
+ patches: [
+ {
+ find: "showCommunicationDisabledStyles",
+ replacement: {
+ match: /&&\i\.\i\.canManageUser\(\i\.\i\.MODERATE_MEMBERS,\i\.author,\i\)/,
+ replace: "",
+ },
+ },
+ ],
+});
diff --git a/src/plugins/usrbg/index.tsx b/src/plugins/usrbg/index.tsx
index 0121d10a..3e8c1225 100644
--- a/src/plugins/usrbg/index.tsx
+++ b/src/plugins/usrbg/index.tsx
@@ -80,6 +80,9 @@ export default definePlugin({
}
],
+
+ data,
+
settingsAboutComponent: () => {
return (
CLICK HERE TO GET YOUR OWN BANNER
@@ -116,7 +119,9 @@ export default definePlugin({
enableStyle(style);
const res = await fetch(BASE_URL);
- if (res.ok)
+ if (res.ok) {
data = await res.json();
+ this.data = data;
+ }
}
});
diff --git a/src/plugins/viewRaw.tsx b/src/plugins/viewRaw.tsx
index 9f13db97..60127645 100644
--- a/src/plugins/viewRaw.tsx
+++ b/src/plugins/viewRaw.tsx
@@ -16,8 +16,10 @@
* along with this program. If not, see .
*/
+import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { addButton, removeButton } from "@api/MessagePopover";
import { definePluginSettings } from "@api/Settings";
+import { CodeBlock } from "@components/CodeBlock";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Devs } from "@utils/constants";
@@ -25,12 +27,12 @@ import { Margins } from "@utils/margins";
import { copyWithToast } from "@utils/misc";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
-import { Button, ChannelStore, Forms, Parser, Text } from "@webpack/common";
+import { Button, ChannelStore, Forms, Menu, Text } from "@webpack/common";
import { Message } from "discord-types/general";
const CopyIcon = () => {
- return
+ return
;
@@ -58,19 +60,7 @@ function cleanMessage(msg: Message) {
return clone;
}
-function CodeBlock(props: { content: string, lang: string; }) {
- return (
- // make text selectable
-
- {Parser.defaultRules.codeBlock.react(props, null, {})}
-
- );
-}
-
-function openViewRawModal(msg: Message) {
- msg = cleanMessage(msg);
- const msgJson = JSON.stringify(msg, null, 4);
-
+function openViewRawModal(json: string, type: string, msgContent?: string) {
const key = openModal(props => (
@@ -80,26 +70,28 @@ function openViewRawModal(msg: Message) {
- {!!msg.content && (
+ {!!msgContent && (
<>
Content
-
+
>
)}
- Message Data
-
+ {type} Data
+
- copyWithToast(msgJson, "Message data copied to clipboard!")}>
- Copy Message JSON
-
- copyWithToast(msg.content, "Content copied to clipboard!")}>
- Copy Raw Content
+ copyWithToast(json, `${type} data copied to clipboard!`)}>
+ Copy {type} JSON
+ {!!msgContent && (
+ copyWithToast(msgContent, "Content copied to clipboard!")}>
+ Copy Raw Content
+
+ )}
@@ -107,6 +99,13 @@ function openViewRawModal(msg: Message) {
));
}
+function openViewRawModalMessage(msg: Message) {
+ msg = cleanMessage(msg);
+ const msgJson = JSON.stringify(msg, null, 4);
+
+ return openViewRawModal(msgJson, "Message", msg.content);
+}
+
const settings = definePluginSettings({
clickMethod: {
description: "Change the button to view the raw content/data of any message.",
@@ -118,10 +117,34 @@ const settings = definePluginSettings({
}
});
+function MakeContextCallback(name: string) {
+ const callback: NavContextMenuPatchCallback = (children, props) => () => {
+ if (name === "Guild" && !props.guild) return;
+ const lastChild = children.at(-1);
+ if (lastChild?.key === "developer-actions") {
+ const p = lastChild.props;
+ if (!Array.isArray(p.children))
+ p.children = [p.children];
+ ({ children } = p);
+ }
+
+ children.splice(-1, 0,
+ openViewRawModal(JSON.stringify(props[name.toLowerCase()], null, 4), name)}
+ icon={CopyIcon}
+ />
+ );
+ };
+ return callback;
+}
+
+
export default definePlugin({
name: "ViewRaw",
- description: "Copy and view the raw content/data of any message.",
- authors: [Devs.KingFish, Devs.Ven, Devs.rad],
+ description: "Copy and view the raw content/data of any message, channel or guild",
+ authors: [Devs.KingFish, Devs.Ven, Devs.rad, Devs.ImLvna],
dependencies: ["MessagePopoverAPI"],
settings,
@@ -131,7 +154,7 @@ export default definePlugin({
if (settings.store.clickMethod === "Right") {
copyWithToast(msg.content);
} else {
- openViewRawModal(msg);
+ openViewRawModalMessage(msg);
}
};
@@ -143,7 +166,7 @@ export default definePlugin({
} else {
e.preventDefault();
e.stopPropagation();
- openViewRawModal(msg);
+ openViewRawModalMessage(msg);
}
};
@@ -160,9 +183,16 @@ export default definePlugin({
onContextMenu: handleContextMenu
};
});
+
+ addContextMenuPatch("guild-context", MakeContextCallback("Guild"));
+ addContextMenuPatch("channel-context", MakeContextCallback("Channel"));
+ addContextMenuPatch("user-context", MakeContextCallback("User"));
},
stop() {
removeButton("CopyRawMessage");
+ removeContextMenuPatch("guild-context", MakeContextCallback("Guild"));
+ removeContextMenuPatch("channel-context", MakeContextCallback("Channel"));
+ removeContextMenuPatch("user-context", MakeContextCallback("User"));
}
});
diff --git a/src/plugins/voiceMessages/index.tsx b/src/plugins/voiceMessages/index.tsx
index 40578a68..a5271c2a 100644
--- a/src/plugins/voiceMessages/index.tsx
+++ b/src/plugins/voiceMessages/index.tsx
@@ -19,7 +19,6 @@
import "./styles.css";
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
-import { Flex } from "@components/Flex";
import { Microphone } from "@components/Icons";
import { Devs } from "@utils/constants";
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
@@ -39,6 +38,7 @@ import { VoiceRecorderWeb } from "./WebRecorder";
const CloudUpload = findLazy(m => m.prototype?.uploadFileToCloud);
const MessageCreator = findByPropsLazy("getSendMessageOptionsForReply", "sendMessage");
const PendingReplyStore = findStoreLazy("PendingReplyStore");
+const OptionClasses = findByPropsLazy("optionName", "optionIcon", "optionLabel");
export type VoiceRecorder = ComponentType<{
setAudioBlob(blob: Blob): void;
@@ -226,12 +226,10 @@ const ctxMenuPatch: NavContextMenuPatchCallback = (children, props) => () => {
-
-
- Send voice message
-
- >
+
}
action={() => openModal(modalProps => )}
/>
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 7cb5eab7..245c8bbb 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -355,6 +355,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "bb010g",
id: 72791153467990016n,
},
+ Dolfies: {
+ name: "Dolfies",
+ id: 852892297661906993n,
+ },
RuukuLada: {
name: "RuukuLada",
id: 119705748346241027n,
diff --git a/src/utils/misc.tsx b/src/utils/misc.tsx
index ec612a91..2b8ccf8a 100644
--- a/src/utils/misc.tsx
+++ b/src/utils/misc.tsx
@@ -74,6 +74,16 @@ export function isObject(obj: unknown): obj is object {
return typeof obj === "object" && obj !== null && !Array.isArray(obj);
}
+/**
+ * Check if an object is empty or in other words has no own properties
+ */
+export function isObjectEmpty(obj: object) {
+ for (const k in obj)
+ if (Object.hasOwn(obj, k)) return false;
+
+ return true;
+}
+
/**
* Returns null if value is not a URL, otherwise return URL object.
* Avoids having to wrap url checks in a try/catch