diff --git a/README.md b/README.md index eb0fb395..a3a6d6b8 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Equicord is a fork of [Vencord](https://github.com/Vendicated/Vencord), with over 300+ plugins. -You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, changes, chat or even support.


+You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, changes, chat or even support. ### Extra included plugins diff --git a/src/main/index.ts b/src/main/index.ts index 5c27b188..28ce041d 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -16,8 +16,9 @@ * along with this program. If not, see . */ -import { app, protocol } from "electron"; +import { app, net, protocol } from "electron"; import { join } from "path"; +import { pathToFileURL } from "url"; import { initCsp } from "./csp"; import { ensureSafePath } from "./ipcMain"; @@ -27,55 +28,71 @@ import { installExt } from "./utils/extensions"; if (!IS_VANILLA && !IS_EXTENSION) { app.whenReady().then(() => { - // Source Maps! Maybe there's a better way but since the renderer is executed - // from a string I don't think any other form of sourcemaps would work - protocol.registerFileProtocol("vencord", ({ url: unsafeUrl }, cb) => { - let url = unsafeUrl.slice("vencord://".length); + protocol.handle("vencord", ({ url: unsafeUrl }) => { + let url = decodeURI(unsafeUrl).slice("vencord://".length).replace(/\?v=\d+$/, ""); + if (url.endsWith("/")) url = url.slice(0, -1); + if (url.startsWith("/themes/")) { const theme = url.slice("/themes/".length); + const safeUrl = ensureSafePath(THEMES_DIR, theme); if (!safeUrl) { - cb({ statusCode: 403 }); - return; + return new Response(null, { + status: 404 + }); } - cb(safeUrl.replace(/\?v=\d+$/, "")); - return; + + return net.fetch(pathToFileURL(safeUrl).toString()); } + + // Source Maps! Maybe there's a better way but since the renderer is executed + // from a string I don't think any other form of sourcemaps would work + switch (url) { case "renderer.js.map": case "preload.js.map": case "patcher.js.map": case "main.js.map": - cb(join(__dirname, url)); - break; + return net.fetch(pathToFileURL(join(__dirname, url)).toString()); default: - cb({ statusCode: 403 }); + return new Response(null, { + status: 404 + }); } }); - protocol.registerFileProtocol("equicord", ({ url: unsafeUrl }, cb) => { - let url = unsafeUrl.slice("equicord://".length); + protocol.handle("equicord", ({ url: unsafeUrl }) => { + let url = decodeURI(unsafeUrl).slice("equicord://".length).replace(/\?v=\d+$/, ""); + if (url.endsWith("/")) url = url.slice(0, -1); + if (url.startsWith("/themes/")) { const theme = url.slice("/themes/".length); + const safeUrl = ensureSafePath(THEMES_DIR, theme); if (!safeUrl) { - cb({ statusCode: 403 }); - return; + return new Response(null, { + status: 404 + }); } - cb(safeUrl.replace(/\?v=\d+$/, "")); - return; + + return net.fetch(pathToFileURL(safeUrl).toString()); } + + // Source Maps! Maybe there's a better way but since the renderer is executed + // from a string I don't think any other form of sourcemaps would work + switch (url) { case "renderer.js.map": case "preload.js.map": case "patcher.js.map": case "main.js.map": - cb(join(__dirname, url)); - break; + return net.fetch(pathToFileURL(join(__dirname, url)).toString()); default: - cb({ statusCode: 403 }); + return new Response(null, { + status: 404 + }); } }); diff --git a/src/main/ipcMain.ts b/src/main/ipcMain.ts index 43fd2099..f7034a68 100644 --- a/src/main/ipcMain.ts +++ b/src/main/ipcMain.ts @@ -34,7 +34,7 @@ import { makeLinksOpenExternally } from "./utils/externalLinks"; mkdirSync(THEMES_DIR, { recursive: true }); export function ensureSafePath(basePath: string, path: string) { - const normalizedBasePath = normalize(basePath); + const normalizedBasePath = normalize(basePath + "/"); const newPath = join(basePath, path); const normalizedPath = normalize(newPath); return normalizedPath.startsWith(normalizedBasePath) ? normalizedPath : null; diff --git a/src/plugins/messageClickActions/index.ts b/src/plugins/messageClickActions/index.ts index 19ccaa95..723ece12 100644 --- a/src/plugins/messageClickActions/index.ts +++ b/src/plugins/messageClickActions/index.ts @@ -20,7 +20,8 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { FluxDispatcher, PermissionsBits, PermissionStore, UserStore } from "@webpack/common"; +import { FluxDispatcher, PermissionsBits, PermissionStore, UserStore, WindowStore } from "@webpack/common"; +import NoReplyMentionPlugin from "plugins/noReplyMention"; const MessageActions = findByPropsLazy("deleteMessage", "startEditMessage"); const EditStore = findByPropsLazy("isEditing", "isEditingAny"); @@ -28,6 +29,7 @@ const EditStore = findByPropsLazy("isEditing", "isEditingAny"); let isDeletePressed = false; const keydown = (e: KeyboardEvent) => e.key === "Backspace" && (isDeletePressed = true); const keyup = (e: KeyboardEvent) => e.key === "Backspace" && (isDeletePressed = false); +const focusChanged = () => !WindowStore.isFocused() && (isDeletePressed = false); const settings = definePluginSettings({ enableDeleteOnClick: { @@ -62,11 +64,13 @@ export default definePlugin({ start() { document.addEventListener("keydown", keydown); document.addEventListener("keyup", keyup); + WindowStore.addChangeListener(focusChanged); }, stop() { document.removeEventListener("keydown", keydown); document.removeEventListener("keyup", keyup); + WindowStore.removeChangeListener(focusChanged); }, onMessageClick(msg: any, channel, event) { @@ -89,9 +93,8 @@ export default definePlugin({ if (msg.hasFlag(EPHEMERAL)) return; const isShiftPress = event.shiftKey && !settings.store.requireModifier; - const NoReplyMention = Vencord.Plugins.plugins.NoReplyMention as any as typeof import("../noReplyMention").default; - const shouldMention = Vencord.Plugins.isPluginEnabled("NoReplyMention") - ? NoReplyMention.shouldMention(msg, isShiftPress) + const shouldMention = Vencord.Plugins.isPluginEnabled(NoReplyMentionPlugin.name) + ? NoReplyMentionPlugin.shouldMention(msg, isShiftPress) : !isShiftPress; FluxDispatcher.dispatch({ diff --git a/src/plugins/quickReply/index.ts b/src/plugins/quickReply/index.ts index f08e9faa..5a6b45f9 100644 --- a/src/plugins/quickReply/index.ts +++ b/src/plugins/quickReply/index.ts @@ -16,19 +16,20 @@ * along with this program. If not, see . */ -import { definePluginSettings, Settings } from "@api/Settings"; +import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { ChannelStore, ComponentDispatch, FluxDispatcher as Dispatcher, MessageStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common"; +import { ChannelStore, ComponentDispatch, FluxDispatcher as Dispatcher, MessageActions, MessageStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common"; import { Message } from "discord-types/general"; +import NoBlockedMessagesPlugin from "plugins/noBlockedMessages"; +import NoReplyMentionPlugin from "plugins/noReplyMention"; -const Kangaroo = findByPropsLazy("jumpToMessage"); const RelationshipStore = findByPropsLazy("getRelationships", "isBlocked"); const isMac = navigator.platform.includes("Mac"); // bruh -let replyIdx = -1; -let editIdx = -1; +let currentlyReplyingId: string | null = null; +let currentlyEditingId: string | null = null; const enum MentionOptions { @@ -69,36 +70,29 @@ export default definePlugin({ flux: { DELETE_PENDING_REPLY() { - replyIdx = -1; + currentlyReplyingId = null; }, MESSAGE_END_EDIT() { - editIdx = -1; + currentlyEditingId = null; + }, + CHANNEL_SELECT() { + currentlyReplyingId = null; + currentlyEditingId = null; }, MESSAGE_START_EDIT: onStartEdit, CREATE_PENDING_REPLY: onCreatePendingReply } }); -function calculateIdx(messages: Message[], id: string) { - const idx = messages.findIndex(m => m.id === id); - return idx === -1 - ? idx - : messages.length - idx - 1; -} - -function onStartEdit({ channelId, messageId, _isQuickEdit }: any) { +function onStartEdit({ messageId, _isQuickEdit }: any) { if (_isQuickEdit) return; - - const meId = UserStore.getCurrentUser().id; - - const messages = MessageStore.getMessages(channelId)._array.filter(m => m.author.id === meId); - editIdx = calculateIdx(messages, messageId); + currentlyEditingId = messageId; } function onCreatePendingReply({ message, _isQuickReply }: { message: Message; _isQuickReply: boolean; }) { if (_isQuickReply) return; - replyIdx = calculateIdx(MessageStore.getMessages(message.channel_id)._array, message.id); + currentlyReplyingId = message.id; } const isCtrl = (e: KeyboardEvent) => isMac ? e.metaKey : e.ctrlKey; @@ -123,10 +117,10 @@ function jumpIfOffScreen(channelId: string, messageId: string) { const vh = Math.max(document.documentElement.clientHeight, window.innerHeight); const rect = element.getBoundingClientRect(); - const isOffscreen = rect.bottom < 200 || rect.top - vh >= -200; + const isOffscreen = rect.bottom < 150 || rect.top - vh >= -150; if (isOffscreen) { - Kangaroo.jumpToMessage({ + MessageActions.jumpToMessage({ channelId, messageId, flash: false, @@ -137,44 +131,48 @@ function jumpIfOffScreen(channelId: string, messageId: string) { function getNextMessage(isUp: boolean, isReply: boolean) { let messages: Array = MessageStore.getMessages(SelectedChannelStore.getChannelId())._array; - if (!isReply) { - // we are editing so only include own - const meId = UserStore.getCurrentUser().id; - messages = messages.filter(m => m.author.id === meId); - } - if (Vencord.Plugins.isPluginEnabled("NoBlockedMessages")) { - messages = messages.filter(m => !RelationshipStore.isBlocked(m.author.id)); - } + const meId = UserStore.getCurrentUser().id; + const hasNoBlockedMessages = Vencord.Plugins.isPluginEnabled(NoBlockedMessagesPlugin.name); - const mutate = (i: number) => isUp - ? Math.min(messages.length - 1, i + 1) - : Math.max(-1, i - 1); + messages = messages.filter(m => { + if (m.deleted) return false; + if (!isReply && m.author.id !== meId) return false; // editing only own messages + if (hasNoBlockedMessages && NoBlockedMessagesPlugin.shouldIgnoreMessage(m)) return false; - const findNextNonDeleted = (i: number) => { - do { - i = mutate(i); - } while (i !== -1 && messages[messages.length - i - 1]?.deleted === true); - return i; + return true; + }); + + const findNextNonDeleted = (id: string | null) => { + if (id === null) return messages[messages.length - 1]; + + const idx = messages.findIndex(m => m.id === id); + if (idx === -1) return messages[messages.length - 1]; + + const i = isUp ? idx - 1 : idx + 1; + return messages[i] ?? null; }; - let i: number; - if (isReply) - replyIdx = i = findNextNonDeleted(replyIdx); - else - editIdx = i = findNextNonDeleted(editIdx); - - return i === - 1 ? undefined : messages[messages.length - i - 1]; + if (isReply) { + const msg = findNextNonDeleted(currentlyReplyingId); + currentlyReplyingId = msg?.id ?? null; + return msg; + } else { + const msg = findNextNonDeleted(currentlyEditingId); + currentlyEditingId = msg?.id ?? null; + return msg; + } } -function shouldMention(message) { - const { enabled, userList, shouldPingListed } = Settings.plugins.NoReplyMention; - const shouldPing = !enabled || (shouldPingListed === userList.includes(message.author.id)); - +function shouldMention(message: Message) { switch (settings.store.shouldMention) { - case MentionOptions.NO_REPLY_MENTION_PLUGIN: return shouldPing; - case MentionOptions.DISABLED: return false; - default: return true; + case MentionOptions.NO_REPLY_MENTION_PLUGIN: + if (!Vencord.Plugins.isPluginEnabled(NoReplyMentionPlugin.name)) return true; + return NoReplyMentionPlugin.shouldMention(message, false); + case MentionOptions.DISABLED: + return false; + default: + return true; } } @@ -182,13 +180,16 @@ function shouldMention(message) { function nextReply(isUp: boolean) { const currChannel = ChannelStore.getChannel(SelectedChannelStore.getChannelId()); if (currChannel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, currChannel)) return; + const message = getNextMessage(isUp, true); - if (!message) + if (!message) { return void Dispatcher.dispatch({ type: "DELETE_PENDING_REPLY", channelId: SelectedChannelStore.getChannelId(), }); + } + const channel = ChannelStore.getChannel(message.channel_id); const meId = UserStore.getCurrentUser().id; @@ -200,6 +201,7 @@ function nextReply(isUp: boolean) { showMentionToggle: !channel.isPrivate() && message.author.id !== meId, _isQuickReply: true }); + ComponentDispatch.dispatchToLastSubscribed("TEXTAREA_FOCUS"); jumpIfOffScreen(channel.id, message.id); } @@ -210,11 +212,13 @@ function nextEdit(isUp: boolean) { if (currChannel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, currChannel)) return; const message = getNextMessage(isUp, false); - if (!message) + if (!message) { return Dispatcher.dispatch({ type: "MESSAGE_END_EDIT", channelId: SelectedChannelStore.getChannelId() }); + } + Dispatcher.dispatch({ type: "MESSAGE_START_EDIT", channelId: message.channel_id, @@ -222,5 +226,6 @@ function nextEdit(isUp: boolean) { content: message.content, _isQuickEdit: true }); + jumpIfOffScreen(message.channel_id, message.id); } diff --git a/src/plugins/webScreenShareFixes.web/index.ts b/src/plugins/webScreenShareFixes.web/index.ts index 1d2be2c0..2616dd0c 100644 --- a/src/plugins/webScreenShareFixes.web/index.ts +++ b/src/plugins/webScreenShareFixes.web/index.ts @@ -25,8 +25,8 @@ export default definePlugin({ replace: ";b=AS:800000;level-asymmetry-allowed=1" }, { - match: "useinbandfec=1", - replace: "useinbandfec=1;stereo=1;sprop-stereo=1" + match: /;usedtx=".concat\((\i)\?"0":"1"\)/, + replace: '$&.concat($1?";stereo=1;sprop-stereo=1":"")' } ] }