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={ 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"))}
+ )} 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
- {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 +
- - + {!!msgContent && ( + + )}
@@ -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 - - +
+ +
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