diff --git a/package.json b/package.json index 9b4067d4..e63c0bcf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.8.1", + "version": "1.8.2", "description": "The other cutest Discord client mod", "homepage": "https://github.com/Equicord/Equicord#readme", "bugs": { diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 8cb7d18a..96aac303 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -16,20 +16,24 @@ * along with this program. If not, see . */ -import { DataStore } from "@api/index"; +import { Link } from "@components/Link"; +import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab"; import { Devs, EquicordDevs, SUPPORT_CHANNEL_ID, SUPPORT_CHANNEL_IDS, VC_SUPPORT_CHANNEL_ID } from "@utils/constants"; import { isEquicordPluginDev, isPluginDev } from "@utils/misc"; +import { Margins } from "@utils/margins"; +import { relaunch } from "@utils/native"; import { makeCodeblock } from "@utils/text"; import definePlugin from "@utils/types"; -import { isOutdated } from "@utils/updater"; -import { Alerts, Forms, UserStore } from "@webpack/common"; +import { isOutdated, update } from "@utils/updater"; +import { Alerts, Card, ChannelStore, Forms, GuildMemberStore, NavigationRouter, Parser, RelationshipStore, UserStore } from "@webpack/common"; import gitHash from "~git-hash"; import plugins from "~plugins"; import settings from "./settings"; +import ErrorBoundary from "@components/ErrorBoundary"; -const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss"; +const VENCORD_GUILD_ID = "1015060230222131221"; const AllowedChannelIds = [ SUPPORT_CHANNEL_ID, @@ -37,6 +41,12 @@ const AllowedChannelIds = [ "1173342942858055721", // Equicord > #support ]; +const TrustedRolesIds = [ + "1026534353167208489", // contributor + "1026504932959977532", // regular + "1042507929485586532", // donor +]; + export default definePlugin({ name: "SupportHelper", required: true, @@ -44,6 +54,14 @@ export default definePlugin({ authors: [Devs.Ven, EquicordDevs.thororen], dependencies: ["CommandsAPI"], + patches: [{ + find: ".BEGINNING_DM.format", + replacement: { + match: /BEGINNING_DM\.format\(\{.+?\}\),(?=.{0,100}userId:(\i\.getRecipientId\(\)))/, + replace: "$& $self.ContributorDmWarningCard({ userId: $1 })," + } + }], + commands: [{ name: "equicord-debug", description: "Send Equicord Debug info", @@ -64,15 +82,13 @@ export default definePlugin({ const isApiPlugin = (plugin: string) => plugin.endsWith("API") || plugins[plugin].required; const enabledPlugins = Object.keys(plugins).filter(p => Vencord.Plugins.isPluginEnabled(p) && !isApiPlugin(p)); - const enabledApiPlugins = Object.keys(plugins).filter(p => Vencord.Plugins.isPluginEnabled(p) && isApiPlugin(p)); const info = { - Vencord: `v${VERSION} • ${gitHash}${settings.additionalInfo} - ${Intl.DateTimeFormat("en-GB", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`, - "Discord Branch": RELEASE_CHANNEL, - Client: client, - Platform: window.navigator.platform, - Outdated: isOutdated, - OpenAsar: "openasar" in window, + Vencord: + `v${VERSION} • [${gitHash}]()` + + `${settings.additionalInfo} - ${Intl.DateTimeFormat("en-GB", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`, + Client: `${RELEASE_CHANNEL} ~ ${client}`, + Platform: window.navigator.platform }; if (IS_DISCORD_DESKTOP) { @@ -80,11 +96,10 @@ export default definePlugin({ } const debugInfo = ` -**Equicord Debug Info** ->>> ${Object.entries(info).map(([k, v]) => `${k}: ${v}`).join("\n")} +>>> ${Object.entries(info).map(([k, v]) => `**${k}**: ${v}`).join("\n")} -Enabled Plugins (${enabledPlugins.length + enabledApiPlugins.length}): -${makeCodeblock(enabledPlugins.join(", ") + "\n\n" + enabledApiPlugins.join(", "))} +Enabled Plugins (${enabledPlugins.length}): +${makeCodeblock(enabledPlugins.join(", "))} `; return { @@ -114,25 +129,75 @@ ${makeCodeblock(enabledPlugins.join(", ") + "\n\n" + enabledApiPlugins.join(", " onConfirm: () => history.back() }); - if (isPluginDev(UserStore.getCurrentUser().id)) return; - if (isEquicordPluginDev(UserStore.getCurrentUser().id)) return; + const selfId = UserStore.getCurrentUser()?.id; + if (!selfId || isPluginDev(selfId) || isEquicordPluginDev(selfId)) return; - if (isOutdated && gitHash !== await DataStore.get(REMEMBER_DISMISS_KEY)) { - const rememberDismiss = () => DataStore.set(REMEMBER_DISMISS_KEY, gitHash); - - Alerts.show({ + if (isOutdated) { + return Alerts.show({ title: "Hold on!", body:
You are using an outdated version of Vencord! Chances are, your issue is already fixed. - - Please first update using the Updater Page in Settings, or use the VencordInstaller (Update Vencord Button) - to do so, in case you can't access the Updater page. + + Please first update before asking for support!
, - onCancel: rememberDismiss, - onConfirm: rememberDismiss + onCancel: () => openUpdaterModal!(), + cancelText: "View Updates", + confirmText: "Update & Restart Now", + async onConfirm() { + await update(); + relaunch(); + }, + secondaryConfirmText: "I know what I'm doing or I can't update" }); } - }, - } + + // @ts-ignore outdated type + const roles = GuildMemberStore.getSelfMember(VENCORD_GUILD_ID)?.roles; + if (!roles || TrustedRolesIds.some(id => roles.includes(id))) return; + + if (IS_UPDATER_DISABLED) { + return Alerts.show({ + title: "Hold on!", + body:
+ You are using an externally updated Vencord version, which we do not provide support for! + + Please either switch to an officially supported version of Vencord, or + contact your package maintainer for support instead. + +
, + onCloseCallback: () => setTimeout(() => NavigationRouter.back(), 50) + }); + } + + const repo = await VencordNative.updater.getRepo(); + if (repo.ok && !repo.value.includes("Vendicated/Vencord")) { + return Alerts.show({ + title: "Hold on!", + body:
+ You are using a fork of Vencord, which we do not provide support for! + + Please either switch to an officially supported version of Vencord, or + contact your package maintainer for support instead. + +
, + onCloseCallback: () => setTimeout(() => NavigationRouter.back(), 50) + }); + } + } + }, + + ContributorDmWarningCard: ErrorBoundary.wrap(({ userId }) => { + if (!isPluginDev(userId)) return null; + if (RelationshipStore.isFriend(userId)) return null; + + return ( + + Please do not private message Vencord plugin developers for support! +
+ Instead, use the Vencord support channel: {Parser.parse("https://discord.com/channels/1015060230222131221/1026515880080842772")} + {!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"} +
+ ); + }, { noop: true }) }); diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index 70e4070c..0b06d035 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -127,7 +127,7 @@ export default definePlugin({ }, // If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children { - match: /unreadMentionsIndicatorBottom,barClassName.+?}\)\]/, + match: /unreadMentionsIndicatorBottom,.+?}\)\]/, replace: "$&.filter($self.makeGuildsBarTreeFilter(!!arguments[0].isBetterFolders))" }, // Export the isBetterFolders variable to the folders component diff --git a/src/plugins/lastfm/index.tsx b/src/plugins/lastfm/index.tsx index 5dfec8a3..1213ece2 100644 --- a/src/plugins/lastfm/index.tsx +++ b/src/plugins/lastfm/index.tsx @@ -77,7 +77,8 @@ const enum NameFormat { ArtistFirst = "artist-first", SongFirst = "song-first", ArtistOnly = "artist", - SongOnly = "song" + SongOnly = "song", + AlbumName = "album" } const applicationId = "1108588077900898414"; @@ -147,6 +148,10 @@ const settings = definePluginSettings({ { label: "Use song name only", value: NameFormat.SongOnly + }, + { + label: "Use album name (falls back to custom status text if song has no album)", + value: NameFormat.AlbumName } ], }, @@ -313,6 +318,8 @@ export default definePlugin({ return trackData.artist; case NameFormat.SongOnly: return trackData.name; + case NameFormat.AlbumName: + return trackData.album || settings.store.statusName; default: return settings.store.statusName; } diff --git a/src/plugins/messageLatency/README.md b/src/plugins/messageLatency/README.md new file mode 100644 index 00000000..8d2a776c --- /dev/null +++ b/src/plugins/messageLatency/README.md @@ -0,0 +1,31 @@ +# MessageLatency + +Displays an indicator for messages that took ≥n seconds to send. + +> **NOTE** +> +> - This plugin only applies to messages received after opening the channel +> - False positives can exist if the user's system clock has drifted. +> - Grouped messages only display latency of the first message + +## Demo + +### Chat View + +![chat-view](https://github.com/Vendicated/Vencord/assets/82430093/69430881-60b3-422f-aa3d-c62953837566) + +### Clock -ve Drift + +![pissbot-on-top](https://github.com/Vendicated/Vencord/assets/82430093/d9248b66-e761-4872-8829-e8bf4fea6ec8) + +### Clock +ve Drift + +![dumb-ai](https://github.com/Vendicated/Vencord/assets/82430093/0e9783cf-51d5-4559-ae10-42399e7d4099) + +### Connection Delay + +![who-this](https://github.com/Vendicated/Vencord/assets/82430093/fd68873d-8630-42cc-a166-e9063d2718b2) + +### Icons + +![icons](https://github.com/Vendicated/Vencord/assets/82430093/17630bd9-44ee-4967-bcdf-3315eb6eca85) diff --git a/src/plugins/messageLatency/index.tsx b/src/plugins/messageLatency/index.tsx new file mode 100644 index 00000000..0b6d7503 --- /dev/null +++ b/src/plugins/messageLatency/index.tsx @@ -0,0 +1,147 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { Devs } from "@utils/constants"; +import { isNonNullish } from "@utils/guards"; +import definePlugin, { OptionType } from "@utils/types"; +import { findExportedComponentLazy } from "@webpack"; +import { SnowflakeUtils, Tooltip } from "@webpack/common"; +import { Message } from "discord-types/general"; + +type FillValue = ("status-danger" | "status-warning" | "text-muted"); +type Fill = [FillValue, FillValue, FillValue]; +type DiffKey = keyof Diff; + +interface Diff { + days: number, + hours: number, + minutes: number, + seconds: number; +} + +const HiddenVisually = findExportedComponentLazy("HiddenVisually"); + +export default definePlugin({ + name: "MessageLatency", + description: "Displays an indicator for messages that took ≥n seconds to send", + authors: [Devs.arHSM], + settings: definePluginSettings({ + latency: { + type: OptionType.NUMBER, + description: "Threshold in seconds for latency indicator", + default: 2 + } + }), + patches: [ + { + find: "showCommunicationDisabledStyles", + replacement: { + match: /(message:(\i),avatar:\i,username:\(0,\i.jsxs\)\(\i.Fragment,\{children:\[)(\i&&)/, + replace: "$1$self.Tooltip()({ message: $2 }),$3" + } + } + ], + stringDelta(delta: number) { + const diff: Diff = { + days: Math.round(delta / (60 * 60 * 24)), + hours: Math.round((delta / (60 * 60)) % 24), + minutes: Math.round((delta / (60)) % 60), + seconds: Math.round(delta % 60), + }; + + const str = (k: DiffKey) => diff[k] > 0 ? `${diff[k]} ${k}` : null; + const keys = Object.keys(diff) as DiffKey[]; + + return keys.map(str).filter(isNonNullish).join(" ") || "0 seconds"; + }, + latencyTooltipData(message: Message) { + const { id, nonce } = message; + + // Message wasn't received through gateway + if (!isNonNullish(nonce)) return null; + + const delta = Math.round((SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce)) / 1000); + + // Thanks dziurwa (I hate you) + // This is when the user's clock is ahead + // Can't do anything if the clock is behind + const abs = Math.abs(delta); + const ahead = abs !== delta; + + const stringDelta = this.stringDelta(abs); + + // Also thanks dziurwa + // 2 minutes + const TROLL_LIMIT = 2 * 60; + const { latency } = this.settings.store; + + const fill: Fill = delta >= TROLL_LIMIT || ahead ? ["text-muted", "text-muted", "text-muted"] : delta >= (latency * 2) ? ["status-danger", "text-muted", "text-muted"] : ["status-warning", "status-warning", "text-muted"]; + + return abs >= latency ? { delta: stringDelta, ahead: abs !== delta, fill } : null; + }, + Tooltip() { + return ErrorBoundary.wrap(({ message }: { message: Message; }) => { + + const d = this.latencyTooltipData(message); + + if (!isNonNullish(d)) return null; + + return + { + props => <> + {} + {/* Time Out indicator uses this, I think this is for a11y */} + Delayed Message + + } + ; + }); + }, + Icon({ delta, fill, props }: { + delta: string; + fill: Fill, + props: { + onClick(): void; + onMouseEnter(): void; + onMouseLeave(): void; + onContextMenu(): void; + onFocus(): void; + onBlur(): void; + "aria-label"?: string; + }; + }) { + return + + + + ; + } +}); diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index 95234d5a..731e4b84 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -255,7 +255,7 @@ export default definePlugin({ replace: "$1" + ".update($3,m =>" + " (($2.message.flags & 64) === 64 || $self.shouldIgnore($2.message, true)) ? m :" + - " $2.message.content !== m.content ?" + + " $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/replyTimestamp/README.md b/src/plugins/replyTimestamp/README.md new file mode 100644 index 00000000..b7952bf3 --- /dev/null +++ b/src/plugins/replyTimestamp/README.md @@ -0,0 +1,5 @@ +# ReplyTimestamp + +Shows timestamps on the previews of replied-to messages. Pretty simple. + +![](https://github.com/Vendicated/Vencord/assets/1547062/62e2b67a-e567-4c7a-884d-4640f897f7e0) diff --git a/src/plugins/replyTimestamp/index.tsx b/src/plugins/replyTimestamp/index.tsx new file mode 100644 index 00000000..05ec28b1 --- /dev/null +++ b/src/plugins/replyTimestamp/index.tsx @@ -0,0 +1,77 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./style.css"; + +import ErrorBoundary from "@components/ErrorBoundary"; +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; +import { findByPropsLazy } from "@webpack"; +import { Timestamp } from "@webpack/common"; +import type { Message } from "discord-types/general"; +import type { HTMLAttributes } from "react"; + +const { getMessageTimestampId } = findByPropsLazy("getMessageTimestampId"); +const { calendarFormat, dateFormat, isSameDay } = findByPropsLazy("calendarFormat", "dateFormat", "isSameDay", "accessibilityLabelCalendarFormat"); +const MessageClasses = findByPropsLazy("separator", "latin24CompactTimeStamp"); + +function Sep(props: HTMLAttributes) { + return ; +} + +const enum ReferencedMessageState { + LOADED = 0, + NOT_LOADED = 1, + DELETED = 2, +} + +type ReferencedMessage = { state: ReferencedMessageState.LOADED; message: Message; } | { state: ReferencedMessageState.NOT_LOADED | ReferencedMessageState.DELETED; }; + +function ReplyTimestamp({ + referencedMessage, + baseMessage, +}: { + referencedMessage: ReferencedMessage, + baseMessage: Message; +}) { + if (referencedMessage.state !== ReferencedMessageState.LOADED) return null; + const refTimestamp = referencedMessage.message.timestamp as any; + const baseTimestamp = baseMessage.timestamp as any; + return ( + + [ + {isSameDay(refTimestamp, baseTimestamp) + ? dateFormat(refTimestamp, "LT") + : calendarFormat(refTimestamp) + } + ] + + ); +} + +export default definePlugin({ + name: "ReplyTimestamp", + description: "Shows a timestamp on replied-message previews", + authors: [Devs.Kyuuhachi], + + patches: [ + { + find: "renderSingleLineMessage:function()", + replacement: { + match: /(?<="aria-label":\i,children:\[)(?=\i,\i,\i\])/, + replace: "$self.ReplyTimestamp(arguments[0])," + } + } + ], + + ReplyTimestamp: ErrorBoundary.wrap(ReplyTimestamp, { noop: true }), +}); diff --git a/src/plugins/replyTimestamp/style.css b/src/plugins/replyTimestamp/style.css new file mode 100644 index 00000000..f4237171 --- /dev/null +++ b/src/plugins/replyTimestamp/style.css @@ -0,0 +1,3 @@ +.vc-reply-timestamp { + margin-right: 0.25em; +} diff --git a/src/plugins/showHiddenThings/index.ts b/src/plugins/showHiddenThings/index.ts index e7be929b..1858582a 100644 --- a/src/plugins/showHiddenThings/index.ts +++ b/src/plugins/showHiddenThings/index.ts @@ -31,12 +31,22 @@ const settings = definePluginSettings({ description: "Show the invites paused tooltip in the server list.", default: true, }, + showModView: { + type: OptionType.BOOLEAN, + description: "Show the member mod view context menu item in all servers.", + default: true, + }, + disableDiscoveryFilters: { + type: OptionType.BOOLEAN, + description: "Disable filters in Server Discovery search that hide servers that don't meet discovery criteria.", + default: true, + }, }); migratePluginSettings("ShowHiddenThings", "ShowTimeouts"); export default definePlugin({ name: "ShowHiddenThings", - tags: ["ShowTimeouts", "ShowInvitesPaused"], + tags: ["ShowTimeouts", "ShowInvitesPaused", "ShowModView", "DisableDiscoveryFilters"], description: "Displays various moderator-only elements regardless of permissions.", authors: [Devs.Dolfies], patches: [ @@ -55,6 +65,22 @@ export default definePlugin({ match: /\i\.\i\.can\(\i\.Permissions.MANAGE_GUILD,\i\)/, replace: "true", }, + }, + { + find: "canAccessGuildMemberModViewWithExperiment:", + predicate: () => settings.store.showModView, + replacement: { + match: /return \i\.hasAny\(\i\.computePermissions\(\{user:\i,context:\i,checkElevated:!1\}\),\i\.MemberSafetyPagePermissions\)/, + replace: "return true", + } + }, + { + find: "auto_removed:", + predicate: () => settings.store.disableDiscoveryFilters, + replacement: { + match: /filters:\i\.join\(" AND "\),facets:\[/, + replace: "facets:[" + } } ], settings, diff --git a/src/plugins/voiceDownload/index.tsx b/src/plugins/voiceDownload/index.tsx new file mode 100644 index 00000000..8586b9f9 --- /dev/null +++ b/src/plugins/voiceDownload/index.tsx @@ -0,0 +1,52 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./style.css"; + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +export default definePlugin({ + name: "VoiceDownload", + description: "Adds a download to voice messages. (Opens a new browser tab)", + authors: [Devs.puv], + patches: [ + { + find: "rippleContainer,children", + replacement: { + match: /\(0,\i\.jsx\).{0,150},children:.{0,50}\("source",{src:(\i)}\)}\)/, + replace: "[$&, $self.renderDownload($1)]" + } + } + ], + + renderDownload(src: string) { + return ( + e.stopPropagation()} + aria-label="Download voice message" + > + + + ); + }, + + Icon: () => ( + + + + ), +}); diff --git a/src/plugins/voiceDownload/style.css b/src/plugins/voiceDownload/style.css new file mode 100644 index 00000000..2b776023 --- /dev/null +++ b/src/plugins/voiceDownload/style.css @@ -0,0 +1,12 @@ +.vc-voice-download { + width: 24px; + height: 24px; + color: var(--interactive-normal); + margin-left: 12px; + cursor: pointer; + position: relative; +} + +.vc-voice-download:hover { + color: var(--interactive-active); +} diff --git a/src/plugins/webScreenShareFixes.web/index.ts b/src/plugins/webScreenShareFixes.web/index.ts new file mode 100644 index 00000000..8d1ab582 --- /dev/null +++ b/src/plugins/webScreenShareFixes.web/index.ts @@ -0,0 +1,30 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +export default definePlugin({ + name: "WebScreenShareFixes", + authors: [Devs.Kaitlyn], + description: "Removes 2500kbps bitrate cap on chromium and vesktop clients.", + enabledByDefault: true, + patches: [ + { + find: "x-google-max-bitrate", + replacement: [ + { + match: /"x-google-max-bitrate=".concat\(\i\)/, + replace: '"x-google-max-bitrate=".concat("80_000")' + }, + { + match: /;level-asymmetry-allowed=1/, + replace: ";b=AS:800000;level-asymmetry-allowed=1" + } + ] + } + ] +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 8ef56066..ddeca008 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -268,6 +268,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "Dziurwa", id: 1001086404203389018n }, + arHSM: { + name: "arHSM", + id: 841509053422632990n + }, F53: { name: "F53", id: 280411966126948353n @@ -428,6 +432,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "newwares", id: 421405303951851520n }, + puv: { + name: "puv", + id: 469441552251355137n + }, Kodarru: { name: "Kodarru", id: 785227396218748949n