From 748a456cfb973cdfcae92d5f342bfba6b3762183 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 28 Jun 2024 18:54:37 -0300 Subject: [PATCH 1/5] Delete patching properties after used --- src/plugins/_core/noTrack.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/_core/noTrack.ts b/src/plugins/_core/noTrack.ts index ef2849bbc..b94a91092 100644 --- a/src/plugins/_core/noTrack.ts +++ b/src/plugins/_core/noTrack.ts @@ -114,6 +114,7 @@ export default definePlugin({ } })); + Reflect.deleteProperty(Function.prototype, "g"); Reflect.deleteProperty(Object.prototype, cacheExtractSym); Reflect.deleteProperty(window, "DiscordSentry"); return { exports: {} }; @@ -130,6 +131,7 @@ export default definePlugin({ set() { new Logger("NoTrack", "#8caaee").error("Failed to disable Sentry. Falling back to deleting window.DiscordSentry"); + Reflect.deleteProperty(Function.prototype, "g"); Reflect.deleteProperty(window, "DiscordSentry"); } }); From 8bf688218c94226855fbb861d5c5ac8dfe423078 Mon Sep 17 00:00:00 2001 From: Lillith Date: Fri, 28 Jun 2024 19:23:42 -0400 Subject: [PATCH 2/5] feat(plugin): PasswordProtect --- src/plugins/passwordProtect/README.md | 5 + .../components/accessModal.tsx | 22 +++++ .../components/contextMenus.tsx | 71 +++++++++++++++ .../passwordProtect/components/lockModal.tsx | 24 +++++ .../passwordProtect/components/modal.tsx | 91 +++++++++++++++++++ .../components/unlockModal.tsx | 26 ++++++ src/plugins/passwordProtect/data.ts | 60 ++++++++++++ src/plugins/passwordProtect/index.tsx | 89 ++++++++++++++++++ src/plugins/passwordProtect/utils.ts | 26 ++++++ 9 files changed, 414 insertions(+) create mode 100644 src/plugins/passwordProtect/README.md create mode 100644 src/plugins/passwordProtect/components/accessModal.tsx create mode 100644 src/plugins/passwordProtect/components/contextMenus.tsx create mode 100644 src/plugins/passwordProtect/components/lockModal.tsx create mode 100644 src/plugins/passwordProtect/components/modal.tsx create mode 100644 src/plugins/passwordProtect/components/unlockModal.tsx create mode 100644 src/plugins/passwordProtect/data.ts create mode 100644 src/plugins/passwordProtect/index.tsx create mode 100644 src/plugins/passwordProtect/utils.ts diff --git a/src/plugins/passwordProtect/README.md b/src/plugins/passwordProtect/README.md new file mode 100644 index 000000000..949a4c39c --- /dev/null +++ b/src/plugins/passwordProtect/README.md @@ -0,0 +1,5 @@ +# PasswordProtect + +Allows you to password protect your dms and channels! + +![A popup warning you that a channel is password protected](https://github.com/Vendicated/Vencord/assets/44179559/2424085e-3090-4310-9d56-62667941c57c) diff --git a/src/plugins/passwordProtect/components/accessModal.tsx b/src/plugins/passwordProtect/components/accessModal.tsx new file mode 100644 index 000000000..bd4b67883 --- /dev/null +++ b/src/plugins/passwordProtect/components/accessModal.tsx @@ -0,0 +1,22 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { openModalLazy } from "@utils/modal"; + +import { checkPassword } from "../data"; +import { ModalType, PasswordModal } from "./modal"; + +export async function openAccessModal(channelId: string, cb: (success: boolean) => void) { + await openModalLazy(async () => { + return modalProps => { + if (password) { + cb(await checkPassword(password, channelId)); + } else { + cb(false); + } + }} />; + }); +} diff --git a/src/plugins/passwordProtect/components/contextMenus.tsx b/src/plugins/passwordProtect/components/contextMenus.tsx new file mode 100644 index 000000000..34b36c0c8 --- /dev/null +++ b/src/plugins/passwordProtect/components/contextMenus.tsx @@ -0,0 +1,71 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; +import { Menu } from "@webpack/common"; + +import { isPasswordProtected } from "../data"; +import { openLockModal } from "./lockModal"; +import { openUnlockModal } from "./unlockModal"; + +function createPasswordItem(channelId: string) { + const isProtected = isPasswordProtected(channelId); + + return ( + + {!isProtected && ( + <> + openLockModal(channelId)} + /> + + )} + + {isProtected && ( + <> + openUnlockModal(channelId)} + /> + + )} + + + ); +} + +const GroupDMContext: NavContextMenuPatchCallback = (children, props) => { + const container = findGroupChildrenByChildId("leave-channel", children); + container?.unshift(createPasswordItem(props.channel.id)); +}; + +const UserContext: NavContextMenuPatchCallback = (children, props) => { + const container = findGroupChildrenByChildId("close-dm", children); + if (container) { + const idx = container.findIndex(c => c?.props?.id === "close-dm"); + container.splice(idx, 0, createPasswordItem(props.channel.id)); + } +}; + +const ChannelContect: NavContextMenuPatchCallback = (children, props) => { + const container = findGroupChildrenByChildId(["mute-channel", "unmute-channel"], children); + container?.unshift(createPasswordItem(props.channel.id)); +}; + + +export const contextMenus = { + "gdm-context": GroupDMContext, + "user-context": UserContext, + "channel-context": ChannelContect +}; diff --git a/src/plugins/passwordProtect/components/lockModal.tsx b/src/plugins/passwordProtect/components/lockModal.tsx new file mode 100644 index 000000000..879184fdb --- /dev/null +++ b/src/plugins/passwordProtect/components/lockModal.tsx @@ -0,0 +1,24 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { openModalLazy } from "@utils/modal"; + +import { setPassword } from "../data"; +import { isChannelCurrent, reloadChannel } from "../utils"; +import { ModalType, PasswordModal } from "./modal"; + +export async function openLockModal(channelId: string) { + await openModalLazy(async () => { + return modalProps => { + if (password) { + setPassword(channelId, password); + if (isChannelCurrent(channelId)) { + reloadChannel(); + } + } + }} />; + }); +} diff --git a/src/plugins/passwordProtect/components/modal.tsx b/src/plugins/passwordProtect/components/modal.tsx new file mode 100644 index 000000000..51da00450 --- /dev/null +++ b/src/plugins/passwordProtect/components/modal.tsx @@ -0,0 +1,91 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { classNameFactory } from "@api/Styles"; +import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal"; +import { Button, Forms, Text, TextInput, useEffect, useState } from "@webpack/common"; + +import { checkPassword } from "../data"; + +export enum ModalType { + Lock = "Lock", + Unlock = "Unlock", + Access = "Access" +} + +interface Props { + channelId: string; + type: ModalType; + callback: (password?: string) => void; + modalProps: ModalProps; +} + +const cl = classNameFactory("vc-password-modal-"); + +export function PasswordModal({ channelId, type, callback, modalProps }: Props) { + + const [password, setPassword] = useState(""); + const [confirmPasswordValue, setConfirmPasswordValue] = useState(""); + const [error, setError] = useState(""); + + useEffect(() => { + (async () => { + if (password !== confirmPasswordValue && [ModalType.Lock, ModalType.Unlock].includes(type)) { + setError("Passwords do not match"); + } else if (password === "") { + setError("Please enter a password"); + } + else if (type !== ModalType.Lock && !(await checkPassword(password, channelId))) { + setError("Incorrect Password"); + } else { + setError(""); + } + })(); + }, [password, confirmPasswordValue]); + + const onSubmit = async (e: React.FormEvent | React.MouseEvent) => { + e.preventDefault(); + if (error) return callback(); + modalProps.onClose(); + callback(password); + }; + return ( + + + {type} + + + {/* form is here so when you press enter while in the text input it submits */} +
+ + + Password + setPassword(e)} + /> + + {[ModalType.Lock, ModalType.Unlock].includes(type) && ( + + Confirm Password + setConfirmPasswordValue(e)} + /> + + )} + + {error && {error}} + + + + +
+
+ ); +} diff --git a/src/plugins/passwordProtect/components/unlockModal.tsx b/src/plugins/passwordProtect/components/unlockModal.tsx new file mode 100644 index 000000000..3d0d594af --- /dev/null +++ b/src/plugins/passwordProtect/components/unlockModal.tsx @@ -0,0 +1,26 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { openModalLazy } from "@utils/modal"; + +import { checkPassword, removePassword } from "../data"; +import { isChannelCurrent, reloadChannel } from "../utils"; +import { ModalType, PasswordModal } from "./modal"; + +export async function openUnlockModal(channelId: string) { + await openModalLazy(async () => { + return modalProps => { + if (password) { + if (await checkPassword(password, channelId)) { + removePassword(channelId); + if (isChannelCurrent(channelId)) { + reloadChannel(); + } + } + } + }} />; + }); +} diff --git a/src/plugins/passwordProtect/data.ts b/src/plugins/passwordProtect/data.ts new file mode 100644 index 000000000..e5eec5042 --- /dev/null +++ b/src/plugins/passwordProtect/data.ts @@ -0,0 +1,60 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { DataStore } from "@api/index"; +import { Channel } from "discord-types/general"; + +import { isChannelCurrent, reloadChannel, sha256 } from "./utils"; + +let data: Record = {}; +const accessedChannels: string[] = []; + +export async function initData() { + const newData = await DataStore.get("passwordProtect"); + if (newData) { + data = newData; + } +} + +export async function saveData() { + await DataStore.set("passwordProtect", data); +} + +export function isLocked(channelId: string) { + if (accessedChannels.includes(channelId)) return false; + return isPasswordProtected(channelId); +} + +export function isPasswordProtected(channelId: string) { + return data?.[channelId] !== undefined; +} + +export function getPasswordHash(channelId: string) { + return data?.[channelId]; +} + +export async function setPassword(channelId: string, password: string) { + data![channelId] = await sha256(password); + await saveData(); +} + +export async function removePassword(channelId: string) { + delete data![channelId]; + await saveData(); +} + +export async function checkPassword(input: string, channelId: string) { + return await sha256(input) === getPasswordHash(channelId); +} + + +export function accessChannel(channel: Channel) { + accessedChannels.push(channel.id); + if (isChannelCurrent(channel.id)) reloadChannel(); + setTimeout(() => { + accessedChannels.splice(accessedChannels.indexOf(channel.id), 1); + }, 1000); +} diff --git a/src/plugins/passwordProtect/index.tsx b/src/plugins/passwordProtect/index.tsx new file mode 100644 index 000000000..42e3d1c19 --- /dev/null +++ b/src/plugins/passwordProtect/index.tsx @@ -0,0 +1,89 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Devs } from "@utils/constants"; +import { getCurrentChannel } from "@utils/discord"; +import definePlugin from "@utils/types"; + +import { openAccessModal } from "./components/accessModal"; +import { contextMenus } from "./components/contextMenus"; +import { accessChannel, initData, isLocked, isPasswordProtected, saveData } from "./data"; + +interface NSFWBlockProps { + title: string; + description: string; + agreement: string; + disagreement: string; + onAgree: () => void; + onDisagree: () => void; +} + +export default definePlugin({ + name: "PasswordProtect", + description: "Passcode protect servers, channels, and dms", + authors: [Devs.ImLvna], + + contextMenus: contextMenus, + + patches: [ + { + find: "GuildNSFWAgreeStore", + replacement: { + match: /didAgree\((\i)\){/, + replace: "$&if($self.isLocked($1)) return false; if($self.isPasswordProtected($1)) return true;" + } + }, + { + find: "return this.nsfw", + replacement: { + match: /return this.nsfw/, + replace: "if($self.isLocked(this.id)) return true;$&" + } + }, + { + find: ".gatedContent,", + replacement: { + match: /this.props/, + replace: "$self.patchProps($&)" + } + } + ], + + patchProps(props: NSFWBlockProps) { + const channel = getCurrentChannel(); + if (!isPasswordProtected(channel.id)) return props; + props.title = "This channel is password protected"; + props.description = "This channel is password protected. Please enter the password to view the content."; + props.agreement = "Enter password"; + props.disagreement = "Cancel"; + const oldOnAgree = props.onAgree; + props.onAgree = () => { + openAccessModal(channel.id, async success => { + console.log(success); + if (success) { + accessChannel(channel); + } + } + ); + }; + return props; + }, + + isLocked, + + isPasswordProtected, + + start() { + initData(); + }, + stop() { + saveData(); + }, + + // navigate(href: string): boolean { + // return Math.random() < 0.5; + // }, +}); diff --git a/src/plugins/passwordProtect/utils.ts b/src/plugins/passwordProtect/utils.ts new file mode 100644 index 000000000..5898a7c11 --- /dev/null +++ b/src/plugins/passwordProtect/utils.ts @@ -0,0 +1,26 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { getCurrentChannel } from "@utils/discord"; +import { NavigationRouter } from "@webpack/common"; + +export async function sha256(message) { + const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(message)); + const array = Array.from(new Uint8Array(buf)); + const str = array.map(b => b.toString(16).padStart(2, "0")).join(""); + return str; +} + +export function isChannelCurrent(channelId: string) { + return getCurrentChannel()?.id === channelId; +} + +export async function reloadChannel() { + const channel = getCurrentChannel(); + NavigationRouter.transitionTo("/channels/@me"); + await new Promise(r => setTimeout(r, 0)); + NavigationRouter.transitionTo(`/channels/${channel.guild_id || "@me"}/${channel.id}`); +} From 33792f995f82b77b9e6c0f7ee1783e0203c4390f Mon Sep 17 00:00:00 2001 From: Lillith Date: Fri, 28 Jun 2024 19:26:21 -0400 Subject: [PATCH 3/5] fix: we dont need onAgree --- src/plugins/passwordProtect/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/passwordProtect/index.tsx b/src/plugins/passwordProtect/index.tsx index 42e3d1c19..57e8469d8 100644 --- a/src/plugins/passwordProtect/index.tsx +++ b/src/plugins/passwordProtect/index.tsx @@ -59,7 +59,6 @@ export default definePlugin({ props.description = "This channel is password protected. Please enter the password to view the content."; props.agreement = "Enter password"; props.disagreement = "Cancel"; - const oldOnAgree = props.onAgree; props.onAgree = () => { openAccessModal(channel.id, async success => { console.log(success); From 68925dbdff7c92d70bca0ff8dc16d63fd399a956 Mon Sep 17 00:00:00 2001 From: Lillith Date: Fri, 28 Jun 2024 19:42:45 -0400 Subject: [PATCH 4/5] fix: add warning that disabling the plugin will allow access --- src/plugins/passwordProtect/index.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/plugins/passwordProtect/index.tsx b/src/plugins/passwordProtect/index.tsx index 57e8469d8..5f0dd5c73 100644 --- a/src/plugins/passwordProtect/index.tsx +++ b/src/plugins/passwordProtect/index.tsx @@ -23,7 +23,7 @@ interface NSFWBlockProps { export default definePlugin({ name: "PasswordProtect", - description: "Passcode protect servers, channels, and dms", + description: "Passcode protect servers, channels, and dms. WARNING: Disabling the plugin will allow anyone to open the channels!", authors: [Devs.ImLvna], contextMenus: contextMenus, @@ -81,8 +81,4 @@ export default definePlugin({ stop() { saveData(); }, - - // navigate(href: string): boolean { - // return Math.random() < 0.5; - // }, }); From 7274df130dcbf105ca67b8cfc6334a2ab17d9199 Mon Sep 17 00:00:00 2001 From: vee Date: Sun, 30 Jun 2024 16:13:52 +0200 Subject: [PATCH 5/5] Discard changes to src/plugins/_core/noTrack.ts --- src/plugins/_core/noTrack.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/plugins/_core/noTrack.ts b/src/plugins/_core/noTrack.ts index b94a91092..ef2849bbc 100644 --- a/src/plugins/_core/noTrack.ts +++ b/src/plugins/_core/noTrack.ts @@ -114,7 +114,6 @@ export default definePlugin({ } })); - Reflect.deleteProperty(Function.prototype, "g"); Reflect.deleteProperty(Object.prototype, cacheExtractSym); Reflect.deleteProperty(window, "DiscordSentry"); return { exports: {} }; @@ -131,7 +130,6 @@ export default definePlugin({ set() { new Logger("NoTrack", "#8caaee").error("Failed to disable Sentry. Falling back to deleting window.DiscordSentry"); - Reflect.deleteProperty(Function.prototype, "g"); Reflect.deleteProperty(window, "DiscordSentry"); } });