From 581fe252a44d7ba24f277cc9d28abd7a8f5d6729 Mon Sep 17 00:00:00 2001 From: AutumnVN Date: Tue, 3 Oct 2023 07:39:34 +0700 Subject: [PATCH 0001/1143] fix imageZoom (#1772) --- src/plugins/imageZoom/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/imageZoom/index.tsx b/src/plugins/imageZoom/index.tsx index cca0db02..60f8a22c 100644 --- a/src/plugins/imageZoom/index.tsx +++ b/src/plugins/imageZoom/index.tsx @@ -165,7 +165,7 @@ export default definePlugin({ { find: '"renderLinkComponent","maxWidth"', replacement: { - match: /(return\(.{1,100}\(\)\.wrapper.{1,100})(src)/, + match: /(return\(.{1,100}\(\)\.wrapper.{1,200})(src)/, replace: `$1id: '${ELEMENT_ID}',$2` } }, From 64c7581286d90ec7fd9a447bf440e5706bd942aa Mon Sep 17 00:00:00 2001 From: Lewis Crichton Date: Tue, 3 Oct 2023 17:46:17 +0100 Subject: [PATCH 0002/1143] feat(permissionfreewill): onboarding deletion --- src/plugins/permissionFreeWill/README.md | 5 +++++ src/plugins/permissionFreeWill/index.ts | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/plugins/permissionFreeWill/README.md b/src/plugins/permissionFreeWill/README.md index ca30575f..04c526b7 100644 --- a/src/plugins/permissionFreeWill/README.md +++ b/src/plugins/permissionFreeWill/README.md @@ -7,3 +7,8 @@ you don't want to do this") and onboarding requirements ("Making this change wil This plugin will let you create permissions in servers that **WILL** lock you out of channels until an administrator can resolve it for you. Please be careful with the overwrites you are making and check carefully. + +## Community Server Channels + +Community Server channels (i.e., `#rules` and `#moderator-only`) are actually mandatory and their existence is enforced +by the API, therefore this plugin cannot remove the restrictions behind them. diff --git a/src/plugins/permissionFreeWill/index.ts b/src/plugins/permissionFreeWill/index.ts index f3fdf159..80d546d6 100644 --- a/src/plugins/permissionFreeWill/index.ts +++ b/src/plugins/permissionFreeWill/index.ts @@ -50,6 +50,17 @@ export default definePlugin({ } ], predicate: () => settings.store.onboarding + }, + // Onboarding deletion + { + find: "Messages.DELETE_DEFAULT_CHANNEL_BODY", + replacement: [ + { + match: /if\((?=null!=\i.{5,20}Messages.DELETE_DEFAULT_CHANNEL_BODY)/, + replace: "$&false&&" + } + ], + predicate: () => settings.store.onboarding } ], settings From 726a1b5d96eef2f32cba56a45ac51961c7f4ba07 Mon Sep 17 00:00:00 2001 From: wntiv-main <60457971+wntiv-main@users.noreply.github.com> Date: Fri, 6 Oct 2023 14:16:21 +1300 Subject: [PATCH 0003/1143] Fix command API (#1776) Co-authored-by: V --- src/plugins/_api/commands.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/_api/commands.ts b/src/plugins/_api/commands.ts index 2197b307..b7d6ef9f 100644 --- a/src/plugins/_api/commands.ts +++ b/src/plugins/_api/commands.ts @@ -26,7 +26,7 @@ export default definePlugin({ patches: [ // obtain BUILT_IN_COMMANDS instance { - find: '"giphy","tenor"', + find: ',"tenor"', replacement: [ { // Matches BUILT_IN_COMMANDS. This is not exported so this is @@ -34,7 +34,7 @@ export default definePlugin({ // patch simpler // textCommands = builtInCommands.filter(...) - match: /(?<=\w=)(\w)(\.filter\(.{0,30}giphy)/, + match: /(?<=\w=)(\w)(\.filter\(.{0,60}tenor)/, replace: "Vencord.Api.Commands._init($1)$2", } ], From 03b5dc9c27250be09ae1a8c80842edf8cde642d9 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 6 Oct 2023 03:17:44 +0200 Subject: [PATCH 0004/1143] BetterRoleDot: Fix ci test false positives --- src/plugins/betterRoleDot/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/betterRoleDot/index.ts b/src/plugins/betterRoleDot/index.ts index e8f69b89..ed121b9a 100644 --- a/src/plugins/betterRoleDot/index.ts +++ b/src/plugins/betterRoleDot/index.ts @@ -48,6 +48,7 @@ export default definePlugin({ { find: ".ADD_ROLE_A11Y_LABEL", predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles, + noWarn: true, replacement: { match: /"dot"===\i/, replace: "true" @@ -56,6 +57,7 @@ export default definePlugin({ { find: ".roleVerifiedIcon", predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles, + noWarn: true, replacement: { match: /"dot"===\i/, replace: "true" From 5eb9dd04dff212686e3395752877ddf62c1f78e2 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 6 Oct 2023 04:00:09 +0200 Subject: [PATCH 0005/1143] Fix member list decorations api --- src/api/MemberListDecorators.ts | 5 ++--- src/plugins/_api/memberListDecorators.ts | 21 ++++++++++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/api/MemberListDecorators.ts b/src/api/MemberListDecorators.ts index fade2a7c..e148bb0a 100644 --- a/src/api/MemberListDecorators.ts +++ b/src/api/MemberListDecorators.ts @@ -20,7 +20,6 @@ import { Channel, User } from "discord-types/general/index.js"; interface DecoratorProps { activities: any[]; - canUseAvatarDecorations: boolean; channel: Channel; /** * Only for DM members @@ -52,9 +51,9 @@ export function removeDecorator(identifier: string) { decorators.delete(identifier); } -export function __addDecoratorsToList(props: DecoratorProps): (JSX.Element | null)[] { +export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] { const isInGuild = !!(props.guildId); - return [...decorators.values()].map(decoratorObj => { + return Array.from(decorators.values(), decoratorObj => { const { decorator, onlyIn } = decoratorObj; // this can most likely be done cleaner if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) { diff --git a/src/plugins/_api/memberListDecorators.ts b/src/plugins/_api/memberListDecorators.ts index 6b8cffab..a6d4125d 100644 --- a/src/plugins/_api/memberListDecorators.ts +++ b/src/plugins/_api/memberListDecorators.ts @@ -22,21 +22,28 @@ import definePlugin from "@utils/types"; export default definePlugin({ name: "MemberListDecoratorsAPI", description: "API to add decorators to member list (both in servers and DMs)", - authors: [Devs.TheSun], + authors: [Devs.TheSun, Devs.Ven], patches: [ { find: "lostPermissionTooltipText,", replacement: { - match: /Fragment,{children:\[(.{30,80})\]/, - replace: "Fragment,{children:Vencord.Api.MemberListDecorators.__addDecoratorsToList(this.props).concat($1)" + match: /decorators:.{0,100}?children:\[(?<=(\i)\.lostPermissionTooltipText.+?)/, + replace: "$&...Vencord.Api.MemberListDecorators.__getDecorators($1)," } }, { find: "PrivateChannel.renderAvatar", - replacement: { - match: /(subText:(.{1,2})\.renderSubtitle\(\).{1,50}decorators):(.{30,100}:null)/, - replace: "$1:Vencord.Api.MemberListDecorators.__addDecoratorsToList($2.props).concat($3)" - } + replacement: [ + // props are shadowed by nested props so we have to do this + { + match: /\i=(\i)\.applicationStream,/, + replace: "$&vencordProps=$1," + }, + { + match: /decorators:(\i\.isSystemDM\(\))\?(.+?):null/, + replace: "decorators:[...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)), $1?$2:null]" + } + ] } ], }); From 79295683eeb513de7cf07cf647bf56de5cae37aa Mon Sep 17 00:00:00 2001 From: AutumnVN Date: Fri, 6 Oct 2023 09:07:16 +0700 Subject: [PATCH 0006/1143] PictureInPicture: pip button hover styles (#1775) Co-authored-by: V --- src/plugins/pictureInPicture/index.tsx | 5 ++++- src/plugins/pictureInPicture/styles.css | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 src/plugins/pictureInPicture/styles.css diff --git a/src/plugins/pictureInPicture/index.tsx b/src/plugins/pictureInPicture/index.tsx index d10d42ff..bb6aee18 100644 --- a/src/plugins/pictureInPicture/index.tsx +++ b/src/plugins/pictureInPicture/index.tsx @@ -4,6 +4,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import "./styles.css"; + import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; @@ -41,6 +43,7 @@ export default definePlugin({ {tooltipProps => (
diff --git a/src/plugins/pictureInPicture/styles.css b/src/plugins/pictureInPicture/styles.css new file mode 100644 index 00000000..d0a81ca5 --- /dev/null +++ b/src/plugins/pictureInPicture/styles.css @@ -0,0 +1,8 @@ +.vc-pip-button { + color: var(--interactive-normal); +} + +.vc-pip-button:hover { + background-color: var(--background-modifier-hover); + color: var(--interactive-hover); +} From 9e63da6d786b36cc27c4a24791f44635ba1e0c23 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 6 Oct 2023 04:08:49 +0200 Subject: [PATCH 0007/1143] bump to v1.5.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 13481d01..c005800a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.5.4", + "version": "1.5.5", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 47a39a062e4437c3e18b1f5cb09f4abe82d0bb82 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 6 Oct 2023 17:54:46 +0200 Subject: [PATCH 0008/1143] Fix Vesktop SettingsCog context menu --- src/plugins/_core/settings.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index 88ebb578..f7fcdd0a 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -39,8 +39,9 @@ export default definePlugin({ addContextMenuPatch("user-settings-cog", children => () => { const section = children.find(c => Array.isArray(c) && c.some(it => it?.props?.id === "VencordSettings")) as any; section?.forEach(c => { - if (c?.props?.id?.startsWith("Vencord")) { - c.props.action = () => SettingsRouter.open(c.props.id); + const id = c?.props?.id; + if (id?.startsWith("Vencord") || id?.startsWith("Vesktop")) { + c.props.action = () => SettingsRouter.open(id); } }); }); From df214e1e93bf7bc6c7b768df75f10f764a41ff13 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 6 Oct 2023 18:01:19 +0200 Subject: [PATCH 0009/1143] chore: remove legacy code --- src/plugins/fakeNitro/index.ts | 3 +-- src/plugins/pronoundb/index.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/plugins/fakeNitro/index.ts b/src/plugins/fakeNitro/index.ts index 07191e14..a11a43de 100644 --- a/src/plugins/fakeNitro/index.ts +++ b/src/plugins/fakeNitro/index.ts @@ -222,8 +222,7 @@ export default definePlugin({ predicate: () => settings.store.enableStreamQualityBypass, replacement: [ "canUseHighVideoUploadQuality", - // TODO: Remove the last two when they get removed from stable - "(?:canStreamQuality|canStreamHighQuality|canStreamMidQuality)", + "canStreamQuality", ].map(func => { return { match: new RegExp(`${func}:function\\(\\i(?:,\\i)?\\){`, "g"), diff --git a/src/plugins/pronoundb/index.ts b/src/plugins/pronoundb/index.ts index 52fefdc3..c9dc2725 100644 --- a/src/plugins/pronoundb/index.ts +++ b/src/plugins/pronoundb/index.ts @@ -68,8 +68,7 @@ export default definePlugin({ find: ".USER_PROFILE_ACTIVITY", replacement: [ { - /* FIXME: old name is getGlobalName, new name is getName. Remove optional Global once stable has also migrated */ - match: /\.get(?:Global)?Name\(\i\);(?<=displayProfile.{0,200})/, + match: /\.getName\(\i\);(?<=displayProfile.{0,200})/, replace: "$&const [vcPronounce,vcPronounSource]=$self.useProfilePronouns(arguments[0].user.id,true);if(arguments[0].displayProfile&&vcPronounce)arguments[0].displayProfile.pronouns=vcPronounce;" }, PRONOUN_TOOLTIP_PATCH From f66e35b65875e7669e5ccdb2710d83f10f41baa3 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 6 Oct 2023 18:05:53 +0200 Subject: [PATCH 0010/1143] fix(SendTimestamps): Do not add to ReviewDB input --- src/plugins/sendTimestamps/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plugins/sendTimestamps/index.tsx b/src/plugins/sendTimestamps/index.tsx index a69136ba..a69dbacc 100644 --- a/src/plugins/sendTimestamps/index.tsx +++ b/src/plugins/sendTimestamps/index.tsx @@ -124,7 +124,7 @@ export default definePlugin({ find: ".activeCommandOption", replacement: { match: /(.)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/, - replace: "$&;try{$2||$1.push($self.chatBarIcon())}catch{}", + replace: "$&;try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}", } }, ], @@ -139,7 +139,9 @@ export default definePlugin({ removePreSendListener(this.listener); }, - chatBarIcon() { + chatBarIcon(chatBoxProps: { type: { analyticsName: string; }; }) { + if (chatBoxProps.type.analyticsName !== "normal") return null; + return ( {({ onMouseEnter, onMouseLeave }) => ( From 664dd0a9920aa697359b1bb07b98795ff0f1beaf Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 6 Oct 2023 18:44:22 +0200 Subject: [PATCH 0011/1143] ReviewDB: allow deleting reviews on own profile --- src/plugins/reviewDB/components/ReviewComponent.tsx | 8 ++++---- src/plugins/reviewDB/components/ReviewsView.tsx | 4 +++- src/plugins/reviewDB/utils.tsx | 8 +++++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/plugins/reviewDB/components/ReviewComponent.tsx b/src/plugins/reviewDB/components/ReviewComponent.tsx index 07bcdb28..57924001 100644 --- a/src/plugins/reviewDB/components/ReviewComponent.tsx +++ b/src/plugins/reviewDB/components/ReviewComponent.tsx @@ -20,7 +20,7 @@ import { openUserProfile } from "@utils/discord"; import { classes } from "@utils/misc"; import { LazyComponent } from "@utils/react"; import { filters, findBulk } from "@webpack"; -import { Alerts, moment, Parser, Timestamp, UserStore } from "@webpack/common"; +import { Alerts, moment, Parser, Timestamp } from "@webpack/common"; import { Review, ReviewType } from "../entities"; import { deleteReview, reportReview } from "../reviewDbApi"; @@ -30,7 +30,7 @@ import { DeleteButton, ReportButton } from "./MessageButton"; import ReviewBadge from "./ReviewBadge"; export default LazyComponent(() => { - // this is terrible, blame ven + // this is terrible, blame mantika const p = filters.byProps; const [ { cozyMessage, buttons, message, buttonsInner, groupStart }, @@ -48,7 +48,7 @@ export default LazyComponent(() => { const dateFormat = new Intl.DateTimeFormat(); - return function ReviewComponent({ review, refetch }: { review: Review; refetch(): void; }) { + return function ReviewComponent({ review, refetch, profileId }: { review: Review; refetch(): void; profileId: string; }) { function openModal() { openUserProfile(review.sender.discordID); } @@ -135,7 +135,7 @@ export default LazyComponent(() => {
- {canDeleteReview(review, UserStore.getCurrentUser().id) && ( + {canDeleteReview(profileId, review) && ( )}
diff --git a/src/plugins/reviewDB/components/ReviewsView.tsx b/src/plugins/reviewDB/components/ReviewsView.tsx index e5bc426d..5eb370f4 100644 --- a/src/plugins/reviewDB/components/ReviewsView.tsx +++ b/src/plugins/reviewDB/components/ReviewsView.tsx @@ -79,6 +79,7 @@ export default function ReviewsView({ refetch={refetch} reviews={reviewData!.reviews} hideOwnReview={hideOwnReview} + profileId={discordId} /> {showInput && ( @@ -93,7 +94,7 @@ export default function ReviewsView({ ); } -function ReviewList({ refetch, reviews, hideOwnReview }: { refetch(): void; reviews: Review[]; hideOwnReview: boolean; }) { +function ReviewList({ refetch, reviews, hideOwnReview, profileId }: { refetch(): void; reviews: Review[]; hideOwnReview: boolean; profileId: string; }) { const myId = UserStore.getCurrentUser().id; return ( @@ -104,6 +105,7 @@ function ReviewList({ refetch, reviews, hideOwnReview }: { refetch(): void; revi key={review.id} review={review} refetch={refetch} + profileId={profileId} /> )} diff --git a/src/plugins/reviewDB/utils.tsx b/src/plugins/reviewDB/utils.tsx index a9c8ca57..63ab84dd 100644 --- a/src/plugins/reviewDB/utils.tsx +++ b/src/plugins/reviewDB/utils.tsx @@ -20,7 +20,7 @@ import { classNameFactory } from "@api/Styles"; import { Logger } from "@utils/Logger"; import { openModal } from "@utils/modal"; import { findByProps } from "@webpack"; -import { React, Toasts } from "@webpack/common"; +import { React, Toasts, UserStore } from "@webpack/common"; import { Review, UserType } from "./entities"; import { settings } from "./settings"; @@ -73,9 +73,11 @@ export function showToast(text: string) { }); } -export function canDeleteReview(review: Review, userId: string) { +export function canDeleteReview(profileId: string, review: Review) { + const myId = UserStore.getCurrentUser().id; return ( - review.sender.discordID === userId + myId === profileId + || review.sender.discordID === profileId || settings.store.user?.type === UserType.Admin ); } From c0f2c974587d75a38e3e753368ef0e2e2be139fd Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 6 Oct 2023 19:40:53 +0200 Subject: [PATCH 0012/1143] ReviewDB: proper multi account support --- src/plugins/reviewDB/auth.tsx | 78 +++++++++++++++++++ .../reviewDB/components/ReviewComponent.tsx | 4 +- .../reviewDB/components/ReviewModal.tsx | 5 +- .../reviewDB/components/ReviewsView.tsx | 7 +- src/plugins/reviewDB/entities.ts | 5 ++ src/plugins/reviewDB/index.tsx | 49 ++++++++---- src/plugins/reviewDB/reviewDbApi.ts | 18 +++-- src/plugins/reviewDB/settings.tsx | 12 ++- src/plugins/reviewDB/utils.tsx | 55 +------------ 9 files changed, 143 insertions(+), 90 deletions(-) create mode 100644 src/plugins/reviewDB/auth.tsx diff --git a/src/plugins/reviewDB/auth.tsx b/src/plugins/reviewDB/auth.tsx new file mode 100644 index 00000000..1d95e47d --- /dev/null +++ b/src/plugins/reviewDB/auth.tsx @@ -0,0 +1,78 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { DataStore } from "@api/index"; +import { Logger } from "@utils/Logger"; +import { openModal } from "@utils/modal"; +import { findByPropsLazy } from "@webpack"; +import { showToast, Toasts, UserStore } from "@webpack/common"; + +import { ReviewDBAuth } from "./entities"; + +const DATA_STORE_KEY = "rdb-auth"; + +const OAuth = findByPropsLazy("OAuth2AuthorizeModal"); + +export let Auth: ReviewDBAuth = {}; + +export async function initAuth() { + Auth = await getAuth() ?? {}; +} + +export async function getAuth(): Promise { + const auth = await DataStore.get(DATA_STORE_KEY); + return auth?.[UserStore.getCurrentUser()?.id]; +} + +export async function getToken() { + const auth = await getAuth(); + return auth?.token; +} + +export async function updateAuth(newAuth: ReviewDBAuth) { + return DataStore.update(DATA_STORE_KEY, auth => { + auth ??= {}; + Auth = auth[UserStore.getCurrentUser().id] ??= {}; + + if (newAuth.token) Auth.token = newAuth.token; + if (newAuth.user) Auth.user = newAuth.user; + + return auth; + }); +} + +export function authorize(callback?: any) { + openModal(props => + { + try { + const url = new URL(response.location); + url.searchParams.append("clientMod", "vencord"); + const res = await fetch(url, { + headers: new Headers({ Accept: "application/json" }) + }); + const { token, success } = await res.json(); + if (success) { + updateAuth({ token }); + showToast("Successfully logged in!"); + callback?.(); + } else if (res.status === 1) { + showToast("An Error occurred while logging in.", Toasts.Type.FAILURE); + } + } catch (e) { + new Logger("ReviewDB").error("Failed to authorize", e); + } + }} + /> + ); +} diff --git a/src/plugins/reviewDB/components/ReviewComponent.tsx b/src/plugins/reviewDB/components/ReviewComponent.tsx index 57924001..18659179 100644 --- a/src/plugins/reviewDB/components/ReviewComponent.tsx +++ b/src/plugins/reviewDB/components/ReviewComponent.tsx @@ -20,12 +20,12 @@ import { openUserProfile } from "@utils/discord"; import { classes } from "@utils/misc"; import { LazyComponent } from "@utils/react"; import { filters, findBulk } from "@webpack"; -import { Alerts, moment, Parser, Timestamp } from "@webpack/common"; +import { Alerts, moment, Parser, showToast, Timestamp } from "@webpack/common"; import { Review, ReviewType } from "../entities"; import { deleteReview, reportReview } from "../reviewDbApi"; import { settings } from "../settings"; -import { canDeleteReview, cl, showToast } from "../utils"; +import { canDeleteReview, cl } from "../utils"; import { DeleteButton, ReportButton } from "./MessageButton"; import ReviewBadge from "./ReviewBadge"; diff --git a/src/plugins/reviewDB/components/ReviewModal.tsx b/src/plugins/reviewDB/components/ReviewModal.tsx index 6e85dc29..9669a2b3 100644 --- a/src/plugins/reviewDB/components/ReviewModal.tsx +++ b/src/plugins/reviewDB/components/ReviewModal.tsx @@ -21,8 +21,8 @@ import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, Mo import { useForceUpdater } from "@utils/react"; import { Paginator, Text, useRef, useState } from "@webpack/common"; +import { Auth } from "../auth"; import { Response, REVIEWS_PER_PAGE } from "../reviewDbApi"; -import { settings } from "../settings"; import { cl } from "../utils"; import ReviewComponent from "./ReviewComponent"; import ReviewsView, { ReviewsInputComponent } from "./ReviewsView"; @@ -35,7 +35,7 @@ function Modal({ modalProps, discordId, name }: { modalProps: any; discordId: st const ref = useRef(null); const reviewCount = data?.reviewCount; - const ownReview = data?.reviews.find(r => r.sender.discordID === settings.store.user?.discordID); + const ownReview = data?.reviews.find(r => r.sender.discordID === Auth.user?.discordID); return ( @@ -68,6 +68,7 @@ function Modal({ modalProps, discordId, name }: { modalProps: any; discordId: st )} (null); const inputType = InputTypes.FORM; inputType.disableAutoFocus = true; diff --git a/src/plugins/reviewDB/entities.ts b/src/plugins/reviewDB/entities.ts index fefb1d3f..0a77fef0 100644 --- a/src/plugins/reviewDB/entities.ts +++ b/src/plugins/reviewDB/entities.ts @@ -36,6 +36,11 @@ export const enum NotificationType { Warning = 3 } +export interface ReviewDBAuth { + token?: string; + user?: ReviewDBUser; +} + export interface Badge { name: string; description: string; diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx index a5197139..b9350ba4 100644 --- a/src/plugins/reviewDB/index.tsx +++ b/src/plugins/reviewDB/index.tsx @@ -23,16 +23,17 @@ import ErrorBoundary from "@components/ErrorBoundary"; import ExpandableHeader from "@components/ExpandableHeader"; import { OpenExternalIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; +import { Logger } from "@utils/Logger"; import definePlugin from "@utils/types"; -import { Alerts, Menu, Parser, useState } from "@webpack/common"; +import { Alerts, Menu, Parser, showToast, useState } from "@webpack/common"; import { Guild, User } from "discord-types/general"; +import { Auth, initAuth, updateAuth } from "./auth"; import { openReviewsModal } from "./components/ReviewModal"; import ReviewsView from "./components/ReviewsView"; import { NotificationType } from "./entities"; import { getCurrentUserInfo, readNotification } from "./reviewDbApi"; import { settings } from "./settings"; -import { showToast } from "./utils"; const guildPopoutPatch: NavContextMenuPatchCallback = (children, props: { guild: Guild, onClose(): void; }) => () => { children.push( @@ -62,31 +63,48 @@ export default definePlugin({ } ], - async start() { - const s = settings.store; - const { token, lastReviewId, notifyReviews } = s; + flux: { + CONNECTION_OPEN: initAuth, + }, - if (!notifyReviews || !token) return; + async start() { + addContextMenuPatch("guild-header-popout", guildPopoutPatch); + + const s = settings.store; + const { lastReviewId, notifyReviews } = s; + + const legacy = s as any as { token?: string; }; + if (legacy.token) { + await updateAuth({ token: legacy.token }); + legacy.token = undefined; + new Logger("ReviewDB").info("Migrated legacy settings"); + } + + await initAuth(); setTimeout(async () => { - const user = await getCurrentUserInfo(token); - if (lastReviewId && lastReviewId < user.lastReviewID) { - s.lastReviewId = user.lastReviewID; - if (user.lastReviewID !== 0) - showToast("You have new reviews on your profile!"); - } + if (!Auth.token) return; - addContextMenuPatch("guild-header-popout", guildPopoutPatch); + const user = await getCurrentUserInfo(Auth.token); + updateAuth({ user }); + + if (notifyReviews) { + if (lastReviewId && lastReviewId < user.lastReviewID) { + s.lastReviewId = user.lastReviewID; + if (user.lastReviewID !== 0) + showToast("You have new reviews on your profile!"); + } + } if (user.notification) { const props = user.notification.type === NotificationType.Ban ? { cancelText: "Appeal", confirmText: "Ok", - onCancel: () => + onCancel: async () => VencordNative.native.openExternal( "https://reviewdb.mantikafasi.dev/api/redirect?" + new URLSearchParams({ - token: settings.store.token!, + token: Auth.token!, page: "dashboard/appeal" }) ) @@ -105,7 +123,6 @@ export default definePlugin({ readNotification(user.notification.id); } - s.user = user; }, 4000); }, diff --git a/src/plugins/reviewDB/reviewDbApi.ts b/src/plugins/reviewDB/reviewDbApi.ts index 5370a9b3..add16dd1 100644 --- a/src/plugins/reviewDB/reviewDbApi.ts +++ b/src/plugins/reviewDB/reviewDbApi.ts @@ -16,9 +16,11 @@ * along with this program. If not, see . */ +import { showToast, Toasts } from "@webpack/common"; + +import { authorize, getToken } from "./auth"; import { Review, ReviewDBUser } from "./entities"; import { settings } from "./settings"; -import { authorize, showToast } from "./utils"; const API_URL = "https://manti.vendicated.dev"; @@ -57,7 +59,7 @@ export async function getReviews(id: string, offset = 0): Promise { }; if (!res.success) { - showToast(res.message); + showToast(res.message, Toasts.Type.FAILURE); return { ...res, reviews: [ @@ -82,7 +84,7 @@ export async function getReviews(id: string, offset = 0): Promise { } export async function addReview(review: any): Promise { - review.token = settings.store.token; + review.token = await getToken(); if (!review.token) { showToast("Please authorize to add a review."); @@ -104,7 +106,7 @@ export async function addReview(review: any): Promise { }); } -export function deleteReview(id: number): Promise { +export async function deleteReview(id: number): Promise { return fetch(API_URL + `/api/reviewdb/users/${id}/reviews`, { method: "DELETE", headers: new Headers({ @@ -112,7 +114,7 @@ export function deleteReview(id: number): Promise { Accept: "application/json", }), body: JSON.stringify({ - token: settings.store.token, + token: await getToken(), reviewid: id }) }).then(r => r.json()); @@ -127,7 +129,7 @@ export async function reportReview(id: number) { }), body: JSON.stringify({ reviewid: id, - token: settings.store.token + token: await getToken() }) }).then(r => r.json()) as Response; @@ -141,11 +143,11 @@ export function getCurrentUserInfo(token: string): Promise { }).then(r => r.json()); } -export function readNotification(id: number) { +export async function readNotification(id: number) { return fetch(API_URL + `/api/reviewdb/notifications?id=${id}`, { method: "PATCH", headers: { - "Authorization": settings.store.token || "", + "Authorization": await getToken() || "", }, }); } diff --git a/src/plugins/reviewDB/settings.tsx b/src/plugins/reviewDB/settings.tsx index e318bc7b..cf61a380 100644 --- a/src/plugins/reviewDB/settings.tsx +++ b/src/plugins/reviewDB/settings.tsx @@ -20,8 +20,7 @@ import { definePluginSettings } from "@api/Settings"; import { OptionType } from "@utils/types"; import { Button } from "@webpack/common"; -import { ReviewDBUser } from "./entities"; -import { authorize } from "./utils"; +import { authorize, getToken } from "./auth"; export const settings = definePluginSettings({ authorize: { @@ -57,10 +56,11 @@ export const settings = definePluginSettings({ type: OptionType.COMPONENT, description: "ReviewDB website", component: () => ( -
+ + + + )} ); } -function ToggleActivityComponentWithBackground({ activity }: { activity: IgnoredActivity; }) { - return ( -
- -
- ); +const ToggleIconOn = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Disable Activity", "M480-320q75 0 127.5-52.5T660-500q0-75-52.5-127.5T480-680q-75 0-127.5 52.5T300-500q0 75 52.5 127.5T480-320Zm0-72q-45 0-76.5-31.5T372-500q0-45 31.5-76.5T480-608q45 0 76.5 31.5T588-500q0 45-31.5 76.5T480-392Zm0 192q-146 0-266-81.5T40-500q54-137 174-218.5T480-800q146 0 266 81.5T920-500q-54 137-174 218.5T480-200Zm0-300Zm0 220q113 0 207.5-59.5T832-500q-50-101-144.5-160.5T480-720q-113 0-207.5 59.5T128-500q50 101 144.5 160.5T480-280Z", fill); +const ToggleIconOff = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Enable Activity", "m644-428-58-58q9-47-27-88t-93-32l-58-58q17-8 34.5-12t37.5-4q75 0 127.5 52.5T660-500q0 20-4 37.5T644-428Zm128 126-58-56q38-29 67.5-63.5T832-500q-50-101-143.5-160.5T480-720q-29 0-57 4t-55 12l-62-62q41-17 84-25.5t90-8.5q151 0 269 83.5T920-500q-23 59-60.5 109.5T772-302Zm20 246L624-222q-35 11-70.5 16.5T480-200q-151 0-269-83.5T40-500q21-53 53-98.5t73-81.5L56-792l56-56 736 736-56 56ZM222-624q-29 26-53 57t-41 67q50 101 143.5 160.5T480-280q20 0 39-2.5t39-5.5l-36-38q-11 3-21 4.5t-21 1.5q-75 0-127.5-52.5T300-500q0-11 1.5-21t4.5-21l-84-82Zm319 93Zm-151 75Z", fill); + +function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) { + if (getIgnoredActivities().some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)"); + return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--primary-400)"); } -function handleActivityToggle(e: React.MouseEvent, activity: IgnoredActivity, forceUpdateComponent: () => void) { +function handleActivityToggle(e: React.MouseEvent, activity: IgnoredActivity, forceUpdateButton: () => void) { e.stopPropagation(); - if (ignoredActivitiesCache.has(activity.id)) ignoredActivitiesCache.delete(activity.id); - else ignoredActivitiesCache.set(activity.id, activity); - forceUpdateComponent(); - saveCacheToDatastore(); + + const ignoredActivityIndex = getIgnoredActivities().findIndex(act => act.id === activity.id); + if (ignoredActivityIndex === -1) settings.store.ignoredActivities = getIgnoredActivities().concat(activity); + else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex); + + // Trigger activities recalculation + ShowCurrentGame?.updateSetting(old => old); + forceUpdateButton(); } -async function saveCacheToDatastore() { - await DataStore.set("IgnoreActivities_ignoredActivities", ignoredActivitiesCache); -} +const settings = definePluginSettings({}).withPrivateSettings<{ + ignoredActivities: IgnoredActivity[]; +}>(); -let ignoredActivitiesCache = new Map(); +function getIgnoredActivities() { + return settings.store.ignoredActivities ??= []; +} export default definePlugin({ name: "IgnoreActivities", authors: [Devs.Nuckyz], - description: "Ignore certain activities (like games and actual activities) from showing up on your status. You can configure which ones are ignored from the Registered Games and Activities tabs.", + description: "Ignore activities from showing up on your status ONLY. You can configure which ones are ignored from the Registered Games and Activities tabs.", + + dependencies: ["SettingsStoreAPI"], + settings, + patches: [ + { + find: '.displayName="LocalActivityStore"', + replacement: [ + { + match: /LISTENING.+?\)\);(?<=(\i)\.push.+?)/, + replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);` + } + ] + }, { find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY", replacement: { - match: /!(\i)(\)return null;var \i=(\i)\.overlay.+?children:)(\[.{0,70}overlayStatusText.+?\])(?=}\)}\(\))/, - replace: (_, platformCheck, restWithoutPlatformCheck, props, children) => "false" - + `${restWithoutPlatformCheck}` - + `(${platformCheck}?${children}:[])` - + `.concat(Vencord.Plugins.plugins.IgnoreActivities.renderToggleGameActivityButton(${props}))` + match: /\(\)\.removeGame.+?null(?<=(\i)\?\i=\i\.\i\.Messages\.SETTINGS_GAMES_NOW_PLAYING_STATE.+?=(\i)\.overlay.+?)/, + replace: (m, nowPlaying, props) => `${m},$self.renderToggleGameActivityButton(${props},${nowPlaying})` } }, { find: ".overlayBadge", replacement: [ { - match: /(?<=\(\)\.badgeContainer,children:).{0,50}?name:(\i)\.name.+?null/, - replace: (m, props) => `[${m},$self.renderToggleActivityButton(${props})]` + match: /(?<=\(\)\.activityTitleText.+?children:(\i)\.name.*?}\),)/, + replace: (_, props) => `$self.renderToggleActivityButton(${props}),` }, { - match: /(?<=\(\)\.badgeContainer,children:).{0,50}?name:(\i\.application)\.name.+?null/, - replace: (m, props) => `${m},$self.renderToggleActivityButton(${props})` + match: /(?<=\(\)\.activityCardDetails.+?children:(\i\.application)\.name.*?}\),)/, + replace: (_, props) => `$self.renderToggleActivityButton(${props}),` } ] - }, - { - find: '.displayName="LocalActivityStore"', - replacement: { - match: /LISTENING.+?\)\);(?<=(\i)\.push.+?)/, - replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);` - } } ], async start() { - const ignoredActivitiesData = await DataStore.get>("IgnoreActivities_ignoredActivities") ?? new Map(); - /** Migrate old data */ - if (Array.isArray(ignoredActivitiesData)) { - for (const id of ignoredActivitiesData) { - ignoredActivitiesCache.set(id, { id, type: ActivitiesTypes.Game }); - } + const oldIgnoredActivitiesData = await DataStore.get>("IgnoreActivities_ignoredActivities"); - await saveCacheToDatastore(); - } else ignoredActivitiesCache = ignoredActivitiesData; + if (oldIgnoredActivitiesData != null) { + settings.store.ignoredActivities = Array.from(oldIgnoredActivitiesData.values()) + .map(activity => ({ ...activity, name: "Unknown Name" })); - if (ignoredActivitiesCache.size !== 0) { - const gamesSeen: { id?: string; exePath: string; }[] = RunningGameStore.getGamesSeen(); + DataStore.del("IgnoreActivities_ignoredActivities"); + } - for (const ignoredActivity of ignoredActivitiesCache.values()) { + if (getIgnoredActivities().length !== 0) { + const gamesSeen = RunningGameStore.getGamesSeen() as { id?: string; exePath: string; }[]; + + for (const [index, ignoredActivity] of getIgnoredActivities().entries()) { if (ignoredActivity.type !== ActivitiesTypes.Game) continue; if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) { - /** Custom added game which no longer exists */ - ignoredActivitiesCache.delete(ignoredActivity.id); + getIgnoredActivities().splice(index, 1); } } - - await saveCacheToDatastore(); } }, - renderToggleGameActivityButton(props: { id?: string; exePath: string; }) { - return ( - - - - ); - }, - - renderToggleActivityButton(props: { id: string; }) { - return ( - - - - ); - }, - isActivityNotIgnored(props: { type: number; application_id?: string; name?: string; }) { - if (props.type === 0) { - if (props.application_id !== undefined) return !ignoredActivitiesCache.has(props.application_id); + if (props.type === 0 || props.type === 3) { + if (props.application_id != null) return !getIgnoredActivities().some(activity => activity.id === props.application_id); else { const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath; - if (exePath) return !ignoredActivitiesCache.has(exePath); + if (exePath) return !getIgnoredActivities().some(activity => activity.id === exePath); } } return true; + }, + + renderToggleGameActivityButton(props: { id?: string; name: string, exePath: string; }, nowPlaying: boolean) { + return ( + +
+ {ToggleActivityComponent({ id: props.id ?? props.exePath, name: props.name, type: ActivitiesTypes.Game }, nowPlaying)} +
+
+ ); + }, + + renderToggleActivityButton(props: { id: string; name: string; }) { + return ( + + {ToggleActivityComponent({ id: props.id, name: props.name, type: ActivitiesTypes.Embedded })} + + ); } }); From 34ac71870529f6fda5abcd98af664012b4f44873 Mon Sep 17 00:00:00 2001 From: Nico Date: Sun, 8 Oct 2023 04:20:36 +0200 Subject: [PATCH 0016/1143] fix(forceOwnerCrown): update broken patch (#1777) --- src/plugins/forceOwnerCrown/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/forceOwnerCrown/index.ts b/src/plugins/forceOwnerCrown/index.ts index 3122410f..5fa63f00 100644 --- a/src/plugins/forceOwnerCrown/index.ts +++ b/src/plugins/forceOwnerCrown/index.ts @@ -27,12 +27,12 @@ export default definePlugin({ patches: [ { // This is the logic where it decides whether to render the owner crown or not - find: ".renderOwner=", + find: ".MULTIPLE_AVATAR", replacement: { - match: /isOwner;return null!=(\w+)?&&/g, - replace: "isOwner;if($self.isGuildOwner(this.props)){$1=true;}return null!=$1&&" + match: /(\i)=(\i)\.isOwner,/, + replace: "$1=$self.isGuildOwner($2)," } - }, + } ], isGuildOwner(props) { // Check if channel is a Group DM, if so return false From 377cf600550da9c1b49924bd996fc86be9011048 Mon Sep 17 00:00:00 2001 From: V Date: Mon, 9 Oct 2023 03:01:35 +0200 Subject: [PATCH 0017/1143] fix global settings listeners --- src/api/Settings.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api/Settings.ts b/src/api/Settings.ts index c380f631..368f88f7 100644 --- a/src/api/Settings.ts +++ b/src/api/Settings.ts @@ -237,7 +237,8 @@ type ResolvePropDeep = P extends "" ? T : export function addSettingsListener(path: Path, onUpdate: (newValue: Settings[Path], path: Path) => void): void; export function addSettingsListener(path: Path, onUpdate: (newValue: Path extends "" ? any : ResolvePropDeep, path: Path extends "" ? string : Path) => void): void; export function addSettingsListener(path: string, onUpdate: (newValue: any, path: string) => void) { - ((onUpdate as SubscriptionCallback)._paths ??= []).push(path); + if (path) + ((onUpdate as SubscriptionCallback)._paths ??= []).push(path); subscriptions.add(onUpdate); } From 390987e4a9d58c4c0eb9d4f6b4101ecf1203ccba Mon Sep 17 00:00:00 2001 From: V Date: Mon, 9 Oct 2023 03:09:56 +0200 Subject: [PATCH 0018/1143] Remove ReviewDB abuse / harassment has gotten pretty bad. i proposed the ability to allow users to delete comments from their own profile, but there seems to be no interest among the reviewdb owners. i don't want vencord to harbour harassment, so i'm removing the plugin for now --- src/plugins/reviewDB/auth.tsx | 78 ------- .../reviewDB/components/MessageButton.tsx | 61 ------ .../reviewDB/components/ReviewBadge.tsx | 46 ---- .../reviewDB/components/ReviewComponent.tsx | 147 ------------- .../reviewDB/components/ReviewModal.tsx | 105 --------- .../reviewDB/components/ReviewsView.tsx | 200 ------------------ src/plugins/reviewDB/entities.ts | 96 --------- src/plugins/reviewDB/index.tsx | 157 -------------- src/plugins/reviewDB/reviewDbApi.ts | 153 -------------- src/plugins/reviewDB/settings.tsx | 85 -------- src/plugins/reviewDB/style.css | 76 ------- src/plugins/reviewDB/utils.tsx | 34 --- 12 files changed, 1238 deletions(-) delete mode 100644 src/plugins/reviewDB/auth.tsx delete mode 100644 src/plugins/reviewDB/components/MessageButton.tsx delete mode 100644 src/plugins/reviewDB/components/ReviewBadge.tsx delete mode 100644 src/plugins/reviewDB/components/ReviewComponent.tsx delete mode 100644 src/plugins/reviewDB/components/ReviewModal.tsx delete mode 100644 src/plugins/reviewDB/components/ReviewsView.tsx delete mode 100644 src/plugins/reviewDB/entities.ts delete mode 100644 src/plugins/reviewDB/index.tsx delete mode 100644 src/plugins/reviewDB/reviewDbApi.ts delete mode 100644 src/plugins/reviewDB/settings.tsx delete mode 100644 src/plugins/reviewDB/style.css delete mode 100644 src/plugins/reviewDB/utils.tsx diff --git a/src/plugins/reviewDB/auth.tsx b/src/plugins/reviewDB/auth.tsx deleted file mode 100644 index 1d95e47d..00000000 --- a/src/plugins/reviewDB/auth.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2023 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { DataStore } from "@api/index"; -import { Logger } from "@utils/Logger"; -import { openModal } from "@utils/modal"; -import { findByPropsLazy } from "@webpack"; -import { showToast, Toasts, UserStore } from "@webpack/common"; - -import { ReviewDBAuth } from "./entities"; - -const DATA_STORE_KEY = "rdb-auth"; - -const OAuth = findByPropsLazy("OAuth2AuthorizeModal"); - -export let Auth: ReviewDBAuth = {}; - -export async function initAuth() { - Auth = await getAuth() ?? {}; -} - -export async function getAuth(): Promise { - const auth = await DataStore.get(DATA_STORE_KEY); - return auth?.[UserStore.getCurrentUser()?.id]; -} - -export async function getToken() { - const auth = await getAuth(); - return auth?.token; -} - -export async function updateAuth(newAuth: ReviewDBAuth) { - return DataStore.update(DATA_STORE_KEY, auth => { - auth ??= {}; - Auth = auth[UserStore.getCurrentUser().id] ??= {}; - - if (newAuth.token) Auth.token = newAuth.token; - if (newAuth.user) Auth.user = newAuth.user; - - return auth; - }); -} - -export function authorize(callback?: any) { - openModal(props => - { - try { - const url = new URL(response.location); - url.searchParams.append("clientMod", "vencord"); - const res = await fetch(url, { - headers: new Headers({ Accept: "application/json" }) - }); - const { token, success } = await res.json(); - if (success) { - updateAuth({ token }); - showToast("Successfully logged in!"); - callback?.(); - } else if (res.status === 1) { - showToast("An Error occurred while logging in.", Toasts.Type.FAILURE); - } - } catch (e) { - new Logger("ReviewDB").error("Failed to authorize", e); - } - }} - /> - ); -} diff --git a/src/plugins/reviewDB/components/MessageButton.tsx b/src/plugins/reviewDB/components/MessageButton.tsx deleted file mode 100644 index 965fd1c1..00000000 --- a/src/plugins/reviewDB/components/MessageButton.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 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 { DeleteIcon } from "@components/Icons"; -import { classes } from "@utils/misc"; -import { findByPropsLazy } from "@webpack"; -import { Tooltip } from "@webpack/common"; - -const iconClasses = findByPropsLazy("button", "wrapper", "disabled", "separator"); - -export function DeleteButton({ onClick }: { onClick(): void; }) { - return ( - - {props => ( -
- -
- )} -
- ); -} - -export function ReportButton({ onClick }: { onClick(): void; }) { - return ( - - {props => ( -
- - - -
- )} -
- ); -} diff --git a/src/plugins/reviewDB/components/ReviewBadge.tsx b/src/plugins/reviewDB/components/ReviewBadge.tsx deleted file mode 100644 index 3c02c354..00000000 --- a/src/plugins/reviewDB/components/ReviewBadge.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 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 { MaskedLinkStore, Tooltip } from "@webpack/common"; - -import { Badge } from "../entities"; -import { cl } from "../utils"; - -export default function ReviewBadge(badge: Badge) { - return ( - - {({ onMouseEnter, onMouseLeave }) => ( - {badge.description} - MaskedLinkStore.openUntrustedLink({ - href: badge.redirectURL, - }) - } - /> - )} - - ); -} diff --git a/src/plugins/reviewDB/components/ReviewComponent.tsx b/src/plugins/reviewDB/components/ReviewComponent.tsx deleted file mode 100644 index 18659179..00000000 --- a/src/plugins/reviewDB/components/ReviewComponent.tsx +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 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 { openUserProfile } from "@utils/discord"; -import { classes } from "@utils/misc"; -import { LazyComponent } from "@utils/react"; -import { filters, findBulk } from "@webpack"; -import { Alerts, moment, Parser, showToast, Timestamp } from "@webpack/common"; - -import { Review, ReviewType } from "../entities"; -import { deleteReview, reportReview } from "../reviewDbApi"; -import { settings } from "../settings"; -import { canDeleteReview, cl } from "../utils"; -import { DeleteButton, ReportButton } from "./MessageButton"; -import ReviewBadge from "./ReviewBadge"; - -export default LazyComponent(() => { - // this is terrible, blame mantika - const p = filters.byProps; - const [ - { cozyMessage, buttons, message, buttonsInner, groupStart }, - { container, isHeader }, - { avatar, clickable, username, wrapper, cozy }, - buttonClasses, - botTag - ] = findBulk( - p("cozyMessage"), - p("container", "isHeader"), - p("avatar", "zalgo"), - p("button", "wrapper", "selected"), - p("botTag", "botTagRegular") - ); - - const dateFormat = new Intl.DateTimeFormat(); - - return function ReviewComponent({ review, refetch, profileId }: { review: Review; refetch(): void; profileId: string; }) { - function openModal() { - openUserProfile(review.sender.discordID); - } - - function delReview() { - Alerts.show({ - title: "Are you sure?", - body: "Do you really want to delete this review?", - confirmText: "Delete", - cancelText: "Nevermind", - onConfirm: () => { - deleteReview(review.id).then(res => { - if (res.success) { - refetch(); - } - showToast(res.message); - }); - } - }); - } - - function reportRev() { - Alerts.show({ - title: "Are you sure?", - body: "Do you really you want to report this review?", - confirmText: "Report", - cancelText: "Nevermind", - // confirmColor: "red", this just adds a class name and breaks the submit button guh - onConfirm: () => reportReview(review.id) - }); - } - - return ( -
- - -
- openModal()} - > - {review.sender.username} - - - {review.type === ReviewType.System && ( - - - System - - - )} -
- {review.sender.badges.map(badge => )} - - { - !settings.store.hideTimestamps && review.type !== ReviewType.System && ( - - {dateFormat.format(review.timestamp * 1000)} - ) - } - -
- {Parser.parseGuildEventDescription(review.comment)} -
- - {review.id !== 0 && ( -
-
- - - {canDeleteReview(profileId, review) && ( - - )} -
-
- )} -
- ); - }; -}); diff --git a/src/plugins/reviewDB/components/ReviewModal.tsx b/src/plugins/reviewDB/components/ReviewModal.tsx deleted file mode 100644 index 9669a2b3..00000000 --- a/src/plugins/reviewDB/components/ReviewModal.tsx +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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 ErrorBoundary from "@components/ErrorBoundary"; -import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; -import { useForceUpdater } from "@utils/react"; -import { Paginator, Text, useRef, useState } from "@webpack/common"; - -import { Auth } from "../auth"; -import { Response, REVIEWS_PER_PAGE } from "../reviewDbApi"; -import { cl } from "../utils"; -import ReviewComponent from "./ReviewComponent"; -import ReviewsView, { ReviewsInputComponent } from "./ReviewsView"; - -function Modal({ modalProps, discordId, name }: { modalProps: any; discordId: string; name: string; }) { - const [data, setData] = useState(); - const [signal, refetch] = useForceUpdater(true); - const [page, setPage] = useState(1); - - const ref = useRef(null); - - const reviewCount = data?.reviewCount; - const ownReview = data?.reviews.find(r => r.sender.discordID === Auth.user?.discordID); - - return ( - - - - - {name}'s Reviews - {!!reviewCount && ({reviewCount} Reviews)} - - - - - -
- ref.current?.scrollTo({ top: 0, behavior: "smooth" })} - hideOwnReview - /> -
-
- - -
- {ownReview && ( - - )} - - - {!!reviewCount && ( - - )} -
-
-
-
- ); -} - -export function openReviewsModal(discordId: string, name: string) { - openModal(props => ( - - )); -} diff --git a/src/plugins/reviewDB/components/ReviewsView.tsx b/src/plugins/reviewDB/components/ReviewsView.tsx deleted file mode 100644 index a87598be..00000000 --- a/src/plugins/reviewDB/components/ReviewsView.tsx +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 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 { LazyComponent, useAwaiter, useForceUpdater } from "@utils/react"; -import { find, findByPropsLazy } from "@webpack"; -import { Forms, React, RelationshipStore, showToast, useRef, UserStore } from "@webpack/common"; - -import { Auth, authorize } from "../auth"; -import { Review } from "../entities"; -import { addReview, getReviews, Response, REVIEWS_PER_PAGE } from "../reviewDbApi"; -import { settings } from "../settings"; -import { cl } from "../utils"; -import ReviewComponent from "./ReviewComponent"; - - -const Editor = findByPropsLazy("start", "end", "addMark"); -const Transform = findByPropsLazy("unwrapNodes"); -const InputTypes = findByPropsLazy("VOICE_CHANNEL_STATUS", "SIDEBAR"); - -const InputComponent = LazyComponent(() => find(m => m?.type?.render?.toString().includes("CHANNEL_TEXT_AREA).AnalyticsLocationProvider"))); - -interface UserProps { - discordId: string; - name: string; -} - -interface Props extends UserProps { - onFetchReviews(data: Response): void; - refetchSignal?: unknown; - showInput?: boolean; - page?: number; - scrollToTop?(): void; - hideOwnReview?: boolean; -} - -export default function ReviewsView({ - discordId, - name, - onFetchReviews, - refetchSignal, - scrollToTop, - page = 1, - showInput = false, - hideOwnReview = false, -}: Props) { - const [signal, refetch] = useForceUpdater(true); - - const [reviewData] = useAwaiter(() => getReviews(discordId, (page - 1) * REVIEWS_PER_PAGE), { - fallbackValue: null, - deps: [refetchSignal, signal, page], - onSuccess: data => { - if (settings.store.hideBlockedUsers) - data!.reviews = data!.reviews?.filter(r => !RelationshipStore.isBlocked(r.sender.discordID)); - - scrollToTop?.(); - onFetchReviews(data!); - } - }); - - if (!reviewData) return null; - - return ( - <> - - - {showInput && ( - r.sender.discordID === UserStore.getCurrentUser().id)} - /> - )} - - ); -} - -function ReviewList({ refetch, reviews, hideOwnReview, profileId }: { refetch(): void; reviews: Review[]; hideOwnReview: boolean; profileId: string; }) { - const myId = UserStore.getCurrentUser().id; - - return ( -
- {reviews?.map(review => - (review.sender.discordID !== myId || !hideOwnReview) && - - )} - - {reviews?.length === 0 && ( - - Looks like nobody reviewed this user yet. You could be the first! - - )} -
- ); -} - - -export function ReviewsInputComponent({ discordId, isAuthor, refetch, name }: { discordId: string, name: string; isAuthor: boolean; refetch(): void; }) { - const { token } = Auth; - const editorRef = useRef(null); - const inputType = InputTypes.FORM; - inputType.disableAutoFocus = true; - - const channel = { - flags_: 256, - guild_id_: null, - id: "0", - getGuildId: () => null, - isPrivate: () => true, - isActiveThread: () => false, - isArchivedLockedThread: () => false, - isDM: () => true, - roles: { "0": { permissions: 0n } }, - getRecipientId: () => "0", - hasFlag: () => false, - }; - - return ( - <> -
{ - if (!token) { - showToast("Opening authorization window..."); - authorize(); - } - }}> - editorRef.current = ref} - textValue="" - onSubmit={ - async res => { - const response = await addReview({ - userid: discordId, - comment: res.value, - }); - - if (response?.success) { - refetch(); - - const slateEditor = editorRef.current.ref.current.getSlateEditor(); - - // clear editor - Transform.delete(slateEditor, { - at: { - anchor: Editor.start(slateEditor, []), - focus: Editor.end(slateEditor, []), - } - }); - } else if (response?.message) { - showToast(response.message); - } - - // even tho we need to return this, it doesnt do anything - return { - shouldClear: false, - shouldRefocus: true, - }; - } - } - /> -
- - - ); -} diff --git a/src/plugins/reviewDB/entities.ts b/src/plugins/reviewDB/entities.ts deleted file mode 100644 index 0a77fef0..00000000 --- a/src/plugins/reviewDB/entities.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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 . -*/ - -export const enum UserType { - Banned = -1, - Normal = 0, - Admin = 1 -} - -export const enum ReviewType { - User = 0, - Server = 1, - Support = 2, - System = 3 -} - -export const enum NotificationType { - Info = 0, - Ban = 1, - Unban = 2, - Warning = 3 -} - -export interface ReviewDBAuth { - token?: string; - user?: ReviewDBUser; -} - -export interface Badge { - name: string; - description: string; - icon: string; - redirectURL: string; - type: number; -} - -export interface BanInfo { - id: string; - discordID: string; - reviewID: number; - reviewContent: string; - banEndDate: number; -} - -export interface Notification { - id: number; - title: string; - content: string; - type: NotificationType; -} - -export interface ReviewDBUser { - ID: number; - discordID: string; - username: string; - profilePhoto: string; - clientMod: string; - warningCount: number; - badges: any[]; - banInfo: BanInfo | null; - notification: Notification | null; - lastReviewID: number; - type: UserType; -} - -export interface ReviewAuthor { - id: number, - discordID: string, - username: string, - profilePhoto: string, - badges: Badge[]; -} - -export interface Review { - comment: string, - id: number, - star: number, - sender: ReviewAuthor, - timestamp: number; - type?: ReviewType; -} diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx deleted file mode 100644 index b9350ba4..00000000 --- a/src/plugins/reviewDB/index.tsx +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 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 "./style.css"; - -import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; -import ErrorBoundary from "@components/ErrorBoundary"; -import ExpandableHeader from "@components/ExpandableHeader"; -import { OpenExternalIcon } from "@components/Icons"; -import { Devs } from "@utils/constants"; -import { Logger } from "@utils/Logger"; -import definePlugin from "@utils/types"; -import { Alerts, Menu, Parser, showToast, useState } from "@webpack/common"; -import { Guild, User } from "discord-types/general"; - -import { Auth, initAuth, updateAuth } from "./auth"; -import { openReviewsModal } from "./components/ReviewModal"; -import ReviewsView from "./components/ReviewsView"; -import { NotificationType } from "./entities"; -import { getCurrentUserInfo, readNotification } from "./reviewDbApi"; -import { settings } from "./settings"; - -const guildPopoutPatch: NavContextMenuPatchCallback = (children, props: { guild: Guild, onClose(): void; }) => () => { - children.push( - openReviewsModal(props.guild.id, props.guild.name)} - /> - ); -}; - -export default definePlugin({ - name: "ReviewDB", - description: "Review other users (Adds a new settings to profiles)", - authors: [Devs.mantikafasi, Devs.Ven], - - settings, - - patches: [ - { - find: "disableBorderColor:!0", - replacement: { - match: /\(.{0,10}\{user:(.),setNote:.,canDM:.,.+?\}\)/, - replace: "$&,$self.getReviewsComponent($1)" - } - } - ], - - flux: { - CONNECTION_OPEN: initAuth, - }, - - async start() { - addContextMenuPatch("guild-header-popout", guildPopoutPatch); - - const s = settings.store; - const { lastReviewId, notifyReviews } = s; - - const legacy = s as any as { token?: string; }; - if (legacy.token) { - await updateAuth({ token: legacy.token }); - legacy.token = undefined; - new Logger("ReviewDB").info("Migrated legacy settings"); - } - - await initAuth(); - - setTimeout(async () => { - if (!Auth.token) return; - - const user = await getCurrentUserInfo(Auth.token); - updateAuth({ user }); - - if (notifyReviews) { - if (lastReviewId && lastReviewId < user.lastReviewID) { - s.lastReviewId = user.lastReviewID; - if (user.lastReviewID !== 0) - showToast("You have new reviews on your profile!"); - } - } - - if (user.notification) { - const props = user.notification.type === NotificationType.Ban ? { - cancelText: "Appeal", - confirmText: "Ok", - onCancel: async () => - VencordNative.native.openExternal( - "https://reviewdb.mantikafasi.dev/api/redirect?" - + new URLSearchParams({ - token: Auth.token!, - page: "dashboard/appeal" - }) - ) - } : {}; - - Alerts.show({ - title: user.notification.title, - body: ( - Parser.parse( - user.notification.content, - false - ) - ), - ...props - }); - - readNotification(user.notification.id); - } - }, 4000); - }, - - stop() { - removeContextMenuPatch("guild-header-popout", guildPopoutPatch); - }, - - getReviewsComponent: ErrorBoundary.wrap((user: User) => { - const [reviewCount, setReviewCount] = useState(); - - return ( - openReviewsModal(user.id, user.username)} - moreTooltipText={ - reviewCount && reviewCount > 50 - ? `View all ${reviewCount} reviews` - : "Open Review Modal" - } - onDropDownClick={state => settings.store.reviewsDropdownState = !state} - defaultState={settings.store.reviewsDropdownState} - > - setReviewCount(r.reviewCount)} - showInput - /> - - ); - }, { message: "Failed to render Reviews" }) -}); diff --git a/src/plugins/reviewDB/reviewDbApi.ts b/src/plugins/reviewDB/reviewDbApi.ts deleted file mode 100644 index add16dd1..00000000 --- a/src/plugins/reviewDB/reviewDbApi.ts +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 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 { showToast, Toasts } from "@webpack/common"; - -import { authorize, getToken } from "./auth"; -import { Review, ReviewDBUser } from "./entities"; -import { settings } from "./settings"; - -const API_URL = "https://manti.vendicated.dev"; - -export const REVIEWS_PER_PAGE = 50; - -export interface Response { - success: boolean, - message: string; - reviews: Review[]; - updated: boolean; - hasNextPage: boolean; - reviewCount: number; -} - -const WarningFlag = 0b00000010; - -export async function getReviews(id: string, offset = 0): Promise { - let flags = 0; - if (!settings.store.showWarning) flags |= WarningFlag; - - const params = new URLSearchParams({ - flags: String(flags), - offset: String(offset) - }); - const req = await fetch(`${API_URL}/api/reviewdb/users/${id}/reviews?${params}`); - - const res = (req.status === 200) - ? await req.json() as Response - : { - success: false, - message: "An Error occured while fetching reviews. Please try again later.", - reviews: [], - updated: false, - hasNextPage: false, - reviewCount: 0 - }; - - if (!res.success) { - showToast(res.message, Toasts.Type.FAILURE); - return { - ...res, - reviews: [ - { - id: 0, - comment: "An Error occured while fetching reviews. Please try again later.", - star: 0, - timestamp: 0, - sender: { - id: 0, - username: "Error", - profilePhoto: "https://cdn.discordapp.com/attachments/1045394533384462377/1084900598035513447/646808599204593683.png?size=128", - discordID: "0", - badges: [] - } - } - ] - }; - } - - return res; -} - -export async function addReview(review: any): Promise { - review.token = await getToken(); - - if (!review.token) { - showToast("Please authorize to add a review."); - authorize(); - return null; - } - - return fetch(API_URL + `/api/reviewdb/users/${review.userid}/reviews`, { - method: "PUT", - body: JSON.stringify(review), - headers: { - "Content-Type": "application/json", - } - }) - .then(r => r.json()) - .then(res => { - showToast(res.message); - return res ?? null; - }); -} - -export async function deleteReview(id: number): Promise { - return fetch(API_URL + `/api/reviewdb/users/${id}/reviews`, { - method: "DELETE", - headers: new Headers({ - "Content-Type": "application/json", - Accept: "application/json", - }), - body: JSON.stringify({ - token: await getToken(), - reviewid: id - }) - }).then(r => r.json()); -} - -export async function reportReview(id: number) { - const res = await fetch(API_URL + "/api/reviewdb/reports", { - method: "PUT", - headers: new Headers({ - "Content-Type": "application/json", - Accept: "application/json", - }), - body: JSON.stringify({ - reviewid: id, - token: await getToken() - }) - }).then(r => r.json()) as Response; - - showToast(res.message); -} - -export function getCurrentUserInfo(token: string): Promise { - return fetch(API_URL + "/api/reviewdb/users", { - body: JSON.stringify({ token }), - method: "POST", - }).then(r => r.json()); -} - -export async function readNotification(id: number) { - return fetch(API_URL + `/api/reviewdb/notifications?id=${id}`, { - method: "PATCH", - headers: { - "Authorization": await getToken() || "", - }, - }); -} diff --git a/src/plugins/reviewDB/settings.tsx b/src/plugins/reviewDB/settings.tsx deleted file mode 100644 index cf61a380..00000000 --- a/src/plugins/reviewDB/settings.tsx +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 { definePluginSettings } from "@api/Settings"; -import { OptionType } from "@utils/types"; -import { Button } from "@webpack/common"; - -import { authorize, getToken } from "./auth"; - -export const settings = definePluginSettings({ - authorize: { - type: OptionType.COMPONENT, - description: "Authorize with ReviewDB", - component: () => ( - - ) - }, - notifyReviews: { - type: OptionType.BOOLEAN, - description: "Notify about new reviews on startup", - default: true, - }, - showWarning: { - type: OptionType.BOOLEAN, - description: "Display warning to be respectful at the top of the reviews list", - default: true, - }, - hideTimestamps: { - type: OptionType.BOOLEAN, - description: "Hide timestamps on reviews", - default: false, - }, - hideBlockedUsers: { - type: OptionType.BOOLEAN, - description: "Hide reviews from blocked users", - default: true, - }, - website: { - type: OptionType.COMPONENT, - description: "ReviewDB website", - component: () => ( - - ) - }, - supportServer: { - type: OptionType.COMPONENT, - description: "ReviewDB Support Server", - component: () => ( - - ) - } -}).withPrivateSettings<{ - lastReviewId?: number; - reviewsDropdownState?: boolean; -}>(); diff --git a/src/plugins/reviewDB/style.css b/src/plugins/reviewDB/style.css deleted file mode 100644 index f4d890fd..00000000 --- a/src/plugins/reviewDB/style.css +++ /dev/null @@ -1,76 +0,0 @@ -[class|="section"]:not([class|="lastSection"]) + .vc-rdb-view { - margin-top: 12px; -} - -.vc-rdb-badge { - vertical-align: middle; - margin-left: 4px; -} - -.vc-rdb-input { - margin-top: 6px; - margin-bottom: 12px; - resize: none; - overflow: hidden; - background: transparent; - border: 1px solid var(--profile-message-input-border-color); -} - -.vc-rdb-modal-footer > div { - width: 100%; - margin: 6px 16px; -} - -/* When input becomes disabled(while sending review), input adds unneccesary padding to left, this prevents it */ -.vc-rdb-input > div > div { - padding-left: 0 !important; -} - -.vc-rdb-placeholder { - margin-bottom: 4px; - font-weight: bold; - font-style: italic; - color: var(--text-muted); -} - -.vc-rdb-input * { - font-size: 14px; -} - -.vc-rdb-modal-footer { - padding: 0; -} - -.vc-rdb-modal-footer .vc-rdb-input { - margin-bottom: 0; - background: var(--input-background); -} - -.vc-rdb-modal-footer [class|="pageControlContainer"] { - margin-top: 0; -} - -.vc-rdb-modal-header { - flex-grow: 1; -} - -.vc-rdb-modal-reviews { - margin-top: 16px; -} - -.vc-rdb-review { - margin-top: 8px; - margin-bottom: 8px; -} - -.vc-rdb-review-comment img { - vertical-align: text-top; -} - -.vc-rdb-review-comment { - overflow-y: hidden; - margin-top: 1px; - margin-bottom: 8px; - color: var(--text-normal); - font-size: 15px; -} diff --git a/src/plugins/reviewDB/utils.tsx b/src/plugins/reviewDB/utils.tsx deleted file mode 100644 index ab66d531..00000000 --- a/src/plugins/reviewDB/utils.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 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 { classNameFactory } from "@api/Styles"; -import { UserStore } from "@webpack/common"; - -import { Auth } from "./auth"; -import { Review, UserType } from "./entities"; - -export const cl = classNameFactory("vc-rdb-"); - -export function canDeleteReview(profileId: string, review: Review) { - const myId = UserStore.getCurrentUser().id; - return ( - myId === profileId - || review.sender.discordID === profileId - || Auth.user?.type === UserType.Admin - ); -} From d81302f64c648f9eba0608ca0c9c6801a853f2b1 Mon Sep 17 00:00:00 2001 From: V Date: Mon, 9 Oct 2023 03:15:43 +0200 Subject: [PATCH 0019/1143] Revert mozilla store compliance changes This reverts commit 97b6699afefe373d510dda5589a0754a4b380153. Vencord is dropping support for the firefox extension, so these changes are now obsolete. revert so users can still install the extension manually and enjoy the full experience --- browser/background.js | 32 +++++++++++++++++++ browser/manifestv2.json | 6 +++- scripts/build/buildWeb.mjs | 6 ++-- src/components/VencordSettings/ThemesTab.tsx | 25 ++++----------- src/components/VencordSettings/VencordTab.tsx | 15 ++++----- src/plugins/_core/supportHelper.tsx | 19 +---------- src/utils/constants.ts | 2 -- 7 files changed, 53 insertions(+), 52 deletions(-) create mode 100644 browser/background.js diff --git a/browser/background.js b/browser/background.js new file mode 100644 index 00000000..1f2d5ec1 --- /dev/null +++ b/browser/background.js @@ -0,0 +1,32 @@ +/** + * @template T + * @param {T[]} arr + * @param {(v: T) => boolean} predicate + */ +function removeFirst(arr, predicate) { + const idx = arr.findIndex(predicate); + if (idx !== -1) arr.splice(idx, 1); +} + +chrome.webRequest.onHeadersReceived.addListener( + ({ responseHeaders, type, url }) => { + if (!responseHeaders) return; + + if (type === "main_frame") { + // In main frame requests, the CSP needs to be removed to enable fetching of custom css + // as desired by the user + removeFirst(responseHeaders, h => h.name.toLowerCase() === "content-security-policy"); + } else if (type === "stylesheet" && url.startsWith("https://raw.githubusercontent.com/")) { + // Most users will load css from GitHub, but GitHub doesn't set the correct content type, + // so we fix it here + removeFirst(responseHeaders, h => h.name.toLowerCase() === "content-type"); + responseHeaders.push({ + name: "Content-Type", + value: "text/css" + }); + } + return { responseHeaders }; + }, + { urls: ["https://raw.githubusercontent.com/*", "*://*.discord.com/*"], types: ["main_frame", "stylesheet"] }, + ["blocking", "responseHeaders"] +); diff --git a/browser/manifestv2.json b/browser/manifestv2.json index a6feada7..3cac9450 100644 --- a/browser/manifestv2.json +++ b/browser/manifestv2.json @@ -26,7 +26,11 @@ } ], - "web_accessible_resources": ["dist/*", "third-party/*"], + "background": { + "scripts": ["background.js"] + }, + + "web_accessible_resources": ["dist/Vencord.js", "dist/Vencord.css"], "browser_specific_settings": { "gecko": { diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs index e4eeb53e..02e4da0c 100644 --- a/scripts/build/buildWeb.mjs +++ b/scripts/build/buildWeb.mjs @@ -145,11 +145,11 @@ async function loadDir(dir, basePath = "") { /** * @type {(target: string, files: string[]) => Promise} */ -async function buildExtension(target, files, noMonaco = false) { +async function buildExtension(target, files) { const entries = { "dist/Vencord.js": await readFile("dist/extension.js"), "dist/Vencord.css": await readFile("dist/extension.css"), - ...(noMonaco ? {} : await loadDir("dist/monaco")), + ...await loadDir("dist/monaco"), ...Object.fromEntries(await Promise.all(RnNoiseFiles.map(async file => [`third-party/rnnoise/${file.replace(/^dist\//, "")}`, await readFile(`node_modules/@sapphi-red/web-noise-suppressor/${file}`)] ))), @@ -195,7 +195,7 @@ const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content await Promise.all([ appendCssRuntime, buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]), - buildExtension("firefox-unpacked", ["content.js", "manifestv2.json", "icon.png"], true), + buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]), ]); Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension.zip"); diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index 573f3b9f..f19cdcb8 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -18,11 +18,9 @@ import { useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; -import { ErrorCard } from "@components/ErrorCard"; import { Flex } from "@components/Flex"; import { DeleteIcon } from "@components/Icons"; import { Link } from "@components/Link"; -import { IsFirefox } from "@utils/constants"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { showItemInFolder } from "@utils/native"; @@ -251,14 +249,12 @@ function ThemesTab() { > Load missing Themes - {!IsFirefox && ( - - )} + @@ -320,15 +316,6 @@ function ThemesTab() { return ( - {IsFirefox && ( - - Warning - - You are using Firefox. Expect the vast majority of themes to not work. - If this is a problem, use a chromium browser or Discord Desktop / Vesktop. - - - )} )} - {!IsFirefox && ( - - )} + {!IS_WEB && ( diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx index 43c499ab..64ee52fc 100644 --- a/src/plugins/spotifyControls/PlayerComponent.tsx +++ b/src/plugins/spotifyControls/PlayerComponent.tsx @@ -38,19 +38,21 @@ function msToHuman(ms: number) { } function Svg(path: string, label: string) { - return () => ( - - - - ); + return function Icon() { + return ( + + + + ); + }; } // KraXen's icons :yesyes: diff --git a/src/plugins/startupTimings/StartupTimingPage.tsx b/src/plugins/startupTimings/StartupTimingPage.tsx index c8cf51da..0950b068 100644 --- a/src/plugins/startupTimings/StartupTimingPage.tsx +++ b/src/plugins/startupTimings/StartupTimingPage.tsx @@ -120,8 +120,8 @@ function ServerTrace({ trace }: ServerTraceProps) { - {lines.map(line => ( - {line} + {lines.map((line, i) => ( + {line} ))} diff --git a/src/plugins/typingTweaks/index.tsx b/src/plugins/typingTweaks/index.tsx index 62bcd41f..ccb863ce 100644 --- a/src/plugins/typingTweaks/index.tsx +++ b/src/plugins/typingTweaks/index.tsx @@ -21,7 +21,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { openUserProfile } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; -import { Avatar, GuildMemberStore, React, RelationshipStore } from "@webpack/common"; +import { Avatar, GuildMemberStore, RelationshipStore } from "@webpack/common"; import { User } from "discord-types/general"; const settings = definePluginSettings({ @@ -135,7 +135,7 @@ export default definePlugin({ return children.map(c => c.type === "strong" - ? + ? : c ); } diff --git a/src/utils/react.tsx b/src/utils/react.tsx index 0181c95b..43a4d121 100644 --- a/src/utils/react.tsx +++ b/src/utils/react.tsx @@ -133,7 +133,7 @@ export function useForceUpdater(withDep?: true) { export function LazyComponent(factory: () => React.ComponentType, attempts = 5) { const get = makeLazy(factory, attempts); - return (props: T) => { + return function Lazy(props: T) { const Component = get() ?? NoopComponent; return ; }; From 584885acf5a650afdff145b3f6bac971995d4a2c Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 31 Oct 2023 23:56:13 +0100 Subject: [PATCH 0182/1143] [skip ci] Revert "add react linting" doesnt work properly :( This reverts commit 18fdc33ee7d1f60d58645c2a98f402988b97e996. --- .eslintrc.json | 12 +- package.json | 1 - pnpm-lock.yaml | 768 ------------------ .../Notifications/NotificationComponent.tsx | 2 +- src/components/ErrorBoundary.tsx | 5 +- src/components/Heart.tsx | 2 +- src/components/Icons.tsx | 2 +- src/components/PluginSettings/index.tsx | 20 +- .../VencordSettings/PatchHelperTab.tsx | 4 +- src/components/VencordSettings/ThemesTab.tsx | 2 +- src/components/VencordSettings/UpdaterTab.tsx | 4 +- src/plugins/betterFolders/FolderSideBar.tsx | 2 +- src/plugins/callTimer/index.tsx | 1 + src/plugins/consoleShortcuts/index.ts | 1 - src/plugins/customRPC/index.tsx | 2 +- src/plugins/emoteCloner/index.tsx | 42 +- src/plugins/gameActivityToggle/index.tsx | 2 +- src/plugins/messageLinkEmbeds/index.tsx | 4 +- src/plugins/moreUserTags/index.tsx | 2 +- src/plugins/mutualGroupDMs/index.tsx | 1 - .../components/RolesAndUsersPermissions.tsx | 3 +- .../components/UserPermissions.tsx | 4 +- src/plugins/pictureInPicture/index.tsx | 2 +- src/plugins/platformIndicators/index.tsx | 32 +- .../readAllNotificationsButton/index.tsx | 2 +- src/plugins/sendTimestamps/index.tsx | 2 +- .../serverProfile/GuildProfileModal.tsx | 1 - .../components/Code.tsx | 6 +- .../components/Highlighter.tsx | 2 +- src/plugins/showConnections/index.tsx | 2 +- .../components/HiddenChannelLockScreen.tsx | 2 +- src/plugins/silentMessageToggle/index.tsx | 2 +- src/plugins/silentTyping/index.tsx | 4 +- .../spotifyControls/PlayerComponent.tsx | 28 +- .../startupTimings/StartupTimingPage.tsx | 4 +- src/plugins/typingTweaks/index.tsx | 4 +- src/utils/react.tsx | 2 +- 37 files changed, 97 insertions(+), 884 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 5d1b7c76..2ee24e8b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,7 +9,6 @@ "unused-imports", "path-alias" ], - "extends": ["plugin:react/recommended", "plugin:react/jsx-runtime"], "settings": { "import/resolver": { "alias": { @@ -21,9 +20,6 @@ ["@components", "./src/components"] ] } - }, - "react": { - "version": "18.2" } }, "rules": { @@ -97,12 +93,6 @@ "unused-imports/no-unused-imports": "error", - "path-alias/no-relative": "error", - - "react/no-unescaped-entities": "off", - "react/prop-types": "off", - - /* we dont target ancient browsers */ - "react/jsx-no-target-blank": "off" + "path-alias/no-relative": "error" } } diff --git a/package.json b/package.json index ec23a4cb..da8fc449 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "eslint": "^8.46.0", "eslint-import-resolver-alias": "^1.1.2", "eslint-plugin-path-alias": "^1.0.0", - "eslint-plugin-react": "^7.33.2", "eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-unused-imports": "^2.0.0", "highlight.js": "10.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2eb54556..be7befab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -87,9 +87,6 @@ devDependencies: eslint-plugin-path-alias: specifier: ^1.0.0 version: 1.0.0(patch_hash=m6sma4g6bh67km3q6igf6uxaja)(eslint@8.46.0) - eslint-plugin-react: - specifier: ^7.33.2 - version: 7.33.2(eslint@8.46.0) eslint-plugin-simple-import-sort: specifier: ^10.0.0 version: 10.0.0(eslint@8.46.0) @@ -858,24 +855,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /array-buffer-byte-length@1.0.0: - resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} - dependencies: - call-bind: 1.0.5 - is-array-buffer: 3.0.2 - dev: true - - /array-includes@3.1.7: - resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - get-intrinsic: 1.2.2 - is-string: 1.0.7 - dev: true - /array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} @@ -886,49 +865,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /array.prototype.flat@1.3.2: - resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - es-shim-unscopables: 1.0.2 - dev: true - - /array.prototype.flatmap@1.3.2: - resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - es-shim-unscopables: 1.0.2 - dev: true - - /array.prototype.tosorted@1.1.2: - resolution: {integrity: sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - es-shim-unscopables: 1.0.2 - get-intrinsic: 1.2.2 - dev: true - - /arraybuffer.prototype.slice@1.0.2: - resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==} - engines: {node: '>= 0.4'} - dependencies: - array-buffer-byte-length: 1.0.0 - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - get-intrinsic: 1.2.2 - is-array-buffer: 3.0.2 - is-shared-array-buffer: 1.0.2 - dev: true - /arrify@1.0.1: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} @@ -948,23 +884,12 @@ packages: resolution: {integrity: sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==} dev: true - /asynciterator.prototype@1.0.0: - resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==} - dependencies: - has-symbols: 1.0.3 - dev: true - /atob@2.1.2: resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} engines: {node: '>= 4.5.0'} hasBin: true dev: true - /available-typed-arrays@1.0.5: - resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} - engines: {node: '>= 0.4'} - dev: true - /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true @@ -1042,14 +967,6 @@ packages: unset-value: 1.0.0 dev: true - /call-bind@1.0.5: - resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} - dependencies: - function-bind: 1.1.2 - get-intrinsic: 1.2.2 - set-function-length: 1.1.1 - dev: true - /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -1259,24 +1176,6 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true - /define-data-property@1.1.1: - resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==} - engines: {node: '>= 0.4'} - dependencies: - get-intrinsic: 1.2.2 - gopd: 1.0.1 - has-property-descriptors: 1.0.1 - dev: true - - /define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} - dependencies: - define-data-property: 1.1.1 - has-property-descriptors: 1.0.1 - object-keys: 1.1.1 - dev: true - /define-property@0.2.5: resolution: {integrity: sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==} engines: {node: '>=0.10.0'} @@ -1322,13 +1221,6 @@ packages: moment: 2.29.4 dev: true - /doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} - dependencies: - esutils: 2.0.3 - dev: true - /doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} @@ -1352,94 +1244,6 @@ packages: is-arrayish: 0.2.1 dev: true - /es-abstract@1.22.3: - resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==} - engines: {node: '>= 0.4'} - dependencies: - array-buffer-byte-length: 1.0.0 - arraybuffer.prototype.slice: 1.0.2 - available-typed-arrays: 1.0.5 - call-bind: 1.0.5 - es-set-tostringtag: 2.0.2 - es-to-primitive: 1.2.1 - function.prototype.name: 1.1.6 - get-intrinsic: 1.2.2 - get-symbol-description: 1.0.0 - globalthis: 1.0.3 - gopd: 1.0.1 - has-property-descriptors: 1.0.1 - has-proto: 1.0.1 - has-symbols: 1.0.3 - hasown: 2.0.0 - internal-slot: 1.0.6 - is-array-buffer: 3.0.2 - is-callable: 1.2.7 - is-negative-zero: 2.0.2 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 - is-string: 1.0.7 - is-typed-array: 1.1.12 - is-weakref: 1.0.2 - object-inspect: 1.13.1 - object-keys: 1.1.1 - object.assign: 4.1.4 - regexp.prototype.flags: 1.5.1 - safe-array-concat: 1.0.1 - safe-regex-test: 1.0.0 - string.prototype.trim: 1.2.8 - string.prototype.trimend: 1.0.7 - string.prototype.trimstart: 1.0.7 - typed-array-buffer: 1.0.0 - typed-array-byte-length: 1.0.0 - typed-array-byte-offset: 1.0.0 - typed-array-length: 1.0.4 - unbox-primitive: 1.0.2 - which-typed-array: 1.1.13 - dev: true - - /es-iterator-helpers@1.0.15: - resolution: {integrity: sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==} - dependencies: - asynciterator.prototype: 1.0.0 - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - es-set-tostringtag: 2.0.2 - function-bind: 1.1.1 - get-intrinsic: 1.2.2 - globalthis: 1.0.3 - has-property-descriptors: 1.0.1 - has-proto: 1.0.1 - has-symbols: 1.0.3 - internal-slot: 1.0.6 - iterator.prototype: 1.1.2 - safe-array-concat: 1.0.1 - dev: true - - /es-set-tostringtag@2.0.2: - resolution: {integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==} - engines: {node: '>= 0.4'} - dependencies: - get-intrinsic: 1.2.2 - has-tostringtag: 1.0.0 - hasown: 2.0.0 - dev: true - - /es-shim-unscopables@1.0.2: - resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} - dependencies: - hasown: 2.0.0 - dev: true - - /es-to-primitive@1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} - engines: {node: '>= 0.4'} - dependencies: - is-callable: 1.2.7 - is-date-object: 1.0.5 - is-symbol: 1.0.4 - dev: true - /esbuild-android-64@0.15.18: resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==} engines: {node: '>=12'} @@ -1720,34 +1524,6 @@ packages: dev: true patched: true - /eslint-plugin-react@7.33.2(eslint@8.46.0): - resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - peerDependenciesMeta: - eslint: - optional: true - dependencies: - array-includes: 3.1.7 - array.prototype.flatmap: 1.3.2 - array.prototype.tosorted: 1.1.2 - doctrine: 2.1.0 - es-iterator-helpers: 1.0.15 - eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4) - estraverse: 5.3.0 - jsx-ast-utils: 3.3.5 - minimatch: 3.1.2 - object.entries: 1.1.7 - object.fromentries: 2.0.7 - object.hasown: 1.1.3 - object.values: 1.1.7 - prop-types: 15.8.1 - resolve: 2.0.0-next.5 - semver: 6.3.1 - string.prototype.matchall: 4.0.10 - dev: true - /eslint-plugin-simple-header@1.0.2: resolution: {integrity: sha512-K1EJ/ueBIjPRA8qR44Ymo+GDmPYYmfoODtainGxVr7PSbX6QiaY+pTuGCrOhO+AtVsYJs8GLSVdGUTXyAxAtOA==} dev: false @@ -2015,12 +1791,6 @@ packages: resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} dev: true - /for-each@0.3.3: - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} - dependencies: - is-callable: 1.2.7 - dev: true - /for-in@1.0.2: resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} engines: {node: '>=0.10.0'} @@ -2053,38 +1823,11 @@ packages: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} dev: true - /function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - dev: true - - /function.prototype.name@1.1.6: - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - functions-have-names: 1.2.3 - dev: true - - /functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - dev: true - /get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} dev: true - /get-intrinsic@1.2.2: - resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} - dependencies: - function-bind: 1.1.2 - has-proto: 1.0.1 - has-symbols: 1.0.3 - hasown: 2.0.0 - dev: true - /get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} @@ -2092,14 +1835,6 @@ packages: pump: 3.0.0 dev: true - /get-symbol-description@1.0.0: - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 - dev: true - /get-tsconfig@4.5.0: resolution: {integrity: sha512-MjhiaIWCJ1sAU4pIQ5i5OfOuHHxVo1oYeNsWTON7jxYkod8pHocXeh+SSbmu5OZZZK73B6cbJ2XADzXehLyovQ==} dev: true @@ -2157,13 +1892,6 @@ packages: type-fest: 0.20.2 dev: true - /globalthis@1.0.3: - resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} - engines: {node: '>= 0.4'} - dependencies: - define-properties: 1.2.1 - dev: true - /globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} @@ -2180,12 +1908,6 @@ packages: resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} dev: true - /gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} - dependencies: - get-intrinsic: 1.2.2 - dev: true - /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} dev: true @@ -2203,10 +1925,6 @@ packages: engines: {node: '>=6'} dev: true - /has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - dev: true - /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -2217,29 +1935,6 @@ packages: engines: {node: '>=8'} dev: true - /has-property-descriptors@1.0.1: - resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} - dependencies: - get-intrinsic: 1.2.2 - dev: true - - /has-proto@1.0.1: - resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} - engines: {node: '>= 0.4'} - dev: true - - /has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} - dev: true - - /has-tostringtag@1.0.0: - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} - engines: {node: '>= 0.4'} - dependencies: - has-symbols: 1.0.3 - dev: true - /has-value@0.3.1: resolution: {integrity: sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==} engines: {node: '>=0.10.0'} @@ -2278,13 +1973,6 @@ packages: function-bind: 1.1.1 dev: true - /hasown@2.0.0: - resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} - engines: {node: '>= 0.4'} - dependencies: - function-bind: 1.1.2 - dev: true - /highlight.js@10.6.0: resolution: {integrity: sha512-8mlRcn5vk/r4+QcqerapwBYTe+iPL5ih6xrNylxrnBdHQiijDETfXX7VIxC3UiCRiINBJfANBAsPzAvRQj8RpQ==} dev: true @@ -2362,15 +2050,6 @@ packages: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} dev: true - /internal-slot@1.0.6: - resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} - engines: {node: '>= 0.4'} - dependencies: - get-intrinsic: 1.2.2 - hasown: 2.0.0 - side-channel: 1.0.4 - dev: true - /is-accessor-descriptor@0.1.6: resolution: {integrity: sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==} engines: {node: '>=0.10.0'} @@ -2385,60 +2064,20 @@ packages: kind-of: 6.0.3 dev: true - /is-array-buffer@3.0.2: - resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} - dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 - is-typed-array: 1.1.12 - dev: true - /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} dev: true - /is-async-function@2.0.0: - resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.0 - dev: true - - /is-bigint@1.0.4: - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} - dependencies: - has-bigints: 1.0.2 - dev: true - - /is-boolean-object@1.1.2: - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - has-tostringtag: 1.0.0 - dev: true - /is-buffer@1.1.6: resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} dev: true - /is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - dev: true - /is-core-module@2.12.0: resolution: {integrity: sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==} dependencies: has: 1.0.3 dev: true - /is-core-module@2.13.1: - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} - dependencies: - hasown: 2.0.0 - dev: true - /is-data-descriptor@0.1.4: resolution: {integrity: sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==} engines: {node: '>=0.10.0'} @@ -2453,13 +2092,6 @@ packages: kind-of: 6.0.3 dev: true - /is-date-object@1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.0 - dev: true - /is-descriptor@0.1.6: resolution: {integrity: sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==} engines: {node: '>=0.10.0'} @@ -2495,24 +2127,11 @@ packages: engines: {node: '>=0.10.0'} dev: true - /is-finalizationregistry@1.0.2: - resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} - dependencies: - call-bind: 1.0.5 - dev: true - /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} dev: true - /is-generator-function@1.0.10: - resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.0 - dev: true - /is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -2520,22 +2139,6 @@ packages: is-extglob: 2.1.1 dev: true - /is-map@2.0.2: - resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} - dev: true - - /is-negative-zero@2.0.2: - resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} - engines: {node: '>= 0.4'} - dev: true - - /is-number-object@1.0.7: - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.0 - dev: true - /is-number@3.0.0: resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==} engines: {node: '>=0.10.0'} @@ -2570,62 +2173,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /is-regex@1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - has-tostringtag: 1.0.0 - dev: true - - /is-set@2.0.2: - resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} - dev: true - - /is-shared-array-buffer@1.0.2: - resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} - dependencies: - call-bind: 1.0.5 - dev: true - - /is-string@1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.0 - dev: true - - /is-symbol@1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} - engines: {node: '>= 0.4'} - dependencies: - has-symbols: 1.0.3 - dev: true - - /is-typed-array@1.1.12: - resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} - engines: {node: '>= 0.4'} - dependencies: - which-typed-array: 1.1.13 - dev: true - - /is-weakmap@2.0.1: - resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} - dev: true - - /is-weakref@1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} - dependencies: - call-bind: 1.0.5 - dev: true - - /is-weakset@2.0.2: - resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} - dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 - dev: true - /is-windows@1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} @@ -2635,10 +2182,6 @@ packages: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} dev: true - /isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - dev: true - /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true @@ -2655,16 +2198,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /iterator.prototype@1.1.2: - resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} - dependencies: - define-properties: 1.2.1 - get-intrinsic: 1.2.2 - has-symbols: 1.0.3 - reflect.getprototypeof: 1.0.4 - set-function-name: 2.0.1 - dev: true - /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true @@ -2696,16 +2229,6 @@ packages: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} dev: false - /jsx-ast-utils@3.3.5: - resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} - engines: {node: '>=4.0'} - dependencies: - array-includes: 3.1.7 - array.prototype.flat: 1.3.2 - object.assign: 4.1.4 - object.values: 1.1.7 - dev: true - /jszip@2.7.0: resolution: {integrity: sha512-JIsRKRVC3gTRo2vM4Wy9WBC3TRcfnIZU8k65Phi3izkvPH975FowRYtKGT6PxevA0XnJ/yO8b0QwV0ydVyQwfw==} dependencies: @@ -2774,13 +2297,6 @@ packages: resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} dev: true - /loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - dependencies: - js-tokens: 4.0.0 - dev: true - /lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -2976,11 +2492,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - dev: true - /object-copy@0.1.0: resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==} engines: {node: '>=0.10.0'} @@ -2990,15 +2501,6 @@ packages: kind-of: 3.2.2 dev: true - /object-inspect@1.13.1: - resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} - dev: true - - /object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - dev: true - /object-visit@1.0.1: resolution: {integrity: sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==} engines: {node: '>=0.10.0'} @@ -3006,41 +2508,6 @@ packages: isobject: 3.0.1 dev: true - /object.assign@4.1.4: - resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - has-symbols: 1.0.3 - object-keys: 1.1.1 - dev: true - - /object.entries@1.1.7: - resolution: {integrity: sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - dev: true - - /object.fromentries@2.0.7: - resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - dev: true - - /object.hasown@1.1.3: - resolution: {integrity: sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==} - dependencies: - define-properties: 1.2.1 - es-abstract: 1.22.3 - dev: true - /object.pick@1.3.0: resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==} engines: {node: '>=0.10.0'} @@ -3048,15 +2515,6 @@ packages: isobject: 3.0.1 dev: true - /object.values@1.1.7: - resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - dev: true - /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: @@ -3219,14 +2677,6 @@ packages: engines: {node: '>=0.4.0'} dev: true - /prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - dev: true - /proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} dev: true @@ -3285,10 +2735,6 @@ packages: engines: {node: '>=8'} dev: true - /react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - dev: true - /read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -3325,18 +2771,6 @@ packages: strip-indent: 3.0.0 dev: true - /reflect.getprototypeof@1.0.4: - resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - get-intrinsic: 1.2.2 - globalthis: 1.0.3 - which-builtin-type: 1.1.3 - dev: true - /regex-not@1.0.2: resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==} engines: {node: '>=0.10.0'} @@ -3345,15 +2779,6 @@ packages: safe-regex: 1.1.0 dev: true - /regexp.prototype.flags@1.5.1: - resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - set-function-name: 2.0.1 - dev: true - /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -3388,15 +2813,6 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: true - /resolve@2.0.0-next.5: - resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} - hasBin: true - dependencies: - is-core-module: 2.13.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - dev: true - /ret@0.1.15: resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} engines: {node: '>=0.12'} @@ -3420,28 +2836,10 @@ packages: queue-microtask: 1.2.3 dev: true - /safe-array-concat@1.0.1: - resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==} - engines: {node: '>=0.4'} - dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 - has-symbols: 1.0.3 - isarray: 2.0.5 - dev: true - /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: true - /safe-regex-test@1.0.0: - resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} - dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 - is-regex: 1.1.4 - dev: true - /safe-regex@1.1.0: resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==} dependencies: @@ -3453,11 +2851,6 @@ packages: hasBin: true dev: true - /semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - dev: true - /semver@7.5.0: resolution: {integrity: sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==} engines: {node: '>=10'} @@ -3466,25 +2859,6 @@ packages: lru-cache: 6.0.0 dev: true - /set-function-length@1.1.1: - resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==} - engines: {node: '>= 0.4'} - dependencies: - define-data-property: 1.1.1 - get-intrinsic: 1.2.2 - gopd: 1.0.1 - has-property-descriptors: 1.0.1 - dev: true - - /set-function-name@2.0.1: - resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} - engines: {node: '>= 0.4'} - dependencies: - define-data-property: 1.1.1 - functions-have-names: 1.2.3 - has-property-descriptors: 1.0.1 - dev: true - /set-value@2.0.1: resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} engines: {node: '>=0.10.0'} @@ -3507,14 +2881,6 @@ packages: engines: {node: '>=8'} dev: true - /side-channel@1.0.4: - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} - dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 - object-inspect: 1.13.1 - dev: true - /signal-exit@4.0.1: resolution: {integrity: sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==} engines: {node: '>=14'} @@ -3640,45 +3006,6 @@ packages: strip-ansi: 6.0.1 dev: true - /string.prototype.matchall@4.0.10: - resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - get-intrinsic: 1.2.2 - has-symbols: 1.0.3 - internal-slot: 1.0.6 - regexp.prototype.flags: 1.5.1 - set-function-name: 2.0.1 - side-channel: 1.0.4 - dev: true - - /string.prototype.trim@1.2.8: - resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - dev: true - - /string.prototype.trimend@1.0.7: - resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - dev: true - - /string.prototype.trimstart@1.0.7: - resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.1 - es-abstract: 1.22.3 - dev: true - /string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: @@ -3936,59 +3263,12 @@ packages: engines: {node: '>=14.16'} dev: true - /typed-array-buffer@1.0.0: - resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 - is-typed-array: 1.1.12 - dev: true - - /typed-array-byte-length@1.0.0: - resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.5 - for-each: 0.3.3 - has-proto: 1.0.1 - is-typed-array: 1.1.12 - dev: true - - /typed-array-byte-offset@1.0.0: - resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} - engines: {node: '>= 0.4'} - dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.5 - for-each: 0.3.3 - has-proto: 1.0.1 - is-typed-array: 1.1.12 - dev: true - - /typed-array-length@1.0.4: - resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} - dependencies: - call-bind: 1.0.5 - for-each: 0.3.3 - is-typed-array: 1.1.12 - dev: true - /typescript@5.0.4: resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} engines: {node: '>=12.20'} hasBin: true dev: true - /unbox-primitive@1.0.2: - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} - dependencies: - call-bind: 1.0.5 - has-bigints: 1.0.2 - has-symbols: 1.0.3 - which-boxed-primitive: 1.0.2 - dev: true - /unbzip2-stream@1.4.3: resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} dependencies: @@ -4068,54 +3348,6 @@ packages: webidl-conversions: 3.0.1 dev: true - /which-boxed-primitive@1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} - dependencies: - is-bigint: 1.0.4 - is-boolean-object: 1.1.2 - is-number-object: 1.0.7 - is-string: 1.0.7 - is-symbol: 1.0.4 - dev: true - - /which-builtin-type@1.1.3: - resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==} - engines: {node: '>= 0.4'} - dependencies: - function.prototype.name: 1.1.6 - has-tostringtag: 1.0.0 - is-async-function: 2.0.0 - is-date-object: 1.0.5 - is-finalizationregistry: 1.0.2 - is-generator-function: 1.0.10 - is-regex: 1.1.4 - is-weakref: 1.0.2 - isarray: 2.0.5 - which-boxed-primitive: 1.0.2 - which-collection: 1.0.1 - which-typed-array: 1.1.13 - dev: true - - /which-collection@1.0.1: - resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} - dependencies: - is-map: 2.0.2 - is-set: 2.0.2 - is-weakmap: 2.0.1 - is-weakset: 2.0.2 - dev: true - - /which-typed-array@1.1.13: - resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==} - engines: {node: '>= 0.4'} - dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.5 - for-each: 0.3.3 - gopd: 1.0.1 - has-tostringtag: 1.0.0 - dev: true - /which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true diff --git a/src/api/Notifications/NotificationComponent.tsx b/src/api/Notifications/NotificationComponent.tsx index 71c40c73..caa4b64e 100644 --- a/src/api/Notifications/NotificationComponent.tsx +++ b/src/api/Notifications/NotificationComponent.tsx @@ -21,7 +21,7 @@ import "./styles.css"; import { useSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { classes } from "@utils/misc"; -import { useEffect, useMemo, useState, useStateFromStores, WindowStore } from "@webpack/common"; +import { React, useEffect, useMemo, useState, useStateFromStores, WindowStore } from "@webpack/common"; import { NotificationData } from "./Notifications"; diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index 02e0e939..ea2e02b5 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -80,7 +80,10 @@ const ErrorBoundary = LazyComponent(() => { if (this.props.noop) return null; if (this.props.fallback) - return {this.props.children}; + return ; const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console."; diff --git a/src/components/Heart.tsx b/src/components/Heart.tsx index 017b4164..b33b8364 100644 --- a/src/components/Heart.tsx +++ b/src/components/Heart.tsx @@ -27,7 +27,7 @@ export function Heart() { > diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index 01012d1f..93b1323e 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -58,7 +58,7 @@ export function LinkIcon({ height = 24, width = 24, className }: IconProps) { className={classes(className, "vc-link-icon")} viewBox="0 0 24 24" > - + diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index 19e9fb3f..fe111fa9 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -30,7 +30,7 @@ import { ChangeList } from "@utils/ChangeList"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { classes, isObjectEmpty } from "@utils/misc"; -import { openModal } from "@utils/modal"; +import { openModalLazy } from "@utils/modal"; import { useAwaiter } from "@utils/react"; import { Plugin } from "@utils/types"; import { findByPropsLazy } from "@webpack"; @@ -95,14 +95,12 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on const isEnabled = () => settings.enabled ?? false; - function openPluginModal() { - openModal(modalProps => ( - onRestartNeeded(plugin.name)} - /> - )); + function openModal() { + openModalLazy(async () => { + return modalProps => { + return onRestartNeeded(plugin.name)} />; + }; + }); } function toggleEnabled() { @@ -161,7 +159,7 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} infoButton={ - diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx index 64ee52fc..43c499ab 100644 --- a/src/plugins/spotifyControls/PlayerComponent.tsx +++ b/src/plugins/spotifyControls/PlayerComponent.tsx @@ -38,21 +38,19 @@ function msToHuman(ms: number) { } function Svg(path: string, label: string) { - return function Icon() { - return ( - - - - ); - }; + return () => ( + + + + ); } // KraXen's icons :yesyes: diff --git a/src/plugins/startupTimings/StartupTimingPage.tsx b/src/plugins/startupTimings/StartupTimingPage.tsx index 0950b068..c8cf51da 100644 --- a/src/plugins/startupTimings/StartupTimingPage.tsx +++ b/src/plugins/startupTimings/StartupTimingPage.tsx @@ -120,8 +120,8 @@ function ServerTrace({ trace }: ServerTraceProps) { - {lines.map((line, i) => ( - {line} + {lines.map(line => ( + {line} ))} diff --git a/src/plugins/typingTweaks/index.tsx b/src/plugins/typingTweaks/index.tsx index ccb863ce..62bcd41f 100644 --- a/src/plugins/typingTweaks/index.tsx +++ b/src/plugins/typingTweaks/index.tsx @@ -21,7 +21,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { openUserProfile } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; -import { Avatar, GuildMemberStore, RelationshipStore } from "@webpack/common"; +import { Avatar, GuildMemberStore, React, RelationshipStore } from "@webpack/common"; import { User } from "discord-types/general"; const settings = definePluginSettings({ @@ -135,7 +135,7 @@ export default definePlugin({ return children.map(c => c.type === "strong" - ? + ? : c ); } diff --git a/src/utils/react.tsx b/src/utils/react.tsx index 43a4d121..0181c95b 100644 --- a/src/utils/react.tsx +++ b/src/utils/react.tsx @@ -133,7 +133,7 @@ export function useForceUpdater(withDep?: true) { export function LazyComponent(factory: () => React.ComponentType, attempts = 5) { const get = makeLazy(factory, attempts); - return function Lazy(props: T) { + return (props: T) => { const Component = get() ?? NoopComponent; return ; }; From 9af2ec65ae5ebcfabedbc3fc703b25868ed0d65b Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 1 Nov 2023 02:09:43 +0100 Subject: [PATCH 0183/1143] OnePingPerDM: fix server pings --- src/plugins/onePingPerDM/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/plugins/onePingPerDM/index.ts b/src/plugins/onePingPerDM/index.ts index 37ca8712..ae38343d 100644 --- a/src/plugins/onePingPerDM/index.ts +++ b/src/plugins/onePingPerDM/index.ts @@ -55,10 +55,8 @@ export default definePlugin({ }], isPrivateChannelRead(message: MessageJSON) { const channelType = ChannelStore.getChannel(message.channel_id)?.type; - if (channelType !== ChannelType.DM && channelType !== ChannelType.GROUP_DM) { - return false; - } if ( + (channelType !== ChannelType.DM && channelType !== ChannelType.GROUP_DM) || (channelType === ChannelType.DM && settings.store.channelToAffect === "group_dm") || (channelType === ChannelType.GROUP_DM && settings.store.channelToAffect === "user_dm") || (settings.store.allowMentions && message.mentions.some(m => m.id === UserStore.getCurrentUser().id)) || From 9d78233afad79402b1422c484f4d233fd9a60540 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sat, 28 Oct 2023 20:32:53 -0300 Subject: [PATCH 0184/1143] Fix displaying BetterFolders sidebar when watching streams in fullscreen --- src/plugins/betterFolders/FolderSideBar.tsx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/plugins/betterFolders/FolderSideBar.tsx b/src/plugins/betterFolders/FolderSideBar.tsx index 5504e1d5..97959873 100644 --- a/src/plugins/betterFolders/FolderSideBar.tsx +++ b/src/plugins/betterFolders/FolderSideBar.tsx @@ -18,16 +18,19 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { LazyComponent } from "@utils/react"; -import { find, findByPropsLazy } from "@webpack"; -import { React, useStateFromStores } from "@webpack/common"; +import { find, findByPropsLazy, findStoreLazy } from "@webpack"; +import { useStateFromStores } from "@webpack/common"; +import type { CSSProperties } from "react"; import { ExpandedGuildFolderStore, settings } from "."; +const ChannelRTCStore = findStoreLazy("ChannelRTCStore"); const Animations = findByPropsLazy("a", "animated", "useTransition"); const GuildsBar = LazyComponent(() => find(m => m.type?.toString().includes('("guildsnav")'))); export default ErrorBoundary.wrap(guildsBarProps => { const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders()); + const isFullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext()); const Sidebar = ( { const visible = !!expandedFolders.size; const guilds = document.querySelector(guildsBarProps.className.split(" ").map(c => `.${c}`).join("")); + // We need to display none if we are in fullscreen. Yes this seems horrible doing with css, but it's literally how Discord does it. + // Also display flex otherwise to fix scrolling + const barStyle = { + display: isFullscreen ? "none" : "flex", + } as CSSProperties; + if (!guilds || !settings.store.sidebarAnim) { return visible - ?
{Sidebar}
+ ?
{Sidebar}
: null; } @@ -54,9 +63,9 @@ export default ErrorBoundary.wrap(guildsBarProps => { leave={{ width: 0 }} config={{ duration: 200 }} > - {(style, show) => + {(animationStyle, show) => show && ( - + {Sidebar} ) From 56a9d79f856fd1bffd703e8723b5c1dfb6de6be1 Mon Sep 17 00:00:00 2001 From: AutumnVN Date: Wed, 1 Nov 2023 08:14:32 +0700 Subject: [PATCH 0185/1143] PiP: fix issues / styles (#1927) --- src/plugins/pictureInPicture/index.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/plugins/pictureInPicture/index.tsx b/src/plugins/pictureInPicture/index.tsx index 4b6f8797..ba4aa838 100644 --- a/src/plugins/pictureInPicture/index.tsx +++ b/src/plugins/pictureInPicture/index.tsx @@ -4,6 +4,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import "./styles.css"; + import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; @@ -28,8 +30,8 @@ export default definePlugin({ { find: ".nonMediaAttachment]", replacement: { - match: /\.nonMediaAttachment\].{0,10}children:\[(\S)/, - replace: "$&,$1&&$self.renderPiPButton()," + match: /\.nonMediaAttachment\]:!(\i).{0,10}children:\[(\S)/, + replace: "$&,$1&&$2&&$self.renderPiPButton()," }, }, ], @@ -40,6 +42,7 @@ export default definePlugin({ {tooltipProps => (
From e37f62ac0a8d93bd354f09567e3e19dd7e8b7d63 Mon Sep 17 00:00:00 2001 From: AutumnVN Date: Wed, 1 Nov 2023 08:19:02 +0700 Subject: [PATCH 0186/1143] imageZoom: dont close carousel modal on image click (#1926) Co-authored-by: Vendicated --- src/plugins/imageZoom/index.tsx | 7 +++++++ src/plugins/imageZoom/styles.css | 18 ++++++++---------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/plugins/imageZoom/index.tsx b/src/plugins/imageZoom/index.tsx index 08f2e29d..75c944eb 100644 --- a/src/plugins/imageZoom/index.tsx +++ b/src/plugins/imageZoom/index.tsx @@ -186,6 +186,13 @@ export default definePlugin({ } ] }, + { + find: ".carouselModal", + replacement: { + match: /(?<=\.carouselModal.{0,100}onClick:)\i,/, + replace: "()=>{}," + } + } ], settings, diff --git a/src/plugins/imageZoom/styles.css b/src/plugins/imageZoom/styles.css index 09c3c858..51e225c0 100644 --- a/src/plugins/imageZoom/styles.css +++ b/src/plugins/imageZoom/styles.css @@ -15,19 +15,17 @@ border-radius: 0; } -.vc-imgzoom-nearest-neighbor > img { - image-rendering: pixelated; /* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */ +.vc-imgzoom-nearest-neighbor>img { + image-rendering: pixelated; + + /* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */ } /* make the carousel take up less space so we can click the backdrop and exit out of it */ -[class|="carouselModal"] { - height: fit-content; - box-shadow: none; +[class*="modalCarouselWrapper_"] { + top: 0 !important; } -[class|="wrapper"]:has(> #vc-imgzoom-magnify-modal) { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); +[class*="carouselModal_"] { + height: 0 !important; } From 9de6c2d4ff17ccf9a2a205b00cccc534692a6c51 Mon Sep 17 00:00:00 2001 From: AutumnVN Date: Wed, 1 Nov 2023 08:19:26 +0700 Subject: [PATCH 0187/1143] previewMessage: fix button (#1919) --- src/plugins/previewMessage/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/previewMessage/index.tsx b/src/plugins/previewMessage/index.tsx index bc675114..f2634ae6 100644 --- a/src/plugins/previewMessage/index.tsx +++ b/src/plugins/previewMessage/index.tsx @@ -137,5 +137,5 @@ export default definePlugin({ }, ], - previewIcon: ErrorBoundary.wrap(PreviewButton, { noop: true }), + chatBarIcon: ErrorBoundary.wrap(PreviewButton, { noop: true }), }); From 5dc0d06be16733e2d5a49b7b8ad8127fea9660ae Mon Sep 17 00:00:00 2001 From: Haruka <96925398+nakoyasha@users.noreply.github.com> Date: Wed, 1 Nov 2023 03:23:45 +0200 Subject: [PATCH 0188/1143] EmoteCloner: make the error toasts useful (#1938) Co-authored-by: Vendicated --- src/plugins/emoteCloner/index.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/plugins/emoteCloner/index.tsx b/src/plugins/emoteCloner/index.tsx index c4473115..219ce435 100644 --- a/src/plugins/emoteCloner/index.tsx +++ b/src/plugins/emoteCloner/index.tsx @@ -155,10 +155,15 @@ async function doClone(guildId: string, data: Sticker | Emoji) { type: Toasts.Type.SUCCESS, id: Toasts.genId() }); - } catch (e) { + } catch (e: any) { + let message = "Something went wrong (check console!)"; + try { + message = JSON.parse(e.text).message; + } catch { } + new Logger("EmoteCloner").error("Failed to clone", data.name, "to", guildId, e); Toasts.show({ - message: "Oopsie something went wrong :( Check console!!!", + message: "Failed to clone: " + message, type: Toasts.Type.FAILURE, id: Toasts.genId() }); From dd61b0c9992c53958508104e3489b63954285121 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 1 Nov 2023 02:25:13 +0100 Subject: [PATCH 0189/1143] bump to v1.6.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index da8fc449..fa463823 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.6.1", + "version": "1.6.2", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 9cf88d42324f3930f707cfdb74613e4b1fac9ff7 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 1 Nov 2023 22:46:06 -0300 Subject: [PATCH 0190/1143] Fix MessageDecorationsAPI --- src/plugins/_api/messageDecorations.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/_api/messageDecorations.ts b/src/plugins/_api/messageDecorations.ts index 1646ad64..b41ec0be 100644 --- a/src/plugins/_api/messageDecorations.ts +++ b/src/plugins/_api/messageDecorations.ts @@ -27,8 +27,8 @@ export default definePlugin({ { find: '"Message Username"', replacement: { - match: /currentUserIsPremium:.{0,70}{children:\i(?=}\))/, - replace: "$&.concat(Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0]))" + match: /\.Messages\.GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE.+?}\),\i(?=\])/, + replace: "$&,...Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])" } } ], From 098da8c3fd95cdd1f299b86d0b919b001009a1df Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 1 Nov 2023 23:26:13 -0300 Subject: [PATCH 0191/1143] Fix ViewIcons --- src/plugins/viewIcons/index.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/plugins/viewIcons/index.tsx b/src/plugins/viewIcons/index.tsx index fd6b7b6b..ccbc8ddc 100644 --- a/src/plugins/viewIcons/index.tsx +++ b/src/plugins/viewIcons/index.tsx @@ -65,7 +65,9 @@ const settings = definePluginSettings({ } }); -function openImage(url: string) { +function openImage(url?: string) { + if (!url) return; + const format = url.startsWith("/") ? "png" : settings.store.format; const u = new URL(url, window.location.href); @@ -171,12 +173,12 @@ export default definePlugin({ }, patches: [ - // Make pfps clickable + // Make pfps clickable and add pointer cursor { - find: "onAddFriend:function", + find: ".AVATAR_DECORATION_STATUS_ROUND_16;", replacement: { - match: /\{src:(\i)(?=,avatarDecoration)/, - replace: "{src:$1,onClick:()=>$self.openImage($1)" + match: /memo\(.{0,50}(?=let{statusColor:\i,status:\i,...\i}=(\i),)/, + replace: (m, props) => `${m}${props}.onClick=()=>$self.openImage(${props}.src);${props}.style={cursor:${props}.src?"pointer":void 0};` } }, // Make banners clickable From 27fffc8bc3fcb6bdca22704f268710f72cc94c4c Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 1 Nov 2023 23:28:52 -0300 Subject: [PATCH 0192/1143] Fix ShowMeYourName --- src/plugins/showMeYourName/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index 6986f69f..62d0645d 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -47,7 +47,7 @@ export default definePlugin({ authors: [Devs.Rini, Devs.TheKodeToad], patches: [ { - find: '"Message Username"', + find: ".useCanSeeRemixBadge)", replacement: { match: /(?<=onContextMenu:\i,children:).*?\}/, replace: "$self.renderUsername(arguments[0])}" From 44b21394b317079721ec5ec48cc3d00bcfeda873 Mon Sep 17 00:00:00 2001 From: AutumnVN Date: Fri, 3 Nov 2023 07:56:31 +0700 Subject: [PATCH 0193/1143] gifPaste: fix unable to use gif picker in profile customization (#1947) --- src/plugins/gifPaste/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/gifPaste/index.ts b/src/plugins/gifPaste/index.ts index 8c1303ba..9ce88f9d 100644 --- a/src/plugins/gifPaste/index.ts +++ b/src/plugins/gifPaste/index.ts @@ -33,8 +33,8 @@ export default definePlugin({ patches: [{ find: ".handleSelectGIF=", replacement: { - match: /\.handleSelectGIF=\i=>\{/, - replace: ".handleSelectGIF=function(gif){return $self.handleSelect(gif);" + match: /\.handleSelectGIF=(\i)=>\{/, + replace: ".handleSelectGIF=$1=>{if (!this.props.className) return $self.handleSelect($1);" } }], From fa9da2d693fb6dde3c973bf467a6e14674f9ea06 Mon Sep 17 00:00:00 2001 From: AutumnVN Date: Fri, 3 Nov 2023 07:57:39 +0700 Subject: [PATCH 0194/1143] noMosaic: play video inline + optional media layout type (#1946) --- src/plugins/noMosaic/index.ts | 45 +++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/plugins/noMosaic/index.ts b/src/plugins/noMosaic/index.ts index 49343503..7f9fad53 100644 --- a/src/plugins/noMosaic/index.ts +++ b/src/plugins/noMosaic/index.ts @@ -4,28 +4,58 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { definePluginSettings } from "@api/Settings"; import { disableStyle, enableStyle } from "@api/Styles"; import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; +import definePlugin, { OptionType } from "@utils/types"; import style from "./styles.css?managed"; +const settings = definePluginSettings({ + inlineVideo: { + description: "Play videos without carousel modal", + type: OptionType.BOOLEAN, + default: true, + restartNeeded: true + }, + mediaLayoutType: { + description: "Choose media layout type", + type: OptionType.SELECT, + restartNeeded: true, + options: [ + { label: "STATIC, render loading image but image isn't resposive, no problem unless discord window width is too small", value: "STATIC", default: true }, + { label: "RESPONSIVE, image is responsive but not render loading image, cause messages shift when loaded", value: "RESPONSIVE" }, + ] + } +}); + export default definePlugin({ name: "NoMosaic", authors: [Devs.AutumnVN], description: "Removes Discord new image mosaic", tags: ["image", "mosaic", "media"], + + settings, + patches: [ { find: ".oneByTwoLayoutThreeGrid", replacement: [{ match: /mediaLayoutType:\i\.\i\.MOSAIC/, - replace: 'mediaLayoutType:"RESPONSIVE"' + replace: "mediaLayoutType:$self.mediaLayoutType()", }, { match: /null!==\(\i=\i\.get\(\i\)\)&&void 0!==\i\?\i:"INVALID"/, replace: '"INVALID"', - },] + }] + }, + { + find: "renderAttachments(", + predicate: () => settings.store.inlineVideo, + replacement: { + match: /url:(\i)\.url\}\);return /, + replace: "$&$1.content_type?.startsWith('image/')&&" + } }, { find: "Messages.REMOVE_ATTACHMENT_TOOLTIP_TEXT", @@ -33,10 +63,17 @@ export default definePlugin({ match: /\i===\i\.\i\.MOSAIC/, replace: "true" } - }], + } + ], + + mediaLayoutType() { + return settings.store.mediaLayoutType; + }, + start() { enableStyle(style); }, + stop() { disableStyle(style); } From fcf2bdda70201c87585b2f3060f9b380e13938e3 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 3 Nov 2023 02:03:53 +0100 Subject: [PATCH 0195/1143] fix TypingTweaks --- src/plugins/typingTweaks/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/typingTweaks/index.tsx b/src/plugins/typingTweaks/index.tsx index 62bcd41f..d3f4a140 100644 --- a/src/plugins/typingTweaks/index.tsx +++ b/src/plugins/typingTweaks/index.tsx @@ -112,7 +112,7 @@ export default definePlugin({ { find: "getCooldownTextStyle", replacement: { - match: /(?<=(\i)\.length\?\i.\i\.Messages.THREE_USERS_TYPING\.format\({\i:(\i),\i:(\i),\i:\i}\):)\i\.\i\.Messages\.SEVERAL_USERS_TYPING/, + match: /(?<=(\i)\.length\?\i.\i\.Messages.THREE_USERS_TYPING\.format\({\i:(\i),(?:\i:)?(\i),\i:\i}\):)\i\.\i\.Messages\.SEVERAL_USERS_TYPING/, replace: (_, users, a, b) => `$self.buildSeveralUsers({ a: ${a}, b: ${b}, count: ${users}.length - 2 })` }, predicate: () => settings.store.alternativeFormatting From 7f73e133647fe612ca5b2e571062f768f47f6cda Mon Sep 17 00:00:00 2001 From: Lewis Crichton Date: Sat, 4 Nov 2023 17:41:38 +0000 Subject: [PATCH 0196/1143] docs: point people to advisories for security bugs (#1957) --- .github/ISSUE_TEMPLATE/bug_report.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 74b2a418..d79f5e49 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -14,7 +14,8 @@ body: DO NOT USE THIS FORM, unless - you are a vencord contributor - you were given explicit permission to use this form by a moderator in our support server - - you are filing a security related report + + DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new) - type: input id: discord From 37b9a6246081400091a815205cc63cb217535661 Mon Sep 17 00:00:00 2001 From: AutumnVN Date: Sun, 5 Nov 2023 00:44:29 +0700 Subject: [PATCH 0197/1143] favGifSearch: fix search bar (#1955) --- src/plugins/favGifSearch/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/favGifSearch/index.tsx b/src/plugins/favGifSearch/index.tsx index 0cb5166e..592d8f54 100644 --- a/src/plugins/favGifSearch/index.tsx +++ b/src/plugins/favGifSearch/index.tsx @@ -60,7 +60,7 @@ interface Instance { } -const containerClasses: { searchBar: string; } = findByPropsLazy("searchBar", "searchHeader", "searchInput"); +const containerClasses: { searchBar: string; } = findByPropsLazy("searchBar", "searchBarFullRow"); export const settings = definePluginSettings({ searchOption: { @@ -182,7 +182,7 @@ function SearchBar({ instance, SearchBarComponent }: { instance: Instance; Searc ref={ref} autoFocus={true} className={containerClasses.searchBar} - size={SearchBarComponent.Sizes.SMALL} + size={SearchBarComponent.Sizes.MEDIUM} onChange={onChange} onClear={() => { setQuery(""); From 77659be4f0d7e6cc8d643037cf4188d1255c143f Mon Sep 17 00:00:00 2001 From: AutumnVN Date: Sun, 5 Nov 2023 00:44:53 +0700 Subject: [PATCH 0198/1143] fakeNitro: disallow emoji in add reaction (#1954) --- src/plugins/fakeNitro/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/fakeNitro/index.ts b/src/plugins/fakeNitro/index.ts index efec9a8b..e4311106 100644 --- a/src/plugins/fakeNitro/index.ts +++ b/src/plugins/fakeNitro/index.ts @@ -201,7 +201,7 @@ export default definePlugin({ predicate: () => settings.store.enableEmojiBypass, replacement: { match: /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))(?=})/g, - replace: (_, rest, premiumCheck) => `${rest},fakeNitroIntention){${premiumCheck}||fakeNitroIntention!=null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)` + replace: (_, rest, premiumCheck) => `${rest},fakeNitroIntention){${premiumCheck}||fakeNitroIntention==null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)` } }, // Allow stickers to be sent everywhere From a257926609d9c88f44015ee0b06040be5936d85f Mon Sep 17 00:00:00 2001 From: AutumnVN Date: Sun, 5 Nov 2023 00:45:17 +0700 Subject: [PATCH 0199/1143] customRpc: fix discord attachment link (#1949) --- src/plugins/customRPC/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/customRPC/index.tsx b/src/plugins/customRPC/index.tsx index 8ed74121..feed52fd 100644 --- a/src/plugins/customRPC/index.tsx +++ b/src/plugins/customRPC/index.tsx @@ -30,6 +30,7 @@ const ActivityClassName = findByPropsLazy("activity", "buttonColor"); const Colors = findByPropsLazy("profileColors"); async function getApplicationAsset(key: string): Promise { + if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, ""); return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0]; } From 2c9793202d1e9538736ec7e795e1b466a46a66cb Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 4 Nov 2023 18:54:02 +0100 Subject: [PATCH 0200/1143] Revert "Fix ViewIcons" This reverts commit 098da8c3fd95cdd1f299b86d0b919b001009a1df. --- src/plugins/viewIcons/index.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/plugins/viewIcons/index.tsx b/src/plugins/viewIcons/index.tsx index ccbc8ddc..fd6b7b6b 100644 --- a/src/plugins/viewIcons/index.tsx +++ b/src/plugins/viewIcons/index.tsx @@ -65,9 +65,7 @@ const settings = definePluginSettings({ } }); -function openImage(url?: string) { - if (!url) return; - +function openImage(url: string) { const format = url.startsWith("/") ? "png" : settings.store.format; const u = new URL(url, window.location.href); @@ -173,12 +171,12 @@ export default definePlugin({ }, patches: [ - // Make pfps clickable and add pointer cursor + // Make pfps clickable { - find: ".AVATAR_DECORATION_STATUS_ROUND_16;", + find: "onAddFriend:function", replacement: { - match: /memo\(.{0,50}(?=let{statusColor:\i,status:\i,...\i}=(\i),)/, - replace: (m, props) => `${m}${props}.onClick=()=>$self.openImage(${props}.src);${props}.style={cursor:${props}.src?"pointer":void 0};` + match: /\{src:(\i)(?=,avatarDecoration)/, + replace: "{src:$1,onClick:()=>$self.openImage($1)" } }, // Make banners clickable From a67c7f841d3a4fc96cb64a61aa37d00454d658bc Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 4 Nov 2023 18:54:29 +0100 Subject: [PATCH 0201/1143] Fix ViewIcons correctly --- src/plugins/viewIcons/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/viewIcons/index.tsx b/src/plugins/viewIcons/index.tsx index fd6b7b6b..2da3e21c 100644 --- a/src/plugins/viewIcons/index.tsx +++ b/src/plugins/viewIcons/index.tsx @@ -173,7 +173,7 @@ export default definePlugin({ patches: [ // Make pfps clickable { - find: "onAddFriend:function", + find: "User Profile Modal - Context Menu", replacement: { match: /\{src:(\i)(?=,avatarDecoration)/, replace: "{src:$1,onClick:()=>$self.openImage($1)" From 370b3d366df87b6359c5844e6f7784f31fbcd748 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 4 Nov 2023 18:59:16 +0100 Subject: [PATCH 0202/1143] OpenInApp: Fix links in messages --- src/plugins/openInApp/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/openInApp/index.ts b/src/plugins/openInApp/index.ts index e26a01eb..a65c4931 100644 --- a/src/plugins/openInApp/index.ts +++ b/src/plugins/openInApp/index.ts @@ -55,8 +55,8 @@ export default definePlugin({ { find: "trackAnnouncementMessageLinkClicked({", replacement: { - match: /(?<=handleClick:function\(\)\{return (\i)\}.+?)async function \1\(.+?\)\{/, - replace: "$& if(await $self.handleLink(...arguments)) return;" + match: /(?<=handleClick:function\(\)\{return (\i)\}.+?)function \1\(.+?\)\{/, + replace: "async $& if(await $self.handleLink(...arguments)) return;" } }, // Make Spotify profile activity links open in app on web From dd44ac1ad2280672e2c60587a177f51a351c77fa Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 4 Nov 2023 19:08:44 +0100 Subject: [PATCH 0203/1143] OpenInApp: Support podcasts --- src/plugins/openInApp/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/openInApp/index.ts b/src/plugins/openInApp/index.ts index a65c4931..5a2641e2 100644 --- a/src/plugins/openInApp/index.ts +++ b/src/plugins/openInApp/index.ts @@ -23,7 +23,7 @@ import { showToast, Toasts } from "@webpack/common"; import type { MouseEvent } from "react"; const ShortUrlMatcher = /^https:\/\/(spotify\.link|s\.team)\/.+$/; -const SpotifyMatcher = /^https:\/\/open\.spotify\.com\/(track|album|artist|playlist|user)\/(.+)(?:\?.+?)?$/; +const SpotifyMatcher = /^https:\/\/open\.spotify\.com\/(track|album|artist|playlist|user|episode)\/(.+)(?:\?.+?)?$/; const SteamMatcher = /^https:\/\/(steamcommunity\.com|(?:help|store)\.steampowered\.com)\/.+$/; const EpicMatcher = /^https:\/\/store\.epicgames\.com\/(.+)$/; From 86e94343cca10b950f2dc8d18d496d6db9f3b728 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sun, 5 Nov 2023 02:07:33 +0100 Subject: [PATCH 0204/1143] bump to v1.6.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fa463823..f3307d7d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.6.2", + "version": "1.6.3", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 04d2dd26c412884e7699f4a104bda4ac6259b995 Mon Sep 17 00:00:00 2001 From: Marvin Witt Date: Sun, 5 Nov 2023 02:06:08 +0100 Subject: [PATCH 0205/1143] fix(dearrow): don't replace thumbnail if only original available (#1959) --- src/plugins/dearrow/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/dearrow/index.tsx b/src/plugins/dearrow/index.tsx index c6bf4e05..f6b9ef95 100644 --- a/src/plugins/dearrow/index.tsx +++ b/src/plugins/dearrow/index.tsx @@ -63,7 +63,7 @@ async function embedDidMount(this: Component) { embed.rawTitle = titles[0].title; } - if (thumbnails[0]?.votes >= 0) { + if (thumbnails[0]?.votes >= 0 && thumbnails[0].timestamp) { embed.dearrow.oldThumb = embed.thumbnail.proxyURL; embed.thumbnail.proxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`; } From 32f204319394ca7545e779b0d5ffb415d64a6e56 Mon Sep 17 00:00:00 2001 From: zImPatrick <23613354+zImPatrick@users.noreply.github.com> Date: Tue, 7 Nov 2023 19:58:10 +0100 Subject: [PATCH 0206/1143] Fix FakeNitro sticker bypass (#1964) --- src/plugins/fakeNitro/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/fakeNitro/index.ts b/src/plugins/fakeNitro/index.ts index e4311106..4d6b7957 100644 --- a/src/plugins/fakeNitro/index.ts +++ b/src/plugins/fakeNitro/index.ts @@ -206,10 +206,10 @@ export default definePlugin({ }, // Allow stickers to be sent everywhere { - find: "canUseStickersEverywhere:function", + find: "canUseCustomStickersEverywhere:function", predicate: () => settings.store.enableStickerBypass, replacement: { - match: /canUseStickersEverywhere:function\(\i\){/, + match: /canUseCustomStickersEverywhere:function\(\i\){/, replace: "$&return true;" }, }, From 119b628f331e9282342df213f941851f98630ebb Mon Sep 17 00:00:00 2001 From: V Date: Thu, 9 Nov 2023 02:32:34 +0100 Subject: [PATCH 0207/1143] feat: simple plugin natives (#1965) --- scripts/build/build.mjs | 64 ++++++++++++++- scripts/build/common.mjs | 12 ++- src/VencordNative.ts | 20 +++-- src/main/ipcPlugins.ts | 77 ++++--------------- src/modules.d.ts | 5 ++ .../fixSpotifyEmbeds.desktop/native.ts | 27 +++++++ src/plugins/openInApp/index.ts | 6 +- src/plugins/openInApp/native.ts | 31 ++++++++ src/plugins/voiceMessages/DesktopRecorder.tsx | 5 +- src/plugins/voiceMessages/native.ts | 24 ++++++ src/utils/IpcEvents.ts | 2 + src/utils/types.ts | 7 ++ 12 files changed, 200 insertions(+), 80 deletions(-) create mode 100644 src/plugins/fixSpotifyEmbeds.desktop/native.ts create mode 100644 src/plugins/openInApp/native.ts create mode 100644 src/plugins/voiceMessages/native.ts diff --git a/scripts/build/build.mjs b/scripts/build/build.mjs index f606f1b0..89cca7e4 100755 --- a/scripts/build/build.mjs +++ b/scripts/build/build.mjs @@ -18,8 +18,10 @@ */ import esbuild from "esbuild"; +import { readdir } from "fs/promises"; +import { join } from "path"; -import { BUILD_TIMESTAMP, commonOpts, globPlugins, isStandalone, updaterDisabled, VERSION, watch } from "./common.mjs"; +import { BUILD_TIMESTAMP, commonOpts, existsAsync, globPlugins, isStandalone, updaterDisabled, VERSION, watch } from "./common.mjs"; const defines = { IS_STANDALONE: isStandalone, @@ -43,13 +45,59 @@ const nodeCommonOpts = { format: "cjs", platform: "node", target: ["esnext"], - external: ["electron", "original-fs", ...commonOpts.external], + external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external], define: defines, }; const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`; const sourcemap = watch ? "inline" : "external"; +/** + * @type {import("esbuild").Plugin} + */ +const globNativesPlugin = { + name: "glob-natives-plugin", + setup: build => { + const filter = /^~pluginNatives$/; + build.onResolve({ filter }, args => { + return { + namespace: "import-natives", + path: args.path + }; + }); + + build.onLoad({ filter, namespace: "import-natives" }, async () => { + const pluginDirs = ["plugins", "userplugins"]; + let code = ""; + let natives = "\n"; + let i = 0; + for (const dir of pluginDirs) { + const dirPath = join("src", dir); + if (!await existsAsync(dirPath)) continue; + const plugins = await readdir(dirPath); + for (const p of plugins) { + if (!await existsAsync(join(dirPath, p, "native.ts"))) continue; + + const nameParts = p.split("."); + const namePartsWithoutTarget = nameParts.length === 1 ? nameParts : nameParts.slice(0, -1); + // pluginName.thing.desktop -> PluginName.thing + const cleanPluginName = p[0].toUpperCase() + namePartsWithoutTarget.join(".").slice(1); + + const mod = `p${i}`; + code += `import * as ${mod} from "./${dir}/${p}/native";\n`; + natives += `${JSON.stringify(cleanPluginName)}:${mod},\n`; + i++; + } + } + code += `export default {${natives}};`; + return { + contents: code, + resolveDir: "./src" + }; + }); + } +}; + await Promise.all([ // Discord Desktop main & renderer & preload esbuild.build({ @@ -62,7 +110,11 @@ await Promise.all([ ...defines, IS_DISCORD_DESKTOP: true, IS_VESKTOP: false - } + }, + plugins: [ + ...nodeCommonOpts.plugins, + globNativesPlugin + ] }), esbuild.build({ ...commonOpts, @@ -107,7 +159,11 @@ await Promise.all([ ...defines, IS_DISCORD_DESKTOP: false, IS_VESKTOP: true - } + }, + plugins: [ + ...nodeCommonOpts.plugins, + globNativesPlugin + ] }), esbuild.build({ ...commonOpts, diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs index 8efe2be6..5488b1b3 100644 --- a/scripts/build/common.mjs +++ b/scripts/build/common.mjs @@ -20,8 +20,8 @@ import "../suppressExperimentalWarnings.js"; import "../checkNodeVersion.js"; import { exec, execSync } from "child_process"; -import { existsSync, readFileSync } from "fs"; -import { readdir, readFile } from "fs/promises"; +import { constants as FsConstants, readFileSync } from "fs"; +import { access, readdir, readFile } from "fs/promises"; import { join, relative } from "path"; import { promisify } from "util"; @@ -47,6 +47,12 @@ export const banner = { const isWeb = process.argv.slice(0, 2).some(f => f.endsWith("buildWeb.mjs")); +export function existsAsync(path) { + return access(path, FsConstants.F_OK) + .then(() => true) + .catch(() => false); +} + // https://github.com/evanw/esbuild/issues/619#issuecomment-751995294 /** * @type {import("esbuild").Plugin} @@ -79,7 +85,7 @@ export const globPlugins = kind => ({ let plugins = "\n"; let i = 0; for (const dir of pluginDirs) { - if (!existsSync(`./src/${dir}`)) continue; + if (!await existsAsync(`./src/${dir}`)) continue; const files = await readdir(`./src/${dir}`); for (const file of files) { if (file.startsWith("_") || file.startsWith(".")) continue; diff --git a/src/VencordNative.ts b/src/VencordNative.ts index dd97b5d2..0faa5569 100644 --- a/src/VencordNative.ts +++ b/src/VencordNative.ts @@ -7,6 +7,7 @@ import { IpcEvents } from "@utils/IpcEvents"; import { IpcRes } from "@utils/types"; import { ipcRenderer } from "electron"; +import { PluginIpcMappings } from "main/ipcPlugins"; import type { UserThemeHeader } from "main/themes"; function invoke(event: IpcEvents, ...args: any[]) { @@ -17,6 +18,16 @@ export function sendSync(event: IpcEvents, ...args: any[]) { return ipcRenderer.sendSync(event, ...args) as T; } +const PluginHelpers = {} as Record Promise>>; +const pluginIpcMap = sendSync(IpcEvents.GET_PLUGIN_IPC_METHOD_MAP); + +for (const [plugin, methods] of Object.entries(pluginIpcMap)) { + const map = PluginHelpers[plugin] = {}; + for (const [methodName, method] of Object.entries(methods)) { + map[methodName] = (...args: any[]) => invoke(method as IpcEvents, ...args); + } +} + export default { themes: { uploadTheme: (fileName: string, fileData: string) => invoke(IpcEvents.UPLOAD_THEME, fileName, fileData), @@ -61,12 +72,5 @@ export default { openExternal: (url: string) => invoke(IpcEvents.OPEN_EXTERNAL, url) }, - pluginHelpers: { - OpenInApp: { - resolveRedirect: (url: string) => invoke(IpcEvents.OPEN_IN_APP__RESOLVE_REDIRECT, url), - }, - VoiceMessages: { - readRecording: (path: string) => invoke(IpcEvents.VOICE_MESSAGES_READ_RECORDING, path), - } - } + pluginHelpers: PluginHelpers }; diff --git a/src/main/ipcPlugins.ts b/src/main/ipcPlugins.ts index 3034fb43..5d679fc0 100644 --- a/src/main/ipcPlugins.ts +++ b/src/main/ipcPlugins.ts @@ -17,73 +17,26 @@ */ import { IpcEvents } from "@utils/IpcEvents"; -import { app, ipcMain } from "electron"; -import { readFile } from "fs/promises"; -import { request } from "https"; -import { basename, normalize } from "path"; +import { ipcMain } from "electron"; -import { getSettings } from "./ipcMain"; +import PluginNatives from "~pluginNatives"; -// FixSpotifyEmbeds -app.on("browser-window-created", (_, win) => { - win.webContents.on("frame-created", (_, { frame }) => { - frame.once("dom-ready", () => { - if (frame.url.startsWith("https://open.spotify.com/embed/")) { - const settings = getSettings().plugins?.FixSpotifyEmbeds; - if (!settings?.enabled) return; +const PluginIpcMappings = {} as Record>; +export type PluginIpcMappings = typeof PluginIpcMappings; - frame.executeJavaScript(` - const original = Audio.prototype.play; - Audio.prototype.play = function() { - this.volume = ${(settings.volume / 100) || 0.1}; - return original.apply(this, arguments); - } - `); - } - }); - }); -}); +for (const [plugin, methods] of Object.entries(PluginNatives)) { + const entries = Object.entries(methods); + if (!entries.length) continue; -// #region OpenInApp -// These links don't support CORS, so this has to be native -const validRedirectUrls = /^https:\/\/(spotify\.link|s\.team)\/.+$/; + const mappings = PluginIpcMappings[plugin] = {}; -function getRedirect(url: string) { - return new Promise((resolve, reject) => { - const req = request(new URL(url), { method: "HEAD" }, res => { - resolve( - res.headers.location - ? getRedirect(res.headers.location) - : url - ); - }); - req.on("error", reject); - req.end(); - }); + for (const [methodName, method] of entries) { + const key = `VencordPluginNative_${plugin}_${methodName}`; + ipcMain.handle(key, method); + mappings[methodName] = key; + } } -ipcMain.handle(IpcEvents.OPEN_IN_APP__RESOLVE_REDIRECT, async (_, url: string) => { - if (!validRedirectUrls.test(url)) return url; - - return getRedirect(url); +ipcMain.on(IpcEvents.GET_PLUGIN_IPC_METHOD_MAP, e => { + e.returnValue = PluginIpcMappings; }); -// #endregion - - -// #region VoiceMessages -ipcMain.handle(IpcEvents.VOICE_MESSAGES_READ_RECORDING, async (_, filePath: string) => { - filePath = normalize(filePath); - const filename = basename(filePath); - const discordBaseDirWithTrailingSlash = normalize(app.getPath("userData") + "/"); - console.log(filename, discordBaseDirWithTrailingSlash, filePath); - if (filename !== "recording.ogg" || !filePath.startsWith(discordBaseDirWithTrailingSlash)) return null; - - try { - const buf = await readFile(filePath); - return new Uint8Array(buf.buffer); - } catch { - return null; - } -}); - -// #endregion diff --git a/src/modules.d.ts b/src/modules.d.ts index d75a84f7..24f34664 100644 --- a/src/modules.d.ts +++ b/src/modules.d.ts @@ -24,6 +24,11 @@ declare module "~plugins" { export default plugins; } +declare module "~pluginNatives" { + const pluginNatives: Record unknown>>; + export default pluginNatives; +} + declare module "~git-hash" { const hash: string; export default hash; diff --git a/src/plugins/fixSpotifyEmbeds.desktop/native.ts b/src/plugins/fixSpotifyEmbeds.desktop/native.ts new file mode 100644 index 00000000..f779c400 --- /dev/null +++ b/src/plugins/fixSpotifyEmbeds.desktop/native.ts @@ -0,0 +1,27 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { app } from "electron"; +import { getSettings } from "main/ipcMain"; + +app.on("browser-window-created", (_, win) => { + win.webContents.on("frame-created", (_, { frame }) => { + frame.once("dom-ready", () => { + if (frame.url.startsWith("https://open.spotify.com/embed/")) { + const settings = getSettings().plugins?.FixSpotifyEmbeds; + if (!settings?.enabled) return; + + frame.executeJavaScript(` + const original = Audio.prototype.play; + Audio.prototype.play = function() { + this.volume = ${(settings.volume / 100) || 0.1}; + return original.apply(this, arguments); + } + `); + } + }); + }); +}); diff --git a/src/plugins/openInApp/index.ts b/src/plugins/openInApp/index.ts index 5a2641e2..0835c061 100644 --- a/src/plugins/openInApp/index.ts +++ b/src/plugins/openInApp/index.ts @@ -18,7 +18,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; -import definePlugin, { OptionType } from "@utils/types"; +import definePlugin, { OptionType, PluginNative } from "@utils/types"; import { showToast, Toasts } from "@webpack/common"; import type { MouseEvent } from "react"; @@ -45,6 +45,8 @@ const settings = definePluginSettings({ } }); +const Native = VencordNative.pluginHelpers.OpenInApp as PluginNative; + export default definePlugin({ name: "OpenInApp", description: "Open Spotify, Steam and Epic Games URLs in their respective apps instead of your browser", @@ -84,7 +86,7 @@ export default definePlugin({ if (!IS_WEB && ShortUrlMatcher.test(url)) { event?.preventDefault(); // CORS jumpscare - url = await VencordNative.pluginHelpers.OpenInApp.resolveRedirect(url); + url = await Native.resolveRedirect(url); } spotify: { diff --git a/src/plugins/openInApp/native.ts b/src/plugins/openInApp/native.ts new file mode 100644 index 00000000..25637422 --- /dev/null +++ b/src/plugins/openInApp/native.ts @@ -0,0 +1,31 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { IpcMainInvokeEvent } from "electron"; +import { request } from "https"; + +// These links don't support CORS, so this has to be native +const validRedirectUrls = /^https:\/\/(spotify\.link|s\.team)\/.+$/; + +function getRedirect(url: string) { + return new Promise((resolve, reject) => { + const req = request(new URL(url), { method: "HEAD" }, res => { + resolve( + res.headers.location + ? getRedirect(res.headers.location) + : url + ); + }); + req.on("error", reject); + req.end(); + }); +} + +export async function resolveRedirect(_: IpcMainInvokeEvent, url: string) { + if (!validRedirectUrls.test(url)) return url; + + return getRedirect(url); +} diff --git a/src/plugins/voiceMessages/DesktopRecorder.tsx b/src/plugins/voiceMessages/DesktopRecorder.tsx index 36f6a60a..a69739a4 100644 --- a/src/plugins/voiceMessages/DesktopRecorder.tsx +++ b/src/plugins/voiceMessages/DesktopRecorder.tsx @@ -16,11 +16,14 @@ * along with this program. If not, see . */ +import { PluginNative } from "@utils/types"; import { Button, showToast, Toasts, useState } from "@webpack/common"; import type { VoiceRecorder } from "."; import { settings } from "./settings"; +const Native = VencordNative.pluginHelpers.VoiceMessages as PluginNative; + export const VoiceRecorderDesktop: VoiceRecorder = ({ setAudioBlob, onRecordingChange }) => { const [recording, setRecording] = useState(false); @@ -49,7 +52,7 @@ export const VoiceRecorderDesktop: VoiceRecorder = ({ setAudioBlob, onRecordingC } else { discordVoice.stopLocalAudioRecording(async (filePath: string) => { if (filePath) { - const buf = await VencordNative.pluginHelpers.VoiceMessages.readRecording(filePath); + const buf = await Native.readRecording(filePath); if (buf) setAudioBlob(new Blob([buf], { type: "audio/ogg; codecs=opus" })); else diff --git a/src/plugins/voiceMessages/native.ts b/src/plugins/voiceMessages/native.ts new file mode 100644 index 00000000..bbc19c89 --- /dev/null +++ b/src/plugins/voiceMessages/native.ts @@ -0,0 +1,24 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { app } from "electron"; +import { readFile } from "fs/promises"; +import { basename, normalize } from "path"; + +export async function readRecording(_, filePath: string) { + filePath = normalize(filePath); + const filename = basename(filePath); + const discordBaseDirWithTrailingSlash = normalize(app.getPath("userData") + "/"); + console.log(filename, discordBaseDirWithTrailingSlash, filePath); + if (filename !== "recording.ogg" || !filePath.startsWith(discordBaseDirWithTrailingSlash)) return null; + + try { + const buf = await readFile(filePath); + return new Uint8Array(buf.buffer); + } catch { + return null; + } +} diff --git a/src/utils/IpcEvents.ts b/src/utils/IpcEvents.ts index 16bcfa65..2027df9c 100644 --- a/src/utils/IpcEvents.ts +++ b/src/utils/IpcEvents.ts @@ -38,6 +38,8 @@ export const enum IpcEvents { BUILD = "VencordBuild", OPEN_MONACO_EDITOR = "VencordOpenMonacoEditor", + GET_PLUGIN_IPC_METHOD_MAP = "VencordGetPluginIpcMethodMap", + OPEN_IN_APP__RESOLVE_REDIRECT = "VencordOIAResolveRedirect", VOICE_MESSAGES_READ_RECORDING = "VencordVMReadRecording", } diff --git a/src/utils/types.ts b/src/utils/types.ts index ff2c79af..b32b127b 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -307,3 +307,10 @@ export type PluginOptionBoolean = PluginSettingBooleanDef & PluginSettingCommon export type PluginOptionSelect = PluginSettingSelectDef & PluginSettingCommon & IsDisabled & IsValid; export type PluginOptionSlider = PluginSettingSliderDef & PluginSettingCommon & IsDisabled & IsValid; export type PluginOptionComponent = PluginSettingComponentDef & PluginSettingCommon; + +export type PluginNative any>> = { + [key in keyof PluginExports]: + PluginExports[key] extends (event: Electron.IpcMainInvokeEvent, ...args: infer Args) => infer Return + ? (...args: Args) => Return extends Promise ? Return : Promise + : never; +}; From 394d2060eb90e5d7104798026fa8a18a02a24d93 Mon Sep 17 00:00:00 2001 From: AutumnVN Date: Thu, 9 Nov 2023 08:34:40 +0700 Subject: [PATCH 0208/1143] searchReply: fix (#1961) --- src/plugins/searchReply/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/searchReply/index.tsx b/src/plugins/searchReply/index.tsx index b28ca8ec..b151712a 100644 --- a/src/plugins/searchReply/index.tsx +++ b/src/plugins/searchReply/index.tsx @@ -20,12 +20,12 @@ import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCal import { ReplyIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; -import { findByCodeLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { ChannelStore, i18n, Menu, PermissionsBits, PermissionStore, SelectedChannelStore } from "@webpack/common"; import { Message } from "discord-types/general"; -const replyFn = findByCodeLazy("showMentionToggle", "TEXTAREA_FOCUS", "shiftKey"); +const messageUtils = findByPropsLazy("replyToMessage"); const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => () => { // make sure the message is in the selected channel @@ -43,7 +43,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag id="reply" label={i18n.Messages.MESSAGE_ACTION_REPLY} icon={ReplyIcon} - action={(e: React.MouseEvent) => replyFn(channel, message, e)} + action={(e: React.MouseEvent) => messageUtils.replyToMessage(channel, message, e)} /> )); } @@ -56,7 +56,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag id="reply" label={i18n.Messages.MESSAGE_ACTION_REPLY} icon={ReplyIcon} - action={(e: React.MouseEvent) => replyFn(channel, message, e)} + action={(e: React.MouseEvent) => messageUtils.replyToMessage(channel, message, e)} /> )); } From 5edc94062c2940a415edb8d66fe9fb346b9cb4fb Mon Sep 17 00:00:00 2001 From: V Date: Thu, 9 Nov 2023 02:42:24 +0100 Subject: [PATCH 0209/1143] make packageManager key less specific --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f3307d7d..1bc2129f 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "typescript": "^5.0.4", "zip-local": "^0.3.5" }, - "packageManager": "pnpm@8.1.1", + "packageManager": "^pnpm@8.1.1", "pnpm": { "patchedDependencies": { "eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch", From 9bd82943e3f178324e38eebe8a683cc1c1ded94c Mon Sep 17 00:00:00 2001 From: AM Date: Thu, 9 Nov 2023 03:42:35 +0000 Subject: [PATCH 0210/1143] [Plugin] Super Reaction Tweaks (#1958) Co-authored-by: V Co-authored-by: Jack Matthews --- src/plugins/superReactionTweaks/README.md | 11 ++++ src/plugins/superReactionTweaks/index.ts | 63 +++++++++++++++++++++++ src/utils/constants.ts | 4 ++ 3 files changed, 78 insertions(+) create mode 100644 src/plugins/superReactionTweaks/README.md create mode 100644 src/plugins/superReactionTweaks/index.ts diff --git a/src/plugins/superReactionTweaks/README.md b/src/plugins/superReactionTweaks/README.md new file mode 100644 index 00000000..624ab866 --- /dev/null +++ b/src/plugins/superReactionTweaks/README.md @@ -0,0 +1,11 @@ +# Super Reaction Tweaks + +This plugin applies configurable various tweaks to super reactions. + +![Screenshot](https://user-images.githubusercontent.com/22851444/281598795-58f07116-9f95-4f64-940b-23a5499f2302.png) + +## Features: + +**Super React By Default** - The reaction picker will default to super reactions instead of normal reactions. + +**Super Reaction Play Limit** - Allows you to decide how many super reaction animations can play at once, including removing the limit entirely. diff --git a/src/plugins/superReactionTweaks/index.ts b/src/plugins/superReactionTweaks/index.ts new file mode 100644 index 00000000..2652eef0 --- /dev/null +++ b/src/plugins/superReactionTweaks/index.ts @@ -0,0 +1,63 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated, ant0n, FieryFlames and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; + +export const settings = definePluginSettings({ + superReactByDefault: { + type: OptionType.BOOLEAN, + description: "Reaction picker will default to Super Reactions", + default: true, + }, + unlimitedSuperReactionPlaying: { + type: OptionType.BOOLEAN, + description: "Remove the limit on Super Reactions playing at once", + default: false, + }, + + superReactionPlayingLimit: { + description: "Max Super Reactions to play at once", + type: OptionType.SLIDER, + default: 20, + markers: [5, 10, 20, 40, 60, 80, 100], + stickToMarkers: true, + }, +}, { + superReactionPlayingLimit: { + disabled() { return this.store.unlimitedSuperReactionPlaying; }, + } +}); + +export default definePlugin({ + name: "SuperReactionTweaks", + description: "Customize the limit of Super Reactions playing at once, and super react by default", + authors: [Devs.FieryFlames, Devs.ant0n], + patches: [ + { + find: ",BURST_REACTION_EFFECT_PLAY", + replacement: { + match: /(?<=BURST_REACTION_EFFECT_PLAY:\i=>{.{50,100})(\i\(\i,\i\))>=\d+/, + replace: "!$self.shouldPlayBurstReaction($1)" + } + }, + { + find: ".hasAvailableBurstCurrency)", + replacement: { + match: /(?<=\.useBurstReactionsExperiment.{0,20})useState\(!1\)(?=.+?(\i===\i\.EmojiIntention.REACTION))/, + replace: "useState($self.settings.store.superReactByDefault && $1)" + } + } + ], + settings, + + shouldPlayBurstReaction(playingCount: number) { + if (settings.store.unlimitedSuperReactionPlaying) return true; + if (playingCount <= settings.store.superReactionPlayingLimit) return true; + return false; + } +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index aeadcdbf..3db2e64f 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -379,6 +379,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "ProffDea", id: 609329952180928513n }, + ant0n: { + name: "ant0n", + id: 145224646868860928n + }, } satisfies Record); // iife so #__PURE__ works correctly From 96126fa39f910960ec10ccc089fe17e91c1a2afe Mon Sep 17 00:00:00 2001 From: V Date: Thu, 9 Nov 2023 07:15:49 +0100 Subject: [PATCH 0211/1143] remove pipebomb from github actions (#1968) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1bc2129f..23d9dd27 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "typescript": "^5.0.4", "zip-local": "^0.3.5" }, - "packageManager": "^pnpm@8.1.1", + "packageManager": "pnpm@8.10.2", "pnpm": { "patchedDependencies": { "eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch", From ea11f2244fde469ce308f8a4e7224430be62f8f1 Mon Sep 17 00:00:00 2001 From: V Date: Mon, 13 Nov 2023 01:37:15 +0100 Subject: [PATCH 0212/1143] README: Add sponsors --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index cd54fcdc..177e65be 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,14 @@ Visit https://vencord.dev/download https://discord.gg/D9uwnFnqmd +## Sponsors + +| **Thanks a lot to all Vencord [sponsors](https://github.com/sponsors/Vendicated)!!** | +|:--:| +| [![](https://meow.vendicated.dev/sponsors.png)](https://github.com/sponsors/Vendicated) | +| *generated using [github-sponsor-graph](https://github.com/Vendicated/github-sponsor-graph)* | + + ## Star History From 77d08c5c28121823dd5ba85265a9d09b832a2932 Mon Sep 17 00:00:00 2001 From: megumin Date: Sat, 11 Nov 2023 00:28:09 +0000 Subject: [PATCH 0213/1143] feat: Add Environment variable to disable updater auto-patching (#1971) --- src/main/patchWin32Updater.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/patchWin32Updater.ts b/src/main/patchWin32Updater.ts index ba7a9224..96717a5a 100644 --- a/src/main/patchWin32Updater.ts +++ b/src/main/patchWin32Updater.ts @@ -32,6 +32,8 @@ function isNewer($new: string, old: string) { } function patchLatest() { + if (process.env.DISABLE_UPDATER_AUTO_PATCHING) return; + try { const currentAppPath = dirname(process.execPath); const currentVersion = basename(currentAppPath); From fd25b5f2968385741e36b5a6624a80d7ef14b61f Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Sun, 12 Nov 2023 15:33:34 -0500 Subject: [PATCH 0214/1143] fix(dearrow): support DeArrow thumbnail submissions at 0 seconds (#1979) --- src/plugins/dearrow/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/dearrow/index.tsx b/src/plugins/dearrow/index.tsx index f6b9ef95..52cf1d53 100644 --- a/src/plugins/dearrow/index.tsx +++ b/src/plugins/dearrow/index.tsx @@ -50,7 +50,7 @@ async function embedDidMount(this: Component) { const { titles, thumbnails } = await res.json(); const hasTitle = titles[0]?.votes >= 0; - const hasThumb = thumbnails[0]?.votes >= 0; + const hasThumb = thumbnails[0]?.votes >= 0 && !thumbnails[0].original; if (!hasTitle && !hasThumb) return; @@ -58,12 +58,12 @@ async function embedDidMount(this: Component) { enabled: true }; - if (titles[0]?.votes >= 0) { + if (hasTitle) { embed.dearrow.oldTitle = embed.rawTitle; embed.rawTitle = titles[0].title; } - if (thumbnails[0]?.votes >= 0 && thumbnails[0].timestamp) { + if (hasThumb) { embed.dearrow.oldThumb = embed.thumbnail.proxyURL; embed.thumbnail.proxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`; } From 7b248ee30952eccd880a93dfe1209d8da188b809 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 15 Nov 2023 14:50:52 -0300 Subject: [PATCH 0215/1143] Fix SHC patches --- src/plugins/showHiddenChannels/index.tsx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 95b16d62..4e275d64 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -68,7 +68,7 @@ export default definePlugin({ patches: [ { // RenderLevel defines if a channel is hidden, collapsed in category, visible, etc - find: ".CannotShow=", + find: '"placeholder-channel-id"', replacement: [ // Remove the special logic for channels we don't have access to { @@ -82,13 +82,8 @@ export default definePlugin({ }, // Make channels we dont have access to be the same level as normal ones { - match: /(?<=renderLevel:(\i\(this,\i\)\?\i\.Show:\i\.WouldShowIfUncollapsed).+?renderLevel:).+?(?=,)/, - replace: (_, renderLevelExpression) => renderLevelExpression - }, - // Make channels we dont have access to be the same level as normal ones - { - match: /(?<=activeJoinedRelevantThreads.+?renderLevel:.+?,threadIds:\i\(this.record.+?renderLevel:)(\i)\..+?(?=,)/, - replace: (_, RenderLevels) => `${RenderLevels}.Show` + match: /(activeJoinedRelevantThreads:.{0,50}VIEW_CHANNEL.+?renderLevel:(.+?),threadIds.+?renderLevel:).+?(?=,threadIds)/g, + replace: (_, rest, defaultRenderLevel) => `${rest}${defaultRenderLevel}` }, // Remove permission checking for getRenderLevel function { From af614465a49cdd70038cf089b195855a7fa1f18c Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 15 Nov 2023 14:54:48 -0300 Subject: [PATCH 0216/1143] Remove obsolete experiments patch --- src/plugins/experiments/index.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/plugins/experiments/index.tsx b/src/plugins/experiments/index.tsx index 89fcf33e..f8ea4b04 100644 --- a/src/plugins/experiments/index.tsx +++ b/src/plugins/experiments/index.tsx @@ -77,15 +77,6 @@ export default definePlugin({ } ] }, - // Fix search history being disabled / broken with isStaff - { - find: '("showNewSearch")', - predicate: () => settings.store.enableIsStaff, - replacement: { - match: /(?<=showNewSearch"\);return)\s?/, - replace: "!1&&" - } - }, { find: 'H1,title:"Experiments"', replacement: { From 3ea6a967153db9150e809f2050b40fa5b4e54a00 Mon Sep 17 00:00:00 2001 From: Jack <30497388+FieryFlames@users.noreply.github.com> Date: Wed, 15 Nov 2023 13:01:50 -0500 Subject: [PATCH 0217/1143] chore: Fix PinDMs patch (#1981) --- src/plugins/pinDms/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/pinDms/index.tsx b/src/plugins/pinDms/index.tsx index ac5957b3..792bdab6 100644 --- a/src/plugins/pinDms/index.tsx +++ b/src/plugins/pinDms/index.tsx @@ -100,10 +100,10 @@ export default definePlugin({ }, { // Fix getRowHeight's check for whether this is the DMs section - // section === DMS - match: /===\i\.DMS&&0/, - // section -1 === DMS - replace: "-1$&" + // DMS (inlined) === section + match: /(?<=getRowHeight=\(.{2,50}?)1===\i/, + // DMS (inlined) === section - 1 + replace: "$&-1" }, { // Override scrollToChannel to properly account for pinned channels From c080a0eaacd34e813800db4f6b632d62a9633a3b Mon Sep 17 00:00:00 2001 From: Thoth <54485853+xero-lib@users.noreply.github.com> Date: Wed, 15 Nov 2023 12:09:26 -0600 Subject: [PATCH 0218/1143] shikiCodeblocks: transform lang to lower case to avoid failing detection (#1990) --- src/plugins/shikiCodeblocks.desktop/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/shikiCodeblocks.desktop/index.ts b/src/plugins/shikiCodeblocks.desktop/index.ts index 12c8c858..f358497d 100644 --- a/src/plugins/shikiCodeblocks.desktop/index.ts +++ b/src/plugins/shikiCodeblocks.desktop/index.ts @@ -67,7 +67,7 @@ export default definePlugin({ createHighlighter, renderHighlighter: ({ lang, content }: { lang: string; content: string; }) => { return createHighlighter({ - lang, + lang: lang?.toLowerCase(), content, isPreview: false, }); From 6578eb487ea16e04fdf704f19a57268aa8078f11 Mon Sep 17 00:00:00 2001 From: AutumnVN Date: Thu, 16 Nov 2023 01:13:19 +0700 Subject: [PATCH 0219/1143] MessageLogger: fix attachment ignore (#1989) --- src/plugins/messageLogger/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index 36f9ab3f..ac43a9f0 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -302,6 +302,7 @@ export default definePlugin({ match: /attachments:(\i)\((\i)\)/, replace: "attachments: $1((() => {" + + " if ($self.shouldIgnore($2)) return $2;" + " let old = arguments[1]?.attachments;" + " if (!old) return $2;" + " let new_ = $2.attachments?.map(a => a.id) ?? [];" + From 4a2657f928bdf88514eae707787f2b26d74da9db Mon Sep 17 00:00:00 2001 From: megumin Date: Wed, 15 Nov 2023 18:15:43 +0000 Subject: [PATCH 0220/1143] fix(channeltags): message author should be clyde (#644) (#1986) --- src/plugins/messageTags/index.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/plugins/messageTags/index.ts b/src/plugins/messageTags/index.ts index d65c388c..66de9d66 100644 --- a/src/plugins/messageTags/index.ts +++ b/src/plugins/messageTags/index.ts @@ -25,10 +25,6 @@ import definePlugin, { OptionType } from "@utils/types"; const EMOTE = "<:luna:1035316192220553236>"; const DATA_KEY = "MessageTags_TAGS"; const MessageTagsMarker = Symbol("MessageTags"); -const author = { - id: "821472922140803112", - bot: false -}; interface Tag { name: string; @@ -59,14 +55,12 @@ function createTagCommand(tag: Tag) { execute: async (_, ctx) => { if (!await getTag(tag.name)) { sendBotMessage(ctx.channel.id, { - author, content: `${EMOTE} The tag **${tag.name}** does not exist anymore! Please reload ur Discord to fix :)` }); return { content: `/${tag.name}` }; } if (Settings.plugins.MessageTags.clyde) sendBotMessage(ctx.channel.id, { - author, content: `${EMOTE} The tag **${tag.name}** has been sent!` }); return { content: tag.message.replaceAll("\\n", "\n") }; @@ -162,7 +156,6 @@ export default definePlugin({ if (await getTag(name)) return sendBotMessage(ctx.channel.id, { - author, content: `${EMOTE} A Tag with the name **${name}** already exists!` }); @@ -176,7 +169,6 @@ export default definePlugin({ await addTag(tag); sendBotMessage(ctx.channel.id, { - author, content: `${EMOTE} Successfully created the tag **${name}**!` }); break; // end 'create' @@ -186,7 +178,6 @@ export default definePlugin({ if (!await getTag(name)) return sendBotMessage(ctx.channel.id, { - author, content: `${EMOTE} A Tag with the name **${name}** does not exist!` }); @@ -194,14 +185,12 @@ export default definePlugin({ await removeTag(name); sendBotMessage(ctx.channel.id, { - author, content: `${EMOTE} Successfully deleted the tag **${name}**!` }); break; // end 'delete' } case "list": { sendBotMessage(ctx.channel.id, { - author, embeds: [ { // @ts-ignore @@ -224,12 +213,10 @@ export default definePlugin({ if (!tag) return sendBotMessage(ctx.channel.id, { - author, content: `${EMOTE} A Tag with the name **${name}** does not exist!` }); sendBotMessage(ctx.channel.id, { - author, content: tag.message.replaceAll("\\n", "\n") }); break; // end 'preview' @@ -237,7 +224,6 @@ export default definePlugin({ default: { sendBotMessage(ctx.channel.id, { - author, content: "Invalid sub-command" }); break; From 45aa9fbb6d1dadcc1746a5a197a6812b41e3215c Mon Sep 17 00:00:00 2001 From: Justice Almanzar Date: Wed, 15 Nov 2023 18:30:31 +0000 Subject: [PATCH 0221/1143] Fix hljs find (#1983) --- src/webpack/common/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 04df3c07..8376925e 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -31,7 +31,7 @@ waitFor(["ComponentDispatch", "ComponentDispatcher"], m => ComponentDispatch = m export const RestAPI: t.RestAPI = findByPropsLazy("getAPIBaseURL", "get"); export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYear"); -export const hljs: typeof import("highlight.js") = findByPropsLazy("highlight"); +export const hljs: typeof import("highlight.js") = findByPropsLazy("highlight", "registerLanguage"); export const lodash: typeof import("lodash") = findByPropsLazy("debounce", "cloneDeep"); From 77749ed5e1e913d0c5c8ce0995a9fdc12cde7ccb Mon Sep 17 00:00:00 2001 From: adryd Date: Wed, 15 Nov 2023 19:26:35 -0500 Subject: [PATCH 0222/1143] oneko: update script version (#1994) --- src/plugins/oneko/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/plugins/oneko/index.ts b/src/plugins/oneko/index.ts index d95ba2bc..c5de470e 100644 --- a/src/plugins/oneko/index.ts +++ b/src/plugins/oneko/index.ts @@ -26,15 +26,13 @@ export default definePlugin({ authors: [Devs.Ven, Devs.adryd], start() { - fetch("https://raw.githubusercontent.com/adryd325/oneko.js/5977144dce83e4d71af1de005d16e38eebeb7b72/oneko.js") + fetch("https://raw.githubusercontent.com/adryd325/oneko.js/8fa8a1864aa71cd7a794d58bc139e755e96a236c/oneko.js") .then(x => x.text()) .then(s => s.replace("./oneko.gif", "https://raw.githubusercontent.com/adryd325/oneko.js/14bab15a755d0e35cd4ae19c931d96d306f99f42/oneko.gif")) .then(eval); }, stop() { - clearInterval(window.onekoInterval); - delete window.onekoInterval; document.getElementById("oneko")?.remove(); } }); From 4a5371a746c03125eefb8d5a35a6037ac56c0439 Mon Sep 17 00:00:00 2001 From: V Date: Thu, 16 Nov 2023 05:40:50 +0100 Subject: [PATCH 0223/1143] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 177e65be..8611babd 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ The cutest Discord client mod -![image](https://github.com/Vendicated/Vencord/assets/45497981/706722b1-32de-4d99-bee9-93993b504334) +| ![image](https://github.com/Vendicated/Vencord/assets/45497981/706722b1-32de-4d99-bee9-93993b504334) | +|:--:| +| A screenshot of vencord showcasing the [vencord-theme](https://github.com/synqat/vencord-theme) | ## Features From 9980c0d04f7fe5ac8002988b4ae29edb1b514b32 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sat, 18 Nov 2023 22:53:50 -0300 Subject: [PATCH 0224/1143] IgnoreActivities: fix and improvements --- src/plugins/ignoreActivities/index.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/plugins/ignoreActivities/index.tsx b/src/plugins/ignoreActivities/index.tsx index 0b42a8b0..4e747f36 100644 --- a/src/plugins/ignoreActivities/index.tsx +++ b/src/plugins/ignoreActivities/index.tsx @@ -8,7 +8,6 @@ import * as DataStore from "@api/DataStore"; import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; -import { useForceUpdater } from "@utils/react"; import definePlugin from "@utils/types"; import { findStoreLazy } from "@webpack"; import { StatusSettingsStores, Tooltip } from "webpack/common"; @@ -27,14 +26,12 @@ interface IgnoredActivity { const RunningGameStore = findStoreLazy("RunningGameStore"); function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) { - const forceUpdate = useForceUpdater(); - return ( {tooltipProps => ( + ) + } +}); + +export default definePlugin({ + name: "ClientTheme", + authors: [Devs.F53], + description: "Recreation of the old client theme experiment. Add a color to your Discord client theme", + settings, + + patches: [ + { + find: "Could not find app-mount", + replacement: { + match: /(?<=Could not find app-mount"\))/, + replace: ",$self.addThemeInitializer()" + } + } + ], + + addThemeInitializer() { + document.addEventListener("DOMContentLoaded", this.themeInitializer = () => { + updateColorVars(settings.store.color); + generateColorOffsets(); + }); + }, + + stop() { + document.removeEventListener("DOMContentLoaded", this.themeInitializer); + document.getElementById("clientThemeVars")?.remove(); + document.getElementById("clientThemeOffsets")?.remove(); + } +}); + +async function generateColorOffsets() { + const variableRegex = /(--primary-[5-9]\d{2}-hsl):.*?(\S*)%;/g; + + const styleLinkNodes = document.querySelectorAll('link[rel="stylesheet"]'); + const variableLightness = {} as Record; + + // Search all stylesheets for color variables + for (const styleLinkNode of styleLinkNodes) { + const cssLink = styleLinkNode.getAttribute("href"); + if (!cssLink) continue; + + const res = await fetch(cssLink); + const cssString = await res.text(); + + // Get lightness values of --primary variables >=500 + let variableMatch = variableRegex.exec(cssString); + while (variableMatch !== null) { + const [, variable, lightness] = variableMatch; + variableLightness[variable] = parseFloat(lightness); + variableMatch = variableRegex.exec(cssString); + } + } + + // Generate offsets + const lightnessOffsets = Object.entries(variableLightness) + .map(([key, lightness]) => { + const lightnessOffset = lightness - variableLightness["--primary-600-hsl"]; + const plusOrMinus = lightnessOffset >= 0 ? "+" : "-"; + return `${key}: var(--theme-h) var(--theme-s) calc(var(--theme-l) ${plusOrMinus} ${Math.abs(lightnessOffset).toFixed(2)}%);`; + }) + .join("\n"); + + const style = document.createElement("style"); + style.setAttribute("id", "clientThemeOffsets"); + style.textContent = `:root:root { + ${lightnessOffsets} + }`; + document.head.appendChild(style); +} + +function updateColorVars(color: string) { + const { hue, saturation, lightness } = hexToHSL(color); + + let style = document.getElementById("clientThemeVars"); + if (!style) { + style = document.createElement("style"); + style.setAttribute("id", "clientThemeVars"); + document.head.appendChild(style); + } + + style.textContent = `:root { + --theme-h: ${hue}; + --theme-s: ${saturation}%; + --theme-l: ${lightness}%; + }`; +} + +// https://css-tricks.com/converting-color-spaces-in-javascript/ +function hexToHSL(hexCode: string) { + // Hex => RGB normalized to 0-1 + const r = parseInt(hexCode.substring(0, 2), 16) / 255; + const g = parseInt(hexCode.substring(2, 4), 16) / 255; + const b = parseInt(hexCode.substring(4, 6), 16) / 255; + + // RGB => HSL + const cMax = Math.max(r, g, b); + const cMin = Math.min(r, g, b); + const delta = cMax - cMin; + + let hue: number, saturation: number, lightness: number; + + lightness = (cMax + cMin) / 2; + + if (delta === 0) { + // If r=g=b then the only thing that matters is lightness + hue = 0; + saturation = 0; + } else { + // Magic + saturation = delta / (1 - Math.abs(2 * lightness - 1)); + + if (cMax === r) + hue = ((g - b) / delta) % 6; + else if (cMax === g) + hue = (b - r) / delta + 2; + else + hue = (r - g) / delta + 4; + hue *= 60; + if (hue < 0) + hue += 360; + } + + // Move saturation and lightness from 0-1 to 0-100 + saturation *= 100; + lightness *= 100; + + return { hue, saturation, lightness }; +} + +// Minimized math just for lightness, lowers lag when changing colors +function hexToLightness(hexCode) { + // Hex => RGB normalized to 0-1 + const r = parseInt(hexCode.substring(0, 2), 16) / 255; + const g = parseInt(hexCode.substring(2, 4), 16) / 255; + const b = parseInt(hexCode.substring(4, 6), 16) / 255; + + const cMax = Math.max(r, g, b); + const cMin = Math.min(r, g, b); + + const lightness = 100 * ((cMax + cMin) / 2); + + return lightness; +} diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 3db2e64f..7f555d32 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -267,6 +267,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "Dziurwa", id: 1001086404203389018n }, + F53: { + name: "F53", + id: 280411966126948353n + }, AutumnVN: { name: "AutumnVN", id: 393694671383166998n From 074ebae3343170c19ca128ed76b0732375ea36bf Mon Sep 17 00:00:00 2001 From: V Date: Wed, 22 Nov 2023 01:57:00 +0100 Subject: [PATCH 0229/1143] ClientTheme fixes --- src/plugins/clientTheme/README.md | 7 +++++++ src/plugins/clientTheme/index.tsx | 21 ++++++++++----------- 2 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 src/plugins/clientTheme/README.md diff --git a/src/plugins/clientTheme/README.md b/src/plugins/clientTheme/README.md new file mode 100644 index 00000000..4b40148c --- /dev/null +++ b/src/plugins/clientTheme/README.md @@ -0,0 +1,7 @@ +# Classic Client Theme + +Revival of the old client theme experiment (The one that came before the sucky one that we actually got) + +![the ClientTheme theme colour picker](https://user-images.githubusercontent.com/37855219/230238053-e90b7098-373a-459a-bb8c-c24e82f69270.png) + +https://github.com/Vendicated/Vencord/assets/45497981/6c1bcb3b-e0c7-4a02-b0b8-c4c5cd954f38 diff --git a/src/plugins/clientTheme/index.tsx b/src/plugins/clientTheme/index.tsx index 3e07b15f..d0026c75 100644 --- a/src/plugins/clientTheme/index.tsx +++ b/src/plugins/clientTheme/index.tsx @@ -11,11 +11,12 @@ import { Devs } from "@utils/constants"; import { getTheme, Theme } from "@utils/discord"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; +import { LazyComponent } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { findByCodeLazy } from "@webpack"; +import { findByCode } from "@webpack"; import { Button, Forms } from "@webpack/common"; -const ColorPicker = findByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR"); +const ColorPicker = LazyComponent(() => findByCode(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR")); const colorPresets = [ "#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D", @@ -24,11 +25,7 @@ const colorPresets = [ ]; function onPickColor(color: number) { - let hexColor = color.toString(16); - - while (hexColor.length < 6) { - hexColor = "0" + hexColor; - } + const hexColor = color.toString(16).padStart(6, "0"); settings.store.color = hexColor; updateColorVars(hexColor); @@ -59,7 +56,8 @@ function ThemeSettings() { {lightnessWarning && Selected color is very light} {lightModeWarning && Light mode isn't supported}
- : null} + : null + } ); } @@ -85,7 +83,7 @@ const settings = definePluginSettings({ export default definePlugin({ name: "ClientTheme", - authors: [Devs.F53], + authors: [Devs.F53, Devs.Nuckyz], description: "Recreation of the old client theme experiment. Add a color to your Discord client theme", settings, @@ -113,8 +111,9 @@ export default definePlugin({ } }); +const variableRegex = /(--primary-[5-9]\d{2}-hsl):.*?(\S*)%;/g; + async function generateColorOffsets() { - const variableRegex = /(--primary-[5-9]\d{2}-hsl):.*?(\S*)%;/g; const styleLinkNodes = document.querySelectorAll('link[rel="stylesheet"]'); const variableLightness = {} as Record; @@ -213,7 +212,7 @@ function hexToHSL(hexCode: string) { } // Minimized math just for lightness, lowers lag when changing colors -function hexToLightness(hexCode) { +function hexToLightness(hexCode: string) { // Hex => RGB normalized to 0-1 const r = parseInt(hexCode.substring(0, 2), 16) / 255; const g = parseInt(hexCode.substring(2, 4), 16) / 255; From 371b5b0be8a6a6fab64ecb2d03c760f85820a063 Mon Sep 17 00:00:00 2001 From: V Date: Wed, 22 Nov 2023 06:14:16 +0100 Subject: [PATCH 0230/1143] allow plugins to specify how soon their start() method is called --- src/Vencord.ts | 16 +++++++++++----- src/plugins/clientTheme/index.tsx | 22 +++++----------------- src/plugins/index.ts | 10 ++++++++-- src/utils/types.ts | 14 ++++++++++++++ 4 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/Vencord.ts b/src/Vencord.ts index 83c69e73..a106a0b7 100644 --- a/src/Vencord.ts +++ b/src/Vencord.ts @@ -27,6 +27,8 @@ export { PlainSettings, Settings }; import "./utils/quickCss"; import "./webpack/patchWebpack"; +import { StartAt } from "@utils/types"; + import { get as dsGet } from "./api/DataStore"; import { showNotification } from "./api/Notifications"; import { PlainSettings, Settings } from "./api/Settings"; @@ -79,7 +81,7 @@ async function syncSettings() { async function init() { await onceReady; - startAllPlugins(); + startAllPlugins(StartAt.WebpackReady); syncSettings(); @@ -130,13 +132,17 @@ async function init() { } } +startAllPlugins(StartAt.Init); init(); -if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && navigator.platform.toLowerCase().startsWith("win")) { - document.addEventListener("DOMContentLoaded", () => { +document.addEventListener("DOMContentLoaded", () => { + startAllPlugins(StartAt.DOMContentLoaded); + + if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && navigator.platform.toLowerCase().startsWith("win")) { document.head.append(Object.assign(document.createElement("style"), { id: "vencord-native-titlebar-style", textContent: "[class*=titleBar]{display: none!important}" })); - }, { once: true }); -} + } +}, { once: true }); + diff --git a/src/plugins/clientTheme/index.tsx b/src/plugins/clientTheme/index.tsx index d0026c75..7cda33e2 100644 --- a/src/plugins/clientTheme/index.tsx +++ b/src/plugins/clientTheme/index.tsx @@ -12,7 +12,7 @@ import { getTheme, Theme } from "@utils/discord"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { LazyComponent } from "@utils/react"; -import definePlugin, { OptionType } from "@utils/types"; +import definePlugin, { OptionType, StartAt } from "@utils/types"; import { findByCode } from "@webpack"; import { Button, Forms } from "@webpack/common"; @@ -87,25 +87,13 @@ export default definePlugin({ description: "Recreation of the old client theme experiment. Add a color to your Discord client theme", settings, - patches: [ - { - find: "Could not find app-mount", - replacement: { - match: /(?<=Could not find app-mount"\))/, - replace: ",$self.addThemeInitializer()" - } - } - ], - - addThemeInitializer() { - document.addEventListener("DOMContentLoaded", this.themeInitializer = () => { - updateColorVars(settings.store.color); - generateColorOffsets(); - }); + startAt: StartAt.DOMContentLoaded, + start() { + updateColorVars(settings.store.color); + generateColorOffsets(); }, stop() { - document.removeEventListener("DOMContentLoaded", this.themeInitializer); document.getElementById("clientThemeVars")?.remove(); document.getElementById("clientThemeOffsets")?.remove(); } diff --git a/src/plugins/index.ts b/src/plugins/index.ts index f6d57726..23483860 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -19,7 +19,7 @@ import { registerCommand, unregisterCommand } from "@api/Commands"; import { Settings } from "@api/Settings"; import { Logger } from "@utils/Logger"; -import { Patch, Plugin } from "@utils/types"; +import { Patch, Plugin, StartAt } from "@utils/types"; import { FluxDispatcher } from "@webpack/common"; import { FluxEvents } from "@webpack/types"; @@ -85,9 +85,15 @@ for (const p of pluginsValues) { } } -export const startAllPlugins = traceFunction("startAllPlugins", function startAllPlugins() { +export const startAllPlugins = traceFunction("startAllPlugins", function startAllPlugins(target: StartAt) { + logger.info(`Starting plugins (stage ${target})`); for (const name in Plugins) if (isPluginEnabled(name)) { + const p = Plugins[name]; + + const startAt = p.startAt ?? StartAt.WebpackReady; + if (startAt !== target) continue; + startPlugin(Plugins[name]); } }); diff --git a/src/utils/types.ts b/src/utils/types.ts index b32b127b..7305cd01 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -80,6 +80,11 @@ export interface PluginDef { * Whether this plugin should be enabled by default, but can be disabled */ enabledByDefault?: boolean; + /** + * When to call the start() method + * @default StartAt.WebpackReady + */ + startAt?: StartAt, /** * Optionally provide settings that the user can configure in the Plugins tab of settings. * @deprecated Use `settings` instead @@ -117,6 +122,15 @@ export interface PluginDef { tags?: string[]; } +export const enum StartAt { + /** Right away, as soon as Vencord initialised */ + Init = "Init", + /** On the DOMContentLoaded event, so once the document is ready */ + DOMContentLoaded = "DOMContentLoaded", + /** Once Discord's core webpack modules have finished loading, so as soon as things like react and flux are available */ + WebpackReady = "WebpackReady" +} + export const enum OptionType { STRING, NUMBER, From ffe6512693c7828c1d3c3bb36db69d69a93abe3f Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 22 Nov 2023 02:49:08 -0300 Subject: [PATCH 0231/1143] Improve component finding api and migrate plugins to use them --- src/components/PluginSettings/PluginModal.tsx | 6 +- src/plugins/betterFolders/FolderSideBar.tsx | 6 +- src/plugins/clientTheme/index.tsx | 5 +- src/plugins/customRPC/index.tsx | 6 +- src/plugins/gameActivityToggle/index.tsx | 4 +- src/plugins/messageLinkEmbeds/index.tsx | 10 +-- .../serverProfile/GuildProfileModal.tsx | 6 +- src/plugins/showConnections/VerifiedIcon.tsx | 6 +- src/plugins/showConnections/index.tsx | 6 +- .../components/HiddenChannelLockScreen.tsx | 10 +-- src/plugins/voiceMessages/VoicePreview.tsx | 5 +- src/plugins/whoReacted/index.tsx | 6 +- src/utils/react.tsx | 68 ++++++++++++++----- src/webpack/webpack.ts | 2 +- 14 files changed, 89 insertions(+), 57 deletions(-) diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx index 03789ac6..7d5750df 100644 --- a/src/components/PluginSettings/PluginModal.tsx +++ b/src/components/PluginSettings/PluginModal.tsx @@ -24,9 +24,9 @@ import { proxyLazy } from "@utils/lazy"; import { Margins } from "@utils/margins"; import { classes, isObjectEmpty } from "@utils/misc"; import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal"; -import { LazyComponent } from "@utils/react"; +import { findComponentByCodeLazy } from "@utils/react"; import { OptionType, Plugin } from "@utils/types"; -import { findByCode, findByPropsLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common"; import { User } from "discord-types/general"; import { Constructor } from "type-fest"; @@ -42,7 +42,7 @@ import { } from "./components"; import { openContributorModal } from "./ContributorModal"; -const UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers")); +const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); const UserRecord: Constructor> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any; diff --git a/src/plugins/betterFolders/FolderSideBar.tsx b/src/plugins/betterFolders/FolderSideBar.tsx index 97959873..13dd9ac8 100644 --- a/src/plugins/betterFolders/FolderSideBar.tsx +++ b/src/plugins/betterFolders/FolderSideBar.tsx @@ -17,8 +17,8 @@ */ import ErrorBoundary from "@components/ErrorBoundary"; -import { LazyComponent } from "@utils/react"; -import { find, findByPropsLazy, findStoreLazy } from "@webpack"; +import { findComponentByCodeLazy } from "@utils/react"; +import { findByPropsLazy, findStoreLazy } from "@webpack"; import { useStateFromStores } from "@webpack/common"; import type { CSSProperties } from "react"; @@ -26,7 +26,7 @@ import { ExpandedGuildFolderStore, settings } from "."; const ChannelRTCStore = findStoreLazy("ChannelRTCStore"); const Animations = findByPropsLazy("a", "animated", "useTransition"); -const GuildsBar = LazyComponent(() => find(m => m.type?.toString().includes('("guildsnav")'))); +const GuildsBar = findComponentByCodeLazy('("guildsnav")'); export default ErrorBoundary.wrap(guildsBarProps => { const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders()); diff --git a/src/plugins/clientTheme/index.tsx b/src/plugins/clientTheme/index.tsx index 7cda33e2..e5de67e4 100644 --- a/src/plugins/clientTheme/index.tsx +++ b/src/plugins/clientTheme/index.tsx @@ -11,12 +11,11 @@ import { Devs } from "@utils/constants"; import { getTheme, Theme } from "@utils/discord"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; -import { LazyComponent } from "@utils/react"; +import { findComponentByCodeLazy } from "@utils/react"; import definePlugin, { OptionType, StartAt } from "@utils/types"; -import { findByCode } from "@webpack"; import { Button, Forms } from "@webpack/common"; -const ColorPicker = LazyComponent(() => findByCode(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR")); +const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR"); const colorPresets = [ "#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D", diff --git a/src/plugins/customRPC/index.tsx b/src/plugins/customRPC/index.tsx index feed52fd..64bfd460 100644 --- a/src/plugins/customRPC/index.tsx +++ b/src/plugins/customRPC/index.tsx @@ -20,12 +20,12 @@ import { definePluginSettings, Settings } from "@api/Settings"; import { Link } from "@components/Link"; import { Devs } from "@utils/constants"; import { isTruthy } from "@utils/guards"; -import { useAwaiter } from "@utils/react"; +import { findComponentByCodeLazy, useAwaiter } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { findByCodeLazy, findByPropsLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { ApplicationAssetUtils, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common"; -const ActivityComponent = findByCodeLazy("onOpenGameProfile"); +const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile"); const ActivityClassName = findByPropsLazy("activity", "buttonColor"); const Colors = findByPropsLazy("profileColors"); diff --git a/src/plugins/gameActivityToggle/index.tsx b/src/plugins/gameActivityToggle/index.tsx index 735f124c..cc68d55b 100644 --- a/src/plugins/gameActivityToggle/index.tsx +++ b/src/plugins/gameActivityToggle/index.tsx @@ -19,13 +19,13 @@ import { disableStyle, enableStyle } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; +import { findComponentByCodeLazy } from "@utils/react"; import definePlugin from "@utils/types"; -import { findByCodeLazy } from "@webpack"; import { StatusSettingsStores } from "@webpack/common"; import style from "./style.css?managed"; -const Button = findByCodeLazy("Button.Sizes.NONE,disabled:"); +const Button = findComponentByCodeLazy("Button.Sizes.NONE,disabled:"); function makeIcon(showCurrentGame?: boolean) { return function () { diff --git a/src/plugins/messageLinkEmbeds/index.tsx b/src/plugins/messageLinkEmbeds/index.tsx index e600d737..d8c3c99f 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -22,9 +22,9 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants.js"; import { classes } from "@utils/misc"; import { Queue } from "@utils/Queue"; -import { LazyComponent } from "@utils/react"; +import { findComponentByCodeLazy } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { find, findByCode, findByPropsLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { Button, ChannelStore, @@ -45,9 +45,9 @@ const messageCache = new Map(); -const Embed = LazyComponent(() => findByCode(".inlineMediaEmbed")); -const AutoModEmbed = LazyComponent(() => findByCode(".withFooter]:", "childrenMessageContent:")); -const ChannelMessage = LazyComponent(() => find(m => m.type?.toString()?.includes("renderSimpleAccessories)"))); +const Embed = findComponentByCodeLazy(".inlineMediaEmbed"); +const AutoModEmbed = findComponentByCodeLazy(".withFooter]:", "childrenMessageContent:"); +const ChannelMessage = findComponentByCodeLazy("renderSimpleAccessories)"); const SearchResultClasses = findByPropsLazy("message", "searchResult"); diff --git a/src/plugins/serverProfile/GuildProfileModal.tsx b/src/plugins/serverProfile/GuildProfileModal.tsx index 80f0842b..9def7bf9 100644 --- a/src/plugins/serverProfile/GuildProfileModal.tsx +++ b/src/plugins/serverProfile/GuildProfileModal.tsx @@ -10,14 +10,14 @@ 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 { findByProps, findByPropsLazy } from "@webpack"; +import { findExportedComponentLazy, useAwaiter } from "@utils/react"; +import { 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 FriendRow = LazyComponent(() => findByProps("FriendRow").FriendRow); +const FriendRow = findExportedComponentLazy("FriendRow"); const cl = classNameFactory("vc-gp-"); diff --git a/src/plugins/showConnections/VerifiedIcon.tsx b/src/plugins/showConnections/VerifiedIcon.tsx index 79e27c45..20005069 100644 --- a/src/plugins/showConnections/VerifiedIcon.tsx +++ b/src/plugins/showConnections/VerifiedIcon.tsx @@ -16,12 +16,12 @@ * along with this program. If not, see . */ -import { LazyComponent } from "@utils/react"; -import { findByCode, findLazy } from "@webpack"; +import { findComponentByCodeLazy } from "@utils/react"; +import { findLazy } from "@webpack"; import { i18n, useToken } from "@webpack/common"; const ColorMap = findLazy(m => m.colors?.INTERACTIVE_MUTED?.css); -const VerifiedIconComponent = LazyComponent(() => findByCode(".CONNECTIONS_ROLE_OFFICIAL_ICON_TOOLTIP")); +const VerifiedIconComponent = findComponentByCodeLazy(".CONNECTIONS_ROLE_OFFICIAL_ICON_TOOLTIP"); export function VerifiedIcon() { const color = useToken(ColorMap.colors.INTERACTIVE_MUTED).hex(); diff --git a/src/plugins/showConnections/index.tsx b/src/plugins/showConnections/index.tsx index 948bdb83..2d2122d6 100644 --- a/src/plugins/showConnections/index.tsx +++ b/src/plugins/showConnections/index.tsx @@ -24,15 +24,15 @@ import { Flex } from "@components/Flex"; import { CopyIcon, LinkIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { copyWithToast } from "@utils/misc"; -import { LazyComponent } from "@utils/react"; +import { findComponentByCodeLazy } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { findByCode, findByCodeLazy, findByPropsLazy, findStoreLazy } from "@webpack"; +import { findByCodeLazy, findByPropsLazy, findStoreLazy } from "@webpack"; import { Text, Tooltip, UserProfileStore } from "@webpack/common"; import { User } from "discord-types/general"; import { VerifiedIcon } from "./VerifiedIcon"; -const Section = LazyComponent(() => findByCode(".lastSection]:")); +const Section = findComponentByCodeLazy(".lastSection]: "); const ThemeStore = findStoreLazy("ThemeStore"); const platforms: { get(type: string): ConnectionPlatform; } = findByPropsLazy("isSupported", "getByUrl"); const getTheme: (user: User, displayProfile: any) => any = findByCodeLazy(',"--profile-gradient-primary-color"'); diff --git a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx index 26efce1d..5231fe8e 100644 --- a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx +++ b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx @@ -18,9 +18,9 @@ import { Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; -import { LazyComponent } from "@utils/react"; +import { findComponentByCodeLazy, findComponentLazy } from "@utils/react"; import { formatDuration } from "@utils/text"; -import { find, findByCode, findByPropsLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common"; import type { Channel } from "discord-types/general"; @@ -81,14 +81,14 @@ const enum ChannelFlags { const ChatScrollClasses = findByPropsLazy("auto", "content", "scrollerBase"); const ChatClasses = findByPropsLazy("chat", "content", "noChat", "chatContent"); -const ChannelBeginHeader = LazyComponent(() => findByCode(".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE")); -const TagComponent = LazyComponent(() => find(m => { +const ChannelBeginHeader = findComponentByCodeLazy(".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE"); +const TagComponent = findComponentLazy(m => { if (typeof m !== "function") return false; const code = Function.prototype.toString.call(m); // Get the component which doesn't include increasedActivity return code.includes(".Messages.FORUM_TAG_A11Y_FILTER_BY_TAG") && !code.includes("increasedActivityPill"); -})); +}); const EmojiParser = findByPropsLazy("convertSurrogateToName"); const EmojiUtils = findByPropsLazy("getURL", "buildEmojiReactionColorsPlatformed"); diff --git a/src/plugins/voiceMessages/VoicePreview.tsx b/src/plugins/voiceMessages/VoicePreview.tsx index 912c55ae..d2c62336 100644 --- a/src/plugins/voiceMessages/VoicePreview.tsx +++ b/src/plugins/voiceMessages/VoicePreview.tsx @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -import { LazyComponent, useTimer } from "@utils/react"; -import { find } from "@webpack"; +import { findComponentByCodeLazy, useTimer } from "@utils/react"; import { cl } from "./utils"; @@ -25,7 +24,7 @@ interface VoiceMessageProps { src: string; waveform: string; } -const VoiceMessage = LazyComponent(() => find(m => m.type?.toString().includes("waveform:"))); +const VoiceMessage = findComponentByCodeLazy("waveform:"); export type VoicePreviewOptions = { src?: string; diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx index fb84c155..15395a1f 100644 --- a/src/plugins/whoReacted/index.tsx +++ b/src/plugins/whoReacted/index.tsx @@ -20,14 +20,14 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { sleep } from "@utils/misc"; import { Queue } from "@utils/Queue"; -import { LazyComponent, useForceUpdater } from "@utils/react"; +import { findComponentByCodeLazy, useForceUpdater } from "@utils/react"; import definePlugin from "@utils/types"; -import { findByCode, findByPropsLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { ChannelStore, FluxDispatcher, React, RestAPI, Tooltip } from "@webpack/common"; import { CustomEmoji } from "@webpack/types"; import { Message, ReactionEmoji, User } from "discord-types/general"; -const UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers")); +const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); const queue = new Queue(); diff --git a/src/utils/react.tsx b/src/utils/react.tsx index 0181c95b..2e3352bc 100644 --- a/src/utils/react.tsx +++ b/src/utils/react.tsx @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import { FilterFn, filters, find, findByProps } from "@webpack"; import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common"; import { makeLazy } from "./lazy"; @@ -77,7 +78,6 @@ interface AwaiterOpts { * @param fallbackValue The fallback value that will be used until the promise resolved * @returns [value, error, isPending] */ - export function useAwaiter(factory: () => Promise): AwaiterRes; export function useAwaiter(factory: () => Promise, providedOpts: AwaiterOpts): AwaiterRes; export function useAwaiter(factory: () => Promise, providedOpts?: AwaiterOpts): AwaiterRes { @@ -113,31 +113,16 @@ export function useAwaiter(factory: () => Promise, providedOpts?: AwaiterO return [state.value, state.error, state.pending]; } + /** * Returns a function that can be used to force rerender react components */ - export function useForceUpdater(): () => void; export function useForceUpdater(withDep: true): [unknown, () => void]; export function useForceUpdater(withDep?: true) { const r = useReducer(x => x + 1, 0); return withDep ? r : r[1]; } -/** - * A lazy component. The factory method is called on first render. For example useful - * for const Component = LazyComponent(() => findByDisplayName("...").default) - * @param factory Function returning a Component - * @param attempts How many times to try to get the component before giving up - * @returns Result of factory function - */ - -export function LazyComponent(factory: () => React.ComponentType, attempts = 5) { - const get = makeLazy(factory, attempts); - return (props: T) => { - const Component = get() ?? NoopComponent; - return ; - }; -} interface TimerOpts { interval?: number; @@ -159,3 +144,52 @@ export function useTimer({ interval = 1000, deps = [] }: TimerOpts) { return time; } + +/** + * Finds the component which includes all the given code. Checks for plain components, memos and forwardRefs + */ +export function findComponentByCode(...code: string[]) { + const filter = filters.byCode(...code); + return find(m => { + if (filter(m)) return true; + if (!m.$$typeof) return false; + if (m.type) return filter(m.type); // memos + if (m.render) return filter(m.render); // forwardRefs + return false; + }) ?? NoopComponent; +} + +/** + * Finds the first component that matches the filter, lazily. + */ +export function findComponentLazy(filter: FilterFn) { + return LazyComponent(() => find(filter)); +} + +/** + * Finds the first component that includes all the given code, lazily + */ +export function findComponentByCodeLazy(...code: string[]) { + return LazyComponent(() => findComponentByCode(...code)); +} + +/** + * Finds the first component that is exported by the first prop name, lazily + */ +export function findExportedComponentLazy(...props: string[]) { + return LazyComponent(() => findByProps(...props)?.[props[0]]); +} + +/** + * A lazy component. The factory method is called on first render. + * @param factory Function returning a Component + * @param attempts How many times to try to get the component before giving up + * @returns Result of factory function + */ +export function LazyComponent(factory: () => React.ComponentType, attempts = 5) { + const get = makeLazy(factory, attempts); + return (props: T) => { + const Component = get() ?? NoopComponent; + return ; + }; +} diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 3ef5ac80..4fb94056 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -386,7 +386,7 @@ export function findStore(name: string) { } /** - * findByDisplayName but lazy + * findStore but lazy */ export function findStoreLazy(name: string) { return proxyLazy(() => findStore(name)); From b21b6d7e5dae67537864274bd9e9f58c1f827b8f Mon Sep 17 00:00:00 2001 From: V Date: Wed, 22 Nov 2023 06:48:59 +0100 Subject: [PATCH 0232/1143] Make it possible to destructure lazy webpack finds --- src/plugins/betterFolders/index.tsx | 5 ++--- src/plugins/greetStickerPicker/index.tsx | 5 ++--- src/utils/lazy.ts | 25 +++++++++++++++++++++--- src/webpack/common/stores.ts | 19 +++++++++--------- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index 8f40d90f..68c50bcf 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -18,9 +18,8 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; -import { proxyLazy } from "@utils/lazy"; import definePlugin, { OptionType } from "@utils/types"; -import { findByProps, findByPropsLazy, findStoreLazy } from "@webpack"; +import { findByPropsLazy, findStoreLazy } from "@webpack"; import { FluxDispatcher, i18n } from "@webpack/common"; import FolderSideBar from "./FolderSideBar"; @@ -31,7 +30,7 @@ enum FolderIconDisplay { MoreThanOneFolderExpanded } -const GuildsTree = proxyLazy(() => findByProps("GuildsTree").GuildsTree); +const { GuildsTree } = findByPropsLazy("GuildsTree"); const SortedGuildStore = findStoreLazy("SortedGuildStore"); export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore"); const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand"); diff --git a/src/plugins/greetStickerPicker/index.tsx b/src/plugins/greetStickerPicker/index.tsx index 2c8f3379..845c7a1b 100644 --- a/src/plugins/greetStickerPicker/index.tsx +++ b/src/plugins/greetStickerPicker/index.tsx @@ -18,9 +18,8 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; -import { proxyLazy } from "@utils/lazy"; import definePlugin, { OptionType } from "@utils/types"; -import { findByProps, findByPropsLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { ContextMenu, FluxDispatcher, Menu } from "@webpack/common"; import { Channel, Message } from "discord-types/general"; @@ -51,7 +50,7 @@ const settings = definePluginSettings({ }>(); const MessageActions = findByPropsLazy("sendGreetMessage"); -const WELCOME_STICKERS = proxyLazy(() => findByProps("WELCOME_STICKERS")?.WELCOME_STICKERS); +const { WELCOME_STICKERS } = findByPropsLazy("WELCOME_STICKERS"); function greet(channel: Channel, message: Message, stickers: string[]) { const options = MessageActions.getSendMessageOptionsForReply({ diff --git a/src/utils/lazy.ts b/src/utils/lazy.ts index 4bac45bc..1c89d511 100644 --- a/src/utils/lazy.ts +++ b/src/utils/lazy.ts @@ -43,7 +43,6 @@ for (const method of [ "construct", "defineProperty", "deleteProperty", - "get", "getOwnPropertyDescriptor", "getPrototypeOf", "has", @@ -86,7 +85,11 @@ handler.getOwnPropertyDescriptor = (target, p) => { * Note that the example below exists already as an api, see {@link findByPropsLazy} * @example const mod = proxyLazy(() => findByProps("blah")); console.log(mod.blah); */ -export function proxyLazy(factory: () => T, attempts = 5): T { +export function proxyLazy(factory: () => T, attempts = 5, isChild = false): T { + let isSameTick = true; + if (!isChild) + setTimeout(() => isSameTick = false, 0); + let tries = 0; const proxyDummy = Object.assign(function () { }, { [kCACHE]: void 0 as T | undefined, @@ -100,5 +103,21 @@ export function proxyLazy(factory: () => T, attempts = 5): T { } }); - return new Proxy(proxyDummy, handler) as any; + return new Proxy(proxyDummy, { + ...handler, + get(target, p, receiver) { + // if we're still in the same tick, it means the lazy was immediately used. + // thus, we lazy proxy the get access to make things like destructuring work as expected + // meow here will also be a lazy + // `const { meow } = findByPropsLazy("meow");` + if (!isChild && isSameTick) + return proxyLazy( + () => Reflect.get(target[kGET](), p, receiver), + attempts, + true + ); + + return Reflect.get(target[kGET](), p, receiver); + } + }) as any; } diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index d10b1866..1b00180f 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -16,11 +16,10 @@ * along with this program. If not, see . */ -import { proxyLazy } from "@utils/lazy"; import type * as Stores from "discord-types/stores"; // eslint-disable-next-line path-alias/no-relative -import { filters, findByProps, findByPropsLazy, mapMangledModuleLazy } from "../webpack"; +import { filters, findByPropsLazy, mapMangledModuleLazy } from "../webpack"; import { waitForStore } from "./internal"; import * as t from "./types/stores"; @@ -78,13 +77,15 @@ export const MaskedLinkStore = mapMangledModuleLazy('"MaskedLinkStore"', { * * @example const user = useStateFromStores([UserStore], () => UserStore.getCurrentUser(), null, (old, current) => old.id === current.id); */ -export const useStateFromStores: ( - stores: t.FluxStore[], - mapper: () => T, - idk?: any, - isEqual?: (old: T, newer: T) => boolean -) => T - = proxyLazy(() => findByProps("useStateFromStores").useStateFromStores); +export const { useStateFromStores }: { + useStateFromStores: ( + stores: t.FluxStore[], + mapper: () => T, + idk?: any, + isEqual?: (old: T, newer: T) => boolean + ) => T; +} + = findByPropsLazy("useStateFromStores"); waitForStore("DraftStore", s => DraftStore = s); waitForStore("UserStore", s => UserStore = s); From 7b24c8ac69b194783caaeb79ea04668554c4bec4 Mon Sep 17 00:00:00 2001 From: V Date: Wed, 22 Nov 2023 07:04:17 +0100 Subject: [PATCH 0233/1143] move new webpack methods to more appropriate file --- src/components/PluginSettings/PluginModal.tsx | 3 +- src/plugins/betterFolders/FolderSideBar.tsx | 3 +- src/plugins/clientTheme/index.tsx | 2 +- src/plugins/customRPC/index.tsx | 4 +-- src/plugins/gameActivityToggle/index.tsx | 2 +- src/plugins/messageLinkEmbeds/index.tsx | 3 +- .../serverProfile/GuildProfileModal.tsx | 4 +-- src/plugins/showConnections/VerifiedIcon.tsx | 3 +- src/plugins/showConnections/index.tsx | 3 +- .../components/HiddenChannelLockScreen.tsx | 3 +- src/plugins/voiceMessages/VoicePreview.tsx | 3 +- src/plugins/whoReacted/index.tsx | 4 +-- src/utils/react.tsx | 36 ------------------- src/webpack/webpack.ts | 36 +++++++++++++++++++ 14 files changed, 52 insertions(+), 57 deletions(-) diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx index 7d5750df..34de43c2 100644 --- a/src/components/PluginSettings/PluginModal.tsx +++ b/src/components/PluginSettings/PluginModal.tsx @@ -24,9 +24,8 @@ import { proxyLazy } from "@utils/lazy"; import { Margins } from "@utils/margins"; import { classes, isObjectEmpty } from "@utils/misc"; import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal"; -import { findComponentByCodeLazy } from "@utils/react"; import { OptionType, Plugin } from "@utils/types"; -import { findByPropsLazy } from "@webpack"; +import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common"; import { User } from "discord-types/general"; import { Constructor } from "type-fest"; diff --git a/src/plugins/betterFolders/FolderSideBar.tsx b/src/plugins/betterFolders/FolderSideBar.tsx index 13dd9ac8..53d24ed9 100644 --- a/src/plugins/betterFolders/FolderSideBar.tsx +++ b/src/plugins/betterFolders/FolderSideBar.tsx @@ -17,8 +17,7 @@ */ import ErrorBoundary from "@components/ErrorBoundary"; -import { findComponentByCodeLazy } from "@utils/react"; -import { findByPropsLazy, findStoreLazy } from "@webpack"; +import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; import { useStateFromStores } from "@webpack/common"; import type { CSSProperties } from "react"; diff --git a/src/plugins/clientTheme/index.tsx b/src/plugins/clientTheme/index.tsx index e5de67e4..7b30863e 100644 --- a/src/plugins/clientTheme/index.tsx +++ b/src/plugins/clientTheme/index.tsx @@ -11,8 +11,8 @@ import { Devs } from "@utils/constants"; import { getTheme, Theme } from "@utils/discord"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; -import { findComponentByCodeLazy } from "@utils/react"; import definePlugin, { OptionType, StartAt } from "@utils/types"; +import { findComponentByCodeLazy } from "@webpack"; import { Button, Forms } from "@webpack/common"; const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR"); diff --git a/src/plugins/customRPC/index.tsx b/src/plugins/customRPC/index.tsx index 64bfd460..3653a077 100644 --- a/src/plugins/customRPC/index.tsx +++ b/src/plugins/customRPC/index.tsx @@ -20,9 +20,9 @@ import { definePluginSettings, Settings } from "@api/Settings"; import { Link } from "@components/Link"; import { Devs } from "@utils/constants"; import { isTruthy } from "@utils/guards"; -import { findComponentByCodeLazy, useAwaiter } from "@utils/react"; +import { useAwaiter } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy } from "@webpack"; +import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { ApplicationAssetUtils, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common"; const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile"); diff --git a/src/plugins/gameActivityToggle/index.tsx b/src/plugins/gameActivityToggle/index.tsx index cc68d55b..2b84d26f 100644 --- a/src/plugins/gameActivityToggle/index.tsx +++ b/src/plugins/gameActivityToggle/index.tsx @@ -19,8 +19,8 @@ import { disableStyle, enableStyle } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; -import { findComponentByCodeLazy } from "@utils/react"; import definePlugin from "@utils/types"; +import { findComponentByCodeLazy } from "@webpack"; import { StatusSettingsStores } from "@webpack/common"; import style from "./style.css?managed"; diff --git a/src/plugins/messageLinkEmbeds/index.tsx b/src/plugins/messageLinkEmbeds/index.tsx index d8c3c99f..76282999 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -22,9 +22,8 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants.js"; import { classes } from "@utils/misc"; import { Queue } from "@utils/Queue"; -import { findComponentByCodeLazy } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy } from "@webpack"; +import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { Button, ChannelStore, diff --git a/src/plugins/serverProfile/GuildProfileModal.tsx b/src/plugins/serverProfile/GuildProfileModal.tsx index 9def7bf9..97b40b76 100644 --- a/src/plugins/serverProfile/GuildProfileModal.tsx +++ b/src/plugins/serverProfile/GuildProfileModal.tsx @@ -10,8 +10,8 @@ import { classNameFactory } from "@api/Styles"; import { openImageModal, openUserProfile } from "@utils/discord"; import { classes } from "@utils/misc"; import { ModalRoot, ModalSize, openModal } from "@utils/modal"; -import { findExportedComponentLazy, useAwaiter } from "@utils/react"; -import { findByPropsLazy } from "@webpack"; +import { useAwaiter } from "@utils/react"; +import { findByPropsLazy, findExportedComponentLazy } 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"; diff --git a/src/plugins/showConnections/VerifiedIcon.tsx b/src/plugins/showConnections/VerifiedIcon.tsx index 20005069..ffdf21e6 100644 --- a/src/plugins/showConnections/VerifiedIcon.tsx +++ b/src/plugins/showConnections/VerifiedIcon.tsx @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -import { findComponentByCodeLazy } from "@utils/react"; -import { findLazy } from "@webpack"; +import { findComponentByCodeLazy, findLazy } from "@webpack"; import { i18n, useToken } from "@webpack/common"; const ColorMap = findLazy(m => m.colors?.INTERACTIVE_MUTED?.css); diff --git a/src/plugins/showConnections/index.tsx b/src/plugins/showConnections/index.tsx index 2d2122d6..67fcc968 100644 --- a/src/plugins/showConnections/index.tsx +++ b/src/plugins/showConnections/index.tsx @@ -24,9 +24,8 @@ import { Flex } from "@components/Flex"; import { CopyIcon, LinkIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { copyWithToast } from "@utils/misc"; -import { findComponentByCodeLazy } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { findByCodeLazy, findByPropsLazy, findStoreLazy } from "@webpack"; +import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; import { Text, Tooltip, UserProfileStore } from "@webpack/common"; import { User } from "discord-types/general"; diff --git a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx index 5231fe8e..649e87aa 100644 --- a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx +++ b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx @@ -18,9 +18,8 @@ import { Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; -import { findComponentByCodeLazy, findComponentLazy } from "@utils/react"; import { formatDuration } from "@utils/text"; -import { findByPropsLazy } from "@webpack"; +import { findByPropsLazy, findComponentByCodeLazy, findComponentLazy } from "@webpack"; import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common"; import type { Channel } from "discord-types/general"; diff --git a/src/plugins/voiceMessages/VoicePreview.tsx b/src/plugins/voiceMessages/VoicePreview.tsx index d2c62336..0976f794 100644 --- a/src/plugins/voiceMessages/VoicePreview.tsx +++ b/src/plugins/voiceMessages/VoicePreview.tsx @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import { findComponentByCodeLazy, useTimer } from "@utils/react"; +import { useTimer } from "@utils/react"; +import { findComponentByCodeLazy } from "@webpack"; import { cl } from "./utils"; diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx index 15395a1f..4a2bdeed 100644 --- a/src/plugins/whoReacted/index.tsx +++ b/src/plugins/whoReacted/index.tsx @@ -20,9 +20,9 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { sleep } from "@utils/misc"; import { Queue } from "@utils/Queue"; -import { findComponentByCodeLazy, useForceUpdater } from "@utils/react"; +import { useForceUpdater } from "@utils/react"; import definePlugin from "@utils/types"; -import { findByPropsLazy } from "@webpack"; +import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { ChannelStore, FluxDispatcher, React, RestAPI, Tooltip } from "@webpack/common"; import { CustomEmoji } from "@webpack/types"; import { Message, ReactionEmoji, User } from "discord-types/general"; diff --git a/src/utils/react.tsx b/src/utils/react.tsx index 2e3352bc..2d2bac39 100644 --- a/src/utils/react.tsx +++ b/src/utils/react.tsx @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import { FilterFn, filters, find, findByProps } from "@webpack"; import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common"; import { makeLazy } from "./lazy"; @@ -145,41 +144,6 @@ export function useTimer({ interval = 1000, deps = [] }: TimerOpts) { return time; } -/** - * Finds the component which includes all the given code. Checks for plain components, memos and forwardRefs - */ -export function findComponentByCode(...code: string[]) { - const filter = filters.byCode(...code); - return find(m => { - if (filter(m)) return true; - if (!m.$$typeof) return false; - if (m.type) return filter(m.type); // memos - if (m.render) return filter(m.render); // forwardRefs - return false; - }) ?? NoopComponent; -} - -/** - * Finds the first component that matches the filter, lazily. - */ -export function findComponentLazy(filter: FilterFn) { - return LazyComponent(() => find(filter)); -} - -/** - * Finds the first component that includes all the given code, lazily - */ -export function findComponentByCodeLazy(...code: string[]) { - return LazyComponent(() => findComponentByCode(...code)); -} - -/** - * Finds the first component that is exported by the first prop name, lazily - */ -export function findExportedComponentLazy(...props: string[]) { - return LazyComponent(() => findByProps(...props)?.[props[0]]); -} - /** * A lazy component. The factory method is called on first render. * @param factory Function returning a Component diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 4fb94056..9e3a0aab 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -18,6 +18,7 @@ import { proxyLazy } from "@utils/lazy"; import { Logger } from "@utils/Logger"; +import { LazyComponent, NoopComponent } from "@utils/react"; import type { WebpackInstance } from "discord-types/other"; import { traceFunction } from "../debug/Tracer"; @@ -392,6 +393,41 @@ export function findStoreLazy(name: string) { return proxyLazy(() => findStore(name)); } +/** + * Finds the component which includes all the given code. Checks for plain components, memos and forwardRefs + */ +export function findComponentByCode(...code: string[]) { + const filter = filters.byCode(...code); + return find(m => { + if (filter(m)) return true; + if (!m.$$typeof) return false; + if (m.type) return filter(m.type); // memos + if (m.render) return filter(m.render); // forwardRefs + return false; + }) ?? NoopComponent; +} + +/** + * Finds the first component that matches the filter, lazily. + */ +export function findComponentLazy(filter: FilterFn) { + return LazyComponent(() => find(filter)); +} + +/** + * Finds the first component that includes all the given code, lazily + */ +export function findComponentByCodeLazy(...code: string[]) { + return LazyComponent(() => findComponentByCode(...code)); +} + +/** + * Finds the first component that is exported by the first prop name, lazily + */ +export function findExportedComponentLazy(...props: string[]) { + return LazyComponent(() => findByProps(...props)?.[props[0]]); +} + /** * Wait for a module that matches the provided filter to be registered, * then call the callback with the module as the first argument From 93a95b6d565d7d6bb501b2776d641f6e25cac542 Mon Sep 17 00:00:00 2001 From: Jack <30497388+FieryFlames@users.noreply.github.com> Date: Wed, 22 Nov 2023 01:23:21 -0500 Subject: [PATCH 0234/1143] feat(patcher): Grouped replacements (#2009) Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Co-authored-by: V --- src/utils/types.ts | 2 ++ src/webpack/patchWebpack.ts | 33 +++++++++++++++++++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/utils/types.ts b/src/utils/types.ts index 7305cd01..16867a43 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -41,6 +41,8 @@ export interface Patch { all?: boolean; /** Do not warn if this patch did no changes */ noWarn?: boolean; + /** Only apply this set of replacements if all of them succeed. Use this if your replacements depend on each other */ + group?: boolean; predicate?(): boolean; } diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index b81415e6..82648f62 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -204,6 +204,9 @@ function patchFactories(factories: Record()).add(patch.plugin); - logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${id}): ${replacement.match}`); - if (IS_DEV) { - logger.debug("Function Source:\n", code); + if (newCode === code) { + if (!patch.noWarn) { + (window.explosivePlugins ??= new Set()).add(patch.plugin); + logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${id}): ${replacement.match}`); + if (IS_DEV) { + logger.debug("Function Source:\n", code); + } + } + + if (patch.group) { + logger.warn(`Undoing patch ${patch.find} by ${patch.plugin} because replacement ${replacement.match} had no effect`); + code = previousCode; + mod = previousMod; + patchedBy.delete(patch.plugin); + break; } } else { code = newCode; @@ -259,9 +272,17 @@ function patchFactories(factories: Record Date: Thu, 23 Nov 2023 02:20:02 +0100 Subject: [PATCH 0235/1143] fix showConnections & better webpack errors --- src/plugins/showConnections/index.tsx | 2 +- src/webpack/webpack.ts | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/plugins/showConnections/index.tsx b/src/plugins/showConnections/index.tsx index 67fcc968..d4d59465 100644 --- a/src/plugins/showConnections/index.tsx +++ b/src/plugins/showConnections/index.tsx @@ -31,7 +31,7 @@ import { User } from "discord-types/general"; import { VerifiedIcon } from "./VerifiedIcon"; -const Section = findComponentByCodeLazy(".lastSection]: "); +const Section = findComponentByCodeLazy(".lastSection", "children:"); const ThemeStore = findStoreLazy("ThemeStore"); const platforms: { get(type: string): ConnectionPlatform; } = findByPropsLazy("isSupported", "getByUrl"); const getTheme: (user: User, displayProfile: any) => any = findByCodeLazy(',"--profile-gradient-primary-color"'); diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 9e3a0aab..d8743e30 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -18,7 +18,7 @@ import { proxyLazy } from "@utils/lazy"; import { Logger } from "@utils/Logger"; -import { LazyComponent, NoopComponent } from "@utils/react"; +import { LazyComponent } from "@utils/react"; import type { WebpackInstance } from "discord-types/other"; import { traceFunction } from "../debug/Tracer"; @@ -398,13 +398,18 @@ export function findStoreLazy(name: string) { */ export function findComponentByCode(...code: string[]) { const filter = filters.byCode(...code); - return find(m => { + const res = find(m => { if (filter(m)) return true; if (!m.$$typeof) return false; if (m.type) return filter(m.type); // memos if (m.render) return filter(m.render); // forwardRefs return false; - }) ?? NoopComponent; + }, { isIndirect: true }); + + if (!res) + handleModuleNotFound("findComponentByCode", ...code); + + return res; } /** From 6869705673de48d1f1463b851308c975c010a1f5 Mon Sep 17 00:00:00 2001 From: V Date: Thu, 23 Nov 2023 02:44:04 +0100 Subject: [PATCH 0236/1143] beef up ConsoleShortcuts --- src/plugins/consoleShortcuts/index.ts | 30 +++++++++++++++++++++++---- src/webpack/webpack.ts | 24 +++++++++++---------- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts index 1c23d60e..10853f25 100644 --- a/src/plugins/consoleShortcuts/index.ts +++ b/src/plugins/consoleShortcuts/index.ts @@ -62,23 +62,27 @@ export default definePlugin({ } let fakeRenderWin: WeakRef | undefined; + const find = newFindWrapper(f => f); return { + ...Vencord.Webpack.Common, wp: Vencord.Webpack, wpc: Webpack.wreq.c, wreq: Webpack.wreq, wpsearch: search, wpex: extract, - wpexs: (code: string) => Vencord.Webpack.extract(Vencord.Webpack.findModuleId(code)!), - find: newFindWrapper(f => f), + wpexs: (code: string) => extract(Webpack.findModuleId(code)!), + find, findAll, findByProps: newFindWrapper(filters.byProps), findAllByProps: (...props: string[]) => findAll(filters.byProps(...props)), findByCode: newFindWrapper(filters.byCode), findAllByCode: (code: string) => findAll(filters.byCode(code)), + findComponentByCode: newFindWrapper(filters.componentByCode), + findAllComponentsByCode: (...code: string[]) => findAll(filters.componentByCode(...code)), + findExportedComponent: (...props: string[]) => find(...props)[props[0]], findStore: newFindWrapper(filters.byStoreName), PluginsApi: Vencord.Plugins, plugins: Vencord.Plugins.plugins, - React, Settings: Vencord.Settings, Api: Vencord.Api, reload: () => location.reload(), @@ -92,7 +96,25 @@ export default definePlugin({ fakeRenderWin = new WeakRef(win); win.focus(); - ReactDOM.render(React.createElement(component, props), win.document.body); + const doc = win.document; + doc.body.style.margin = "1em"; + + if (!win.prepared) { + win.prepared = true; + + [...document.querySelectorAll("style"), ...document.querySelectorAll("link[rel=stylesheet]")].forEach(s => { + const n = s.cloneNode(true) as HTMLStyleElement | HTMLLinkElement; + + if (s.parentElement?.tagName === "HEAD") + doc.head.append(n); + else if (n.id?.startsWith("vencord-") || n.id?.startsWith("vcd-")) + doc.documentElement.append(n); + else + doc.body.append(n); + }); + } + + ReactDOM.render(React.createElement(component, props), doc.body.appendChild(document.createElement("div"))); } }; }, diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index d8743e30..c7be62da 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -52,7 +52,18 @@ export const filters = { return true; }, byStoreName: (name: string): FilterFn => m => - m.constructor?.displayName === name + m.constructor?.displayName === name, + + componentByCode: (...code: string[]): FilterFn => { + const filter = filters.byCode(...code); + return m => { + if (filter(m)) return true; + if (!m.$$typeof) return false; + if (m.type) return filter(m.type); // memos + if (m.render) return filter(m.render); // forwardRefs + return false; + }; + } }; export const subscriptions = new Map(); @@ -397,18 +408,9 @@ export function findStoreLazy(name: string) { * Finds the component which includes all the given code. Checks for plain components, memos and forwardRefs */ export function findComponentByCode(...code: string[]) { - const filter = filters.byCode(...code); - const res = find(m => { - if (filter(m)) return true; - if (!m.$$typeof) return false; - if (m.type) return filter(m.type); // memos - if (m.render) return filter(m.render); // forwardRefs - return false; - }, { isIndirect: true }); - + const res = find(filters.componentByCode(...code), { isIndirect: true }); if (!res) handleModuleNotFound("findComponentByCode", ...code); - return res; } From 63451bad25c9f246e6d458f0472e2b57d696a581 Mon Sep 17 00:00:00 2001 From: V Date: Thu, 23 Nov 2023 03:11:17 +0100 Subject: [PATCH 0237/1143] Remove obsolete mapMangledModule ~ modules are no longer mangled --- src/plugins/gifPaste/index.ts | 8 ++-- src/plugins/greetStickerPicker/index.tsx | 4 +- src/plugins/imageZoom/index.tsx | 6 +-- .../components/RolesAndUsersPermissions.tsx | 6 +-- .../spotifyControls/PlayerComponent.tsx | 6 +-- src/utils/modal.tsx | 10 +---- src/webpack/common/menu.ts | 8 +--- src/webpack/common/stores.ts | 6 +-- src/webpack/common/types/menu.d.ts | 6 +-- src/webpack/common/types/utils.d.ts | 21 ++++++++++ src/webpack/common/utils.ts | 14 ++----- src/webpack/webpack.ts | 41 ------------------- 12 files changed, 46 insertions(+), 90 deletions(-) diff --git a/src/plugins/gifPaste/index.ts b/src/plugins/gifPaste/index.ts index 9ce88f9d..3e864b31 100644 --- a/src/plugins/gifPaste/index.ts +++ b/src/plugins/gifPaste/index.ts @@ -19,11 +19,9 @@ import { Devs } from "@utils/constants"; import { insertTextIntoChatInputBox } from "@utils/discord"; import definePlugin from "@utils/types"; -import { filters, mapMangledModuleLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; -const ExpressionPickerState = mapMangledModuleLazy('name:"expression-picker-last-active-view"', { - close: filters.byCode("activeView:null", "setState") -}); +const { closeExpressionPicker } = findByPropsLazy("closeExpressionPicker"); export default definePlugin({ name: "GifPaste", @@ -41,7 +39,7 @@ export default definePlugin({ handleSelect(gif?: { url: string; }) { if (gif) { insertTextIntoChatInputBox(gif.url + " "); - ExpressionPickerState.close(); + closeExpressionPicker(); } } }); diff --git a/src/plugins/greetStickerPicker/index.tsx b/src/plugins/greetStickerPicker/index.tsx index 845c7a1b..9623d422 100644 --- a/src/plugins/greetStickerPicker/index.tsx +++ b/src/plugins/greetStickerPicker/index.tsx @@ -20,7 +20,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { ContextMenu, FluxDispatcher, Menu } from "@webpack/common"; +import { ContextMenuApi, FluxDispatcher, Menu } from "@webpack/common"; import { Channel, Message } from "discord-types/general"; interface Sticker { @@ -183,6 +183,6 @@ export default definePlugin({ } ) { if (!(props.message as any).deleted) - ContextMenu.open(event, () => ); + ContextMenuApi.openContextMenu(event, () => ); } }); diff --git a/src/plugins/imageZoom/index.tsx b/src/plugins/imageZoom/index.tsx index 75c944eb..c14754c8 100644 --- a/src/plugins/imageZoom/index.tsx +++ b/src/plugins/imageZoom/index.tsx @@ -23,7 +23,7 @@ import { makeRange } from "@components/PluginSettings/components"; import { Devs } from "@utils/constants"; import { debounce } from "@utils/debounce"; import definePlugin, { OptionType } from "@utils/types"; -import { ContextMenu, Menu, React, ReactDOM } from "@webpack/common"; +import { ContextMenuApi, Menu, React, ReactDOM } from "@webpack/common"; import type { Root } from "react-dom/client"; import { Magnifier, MagnifierProps } from "./components/Magnifier"; @@ -89,7 +89,7 @@ const imageContextMenuPatch: NavContextMenuPatchCallback = children => () => { checked={settings.store.square} action={() => { settings.store.square = !settings.store.square; - ContextMenu.close(); + ContextMenuApi.closeContextMenu(); }} /> () => { checked={settings.store.nearestNeighbour} action={() => { settings.store.nearestNeighbour = !settings.store.nearestNeighbour; - ContextMenu.close(); + ContextMenuApi.closeContextMenu(); }} /> { if ((settings.store as any).unsafeViewAsRole && permission.type === PermissionType.Role) - ContextMenu.open(e, () => ( + ContextMenuApi.openContextMenu(e, () => ( ) => - ContextMenu.open(e, () => ); + ContextMenuApi.openContextMenu(e, () => ); } function Controls() { @@ -277,7 +277,7 @@ function Info({ track }: { track: Track; }) { alt="Album Image" onClick={() => setCoverExpanded(!coverExpanded)} onContextMenu={e => { - ContextMenu.open(e, () => ); + ContextMenuApi.openContextMenu(e, () => ); }} /> )} diff --git a/src/utils/modal.tsx b/src/utils/modal.tsx index 1efb9cdd..6758a1a1 100644 --- a/src/utils/modal.tsx +++ b/src/utils/modal.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { filters, findByProps, findByPropsLazy, mapMangledModuleLazy } from "@webpack"; +import { findByProps, findByPropsLazy } from "@webpack"; import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react"; import { LazyComponent } from "./react"; @@ -49,13 +49,7 @@ export interface ModalOptions { type RenderFunction = (props: ModalProps) => ReactNode; -export const Modals = mapMangledModuleLazy(".closeWithCircleBackground", { - ModalRoot: filters.byCode(".root"), - ModalHeader: filters.byCode(".header"), - ModalContent: filters.byCode(".content"), - ModalFooter: filters.byCode(".footerSeparator"), - ModalCloseButton: filters.byCode(".closeWithCircleBackground"), -}) as { +export const Modals = findByPropsLazy("ModalRoot", "ModalCloseButton") as { ModalRoot: ComponentType Menu = m); -export const ContextMenu: t.ContextMenuApi = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN"', { - open: filters.byCode("stopPropagation"), - openLazy: m => m.toString().length < 50, - close: filters.byCode("CONTEXT_MENU_CLOSE") -}); +export const ContextMenuApi: t.ContextMenuApi = findByPropsLazy("closeContextMenu", "openContextMenu"); diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index 1b00180f..0c470d6a 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -19,7 +19,7 @@ import type * as Stores from "discord-types/stores"; // eslint-disable-next-line path-alias/no-relative -import { filters, findByPropsLazy, mapMangledModuleLazy } from "../webpack"; +import { findByPropsLazy } from "../webpack"; import { waitForStore } from "./internal"; import * as t from "./types/stores"; @@ -62,10 +62,6 @@ export let EmojiStore: t.EmojiStore; export let WindowStore: t.WindowStore; export let DraftStore: t.DraftStore; -export const MaskedLinkStore = mapMangledModuleLazy('"MaskedLinkStore"', { - openUntrustedLink: filters.byCode(".apply(this,arguments)") -}); - /** * React hook that returns stateful data for one or more stores * You might need a custom comparator (4th argument) if your store data is an object diff --git a/src/webpack/common/types/menu.d.ts b/src/webpack/common/types/menu.d.ts index 29f3ffae..0b8ab5c6 100644 --- a/src/webpack/common/types/menu.d.ts +++ b/src/webpack/common/types/menu.d.ts @@ -75,14 +75,14 @@ export interface Menu { } export interface ContextMenuApi { - close(): void; - open( + closeContextMenu(): void; + openContextMenu( event: UIEvent, render?: Menu["Menu"], options?: { enableSpellCheck?: boolean; }, renderLazy?: () => Promise ): void; - openLazy( + openContextMenuLazy( event: UIEvent, renderLazy?: () => Promise, options?: { enableSpellCheck?: boolean; } diff --git a/src/webpack/common/types/utils.d.ts b/src/webpack/common/types/utils.d.ts index 64a68f2c..24665914 100644 --- a/src/webpack/common/types/utils.d.ts +++ b/src/webpack/common/types/utils.d.ts @@ -161,3 +161,24 @@ export interface i18n { Messages: Record; } + +export interface Clipboard { + copy(text: string): void; + SUPPORTS_COPY: boolean; +} + +export interface NavigationRouter { + back(): void; + forward(): void; + hasNavigated(): boolean; + getHistory(): { + action: string; + length: 50; + [key: string]: any; + }; + transitionTo(path: string, ...args: unknown[]): void; + transitionToGuild(guildId: string, ...args: unknown[]): void; + replaceWith(...args: unknown[]): void; + getLastRouteChangeSource(): any; + getLastRouteChangeSourceLocationStack(): any; +} diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 8376925e..2a3d4e67 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -20,7 +20,7 @@ import { proxyLazy } from "@utils/lazy"; import type { Channel, User } from "discord-types/general"; // eslint-disable-next-line path-alias/no-relative -import { _resolveReady, filters, find, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "../webpack"; +import { _resolveReady, find, findByPropsLazy, findLazy, waitFor } from "../webpack"; import type * as t from "./types/utils"; export let FluxDispatcher: t.FluxDispatcher; @@ -102,17 +102,9 @@ export const ApplicationAssetUtils = findByPropsLazy("fetchAssetIds", "getAssetI fetchAssetIds: (applicationId: string, e: string[]) => Promise; }; -export const Clipboard = mapMangledModuleLazy('document.queryCommandEnabled("copy")||document.queryCommandSupported("copy")', { - copy: filters.byCode(".copy("), - SUPPORTS_COPY: x => typeof x === "boolean", -}); +export const Clipboard: t.Clipboard = findByPropsLazy("SUPPORTS_COPY", "copy"); -export const NavigationRouter = mapMangledModuleLazy("transitionToGuild - ", { - transitionTo: filters.byCode("transitionTo -"), - transitionToGuild: filters.byCode("transitionToGuild -"), - goBack: filters.byCode("goBack()"), - goForward: filters.byCode("goForward()"), -}); +export const NavigationRouter: t.NavigationRouter = findByPropsLazy("transitionTo", "replaceWith", "transitionToGuild"); waitFor(["dispatch", "subscribe"], m => { FluxDispatcher = m; diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index c7be62da..b9c67e8f 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -312,47 +312,6 @@ export const findModuleId = traceFunction("findModuleId", function findModuleId( return null; }); -/** - * Finds a mangled module by the provided code "code" (must be unique and can be anywhere in the module) - * then maps it into an easily usable module via the specified mappers - * @param code Code snippet - * @param mappers Mappers to create the non mangled exports - * @returns Unmangled exports as specified in mappers - * - * @example mapMangledModule("headerIdIsManaged:", { - * openModal: filters.byCode("headerIdIsManaged:"), - * closeModal: filters.byCode("key==") - * }) - */ -export const mapMangledModule = traceFunction("mapMangledModule", function mapMangledModule(code: string, mappers: Record): Record { - const exports = {} as Record; - - const id = findModuleId(code); - if (id === null) - return exports; - - const mod = wreq(id); - outer: - for (const key in mod) { - const member = mod[key]; - for (const newName in mappers) { - // if the current mapper matches this module - if (mappers[newName](member)) { - exports[newName] = member; - continue outer; - } - } - } - return exports; -}); - -/** - * Same as {@link mapMangledModule} but lazy - */ -export function mapMangledModuleLazy(code: string, mappers: Record): Record { - return proxyLazy(() => mapMangledModule(code, mappers)); -} - /** * Find the first module that has the specified properties */ From 9efc0ff5797cfdd346d0a1951db4d3480137f72d Mon Sep 17 00:00:00 2001 From: V Date: Thu, 23 Nov 2023 03:21:58 +0100 Subject: [PATCH 0238/1143] Remove obsolete nested webpack search --- src/webpack/patchWebpack.ts | 15 ++------ src/webpack/webpack.ts | 68 ------------------------------------- 2 files changed, 3 insertions(+), 80 deletions(-) diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index 82648f62..0311e171 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -170,18 +170,9 @@ function patchFactories(factories: Record Date: Thu, 23 Nov 2023 03:41:09 +0100 Subject: [PATCH 0239/1143] Remove obsolete webpack hacks --- src/webpack/patchWebpack.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index 0311e171..b992cfde 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -119,12 +119,9 @@ function patchFactories(factories: Record mod.toString(); - factory.original = originalMod; - } catch { } + factory.toString = () => mod.toString(); + factory.original = originalMod; for (let i = 0; i < patches.length; i++) { const patch = patches[i]; @@ -210,7 +205,6 @@ function patchFactories(factories: Record()).add(patch.plugin); logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${id}): ${replacement.match}`); if (IS_DEV) { logger.debug("Function Source:\n", code); From 4832a9433fe6575d4a15cd733246cbc1498666c4 Mon Sep 17 00:00:00 2001 From: V Date: Thu, 23 Nov 2023 06:18:34 +0100 Subject: [PATCH 0240/1143] fix broken webpack finds --- src/plugins/friendInvites/index.ts | 4 ++-- src/plugins/webContextMenus.web/index.ts | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/plugins/friendInvites/index.ts b/src/plugins/friendInvites/index.ts index 92209986..e5ff447e 100644 --- a/src/plugins/friendInvites/index.ts +++ b/src/plugins/friendInvites/index.ts @@ -23,7 +23,7 @@ import { findByPropsLazy } from "@webpack"; import { RestAPI, UserStore } from "@webpack/common"; const FriendInvites = findByPropsLazy("createFriendInvite"); -const uuid = findByPropsLazy("v4", "v1"); +const { uuid4 } = findByPropsLazy("uuid4"); export default definePlugin({ name: "FriendInvites", @@ -56,7 +56,7 @@ export default definePlugin({ let invite: any; if (uses === 1) { - const random = uuid.v4(); + const random = uuid4(); const { body: { invite_suggestions } } = await RestAPI.post({ url: "/friend-finder/find-friends", body: { diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index bbfaa674..9c4dd67c 100644 --- a/src/plugins/webContextMenus.web/index.ts +++ b/src/plugins/webContextMenus.web/index.ts @@ -20,8 +20,8 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { saveFile } from "@utils/web"; -import { findByProps, findLazy } from "@webpack"; -import { Clipboard } from "@webpack/common"; +import { findByProps } from "@webpack"; +import { Clipboard, ComponentDispatch } from "@webpack/common"; async function fetchImage(url: string) { const res = await fetch(url); @@ -30,7 +30,6 @@ async function fetchImage(url: string) { return await res.blob(); } -const MiniDispatcher = findLazy(m => m.emitter?._events?.INSERT_TEXT); const settings = definePluginSettings({ // This needs to be all in one setting because to enable any of these, we need to make Discord use their desktop context @@ -213,7 +212,7 @@ export default definePlugin({ cut() { this.copy(); - MiniDispatcher.dispatch("INSERT_TEXT", { rawText: "" }); + ComponentDispatch.dispatch("INSERT_TEXT", { rawText: "" }); }, async paste() { From 0f74817e255727ad7b1fa6a6d963f42115e29070 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Thu, 23 Nov 2023 02:21:59 -0300 Subject: [PATCH 0241/1143] Fix broken SHC find --- .../showHiddenChannels/components/HiddenChannelLockScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx index 649e87aa..87946110 100644 --- a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx +++ b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx @@ -90,7 +90,7 @@ const TagComponent = findComponentLazy(m => { }); const EmojiParser = findByPropsLazy("convertSurrogateToName"); -const EmojiUtils = findByPropsLazy("getURL", "buildEmojiReactionColorsPlatformed"); +const EmojiUtils = findByPropsLazy("getURL", "getEmojiColors"); const ChannelTypesToChannelNames = { [ChannelTypes.GUILD_TEXT]: "text", From f39f16d34bd12a19c4c06b2ac4d7ba4010c1dcef Mon Sep 17 00:00:00 2001 From: V Date: Thu, 23 Nov 2023 06:43:22 +0100 Subject: [PATCH 0242/1143] fix circular import bricking browser version --- src/utils/lazyReact.tsx | 23 +++++++++++++++++++++++ src/utils/react.tsx | 17 ++--------------- src/webpack/webpack.ts | 5 +---- 3 files changed, 26 insertions(+), 19 deletions(-) create mode 100644 src/utils/lazyReact.tsx diff --git a/src/utils/lazyReact.tsx b/src/utils/lazyReact.tsx new file mode 100644 index 00000000..abd300a9 --- /dev/null +++ b/src/utils/lazyReact.tsx @@ -0,0 +1,23 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { makeLazy } from "./lazy"; + +const NoopComponent = () => null; + +/** + * A lazy component. The factory method is called on first render. + * @param factory Function returning a Component + * @param attempts How many times to try to get the component before giving up + * @returns Result of factory function + */ +export function LazyComponent(factory: () => React.ComponentType, attempts = 5) { + const get = makeLazy(factory, attempts); + return (props: T) => { + const Component = get() ?? NoopComponent; + return ; + }; +} diff --git a/src/utils/react.tsx b/src/utils/react.tsx index 2d2bac39..f31549f1 100644 --- a/src/utils/react.tsx +++ b/src/utils/react.tsx @@ -18,9 +18,10 @@ import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common"; -import { makeLazy } from "./lazy"; import { checkIntersecting } from "./misc"; +export * from "./lazyReact"; + export const NoopComponent = () => null; /** @@ -143,17 +144,3 @@ export function useTimer({ interval = 1000, deps = [] }: TimerOpts) { return time; } - -/** - * A lazy component. The factory method is called on first render. - * @param factory Function returning a Component - * @param attempts How many times to try to get the component before giving up - * @returns Result of factory function - */ -export function LazyComponent(factory: () => React.ComponentType, attempts = 5) { - const get = makeLazy(factory, attempts); - return (props: T) => { - const Component = get() ?? NoopComponent; - return ; - }; -} diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 2c55fd36..b4fdb4e1 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -17,8 +17,8 @@ */ import { proxyLazy } from "@utils/lazy"; +import { LazyComponent } from "@utils/lazyReact"; import { Logger } from "@utils/Logger"; -import { LazyComponent } from "@utils/react"; import type { WebpackInstance } from "discord-types/other"; import { traceFunction } from "../debug/Tracer"; @@ -338,9 +338,6 @@ export function waitFor(filter: string | string[] | FilterFn, callback: Callback else if (typeof filter !== "function") throw new Error("filter must be a string, string[] or function, got " + typeof filter); - const [existing, id] = find(filter!, { isIndirect: true, isWaitFor: true }); - if (existing) return void callback(existing, id); - subscriptions.set(filter, callback); } From 81fb7c6322f4fd5f56ea95b268597f5365af6f39 Mon Sep 17 00:00:00 2001 From: V Date: Thu, 23 Nov 2023 06:45:01 +0100 Subject: [PATCH 0243/1143] add back code that got lost --- src/webpack/webpack.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index b4fdb4e1..980288e9 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -338,6 +338,9 @@ export function waitFor(filter: string | string[] | FilterFn, callback: Callback else if (typeof filter !== "function") throw new Error("filter must be a string, string[] or function, got " + typeof filter); + const [existing, id] = find(filter!, { isIndirect: true, isWaitFor: true }); + if (existing) return void callback(existing, id); + subscriptions.set(filter, callback); } From e14fba28a51981e76f23dad9b9145b49421dac07 Mon Sep 17 00:00:00 2001 From: cat <101748822+capillarys@users.noreply.github.com> Date: Thu, 23 Nov 2023 17:31:25 +0000 Subject: [PATCH 0244/1143] ClearURLS for x.com links (#2005) --- src/plugins/clearURLs/defaultRules.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/clearURLs/defaultRules.ts b/src/plugins/clearURLs/defaultRules.ts index d6914b90..e7c3ecbe 100644 --- a/src/plugins/clearURLs/defaultRules.ts +++ b/src/plugins/clearURLs/defaultRules.ts @@ -121,6 +121,9 @@ export const defaultRules = [ "t@*.twitter.com", "s@*.twitter.com", "ref_*@*.twitter.com", + "t@*.x.com", + "s@*.x.com", + "ref_*@*.x.com", "tt_medium", "tt_content", "lr@yandex.*", From fdddfdb05b339a7c677044756b75cdff43f45e16 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 24 Nov 2023 16:32:30 -0300 Subject: [PATCH 0245/1143] feat(plugins): FixImagesQuality --- src/plugins/fixImagesQuality/index.ts | 35 +++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/plugins/fixImagesQuality/index.ts diff --git a/src/plugins/fixImagesQuality/index.ts b/src/plugins/fixImagesQuality/index.ts new file mode 100644 index 00000000..e0dfcb6d --- /dev/null +++ b/src/plugins/fixImagesQuality/index.ts @@ -0,0 +1,35 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 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: "FixImagesQuality", + description: "Fixes the quality of images in the chat being horrible.", + authors: [Devs.Nuckyz], + patches: [ + { + find: "handleImageLoad=", + replacement: { + match: /(?<=getSrc\(\i\){.+?format:)\i/, + replace: "null" + } + } + ] +}); From 3e8e106be7f010a87871a44f75446053aae3b6be Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 24 Nov 2023 16:49:19 -0300 Subject: [PATCH 0246/1143] Fix broken patches --- src/plugins/fixImagesQuality/index.ts | 20 +++------------ src/plugins/sortFriendRequests/index.tsx | 31 ++++++++++++------------ src/plugins/webContextMenus.web/index.ts | 6 ++--- 3 files changed, 22 insertions(+), 35 deletions(-) diff --git a/src/plugins/fixImagesQuality/index.ts b/src/plugins/fixImagesQuality/index.ts index e0dfcb6d..d94c8e3b 100644 --- a/src/plugins/fixImagesQuality/index.ts +++ b/src/plugins/fixImagesQuality/index.ts @@ -1,20 +1,8 @@ /* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 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 . -*/ + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; diff --git a/src/plugins/sortFriendRequests/index.tsx b/src/plugins/sortFriendRequests/index.tsx index 3698379c..c40a1814 100644 --- a/src/plugins/sortFriendRequests/index.tsx +++ b/src/plugins/sortFriendRequests/index.tsx @@ -16,17 +16,27 @@ * along with this program. If not, see . */ +import { definePluginSettings } from "@api/Settings"; import { Flex } from "@components/Flex"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { RelationshipStore } from "@webpack/common"; import { User } from "discord-types/general"; -import { Settings } from "Vencord"; + +const settings = definePluginSettings({ + showDates: { + type: OptionType.BOOLEAN, + description: "Show dates on friend requests", + default: false, + restartNeeded: true + } +}); export default definePlugin({ name: "SortFriendRequests", authors: [Devs.Megu], description: "Sorts friend requests by date of receipt", + settings, patches: [{ find: "getRelationshipCounts(){", @@ -35,13 +45,11 @@ export default definePlugin({ replace: ".sortBy((row) => $self.sortList(row))" } }, { - find: "RelationshipTypes.PENDING_INCOMING?", + find: ".Messages.FRIEND_REQUEST_CANCEL", replacement: { - predicate: () => Settings.plugins.SortFriendRequests.showDates, - match: /(user:(\i),.{10,50}),subText:(\i),(className:\i\.userInfo}\))/, - replace: (_, pre, user, subtext, post) => `${pre}, - subText: $self.makeSubtext(${subtext}, ${user}), - ${post}` + predicate: () => settings.store.showDates, + match: /subText:(\i)(?=,className:\i\.userInfo}\))(?<=user:(\i).+?)/, + replace: (_, subtext, user) => `subText:$self.makeSubtext(${subtext},${user})` } }], @@ -63,14 +71,5 @@ export default definePlugin({ {!isNaN(since.getTime()) && Received — {since.toDateString()}} ); - }, - - options: { - showDates: { - type: OptionType.BOOLEAN, - description: "Show dates on friend requests", - default: false, - restartNeeded: true - } } }); diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index 9c4dd67c..aa575bfd 100644 --- a/src/plugins/webContextMenus.web/index.ts +++ b/src/plugins/webContextMenus.web/index.ts @@ -120,9 +120,9 @@ export default definePlugin({ find: 'navId:"image-context"', predicate: () => settings.store.addBack, replacement: { - // return IS_DESKTOP ? React.createElement(Menu, ...) - match: /return \i\.\i\?/, - replace: "return true?" + // return IS_DESKTOP && null != ... ? React.createElement(Menu, ...) + match: /return \i\.\i(?=&&null)/, + replace: "return true" } }, From 534565db256e9aaf47f3b3092cbe6f4e0bc982be Mon Sep 17 00:00:00 2001 From: V Date: Sat, 25 Nov 2023 01:32:21 +0100 Subject: [PATCH 0247/1143] Add webpack find testing (#2016) Co-authored-by: V Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com> --- .github/workflows/reportBrokenPlugins.yml | 2 +- scripts/build/build.mjs | 4 +- scripts/build/buildWeb.mjs | 4 +- scripts/build/common.mjs | 1 + scripts/generateReport.ts | 212 +++++++++++++++--- src/plugins/fakeNitro/index.ts | 9 +- .../components/UserPermissions.tsx | 5 +- src/plugins/spotifyControls/SpotifyStore.ts | 9 +- src/plugins/typingIndicator/index.tsx | 5 +- src/plugins/vencordToolbox/index.tsx | 7 +- src/utils/lazy.ts | 2 +- src/utils/lazyReact.tsx | 6 +- src/webpack/common/internal.tsx | 10 +- src/webpack/common/utils.ts | 44 ++-- src/webpack/webpack.ts | 66 +++++- 15 files changed, 289 insertions(+), 97 deletions(-) diff --git a/.github/workflows/reportBrokenPlugins.yml b/.github/workflows/reportBrokenPlugins.yml index 8bc93618..13202200 100644 --- a/.github/workflows/reportBrokenPlugins.yml +++ b/.github/workflows/reportBrokenPlugins.yml @@ -29,7 +29,7 @@ jobs: sudo apt-get install -y chromium-browser - name: Build web - run: pnpm buildWeb --standalone + run: pnpm buildWeb --standalone --dev - name: Create Report timeout-minutes: 10 diff --git a/scripts/build/build.mjs b/scripts/build/build.mjs index 89cca7e4..a2e0e002 100755 --- a/scripts/build/build.mjs +++ b/scripts/build/build.mjs @@ -21,11 +21,11 @@ import esbuild from "esbuild"; import { readdir } from "fs/promises"; import { join } from "path"; -import { BUILD_TIMESTAMP, commonOpts, existsAsync, globPlugins, isStandalone, updaterDisabled, VERSION, watch } from "./common.mjs"; +import { BUILD_TIMESTAMP, commonOpts, existsAsync, globPlugins, isDev, isStandalone, updaterDisabled, VERSION, watch } from "./common.mjs"; const defines = { IS_STANDALONE: isStandalone, - IS_DEV: JSON.stringify(watch), + IS_DEV: JSON.stringify(isDev), IS_UPDATER_DISABLED: updaterDisabled, IS_WEB: false, IS_EXTENSION: false, diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs index 353f4e06..b4c72606 100644 --- a/scripts/build/buildWeb.mjs +++ b/scripts/build/buildWeb.mjs @@ -23,7 +23,7 @@ import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises import { join } from "path"; import Zip from "zip-local"; -import { BUILD_TIMESTAMP, commonOpts, globPlugins, VERSION, watch } from "./common.mjs"; +import { BUILD_TIMESTAMP, commonOpts, globPlugins, isDev, VERSION } from "./common.mjs"; /** * @type {esbuild.BuildOptions} @@ -43,7 +43,7 @@ const commonOptions = { IS_WEB: "true", IS_EXTENSION: "false", IS_STANDALONE: "true", - IS_DEV: JSON.stringify(watch), + IS_DEV: JSON.stringify(isDev), IS_DISCORD_DESKTOP: "false", IS_VESKTOP: "false", IS_UPDATER_DISABLED: "true", diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs index 5488b1b3..5c34ad03 100644 --- a/scripts/build/common.mjs +++ b/scripts/build/common.mjs @@ -33,6 +33,7 @@ export const VERSION = PackageJSON.version; // https://reproducible-builds.org/docs/source-date-epoch/ export const BUILD_TIMESTAMP = Number(process.env.SOURCE_DATE_EPOCH) || Date.now(); export const watch = process.argv.includes("--watch"); +export const isDev = watch || process.argv.includes("--dev"); export const isStandalone = JSON.stringify(process.argv.includes("--standalone")); export const updaterDisabled = JSON.stringify(process.argv.includes("--disable-updater")); export const gitHash = process.env.VENCORD_HASH || execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim(); diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts index 4e044c94..cadf0c2a 100644 --- a/scripts/generateReport.ts +++ b/scripts/generateReport.ts @@ -34,7 +34,7 @@ for (const variable of ["DISCORD_TOKEN", "CHROMIUM_BIN"]) { const CANARY = process.env.USE_CANARY === "true"; const browser = await pup.launch({ - headless: true, + headless: "new", executablePath: process.env.CHROMIUM_BIN }); @@ -58,14 +58,16 @@ const report = { plugin: string; error: string; }[], - otherErrors: [] as string[] + otherErrors: [] as string[], + badWebpackFinds: [] as string[] }; const IGNORED_DISCORD_ERRORS = [ "KeybindStore: Looking for callback action", "Unable to process domain list delta: Client revision number is null", "Downloading the full bad domains file", - /\[GatewaySocket\].{0,110}Cannot access '/ + /\[GatewaySocket\].{0,110}Cannot access '/, + "search for 'name' in undefined" ] as Array; function toCodeBlock(s: string) { @@ -74,7 +76,10 @@ function toCodeBlock(s: string) { } async function printReport() { + console.log(); + console.log("# Vencord Report" + (CANARY ? " (Canary)" : "")); + console.log(); console.log("## Bad Patches"); @@ -87,12 +92,19 @@ async function printReport() { console.log(); + console.log("## Bad Webpack Finds"); + report.badWebpackFinds.forEach(p => console.log("- " + p)); + + console.log(); + console.log("## Bad Starts"); report.badStarts.forEach(p => { console.log(`- ${p.plugin}`); console.log(` - Error: ${toCodeBlock(p.error)}`); }); + console.log(); + report.otherErrors = report.otherErrors.filter(e => !IGNORED_DISCORD_ERRORS.some(regex => e.match(regex))); console.log("## Discord Errors"); @@ -100,8 +112,9 @@ async function printReport() { console.log(`- ${toCodeBlock(e)}`); }); + console.log(); + if (process.env.DISCORD_WEBHOOK) { - // this code was written almost entirely by Copilot xD await fetch(process.env.DISCORD_WEBHOOK, { method: "POST", headers: { @@ -110,7 +123,7 @@ async function printReport() { body: JSON.stringify({ description: "Here's the latest Vencord Report!", username: "Vencord Reporter" + (CANARY ? " (Canary)" : ""), - avatar_url: "https://cdn.discordapp.com/icons/1015060230222131221/f0204a918c6c9c9a43195997e97d8adf.webp", + avatar_url: "https://cdn.discordapp.com/icons/1015060230222131221/6101cff21e241cebb60c4a01563d0c01.webp?size=512", embeds: [ { title: "Bad Patches", @@ -125,6 +138,11 @@ async function printReport() { }).join("\n\n") || "None", color: report.badPatches.length ? 0xff0000 : 0x00ff00 }, + { + title: "Bad Webpack Finds", + description: report.badWebpackFinds.map(toCodeBlock).join("\n") || "None", + color: report.badWebpackFinds.length ? 0xff0000 : 0x00ff00 + }, { title: "Bad Starts", description: report.badStarts.map(p => { @@ -153,29 +171,35 @@ async function printReport() { page.on("console", async e => { const level = e.type(); - const args = e.args(); + const rawArgs = e.args(); - const firstArg = (await args[0]?.jsonValue()); - if (firstArg === "PUPPETEER_TEST_DONE_SIGNAL") { + const firstArg = await rawArgs[0]?.jsonValue(); + if (firstArg === "[PUPPETEER_TEST_DONE_SIGNAL]") { await browser.close(); await printReport(); process.exit(); } - const isVencord = (await args[0]?.jsonValue()) === "[Vencord]"; - const isDebug = (await args[0]?.jsonValue()) === "[PUP_DEBUG]"; + const isVencord = firstArg === "[Vencord]"; + const isDebug = firstArg === "[PUP_DEBUG]"; + const isWebpackFindFail = firstArg === "[PUP_WEBPACK_FIND_FAIL]"; + + if (isWebpackFindFail) { + process.exitCode = 1; + report.badWebpackFinds.push(await rawArgs[1].jsonValue() as string); + } if (isVencord) { - // make ci fail - process.exitCode = 1; + const args = await Promise.all(e.args().map(a => a.jsonValue())); - const jsonArgs = await Promise.all(args.map(a => a.jsonValue())); - const [, tag, message] = jsonArgs; - const cause = await maybeGetError(args[3]); + const [, tag, message] = args as Array; + const cause = await maybeGetError(e.args()[3]); switch (tag) { case "WebpackInterceptor:": - const [, plugin, type, id, regex] = (message as string).match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/)!; + process.exitCode = 1; + + const [, plugin, type, id, regex] = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/)!; report.badPatches.push({ plugin, type, @@ -183,17 +207,26 @@ page.on("console", async e => { match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"), error: cause }); + break; case "PluginManager:": - const [, name] = (message as string).match(/Failed to start (.+)/)!; + const failedToStartMatch = message.match(/Failed to start (.+)/); + if (!failedToStartMatch) break; + + process.exitCode = 1; + + const [, name] = failedToStartMatch; report.badStarts.push({ plugin: name, error: cause }); + break; } - } else if (isDebug) { - console.error(e.text()); + } + + if (isDebug) { + console.log(e.text()); } else if (level === "error") { const text = await Promise.all( e.args().map(async a => { @@ -206,8 +239,8 @@ page.on("console", async e => { ).then(a => a.join(" ").trim()); - if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of")) { - console.error("Got unexpected error", text); + if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of") && !text.includes("found no module Filter:")) { + console.error("[Unexpected Error]", text); report.otherErrors.push(text); } } @@ -219,17 +252,16 @@ page.on("pageerror", e => console.error("[Page Error]", e)); await page.setBypassCSP(true); function runTime(token: string) { - console.error("[PUP_DEBUG]", "Starting test..."); + console.log("[PUP_DEBUG]", "Starting test..."); try { - // spoof languages to not be suspicious + // Spoof languages to not be suspicious Object.defineProperty(navigator, "languages", { get: function () { return ["en-US", "en"]; }, }); - // Monkey patch Logger to not log with custom css // @ts-ignore Vencord.Util.Logger.prototype._log = function (level, levelColor, args) { @@ -237,7 +269,7 @@ function runTime(token: string) { console[level]("[Vencord]", this.name + ":", ...args); }; - // force enable all plugins and patches + // Force enable all plugins and patches Vencord.Plugins.patches.length = 0; Object.values(Vencord.Plugins.plugins).forEach(p => { // Needs native server to run @@ -247,8 +279,14 @@ function runTime(token: string) { p.patches?.forEach(patch => { patch.plugin = p.name; delete patch.predicate; + if (!Array.isArray(patch.replacement)) patch.replacement = [patch.replacement]; + + patch.replacement.forEach(r => { + delete r.predicate; + }); + Vencord.Plugins.patches.push(patch); }); }); @@ -256,41 +294,141 @@ function runTime(token: string) { Vencord.Webpack.waitFor( "loginToken", m => { - console.error("[PUP_DEBUG]", "Logging in with token..."); + console.log("[PUP_DEBUG]", "Logging in with token..."); m.loginToken(token); } ); - // force load all chunks + // Force load all chunks Vencord.Webpack.onceReady.then(() => setTimeout(async () => { - console.error("[PUP_DEBUG]", "Webpack is ready!"); + console.log("[PUP_DEBUG]", "Webpack is ready!"); const { wreq } = Vencord.Webpack; - console.error("[PUP_DEBUG]", "Loading all chunks..."); - const ids = Function("return" + wreq.u.toString().match(/(?<=\()\{.+?\}/s)![0])(); - for (const id in ids) { + console.log("[PUP_DEBUG]", "Loading all chunks..."); + + let chunks = null as Record | null; + const sym = Symbol("Vencord.chunksExtract"); + + Object.defineProperty(Object.prototype, sym, { + get() { + chunks = this; + }, + set() { }, + configurable: true, + }); + + await (wreq as any).el(sym); + delete Object.prototype[sym]; + + const validChunksEntryPoints = [] as string[]; + const validChunks = [] as string[]; + const invalidChunks = [] as string[]; + + if (!chunks) throw new Error("Failed to get chunks"); + + chunksLoop: + for (const entryPoint in chunks) { + const chunkIds = chunks[entryPoint]; + + for (const id of chunkIds) { + if (!wreq.u(id)) continue; + + const isWasm = await fetch(wreq.p + wreq.u(id)) + .then(r => r.text()) + .then(t => t.includes(".module.wasm") || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push")); + + await new Promise(r => setTimeout(r, 150)); + + if (isWasm) { + invalidChunks.push(id); + continue chunksLoop; + } + + validChunks.push(id); + } + + validChunksEntryPoints.push(entryPoint); + } + + for (const entryPoint of validChunksEntryPoints) { + try { + // Loads all chunks required for an entry point + await (wreq as any).el(entryPoint); + } catch (err) { } + } + + const allChunks = Function("return " + (wreq.u.toString().match(/(?<=\()\{.+?\}/s)?.[0] ?? "null"))() as Record | null; + if (!allChunks) throw new Error("Failed to get all chunks"); + const chunksLeft = Object.keys(allChunks).filter(id => { + return !(validChunks.includes(id) || invalidChunks.includes(id)); + }); + + for (const id of chunksLeft) { const isWasm = await fetch(wreq.p + wreq.u(id)) .then(r => r.text()) .then(t => t.includes(".module.wasm") || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push")); - if (!isWasm) - await wreq.e(id as any); + // Loads a chunk + if (!isWasm) await wreq.e(id as any); await new Promise(r => setTimeout(r, 150)); } - console.error("[PUP_DEBUG]", "Finished loading chunks!"); + + // Make sure every chunk has finished loading + await new Promise(r => setTimeout(r, 1000)); + + for (const entryPoint of validChunksEntryPoints) { + try { + if (wreq.m[entryPoint]) wreq(entryPoint as any); + } catch (err) { + console.error(err); + } + } + + console.log("[PUP_DEBUG]", "Finished loading all chunks!"); for (const patch of Vencord.Plugins.patches) { if (!patch.all) { new Vencord.Util.Logger("WebpackInterceptor").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`); } } - setTimeout(() => console.log("PUPPETEER_TEST_DONE_SIGNAL"), 1000); + + for (const [searchType, args] of Vencord.Webpack.lazyWebpackSearchHistory) { + let method = searchType; + + if (searchType === "findComponent") method = "find"; + if (searchType === "findExportedComponent") method = "findByProps"; + if (searchType === "waitFor" || searchType === "waitForComponent" || searchType === "waitForStore") { + if (typeof args[0] === "string") method = "findByProps"; + else method = "find"; + } + + try { + let result: any; + + if (method === "proxyLazyWebpack" || method === "LazyComponentWebpack") { + const [factory] = args; + result = factory(); + } else { + // @ts-ignore + result = Vencord.Webpack[method](...args); + } + + if (result == null || ("$$get" in result && result.$$get() == null)) throw "a rock at ben shapiro"; + } catch (e) { + let logMessage = searchType; + if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`; + else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`; + + console.log("[PUP_WEBPACK_FIND_FAIL]", logMessage); + } + } + + setTimeout(() => console.log("[PUPPETEER_TEST_DONE_SIGNAL]"), 1000); }, 1000)); } catch (e) { - console.error("[PUP_DEBUG]", "A fatal error occurred"); - console.error("[PUP_DEBUG]", e); + console.log("[PUP_DEBUG]", "A fatal error occurred:", e); process.exit(1); } } diff --git a/src/plugins/fakeNitro/index.ts b/src/plugins/fakeNitro/index.ts index 4d6b7957..3ac75556 100644 --- a/src/plugins/fakeNitro/index.ts +++ b/src/plugins/fakeNitro/index.ts @@ -21,10 +21,9 @@ import { definePluginSettings, Settings } from "@api/Settings"; import { Devs } from "@utils/constants"; import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies"; import { getCurrentGuild } from "@utils/discord"; -import { proxyLazy } from "@utils/lazy"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy, findStoreLazy } from "@webpack"; +import { findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack"; import { ChannelStore, EmojiStore, FluxDispatcher, lodash, Parser, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common"; import type { Message } from "discord-types/general"; import { applyPalette, GIFEncoder, quantize } from "gifenc"; @@ -48,9 +47,9 @@ function searchProtoClassField(localName: string, protoClass: any) { return fieldGetter?.(); } -const PreloadedUserSettingsActionCreators = proxyLazy(() => UserSettingsActionCreators.PreloadedUserSettingsActionCreators); -const AppearanceSettingsActionCreators = proxyLazy(() => searchProtoClassField("appearance", PreloadedUserSettingsActionCreators.ProtoClass)); -const ClientThemeSettingsActionsCreators = proxyLazy(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators)); +const PreloadedUserSettingsActionCreators = proxyLazyWebpack(() => UserSettingsActionCreators.PreloadedUserSettingsActionCreators); +const AppearanceSettingsActionCreators = proxyLazyWebpack(() => searchProtoClassField("appearance", PreloadedUserSettingsActionCreators.ProtoClass)); +const ClientThemeSettingsActionsCreators = proxyLazyWebpack(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators)); const USE_EXTERNAL_EMOJIS = 1n << 18n; const USE_EXTERNAL_STICKERS = 1n << 37n; diff --git a/src/plugins/permissionsViewer/components/UserPermissions.tsx b/src/plugins/permissionsViewer/components/UserPermissions.tsx index aeb97664..b75bafdc 100644 --- a/src/plugins/permissionsViewer/components/UserPermissions.tsx +++ b/src/plugins/permissionsViewer/components/UserPermissions.tsx @@ -18,9 +18,8 @@ import ErrorBoundary from "@components/ErrorBoundary"; import ExpandableHeader from "@components/ExpandableHeader"; -import { proxyLazy } from "@utils/lazy"; import { classes } from "@utils/misc"; -import { filters, findBulk } from "@webpack"; +import { filters, findBulk, proxyLazyWebpack } from "@webpack"; import { i18n, PermissionsBits, Text, Tooltip, useMemo, UserStore } from "@webpack/common"; import type { Guild, GuildMember } from "discord-types/general"; @@ -36,7 +35,7 @@ interface UserPermission { type UserPermissions = Array; -const Classes = proxyLazy(() => { +const Classes = proxyLazyWebpack(() => { const modules = findBulk( filters.byProps("roles", "rolePill", "rolePillBorder"), filters.byProps("roleCircle", "dotBorderBase", "dotBorderColor"), diff --git a/src/plugins/spotifyControls/SpotifyStore.ts b/src/plugins/spotifyControls/SpotifyStore.ts index f940d8d5..b3cd0b28 100644 --- a/src/plugins/spotifyControls/SpotifyStore.ts +++ b/src/plugins/spotifyControls/SpotifyStore.ts @@ -17,8 +17,7 @@ */ import { Settings } from "@api/Settings"; -import { proxyLazy } from "@utils/lazy"; -import { findByPropsLazy } from "@webpack"; +import { findByProps, proxyLazyWebpack } from "@webpack"; import { Flux, FluxDispatcher } from "@webpack/common"; export interface Track { @@ -66,12 +65,12 @@ interface Device { type Repeat = "off" | "track" | "context"; // Don't wanna run before Flux and Dispatcher are ready! -export const SpotifyStore = proxyLazy(() => { +export const SpotifyStore = proxyLazyWebpack(() => { // For some reason ts hates extends Flux.Store const { Store } = Flux; - const SpotifySocket = findByPropsLazy("getActiveSocketAndDevice"); - const SpotifyUtils = findByPropsLazy("SpotifyAPI"); + const SpotifySocket = findByProps("getActiveSocketAndDevice"); + const SpotifyUtils = findByProps("SpotifyAPI"); const API_BASE = "https://api.spotify.com/v1/me/player"; diff --git a/src/plugins/typingIndicator/index.tsx b/src/plugins/typingIndicator/index.tsx index 86bfbb4f..e35eb9b6 100644 --- a/src/plugins/typingIndicator/index.tsx +++ b/src/plugins/typingIndicator/index.tsx @@ -19,14 +19,13 @@ import { definePluginSettings, Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; -import { LazyComponent } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { find, findStoreLazy } from "@webpack"; +import { find, findStoreLazy, LazyComponentWebpack } from "@webpack"; import { ChannelStore, GuildMemberStore, i18n, RelationshipStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; import { buildSeveralUsers } from "../typingTweaks"; -const ThreeDots = LazyComponent(() => { +const ThreeDots = LazyComponentWebpack(() => { // This doesn't really need to explicitly find Dots' own module, but it's fine const res = find(m => m.Dots && !m.Menu); diff --git a/src/plugins/vencordToolbox/index.tsx b/src/plugins/vencordToolbox/index.tsx index cd266c6f..bb63a86b 100644 --- a/src/plugins/vencordToolbox/index.tsx +++ b/src/plugins/vencordToolbox/index.tsx @@ -22,15 +22,14 @@ import { openNotificationLogModal } from "@api/Notifications/notificationLog"; import { Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; -import { LazyComponent } from "@utils/react"; import definePlugin from "@utils/types"; -import { filters, find } from "@webpack"; +import { filters, find, LazyComponentWebpack } from "@webpack"; import { Menu, Popout, useState } from "@webpack/common"; import type { ReactNode } from "react"; -const HeaderBarIcon = LazyComponent(() => { +const HeaderBarIcon = LazyComponentWebpack(() => { const filter = filters.byCode(".HEADER_BAR_BADGE"); - return find(m => m.Icon && filter(m.Icon)).Icon; + return find(m => m.Icon && filter(m.Icon))?.Icon; }); function VencordPopout(onClose: () => void) { diff --git a/src/utils/lazy.ts b/src/utils/lazy.ts index 1c89d511..32336fb4 100644 --- a/src/utils/lazy.ts +++ b/src/utils/lazy.ts @@ -76,7 +76,7 @@ handler.getOwnPropertyDescriptor = (target, p) => { }; /** - * Wraps the result of {@see makeLazy} in a Proxy you can consume as if it wasn't lazy. + * Wraps the result of {@link makeLazy} in a Proxy you can consume as if it wasn't lazy. * On first property access, the lazy is evaluated * @param factory lazy factory * @param attempts how many times to try to evaluate the lazy before giving up diff --git a/src/utils/lazyReact.tsx b/src/utils/lazyReact.tsx index abd300a9..e45ca079 100644 --- a/src/utils/lazyReact.tsx +++ b/src/utils/lazyReact.tsx @@ -16,8 +16,12 @@ const NoopComponent = () => null; */ export function LazyComponent(factory: () => React.ComponentType, attempts = 5) { const get = makeLazy(factory, attempts); - return (props: T) => { + const LazyComponent = (props: T) => { const Component = get() ?? NoopComponent; return ; }; + + LazyComponent.$$get = get; + + return LazyComponent; } diff --git a/src/webpack/common/internal.tsx b/src/webpack/common/internal.tsx index 66c52de0..9a89af36 100644 --- a/src/webpack/common/internal.tsx +++ b/src/webpack/common/internal.tsx @@ -19,9 +19,11 @@ import { LazyComponent } from "@utils/react"; // eslint-disable-next-line path-alias/no-relative -import { FilterFn, filters, waitFor } from "../webpack"; +import { FilterFn, filters, lazyWebpackSearchHistory, waitFor } from "../webpack"; export function waitForComponent = React.ComponentType & Record>(name: string, filter: FilterFn | string | string[]): T { + if (IS_DEV) lazyWebpackSearchHistory.push(["waitForComponent", Array.isArray(filter) ? filter : [filter]]); + let myValue: T = function () { throw new Error(`Vencord could not find the ${name} Component`); } as any; @@ -30,11 +32,13 @@ export function waitForComponent = React.Comp waitFor(filter, (v: any) => { myValue = v; Object.assign(lazyComponent, v); - }); + }, { isIndirect: true }); return lazyComponent; } export function waitForStore(name: string, cb: (v: any) => void) { - waitFor(filters.byStoreName(name), cb); + if (IS_DEV) lazyWebpackSearchHistory.push(["waitForStore", [name]]); + + waitFor(filters.byStoreName(name), cb, { isIndirect: true }); } diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 2a3d4e67..cef4d51d 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -16,14 +16,23 @@ * along with this program. If not, see . */ -import { proxyLazy } from "@utils/lazy"; import type { Channel, User } from "discord-types/general"; // eslint-disable-next-line path-alias/no-relative -import { _resolveReady, find, findByPropsLazy, findLazy, waitFor } from "../webpack"; +import { _resolveReady, findByPropsLazy, findLazy, waitFor } from "../webpack"; import type * as t from "./types/utils"; export let FluxDispatcher: t.FluxDispatcher; + +waitFor(["dispatch", "subscribe"], m => { + FluxDispatcher = m; + const cb = () => { + m.unsubscribe("CONNECTION_OPEN", cb); + _resolveReady(); + }; + m.subscribe("CONNECTION_OPEN", cb); +}); + export let ComponentDispatch; waitFor(["ComponentDispatch", "ComponentDispatcher"], m => ComponentDispatch = m.ComponentDispatch); @@ -41,7 +50,9 @@ export let SnowflakeUtils: t.SnowflakeUtils; waitFor(["fromTimestamp", "extractTimestamp"], m => SnowflakeUtils = m); export let Parser: t.Parser; +waitFor("parseTopic", m => Parser = m); export let Alerts: t.Alerts; +waitFor(["show", "close"], m => Alerts = m); const ToastType = { MESSAGE: 0, @@ -82,6 +93,13 @@ export const Toasts = { } }; +// This is the same module but this is easier +waitFor("showToast", m => { + Toasts.show = m.showToast; + Toasts.pop = m.popToast; +}); + + /** * Show a simple toast. If you need more options, use Toasts.show manually */ @@ -106,26 +124,8 @@ export const Clipboard: t.Clipboard = findByPropsLazy("SUPPORTS_COPY", "copy"); export const NavigationRouter: t.NavigationRouter = findByPropsLazy("transitionTo", "replaceWith", "transitionToGuild"); -waitFor(["dispatch", "subscribe"], m => { - FluxDispatcher = m; - const cb = () => { - m.unsubscribe("CONNECTION_OPEN", cb); - _resolveReady(); - }; - m.subscribe("CONNECTION_OPEN", cb); -}); - - -// This is the same module but this is easier -waitFor("showToast", m => { - Toasts.show = m.showToast; - Toasts.pop = m.popToast; -}); - -waitFor(["show", "close"], m => Alerts = m); -waitFor("parseTopic", m => Parser = m); - export let SettingsRouter: any; waitFor(["open", "saveAccountChanges"], m => SettingsRouter = m); -export const PermissionsBits: t.PermissionsBits = proxyLazy(() => find(m => typeof m.Permissions?.ADMINISTRATOR === "bigint").Permissions); +const { Permissions } = findLazy(m => typeof m.Permissions?.ADMINISTRATOR === "bigint") as { Permissions: t.PermissionsBits; }; +export { Permissions as PermissionsBits }; diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 980288e9..4bdec707 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -127,13 +127,6 @@ export const find = traceFunction("find", function find(filter: FilterFn, { isIn return isWaitFor ? [null, null] : null; }); -/** - * find but lazy - */ -export function findLazy(filter: FilterFn) { - return proxyLazy(() => find(filter)); -} - export function findAll(filter: FilterFn) { if (typeof filter !== "function") throw new Error("Invalid filter. Expected a function got " + typeof filter); @@ -244,6 +237,49 @@ export const findModuleId = traceFunction("findModuleId", function findModuleId( return null; }); +export const lazyWebpackSearchHistory = [] as Array<["find" | "findByProps" | "findByCode" | "findStore" | "findComponent" | "findComponentByCode" | "findExportedComponent" | "waitFor" | "waitForComponent" | "waitForStore" | "proxyLazyWebpack" | "LazyComponentWebpack", any[]]>; + +/** + * This is just a wrapper around {@link proxyLazy} to make our reporter test for your webpack finds. + * + * Wraps the result of {@link makeLazy} in a Proxy you can consume as if it wasn't lazy. + * On first property access, the lazy is evaluated + * @param factory lazy factory + * @param attempts how many times to try to evaluate the lazy before giving up + * @returns Proxy + * + * Note that the example below exists already as an api, see {@link findByPropsLazy} + * @example const mod = proxyLazy(() => findByProps("blah")); console.log(mod.blah); + */ +export function proxyLazyWebpack(factory: () => any, attempts?: number) { + if (IS_DEV) lazyWebpackSearchHistory.push(["proxyLazyWebpack", [factory]]); + + return proxyLazy(factory, attempts); +} + +/** + * This is just a wrapper around {@link LazyComponent} to make our reporter test for your webpack finds. + * + * A lazy component. The factory method is called on first render. + * @param factory Function returning a Component + * @param attempts How many times to try to get the component before giving up + * @returns Result of factory function + */ +export function LazyComponentWebpack(factory: () => any, attempts?: number) { + if (IS_DEV) lazyWebpackSearchHistory.push(["LazyComponentWebpack", [factory]]); + + return LazyComponent(factory, attempts); +} + +/** + * find but lazy + */ +export function findLazy(filter: FilterFn) { + if (IS_DEV) lazyWebpackSearchHistory.push(["find", [filter]]); + + return proxyLazy(() => find(filter)); +} + /** * Find the first module that has the specified properties */ @@ -258,6 +294,8 @@ export function findByProps(...props: string[]) { * findByProps but lazy */ export function findByPropsLazy(...props: string[]) { + if (IS_DEV) lazyWebpackSearchHistory.push(["findByProps", props]); + return proxyLazy(() => findByProps(...props)); } @@ -275,6 +313,8 @@ export function findByCode(...code: string[]) { * findByCode but lazy */ export function findByCodeLazy(...code: string[]) { + if (IS_DEV) lazyWebpackSearchHistory.push(["findByCode", code]); + return proxyLazy(() => findByCode(...code)); } @@ -292,6 +332,8 @@ export function findStore(name: string) { * findStore but lazy */ export function findStoreLazy(name: string) { + if (IS_DEV) lazyWebpackSearchHistory.push(["findStore", [name]]); + return proxyLazy(() => findStore(name)); } @@ -309,6 +351,8 @@ export function findComponentByCode(...code: string[]) { * Finds the first component that matches the filter, lazily. */ export function findComponentLazy(filter: FilterFn) { + if (IS_DEV) lazyWebpackSearchHistory.push(["findComponent", [filter]]); + return LazyComponent(() => find(filter)); } @@ -316,6 +360,8 @@ export function findComponentLazy(filter: FilterFn) { * Finds the first component that includes all the given code, lazily */ export function findComponentByCodeLazy(...code: string[]) { + if (IS_DEV) lazyWebpackSearchHistory.push(["findComponentByCode", code]); + return LazyComponent(() => findComponentByCode(...code)); } @@ -323,6 +369,8 @@ export function findComponentByCodeLazy(...code: string[ * Finds the first component that is exported by the first prop name, lazily */ export function findExportedComponentLazy(...props: string[]) { + if (IS_DEV) lazyWebpackSearchHistory.push(["findExportedComponent", props]); + return LazyComponent(() => findByProps(...props)?.[props[0]]); } @@ -330,7 +378,9 @@ export function findExportedComponentLazy(...props: stri * Wait for a module that matches the provided filter to be registered, * then call the callback with the module as the first argument */ -export function waitFor(filter: string | string[] | FilterFn, callback: CallbackFn) { +export function waitFor(filter: string | string[] | FilterFn, callback: CallbackFn, { isIndirect = false }: { isIndirect?: boolean; } = {}) { + if (IS_DEV && !isIndirect) lazyWebpackSearchHistory.push(["waitFor", Array.isArray(filter) ? filter : [filter]]); + if (typeof filter === "string") filter = filters.byProps(filter); else if (Array.isArray(filter)) From 598ffe6368bf454b65c522d4dfbca8a1afc9a05c Mon Sep 17 00:00:00 2001 From: V Date: Sat, 25 Nov 2023 01:51:50 +0100 Subject: [PATCH 0248/1143] reporter: remove sleeps --- scripts/generateReport.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts index cadf0c2a..3cbab55e 100644 --- a/scripts/generateReport.ts +++ b/scripts/generateReport.ts @@ -338,8 +338,6 @@ function runTime(token: string) { .then(r => r.text()) .then(t => t.includes(".module.wasm") || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push")); - await new Promise(r => setTimeout(r, 150)); - if (isWasm) { invalidChunks.push(id); continue chunksLoop; @@ -371,8 +369,6 @@ function runTime(token: string) { // Loads a chunk if (!isWasm) await wreq.e(id as any); - - await new Promise(r => setTimeout(r, 150)); } // Make sure every chunk has finished loading From 68fca7854179222ba29cb7428f34874f3576fe05 Mon Sep 17 00:00:00 2001 From: V Date: Sat, 25 Nov 2023 01:56:29 +0100 Subject: [PATCH 0249/1143] reporter: test dev instead of main --- .github/workflows/reportBrokenPlugins.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/reportBrokenPlugins.yml b/.github/workflows/reportBrokenPlugins.yml index 13202200..4b09463e 100644 --- a/.github/workflows/reportBrokenPlugins.yml +++ b/.github/workflows/reportBrokenPlugins.yml @@ -12,6 +12,12 @@ jobs: steps: - uses: actions/checkout@v3 + if: ${{ github.event_name == 'schedule' }} + with: + ref: dev + + - uses: actions/checkout@v3 + if: ${{ github.event_name == 'workflow_dispatch' }} - uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json From 7c3b247d84c01882830e754e0e2995c36e4a2a33 Mon Sep 17 00:00:00 2001 From: V Date: Sat, 25 Nov 2023 02:43:29 +0100 Subject: [PATCH 0250/1143] fix WebContextMenus --- src/plugins/webContextMenus.web/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index aa575bfd..cf50bb86 100644 --- a/src/plugins/webContextMenus.web/index.ts +++ b/src/plugins/webContextMenus.web/index.ts @@ -118,10 +118,11 @@ export default definePlugin({ // Add back image context menu { find: 'navId:"image-context"', + all: true, predicate: () => settings.store.addBack, replacement: { - // return IS_DESKTOP && null != ... ? React.createElement(Menu, ...) - match: /return \i\.\i(?=&&null)/, + // return IS_DESKTOP ? React.createElement(Menu, ...) + match: /return \i\.\i(?=\?|&&)/, replace: "return true" } }, From 604cf002116e4172058b2f1c8a6df4a187cf61ab Mon Sep 17 00:00:00 2001 From: V Date: Sat, 25 Nov 2023 02:51:19 +0100 Subject: [PATCH 0251/1143] reporter: fix markdown output --- scripts/generateReport.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts index 3cbab55e..54097671 100644 --- a/scripts/generateReport.ts +++ b/scripts/generateReport.ts @@ -226,7 +226,7 @@ page.on("console", async e => { } if (isDebug) { - console.log(e.text()); + console.error(e.text()); } else if (level === "error") { const text = await Promise.all( e.args().map(async a => { From 6bbf562ab6b92ad7cc267cfa53aec6a88b7c006d Mon Sep 17 00:00:00 2001 From: V Date: Sat, 25 Nov 2023 02:51:58 +0100 Subject: [PATCH 0252/1143] bump to v1.6.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 23d9dd27..4d0dd262 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.6.3", + "version": "1.6.4", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From ec16fd874189411479890e00d93770001f9d09ab Mon Sep 17 00:00:00 2001 From: V Date: Sat, 25 Nov 2023 02:55:59 +0100 Subject: [PATCH 0253/1143] fix ci --- .github/workflows/test.yml | 2 ++ src/utils/lazyReact.tsx | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 46d56418..a756681c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,9 +3,11 @@ on: push: branches: - main + - dev pull_request: branches: - main + - dev jobs: test: runs-on: ubuntu-latest diff --git a/src/utils/lazyReact.tsx b/src/utils/lazyReact.tsx index e45ca079..da36d4e7 100644 --- a/src/utils/lazyReact.tsx +++ b/src/utils/lazyReact.tsx @@ -4,6 +4,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { ComponentType } from "react"; + import { makeLazy } from "./lazy"; const NoopComponent = () => null; @@ -23,5 +25,5 @@ export function LazyComponent(factory: () => React.Compo LazyComponent.$$get = get; - return LazyComponent; + return LazyComponent as ComponentType; } From 6573c4757cc1b2e3847c037d254cbdd11653b579 Mon Sep 17 00:00:00 2001 From: Korbo Date: Tue, 28 Nov 2023 12:32:29 -0600 Subject: [PATCH 0254/1143] fix: strikethrough in SilentMessageToggle's disabled SVG (#2004) --- src/plugins/silentMessageToggle/index.tsx | 19 +++++++++---------- src/utils/constants.ts | 4 ++++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/plugins/silentMessageToggle/index.tsx b/src/plugins/silentMessageToggle/index.tsx index a1ee8de8..b7b33826 100644 --- a/src/plugins/silentMessageToggle/index.tsx +++ b/src/plugins/silentMessageToggle/index.tsx @@ -80,16 +80,15 @@ function SilentMessageToggle(chatBoxProps: { style={{ padding: "0 6px" }} >
- - - - - {!enabled && } - + + + {!enabled && <> + + + + + + }
diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 7f555d32..0ff7da72 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -387,6 +387,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "ant0n", id: 145224646868860928n }, + Korbo: { + name: "Korbo", + id: 455856406420258827n + }, } satisfies Record); // iife so #__PURE__ works correctly From 1b179f3c6d2b109251a9abcedfa90666ba0989dd Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 24 Nov 2023 23:14:18 -0300 Subject: [PATCH 0255/1143] Simplify some components finds; Make undo of patch groups more clear --- src/plugins/typingIndicator/index.tsx | 9 ++------- src/plugins/vencordToolbox/index.tsx | 7 ++----- src/webpack/patchWebpack.ts | 4 ++-- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/plugins/typingIndicator/index.tsx b/src/plugins/typingIndicator/index.tsx index e35eb9b6..c5cf5a9d 100644 --- a/src/plugins/typingIndicator/index.tsx +++ b/src/plugins/typingIndicator/index.tsx @@ -20,17 +20,12 @@ import { definePluginSettings, Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { find, findStoreLazy, LazyComponentWebpack } from "@webpack"; +import { findExportedComponentLazy, findStoreLazy } from "@webpack"; import { ChannelStore, GuildMemberStore, i18n, RelationshipStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; import { buildSeveralUsers } from "../typingTweaks"; -const ThreeDots = LazyComponentWebpack(() => { - // This doesn't really need to explicitly find Dots' own module, but it's fine - const res = find(m => m.Dots && !m.Menu); - - return res?.Dots; -}); +const ThreeDots = findExportedComponentLazy("Dots", "AnimatedDots"); const TypingStore = findStoreLazy("TypingStore"); const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore"); diff --git a/src/plugins/vencordToolbox/index.tsx b/src/plugins/vencordToolbox/index.tsx index bb63a86b..2b0ce14e 100644 --- a/src/plugins/vencordToolbox/index.tsx +++ b/src/plugins/vencordToolbox/index.tsx @@ -23,14 +23,11 @@ import { Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; -import { filters, find, LazyComponentWebpack } from "@webpack"; +import { findExportedComponentLazy } from "@webpack"; import { Menu, Popout, useState } from "@webpack/common"; import type { ReactNode } from "react"; -const HeaderBarIcon = LazyComponentWebpack(() => { - const filter = filters.byCode(".HEADER_BAR_BADGE"); - return find(m => m.Icon && filter(m.Icon))?.Icon; -}); +const HeaderBarIcon = findExportedComponentLazy("Icon", "Divider"); function VencordPopout(onClose: () => void) { const pluginEntries = [] as ReactNode[]; diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index b992cfde..76a9fb0f 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -212,7 +212,7 @@ function patchFactories(factories: Record Date: Mon, 27 Nov 2023 02:56:57 -0300 Subject: [PATCH 0256/1143] Utility function for loading Discord chunks (#2017) --- scripts/generateReport.ts | 8 ++- src/utils/lazyReact.tsx | 2 +- src/webpack/patchWebpack.ts | 8 +-- src/webpack/webpack.ts | 127 ++++++++++++++++++++++++++++++------ 4 files changed, 118 insertions(+), 27 deletions(-) diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts index 54097671..719a8456 100644 --- a/scripts/generateReport.ts +++ b/scripts/generateReport.ts @@ -406,15 +406,21 @@ function runTime(token: string) { if (method === "proxyLazyWebpack" || method === "LazyComponentWebpack") { const [factory] = args; result = factory(); + } else if (method === "extractAndLoadChunks") { + const [code, matcher] = args; + + const module = Vencord.Webpack.findModuleFactory(...code); + if (module) result = module.toString().match(matcher); } else { // @ts-ignore result = Vencord.Webpack[method](...args); } - if (result == null || ("$$get" in result && result.$$get() == null)) throw "a rock at ben shapiro"; + if (result == null || ("$$vencordInternal" in result && result.$$vencordInternal() == null)) throw "a rock at ben shapiro"; } catch (e) { let logMessage = searchType; if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`; + else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`; else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`; console.log("[PUP_WEBPACK_FIND_FAIL]", logMessage); diff --git a/src/utils/lazyReact.tsx b/src/utils/lazyReact.tsx index da36d4e7..4896a058 100644 --- a/src/utils/lazyReact.tsx +++ b/src/utils/lazyReact.tsx @@ -23,7 +23,7 @@ export function LazyComponent(factory: () => React.Compo return ; }; - LazyComponent.$$get = get; + LazyComponent.$$vencordInternal = get; return LazyComponent as ComponentType; } diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index 76a9fb0f..f131471b 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -152,11 +152,9 @@ function patchFactories(factories: Record(); export const listeners = new Set(); -export type CallbackFn = (mod: any, id: number) => void; +export type CallbackFn = (mod: any, id: string) => void; export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) { if (cache !== void 0) throw "no."; @@ -111,12 +112,12 @@ export const find = traceFunction("find", function find(filter: FilterFn, { isIn if (!mod?.exports) continue; if (filter(mod.exports)) { - return isWaitFor ? [mod.exports, Number(key)] : mod.exports; + return isWaitFor ? [mod.exports, key] : mod.exports; } if (mod.exports.default && filter(mod.exports.default)) { const found = mod.exports.default; - return isWaitFor ? [found, Number(key)] : found; + return isWaitFor ? [found, key] : found; } } @@ -214,18 +215,21 @@ export const findBulk = traceFunction("findBulk", function findBulk(...filterFns }); /** - * Find the id of a module by its code - * @param code Code - * @returns number or null + * Find the id of the first module factory that includes all the given code + * @returns string or null */ -export const findModuleId = traceFunction("findModuleId", function findModuleId(code: string) { +export const findModuleId = traceFunction("findModuleId", function findModuleId(...code: string[]) { + outer: for (const id in wreq.m) { - if (wreq.m[id].toString().includes(code)) { - return Number(id); + const str = wreq.m[id].toString(); + + for (const c of code) { + if (!str.includes(c)) continue outer; } + return id; } - const err = new Error("Didn't find module with code:\n" + code); + const err = new Error("Didn't find module with code(s):\n" + code.join("\n")); if (IS_DEV) { if (!devToolsOpen) // Strict behaviour in DevBuilds to fail early and make sure the issue is found @@ -237,7 +241,18 @@ export const findModuleId = traceFunction("findModuleId", function findModuleId( return null; }); -export const lazyWebpackSearchHistory = [] as Array<["find" | "findByProps" | "findByCode" | "findStore" | "findComponent" | "findComponentByCode" | "findExportedComponent" | "waitFor" | "waitForComponent" | "waitForStore" | "proxyLazyWebpack" | "LazyComponentWebpack", any[]]>; +/** + * Find the first module factory that includes all the given code + * @returns The module factory or null + */ +export function findModuleFactory(...code: string[]) { + const id = findModuleId(...code); + if (!id) return null; + + return wreq.m[id]; +} + +export const lazyWebpackSearchHistory = [] as Array<["find" | "findByProps" | "findByCode" | "findStore" | "findComponent" | "findComponentByCode" | "findExportedComponent" | "waitFor" | "waitForComponent" | "waitForStore" | "proxyLazyWebpack" | "LazyComponentWebpack" | "extractAndLoadChunks", any[]]>; /** * This is just a wrapper around {@link proxyLazy} to make our reporter test for your webpack finds. @@ -272,7 +287,7 @@ export function LazyComponentWebpack(factory: () => any, } /** - * find but lazy + * Find the first module that matches the filter, lazily */ export function findLazy(filter: FilterFn) { if (IS_DEV) lazyWebpackSearchHistory.push(["find", [filter]]); @@ -291,7 +306,7 @@ export function findByProps(...props: string[]) { } /** - * findByProps but lazy + * Find the first module that has the specified properties, lazily */ export function findByPropsLazy(...props: string[]) { if (IS_DEV) lazyWebpackSearchHistory.push(["findByProps", props]); @@ -300,7 +315,7 @@ export function findByPropsLazy(...props: string[]) { } /** - * Find a function by its code + * Find the first function that includes all the given code */ export function findByCode(...code: string[]) { const res = find(filters.byCode(...code), { isIndirect: true }); @@ -310,7 +325,7 @@ export function findByCode(...code: string[]) { } /** - * findByCode but lazy + * Find the first function that includes all the given code, lazily */ export function findByCodeLazy(...code: string[]) { if (IS_DEV) lazyWebpackSearchHistory.push(["findByCode", code]); @@ -329,7 +344,7 @@ export function findStore(name: string) { } /** - * findStore but lazy + * Find a store by its displayName, lazily */ export function findStoreLazy(name: string) { if (IS_DEV) lazyWebpackSearchHistory.push(["findStore", [name]]); @@ -353,7 +368,13 @@ export function findComponentByCode(...code: string[]) { export function findComponentLazy(filter: FilterFn) { if (IS_DEV) lazyWebpackSearchHistory.push(["findComponent", [filter]]); - return LazyComponent(() => find(filter)); + + return LazyComponent(() => { + const res = find(filter, { isIndirect: true }); + if (!res) + handleModuleNotFound("findComponent", filter); + return res; + }); } /** @@ -362,7 +383,12 @@ export function findComponentLazy(filter: FilterFn) { export function findComponentByCodeLazy(...code: string[]) { if (IS_DEV) lazyWebpackSearchHistory.push(["findComponentByCode", code]); - return LazyComponent(() => findComponentByCode(...code)); + return LazyComponent(() => { + const res = find(filters.componentByCode(...code), { isIndirect: true }); + if (!res) + handleModuleNotFound("findComponentByCode", ...code); + return res; + }); } /** @@ -371,7 +397,68 @@ export function findComponentByCodeLazy(...code: string[ export function findExportedComponentLazy(...props: string[]) { if (IS_DEV) lazyWebpackSearchHistory.push(["findExportedComponent", props]); - return LazyComponent(() => findByProps(...props)?.[props[0]]); + return LazyComponent(() => { + const res = find(filters.byProps(...props), { isIndirect: true }); + if (!res) + handleModuleNotFound("findExportedComponent", ...props); + return res[props[0]]; + }); +} + +/** + * Extract and load chunks using their entry point + * @param code An array of all the code the module factory containing the entry point (as of using it to load chunks) must include + * @param matcher A RegExp that returns the entry point id as the first capture group. Defaults to a matcher that captures the first entry point found in the module factory + */ +export async function extractAndLoadChunks(code: string[], matcher: RegExp = /\.el\("(.+?)"\)(?<=(\i)\.el.+?)\.then\(\2\.bind\(\2,"\1"\)\)/) { + const module = findModuleFactory(...code); + if (!module) { + const err = new Error("extractAndLoadChunks: Couldn't find module factory"); + logger.warn(err, "Code:", code, "Matcher:", matcher); + + return; + } + + const match = module.toString().match(canonicalizeMatch(matcher)); + if (!match) { + const err = new Error("extractAndLoadChunks: Couldn't find entry point id in module factory code"); + logger.warn(err, "Code:", code, "Matcher:", matcher); + + // Strict behaviour in DevBuilds to fail early and make sure the issue is found + if (IS_DEV && !devToolsOpen) + throw err; + + return; + } + + const [, id] = match; + if (!id || !Number(id)) { + const err = new Error("extractAndLoadChunks: Matcher didn't return a capturing group with the entry point, or the entry point returned wasn't a number"); + logger.warn(err, "Code:", code, "Matcher:", matcher); + + // Strict behaviour in DevBuilds to fail early and make sure the issue is found + if (IS_DEV && !devToolsOpen) + throw err; + + return; + } + + await (wreq as any).el(id); + return wreq(id as any); +} + +/** + * This is just a wrapper around {@link extractAndLoadChunks} to make our reporter test for your webpack finds. + * + * Extract and load chunks using their entry point + * @param code An array of all the code the module factory containing the entry point (as of using it to load chunks) must include + * @param matcher A RegExp that returns the entry point id as the first capture group. Defaults to a matcher that captures the first entry point found in the module factory + * @returns A function that loads the chunks on first call + */ +export function extractAndLoadChunksLazy(code: string[], matcher: RegExp = /\.el\("(.+?)"\)(?<=(\i)\.el.+?)\.then\(\2\.bind\(\2,"\1"\)\)/) { + if (IS_DEV) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]); + + return () => extractAndLoadChunks(code, matcher); } /** @@ -433,7 +520,7 @@ export function search(...filters: Array) { * so putting breakpoints or similar will have no effect. * @param id The id of the module to extract */ -export function extract(id: number) { +export function extract(id: string | number) { const mod = wreq.m[id] as Function; if (!mod) return null; From f814eeb74c3a562b0ad632c808f63dfddd53f59b Mon Sep 17 00:00:00 2001 From: V Date: Mon, 27 Nov 2023 16:05:25 +0100 Subject: [PATCH 0257/1143] VoiceMessages: fix preview being blank --- src/plugins/voiceMessages/VoicePreview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/voiceMessages/VoicePreview.tsx b/src/plugins/voiceMessages/VoicePreview.tsx index 0976f794..9c77d832 100644 --- a/src/plugins/voiceMessages/VoicePreview.tsx +++ b/src/plugins/voiceMessages/VoicePreview.tsx @@ -25,7 +25,7 @@ interface VoiceMessageProps { src: string; waveform: string; } -const VoiceMessage = findComponentByCodeLazy("waveform:"); +const VoiceMessage = findComponentByCodeLazy("waveform:", "onVolumeChange"); export type VoicePreviewOptions = { src?: string; From 597a74ff6c740e0ff26ca53a56f175b6d06e2e47 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Tue, 28 Nov 2023 17:05:11 -0300 Subject: [PATCH 0258/1143] ClientTheme: make color picker finder more specific --- src/plugins/clientTheme/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/clientTheme/index.tsx b/src/plugins/clientTheme/index.tsx index 7b30863e..d7592996 100644 --- a/src/plugins/clientTheme/index.tsx +++ b/src/plugins/clientTheme/index.tsx @@ -15,7 +15,7 @@ import definePlugin, { OptionType, StartAt } from "@utils/types"; import { findComponentByCodeLazy } from "@webpack"; import { Button, Forms } from "@webpack/common"; -const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR"); +const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)"); const colorPresets = [ "#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D", From 9b6308a8355910f5911c1202e6a409636cb9e553 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Tue, 28 Nov 2023 22:12:00 -0300 Subject: [PATCH 0259/1143] Fix a console shortcut and suppressExperimentalWarnings on more scripts --- package.json | 4 ++-- src/plugins/consoleShortcuts/index.ts | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 4d0dd262..d035dcb6 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "doc": "docs" }, "scripts": { - "build": "node scripts/build/build.mjs", + "build": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs", "buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.mjs", "generatePluginJson": "tsx scripts/generatePluginList.ts", "inject": "node scripts/runInstaller.mjs", @@ -28,7 +28,7 @@ "testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc", "testTsc": "tsc --noEmit", "uninject": "node scripts/runInstaller.mjs", - "watch": "node scripts/build/build.mjs --watch" + "watch": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs --watch" }, "dependencies": { "@sapphi-red/web-noise-suppressor": "0.3.3", diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts index 10853f25..e25e7cb3 100644 --- a/src/plugins/consoleShortcuts/index.ts +++ b/src/plugins/consoleShortcuts/index.ts @@ -63,6 +63,7 @@ export default definePlugin({ let fakeRenderWin: WeakRef | undefined; const find = newFindWrapper(f => f); + const findByProps = newFindWrapper(filters.byProps); return { ...Vencord.Webpack.Common, wp: Vencord.Webpack, @@ -73,13 +74,13 @@ export default definePlugin({ wpexs: (code: string) => extract(Webpack.findModuleId(code)!), find, findAll, - findByProps: newFindWrapper(filters.byProps), + findByProps, findAllByProps: (...props: string[]) => findAll(filters.byProps(...props)), findByCode: newFindWrapper(filters.byCode), findAllByCode: (code: string) => findAll(filters.byCode(code)), findComponentByCode: newFindWrapper(filters.componentByCode), findAllComponentsByCode: (...code: string[]) => findAll(filters.componentByCode(...code)), - findExportedComponent: (...props: string[]) => find(...props)[props[0]], + findExportedComponent: (...props: string[]) => findByProps(...props)[props[0]], findStore: newFindWrapper(filters.byStoreName), PluginsApi: Vencord.Plugins, plugins: Vencord.Plugins.plugins, From 091d29bf5e89f1a41f5528c520bb6cfac654b3e4 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 29 Nov 2023 16:14:05 -0300 Subject: [PATCH 0260/1143] CrashHandler: attempt to prevent more crashes --- src/plugins/crashHandler/index.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/plugins/crashHandler/index.ts b/src/plugins/crashHandler/index.ts index a1ba01c3..202cac04 100644 --- a/src/plugins/crashHandler/index.ts +++ b/src/plugins/crashHandler/index.ts @@ -24,11 +24,14 @@ import { closeAllModals } from "@utils/modal"; import definePlugin, { OptionType } from "@utils/types"; import { maybePromptToUpdate } from "@utils/updater"; import { findByPropsLazy } from "@webpack"; -import { FluxDispatcher, NavigationRouter } from "@webpack/common"; +import { FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common"; import type { ReactElement } from "react"; const CrashHandlerLogger = new Logger("CrashHandler"); const ModalStack = findByPropsLazy("pushLazy", "popAll"); +const DraftManager = findByPropsLazy("clearDraft", "saveDraft"); +const { DraftType } = findByPropsLazy("DraftType"); +const { closeExpressionPicker } = findByPropsLazy("closeExpressionPicker", "openExpressionPicker"); const settings = definePluginSettings({ attemptToPreventCrashes: { @@ -115,6 +118,20 @@ export default definePlugin({ } catch { } } + try { + const channelId = SelectedChannelStore.getChannelId(); + + DraftManager.clearDraft(channelId, DraftType.ChannelMessage); + DraftManager.clearDraft(channelId, DraftType.FirstThreadMessage); + } catch (err) { + CrashHandlerLogger.debug("Failed to clear drafts.", err); + } + try { + closeExpressionPicker(); + } + catch (err) { + CrashHandlerLogger.debug("Failed to close expression picker.", err); + } try { FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" }); } catch (err) { From 9945219de70fb70f43577c7df2c231d5b82a23de Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 29 Nov 2023 23:14:52 -0300 Subject: [PATCH 0261/1143] openInviteModal utility Co-authored-by: AutumnVN --- src/api/Commands/commandHelpers.ts | 4 ++-- src/plugins/greetStickerPicker/index.tsx | 2 +- src/plugins/spotifyShareCommands/index.ts | 4 ++-- src/plugins/voiceMessages/index.tsx | 4 ++-- src/utils/discord.tsx | 17 +++++++++++++++-- 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/api/Commands/commandHelpers.ts b/src/api/Commands/commandHelpers.ts index 2fd18903..ebcc4e2f 100644 --- a/src/api/Commands/commandHelpers.ts +++ b/src/api/Commands/commandHelpers.ts @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import { MessageActions } from "@utils/discord"; import { mergeDefaults } from "@utils/misc"; import { findByPropsLazy } from "@webpack"; import { SnowflakeUtils } from "@webpack/common"; @@ -24,7 +25,6 @@ import type { PartialDeep } from "type-fest"; import { Argument } from "./types"; -const MessageCreator = findByPropsLazy("createBotMessage"); const MessageSender = findByPropsLazy("receiveMessage"); export function generateId() { @@ -38,7 +38,7 @@ export function generateId() { * @returns {Message} */ export function sendBotMessage(channelId: string, message: PartialDeep): Message { - const botMessage = MessageCreator.createBotMessage({ channelId, content: "", embeds: [] }); + const botMessage = MessageActions.createBotMessage({ channelId, content: "", embeds: [] }); MessageSender.receiveMessage(channelId, mergeDefaults(message, botMessage)); diff --git a/src/plugins/greetStickerPicker/index.tsx b/src/plugins/greetStickerPicker/index.tsx index 9623d422..c2104af4 100644 --- a/src/plugins/greetStickerPicker/index.tsx +++ b/src/plugins/greetStickerPicker/index.tsx @@ -18,6 +18,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; +import { MessageActions } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; import { ContextMenuApi, FluxDispatcher, Menu } from "@webpack/common"; @@ -49,7 +50,6 @@ const settings = definePluginSettings({ unholyMultiGreetEnabled?: boolean; }>(); -const MessageActions = findByPropsLazy("sendGreetMessage"); const { WELCOME_STICKERS } = findByPropsLazy("WELCOME_STICKERS"); function greet(channel: Channel, message: Message, stickers: string[]) { diff --git a/src/plugins/spotifyShareCommands/index.ts b/src/plugins/spotifyShareCommands/index.ts index 7634e9d5..3569dd28 100644 --- a/src/plugins/spotifyShareCommands/index.ts +++ b/src/plugins/spotifyShareCommands/index.ts @@ -18,6 +18,7 @@ import { ApplicationCommandInputType, sendBotMessage } from "@api/Commands"; import { Devs } from "@utils/constants"; +import { MessageActions } from "@utils/discord"; import definePlugin from "@utils/types"; import { findByPropsLazy } from "@webpack"; import { FluxDispatcher } from "@webpack/common"; @@ -53,7 +54,6 @@ interface Track { } const Spotify = findByPropsLazy("getPlayerState"); -const MessageCreator = findByPropsLazy("getSendMessageOptionsForReply", "sendMessage"); const PendingReplyStore = findByPropsLazy("getPendingReply"); function sendMessage(channelId, message) { @@ -65,7 +65,7 @@ function sendMessage(channelId, message) { ...message }; const reply = PendingReplyStore.getPendingReply(channelId); - MessageCreator.sendMessage(channelId, message, void 0, MessageCreator.getSendMessageOptionsForReply(reply)) + MessageActions.sendMessage(channelId, message, void 0, MessageActions.getSendMessageOptionsForReply(reply)) .then(() => { if (reply) { FluxDispatcher.dispatch({ type: "DELETE_PENDING_REPLY", channelId }); diff --git a/src/plugins/voiceMessages/index.tsx b/src/plugins/voiceMessages/index.tsx index 7c8a0694..17e10a4b 100644 --- a/src/plugins/voiceMessages/index.tsx +++ b/src/plugins/voiceMessages/index.tsx @@ -21,6 +21,7 @@ import "./styles.css"; import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; import { Microphone } from "@components/Icons"; import { Devs } from "@utils/constants"; +import { MessageActions } from "@utils/discord"; import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; import definePlugin from "@utils/types"; @@ -36,7 +37,6 @@ import { VoicePreview } from "./VoicePreview"; import { VoiceRecorderWeb } from "./WebRecorder"; const CloudUtils = findByPropsLazy("CloudUpload"); -const MessageCreator = findByPropsLazy("getSendMessageOptionsForReply", "sendMessage"); const PendingReplyStore = findStoreLazy("PendingReplyStore"); const OptionClasses = findByPropsLazy("optionName", "optionIcon", "optionLabel"); @@ -100,7 +100,7 @@ function sendAudio(blob: Blob, meta: AudioMetadata) { waveform: meta.waveform, duration_secs: meta.duration, }], - message_reference: reply ? MessageCreator.getSendMessageOptionsForReply(reply)?.messageReference : null, + message_reference: reply ? MessageActions.getSendMessageOptionsForReply(reply)?.messageReference : null, } }); }); diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx index 96193f21..41e18f9d 100644 --- a/src/utils/discord.tsx +++ b/src/utils/discord.tsx @@ -23,8 +23,21 @@ import { Guild, Message, User } from "discord-types/general"; import { ImageModal, ModalRoot, ModalSize, openModal } from "./modal"; -const MessageActions = findByPropsLazy("editMessage", "sendMessage"); -const UserProfileActions = findByPropsLazy("openUserProfileModal", "closeUserProfileModal"); +export const MessageActions = findByPropsLazy("editMessage", "sendMessage"); +export const UserProfileActions = findByPropsLazy("openUserProfileModal", "closeUserProfileModal"); +export const InviteActions = findByPropsLazy("resolveInvite"); + +export async function openInviteModal(code: string) { + const { invite } = await InviteActions.resolveInvite(code, "Desktop Modal"); + if (!invite) throw new Error("Invalid invite: " + code); + + FluxDispatcher.dispatch({ + type: "INVITE_MODAL_OPEN", + invite, + code, + context: "APP" + }); +} export function getCurrentChannel() { return ChannelStore.getChannel(SelectedChannelStore.getChannelId()); From 8ef1882d4375c18b485d5afa0432b938e04d27e6 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Thu, 30 Nov 2023 00:43:23 -0300 Subject: [PATCH 0262/1143] use findBulk on CrashHandler and cleaups --- src/plugins/crashHandler/index.ts | 23 ++++++++++++++----- .../components/UserPermissions.tsx | 10 ++++---- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/plugins/crashHandler/index.ts b/src/plugins/crashHandler/index.ts index 202cac04..9d38b7d1 100644 --- a/src/plugins/crashHandler/index.ts +++ b/src/plugins/crashHandler/index.ts @@ -23,15 +23,26 @@ import { Logger } from "@utils/Logger"; import { closeAllModals } from "@utils/modal"; import definePlugin, { OptionType } from "@utils/types"; import { maybePromptToUpdate } from "@utils/updater"; -import { findByPropsLazy } from "@webpack"; +import { filters, findBulk, proxyLazyWebpack } from "@webpack"; import { FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common"; import type { ReactElement } from "react"; const CrashHandlerLogger = new Logger("CrashHandler"); -const ModalStack = findByPropsLazy("pushLazy", "popAll"); -const DraftManager = findByPropsLazy("clearDraft", "saveDraft"); -const { DraftType } = findByPropsLazy("DraftType"); -const { closeExpressionPicker } = findByPropsLazy("closeExpressionPicker", "openExpressionPicker"); +const { ModalStack, DraftManager, DraftType, closeExpressionPicker } = proxyLazyWebpack(() => { + const modules = findBulk( + filters.byProps("pushLazy", "popAll"), + filters.byProps("clearDraft", "saveDraft"), + filters.byProps("DraftType"), + filters.byProps("closeExpressionPicker", "openExpressionPicker"), + ); + + return { + ModalStack: modules[0], + DraftManager: modules[1], + DraftType: modules[2]?.DraftType, + closeExpressionPicker: modules[3]?.closeExpressionPicker, + }; +}); const settings = definePluginSettings({ attemptToPreventCrashes: { @@ -138,7 +149,7 @@ export default definePlugin({ CrashHandlerLogger.debug("Failed to close open context menu.", err); } try { - ModalStack?.popAll(); + ModalStack.popAll(); } catch (err) { CrashHandlerLogger.debug("Failed to close old modals.", err); } diff --git a/src/plugins/permissionsViewer/components/UserPermissions.tsx b/src/plugins/permissionsViewer/components/UserPermissions.tsx index b75bafdc..3c676771 100644 --- a/src/plugins/permissionsViewer/components/UserPermissions.tsx +++ b/src/plugins/permissionsViewer/components/UserPermissions.tsx @@ -35,15 +35,13 @@ interface UserPermission { type UserPermissions = Array; -const Classes = proxyLazyWebpack(() => { - const modules = findBulk( +const Classes = proxyLazyWebpack(() => + Object.assign({}, ...findBulk( filters.byProps("roles", "rolePill", "rolePillBorder"), filters.byProps("roleCircle", "dotBorderBase", "dotBorderColor"), filters.byProps("roleNameOverflow", "root", "roleName", "roleRemoveButton") - ); - - return Object.assign({}, ...modules); -}) as Record<"roles" | "rolePill" | "rolePillBorder" | "desaturateUserColors" | "flex" | "alignCenter" | "justifyCenter" | "svg" | "background" | "dot" | "dotBorderColor" | "roleCircle" | "dotBorderBase" | "flex" | "alignCenter" | "justifyCenter" | "wrap" | "root" | "role" | "roleRemoveButton" | "roleDot" | "roleFlowerStar" | "roleRemoveIcon" | "roleRemoveIconFocused" | "roleVerifiedIcon" | "roleName" | "roleNameOverflow" | "actionButton" | "overflowButton" | "addButton" | "addButtonIcon" | "overflowRolesPopout" | "overflowRolesPopoutArrowWrapper" | "overflowRolesPopoutArrow" | "popoutBottom" | "popoutTop" | "overflowRolesPopoutHeader" | "overflowRolesPopoutHeaderIcon" | "overflowRolesPopoutHeaderText" | "roleIcon", string>; + )) +) as Record<"roles" | "rolePill" | "rolePillBorder" | "desaturateUserColors" | "flex" | "alignCenter" | "justifyCenter" | "svg" | "background" | "dot" | "dotBorderColor" | "roleCircle" | "dotBorderBase" | "flex" | "alignCenter" | "justifyCenter" | "wrap" | "root" | "role" | "roleRemoveButton" | "roleDot" | "roleFlowerStar" | "roleRemoveIcon" | "roleRemoveIconFocused" | "roleVerifiedIcon" | "roleName" | "roleNameOverflow" | "actionButton" | "overflowButton" | "addButton" | "addButtonIcon" | "overflowRolesPopout" | "overflowRolesPopoutArrowWrapper" | "overflowRolesPopoutArrow" | "popoutBottom" | "popoutTop" | "overflowRolesPopoutHeader" | "overflowRolesPopoutHeaderIcon" | "overflowRolesPopoutHeaderText" | "roleIcon", string>; function UserPermissionsComponent({ guild, guildMember, showBorder }: { guild: Guild; guildMember: GuildMember; showBorder: boolean; }) { const stns = settings.use(["permissionsSortOrder"]); From b47a5f569e3684857048207f0da0d7f6de80c78a Mon Sep 17 00:00:00 2001 From: Jack <30497388+FieryFlames@users.noreply.github.com> Date: Thu, 30 Nov 2023 00:10:50 -0500 Subject: [PATCH 0263/1143] feat: Add Decor plugin (#910) --- package.json | 3 +- pnpm-lock.yaml | 21 +- src/components/Icons.tsx | 35 +++ src/plugins/decor/README.md | 17 ++ src/plugins/decor/index.tsx | 168 +++++++++++ src/plugins/decor/lib/api.ts | 83 ++++++ src/plugins/decor/lib/constants.ts | 16 ++ .../decor/lib/stores/AuthorizationStore.tsx | 102 +++++++ .../lib/stores/CurrentUserDecorationsStore.ts | 56 ++++ .../decor/lib/stores/UsersDecorationsStore.ts | 118 ++++++++ src/plugins/decor/lib/utils/decoration.ts | 17 ++ .../DecorDecorationGridDecoration.tsx | 35 +++ .../decor/ui/components/DecorSection.tsx | 59 ++++ .../ui/components/DecorationContextMenu.tsx | 47 +++ .../ui/components/DecorationGridCreate.tsx | 30 ++ .../ui/components/DecorationGridNone.tsx | 30 ++ src/plugins/decor/ui/components/Grid.tsx | 28 ++ .../decor/ui/components/SectionedGridList.tsx | 38 +++ src/plugins/decor/ui/components/index.ts | 33 +++ src/plugins/decor/ui/index.ts | 13 + .../decor/ui/modals/ChangeDecorationModal.tsx | 270 ++++++++++++++++++ .../decor/ui/modals/CreateDecorationModal.tsx | 163 +++++++++++ src/plugins/decor/ui/styles.css | 80 ++++++ src/utils/cloud.tsx | 5 +- src/utils/discord.tsx | 24 +- src/webpack/common/components.ts | 4 +- src/webpack/common/types/components.d.ts | 1 + src/webpack/common/utils.ts | 10 +- tsconfig.json | 1 + 29 files changed, 1493 insertions(+), 14 deletions(-) create mode 100644 src/plugins/decor/README.md create mode 100644 src/plugins/decor/index.tsx create mode 100644 src/plugins/decor/lib/api.ts create mode 100644 src/plugins/decor/lib/constants.ts create mode 100644 src/plugins/decor/lib/stores/AuthorizationStore.tsx create mode 100644 src/plugins/decor/lib/stores/CurrentUserDecorationsStore.ts create mode 100644 src/plugins/decor/lib/stores/UsersDecorationsStore.ts create mode 100644 src/plugins/decor/lib/utils/decoration.ts create mode 100644 src/plugins/decor/ui/components/DecorDecorationGridDecoration.tsx create mode 100644 src/plugins/decor/ui/components/DecorSection.tsx create mode 100644 src/plugins/decor/ui/components/DecorationContextMenu.tsx create mode 100644 src/plugins/decor/ui/components/DecorationGridCreate.tsx create mode 100644 src/plugins/decor/ui/components/DecorationGridNone.tsx create mode 100644 src/plugins/decor/ui/components/Grid.tsx create mode 100644 src/plugins/decor/ui/components/SectionedGridList.tsx create mode 100644 src/plugins/decor/ui/components/index.ts create mode 100644 src/plugins/decor/ui/index.ts create mode 100644 src/plugins/decor/ui/modals/ChangeDecorationModal.tsx create mode 100644 src/plugins/decor/ui/modals/CreateDecorationModal.tsx create mode 100644 src/plugins/decor/ui/styles.css diff --git a/package.json b/package.json index d035dcb6..af472093 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,8 @@ "tsx": "^3.12.7", "type-fest": "^3.9.0", "typescript": "^5.0.4", - "zip-local": "^0.3.5" + "zip-local": "^0.3.5", + "zustand": "^3.7.2" }, "packageManager": "pnpm@8.10.2", "pnpm": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be7befab..43866f50 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,9 +1,5 @@ lockfileVersion: '6.0' -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - patchedDependencies: eslint-plugin-path-alias@1.0.0: hash: m6sma4g6bh67km3q6igf6uxaja @@ -123,6 +119,9 @@ devDependencies: zip-local: specifier: ^0.3.5 version: 0.3.5 + zustand: + specifier: ^3.7.2 + version: 3.7.2 packages: @@ -3450,8 +3449,22 @@ packages: q: 1.5.1 dev: true + /zustand@3.7.2: + resolution: {integrity: sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==} + engines: {node: '>=12.7.0'} + peerDependencies: + react: '>=16.8' + peerDependenciesMeta: + react: + optional: true + dev: true + github.com/mattdesl/gifenc/64842fca317b112a8590f8fef2bf3825da8f6fe3: resolution: {tarball: https://codeload.github.com/mattdesl/gifenc/tar.gz/64842fca317b112a8590f8fef2bf3825da8f6fe3} name: gifenc version: 1.0.3 dev: false + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index 93b1323e..2eb83d4e 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -255,3 +255,38 @@ export function DeleteIcon(props: IconProps) { ); } + +export function PlusIcon(props: IconProps) { + return ( + + + + ); +} + +export function NoEntrySignIcon(props: IconProps) { + return ( + + + + + ); +} diff --git a/src/plugins/decor/README.md b/src/plugins/decor/README.md new file mode 100644 index 00000000..467a6145 --- /dev/null +++ b/src/plugins/decor/README.md @@ -0,0 +1,17 @@ +# Decor + +Custom avatar decorations! + +![Custom decorations in chat](https://github.com/Vendicated/Vencord/assets/30497388/b0c4c4c8-8723-42a8-b50f-195ad4e26136) + +Create and use your own custom avatar decorations, or pick your favorite from the presets. + +You'll be able to see the custom avatar decorations of other users of this plugin, and they'll be able to see your custom avatar decoration. + +You can select and manage your custom avatar decorations under the "Profiles" page in settings, or in the plugin settings. + +![Custom decorations management](https://github.com/Vendicated/Vencord/assets/30497388/74fe8a9e-a2a2-4b29-bc10-9eaa58208ad4) + +Review the [guidelines](https://github.com/decor-discord/.github/blob/main/GUIDELINES.md) before creating your own custom avatar decoration. + +Join the [Discord server](https://discord.gg/dXp2SdxDcP) for support and notifications on your decoration's review. diff --git a/src/plugins/decor/index.tsx b/src/plugins/decor/index.tsx new file mode 100644 index 00000000..4dd7aa0c --- /dev/null +++ b/src/plugins/decor/index.tsx @@ -0,0 +1,168 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated, FieryFlames and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./ui/styles.css"; + +import { definePluginSettings } from "@api/Settings"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { Link } from "@components/Link"; +import { Devs } from "@utils/constants"; +import { Margins } from "@utils/margins"; +import { classes } from "@utils/misc"; +import { closeAllModals } from "@utils/modal"; +import definePlugin, { OptionType } from "@utils/types"; +import { findByPropsLazy } from "@webpack"; +import { FluxDispatcher, Forms, UserStore } from "@webpack/common"; + +import { CDN_URL, RAW_SKU_ID, SKU_ID } from "./lib/constants"; +import { useAuthorizationStore } from "./lib/stores/AuthorizationStore"; +import { useCurrentUserDecorationsStore } from "./lib/stores/CurrentUserDecorationsStore"; +import { useUserDecorAvatarDecoration, useUsersDecorationsStore } from "./lib/stores/UsersDecorationsStore"; +import { setDecorationGridDecoration, setDecorationGridItem } from "./ui/components"; +import DecorSection from "./ui/components/DecorSection"; + +const { isAnimatedAvatarDecoration } = findByPropsLazy("isAnimatedAvatarDecoration"); +export interface AvatarDecoration { + asset: string; + skuId: string; +} + +const settings = definePluginSettings({ + changeDecoration: { + type: OptionType.COMPONENT, + description: "Change your avatar decoration", + component() { + return
+ + + You can also access Decor decorations from the { + e.preventDefault(); + closeAllModals(); + FluxDispatcher.dispatch({ type: "USER_SETTINGS_MODAL_SET_SECTION", section: "Profile Customization" }); + }} + >Profiles page. + +
; + } + } +}); +export default definePlugin({ + name: "Decor", + description: "Create and use your own custom avatar decorations, or pick your favorite from the presets.", + authors: [Devs.FieryFlames], + patches: [ + // Patch MediaResolver to return correct URL for Decor avatar decorations + { + find: "getAvatarDecorationURL:", + replacement: { + match: /(?<=function \i\(\i\){)(?=let{avatarDecoration)/, + replace: "const vcDecorDecoration=$self.getDecorAvatarDecorationURL(arguments[0]);if(vcDecorDecoration)return vcDecorDecoration;" + } + }, + // Patch profile customization settings to include Decor section + { + find: "DefaultCustomizationSections", + replacement: { + match: /(?<={user:\i},"decoration"\),)/, + replace: "$self.DecorSection()," + } + }, + // Decoration modal module + { + find: ".decorationGridItem", + replacement: [ + { + match: /(?<==)\i=>{let{children.{20,100}decorationGridItem/, + replace: "$self.DecorationGridItem=$&" + }, + { + match: /(?<==)\i=>{let{user:\i,avatarDecoration.{300,600}decorationGridItemChurned/, + replace: "$self.DecorationGridDecoration=$&" + }, + // Remove NEW label from decor avatar decorations + { + match: /(?<=\.Section\.PREMIUM_PURCHASE&&\i;if\()(?<=avatarDecoration:(\i).+?)/, + replace: "$1.skuId===$self.SKU_ID||" + } + ] + }, + { + find: "isAvatarDecorationAnimating:", + group: true, + replacement: [ + // Add Decor avatar decoration hook to avatar decoration hook + { + match: /(?<=TryItOut:\i}\),)(?<=user:(\i).+?)/, + replace: "vcDecorAvatarDecoration=$self.useUserDecorAvatarDecoration($1)," + }, + // Use added hook + { + match: /(?<={avatarDecoration:).{1,20}?(?=,)(?<=avatarDecorationOverride:(\i).+?)/, + replace: "$1??vcDecorAvatarDecoration??($&)" + }, + // Make memo depend on added hook + { + match: /(?<=size:\i}\),\[)/, + replace: "vcDecorAvatarDecoration," + } + ] + }, + // Current user area, at bottom of channels/dm list + { + find: "renderAvatarWithPopout(){", + replacement: [ + // Use Decor avatar decoration hook + { + match: /(?<=getAvatarDecorationURL\)\({avatarDecoration:)(\i).avatarDecoration(?=,)/, + replace: "$self.useUserDecorAvatarDecoration($1)??$&" + } + ] + } + ], + settings, + + flux: { + CONNECTION_OPEN: () => { + useAuthorizationStore.getState().init(); + useCurrentUserDecorationsStore.getState().clear(); + useUsersDecorationsStore.getState().fetch(UserStore.getCurrentUser().id, true); + }, + USER_PROFILE_MODAL_OPEN: data => { + useUsersDecorationsStore.getState().fetch(data.userId, true); + }, + }, + + set DecorationGridItem(e: any) { + setDecorationGridItem(e); + }, + + set DecorationGridDecoration(e: any) { + setDecorationGridDecoration(e); + }, + + SKU_ID, + + useUserDecorAvatarDecoration, + + async start() { + useUsersDecorationsStore.getState().fetch(UserStore.getCurrentUser().id, true); + }, + + getDecorAvatarDecorationURL({ avatarDecoration, canAnimate }: { avatarDecoration: AvatarDecoration | null; canAnimate?: boolean; }) { + // Only Decor avatar decorations have this SKU ID + if (avatarDecoration?.skuId === SKU_ID) { + const url = new URL(`${CDN_URL}/${avatarDecoration.asset}.png`); + url.searchParams.set("animate", (!!canAnimate && isAnimatedAvatarDecoration(avatarDecoration.asset)).toString()); + return url.toString(); + } else if (avatarDecoration?.skuId === RAW_SKU_ID) { + return avatarDecoration.asset; + } + }, + + DecorSection: ErrorBoundary.wrap(DecorSection) +}); diff --git a/src/plugins/decor/lib/api.ts b/src/plugins/decor/lib/api.ts new file mode 100644 index 00000000..3719cf24 --- /dev/null +++ b/src/plugins/decor/lib/api.ts @@ -0,0 +1,83 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { API_URL } from "./constants"; +import { useAuthorizationStore } from "./stores/AuthorizationStore"; + +export interface Preset { + id: string; + name: string; + description: string | null; + decorations: Decoration[]; + authorIds: string[]; +} + +export interface Decoration { + hash: string; + animated: boolean; + alt: string | null; + authorId: string | null; + reviewed: boolean | null; + presetId: string | null; +} + +export interface NewDecoration { + file: File; + alt: string | null; +} + +export async function fetchApi(url: RequestInfo, options?: RequestInit) { + const res = await fetch(url, { + ...options, + headers: { + ...options?.headers, + Authorization: `Bearer ${useAuthorizationStore.getState().token}` + } + }); + + if (res.ok) return res; + else throw new Error(await res.text()); +} + +export const getUsersDecorations = async (ids?: string[]): Promise> => { + if (ids?.length === 0) return {}; + + const url = new URL(API_URL + "/users"); + if (ids && ids.length !== 0) url.searchParams.set("ids", JSON.stringify(ids)); + + return await fetch(url).then(c => c.json()); +}; + +export const getUserDecorations = async (id: string = "@me"): Promise => + fetchApi(API_URL + `/users/${id}/decorations`).then(c => c.json()); + +export const getUserDecoration = async (id: string = "@me"): Promise => + fetchApi(API_URL + `/users/${id}/decoration`).then(c => c.json()); + +export const setUserDecoration = async (decoration: Decoration | NewDecoration | null, id: string = "@me"): Promise => { + const formData = new FormData(); + + if (!decoration) { + formData.append("hash", "null"); + } else if ("hash" in decoration) { + formData.append("hash", decoration.hash); + } else if ("file" in decoration) { + formData.append("image", decoration.file); + formData.append("alt", decoration.alt ?? "null"); + } + + return fetchApi(API_URL + `/users/${id}/decoration`, { method: "PUT", body: formData }).then(c => + decoration && "file" in decoration ? c.json() : c.text() + ); +}; + +export const getDecoration = async (hash: string): Promise => fetch(API_URL + `/decorations/${hash}`).then(c => c.json()); + +export const deleteDecoration = async (hash: string): Promise => { + await fetchApi(API_URL + `/decorations/${hash}`, { method: "DELETE" }); +}; + +export const getPresets = async (): Promise => fetch(API_URL + "/decorations/presets").then(c => c.json()); diff --git a/src/plugins/decor/lib/constants.ts b/src/plugins/decor/lib/constants.ts new file mode 100644 index 00000000..ce0b5979 --- /dev/null +++ b/src/plugins/decor/lib/constants.ts @@ -0,0 +1,16 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +export const BASE_URL = "https://decor.fieryflames.dev"; +export const API_URL = BASE_URL + "/api"; +export const AUTHORIZE_URL = API_URL + "/authorize"; +export const CDN_URL = "https://ugc.decor.fieryflames.dev"; +export const CLIENT_ID = "1096966363416899624"; +export const SKU_ID = "100101099111114"; // decor in ascii numbers +export const RAW_SKU_ID = "11497119"; // raw in ascii numbers +export const GUILD_ID = "1096357702931841148"; +export const INVITE_KEY = "dXp2SdxDcP"; +export const DECORATION_FETCH_COOLDOWN = 1000 * 60 * 60 * 4; // 4 hours diff --git a/src/plugins/decor/lib/stores/AuthorizationStore.tsx b/src/plugins/decor/lib/stores/AuthorizationStore.tsx new file mode 100644 index 00000000..e31b1f43 --- /dev/null +++ b/src/plugins/decor/lib/stores/AuthorizationStore.tsx @@ -0,0 +1,102 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { DataStore } from "@api/index"; +import { proxyLazy } from "@utils/lazy"; +import { Logger } from "@utils/Logger"; +import { openModal } from "@utils/modal"; +import { OAuth2AuthorizeModal, showToast, Toasts, UserStore, zustandCreate, zustandPersist } from "@webpack/common"; +import type { StateStorage } from "zustand/middleware"; + +import { AUTHORIZE_URL, CLIENT_ID } from "../constants"; + +interface AuthorizationState { + token: string | null; + tokens: Record; + init: () => void; + authorize: () => Promise; + setToken: (token: string) => void; + remove: (id: string) => void; + isAuthorized: () => boolean; +} + +const indexedDBStorage: StateStorage = { + async getItem(name: string): Promise { + return DataStore.get(name).then(v => v ?? null); + }, + async setItem(name: string, value: string): Promise { + await DataStore.set(name, value); + }, + async removeItem(name: string): Promise { + await DataStore.del(name); + }, +}; + +// TODO: Move switching accounts subscription inside the store? +export const useAuthorizationStore = proxyLazy(() => zustandCreate( + zustandPersist( + (set, get) => ({ + token: null, + tokens: {}, + init: () => { set({ token: get().tokens[UserStore.getCurrentUser().id] ?? null }); }, + setToken: (token: string) => set({ token, tokens: { ...get().tokens, [UserStore.getCurrentUser().id]: token } }), + remove: (id: string) => { + const { tokens, init } = get(); + const newTokens = { ...tokens }; + delete newTokens[id]; + set({ tokens: newTokens }); + + init(); + }, + async authorize() { + return new Promise((resolve, reject) => openModal(props => + { + try { + const url = new URL(response.location); + url.searchParams.append("client", "vencord"); + + const req = await fetch(url); + + if (req?.ok) { + const token = await req.text(); + get().setToken(token); + } else { + throw new Error("Request not OK"); + } + resolve(void 0); + } catch (e) { + if (e instanceof Error) { + showToast(`Failed to authorize: ${e.message}`, Toasts.Type.FAILURE); + new Logger("Decor").error("Failed to authorize", e); + reject(e); + } + } + }} + />, { + onCloseCallback() { + reject(new Error("Authorization cancelled")); + }, + } + )); + }, + isAuthorized: () => !!get().token, + }), + { + name: "decor-auth", + getStorage: () => indexedDBStorage, + partialize: state => ({ tokens: state.tokens }), + onRehydrateStorage: () => state => state?.init() + } + ) +)); diff --git a/src/plugins/decor/lib/stores/CurrentUserDecorationsStore.ts b/src/plugins/decor/lib/stores/CurrentUserDecorationsStore.ts new file mode 100644 index 00000000..1485a743 --- /dev/null +++ b/src/plugins/decor/lib/stores/CurrentUserDecorationsStore.ts @@ -0,0 +1,56 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { proxyLazy } from "@utils/lazy"; +import { UserStore, zustandCreate } from "@webpack/common"; + +import { Decoration, deleteDecoration, getUserDecoration, getUserDecorations, NewDecoration, setUserDecoration } from "../api"; +import { decorationToAsset } from "../utils/decoration"; +import { useUsersDecorationsStore } from "./UsersDecorationsStore"; + +interface UserDecorationsState { + decorations: Decoration[]; + selectedDecoration: Decoration | null; + fetch: () => Promise; + delete: (decoration: Decoration | string) => Promise; + create: (decoration: NewDecoration) => Promise; + select: (decoration: Decoration | null) => Promise; + clear: () => void; +} + +export const useCurrentUserDecorationsStore = proxyLazy(() => zustandCreate((set, get) => ({ + decorations: [], + selectedDecoration: null, + async fetch() { + const decorations = await getUserDecorations(); + const selectedDecoration = await getUserDecoration(); + + set({ decorations, selectedDecoration }); + }, + async create(newDecoration: NewDecoration) { + const decoration = (await setUserDecoration(newDecoration)) as Decoration; + set({ decorations: [...get().decorations, decoration] }); + }, + async delete(decoration: Decoration | string) { + const hash = typeof decoration === "object" ? decoration.hash : decoration; + await deleteDecoration(hash); + + const { selectedDecoration, decorations } = get(); + const newState = { + decorations: decorations.filter(d => d.hash !== hash), + selectedDecoration: selectedDecoration?.hash === hash ? null : selectedDecoration + }; + + set(newState); + }, + async select(decoration: Decoration | null) { + if (get().selectedDecoration === decoration) return; + set({ selectedDecoration: decoration }); + setUserDecoration(decoration); + useUsersDecorationsStore.getState().set(UserStore.getCurrentUser().id, decoration ? decorationToAsset(decoration) : null); + }, + clear: () => set({ decorations: [], selectedDecoration: null }) +}))); diff --git a/src/plugins/decor/lib/stores/UsersDecorationsStore.ts b/src/plugins/decor/lib/stores/UsersDecorationsStore.ts new file mode 100644 index 00000000..7295a3b1 --- /dev/null +++ b/src/plugins/decor/lib/stores/UsersDecorationsStore.ts @@ -0,0 +1,118 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { debounce } from "@utils/debounce"; +import { proxyLazy } from "@utils/lazy"; +import { useEffect, useState, zustandCreate } from "@webpack/common"; +import { User } from "discord-types/general"; + +import { AvatarDecoration } from "../../"; +import { getUsersDecorations } from "../api"; +import { DECORATION_FETCH_COOLDOWN, SKU_ID } from "../constants"; + +interface UserDecorationData { + asset: string | null; + fetchedAt: Date; +} + +interface UsersDecorationsState { + usersDecorations: Map; + fetchQueue: Set; + bulkFetch: () => Promise; + fetch: (userId: string, force?: boolean) => Promise; + fetchMany: (userIds: string[]) => Promise; + get: (userId: string) => UserDecorationData | undefined; + getAsset: (userId: string) => string | null | undefined; + has: (userId: string) => boolean; + set: (userId: string, decoration: string | null) => void; +} + +export const useUsersDecorationsStore = proxyLazy(() => zustandCreate((set, get) => ({ + usersDecorations: new Map(), + fetchQueue: new Set(), + bulkFetch: debounce(async () => { + const { fetchQueue, usersDecorations } = get(); + + if (fetchQueue.size === 0) return; + + set({ fetchQueue: new Set() }); + + const fetchIds = Array.from(fetchQueue); + const fetchedUsersDecorations = await getUsersDecorations(fetchIds); + + const newUsersDecorations = new Map(usersDecorations); + + const now = new Date(); + for (const fetchId of fetchIds) { + const newDecoration = fetchedUsersDecorations[fetchId] ?? null; + newUsersDecorations.set(fetchId, { asset: newDecoration, fetchedAt: now }); + } + + set({ usersDecorations: newUsersDecorations }); + }), + async fetch(userId: string, force: boolean = false) { + const { usersDecorations, fetchQueue, bulkFetch } = get(); + + const { fetchedAt } = usersDecorations.get(userId) ?? {}; + if (fetchedAt) { + if (!force && Date.now() - fetchedAt.getTime() < DECORATION_FETCH_COOLDOWN) return; + } + + set({ fetchQueue: new Set(fetchQueue).add(userId) }); + bulkFetch(); + }, + async fetchMany(userIds) { + if (!userIds.length) return; + const { usersDecorations, fetchQueue, bulkFetch } = get(); + + const newFetchQueue = new Set(fetchQueue); + + const now = Date.now(); + for (const userId of userIds) { + const { fetchedAt } = usersDecorations.get(userId) ?? {}; + if (fetchedAt) { + if (now - fetchedAt.getTime() < DECORATION_FETCH_COOLDOWN) continue; + } + newFetchQueue.add(userId); + } + + set({ fetchQueue: newFetchQueue }); + bulkFetch(); + }, + get(userId: string) { return get().usersDecorations.get(userId); }, + getAsset(userId: string) { return get().usersDecorations.get(userId)?.asset; }, + has(userId: string) { return get().usersDecorations.has(userId); }, + set(userId: string, decoration: string | null) { + const { usersDecorations } = get(); + const newUsersDecorations = new Map(usersDecorations); + + newUsersDecorations.set(userId, { asset: decoration, fetchedAt: new Date() }); + set({ usersDecorations: newUsersDecorations }); + } +}))); + +export function useUserDecorAvatarDecoration(user?: User): AvatarDecoration | null | undefined { + const [decorAvatarDecoration, setDecorAvatarDecoration] = useState(user ? useUsersDecorationsStore.getState().getAsset(user.id) ?? null : null); + + useEffect(() => { + const destructor = useUsersDecorationsStore.subscribe( + state => { + if (!user) return; + const newDecorAvatarDecoration = state.getAsset(user.id); + if (!newDecorAvatarDecoration) return; + if (decorAvatarDecoration !== newDecorAvatarDecoration) setDecorAvatarDecoration(newDecorAvatarDecoration); + } + ); + + if (user) { + const { fetch: fetchUserDecorAvatarDecoration } = useUsersDecorationsStore.getState(); + fetchUserDecorAvatarDecoration(user.id); + } + return destructor; + }, []); + + return decorAvatarDecoration ? { asset: decorAvatarDecoration, skuId: SKU_ID } : null; +} diff --git a/src/plugins/decor/lib/utils/decoration.ts b/src/plugins/decor/lib/utils/decoration.ts new file mode 100644 index 00000000..176507ef --- /dev/null +++ b/src/plugins/decor/lib/utils/decoration.ts @@ -0,0 +1,17 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { AvatarDecoration } from "../../"; +import { Decoration } from "../api"; +import { SKU_ID } from "../constants"; + +export function decorationToAsset(decoration: Decoration) { + return `${decoration.animated ? "a_" : ""}${decoration.hash}`; +} + +export function decorationToAvatarDecoration(decoration: Decoration): AvatarDecoration { + return { asset: decorationToAsset(decoration), skuId: SKU_ID }; +} diff --git a/src/plugins/decor/ui/components/DecorDecorationGridDecoration.tsx b/src/plugins/decor/ui/components/DecorDecorationGridDecoration.tsx new file mode 100644 index 00000000..deaeef63 --- /dev/null +++ b/src/plugins/decor/ui/components/DecorDecorationGridDecoration.tsx @@ -0,0 +1,35 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { ContextMenuApi } from "@webpack/common"; +import type { HTMLProps } from "react"; + +import { Decoration } from "../../lib/api"; +import { decorationToAvatarDecoration } from "../../lib/utils/decoration"; +import { DecorationGridDecoration } from "."; +import DecorationContextMenu from "./DecorationContextMenu"; + +interface DecorDecorationGridDecorationProps extends HTMLProps { + decoration: Decoration; + isSelected: boolean; + onSelect: () => void; +} + +export default function DecorDecorationGridDecoration(props: DecorDecorationGridDecorationProps) { + const { decoration } = props; + + return { + ContextMenuApi.openContextMenu(e, () => ( + + )); + }} + avatarDecoration={decorationToAvatarDecoration(decoration)} + />; +} diff --git a/src/plugins/decor/ui/components/DecorSection.tsx b/src/plugins/decor/ui/components/DecorSection.tsx new file mode 100644 index 00000000..f11a87a5 --- /dev/null +++ b/src/plugins/decor/ui/components/DecorSection.tsx @@ -0,0 +1,59 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Flex } from "@components/Flex"; +import { findByCodeLazy } from "@webpack"; +import { Button, useEffect } from "@webpack/common"; + +import { useAuthorizationStore } from "../../lib/stores/AuthorizationStore"; +import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore"; +import { cl } from "../"; +import { openChangeDecorationModal } from "../modals/ChangeDecorationModal"; + +const CustomizationSection = findByCodeLazy(".customizationSectionBackground"); + +interface DecorSectionProps { + hideTitle?: boolean; + hideDivider?: boolean; + noMargin?: boolean; +} + +export default function DecorSection({ hideTitle = false, hideDivider = false, noMargin = false }: DecorSectionProps) { + const authorization = useAuthorizationStore(); + const { selectedDecoration, select: selectDecoration, fetch: fetchDecorations } = useCurrentUserDecorationsStore(); + + useEffect(() => { + if (authorization.isAuthorized()) fetchDecorations(); + }, [authorization.token]); + + return + + + {selectedDecoration && authorization.isAuthorized() && } + + ; +} diff --git a/src/plugins/decor/ui/components/DecorationContextMenu.tsx b/src/plugins/decor/ui/components/DecorationContextMenu.tsx new file mode 100644 index 00000000..7451bb22 --- /dev/null +++ b/src/plugins/decor/ui/components/DecorationContextMenu.tsx @@ -0,0 +1,47 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { CopyIcon, DeleteIcon } from "@components/Icons"; +import { Alerts, Clipboard, ContextMenuApi, Menu, UserStore } from "webpack/common"; + +import { Decoration } from "../../lib/api"; +import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore"; +import { cl } from "../"; + +export default function DecorationContextMenu({ decoration }: { decoration: Decoration; }) { + const { delete: deleteDecoration } = useCurrentUserDecorationsStore(); + + return + Clipboard.copy(decoration.hash)} + /> + {decoration.authorId === UserStore.getCurrentUser().id && + Alerts.show({ + title: "Delete Decoration", + body: `Are you sure you want to delete ${decoration.alt}?`, + confirmText: "Delete", + confirmColor: cl("danger-btn"), + cancelText: "Cancel", + onConfirm() { + deleteDecoration(decoration); + } + })} + /> + } + ; +} diff --git a/src/plugins/decor/ui/components/DecorationGridCreate.tsx b/src/plugins/decor/ui/components/DecorationGridCreate.tsx new file mode 100644 index 00000000..7699b23d --- /dev/null +++ b/src/plugins/decor/ui/components/DecorationGridCreate.tsx @@ -0,0 +1,30 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { PlusIcon } from "@components/Icons"; +import { i18n, Text } from "@webpack/common"; +import { HTMLProps } from "react"; + +import { DecorationGridItem } from "."; + +type DecorationGridCreateProps = HTMLProps & { + onSelect: () => void; +}; + +export default function DecorationGridCreate(props: DecorationGridCreateProps) { + return + + + {i18n.Messages.CREATE} + + ; +} diff --git a/src/plugins/decor/ui/components/DecorationGridNone.tsx b/src/plugins/decor/ui/components/DecorationGridNone.tsx new file mode 100644 index 00000000..b6114c67 --- /dev/null +++ b/src/plugins/decor/ui/components/DecorationGridNone.tsx @@ -0,0 +1,30 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { NoEntrySignIcon } from "@components/Icons"; +import { i18n, Text } from "@webpack/common"; +import { HTMLProps } from "react"; + +import { DecorationGridItem } from "."; + +type DecorationGridNoneProps = HTMLProps & { + isSelected: boolean; + onSelect: () => void; +}; + +export default function DecorationGridNone(props: DecorationGridNoneProps) { + return + + + {i18n.Messages.NONE} + + ; +} diff --git a/src/plugins/decor/ui/components/Grid.tsx b/src/plugins/decor/ui/components/Grid.tsx new file mode 100644 index 00000000..40180248 --- /dev/null +++ b/src/plugins/decor/ui/components/Grid.tsx @@ -0,0 +1,28 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { React } from "@webpack/common"; + +import { cl } from "../"; + +export interface GridProps { + renderItem: (item: ItemT) => JSX.Element; + getItemKey: (item: ItemT) => string; + itemKeyPrefix?: string; + items: Array; +} + +export default function Grid({ renderItem, getItemKey, itemKeyPrefix: ikp, items }: GridProps) { + return
+ {items.map(item => + + {renderItem(item)} + + )} +
; +} diff --git a/src/plugins/decor/ui/components/SectionedGridList.tsx b/src/plugins/decor/ui/components/SectionedGridList.tsx new file mode 100644 index 00000000..9a6ec1b8 --- /dev/null +++ b/src/plugins/decor/ui/components/SectionedGridList.tsx @@ -0,0 +1,38 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { classes } from "@utils/misc"; +import { findByPropsLazy } from "@webpack"; +import { React } from "@webpack/common"; + +import { cl } from "../"; +import Grid, { GridProps } from "./Grid"; + +const ScrollerClasses = findByPropsLazy("managedReactiveScroller"); + +type Section = SectionT & { + items: Array; +}; + +interface SectionedGridListProps> extends Omit, "items"> { + renderSectionHeader: (section: SectionU) => JSX.Element; + getSectionKey: (section: SectionU) => string; + sections: SectionU[]; +} + +export default function SectionedGridList(props: SectionedGridListProps) { + return
+ {props.sections.map(section =>
+ {props.renderSectionHeader(section)} + +
)} +
; +} diff --git a/src/plugins/decor/ui/components/index.ts b/src/plugins/decor/ui/components/index.ts new file mode 100644 index 00000000..8f39a10e --- /dev/null +++ b/src/plugins/decor/ui/components/index.ts @@ -0,0 +1,33 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { findComponentByCode, LazyComponentWebpack } from "@webpack"; +import { React } from "@webpack/common"; +import type { ComponentType, HTMLProps, PropsWithChildren } from "react"; + +import { AvatarDecoration } from "../.."; + +type DecorationGridItemComponent = ComponentType> & { + onSelect: () => void, + isSelected: boolean, +}>; + +export let DecorationGridItem: DecorationGridItemComponent; +export const setDecorationGridItem = v => DecorationGridItem = v; + +export const AvatarDecorationModalPreview = LazyComponentWebpack(() => { + const component = findComponentByCode("AvatarDecorationModalPreview"); + return React.memo(component); +}); + +type DecorationGridDecorationComponent = React.ComponentType & { + avatarDecoration: AvatarDecoration; + onSelect: () => void, + isSelected: boolean, +}>; + +export let DecorationGridDecoration: DecorationGridDecorationComponent; +export const setDecorationGridDecoration = v => DecorationGridDecoration = v; diff --git a/src/plugins/decor/ui/index.ts b/src/plugins/decor/ui/index.ts new file mode 100644 index 00000000..52b169d7 --- /dev/null +++ b/src/plugins/decor/ui/index.ts @@ -0,0 +1,13 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { classNameFactory } from "@api/Styles"; +import { extractAndLoadChunksLazy } from "@webpack"; + +export const cl = classNameFactory("vc-decor-"); + +export const requireAvatarDecorationModal = extractAndLoadChunksLazy(["openAvatarDecorationModal:"]); +export const requireCreateStickerModal = extractAndLoadChunksLazy(["stickerInspected]:"]); diff --git a/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx b/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx new file mode 100644 index 00000000..bed00717 --- /dev/null +++ b/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx @@ -0,0 +1,270 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Flex } from "@components/Flex"; +import { openInviteModal } from "@utils/discord"; +import { Margins } from "@utils/margins"; +import { classes } from "@utils/misc"; +import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; +import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; +import { Alerts, Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Parser, Text, Tooltip, useEffect, UserStore, UserUtils, useState } from "@webpack/common"; +import { User } from "discord-types/general"; + +import { Decoration, getPresets, Preset } from "../../lib/api"; +import { GUILD_ID, INVITE_KEY } from "../../lib/constants"; +import { useAuthorizationStore } from "../../lib/stores/AuthorizationStore"; +import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore"; +import { decorationToAvatarDecoration } from "../../lib/utils/decoration"; +import { cl, requireAvatarDecorationModal } from "../"; +import { AvatarDecorationModalPreview } from "../components"; +import DecorationGridCreate from "../components/DecorationGridCreate"; +import DecorationGridNone from "../components/DecorationGridNone"; +import DecorDecorationGridDecoration from "../components/DecorDecorationGridDecoration"; +import SectionedGridList from "../components/SectionedGridList"; +import { openCreateDecorationModal } from "./CreateDecorationModal"; + +const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); +const DecorationModalStyles = findByPropsLazy("modalFooterShopButton"); + +function usePresets() { + const [presets, setPresets] = useState([]); + useEffect(() => { getPresets().then(setPresets); }, []); + return presets; +} + +interface Section { + title: string; + subtitle?: string; + sectionKey: string; + items: ("none" | "create" | Decoration)[]; + authorIds?: string[]; +} + +function SectionHeader({ section }: { section: Section; }) { + const hasSubtitle = typeof section.subtitle !== "undefined"; + const hasAuthorIds = typeof section.authorIds !== "undefined"; + + const [authors, setAuthors] = useState([]); + + useEffect(() => { + (async () => { + if (!section.authorIds) return; + + for (const authorId of section.authorIds) { + const author = UserStore.getUser(authorId) ?? await UserUtils.getUser(authorId); + setAuthors(authors => [...authors, author]); + } + })(); + }, [section.authorIds]); + + return
+ + {section.title} + {hasAuthorIds && + } + + {hasSubtitle && + + {section.subtitle} + + } +
; +} + +export default function ChangeDecorationModal(props: any) { + // undefined = not trying, null = none, Decoration = selected + const [tryingDecoration, setTryingDecoration] = useState(undefined); + const isTryingDecoration = typeof tryingDecoration !== "undefined"; + + const avatarDecorationOverride = tryingDecoration != null ? decorationToAvatarDecoration(tryingDecoration) : tryingDecoration; + + const { + decorations, + selectedDecoration, + fetch: fetchUserDecorations, + select: selectDecoration + } = useCurrentUserDecorationsStore(); + + useEffect(() => { + fetchUserDecorations(); + }, []); + + const activeSelectedDecoration = isTryingDecoration ? tryingDecoration : selectedDecoration; + const activeDecorationHasAuthor = typeof activeSelectedDecoration?.authorId !== "undefined"; + const hasDecorationPendingReview = decorations.some(d => d.reviewed === false); + + const presets = usePresets(); + const presetDecorations = presets.flatMap(preset => preset.decorations); + + const activeDecorationPreset = presets.find(preset => preset.id === activeSelectedDecoration?.presetId); + const isActiveDecorationPreset = typeof activeDecorationPreset !== "undefined"; + + const ownDecorations = decorations.filter(d => !presetDecorations.some(p => p.hash === d.hash)); + + const data = [ + { + title: "Your Decorations", + sectionKey: "ownDecorations", + items: ["none", ...ownDecorations, "create"] + }, + ...presets.map(preset => ({ + title: preset.name, + subtitle: preset.description || undefined, + sectionKey: `preset-${preset.id}`, + items: preset.decorations, + authorIds: preset.authorIds + })) + ] as Section[]; + + return + + + Change Decoration + + + + + { + if (typeof item === "string") { + switch (item) { + case "none": + return setTryingDecoration(null)} + />; + case "create": + return + {tooltipProps => { }} + />} + ; + } + } else { + return + {tooltipProps => ( + setTryingDecoration(item) : () => { }} + isSelected={activeSelectedDecoration?.hash === item.hash} + decoration={item} + /> + )} + ; + } + }} + getItemKey={item => typeof item === "string" ? item : item.hash} + getSectionKey={section => section.sectionKey} + renderSectionHeader={section => } + sections={data} + /> +
+ + {isActiveDecorationPreset && Part of the {activeDecorationPreset.name} Preset} + {typeof activeSelectedDecoration === "object" && + + {activeSelectedDecoration?.alt} + + } + {activeDecorationHasAuthor && Created by {Parser.parse(`<@${activeSelectedDecoration.authorId}>`)}} +
+
+ +
+ + +
+
+ + + {tooltipProps => } + +
+
+
; +} + +export const openChangeDecorationModal = () => + requireAvatarDecorationModal().then(() => openModal(props => )); diff --git a/src/plugins/decor/ui/modals/CreateDecorationModal.tsx b/src/plugins/decor/ui/modals/CreateDecorationModal.tsx new file mode 100644 index 00000000..a5937b0d --- /dev/null +++ b/src/plugins/decor/ui/modals/CreateDecorationModal.tsx @@ -0,0 +1,163 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Link } from "@components/Link"; +import { openInviteModal } from "@utils/discord"; +import { Margins } from "@utils/margins"; +import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; +import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; +import { Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Text, TextInput, useEffect, useMemo, UserStore, useState } from "@webpack/common"; + +import { GUILD_ID, INVITE_KEY, RAW_SKU_ID } from "../../lib/constants"; +import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore"; +import { cl, requireAvatarDecorationModal, requireCreateStickerModal } from "../"; +import { AvatarDecorationModalPreview } from "../components"; + + +const DecorationModalStyles = findByPropsLazy("modalFooterShopButton"); + +const FileUpload = findComponentByCodeLazy("fileUploadInput,"); + +function useObjectURL(object: Blob | MediaSource | null) { + const [url, setUrl] = useState(null); + + useEffect(() => { + if (!object) return; + + const objectUrl = URL.createObjectURL(object); + setUrl(objectUrl); + + return () => { + URL.revokeObjectURL(objectUrl); + setUrl(null); + }; + }, [object]); + + return url; +} + +export default function CreateDecorationModal(props) { + const [name, setName] = useState(""); + const [file, setFile] = useState(null); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + if (error) setError(null); + }, [file]); + + const { create: createDecoration } = useCurrentUserDecorationsStore(); + + const fileUrl = useObjectURL(file); + + const decoration = useMemo(() => fileUrl ? { asset: fileUrl, skuId: RAW_SKU_ID } : null, [fileUrl]); + + return + + + Create Decoration + + + + +
+
+ {error !== null && {error.message}} + + + + File should be APNG or PNG. + + + + + + This name will be used when referring to this decoration. + + +
+
+ +
+
+ + Make sure your decoration does not violate + the guidelines + before creating your decoration. +
You can receive updates on your decoration's review by joining { + e.preventDefault(); + if (!GuildStore.getGuild(GUILD_ID)) { + const inviteAccepted = await openInviteModal(INVITE_KEY); + if (inviteAccepted) { + closeAllModals(); + FluxDispatcher.dispatch({ type: "LAYER_POP_ALL" }); + } + } else { + closeAllModals(); + FluxDispatcher.dispatch({ type: "LAYER_POP_ALL" }); + NavigationRouter.transitionToGuild(GUILD_ID); + } + }} + > + Decor's Discord server + . +
+
+ + + + +
; +} + +export const openCreateDecorationModal = () => + Promise.all([requireAvatarDecorationModal(), requireCreateStickerModal()]) + .then(() => openModal(props => )); diff --git a/src/plugins/decor/ui/styles.css b/src/plugins/decor/ui/styles.css new file mode 100644 index 00000000..ff10c82f --- /dev/null +++ b/src/plugins/decor/ui/styles.css @@ -0,0 +1,80 @@ +.vc-decor-danger-btn { + color: var(--white-500); + background-color: var(--button-danger-background); +} + +.vc-decor-change-decoration-modal-content { + position: relative; + display: flex; + border-radius: 5px 5px 0 0; + padding: 0 16px; + gap: 4px +} + +.vc-decor-change-decoration-modal-preview { + display: flex; + flex-direction: column; + margin-top: 24px; + gap: 8px; + max-width: 280px; +} + +.vc-decor-change-decoration-modal-decoration { + width: 80px; + height: 80px; +} + +.vc-decor-change-decoration-modal-footer { + justify-content: space-between; +} + +.vc-decor-change-decoration-modal-footer-btn-container { + display: flex; + flex-direction: row-reverse; +} + +.vc-decor-create-decoration-modal-content { + display: flex; + flex-direction: column; + gap: 20px; + padding: 0 16px; +} + +.vc-decor-create-decoration-modal-form-preview-container { + display: flex; + gap: 16px; +} + +.vc-decor-modal-header { + padding: 16px; +} + +.vc-decor-modal-footer { + padding: 16px; +} + +.vc-decor-create-decoration-modal-form { + display: flex; + flex-direction: column; + flex-grow: 1; + gap: 16px; +} + +.vc-decor-sectioned-grid-list-container { + display: flex; + flex-direction: column; + overflow: hidden scroll; + max-height: 512px; + width: 352px; /* ((80 + 8 (grid gap)) * desired columns) (scrolled takes the extra 8 padding off conveniently) */ + gap: 12px; +} + +.vc-decor-sectioned-grid-list-grid { + display: flex; + flex-wrap: wrap; + gap: 8px +} + +.vc-decor-section-remove-margin { + margin-bottom: 0; +} diff --git a/src/utils/cloud.tsx b/src/utils/cloud.tsx index 02930622..f56c78dc 100644 --- a/src/utils/cloud.tsx +++ b/src/utils/cloud.tsx @@ -19,8 +19,7 @@ import * as DataStore from "@api/DataStore"; import { showNotification } from "@api/Notifications"; import { Settings } from "@api/Settings"; -import { findByProps } from "@webpack"; -import { UserStore } from "@webpack/common"; +import { OAuth2AuthorizeModal, UserStore } from "@webpack/common"; import { Logger } from "./Logger"; import { openModal } from "./modal"; @@ -91,8 +90,6 @@ export async function authorizeCloud() { return; } - const { OAuth2AuthorizeModal } = findByProps("OAuth2AuthorizeModal"); - openModal((props: any) => (r => { + let onClose: () => void, onAccept: () => void; + let inviteAccepted = false; + + FluxDispatcher.subscribe("INVITE_ACCEPT", onAccept = () => { + inviteAccepted = true; + }); + + FluxDispatcher.subscribe("INVITE_MODAL_CLOSE", onClose = () => { + FluxDispatcher.unsubscribe("INVITE_MODAL_CLOSE", onClose); + FluxDispatcher.unsubscribe("INVITE_ACCEPT", onAccept); + r(inviteAccepted); + }); + }); } export function getCurrentChannel() { diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index e44b1c9f..d7bb5d75 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -17,7 +17,7 @@ */ // eslint-disable-next-line path-alias/no-relative -import { filters, waitFor } from "@webpack"; +import { filters, findByPropsLazy, waitFor } from "@webpack"; import { waitForComponent } from "./internal"; import * as t from "./types/components"; @@ -55,6 +55,8 @@ export const MaskedLink = waitForComponent("MaskedLink", m => m?.t export const Timestamp = waitForComponent("Timestamp", filters.byCode(".Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format")); export const Flex = waitForComponent("Flex", ["Justify", "Align", "Wrap"]); +export const { OAuth2AuthorizeModal } = findByPropsLazy("OAuth2AuthorizeModal"); + waitFor(["FormItem", "Button"], m => { ({ useToken, Card, Button, FormSwitch: Switch, Tooltip, TextInput, TextArea, Text, Select, SearchableSelect, Slider, ButtonLooks, TabBar, Popout, Dialog, Paginator, ScrollerThin, Clickable, Avatar } = m); Forms = m; diff --git a/src/webpack/common/types/components.d.ts b/src/webpack/common/types/components.d.ts index 5d5424fe..b9bc434c 100644 --- a/src/webpack/common/types/components.d.ts +++ b/src/webpack/common/types/components.d.ts @@ -126,6 +126,7 @@ export type Button = ComponentType; focusProps?: any; + submitting?: boolean; submittingStartedLabel?: string; submittingFinishedLabel?: string; diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index cef4d51d..f5d2a966 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -19,7 +19,7 @@ import type { Channel, User } from "discord-types/general"; // eslint-disable-next-line path-alias/no-relative -import { _resolveReady, findByPropsLazy, findLazy, waitFor } from "../webpack"; +import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, waitFor } from "../webpack"; import type * as t from "./types/utils"; export let FluxDispatcher: t.FluxDispatcher; @@ -127,5 +127,9 @@ export const NavigationRouter: t.NavigationRouter = findByPropsLazy("transitionT export let SettingsRouter: any; waitFor(["open", "saveAccountChanges"], m => SettingsRouter = m); -const { Permissions } = findLazy(m => typeof m.Permissions?.ADMINISTRATOR === "bigint") as { Permissions: t.PermissionsBits; }; -export { Permissions as PermissionsBits }; +export const { Permissions: PermissionsBits } = findLazy(m => typeof m.Permissions?.ADMINISTRATOR === "bigint") as { Permissions: t.PermissionsBits; }; + +export const zustandCreate: typeof import("zustand").default = findByCodeLazy("will be removed in v4"); + +const persistFilter = filters.byCode("[zustand persist middleware]"); +export const { persist: zustandPersist }: typeof import("zustand/middleware") = findLazy(m => m.persist && persistFilter(m.persist)); diff --git a/tsconfig.json b/tsconfig.json index db540745..4563f3f8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "allowSyntheticDefaultImports": true, "esModuleInterop": true, + "skipLibCheck": true, "lib": [ "DOM", "DOM.Iterable", From 66dbe7ef07c69f8c9963c947c5f1eac03b0e1222 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Thu, 30 Nov 2023 02:26:18 -0300 Subject: [PATCH 0264/1143] Fix reporter testing for extractAndLoadChunks --- scripts/generateReport.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts index 719a8456..c1a4f711 100644 --- a/scripts/generateReport.ts +++ b/scripts/generateReport.ts @@ -410,7 +410,7 @@ function runTime(token: string) { const [code, matcher] = args; const module = Vencord.Webpack.findModuleFactory(...code); - if (module) result = module.toString().match(matcher); + if (module) result = module.toString().match(Vencord.Util.canonicalizeMatch(matcher)); } else { // @ts-ignore result = Vencord.Webpack[method](...args); From 8e1546be000748ac647d5a1950e9c5750a016b27 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Thu, 30 Nov 2023 02:38:12 -0300 Subject: [PATCH 0265/1143] Include ignored Discord errors in summary --- scripts/generateReport.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts index c1a4f711..a75a5985 100644 --- a/scripts/generateReport.ts +++ b/scripts/generateReport.ts @@ -105,7 +105,14 @@ async function printReport() { console.log(); - report.otherErrors = report.otherErrors.filter(e => !IGNORED_DISCORD_ERRORS.some(regex => e.match(regex))); + const ignoredErrors = [] as string[]; + report.otherErrors = report.otherErrors.filter(e => { + if (IGNORED_DISCORD_ERRORS.some(regex => e.match(regex))) { + ignoredErrors.push(e); + return false; + } + return true; + }); console.log("## Discord Errors"); report.otherErrors.forEach(e => { @@ -114,6 +121,13 @@ async function printReport() { console.log(); + console.log("## Ignored Discord Errors"); + ignoredErrors.forEach(e => { + console.log(`- ${toCodeBlock(e)}`); + }); + + console.log(); + if (process.env.DISCORD_WEBHOOK) { await fetch(process.env.DISCORD_WEBHOOK, { method: "POST", From fccdd3dc08fb45fb9dad79d2dc8a0ac5ddb120f9 Mon Sep 17 00:00:00 2001 From: V Date: Thu, 30 Nov 2023 17:28:53 +0100 Subject: [PATCH 0266/1143] migrate to new badge api we used to store badges on the discord cdn. since discord is now making it harder to use their cdn for such purposes (due to expiring links), we are forced to stop using it thus, badges are now stored on our server, accessible via https://badges.vencord.dev. The full list of badges is now at https://badges.vencord.dev/badges.json --- src/plugins/_api/badges.tsx | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/plugins/_api/badges.tsx b/src/plugins/_api/badges.tsx index 11e843db..16b244a1 100644 --- a/src/plugins/_api/badges.tsx +++ b/src/plugins/_api/badges.tsx @@ -22,14 +22,13 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Flex } from "@components/Flex"; import { Heart } from "@components/Heart"; import { Devs } from "@utils/constants"; -import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { isPluginDev } from "@utils/misc"; import { closeModal, Modals, openModal } from "@utils/modal"; import definePlugin from "@utils/types"; import { Forms, Toasts } from "@webpack/common"; -const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/attachments/1033680203433660458/1092089947126780035/favicon.png"; +const CONTRIBUTOR_BADGE = "https://vencord.dev/assets/favicon.png"; const ContributorBadge: ProfileBadge = { description: "Vencord Contributor", @@ -45,7 +44,7 @@ const ContributorBadge: ProfileBadge = { link: "https://github.com/Vendicated/Vencord" }; -let DonorBadges = {} as Record[]>; +let DonorBadges = {} as Record>>; async function loadBadges(noCache = false) { DonorBadges = {}; @@ -54,19 +53,8 @@ async function loadBadges(noCache = false) { if (noCache) init.cache = "no-cache"; - const badges = await fetch("https://gist.githubusercontent.com/Vendicated/51a3dd775f6920429ec6e9b735ca7f01/raw/badges.csv", init) - .then(r => r.text()); - - const lines = badges.trim().split("\n"); - if (lines.shift() !== "id,tooltip,image") { - new Logger("BadgeAPI").error("Invalid badges.csv file!"); - return; - } - - for (const line of lines) { - const [id, description, image] = line.split(","); - (DonorBadges[id] ??= []).push({ image, description }); - } + DonorBadges = await fetch("https://badges.vencord.dev/badges.json", init) + .then(r => r.json()); } export default definePlugin({ @@ -127,7 +115,8 @@ export default definePlugin({ getDonorBadges(userId: string) { return DonorBadges[userId]?.map(badge => ({ - ...badge, + image: badge.badge, + description: badge.tooltip, position: BadgePosition.START, props: { style: { From 3e7d9462961ba2d1291cb68b9d5618c46c568451 Mon Sep 17 00:00:00 2001 From: megumin Date: Thu, 30 Nov 2023 19:32:48 +0000 Subject: [PATCH 0267/1143] fix(SpotifyControls): Requests double-sending when using Spotify Connect (#2023) --- src/plugins/spotifyControls/index.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/plugins/spotifyControls/index.tsx b/src/plugins/spotifyControls/index.tsx index 829f7850..cfb352ef 100644 --- a/src/plugins/spotifyControls/index.tsx +++ b/src/plugins/spotifyControls/index.tsx @@ -55,13 +55,19 @@ export default definePlugin({ replace: "return [$self.renderPlayer(),$1]" } }, - // Adds POST and a Marker to the SpotifyAPI (so we can easily find it) { find: ".PLAYER_DEVICES", - replacement: { + replacement: [{ + // Adds POST and a Marker to the SpotifyAPI (so we can easily find it) match: /get:(\i)\.bind\(null,(\i\.\i)\.get\)/, replace: "post:$1.bind(null,$2.post),$&" - } + }, + { + // Spotify Connect API returns status 202 instead of 204 when skipping tracks. + // Discord rejects 202 which causes the request to send twice. This patch prevents this. + match: /202===\i\.status/, + replace: "false", + }] }, // Discord doesn't give you the repeat kind, only a boolean { From 80016180b612718e7a73b6a24169e8ccfec5e166 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 1 Dec 2023 16:39:15 -0300 Subject: [PATCH 0268/1143] FixImagesQuality: no longer make gifs play when autoplay is off --- src/plugins/fixImagesQuality/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/plugins/fixImagesQuality/index.ts b/src/plugins/fixImagesQuality/index.ts index d94c8e3b..4acd9a92 100644 --- a/src/plugins/fixImagesQuality/index.ts +++ b/src/plugins/fixImagesQuality/index.ts @@ -14,10 +14,12 @@ export default definePlugin({ patches: [ { find: "handleImageLoad=", - replacement: { - match: /(?<=getSrc\(\i\){.+?format:)\i/, - replace: "null" - } + replacement: [ + { + match: /(?<=getSrc\(\i\){.+?return )\i\.SUPPORTS_WEBP.+?:(?=\i&&\(\i="png"\))/, + replace: "" + } + ] } ] }); From 9dd00fb766f6fdf74b037a76a16b43a0edf8866a Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 1 Dec 2023 23:06:18 -0300 Subject: [PATCH 0269/1143] Fix SuperReactionTweaks patch --- src/plugins/superReactionTweaks/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/superReactionTweaks/index.ts b/src/plugins/superReactionTweaks/index.ts index 2652eef0..0e58eb0a 100644 --- a/src/plugins/superReactionTweaks/index.ts +++ b/src/plugins/superReactionTweaks/index.ts @@ -46,10 +46,10 @@ export default definePlugin({ } }, { - find: ".hasAvailableBurstCurrency)", + find: ".trackEmojiSearchEmpty,200", replacement: { - match: /(?<=\.useBurstReactionsExperiment.{0,20})useState\(!1\)(?=.+?(\i===\i\.EmojiIntention.REACTION))/, - replace: "useState($self.settings.store.superReactByDefault && $1)" + match: /(\.trackEmojiSearchEmpty,200(?=.+?isBurstReaction:(\i).+?(\i===\i\.EmojiIntention.REACTION)).+?\[\2,\i\]=\i\.useState\().+?\)/, + replace: (_, rest, isBurstReactionVariable, isReactionIntention) => `${rest}$self.settings.store.superReactByDefault&&${isReactionIntention})` } } ], From 08036f7af28dba4978abed93f9a7b6af5cf979cb Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 6 Dec 2023 01:37:42 -0300 Subject: [PATCH 0270/1143] convert non lazy finds to test with reporter --- src/plugins/muteNewGuild/index.tsx | 6 ++++-- src/plugins/webContextMenus.web/index.ts | 6 +++--- src/utils/modal.tsx | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/plugins/muteNewGuild/index.tsx b/src/plugins/muteNewGuild/index.tsx index 7586830b..08c558a9 100644 --- a/src/plugins/muteNewGuild/index.tsx +++ b/src/plugins/muteNewGuild/index.tsx @@ -19,7 +19,9 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { findByProps } from "@webpack"; +import { findByPropsLazy } from "@webpack"; + +const { updateGuildNotificationSettings } = findByPropsLazy("updateGuildNotificationSettings"); const settings = definePluginSettings({ guild: { @@ -63,7 +65,7 @@ export default definePlugin({ handleMute(guildId: string | null) { if (guildId === "@me" || guildId === "null" || guildId == null) return; - findByProps("updateGuildNotificationSettings").updateGuildNotificationSettings(guildId, + updateGuildNotificationSettings(guildId, { muted: settings.store.guild, suppress_everyone: settings.store.everyone, diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index cf50bb86..eb076dfd 100644 --- a/src/plugins/webContextMenus.web/index.ts +++ b/src/plugins/webContextMenus.web/index.ts @@ -20,9 +20,11 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { saveFile } from "@utils/web"; -import { findByProps } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { Clipboard, ComponentDispatch } from "@webpack/common"; +const ctxMenuCallbacks = findByPropsLazy("contextMenuCallbackNative"); + async function fetchImage(url: string) { const res = await fetch(url); if (res.status !== 200) return; @@ -55,7 +57,6 @@ export default definePlugin({ start() { if (settings.store.addBack) { - const ctxMenuCallbacks = findByProps("contextMenuCallbackNative"); window.removeEventListener("contextmenu", ctxMenuCallbacks.contextMenuCallbackWeb); window.addEventListener("contextmenu", ctxMenuCallbacks.contextMenuCallbackNative); this.changedListeners = true; @@ -64,7 +65,6 @@ export default definePlugin({ stop() { if (this.changedListeners) { - const ctxMenuCallbacks = findByProps("contextMenuCallbackNative"); window.removeEventListener("contextmenu", ctxMenuCallbacks.contextMenuCallbackNative); window.addEventListener("contextmenu", ctxMenuCallbacks.contextMenuCallbackWeb); } diff --git a/src/utils/modal.tsx b/src/utils/modal.tsx index 6758a1a1..b4d0f59f 100644 --- a/src/utils/modal.tsx +++ b/src/utils/modal.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { findByProps, findByPropsLazy } from "@webpack"; +import { findByPropsLazy, findExportedComponentLazy } from "@webpack"; import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react"; import { LazyComponent } from "./react"; @@ -118,7 +118,7 @@ export type ImageModal = ComponentType<{ shouldHideMediaOptions?: boolean; }>; -export const ImageModal = LazyComponent(() => findByProps("ImageModal").ImageModal as ImageModal); +export const ImageModal = findExportedComponentLazy("ImageModal") as ImageModal; export const ModalRoot = LazyComponent(() => Modals.ModalRoot); export const ModalHeader = LazyComponent(() => Modals.ModalHeader); From cf7028331c152da10d64e810238157bf6b267ec4 Mon Sep 17 00:00:00 2001 From: Syncx <47534062+Syncxv@users.noreply.github.com> Date: Thu, 7 Dec 2023 02:55:29 +0530 Subject: [PATCH 0271/1143] Native Folder Support (#2031) --- scripts/build/build.mjs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/build/build.mjs b/scripts/build/build.mjs index a2e0e002..0c2a930a 100755 --- a/scripts/build/build.mjs +++ b/scripts/build/build.mjs @@ -76,7 +76,11 @@ const globNativesPlugin = { if (!await existsAsync(dirPath)) continue; const plugins = await readdir(dirPath); for (const p of plugins) { - if (!await existsAsync(join(dirPath, p, "native.ts"))) continue; + const nativePath = join(dirPath, p, "native.ts"); + const indexNativePath = join(dirPath, p, "native/index.ts"); + + if (!(await existsAsync(nativePath)) && !(await existsAsync(indexNativePath))) + continue; const nameParts = p.split("."); const namePartsWithoutTarget = nameParts.length === 1 ? nameParts : nameParts.slice(0, -1); From 3453d0c97c880a9f12d757d1fb07c72efc077e2a Mon Sep 17 00:00:00 2001 From: Carter Date: Wed, 6 Dec 2023 14:25:53 -0700 Subject: [PATCH 0272/1143] feat(clearUrls): moar twitter rules (#2029) --- src/plugins/clearURLs/defaultRules.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/plugins/clearURLs/defaultRules.ts b/src/plugins/clearURLs/defaultRules.ts index e7c3ecbe..0633b717 100644 --- a/src/plugins/clearURLs/defaultRules.ts +++ b/src/plugins/clearURLs/defaultRules.ts @@ -124,6 +124,18 @@ export const defaultRules = [ "t@*.x.com", "s@*.x.com", "ref_*@*.x.com", + "t@*.fixupx.com", + "s@*.fixupx.com", + "ref_*@*.fixupx.com", + "t@*.fxtwitter.com", + "s@*.fxtwitter.com", + "ref_*@*.fxtwitter.com", + "t@*.twittpr.com", + "s@*.twittpr.com", + "ref_*@*.twittpr.com", + "t@*.fixvx.com", + "s@*.fixvx.com", + "ref_*@*.fixvx.com", "tt_medium", "tt_content", "lr@yandex.*", From c0b6d8f1c4a6c103f0a5ff4af2fad246f8d035b6 Mon Sep 17 00:00:00 2001 From: AutumnVN Date: Thu, 7 Dec 2023 04:27:06 +0700 Subject: [PATCH 0273/1143] devCompanion: add findComponentByCode (#2026) --- src/plugins/devCompanion.dev/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/devCompanion.dev/index.tsx b/src/plugins/devCompanion.dev/index.tsx index 2fa01f25..25fd563e 100644 --- a/src/plugins/devCompanion.dev/index.tsx +++ b/src/plugins/devCompanion.dev/index.tsx @@ -215,6 +215,9 @@ function initWs(isManual = false) { case "ModuleId": results = Object.keys(search(parsedArgs[0])); break; + case "ComponentByCode": + results = findAll(filters.componentByCode(...parsedArgs)); + break; default: return reply("Unknown Find Type " + type); } From 9a89f7b3b228a3a0ed9c4a0ac42d03e87a6aa3c8 Mon Sep 17 00:00:00 2001 From: nya <24845294+nyakowint@users.noreply.github.com> Date: Wed, 6 Dec 2023 16:31:22 -0500 Subject: [PATCH 0274/1143] feat(plugin): XSOverlay (#1901) Co-authored-by: V --- src/plugins/iLoveSpam/index.ts | 2 +- src/plugins/xsOverlay.desktop/README.md | 15 ++ src/plugins/xsOverlay.desktop/index.ts | 288 ++++++++++++++++++++++++ src/plugins/xsOverlay.desktop/native.ts | 16 ++ src/utils/constants.ts | 4 +- 5 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 src/plugins/xsOverlay.desktop/README.md create mode 100644 src/plugins/xsOverlay.desktop/index.ts create mode 100644 src/plugins/xsOverlay.desktop/native.ts diff --git a/src/plugins/iLoveSpam/index.ts b/src/plugins/iLoveSpam/index.ts index 8556de42..bb0b2053 100644 --- a/src/plugins/iLoveSpam/index.ts +++ b/src/plugins/iLoveSpam/index.ts @@ -22,7 +22,7 @@ import definePlugin from "@utils/types"; export default definePlugin({ name: "iLoveSpam", description: "Do not hide messages from 'likely spammers'", - authors: [Devs.botato, Devs.Animal], + authors: [Devs.botato, Devs.Nyako], patches: [ { find: "hasFlag:{writable", diff --git a/src/plugins/xsOverlay.desktop/README.md b/src/plugins/xsOverlay.desktop/README.md new file mode 100644 index 00000000..477e30bf --- /dev/null +++ b/src/plugins/xsOverlay.desktop/README.md @@ -0,0 +1,15 @@ +# XSOverlay Notifier + +Sends Discord messages to [XSOverlay](https://store.steampowered.com/app/1173510/XSOverlay/) for easier viewing while using VR. + +## Preview + +![](https://github.com/Vendicated/Vencord/assets/24845294/205d2055-bb4a-44e4-b7e3-265391bccd40) + +![](https://github.com/Vendicated/Vencord/assets/24845294/f15eff61-2d52-4620-bcab-808ecb1606d2) + +## Usage +- Enable this plugin +- Set plugin settings as desired +- Open XSOverlay +- get ping spammed diff --git a/src/plugins/xsOverlay.desktop/index.ts b/src/plugins/xsOverlay.desktop/index.ts new file mode 100644 index 00000000..5461696e --- /dev/null +++ b/src/plugins/xsOverlay.desktop/index.ts @@ -0,0 +1,288 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; +import { makeRange } from "@components/PluginSettings/components"; +import { Devs } from "@utils/constants"; +import { Logger } from "@utils/Logger"; +import definePlugin, { OptionType, PluginNative } from "@utils/types"; +import { findByPropsLazy } from "@webpack"; +import { ChannelStore, GuildStore, UserStore } from "@webpack/common"; +import type { Channel, Embed, GuildMember, MessageAttachment, User } from "discord-types/general"; + +const enum ChannelTypes { + DM = 1, + GROUP_DM = 3 +} + +interface Message { + guild_id: string, + attachments: MessageAttachment[], + author: User, + channel_id: string, + components: any[], + content: string, + edited_timestamp: string, + embeds: Embed[], + sticker_items?: Sticker[], + flags: number, + id: string, + member: GuildMember, + mention_everyone: boolean, + mention_roles: string[], + mentions: Mention[], + nonce: string, + pinned: false, + referenced_message: any, + timestamp: string, + tts: boolean, + type: number; +} + +interface Mention { + avatar: string, + avatar_decoration_data: any, + discriminator: string, + global_name: string, + id: string, + public_flags: number, + username: string; +} + +interface Sticker { + t: "Sticker"; + description: string; + format_type: number; + guild_id: string; + id: string; + name: string; + tags: string; + type: number; +} + +interface Call { + channel_id: string, + guild_id: string, + message_id: string, + region: string, + ringing: string[]; +} + +const MuteStore = findByPropsLazy("isSuppressEveryoneEnabled"); +const XSLog = new Logger("XSOverlay"); + +const settings = definePluginSettings({ + ignoreBots: { + type: OptionType.BOOLEAN, + description: "Ignore messages from bots", + default: false + }, + pingColor: { + type: OptionType.STRING, + description: "User mention color", + default: "#7289da" + }, + channelPingColor: { + type: OptionType.STRING, + description: "Channel mention color", + default: "#8a2be2" + }, + soundPath: { + type: OptionType.STRING, + description: "Notification sound (default/warning/error)", + default: "default" + }, + timeout: { + type: OptionType.NUMBER, + description: "Notif duration (secs)", + default: 1.0, + }, + opacity: { + type: OptionType.SLIDER, + description: "Notif opacity", + default: 1, + markers: makeRange(0, 1, 0.1) + }, + volume: { + type: OptionType.SLIDER, + description: "Volume", + default: 0.2, + markers: makeRange(0, 1, 0.1) + }, +}); + +const Native = VencordNative.pluginHelpers.XsOverlay as PluginNative; + +export default definePlugin({ + name: "XSOverlay", + description: "Forwards discord notifications to XSOverlay, for easy viewing in VR", + authors: [Devs.Nyako], + tags: ["vr", "notify"], + settings, + flux: { + CALL_UPDATE({ call }: { call: Call; }) { + if (call?.ringing?.includes(UserStore.getCurrentUser().id)) { + const channel = ChannelStore.getChannel(call.channel_id); + sendOtherNotif("Incoming call", `${channel.name} is calling you...`); + } + }, + MESSAGE_CREATE({ message, optimistic }: { message: Message; optimistic: boolean; }) { + // Apparently without this try/catch, discord's socket connection dies if any part of this errors + try { + if (optimistic) return; + const channel = ChannelStore.getChannel(message.channel_id); + if (!shouldNotify(message, channel)) return; + + const pingColor = settings.store.pingColor.replaceAll("#", "").trim(); + const channelPingColor = settings.store.channelPingColor.replaceAll("#", "").trim(); + let finalMsg = message.content; + let titleString = ""; + + if (channel.guild_id) { + const guild = GuildStore.getGuild(channel.guild_id); + titleString = `${message.author.username} (${guild.name}, #${channel.name})`; + } + + + switch (channel.type) { + case ChannelTypes.DM: + titleString = message.author.username.trim(); + break; + case ChannelTypes.GROUP_DM: + const channelName = channel.name.trim() ?? channel.rawRecipients.map(e => e.username).join(", "); + titleString = `${message.author.username} (${channelName})`; + break; + } + + if (message.referenced_message) { + titleString += " (reply)"; + } + + if (message.embeds.length > 0) { + finalMsg += " [embed] "; + if (message.content === "") { + finalMsg = "sent message embed(s)"; + } + } + + if (message.sticker_items) { + finalMsg += " [sticker] "; + if (message.content === "") { + finalMsg = "sent a sticker"; + } + } + + const images = message.attachments.filter(e => + typeof e?.content_type === "string" + && e?.content_type.startsWith("image") + ); + + + images.forEach(img => { + finalMsg += ` [image: ${img.filename}] `; + }); + + message.attachments.filter(a => a && !a.content_type?.startsWith("image")).forEach(a => { + finalMsg += ` [attachment: ${a.filename}] `; + }); + + // make mentions readable + if (message.mentions.length > 0) { + finalMsg = finalMsg.replace(/<@!?(\d{17,20})>/g, (_, id) => `@${UserStore.getUser(id)?.username || "unknown-user"}`); + } + + if (message.mention_roles.length > 0) { + for (const roleId of message.mention_roles) { + const role = GuildStore.getGuild(channel.guild_id).roles[roleId]; + if (!role) continue; + const roleColor = role.colorString ?? `#${pingColor}`; + finalMsg = finalMsg.replace(`<@&${roleId}>`, `@${role.name}`); + } + } + + // make emotes and channel mentions readable + const emoteMatches = finalMsg.match(new RegExp("()", "g")); + const channelMatches = finalMsg.match(new RegExp("<(#\\d+)>", "g")); + + if (emoteMatches) { + for (const eMatch of emoteMatches) { + finalMsg = finalMsg.replace(new RegExp(`${eMatch}`, "g"), `:${eMatch.split(":")[1]}:`); + } + } + + if (channelMatches) { + for (const cMatch of channelMatches) { + let channelId = cMatch.split("<#")[1]; + channelId = channelId.substring(0, channelId.length - 1); + finalMsg = finalMsg.replace(new RegExp(`${cMatch}`, "g"), `#${ChannelStore.getChannel(channelId).name}`); + } + } + + sendMsgNotif(titleString, finalMsg, message); + } catch (err) { + XSLog.error(`Failed to catch MESSAGE_CREATE: ${err}`); + } + } + } +}); + +function sendMsgNotif(titleString: string, content: string, message: Message) { + fetch(`https://cdn.discordapp.com/avatars/${message.author.id}/${message.author.avatar}.png?size=128`).then(response => response.arrayBuffer()).then(result => { + const msgData = { + messageType: 1, + index: 0, + timeout: settings.store.timeout, + height: calculateHeight(cleanMessage(content)), + opacity: settings.store.opacity, + volume: settings.store.volume, + audioPath: settings.store.soundPath, + title: titleString, + content: content, + useBase64Icon: true, + icon: result, + sourceApp: "Vencord" + }; + Native.sendToOverlay(msgData); + }); +} + +function sendOtherNotif(content: string, titleString: string) { + const msgData = { + messageType: 1, + index: 0, + timeout: settings.store.timeout, + height: calculateHeight(cleanMessage(content)), + opacity: settings.store.opacity, + volume: settings.store.volume, + audioPath: settings.store.soundPath, + title: titleString, + content: content, + useBase64Icon: false, + icon: null, + sourceApp: "Vencord" + }; + Native.sendToOverlay(msgData); +} + +function shouldNotify(message: Message, channel: Channel) { + const currentUser = UserStore.getCurrentUser(); + if (message.author.id === currentUser.id) return false; + if (message.author.bot && settings.store.ignoreBots) return false; + if (MuteStore.allowAllMessages(channel) || message.mention_everyone && !MuteStore.isSuppressEveryoneEnabled(message.guild_id)) return true; + + return message.mentions.some(m => m.id === currentUser.id); +} + +function calculateHeight(content: string) { + if (content.length <= 100) return 100; + if (content.length <= 200) return 150; + if (content.length <= 300) return 200; + return 250; +} + +function cleanMessage(content: string) { + return content.replace(new RegExp("<[^>]*>", "g"), ""); +} diff --git a/src/plugins/xsOverlay.desktop/native.ts b/src/plugins/xsOverlay.desktop/native.ts new file mode 100644 index 00000000..82809383 --- /dev/null +++ b/src/plugins/xsOverlay.desktop/native.ts @@ -0,0 +1,16 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { createSocket, Socket } from "dgram"; + +let xsoSocket: Socket; + +export function sendToOverlay(_, data: any) { + data.icon = Buffer.from(data.icon).toString("base64"); + const json = JSON.stringify(data); + xsoSocket ??= createSocket("udp4"); + xsoSocket.send(json, 42069, "127.0.0.1"); +} diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 0ff7da72..daa4a74d 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -78,8 +78,8 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "Samu", id: 702973430449832038n, }, - Animal: { - name: "Animal", + Nyako: { + name: "nyako", id: 118437263754395652n }, MaiKokain: { From 613b2dc5f61fcdd6b03d1e66da89f7c8062d118b Mon Sep 17 00:00:00 2001 From: fres621 <126067139+fres621@users.noreply.github.com> Date: Wed, 6 Dec 2023 21:54:23 +0000 Subject: [PATCH 0275/1143] Fix QuickMention not working in DMs (#2028) Co-authored-by: V --- src/plugins/quickMention/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/quickMention/index.tsx b/src/plugins/quickMention/index.tsx index 9720c7d3..df86e9b7 100644 --- a/src/plugins/quickMention/index.tsx +++ b/src/plugins/quickMention/index.tsx @@ -31,7 +31,7 @@ export default definePlugin({ start() { addButton("QuickMention", msg => { const channel = ChannelStore.getChannel(msg.channel_id); - if (!PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return null; + if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return null; return { label: "Quick Mention", From 9faa1331da67b76a50697d61c00f72f5601b8b4a Mon Sep 17 00:00:00 2001 From: philipbry <81459908+philipbry@users.noreply.github.com> Date: Wed, 6 Dec 2023 23:15:34 +0100 Subject: [PATCH 0276/1143] new plugin: notificationVolume (#1992) Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Co-authored-by: V --- src/plugins/notificationVolume/README.md | 3 ++ src/plugins/notificationVolume/index.ts | 35 ++++++++++++++++++++++++ src/utils/constants.ts | 4 +++ 3 files changed, 42 insertions(+) create mode 100644 src/plugins/notificationVolume/README.md create mode 100644 src/plugins/notificationVolume/index.ts diff --git a/src/plugins/notificationVolume/README.md b/src/plugins/notificationVolume/README.md new file mode 100644 index 00000000..05e5af4e --- /dev/null +++ b/src/plugins/notificationVolume/README.md @@ -0,0 +1,3 @@ +# NotificationVolume + +Set a separate volume for notifications and in-app sounds (e.g. messages, call sound, mute/unmute) helping your ears stay healthy for many years to come. diff --git a/src/plugins/notificationVolume/index.ts b/src/plugins/notificationVolume/index.ts new file mode 100644 index 00000000..50eabee7 --- /dev/null +++ b/src/plugins/notificationVolume/index.ts @@ -0,0 +1,35 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; + +const settings = definePluginSettings({ + notificationVolume: { + type: OptionType.SLIDER, + description: "Notification volume", + markers: [0, 25, 50, 75, 100], + default: 100, + stickToMarkers: false + } +}); + +export default definePlugin({ + name: "NotificationVolume", + description: "Save your ears and set a separate volume for notifications and in-app sounds", + authors: [Devs.philipbry], + settings, + patches: [ + { + find: "_ensureAudio(){", + replacement: { + match: /onloadeddata=\(\)=>\{.\.volume=/, + replace: "$&$self.settings.store.notificationVolume/100*" + }, + }, + ], +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index daa4a74d..1c477b12 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -387,6 +387,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "ant0n", id: 145224646868860928n }, + philipbry: { + name: "philipbry", + id: 554994003318276106n + }, Korbo: { name: "Korbo", id: 455856406420258827n From e4942397dc0a536fcdc08f060977188d1376268b Mon Sep 17 00:00:00 2001 From: Nickyux <30734036+nmsturcke@users.noreply.github.com> Date: Wed, 6 Dec 2023 23:18:03 +0100 Subject: [PATCH 0277/1143] MessageClickActions: Ignore Ephemeral & Deleted Messages (#1972) Co-authored-by: V --- src/plugins/messageClickActions/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/plugins/messageClickActions/index.ts b/src/plugins/messageClickActions/index.ts index 08cee4c9..052c33f6 100644 --- a/src/plugins/messageClickActions/index.ts +++ b/src/plugins/messageClickActions/index.ts @@ -72,6 +72,7 @@ export default definePlugin({ if (event.detail < 2) return; if (settings.store.requireModifier && !event.ctrlKey && !event.shiftKey) return; if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return; + if (msg.deleted === true) return; if (isMe) { if (!settings.store.enableDoubleClickToEdit || EditStore.isEditing(channel.id, msg.id)) return; @@ -81,6 +82,9 @@ export default definePlugin({ } else { if (!settings.store.enableDoubleClickToReply) return; + const EPHEMERAL = 64; + if (msg.hasFlag(EPHEMERAL)) return; + FluxDispatcher.dispatch({ type: "CREATE_PENDING_REPLY", channel, From 920252956f60377b8aa593c217830391dfa344ff Mon Sep 17 00:00:00 2001 From: AutumnVN Date: Thu, 7 Dec 2023 05:42:40 +0700 Subject: [PATCH 0278/1143] shikiCodeBlocks: support file preview (#1977) --- src/plugins/shikiCodeblocks.desktop/index.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/plugins/shikiCodeblocks.desktop/index.ts b/src/plugins/shikiCodeblocks.desktop/index.ts index f358497d..ef1b5d3d 100644 --- a/src/plugins/shikiCodeblocks.desktop/index.ts +++ b/src/plugins/shikiCodeblocks.desktop/index.ts @@ -42,6 +42,13 @@ export default definePlugin({ match: /codeBlock:\{react\((\i),(\i),(\i)\)\{/, replace: "$&return $self.renderHighlighter($1,$2,$3);" } + }, + { + find: ".PREVIEW_NUM_LINES", + replacement: { + match: /(?<=function \i\((\i)\)\{)(?=let\{text:\i,language:)/, + replace: "return $self.renderHighlighter({lang:$1.language,content:$1.text});" + } } ], start: async () => { From fd9c675942e727570776b4018c26674d21ecc6e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Han=20Seung=20Min=20-=20=ED=95=9C=EC=8A=B9=EB=AF=BC?= Date: Thu, 7 Dec 2023 08:25:13 +0900 Subject: [PATCH 0279/1143] fix: patch helper overflow (#2007) --- src/components/VencordSettings/PatchHelperTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/VencordSettings/PatchHelperTab.tsx b/src/components/VencordSettings/PatchHelperTab.tsx index 0b869a51..35f46ef5 100644 --- a/src/components/VencordSettings/PatchHelperTab.tsx +++ b/src/components/VencordSettings/PatchHelperTab.tsx @@ -108,7 +108,7 @@ function ReplacementComponent({ module, match, replacement, setReplacementError function renderDiff() { return diff?.map(p => { const color = p.added ? "lime" : p.removed ? "red" : "grey"; - return
{p.value}
; + return
{p.value}
; }); } From 6ee50d30f6b0886aa03a2829618205f0f53542db Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 6 Dec 2023 18:26:08 -0500 Subject: [PATCH 0280/1143] fix(dearrow): remove > from DeArrow titles (#1999) --- src/plugins/dearrow/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/dearrow/index.tsx b/src/plugins/dearrow/index.tsx index 52cf1d53..80eb84aa 100644 --- a/src/plugins/dearrow/index.tsx +++ b/src/plugins/dearrow/index.tsx @@ -60,7 +60,7 @@ async function embedDidMount(this: Component) { if (hasTitle) { embed.dearrow.oldTitle = embed.rawTitle; - embed.rawTitle = titles[0].title; + embed.rawTitle = titles[0].title.replace(/ >(\S)/g, " $1"); } if (hasThumb) { From 34cbb22efe82484ca7fa5e655411679f9c82422a Mon Sep 17 00:00:00 2001 From: Damien Erambert Date: Wed, 6 Dec 2023 15:30:41 -0800 Subject: [PATCH 0281/1143] feat: add dropdown to choose vibrancy value on macOS (#1941) Co-authored-by: V --- src/api/Settings.ts | 20 ++++- src/components/VencordSettings/VencordTab.tsx | 79 +++++++++++++++++-- src/main/patcher.ts | 10 ++- 3 files changed, 100 insertions(+), 9 deletions(-) diff --git a/src/api/Settings.ts b/src/api/Settings.ts index 368f88f7..004a8988 100644 --- a/src/api/Settings.ts +++ b/src/api/Settings.ts @@ -38,7 +38,21 @@ export interface Settings { frameless: boolean; transparent: boolean; winCtrlQ: boolean; - macosTranslucency: boolean; + macosVibrancyStyle: + | "content" + | "fullscreen-ui" + | "header" + | "hud" + | "menu" + | "popover" + | "selection" + | "sidebar" + | "titlebar" + | "tooltip" + | "under-page" + | "window" + | undefined; + macosTranslucency: boolean | undefined; disableMinSize: boolean; winNativeTitleBar: boolean; plugins: { @@ -74,7 +88,9 @@ const DefaultSettings: Settings = { frameless: false, transparent: false, winCtrlQ: false, - macosTranslucency: false, + // Replaced by macosVibrancyStyle + macosTranslucency: undefined, + macosVibrancyStyle: undefined, disableMinSize: false, winNativeTitleBar: false, plugins: {}, diff --git a/src/components/VencordSettings/VencordTab.tsx b/src/components/VencordSettings/VencordTab.tsx index a8e9ea5b..07d777eb 100644 --- a/src/components/VencordSettings/VencordTab.tsx +++ b/src/components/VencordSettings/VencordTab.tsx @@ -48,6 +48,15 @@ function VencordSettings() { const isWindows = navigator.platform.toLowerCase().startsWith("win"); const isMac = navigator.platform.toLowerCase().startsWith("mac"); + const needsVibrancySettings = IS_DISCORD_DESKTOP && isMac; + + // One-time migration of the old setting to the new one if necessary. + React.useEffect(() => { + if (settings.macosTranslucency === true && !settings.macosVibrancyStyle) { + settings.macosVibrancyStyle = "sidebar"; + settings.macosTranslucency = undefined; + } + }, []); const Switches: Array; @@ -89,11 +98,6 @@ function VencordSettings() { title: "Disable minimum window size", note: "Requires a full restart" }, - IS_DISCORD_DESKTOP && isMac && { - key: "macosTranslucency", - title: "Enable translucent window", - note: "Requires a full restart" - } ]; return ( @@ -152,6 +156,71 @@ function VencordSettings() {
+ {needsVibrancySettings && <> + Window vibrancy style (requires restart) +