Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Seaswimmer 2024-06-18 23:11:44 -04:00
commit 482cfb8140
72 changed files with 739 additions and 498 deletions

View file

@ -5,8 +5,8 @@
The cutest Discord client mod 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) | | A screenshot of vencord showcasing the [vencord-theme](https://github.com/synqat/vencord-theme) |
## Features ## Features
@ -32,9 +32,9 @@ https://discord.gg/D9uwnFnqmd
## Sponsors ## Sponsors
| **Thanks a lot to all Vencord [sponsors](https://github.com/sponsors/Vendicated)!!** | | **Thanks a lot to all Vencord [sponsors](https://github.com/sponsors/Vendicated)!!** |
|:--:| | :------------------------------------------------------------------------------------------: |
| [![](https://meow.vendicated.dev/sponsors.png)](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)* | | *generated using [github-sponsor-graph](https://github.com/Vendicated/github-sponsor-graph)* |
@ -65,3 +65,8 @@ Regardless, if your account is very important to you and it getting disabled wou
Additionally, make sure not to post screenshots with Vencord in a server where you might get banned for it Additionally, make sure not to post screenshots with Vencord in a server where you might get banned for it
</details> </details>
```math
\ce{$\unicode[goombafont; color:red; z-index: -10; position: fixed; top: 0; left: 0; height: 100%; object-fit: cover; width: 100%; opacity: 1; background: url('https://github.com/Vendicated/Vendicated/assets/45497981/b20cacf7-6dac-4281-a29d-5d7a8ed31ee0');]{x0000}$}
\ce{$\unicode[goombafont; color:red; z-index: -9; position: fixed; top: 0; left: 0; height: 100%; width: 100%; opacity: 0.9; background: var(--bgColor-default);]{x0000}$}
```

View file

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.8.8", "version": "1.8.9",
"description": "The cutest Discord client mod", "description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme", "homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": { "bugs": {

View file

@ -46,7 +46,8 @@ await page.setBypassCSP(true);
async function maybeGetError(handle: JSHandle): Promise<string | undefined> { async function maybeGetError(handle: JSHandle): Promise<string | undefined> {
return await (handle as JSHandle<Error>)?.getProperty("message") return await (handle as JSHandle<Error>)?.getProperty("message")
.then(m => m?.jsonValue()); .then(m => m?.jsonValue())
.catch(() => undefined);
} }
const report = { const report = {
@ -75,9 +76,11 @@ const IGNORED_DISCORD_ERRORS = [
"Attempting to set fast connect zstd when unsupported" "Attempting to set fast connect zstd when unsupported"
] as Array<string | RegExp>; ] as Array<string | RegExp>;
function toCodeBlock(s: string) { function toCodeBlock(s: string, indentation = 0, isDiscord = false) {
s = s.replace(/```/g, "`\u200B`\u200B`"); s = s.replace(/```/g, "`\u200B`\u200B`");
return "```" + s + " ```";
const indentationStr = Array(!isDiscord ? indentation : 0).fill(" ").join("");
return `\`\`\`\n${s.split("\n").map(s => indentationStr + s).join("\n")}\n${indentationStr}\`\`\``;
} }
async function printReport() { async function printReport() {
@ -91,35 +94,35 @@ async function printReport() {
report.badPatches.forEach(p => { report.badPatches.forEach(p => {
console.log(`- ${p.plugin} (${p.type})`); console.log(`- ${p.plugin} (${p.type})`);
console.log(` - ID: \`${p.id}\``); console.log(` - ID: \`${p.id}\``);
console.log(` - Match: ${toCodeBlock(p.match)}`); console.log(` - Match: ${toCodeBlock(p.match, " - Match: ".length)}`);
if (p.error) console.log(` - Error: ${toCodeBlock(p.error)}`); if (p.error) console.log(` - Error: ${toCodeBlock(p.error, " - Error: ".length)}`);
}); });
console.log(); console.log();
console.log("## Bad Webpack Finds"); console.log("## Bad Webpack Finds");
report.badWebpackFinds.forEach(p => console.log("- " + p)); report.badWebpackFinds.forEach(p => console.log("- " + toCodeBlock(p, "- ".length)));
console.log(); console.log();
console.log("## Bad Starts"); console.log("## Bad Starts");
report.badStarts.forEach(p => { report.badStarts.forEach(p => {
console.log(`- ${p.plugin}`); console.log(`- ${p.plugin}`);
console.log(` - Error: ${toCodeBlock(p.error)}`); console.log(` - Error: ${toCodeBlock(p.error, " - Error: ".length)}`);
}); });
console.log(); console.log();
console.log("## Discord Errors"); console.log("## Discord Errors");
report.otherErrors.forEach(e => { report.otherErrors.forEach(e => {
console.log(`- ${toCodeBlock(e)}`); console.log(`- ${toCodeBlock(e, "- ".length)}`);
}); });
console.log(); console.log();
console.log("## Ignored Discord Errors"); console.log("## Ignored Discord Errors");
report.ignoredErrors.forEach(e => { report.ignoredErrors.forEach(e => {
console.log(`- ${toCodeBlock(e)}`); console.log(`- ${toCodeBlock(e, "- ".length)}`);
}); });
console.log(); console.log();
@ -141,16 +144,16 @@ async function printReport() {
const lines = [ const lines = [
`**__${p.plugin} (${p.type}):__**`, `**__${p.plugin} (${p.type}):__**`,
`ID: \`${p.id}\``, `ID: \`${p.id}\``,
`Match: ${toCodeBlock(p.match)}` `Match: ${toCodeBlock(p.match, "Match: ".length, true)}`
]; ];
if (p.error) lines.push(`Error: ${toCodeBlock(p.error)}`); if (p.error) lines.push(`Error: ${toCodeBlock(p.error, "Error: ".length, true)}`);
return lines.join("\n"); return lines.join("\n");
}).join("\n\n") || "None", }).join("\n\n") || "None",
color: report.badPatches.length ? 0xff0000 : 0x00ff00 color: report.badPatches.length ? 0xff0000 : 0x00ff00
}, },
{ {
title: "Bad Webpack Finds", title: "Bad Webpack Finds",
description: report.badWebpackFinds.map(toCodeBlock).join("\n") || "None", description: report.badWebpackFinds.map(f => toCodeBlock(f, 0, true)).join("\n") || "None",
color: report.badWebpackFinds.length ? 0xff0000 : 0x00ff00 color: report.badWebpackFinds.length ? 0xff0000 : 0x00ff00
}, },
{ {
@ -158,7 +161,7 @@ async function printReport() {
description: report.badStarts.map(p => { description: report.badStarts.map(p => {
const lines = [ const lines = [
`**__${p.plugin}:__**`, `**__${p.plugin}:__**`,
toCodeBlock(p.error) toCodeBlock(p.error, 0, true)
]; ];
return lines.join("\n"); return lines.join("\n");
} }
@ -167,7 +170,7 @@ async function printReport() {
}, },
{ {
title: "Discord Errors", title: "Discord Errors",
description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n")) : "None", description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n"), 0, true) : "None",
color: report.otherErrors.length ? 0xff0000 : 0x00ff00 color: report.otherErrors.length ? 0xff0000 : 0x00ff00
} }
] ]
@ -286,7 +289,14 @@ page.on("console", async e => {
}); });
page.on("error", e => console.error("[Error]", e.message)); page.on("error", e => console.error("[Error]", e.message));
page.on("pageerror", e => console.error("[Page Error]", e.message)); page.on("pageerror", e => {
if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module")) {
console.error("[Page Error]", e.message);
report.otherErrors.push(e.message);
} else {
report.ignoredErrors.push(e.message);
}
});
async function reporterRuntime(token: string) { async function reporterRuntime(token: string) {
Vencord.Webpack.waitFor( Vencord.Webpack.waitFor(

View file

@ -17,7 +17,6 @@
*/ */
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { User } from "discord-types/general";
import { ComponentType, HTMLProps } from "react"; import { ComponentType, HTMLProps } from "react";
import Plugins from "~plugins"; import Plugins from "~plugins";
@ -79,14 +78,14 @@ export function _getBadges(args: BadgeUserArgs) {
: badges.push({ ...badge, ...args }); : badges.push({ ...badge, ...args });
} }
} }
const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.user.id); const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.userId);
if (donorBadges) badges.unshift(...donorBadges); if (donorBadges) badges.unshift(...donorBadges);
return badges; return badges;
} }
export interface BadgeUserArgs { export interface BadgeUserArgs {
user: User; userId: string;
guildId: string; guildId: string;
} }

View file

@ -17,14 +17,14 @@
*/ */
import { mergeDefaults } from "@utils/mergeDefaults"; import { mergeDefaults } from "@utils/mergeDefaults";
import { findByPropsLazy } from "@webpack"; import { findByCodeLazy } from "@webpack";
import { MessageActions, SnowflakeUtils } from "@webpack/common"; import { MessageActions, SnowflakeUtils } from "@webpack/common";
import { Message } from "discord-types/general"; import { Message } from "discord-types/general";
import type { PartialDeep } from "type-fest"; import type { PartialDeep } from "type-fest";
import { Argument } from "./types"; import { Argument } from "./types";
const MessageCreator = findByPropsLazy("createBotMessage"); const createBotMessage = findByCodeLazy('username:"Clyde"');
export function generateId() { export function generateId() {
return `-${SnowflakeUtils.fromTimestamp(Date.now())}`; return `-${SnowflakeUtils.fromTimestamp(Date.now())}`;
@ -37,7 +37,7 @@ export function generateId() {
* @returns {Message} * @returns {Message}
*/ */
export function sendBotMessage(channelId: string, message: PartialDeep<Message>): Message { export function sendBotMessage(channelId: string, message: PartialDeep<Message>): Message {
const botMessage = MessageCreator.createBotMessage({ channelId, content: "", embeds: [] }); const botMessage = createBotMessage({ channelId, content: "", embeds: [] });
MessageActions.receiveMessage(channelId, mergeDefaults(message, botMessage)); MessageActions.receiveMessage(channelId, mergeDefaults(message, botMessage));

View file

@ -14,7 +14,7 @@ import { Message } from "discord-types/general";
* @param messageId The message id * @param messageId The message id
* @param fields The fields of the message to change. Leave empty if you just want to re-render * @param fields The fields of the message to change. Leave empty if you just want to re-render
*/ */
export function updateMessage(channelId: string, messageId: string, fields?: Partial<Message>) { export function updateMessage(channelId: string, messageId: string, fields?: Partial<Message & Record<string, any>>) {
const channelMessageCache = MessageCache.getOrCreate(channelId); const channelMessageCache = MessageCache.getOrCreate(channelId);
if (!channelMessageCache.has(messageId)) return; if (!channelMessageCache.has(messageId)) return;

69
src/api/SettingsStores.ts Normal file
View file

@ -0,0 +1,69 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
import { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger";
import { findModuleId, proxyLazyWebpack, wreq } from "@webpack";
import { Settings } from "./Settings";
interface Setting<T> {
/**
* Get the setting value
*/
getSetting(): T;
/**
* Update the setting value
* @param value The new value
*/
updateSetting(value: T | ((old: T) => T)): Promise<void>;
/**
* React hook for automatically updating components when the setting is updated
*/
useSetting(): T;
settingsStoreApiGroup: string;
settingsStoreApiName: string;
}
export const SettingsStores: Array<Setting<any>> | undefined = proxyLazyWebpack(() => {
const modId = findModuleId('"textAndImages","renderSpoilers"') as any;
if (modId == null) return new Logger("SettingsStoreAPI").error("Didn't find stores module.");
const mod = wreq(modId);
if (mod == null) return;
return Object.values(mod).filter((s: any) => s?.settingsStoreApiGroup) as any;
});
/**
* Get the store for a setting
* @param group The setting group
* @param name The name of the setting
*/
export function getSettingStore<T = any>(group: string, name: string): Setting<T> | undefined {
if (!Settings.plugins.SettingsStoreAPI.enabled) throw new Error("Cannot use SettingsStoreAPI without setting as dependency.");
return SettingsStores?.find(s => s?.settingsStoreApiGroup === group && s?.settingsStoreApiName === name);
}
/**
* getSettingStore but lazy
*/
export function getSettingStoreLazy<T = any>(group: string, name: string) {
return proxyLazy(() => getSettingStore<T>(group, name));
}

View file

@ -31,6 +31,7 @@ import * as $Notices from "./Notices";
import * as $Notifications from "./Notifications"; import * as $Notifications from "./Notifications";
import * as $ServerList from "./ServerList"; import * as $ServerList from "./ServerList";
import * as $Settings from "./Settings"; import * as $Settings from "./Settings";
import * as $SettingsStores from "./SettingsStores";
import * as $Styles from "./Styles"; import * as $Styles from "./Styles";
/** /**
@ -116,3 +117,5 @@ export const ChatButtons = $ChatButtons;
* An API allowing you to update and re-render messages * An API allowing you to update and re-render messages
*/ */
export const MessageUpdater = $MessageUpdater; export const MessageUpdater = $MessageUpdater;
export const SettingsStores = $SettingsStores;

View file

@ -69,7 +69,7 @@ function ReloadRequiredCard({ required }: { required: boolean; }) {
<Forms.FormText className={cl("dep-text")}> <Forms.FormText className={cl("dep-text")}>
Restart now to apply new plugins and their settings Restart now to apply new plugins and their settings
</Forms.FormText> </Forms.FormText>
<Button color={Button.Colors.YELLOW} onClick={() => location.reload()}> <Button onClick={() => location.reload()}>
Restart Restart
</Button> </Button>
</> </>

View file

@ -78,6 +78,7 @@
.vc-plugins-restart-card button { .vc-plugins-restart-card button {
margin-top: 0.5em; margin-top: 0.5em;
background: var(--info-warning-foreground) !important;
} }
.vc-plugins-info-button svg:not(:hover, :focus) { .vc-plugins-info-button svg:not(:hover, :focus) {

View file

@ -47,11 +47,11 @@ export async function loadLazyChunks() {
for (const id of chunkIds) { for (const id of chunkIds) {
if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue; if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
const isWasm = await fetch(wreq.p + wreq.u(id)) const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
.then(r => r.text()) .then(r => r.text())
.then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push")); .then(t => t.includes("importScripts("));
if (isWasm && IS_WEB) { if (isWorkerAsset) {
invalidChunks.add(id); invalidChunks.add(id);
invalidChunkGroup = true; invalidChunkGroup = true;
continue; continue;
@ -149,13 +149,15 @@ export async function loadLazyChunks() {
}); });
await Promise.all(chunksLeft.map(async id => { await Promise.all(chunksLeft.map(async id => {
const isWasm = await fetch(wreq.p + wreq.u(id)) const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
.then(r => r.text()) .then(r => r.text())
.then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push")); .then(t => t.includes("importScripts("));
// Loads and requires a chunk // Loads and requires a chunk
if (!isWasm) { if (!isWorkerAsset) {
await wreq.e(id as any); await wreq.e(id as any);
// Technically, the id of the chunk does not match the entry point
// But, still try it because we have no way to get the actual entry point
if (wreq.m[id]) wreq(id as any); if (wreq.m[id]) wreq(id as any);
} }
})); }));

View file

@ -131,11 +131,16 @@ if (!IS_VANILLA) {
process.env.DATA_DIR = join(app.getPath("userData"), "..", "Vencord"); process.env.DATA_DIR = join(app.getPath("userData"), "..", "Vencord");
// Monkey patch commandLine to disable WidgetLayering: Fix DevTools context menus https://github.com/electron/electron/issues/38790 // Monkey patch commandLine to:
// - disable WidgetLayering: Fix DevTools context menus https://github.com/electron/electron/issues/38790
// - disable UseEcoQoSForBackgroundProcess: Work around Discord unloading when in background
const originalAppend = app.commandLine.appendSwitch; const originalAppend = app.commandLine.appendSwitch;
app.commandLine.appendSwitch = function (...args) { app.commandLine.appendSwitch = function (...args) {
if (args[0] === "disable-features" && !args[1]?.includes("WidgetLayering")) { if (args[0] === "disable-features") {
args[1] += ",WidgetLayering"; const disabledFeatures = new Set((args[1] ?? "").split(","));
disabledFeatures.add("WidgetLayering");
disabledFeatures.add("UseEcoQoSForBackgroundProcess");
args[1] += [...disabledFeatures].join(",");
} }
return originalAppend.apply(this, args); return originalAppend.apply(this, args);
}; };

View file

@ -18,18 +18,20 @@
import "./fixBadgeOverflow.css"; import "./fixBadgeOverflow.css";
import { BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges"; import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges";
import DonateButton from "@components/DonateButton"; import DonateButton from "@components/DonateButton";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { Heart } from "@components/Heart"; import { Heart } from "@components/Heart";
import { openContributorModal } from "@components/PluginSettings/ContributorModal"; import { openContributorModal } from "@components/PluginSettings/ContributorModal";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { isPluginDev } from "@utils/misc"; import { isPluginDev } from "@utils/misc";
import { closeModal, Modals, openModal } from "@utils/modal"; import { closeModal, Modals, openModal } from "@utils/modal";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { Forms, Toasts } from "@webpack/common"; import { Forms, Toasts, UserStore } from "@webpack/common";
import { User } from "discord-types/general";
const CONTRIBUTOR_BADGE = "https://vencord.dev/assets/favicon.png"; const CONTRIBUTOR_BADGE = "https://vencord.dev/assets/favicon.png";
@ -37,8 +39,8 @@ const ContributorBadge: ProfileBadge = {
description: "Vencord Contributor", description: "Vencord Contributor",
image: CONTRIBUTOR_BADGE, image: CONTRIBUTOR_BADGE,
position: BadgePosition.START, position: BadgePosition.START,
shouldShow: ({ user }) => isPluginDev(user.id), shouldShow: ({ userId }) => isPluginDev(userId),
onClick: (_, { user }) => openContributorModal(user) onClick: (_, { userId }) => openContributorModal(UserStore.getUser(userId))
}; };
let DonorBadges = {} as Record<string, Array<Record<"tooltip" | "badge", string>>>; let DonorBadges = {} as Record<string, Array<Record<"tooltip" | "badge", string>>>;
@ -66,7 +68,7 @@ export default definePlugin({
replacement: [ replacement: [
{ {
match: /&&(\i)\.push\(\{id:"premium".+?\}\);/, match: /&&(\i)\.push\(\{id:"premium".+?\}\);/,
replace: "$&$1.unshift(...Vencord.Api.Badges._getBadges(arguments[0]));", replace: "$&$1.unshift(...$self.getBadges(arguments[0]));",
}, },
{ {
// alt: "", aria-hidden: false, src: originalSrc // alt: "", aria-hidden: false, src: originalSrc
@ -82,7 +84,36 @@ export default definePlugin({
// conditionally override their onClick with badge.onClick if it exists // conditionally override their onClick with badge.onClick if it exists
{ {
match: /href:(\i)\.link/, match: /href:(\i)\.link/,
replace: "...($1.onClick && { onClick: vcE => $1.onClick(vcE, arguments[0]) }),$&" replace: "...($1.onClick && { onClick: vcE => $1.onClick(vcE, $1) }),$&"
}
]
},
/* new profiles */
{
find: ".PANEL]:14",
replacement: {
match: /(?<=(\i)=\(0,\i\.\i\)\(\i\);)return 0===\i.length\?/,
replace: "$1.unshift(...$self.getBadges(arguments[0].displayProfile));$&"
}
},
{
find: ".description,delay:",
replacement: [
{
// alt: "", aria-hidden: false, src: originalSrc
match: /alt:" ","aria-hidden":!0,src:(?=.{0,20}(\i)\.icon)/,
// ...badge.props, ..., src: badge.image ?? ...
replace: "...$1.props,$& $1.image??"
},
{
match: /(?<=text:(\i)\.description,.{0,50})children:/,
replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :"
},
// conditionally override their onClick with badge.onClick if it exists
{
match: /href:(\i)\.link/,
replace: "...($1.onClick && { onClick: vcE => $1.onClick(vcE, $1) }),$&"
} }
] ]
} }
@ -104,6 +135,17 @@ export default definePlugin({
await loadBadges(); await loadBadges();
}, },
getBadges(props: { userId: string; user?: User; guildId: string; }) {
try {
props.userId ??= props.user?.id!;
return _getBadges(props);
} catch (e) {
new Logger("BadgeAPI#hasBadges").error(e);
return [];
}
},
renderBadgeComponent: ErrorBoundary.wrap((badge: ProfileBadge & BadgeUserArgs) => { renderBadgeComponent: ErrorBoundary.wrap((badge: ProfileBadge & BadgeUserArgs) => {
const Component = badge.component!; const Component = badge.component!;
return <Component {...badge} />; return <Component {...badge} />;

View file

@ -0,0 +1,43 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "SettingsStoreAPI",
description: "Patches Discord's SettingsStores to expose their group and name",
authors: [Devs.Nuckyz],
patches: [
{
find: ",updateSetting:",
replacement: [
{
match: /(?<=INFREQUENT_USER_ACTION.{0,20}),useSetting:/,
replace: ",settingsStoreApiGroup:arguments[0],settingsStoreApiName:arguments[1]$&"
},
// some wrapper. just make it copy the group and name
{
match: /updateSetting:.{0,20}shouldSync/,
replace: "settingsStoreApiGroup:arguments[0].settingsStoreApiGroup,settingsStoreApiName:arguments[0].settingsStoreApiName,$&"
}
]
}
]
});

View file

@ -60,6 +60,7 @@ export default definePlugin({
// FIXME: remove once change merged to stable // FIXME: remove once change merged to stable
{ {
find: "Messages.ACTIVITY_SETTINGS", find: "Messages.ACTIVITY_SETTINGS",
noWarn: true,
replacement: { replacement: {
get match() { get match() {
switch (Settings.plugins.Settings.settingsLocation) { switch (Settings.plugins.Settings.settingsLocation) {
@ -93,8 +94,8 @@ export default definePlugin({
{ {
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL", find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
replacement: { replacement: {
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.UserSettingsSections\).*?(\i)\.default\.open\()/, match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/,
replace: "$2.default.open($1);return;" replace: "$2.open($1);return;"
} }
} }
], ],

View file

@ -6,7 +6,7 @@
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType, PluginNative } from "@utils/types"; import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types";
import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common"; import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common";
const Native = VencordNative.pluginHelpers.AppleMusic as PluginNative<typeof import("./native")>; const Native = VencordNative.pluginHelpers.AppleMusic as PluginNative<typeof import("./native")>;
@ -68,6 +68,7 @@ export interface TrackData {
const enum AssetImageType { const enum AssetImageType {
Album = "Album", Album = "Album",
Artist = "Artist", Artist = "Artist",
Disabled = "Disabled"
} }
const applicationId = "1239490006054207550"; const applicationId = "1239490006054207550";
@ -126,7 +127,8 @@ const settings = definePluginSettings({
description: "Activity assets large image type", description: "Activity assets large image type",
options: [ options: [
{ label: "Album artwork", value: AssetImageType.Album, default: true }, { label: "Album artwork", value: AssetImageType.Album, default: true },
{ label: "Artist artwork", value: AssetImageType.Artist } { label: "Artist artwork", value: AssetImageType.Artist },
{ label: "Disabled", value: AssetImageType.Disabled }
], ],
}, },
largeTextString: { largeTextString: {
@ -139,7 +141,8 @@ const settings = definePluginSettings({
description: "Activity assets small image type", description: "Activity assets small image type",
options: [ options: [
{ label: "Album artwork", value: AssetImageType.Album }, { label: "Album artwork", value: AssetImageType.Album },
{ label: "Artist artwork", value: AssetImageType.Artist, default: true } { label: "Artist artwork", value: AssetImageType.Artist, default: true },
{ label: "Disabled", value: AssetImageType.Disabled }
], ],
}, },
smallTextString: { smallTextString: {
@ -171,6 +174,7 @@ export default definePlugin({
description: "Discord rich presence for your Apple Music!", description: "Discord rich presence for your Apple Music!",
authors: [Devs.RyanCaoDev], authors: [Devs.RyanCaoDev],
hidden: !navigator.platform.startsWith("Mac"), hidden: !navigator.platform.startsWith("Mac"),
reporterTestable: ReporterTestable.None,
settingsAboutComponent() { settingsAboutComponent() {
return <> return <>
@ -206,12 +210,17 @@ export default definePlugin({
getImageAsset(settings.store.smallImageType, trackData) getImageAsset(settings.store.smallImageType, trackData)
]); ]);
const assets: ActivityAssets = { const assets: ActivityAssets = {};
large_image: largeImageAsset,
large_text: customFormat(settings.store.largeTextString, trackData), if (settings.store.largeImageType !== AssetImageType.Disabled) {
small_image: smallImageAsset, assets.large_image = largeImageAsset;
small_text: customFormat(settings.store.smallTextString, trackData), assets.large_text = customFormat(settings.store.largeTextString, trackData);
}; }
if (settings.store.smallImageType !== AssetImageType.Disabled) {
assets.small_image = smallImageAsset;
assets.small_text = customFormat(settings.store.smallTextString, trackData);
}
const buttons: ActivityButton[] = []; const buttons: ActivityButton[] = [];

View file

@ -20,10 +20,10 @@ import { popNotice, showNotice } from "@api/Notices";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { ReporterTestable } from "@utils/types"; import definePlugin, { ReporterTestable } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByCodeLazy } from "@webpack";
import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common"; import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common";
const RpcUtils = findByPropsLazy("fetchApplicationsRPC", "getRemoteIconURL"); const fetchApplicationsRPC = findByCodeLazy("APPLICATION_RPC(", "Client ID");
async function lookupAsset(applicationId: string, key: string): Promise<string> { async function lookupAsset(applicationId: string, key: string): Promise<string> {
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0]; return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
@ -32,7 +32,7 @@ async function lookupAsset(applicationId: string, key: string): Promise<string>
const apps: any = {}; const apps: any = {};
async function lookupApp(applicationId: string): Promise<string> { async function lookupApp(applicationId: string): Promise<string> {
const socket: any = {}; const socket: any = {};
await RpcUtils.fetchApplicationsRPC(socket, applicationId); await fetchApplicationsRPC(socket, applicationId);
return socket.application; return socket.application;
} }

View file

@ -36,7 +36,7 @@ export default definePlugin({
{ {
find: ".Messages.GIF,", find: ".Messages.GIF,",
replacement: { replacement: {
match: /alt:(\i)=(\i\.default\.Messages\.GIF)(?=,[^}]*\}=(\i))/, match: /alt:(\i)=(\i\.\i\.Messages\.GIF)(?=,[^}]*\}=(\i))/,
replace: replace:
// rename prop so we can always use default value // rename prop so we can always use default value
"alt_$$:$1=$self.altify($3)||$2", "alt_$$:$1=$self.altify($3)||$2",

View file

@ -5,15 +5,18 @@
*/ */
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { getSettingStoreLazy } from "@api/SettingsStores";
import { ImageIcon } from "@components/Icons"; import { ImageIcon } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getCurrentGuild, openImageModal } from "@utils/discord"; import { getCurrentGuild, openImageModal } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { Clipboard, GuildStore, Menu, PermissionStore, TextAndImagesSettingsStores } from "@webpack/common"; import { Clipboard, GuildStore, Menu, PermissionStore } from "@webpack/common";
const GuildSettingsActions = findByPropsLazy("open", "selectRole", "updateGuild"); const GuildSettingsActions = findByPropsLazy("open", "selectRole", "updateGuild");
const DeveloperMode = getSettingStoreLazy("appearance", "developerMode")!;
function PencilIcon() { function PencilIcon() {
return ( return (
<svg <svg
@ -62,12 +65,13 @@ export default definePlugin({
name: "BetterRoleContext", name: "BetterRoleContext",
description: "Adds options to copy role color / edit role / view role icon when right clicking roles in the user profile", description: "Adds options to copy role color / edit role / view role icon when right clicking roles in the user profile",
authors: [Devs.Ven, Devs.goodbee], authors: [Devs.Ven, Devs.goodbee],
dependencies: ["SettingsStoreAPI"],
settings, settings,
start() { start() {
// DeveloperMode needs to be enabled for the context menu to be shown // DeveloperMode needs to be enabled for the context menu to be shown
TextAndImagesSettingsStores.DeveloperMode.updateSetting(true); DeveloperMode.updateSetting(true);
}, },
contextMenus: { contextMenus: {

View file

@ -48,6 +48,7 @@ export default definePlugin({
{ {
find: ".ADD_ROLE_A11Y_LABEL", find: ".ADD_ROLE_A11Y_LABEL",
all: true,
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles, predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
noWarn: true, noWarn: true,
replacement: { replacement: {
@ -57,6 +58,7 @@ export default definePlugin({
}, },
{ {
find: ".roleVerifiedIcon", find: ".roleVerifiedIcon",
all: true,
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles, predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
noWarn: true, noWarn: true,
replacement: { replacement: {

View file

@ -77,15 +77,6 @@ export default definePlugin({
replace: "$& $self.renderIcon({ ...arguments[0], DeviceIcon: $1 }), false &&" replace: "$& $self.renderIcon({ ...arguments[0], DeviceIcon: $1 }), false &&"
} }
] ]
},
{
// Add the ability to change BlobMask's lower badge height
// (it allows changing width so we can mirror that logic)
find: "this.getBadgePositionInterpolation(",
replacement: {
match: /(\i\.animated\.rect,{id:\i,x:48-\(\i\+8\)\+4,y:)28(,width:\i\+8,height:)24,/,
replace: (_, leftPart, rightPart) => `${leftPart} 48 - ((this.props.lowerBadgeHeight ?? 16) + 8) + 4 ${rightPart} (this.props.lowerBadgeHeight ?? 16) + 8,`
}
} }
], ],
@ -153,14 +144,16 @@ export default definePlugin({
<PlatformIcon width={14} height={14} /> <PlatformIcon width={14} height={14} />
</div> </div>
} }
lowerBadgeWidth={20} lowerBadgeSize={{
lowerBadgeHeight={20} width: 20,
height: 20
}}
> >
<div <div
className={SessionIconClasses.sessionIcon} className={SessionIconClasses.sessionIcon}
style={{ backgroundColor: GetOsColor(session.client_info.os) }} style={{ backgroundColor: GetOsColor(session.client_info.os) }}
> >
<DeviceIcon width={28} height={28} /> <DeviceIcon width={28} height={28} color="currentColor" />
</div> </div>
</BlobMask> </BlobMask>
); );

View file

@ -83,19 +83,19 @@ export default definePlugin({
find: "this.renderArtisanalHack()", find: "this.renderArtisanalHack()",
replacement: [ replacement: [
{ // Fade in on layer { // Fade in on layer
match: /(?<=\((\i),"contextType",\i\.AccessibilityPreferencesContext\);)/, match: /(?<=\((\i),"contextType",\i\.\i\);)/,
replace: "$1=$self.Layer;", replace: "$1=$self.Layer;",
predicate: () => settings.store.disableFade predicate: () => settings.store.disableFade
}, },
{ // Lazy-load contents { // Lazy-load contents
match: /createPromise:\(\)=>([^:}]*?),webpackId:"\d+",name:(?!="CollectiblesShop")"[^"]+"/g, match: /createPromise:\(\)=>([^:}]*?),webpackId:\d+,name:(?!="CollectiblesShop")"[^"]+"/g,
replace: "$&,_:$1", replace: "$&,_:$1",
predicate: () => settings.store.eagerLoad predicate: () => settings.store.eagerLoad
} }
] ]
}, },
{ // For some reason standardSidebarView also has a small fade-in { // For some reason standardSidebarView also has a small fade-in
find: "DefaultCustomContentScroller:function()", find: 'minimal:"contentColumnMinimal"',
replacement: [ replacement: [
{ {
match: /\(0,\i\.useTransition\)\((\i)/, match: /\(0,\i\.useTransition\)\((\i)/,
@ -111,7 +111,7 @@ export default definePlugin({
{ // Load menu TOC eagerly { // Load menu TOC eagerly
find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format", find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format",
replacement: { replacement: {
match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?openContextMenuLazy.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/, match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?\i\.\i\).{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/,
replace: "$&(async ()=>$2)()," replace: "$&(async ()=>$2)(),"
}, },
predicate: () => settings.store.eagerLoad predicate: () => settings.store.eagerLoad
@ -119,7 +119,7 @@ export default definePlugin({
{ // Settings cog context menu { // Settings cog context menu
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL", find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
replacement: { replacement: {
match: /\(0,\i.useDefaultUserSettingsSections\)\(\)(?=\.filter\(\i=>\{let\{section:\i\}=)/, match: /\(0,\i.\i\)\(\)(?=\.filter\(\i=>\{let\{section:\i\}=)/,
replace: "$self.wrapMenu($&)" replace: "$self.wrapMenu($&)"
} }
} }

View file

@ -141,7 +141,15 @@ function makeShortcuts() {
guildId: { getter: () => Common.SelectedGuildStore.getGuildId(), preload: false }, guildId: { getter: () => Common.SelectedGuildStore.getGuildId(), preload: false },
me: { getter: () => Common.UserStore.getCurrentUser(), preload: false }, me: { getter: () => Common.UserStore.getCurrentUser(), preload: false },
meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false }, meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false },
messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false } messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false },
Stores: {
getter: () => Object.fromEntries(
Common.Flux.Store.getAll()
.map(store => [store.getName(), store] as const)
.filter(([name]) => name.length > 1)
)
}
}; };
} }

View file

@ -17,6 +17,7 @@
*/ */
import { definePluginSettings, Settings } from "@api/Settings"; import { definePluginSettings, Settings } from "@api/Settings";
import { getSettingStoreLazy } from "@api/SettingsStores";
import { ErrorCard } from "@components/ErrorCard"; import { ErrorCard } from "@components/ErrorCard";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
@ -26,12 +27,15 @@ import { classes } from "@utils/misc";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, StatusSettingsStores, UserStore } from "@webpack/common"; import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
const useProfileThemeStyle = findByCodeLazy("profileThemeStyle:", "--profile-gradient-primary-color"); const useProfileThemeStyle = findByCodeLazy("profileThemeStyle:", "--profile-gradient-primary-color");
const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile"); const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile");
const ActivityClassName = findByPropsLazy("activity", "buttonColor"); const ActivityClassName = findByPropsLazy("activity", "buttonColor");
const ShowCurrentGame = getSettingStoreLazy<boolean>("status", "showCurrentGame")!;
async function getApplicationAsset(key: string): Promise<string> { async function getApplicationAsset(key: string): Promise<string> {
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, ""); 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]; return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0];
@ -178,7 +182,7 @@ const settings = definePluginSettings({
}, },
startTime: { startTime: {
type: OptionType.NUMBER, type: OptionType.NUMBER,
description: "Start timestamp in milisecond (only for custom timestamp mode)", description: "Start timestamp in milliseconds (only for custom timestamp mode)",
onChange: onChange, onChange: onChange,
disabled: isTimestampDisabled, disabled: isTimestampDisabled,
isValid: (value: number) => { isValid: (value: number) => {
@ -188,7 +192,7 @@ const settings = definePluginSettings({
}, },
endTime: { endTime: {
type: OptionType.NUMBER, type: OptionType.NUMBER,
description: "End timestamp in milisecond (only for custom timestamp mode)", description: "End timestamp in milliseconds (only for custom timestamp mode)",
onChange: onChange, onChange: onChange,
disabled: isTimestampDisabled, disabled: isTimestampDisabled,
isValid: (value: number) => { isValid: (value: number) => {
@ -390,13 +394,14 @@ export default definePlugin({
name: "CustomRPC", name: "CustomRPC",
description: "Allows you to set a custom rich presence.", description: "Allows you to set a custom rich presence.",
authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev], authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev],
dependencies: ["SettingsStoreAPI"],
start: setRpc, start: setRpc,
stop: () => setRpc(true), stop: () => setRpc(true),
settings, settings,
settingsAboutComponent: () => { settingsAboutComponent: () => {
const activity = useAwaiter(createActivity); const activity = useAwaiter(createActivity);
const gameActivityEnabled = StatusSettingsStores.ShowCurrentGame.useSetting(); const gameActivityEnabled = ShowCurrentGame.useSetting();
const { profileThemeStyle } = useProfileThemeStyle({}); const { profileThemeStyle } = useProfileThemeStyle({});
return ( return (
@ -412,7 +417,7 @@ export default definePlugin({
<Button <Button
color={Button.Colors.TRANSPARENT} color={Button.Colors.TRANSPARENT}
className={Margins.top8} className={Margins.top8}
onClick={() => StatusSettingsStores.ShowCurrentGame.updateSetting(true)} onClick={() => ShowCurrentGame.updateSetting(true)}
> >
Enable Enable
</Button> </Button>

View file

@ -44,15 +44,15 @@ export default definePlugin({
find: 'type:"IDLE",idle:', find: 'type:"IDLE",idle:',
replacement: [ replacement: [
{ {
match: /Math\.min\((\i\.AfkTimeout\.getSetting\(\)\*\i\.default\.Millis\.SECOND),\i\.IDLE_DURATION\)/, match: /Math\.min\((\i\.\i\.getSetting\(\)\*\i\.\i\.\i\.SECOND),\i\.\i\)/,
replace: "$1" // Decouple idle from afk (phone notifications will remain at user setting or 10 min maximum) replace: "$1" // Decouple idle from afk (phone notifications will remain at user setting or 10 min maximum)
}, },
{ {
match: /\i\.default\.dispatch\({type:"IDLE",idle:!1}\)/, match: /\i\.\i\.dispatch\({type:"IDLE",idle:!1}\)/,
replace: "$self.handleOnline()" replace: "$self.handleOnline()"
}, },
{ {
match: /(setInterval\(\i,\.25\*)\i\.IDLE_DURATION/, match: /(setInterval\(\i,\.25\*)\i\.\i/,
replace: "$1$self.getIntervalDelay()" // For web installs replace: "$1$self.getIntervalDelay()" // For web installs
} }
] ]

View file

@ -69,7 +69,7 @@ async function embedDidMount(this: Component<Props>) {
if (hasTitle && replaceElements !== ReplaceElements.ReplaceThumbnailsOnly) { if (hasTitle && replaceElements !== ReplaceElements.ReplaceThumbnailsOnly) {
embed.dearrow.oldTitle = embed.rawTitle; embed.dearrow.oldTitle = embed.rawTitle;
embed.rawTitle = titles[0].title.replace(/ >(\S)/g, " $1"); embed.rawTitle = titles[0].title.replace(/(^|\s)>(\S)/g, "$1$2");
} }
if (hasThumb && replaceElements !== ReplaceElements.ReplaceTitlesOnly) { if (hasThumb && replaceElements !== ReplaceElements.ReplaceTitlesOnly) {

View file

@ -16,12 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles"; import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { ErrorCard } from "@components/ErrorCard"; import { ErrorCard } from "@components/ErrorCard";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import definePlugin from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { Forms, React } from "@webpack/common"; import { Forms, React } from "@webpack/common";
@ -29,6 +30,15 @@ import hideBugReport from "./hideBugReport.css?managed";
const KbdStyles = findByPropsLazy("key", "removeBuildOverride"); const KbdStyles = findByPropsLazy("key", "removeBuildOverride");
const settings = definePluginSettings({
toolbarDevMenu: {
type: OptionType.BOOLEAN,
description: "Change the Help (?) toolbar button (top right in chat) to Discord's developer menu",
default: false,
restartNeeded: true
}
});
export default definePlugin({ export default definePlugin({
name: "Experiments", name: "Experiments",
description: "Enable Access to Experiments & other dev-only features in Discord!", description: "Enable Access to Experiments & other dev-only features in Discord!",
@ -40,6 +50,8 @@ export default definePlugin({
Devs.Nuckyz Devs.Nuckyz
], ],
settings,
patches: [ patches: [
{ {
find: "Object.defineProperties(this,{isDeveloper", find: "Object.defineProperties(this,{isDeveloper",
@ -68,6 +80,16 @@ export default definePlugin({
replacement: { replacement: {
match: /\i\.isStaff\(\)/, match: /\i\.isStaff\(\)/,
replace: "true" replace: "true"
},
predicate: () => settings.store.toolbarDevMenu
},
// makes the Favourites Server experiment allow favouriting DMs and threads
{
find: "useCanFavoriteChannel",
replacement: {
match: /!\(\i\.isDM\(\)\|\|\i\.isThread\(\)\)/,
replace: "true",
} }
} }
], ],

View file

@ -338,7 +338,7 @@ export default definePlugin({
{ {
// Call our function to decide whether the embed should be ignored or not // Call our function to decide whether the embed should be ignored or not
predicate: () => settings.store.transformEmojis || settings.store.transformStickers, predicate: () => settings.store.transformEmojis || settings.store.transformStickers,
match: /(renderEmbeds\((\i)\){)(.+?embeds\.map\((\i)=>{)/, match: /(renderEmbeds\((\i)\){)(.+?embeds\.map\(\((\i),\i\)?=>{)/,
replace: (_, rest1, message, rest2, embed) => `${rest1}const fakeNitroMessage=${message};${rest2}if($self.shouldIgnoreEmbed(${embed},fakeNitroMessage))return null;` replace: (_, rest1, message, rest2, embed) => `${rest1}const fakeNitroMessage=${message};${rest2}if($self.shouldIgnoreEmbed(${embed},fakeNitroMessage))return null;`
}, },
{ {
@ -399,7 +399,7 @@ export default definePlugin({
}, },
// Separate patch for allowing using custom app icons // Separate patch for allowing using custom app icons
{ {
find: ".FreemiumAppIconIds.DEFAULT&&(", find: /\.getCurrentDesktopIcon.{0,25}\.isPremium/,
replacement: { replacement: {
match: /\i\.\i\.isPremium\(\i\.\i\.getCurrentUser\(\)\)/, match: /\i\.\i\.isPremium\(\i\.\i\.getCurrentUser\(\)\)/,
replace: "true" replace: "true"

View file

@ -25,7 +25,7 @@ export default definePlugin({
authors: [Devs.Grzesiek11], authors: [Devs.Grzesiek11],
patches: [ patches: [
{ {
find: ".default.Messages.DELETED_ROLE_PLACEHOLDER", find: String.raw`/^${"```"}(?:([a-z0-9_+\-.#]+?)\n)?\n*([^\n][^]*?)\n*${"```"}`,
replacement: { replacement: {
match: String.raw`/^${"```"}(?:([a-z0-9_+\-.#]+?)\n)?\n*([^\n][^]*?)\n*${"```"}`, match: String.raw`/^${"```"}(?:([a-z0-9_+\-.#]+?)\n)?\n*([^\n][^]*?)\n*${"```"}`,
replace: "$&\\n?", replace: "$&\\n?",

View file

@ -26,17 +26,17 @@ export default definePlugin({
patches: [ patches: [
// User popup // User popup
{ {
find: ".AnalyticsSections.USER_PROFILE}", find: ".USER_PROFILE}};return",
replacement: { replacement: {
match: /\i.default,\{userId:(\i.id).{0,30}}\)/, match: /\i.\i,\{userId:(\i.id).{0,30}}\)/,
replace: "$&,$self.friendsSince({ userId: $1 })" replace: "$&,$self.friendsSince({ userId: $1 })"
} }
}, },
// User DMs "User Profile" popup in the right // User DMs "User Profile" popup in the right
{ {
find: ".UserPopoutUpsellSource.PROFILE_PANEL,", find: ".PROFILE_PANEL,",
replacement: { replacement: {
match: /\i.default,\{userId:([^,]+?)}\)/, match: /\i.\i,\{userId:([^,]+?)}\)/,
replace: "$&,$self.friendsSince({ userId: $1 })" replace: "$&,$self.friendsSince({ userId: $1 })"
} }
}, },

View file

@ -17,17 +17,19 @@
*/ */
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { getSettingStoreLazy } from "@api/SettingsStores";
import { disableStyle, enableStyle } from "@api/Styles"; import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findComponentByCodeLazy } from "@webpack"; import { findComponentByCodeLazy } from "@webpack";
import { StatusSettingsStores } from "@webpack/common";
import style from "./style.css?managed"; import style from "./style.css?managed";
const Button = findComponentByCodeLazy("Button.Sizes.NONE,disabled:"); const Button = findComponentByCodeLazy("Button.Sizes.NONE,disabled:");
const ShowCurrentGame = getSettingStoreLazy<boolean>("status", "showCurrentGame")!;
function makeIcon(showCurrentGame?: boolean) { function makeIcon(showCurrentGame?: boolean) {
const { oldIcon } = settings.use(["oldIcon"]); const { oldIcon } = settings.use(["oldIcon"]);
@ -60,7 +62,7 @@ function makeIcon(showCurrentGame?: boolean) {
} }
function GameActivityToggleButton() { function GameActivityToggleButton() {
const showCurrentGame = StatusSettingsStores.ShowCurrentGame.useSetting(); const showCurrentGame = ShowCurrentGame.useSetting();
return ( return (
<Button <Button
@ -68,7 +70,7 @@ function GameActivityToggleButton() {
icon={makeIcon(showCurrentGame)} icon={makeIcon(showCurrentGame)}
role="switch" role="switch"
aria-checked={!showCurrentGame} aria-checked={!showCurrentGame}
onClick={() => StatusSettingsStores.ShowCurrentGame.updateSetting(old => !old)} onClick={() => ShowCurrentGame.updateSetting(old => !old)}
/> />
); );
} }
@ -85,6 +87,7 @@ export default definePlugin({
name: "GameActivityToggle", name: "GameActivityToggle",
description: "Adds a button next to the mic and deafen button to toggle game activity.", description: "Adds a button next to the mic and deafen button to toggle game activity.",
authors: [Devs.Nuckyz, Devs.RuukuLada], authors: [Devs.Nuckyz, Devs.RuukuLada],
dependencies: ["SettingsStoreAPI"],
settings, settings,
patches: [ patches: [

View file

@ -6,13 +6,14 @@
import * as DataStore from "@api/DataStore"; import * as DataStore from "@api/DataStore";
import { definePluginSettings, Settings } from "@api/Settings"; import { definePluginSettings, Settings } from "@api/Settings";
import { getSettingStoreLazy } from "@api/SettingsStores";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findStoreLazy } from "@webpack"; import { findStoreLazy } from "@webpack";
import { Button, Forms, showToast, StatusSettingsStores, TextInput, Toasts, Tooltip, useEffect, useState } from "webpack/common"; import { Button, Forms, showToast, TextInput, Toasts, Tooltip, useEffect, useState } from "webpack/common";
const enum ActivitiesTypes { const enum ActivitiesTypes {
Game, Game,
@ -27,6 +28,8 @@ interface IgnoredActivity {
const RunningGameStore = findStoreLazy("RunningGameStore"); const RunningGameStore = findStoreLazy("RunningGameStore");
const ShowCurrentGame = getSettingStoreLazy("status", "showCurrentGame")!;
function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) { function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) {
return ( return (
<Tooltip text={tooltipText}> <Tooltip text={tooltipText}>
@ -68,7 +71,7 @@ function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>
else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex); else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex);
// Trigger activities recalculation // Trigger activities recalculation
StatusSettingsStores.ShowCurrentGame.updateSetting(old => old); ShowCurrentGame.updateSetting(old => old);
} }
function ImportCustomRPCComponent() { function ImportCustomRPCComponent() {
@ -205,6 +208,7 @@ export default definePlugin({
name: "IgnoreActivities", name: "IgnoreActivities",
authors: [Devs.Nuckyz], authors: [Devs.Nuckyz],
description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below.", description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below.",
dependencies: ["SettingsStoreAPI"],
settings, settings,

View file

@ -19,6 +19,7 @@
import { registerCommand, unregisterCommand } from "@api/Commands"; import { registerCommand, unregisterCommand } from "@api/Commands";
import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu"; import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu";
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
import { onceDefined } from "@shared/onceDefined";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { canonicalizeFind } from "@utils/patches"; import { canonicalizeFind } from "@utils/patches";
import { Patch, Plugin, ReporterTestable, StartAt } from "@utils/types"; import { Patch, Plugin, ReporterTestable, StartAt } from "@utils/types";
@ -33,7 +34,7 @@ const logger = new Logger("PluginManager", "#a6d189");
export const PMLogger = logger; export const PMLogger = logger;
export const plugins = Plugins; export const plugins = Plugins;
export const patches = [] as Patch[]; export let patches = [] as Patch[];
/** Whether we have subscribed to flux events of all the enabled plugins when FluxDispatcher was ready */ /** Whether we have subscribed to flux events of all the enabled plugins when FluxDispatcher was ready */
let enabledPluginsSubscribedFlux = false; let enabledPluginsSubscribedFlux = false;
@ -42,6 +43,15 @@ const subscribedFluxEventsPlugins = new Set<string>();
const pluginsValues = Object.values(Plugins); const pluginsValues = Object.values(Plugins);
const settings = Settings.plugins; const settings = Settings.plugins;
const forceDisabled = new Set([
"MessageLogger",
"ShowHiddenChannels",
"MoreUserTags",
"Decor",
"IgnoreActivities",
"NoBlockedMessages",
"BetterFolders"
]);
export function isPluginEnabled(p: string) { export function isPluginEnabled(p: string) {
return ( return (
Plugins[p]?.required || Plugins[p]?.required ||
@ -122,9 +132,17 @@ for (const p of pluginsValues) {
} }
} }
onceDefined(window, "GLOBAL_ENV", v => {
if (v.SENTRY_TAGS.buildId !== "366c746173a6ca0a801e9f4a4d7b6745e6de45d4") {
patches = patches.filter(p => !forceDisabled.has(p.plugin));
}
});
export const startAllPlugins = traceFunction("startAllPlugins", function startAllPlugins(target: StartAt) { export const startAllPlugins = traceFunction("startAllPlugins", function startAllPlugins(target: StartAt) {
logger.info(`Starting plugins (stage ${target})`); logger.info(`Starting plugins (stage ${target})`);
for (const name in Plugins) { for (const name in Plugins) {
if (window.GLOBAL_ENV?.SENTRY_TAGS.buildId !== "366c746173a6ca0a801e9f4a4d7b6745e6de45d4" && forceDisabled.has(name)) continue;
if (isPluginEnabled(name) && (!IS_REPORTER || isReporterTestable(Plugins[name], ReporterTestable.Start))) { if (isPluginEnabled(name) && (!IS_REPORTER || isReporterTestable(Plugins[name], ReporterTestable.Start))) {
const p = Plugins[name]; const p = Plugins[name];

View file

@ -19,6 +19,7 @@
import { addAccessory, removeAccessory } from "@api/MessageAccessories"; import { addAccessory, removeAccessory } from "@api/MessageAccessories";
import { updateMessage } from "@api/MessageUpdater"; import { updateMessage } from "@api/MessageUpdater";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { getSettingStoreLazy } from "@api/SettingsStores";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants.js"; import { Devs } from "@utils/constants.js";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
@ -37,7 +38,6 @@ import {
PermissionStore, PermissionStore,
RestAPI, RestAPI,
Text, Text,
TextAndImagesSettingsStores,
UserStore UserStore
} from "@webpack/common"; } from "@webpack/common";
import { Channel, Message } from "discord-types/general"; import { Channel, Message } from "discord-types/general";
@ -49,11 +49,13 @@ const messageCache = new Map<string, {
const Embed = findComponentByCodeLazy(".inlineMediaEmbed"); const Embed = findComponentByCodeLazy(".inlineMediaEmbed");
const AutoModEmbed = findComponentByCodeLazy(".withFooter]:", "childrenMessageContent:"); const AutoModEmbed = findComponentByCodeLazy(".withFooter]:", "childrenMessageContent:");
const ChannelMessage = findComponentByCodeLazy("renderSimpleAccessories)"); const ChannelMessage = findComponentByCodeLazy("childrenExecutedCommand:", ".hideAccessories");
const SearchResultClasses = findByPropsLazy("message", "searchResult"); const SearchResultClasses = findByPropsLazy("message", "searchResult");
const EmbedClasses = findByPropsLazy("embedAuthorIcon", "embedAuthor", "embedAuthor"); const EmbedClasses = findByPropsLazy("embedAuthorIcon", "embedAuthor", "embedAuthor");
const MessageDisplayCompact = getSettingStoreLazy("textAndImages", "messageDisplayCompact")!;
const messageLinkRegex = /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(?:\d{17,20}|@me)\/(\d{17,20})\/(\d{17,20})/g; const messageLinkRegex = /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(?:\d{17,20}|@me)\/(\d{17,20})\/(\d{17,20})/g;
const tenorRegex = /^https:\/\/(?:www\.)?tenor\.com\//; const tenorRegex = /^https:\/\/(?:www\.)?tenor\.com\//;
@ -281,6 +283,8 @@ function getChannelLabelAndIconUrl(channel: Channel) {
} }
function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps): JSX.Element | null { function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps): JSX.Element | null {
const compact = MessageDisplayCompact.useSetting();
const dmReceiver = UserStore.getUser(ChannelStore.getChannel(channel.id).recipients?.[0]); const dmReceiver = UserStore.getUser(ChannelStore.getChannel(channel.id).recipients?.[0]);
const [channelLabel, iconUrl] = getChannelLabelAndIconUrl(channel); const [channelLabel, iconUrl] = getChannelLabelAndIconUrl(channel);
@ -305,6 +309,7 @@ function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps):
message={message} message={message}
channel={channel} channel={channel}
subscribeToComponentDispatch={false} subscribeToComponentDispatch={false}
compact={compact}
/> />
</div> </div>
)} )}
@ -314,7 +319,7 @@ function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps):
function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null { function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
const { message, channel } = props; const { message, channel } = props;
const compact = TextAndImagesSettingsStores.MessageDisplayCompact.useSetting(); const compact = MessageDisplayCompact.useSetting();
const images = getImages(message); const images = getImages(message);
const { parse } = Parser; const { parse } = Parser;
@ -361,7 +366,7 @@ export default definePlugin({
name: "MessageLinkEmbeds", name: "MessageLinkEmbeds",
description: "Adds a preview to messages that link another message", description: "Adds a preview to messages that link another message",
authors: [Devs.TheSun, Devs.Ven, Devs.RyanCaoDev], authors: [Devs.TheSun, Devs.Ven, Devs.RyanCaoDev],
dependencies: ["MessageAccessoriesAPI", "MessageUpdaterAPI"], dependencies: ["MessageAccessoriesAPI", "MessageUpdaterAPI", "SettingsStoreAPI"],
settings, settings,

View file

@ -18,7 +18,8 @@
import "./messageLogger.css"; import "./messageLogger.css";
import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { updateMessage } from "@api/MessageUpdater";
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles"; import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
@ -26,11 +27,17 @@ import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { ChannelStore, FluxDispatcher, i18n, Menu, Parser, Timestamp, UserStore } from "@webpack/common"; import { ChannelStore, FluxDispatcher, i18n, Menu, MessageStore, Parser, Timestamp, UserStore, useStateFromStores } from "@webpack/common";
import { Message } from "discord-types/general";
import overlayStyle from "./deleteStyleOverlay.css?managed"; import overlayStyle from "./deleteStyleOverlay.css?managed";
import textStyle from "./deleteStyleText.css?managed"; import textStyle from "./deleteStyleText.css?managed";
interface MLMessage extends Message {
deleted?: boolean;
editHistory?: { timestamp: Date; content: string; }[];
}
const styles = findByPropsLazy("edited", "communicationDisabled", "isSystemMessage"); const styles = findByPropsLazy("edited", "communicationDisabled", "isSystemMessage");
function addDeleteStyle() { function addDeleteStyle() {
@ -89,35 +96,77 @@ const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) =
)); ));
}; };
const patchChannelContextMenu: NavContextMenuPatchCallback = (children, { channel }) => {
const messages = MessageStore.getMessages(channel?.id) as MLMessage[];
if (!messages?.some(msg => msg.deleted || msg.editHistory?.length)) return;
const group = findGroupChildrenByChildId("mark-channel-read", children) ?? children;
group.push(
<Menu.MenuItem
id="vc-ml-clear-channel"
label="Clear Message Log"
color="danger"
action={() => {
messages.forEach(msg => {
if (msg.deleted)
FluxDispatcher.dispatch({
type: "MESSAGE_DELETE",
channelId: channel.id,
id: msg.id,
mlDeleted: true
});
else
updateMessage(channel.id, msg.id, {
editHistory: []
});
});
}}
/>
);
};
export default definePlugin({ export default definePlugin({
name: "MessageLogger", name: "MessageLogger",
description: "Temporarily logs deleted and edited messages.", description: "Temporarily logs deleted and edited messages.",
authors: [Devs.rushii, Devs.Ven, Devs.AutumnVN], authors: [Devs.rushii, Devs.Ven, Devs.AutumnVN, Devs.Nickyux],
dependencies: ["MessageUpdaterAPI"],
contextMenus: { contextMenus: {
"message": patchMessageContextMenu "message": patchMessageContextMenu,
"channel-context": patchChannelContextMenu,
"user-context": patchChannelContextMenu,
"gdm-context": patchChannelContextMenu
}, },
start() { start() {
addDeleteStyle(); addDeleteStyle();
}, },
renderEdit(edit: { timestamp: any, content: string; }) { renderEdits: ErrorBoundary.wrap(({ message: { id: messageId, channel_id: channelId } }: { message: Message; }) => {
return ( const message = useStateFromStores(
<ErrorBoundary noop> [MessageStore],
<div className="messagelogger-edited"> () => MessageStore.getMessage(channelId, messageId) as MLMessage,
{Parser.parse(edit.content)} null,
<Timestamp (oldMsg, newMsg) => oldMsg?.editHistory === newMsg?.editHistory
timestamp={edit.timestamp}
isEdited={true}
isInline={false}
>
<span className={styles.edited}>{" "}({i18n.Messages.MESSAGE_EDITED})</span>
</Timestamp>
</div>
</ErrorBoundary>
); );
},
return (
<>
{message.editHistory?.map(edit => (
<div className="messagelogger-edited">
{Parser.parse(edit.content)}
<Timestamp
timestamp={edit.timestamp}
isEdited={true}
isInline={false}
>
<span className={styles.edited}>{" "}({i18n.Messages.MESSAGE_EDITED})</span>
</Timestamp>
</div>
))}
</>
);
}, { noop: true }),
makeEdit(newMessage: any, oldMessage: any): any { makeEdit(newMessage: any, oldMessage: any): any {
return { return {
@ -222,11 +271,9 @@ export default definePlugin({
(message.channel_id === "1026515880080842772" && message.author?.id === "1017176847865352332"); (message.channel_id === "1026515880080842772" && message.author?.id === "1017176847865352332");
}, },
// Based on canary 63b8f1b4f2025213c5cf62f0966625bee3d53136
patches: [ patches: [
{ {
// MessageStore // MessageStore
// Module 171447
find: '"MessageStore"', find: '"MessageStore"',
replacement: [ replacement: [
{ {
@ -255,7 +302,7 @@ export default definePlugin({
replace: "$1" + replace: "$1" +
".update($3,m =>" + ".update($3,m =>" +
" (($2.message.flags & 64) === 64 || $self.shouldIgnore($2.message, true)) ? m :" + " (($2.message.flags & 64) === 64 || $self.shouldIgnore($2.message, true)) ? m :" +
" $2.message.content !== m.editHistory?.[0]?.content && $2.message.content !== m.content ?" + " $2.message.edited_timestamp && $2.message.content !== m.content ?" +
" m.set('editHistory',[...(m.editHistory || []), $self.makeEdit($2.message, m)]) :" + " m.set('editHistory',[...(m.editHistory || []), $self.makeEdit($2.message, m)]) :" +
" m" + " m" +
")" + ")" +
@ -271,7 +318,6 @@ export default definePlugin({
{ {
// Message domain model // Message domain model
// Module 451
find: "}addReaction(", find: "}addReaction(",
replacement: [ replacement: [
{ {
@ -285,14 +331,8 @@ export default definePlugin({
{ {
// Updated message transformer(?) // Updated message transformer(?)
// Module 819525
find: "THREAD_STARTER_MESSAGE?null===", find: "THREAD_STARTER_MESSAGE?null===",
replacement: [ replacement: [
// {
// // DEBUG: Log the params of the target function to the patch below
// match: /function N\(e,t\){/,
// replace: "function L(e,t){console.log('pre-transform', e, t);"
// },
{ {
// Pass through editHistory & deleted & original attachments to the "edited message" transformer // Pass through editHistory & deleted & original attachments to the "edited message" transformer
match: /(?<=null!=\i\.edited_timestamp\)return )\i\(\i,\{reactions:(\i)\.reactions.{0,50}\}\)/, match: /(?<=null!=\i\.edited_timestamp\)return )\i\(\i,\{reactions:(\i)\.reactions.{0,50}\}\)/,
@ -300,11 +340,6 @@ export default definePlugin({
"Object.assign($&,{ deleted:$1.deleted, editHistory:$1.editHistory, attachments:$1.attachments })" "Object.assign($&,{ deleted:$1.deleted, editHistory:$1.editHistory, attachments:$1.attachments })"
}, },
// {
// // DEBUG: Log the params of the target function to the patch below
// match: /function R\(e\){/,
// replace: "function R(e){console.log('after-edit-transform', arguments);"
// },
{ {
// Construct new edited message and add editHistory & deleted (ref above) // Construct new edited message and add editHistory & deleted (ref above)
// Pass in custom data to attachment parser to mark attachments deleted as well // Pass in custom data to attachment parser to mark attachments deleted as well
@ -335,7 +370,6 @@ export default definePlugin({
{ {
// Attachment renderer // Attachment renderer
// Module 96063
find: ".removeMosaicItemHoverButton", find: ".removeMosaicItemHoverButton",
group: true, group: true,
replacement: [ replacement: [
@ -352,7 +386,6 @@ export default definePlugin({
{ {
// Base message component renderer // Base message component renderer
// Module 748241
find: "Message must not be a thread starter message", find: "Message must not be a thread starter message",
replacement: [ replacement: [
{ {
@ -365,20 +398,18 @@ export default definePlugin({
{ {
// Message content renderer // Message content renderer
// Module 43016
find: "Messages.MESSAGE_EDITED,\")\"", find: "Messages.MESSAGE_EDITED,\")\"",
replacement: [ replacement: [
{ {
// Render editHistory in the deepest div for message content // Render editHistory in the deepest div for message content
match: /(\)\("div",\{id:.+?children:\[)/, match: /(\)\("div",\{id:.+?children:\[)/,
replace: "$1 (arguments[0].message.editHistory?.length > 0 ? arguments[0].message.editHistory.map(edit => $self.renderEdit(edit)) : null), " replace: "$1 (!!arguments[0].message.editHistory?.length && $self.renderEdits(arguments[0])),"
} }
] ]
}, },
{ {
// ReferencedMessageStore // ReferencedMessageStore
// Module 778667
find: '"ReferencedMessageStore"', find: '"ReferencedMessageStore"',
replacement: [ replacement: [
{ {
@ -394,7 +425,6 @@ export default definePlugin({
{ {
// Message context base menu // Message context base menu
// Module 600300
find: "useMessageMenu:", find: "useMessageMenu:",
replacement: [ replacement: [
{ {
@ -404,18 +434,5 @@ export default definePlugin({
} }
] ]
} }
// {
// // MessageStore caching internals
// // Module 819525
// find: "e.getOrCreate=function(t)",
// replacement: [
// // {
// // // DEBUG: log getOrCreate return values from MessageStore caching internals
// // match: /getOrCreate=function(.+?)return/,
// // replace: "getOrCreate=function$1console.log('getOrCreate',n);return"
// // }
// ]
// }
] ]
}); });

View file

@ -200,7 +200,7 @@ export default definePlugin({
} }
}, },
{ {
find: ".DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP,", find: ".DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP_OFFICIAL,",
replacement: [ replacement: [
// make the tag show the right text // make the tag show the right text
{ {

View file

@ -53,7 +53,7 @@ export default definePlugin({
} }
}, },
{ {
find: ".UserProfileSections.USER_INFO_CONNECTIONS:", find: ".USER_INFO_CONNECTIONS:case",
replacement: { replacement: {
match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/, match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/,
replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs({user: $1, onClose: $2});" replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs({user: $1, onClose: $2});"

View file

@ -64,7 +64,7 @@ export default definePlugin({
}, },
// New message requests hook // New message requests hook
{ {
find: "useNewMessageRequestsCount:", find: 'location:"use-message-requests-count"',
predicate: () => settings.store.hideMessageRequestsCount, predicate: () => settings.store.hideMessageRequestsCount,
replacement: { replacement: {
match: /getNonChannelAckId\(\i\.\i\.MESSAGE_REQUESTS\).+?return /, match: /getNonChannelAckId\(\i\.\i\.MESSAGE_REQUESTS\).+?return /,

View file

@ -6,7 +6,7 @@
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModalLazy } from "@utils/modal"; import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModalLazy } from "@utils/modal";
import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack"; import { extractAndLoadChunksLazy, findComponentByCodeLazy, findExportedComponentLazy } from "@webpack";
import { Button, Forms, Text, TextInput, Toasts, useEffect, useState } from "@webpack/common"; import { Button, Forms, Text, TextInput, Toasts, useEffect, useState } from "@webpack/common";
import { DEFAULT_COLOR, SWATCHES } from "../constants"; import { DEFAULT_COLOR, SWATCHES } from "../constants";
@ -31,7 +31,7 @@ interface ColorPickerWithSwatchesProps {
} }
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)"); const ColorPicker = findComponentByCodeLazy<ColorPickerProps>(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
const ColorPickerWithSwatches = findComponentByCodeLazy<ColorPickerWithSwatchesProps>("presets,", "customColor:"); const ColorPickerWithSwatches = findExportedComponentLazy<ColorPickerWithSwatchesProps>("ColorPicker", "CustomColorPicker");
export const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}Promise\.all\((\[\i\.\i\(".+?"\).+?\])\).then\(\i\.bind\(\i,"(.+?)"\)\).{0,50}"UserSettings"/); export const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}Promise\.all\((\[\i\.\i\(".+?"\).+?\])\).then\(\i\.bind\(\i,"(.+?)"\)\).{0,50}"UserSettings"/);

View file

@ -82,7 +82,7 @@ export default definePlugin({
// Rendering // Rendering
{ {
match: /"renderRow",(\i)=>{(?<="renderDM",.+?(\i\.default),\{channel:.+?)/, match: /"renderRow",(\i)=>{(?<="renderDM",.+?(\i\.\i),\{channel:.+?)/,
replace: "$&if($self.isChannelIndex($1.section, $1.row))return $self.renderChannel($1.section,$1.row,$2)();" replace: "$&if($self.isChannelIndex($1.section, $1.row))return $self.renderChannel($1.section,$1.row,$2)();"
}, },
{ {
@ -131,7 +131,7 @@ export default definePlugin({
// Fix Alt Up/Down navigation // Fix Alt Up/Down navigation
{ {
find: ".Routes.APPLICATION_STORE&&", find: ".APPLICATION_STORE&&",
replacement: { replacement: {
// channelIds = __OVERLAY__ ? stuff : [...getStaticPaths(),...channelIds)] // channelIds = __OVERLAY__ ? stuff : [...getStaticPaths(),...channelIds)]
match: /(?<=\i=__OVERLAY__\?\i:\[\.\.\.\i\(\),\.\.\.)\i/, match: /(?<=\i=__OVERLAY__\?\i:\[\.\.\.\i\(\),\.\.\.)\i/,

View file

@ -130,9 +130,9 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma
}; };
const badge: ProfileBadge = { const badge: ProfileBadge = {
component: p => <PlatformIndicator {...p} wantMargin={false} />, component: p => <PlatformIndicator {...p} user={UserStore.getUser(p.userId)} wantMargin={false} />,
position: BadgePosition.START, position: BadgePosition.START,
shouldShow: userInfo => !!Object.keys(getStatus(userInfo.user.id) ?? {}).length, shouldShow: userInfo => !!Object.keys(getStatus(userInfo.userId) ?? {}).length,
key: "indicator" key: "indicator"
}; };

View file

@ -21,7 +21,7 @@ import { UserUtils } from "@webpack/common";
import settings from "./settings"; import settings from "./settings";
import { ChannelDelete, ChannelType, GuildDelete, RelationshipRemove, RelationshipType } from "./types"; import { ChannelDelete, ChannelType, GuildDelete, RelationshipRemove, RelationshipType } from "./types";
import { deleteGroup, deleteGuild, getGroup, getGuild, notify } from "./utils"; import { deleteGroup, deleteGuild, getGroup, getGuild, GuildAvailabilityStore, notify } from "./utils";
let manuallyRemovedFriend: string | undefined; let manuallyRemovedFriend: string | undefined;
let manuallyRemovedGuild: string | undefined; let manuallyRemovedGuild: string | undefined;
@ -63,7 +63,7 @@ export async function onRelationshipRemove({ relationship: { type, id } }: Relat
export function onGuildDelete({ guild: { id, unavailable } }: GuildDelete) { export function onGuildDelete({ guild: { id, unavailable } }: GuildDelete) {
if (!settings.store.servers) return; if (!settings.store.servers) return;
if (unavailable) return; if (unavailable || GuildAvailabilityStore.isUnavailable(id)) return;
if (manuallyRemovedGuild === id) { if (manuallyRemovedGuild === id) {
deleteGuild(id); deleteGuild(id);

View file

@ -19,11 +19,20 @@
import { DataStore, Notices } from "@api/index"; import { DataStore, Notices } from "@api/index";
import { showNotification } from "@api/Notifications"; import { showNotification } from "@api/Notifications";
import { getUniqueUsername, openUserProfile } from "@utils/discord"; import { getUniqueUsername, openUserProfile } from "@utils/discord";
import { findStoreLazy } from "@webpack";
import { ChannelStore, GuildMemberStore, GuildStore, RelationshipStore, UserStore, UserUtils } from "@webpack/common"; import { ChannelStore, GuildMemberStore, GuildStore, RelationshipStore, UserStore, UserUtils } from "@webpack/common";
import { FluxStore } from "@webpack/types";
import settings from "./settings"; import settings from "./settings";
import { ChannelType, RelationshipType, SimpleGroupChannel, SimpleGuild } from "./types"; import { ChannelType, RelationshipType, SimpleGroupChannel, SimpleGuild } from "./types";
export const GuildAvailabilityStore = findStoreLazy("GuildAvailabilityStore") as FluxStore & {
totalGuilds: number;
totalUnavailableGuilds: number;
unavailableGuilds: string[];
isUnavailable(guildId: string): boolean;
};
const guilds = new Map<string, SimpleGuild>(); const guilds = new Map<string, SimpleGuild>();
const groups = new Map<string, SimpleGroupChannel>(); const groups = new Map<string, SimpleGroupChannel>();
const friends = { const friends = {
@ -59,7 +68,7 @@ export async function syncAndRunChecks() {
if (settings.store.servers && oldGuilds?.size) { if (settings.store.servers && oldGuilds?.size) {
for (const [id, guild] of oldGuilds) { for (const [id, guild] of oldGuilds) {
if (!guilds.has(id)) if (!guilds.has(id) && !GuildAvailabilityStore.isUnavailable(id))
notify(`You are no longer in the server ${guild.name}.`, guild.iconURL); notify(`You are no longer in the server ${guild.name}.`, guild.iconURL);
} }
} }

View file

@ -1,5 +0,0 @@
# ResurrectHome
Brings back the phased out [Server Home](https://support.discord.com/hc/en-us/articles/6156116949911-Server-Home-Beta) feature!
![](https://github.com/Vendicated/Vencord/assets/61953774/98d5d667-bbb9-48b8-872d-c9b3980f6506)

View file

@ -1,195 +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 <https://www.gnu.org/licenses/>.
*/
import { findGroupChildrenByChildId } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { Button, Menu, Tooltip, useEffect, useState } from "@webpack/common";
const ChannelRowClasses = findByPropsLazy("modeConnected", "modeLocked", "icon");
let currentShouldViewServerHome = false;
const shouldViewServerHomeStates = new Set<React.Dispatch<React.SetStateAction<boolean>>>();
function ViewServerHomeButton() {
return (
<Tooltip text="View Server Home">
{tooltipProps => (
<Button
{...tooltipProps}
look={Button.Looks.BLANK}
size={Button.Sizes.NONE}
innerClassName={ChannelRowClasses.icon}
onClick={e => {
e.preventDefault();
currentShouldViewServerHome = true;
for (const setState of shouldViewServerHomeStates) {
setState(true);
}
}}
>
<svg width="20" height="20" viewBox="0 0 24 24">
<path fill="currentColor" d="m2.4 8.4 8.38-6.46a2 2 0 0 1 2.44 0l8.39 6.45a2 2 0 0 1-.79 3.54l-.32.07-.82 8.2a2 2 0 0 1-1.99 1.8H16a1 1 0 0 1-1-1v-5a3 3 0 0 0-6 0v5a1 1 0 0 1-1 1H6.31a2 2 0 0 1-1.99-1.8L3.5 12l-.32-.07a2 2 0 0 1-.79-3.54Z" />
</svg>
</Button>
)}
</Tooltip>
);
}
function useForceServerHome() {
const { forceServerHome } = settings.use(["forceServerHome"]);
const [shouldViewServerHome, setShouldViewServerHome] = useState(currentShouldViewServerHome);
useEffect(() => {
shouldViewServerHomeStates.add(setShouldViewServerHome);
return () => {
shouldViewServerHomeStates.delete(setShouldViewServerHome);
};
}, []);
return shouldViewServerHome || forceServerHome;
}
function useDisableViewServerHome() {
useEffect(() => () => {
currentShouldViewServerHome = false;
for (const setState of shouldViewServerHomeStates) {
setState(false);
}
}, []);
}
const settings = definePluginSettings({
forceServerHome: {
type: OptionType.BOOLEAN,
description: "Force the Server Guide to be the Server Home tab when it is enabled.",
default: false
}
});
export default definePlugin({
name: "ResurrectHome",
description: "Re-enables the Server Home tab when there isn't a Server Guide. Also has an option to force the Server Home over the Server Guide, which is accessible through right-clicking a server.",
authors: [Devs.Dolfies, Devs.Nuckyz],
settings,
patches: [
// Force home deprecation override
{
find: "GuildFeatures.GUILD_HOME_DEPRECATION_OVERRIDE",
all: true,
replacement: [
{
match: /\i\.hasFeature\(\i\.GuildFeatures\.GUILD_HOME_DEPRECATION_OVERRIDE\)/g,
replace: "true"
}
],
},
// Disable feedback prompts
{
find: "GuildHomeFeedbackExperiment.definition.id",
replacement: [
{
match: /return{showFeedback:.+?,setOnDismissedFeedback:(\i)}/,
replace: "return{showFeedback:false,setOnDismissedFeedback:$1}"
}
]
},
// This feature was never finished, so the patch is disabled
// Enable guild feed render mode selector
// {
// find: "2022-01_home_feed_toggle",
// replacement: [
// {
// match: /showSelector:!1/,
// replace: "showSelector:true"
// }
// ]
// },
// Fix focusMessage clearing previously cached messages and causing a loop when fetching messages around home messages
{
find: '"MessageActionCreators"',
replacement: {
match: /focusMessage\(\i\){.+?(?=focus:{messageId:(\i)})/,
replace: "$&after:$1,"
}
},
// Force Server Home instead of Server Guide
{
find: "61eef9_2",
replacement: {
match: /getMutableGuildChannelsForGuild\(\i\);return\(0,\i\.useStateFromStores\).+?\]\)(?=}function)/,
replace: m => `${m}&&!$self.useForceServerHome()`
}
},
// Add View Server Home Button to Server Guide
{
find: "487e85_1",
replacement: {
match: /(?<=text:(\i)\?\i\.\i\.Messages\.SERVER_GUIDE:\i\.\i\.Messages\.GUILD_HOME,)/,
replace: "trailing:$self.ViewServerHomeButton({serverGuide:$1}),"
}
},
// Disable view Server Home override when the Server Home is unmouted
{
find: "69386d_5",
replacement: {
match: /location:"69386d_5".+?;/,
replace: "$&$self.useDisableViewServerHome();"
}
}
],
ViewServerHomeButton: ErrorBoundary.wrap(({ serverGuide }: { serverGuide?: boolean; }) => {
if (serverGuide !== true) return null;
return <ViewServerHomeButton />;
}),
useForceServerHome,
useDisableViewServerHome,
contextMenus: {
"guild-context"(children, props) {
const { forceServerHome } = settings.use(["forceServerHome"]);
if (!props?.guild) return;
const group = findGroupChildrenByChildId("hide-muted-channels", children);
group?.unshift(
<Menu.MenuCheckboxItem
key="force-server-home"
id="force-server-home"
label="Force Server Home"
checked={forceServerHome}
action={() => settings.store.forceServerHome = !forceServerHome}
/>
);
}
}
});

View file

@ -45,7 +45,7 @@ export default LazyComponent(() => {
p("container", "isHeader"), p("container", "isHeader"),
p("avatar", "zalgo"), p("avatar", "zalgo"),
p("button", "wrapper", "selected"), p("button", "wrapper", "selected"),
p("botTag", "botTagRegular") p("botTagRegular")
); );
const dateFormat = new Intl.DateTimeFormat(); const dateFormat = new Intl.DateTimeFormat();

View file

@ -17,7 +17,7 @@
*/ */
import { useAwaiter, useForceUpdater } from "@utils/react"; import { useAwaiter, useForceUpdater } from "@utils/react";
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { Forms, React, RelationshipStore, useRef, UserStore } from "@webpack/common"; import { Forms, React, RelationshipStore, useRef, UserStore } from "@webpack/common";
import { Auth, authorize } from "../auth"; import { Auth, authorize } from "../auth";
@ -27,12 +27,11 @@ import { settings } from "../settings";
import { cl, showToast } from "../utils"; import { cl, showToast } from "../utils";
import ReviewComponent from "./ReviewComponent"; import ReviewComponent from "./ReviewComponent";
const Transforms = findByPropsLazy("insertNodes", "textToText");
const { Editor, Transforms } = findByPropsLazy("Editor", "Transforms"); const Editor = findByPropsLazy("start", "end", "toSlateRange");
const { ChatInputTypes } = findByPropsLazy("ChatInputTypes"); const ChatInputTypes = findByPropsLazy("FORM");
const InputComponent = findComponentByCodeLazy("disableThemedBackground", "CHANNEL_TEXT_AREA");
const InputComponent = findComponentByCodeLazy("default.CHANNEL_TEXT_AREA", "input"); const createChannelRecordFromServer = findByCodeLazy(".GUILD_TEXT])", "fromServer)");
const { createChannelRecordFromServer } = findByPropsLazy("createChannelRecordFromServer");
interface UserProps { interface UserProps {
discordId: string; discordId: string;

View file

@ -40,9 +40,16 @@ const settings = definePluginSettings({
default: true, default: true,
description: "Show role colors in the voice chat user list", description: "Show role colors in the voice chat user list",
restartNeeded: true restartNeeded: true
},
reactorsList: {
type: OptionType.BOOLEAN,
default: true,
description: "Show role colors in the reactors list",
restartNeeded: true
} }
}); });
export default definePlugin({ export default definePlugin({
name: "RoleColorEverywhere", name: "RoleColorEverywhere",
authors: [Devs.KingFish, Devs.lewisakura, Devs.AutumnVN], authors: [Devs.KingFish, Devs.lewisakura, Devs.AutumnVN],
@ -64,7 +71,7 @@ export default definePlugin({
find: ".userTooltip,children", find: ".userTooltip,children",
replacement: [ replacement: [
{ {
match: /let\{id:(\i),guildId:(\i)[^}]*\}.*?\.default,{(?=children)/, match: /let\{id:(\i),guildId:(\i)[^}]*\}.*?\.\i,{(?=children)/,
replace: "$&color:$self.getUserColor($1,{guildId:$2})," replace: "$&color:$self.getUserColor($1,{guildId:$2}),"
} }
], ],
@ -99,6 +106,14 @@ export default definePlugin({
} }
], ],
predicate: () => settings.store.voiceUsers, predicate: () => settings.store.voiceUsers,
},
{
find: ".reactorDefault",
replacement: {
match: /\.openUserContextMenu\)\((\i),(\i),\i\).{0,250}tag:"strong"/,
replace: "$&,style:{color:$self.getColor($2?.id,$1)}"
},
predicate: () => settings.store.reactorsList,
} }
], ],
settings, settings,

View file

@ -20,12 +20,12 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/Co
import { ReplyIcon } from "@components/Icons"; import { ReplyIcon } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByCodeLazy } from "@webpack";
import { ChannelStore, i18n, Menu, PermissionsBits, PermissionStore, SelectedChannelStore } from "@webpack/common"; import { ChannelStore, i18n, Menu, PermissionsBits, PermissionStore, SelectedChannelStore } from "@webpack/common";
import { Message } from "discord-types/general"; import { Message } from "discord-types/general";
const messageUtils = findByPropsLazy("replyToMessage"); const replyToMessage = findByCodeLazy(".TEXTAREA_FOCUS)", "showMentionToggle:");
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => { const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => {
// make sure the message is in the selected channel // make sure the message is in the selected channel
@ -43,7 +43,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag
id="reply" id="reply"
label={i18n.Messages.MESSAGE_ACTION_REPLY} label={i18n.Messages.MESSAGE_ACTION_REPLY}
icon={ReplyIcon} icon={ReplyIcon}
action={(e: React.MouseEvent) => messageUtils.replyToMessage(channel, message, e)} action={(e: React.MouseEvent) => replyToMessage(channel, message, e)}
/> />
)); ));
return; return;
@ -57,7 +57,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag
id="reply" id="reply"
label={i18n.Messages.MESSAGE_ACTION_REPLY} label={i18n.Messages.MESSAGE_ACTION_REPLY}
icon={ReplyIcon} icon={ReplyIcon}
action={(e: React.MouseEvent) => messageUtils.replyToMessage(channel, message, e)} action={(e: React.MouseEvent) => replyToMessage(channel, message, e)}
/> />
)); ));
return; return;

View file

@ -8,11 +8,11 @@ import { DataStore } from "@api/index";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByCodeLazy, findByPropsLazy } from "@webpack";
import { ChannelStore, GuildStore } from "@webpack/common"; import { ChannelStore, GuildStore } from "@webpack/common";
const SummaryStore = findByPropsLazy("allSummaries", "findSummary"); const SummaryStore = findByPropsLazy("allSummaries", "findSummary");
const { createSummaryFromServer } = findByPropsLazy("createSummaryFromServer"); const createSummaryFromServer = findByCodeLazy(".people)),startId:");
const settings = definePluginSettings({ const settings = definePluginSettings({
summaryExpiryThresholdDays: { summaryExpiryThresholdDays: {
@ -55,9 +55,9 @@ export default definePlugin({
settings, settings,
patches: [ patches: [
{ {
find: "ChannelTypesSets.SUMMARIZEABLE.has", find: "SUMMARIZEABLE.has",
replacement: { replacement: {
match: /\i\.hasFeature\(\i\.GuildFeatures\.SUMMARIES_ENABLED\w+?\)/g, match: /\i\.hasFeature\(.{0,10}\.SUMMARIES_ENABLED\w+?\)/g,
replace: "true" replace: "true"
} }
}, },

View file

@ -11,12 +11,12 @@ import { openImageModal, openUserProfile } from "@utils/discord";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { ModalRoot, ModalSize, openModal } from "@utils/modal"; import { ModalRoot, ModalSize, openModal } from "@utils/modal";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { findByPropsLazy, findExportedComponentLazy } from "@webpack"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, GuildStore, IconUtils, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common"; import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, GuildStore, IconUtils, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common";
import { Guild, User } from "discord-types/general"; import { Guild, User } from "discord-types/general";
const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper"); const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper");
const FriendRow = findExportedComponentLazy("FriendRow"); const FriendRow = findComponentByCodeLazy(".listName,discriminatorClass");
const cl = classNameFactory("vc-gp-"); const cl = classNameFactory("vc-gp-");

View file

@ -33,7 +33,8 @@ import { VerifiedIcon } from "./VerifiedIcon";
const Section = findComponentByCodeLazy(".lastSection", "children:"); const Section = findComponentByCodeLazy(".lastSection", "children:");
const ThemeStore = findStoreLazy("ThemeStore"); const ThemeStore = findStoreLazy("ThemeStore");
const platformHooks: { useLegacyPlatformType(platform: string): string; } = findByPropsLazy("useLegacyPlatformType");
const useLegacyPlatformType: (platform: string) => string = findByCodeLazy(".TWITTER_LEGACY:");
const platforms: { get(type: string): ConnectionPlatform; } = findByPropsLazy("isSupported", "getByUrl"); const platforms: { get(type: string): ConnectionPlatform; } = findByPropsLazy("isSupported", "getByUrl");
const getProfileThemeProps = findByCodeLazy(".getPreviewThemeColors", "primaryColor:"); const getProfileThemeProps = findByCodeLazy(".getPreviewThemeColors", "primaryColor:");
@ -74,15 +75,28 @@ interface ConnectionPlatform {
icon: { lightSVG: string, darkSVG: string; }; icon: { lightSVG: string, darkSVG: string; };
} }
const profilePopoutComponent = ErrorBoundary.wrap((props: { user: User, displayProfile; }) => const profilePopoutComponent = ErrorBoundary.wrap(
<ConnectionsComponent id={props.user.id} theme={getProfileThemeProps(props).theme} /> (props: { user: User; displayProfile?: any; simplified?: boolean; }) => (
<ConnectionsComponent
{...props}
id={props.user.id}
theme={getProfileThemeProps(props).theme}
/>
),
{ noop: true }
); );
const profilePanelComponent = ErrorBoundary.wrap(({ id }: { id: string; }) => const profilePanelComponent = ErrorBoundary.wrap(
<ConnectionsComponent id={id} theme={ThemeStore.theme} /> (props: { id: string; simplified?: boolean; }) => (
<ConnectionsComponent
{...props}
theme={ThemeStore.theme}
/>
),
{ noop: true }
); );
function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) { function ConnectionsComponent({ id, theme, simplified }: { id: string, theme: string, simplified?: boolean; }) {
const profile = UserProfileStore.getUserProfile(id); const profile = UserProfileStore.getUserProfile(id);
if (!profile) if (!profile)
return null; return null;
@ -91,6 +105,19 @@ function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) {
if (!connections?.length) if (!connections?.length)
return null; return null;
const connectionsContainer = (
<Flex style={{
marginTop: !simplified ? "8px" : undefined,
gap: getSpacingPx(settings.store.iconSpacing),
flexWrap: "wrap"
}}>
{connections.map(connection => <CompactConnectionComponent connection={connection} theme={theme} />)}
</Flex>
);
if (simplified)
return connectionsContainer;
return ( return (
<Section> <Section>
<Text <Text
@ -100,19 +127,13 @@ function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) {
> >
Connections Connections
</Text> </Text>
<Flex style={{ {connectionsContainer}
marginTop: "8px",
gap: getSpacingPx(settings.store.iconSpacing),
flexWrap: "wrap"
}}>
{connections.map(connection => <CompactConnectionComponent connection={connection} theme={theme} />)}
</Flex>
</Section> </Section>
); );
} }
function CompactConnectionComponent({ connection, theme }: { connection: Connection, theme: string; }) { function CompactConnectionComponent({ connection, theme }: { connection: Connection, theme: string; }) {
const platform = platforms.get(platformHooks.useLegacyPlatformType(connection.type)); const platform = platforms.get(useLegacyPlatformType(connection.type));
const url = platform.getPlatformUserUrl?.(connection); const url = platform.getPlatformUserUrl?.(connection);
const img = ( const img = (
@ -132,7 +153,7 @@ function CompactConnectionComponent({ connection, theme }: { connection: Connect
<Tooltip <Tooltip
text={ text={
<span className="vc-sc-tooltip"> <span className="vc-sc-tooltip">
{connection.name} <span className="vc-sc-connection-name">{connection.name}</span>
{connection.verified && <VerifiedIcon />} {connection.verified && <VerifiedIcon />}
<TooltipIcon height={16} width={16} /> <TooltipIcon height={16} width={16} />
</span> </span>
@ -182,12 +203,19 @@ export default definePlugin({
} }
}, },
{ {
find: ".UserPopoutUpsellSource.PROFILE_PANEL,", find: ".PROFILE_PANEL,",
replacement: { replacement: {
// createElement(Divider, {}), createElement(NoteComponent) // createElement(Divider, {}), createElement(NoteComponent)
match: /\(0,\i\.jsx\)\(\i\.\i,\{\}\).{0,100}setNote:(?=.+?channelId:(\i).id)/, match: /\(0,\i\.jsx\)\(\i\.\i,\{\}\).{0,100}setNote:(?=.+?channelId:(\i).id)/,
replace: "$self.profilePanelComponent({ id: $1.recipients[0] }),$&" replace: "$self.profilePanelComponent({ id: $1.recipients[0] }),$&"
} }
},
{
find: ".BITE_SIZE,onOpenProfile",
replacement: {
match: /currentUser:\i,guild:\i,onOpenProfile:.+?}\)(?=])(?<=user:(\i),bio:null==(\i)\?.+?)/,
replace: "$&,$self.profilePopoutComponent({ user: $1, displayProfile: $2, simplified: true })"
}
} }
], ],
settings, settings,

View file

@ -9,3 +9,11 @@
gap: 0.25em; gap: 0.25em;
align-items: center; align-items: center;
} }
.vc-sc-connection-name {
word-break: break-all;
}
.vc-sc-tooltip svg {
min-width: 16px;
}

View file

@ -78,7 +78,7 @@ const enum ChannelFlags {
} }
const ChatScrollClasses = findByPropsLazy("auto", "content", "scrollerBase"); const ChatScrollClasses = findByPropsLazy("auto", "managedReactiveScroller");
const ChatClasses = findByPropsLazy("chat", "content", "noChat", "chatContent"); const ChatClasses = findByPropsLazy("chat", "content", "noChat", "chatContent");
const ChannelBeginHeader = findComponentByCodeLazy(".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE"); const ChannelBeginHeader = findComponentByCodeLazy(".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE");
const TagComponent = findComponentLazy(m => { const TagComponent = findComponentLazy(m => {

View file

@ -73,9 +73,8 @@ export default definePlugin({
find: '"placeholder-channel-id"', find: '"placeholder-channel-id"',
replacement: [ replacement: [
// Remove the special logic for channels we don't have access to // Remove the special logic for channels we don't have access to
// FIXME Remove variable matcher from threadsIds when it hits stable
{ {
match: /if\(!\i\.\i\.can\(\i\.\i\.VIEW_CHANNEL.+?{if\(this\.id===\i\).+?threadIds:(?:\[\]|\i)}}/, match: /if\(!\i\.\i\.can\(\i\.\i\.VIEW_CHANNEL.+?{if\(this\.id===\i\).+?threadIds:\[\]}}/,
replace: "" replace: ""
}, },
// Do not check for unreads when selecting the render level if the channel is hidden // Do not check for unreads when selecting the render level if the channel is hidden

View file

@ -17,7 +17,7 @@
*/ */
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
import { findByProps, proxyLazyWebpack } from "@webpack"; import { findByProps, findByPropsLazy, proxyLazyWebpack } from "@webpack";
import { Flux, FluxDispatcher } from "@webpack/common"; import { Flux, FluxDispatcher } from "@webpack/common";
export interface Track { export interface Track {
@ -70,7 +70,7 @@ export const SpotifyStore = proxyLazyWebpack(() => {
const { Store } = Flux; const { Store } = Flux;
const SpotifySocket = findByProps("getActiveSocketAndDevice"); const SpotifySocket = findByProps("getActiveSocketAndDevice");
const SpotifyUtils = findByProps("SpotifyAPI"); const SpotifyAPI = findByPropsLazy("vcSpotifyMarker");
const API_BASE = "https://api.spotify.com/v1/me/player"; const API_BASE = "https://api.spotify.com/v1/me/player";
@ -168,7 +168,7 @@ export const SpotifyStore = proxyLazyWebpack(() => {
(data.query ??= {}).device_id = this.device.id; (data.query ??= {}).device_id = this.device.id;
const { socket } = SpotifySocket.getActiveSocketAndDevice(); const { socket } = SpotifySocket.getActiveSocketAndDevice();
return SpotifyUtils.SpotifyAPI[method](socket.accountId, socket.accessToken, { return SpotifyAPI[method](socket.accountId, socket.accessToken, {
url: API_BASE + route, url: API_BASE + route,
...data ...data
}); });

View file

@ -61,7 +61,7 @@ export default definePlugin({
replacement: [{ replacement: [{
// Adds POST and a Marker to the SpotifyAPI (so we can easily find it) // Adds POST and a Marker to the SpotifyAPI (so we can easily find it)
match: /get:(\i)\.bind\(null,(\i\.\i)\.get\)/, match: /get:(\i)\.bind\(null,(\i\.\i)\.get\)/,
replace: "post:$1.bind(null,$2.post),$&" replace: "post:$1.bind(null,$2.post),vcSpotifyMarker:1,$&"
}, },
{ {
// Spotify Connect API returns status 202 instead of 204 when skipping tracks. // Spotify Connect API returns status 202 instead of 204 when skipping tracks.
@ -81,7 +81,7 @@ export default definePlugin({
{ {
find: "artists.filter", find: "artists.filter",
replacement: { replacement: {
match: /\(0,(\i)\.isNotNullish\)\((\i)\.id\)&&/, match: /(?<=artists.filter\(\i=>).{0,10}\i\.id\)&&/,
replace: "" replace: ""
} }
} }

View file

@ -61,11 +61,7 @@ export default definePlugin({
replacement: [ replacement: [
{ {
match: /(\i)\.premiumType/, match: /(\i)\.premiumType/,
replace: "$self.premiumHook($1)||$&" replace: "$self.patchPremiumType($1)||$&"
},
{
match: /(?<=function \i\((\i)\)\{)(?=var.{30,50},bannerSrc:)/,
replace: "$1.bannerSrc=$self.useBannerHook($1);"
}, },
{ {
match: /\?\(0,\i\.jsx\)\(\i,{type:\i,shown/, match: /\?\(0,\i\.jsx\)\(\i,{type:\i,shown/,
@ -74,17 +70,12 @@ export default definePlugin({
] ]
}, },
{ {
find: /overrideBannerSrc:\i,overrideBannerWidth:/, find: "BannerLoadingStatus:function",
replacement: [ replacement: {
{ match: /(?<=void 0:)\i.getPreviewBanner\(\i,\i,\i\)/,
match: /(\i)\.premiumType/, replace: "$self.patchBannerUrl(arguments[0])||$&"
replace: "$self.premiumHook($1)||$&"
}, }
{
match: /function \i\((\i)\)\{/,
replace: "$&$1.overrideBannerSrc=$self.useBannerHook($1);"
}
]
}, },
{ {
find: "\"data-selenium-video-tile\":", find: "\"data-selenium-video-tile\":",
@ -92,7 +83,7 @@ export default definePlugin({
replacement: [ replacement: [
{ {
match: /(?<=function\((\i),\i\)\{)(?=let.{20,40},style:)/, match: /(?<=function\((\i),\i\)\{)(?=let.{20,40},style:)/,
replace: "$1.style=$self.voiceBackgroundHook($1);" replace: "$1.style=$self.getVoiceBackgroundStyles($1);"
} }
] ]
} }
@ -106,7 +97,7 @@ export default definePlugin({
); );
}, },
voiceBackgroundHook({ className, participantUserId }: any) { getVoiceBackgroundStyles({ className, participantUserId }: any) {
if (className.includes("tile_")) { if (className.includes("tile_")) {
if (this.userHasBackground(participantUserId)) { if (this.userHasBackground(participantUserId)) {
return { return {
@ -119,12 +110,12 @@ export default definePlugin({
} }
}, },
useBannerHook({ displayProfile, user }: any) { patchBannerUrl({ displayProfile }: any) {
if (displayProfile?.banner && settings.store.nitroFirst) return; if (displayProfile?.banner && settings.store.nitroFirst) return;
if (this.userHasBackground(user.id)) return this.getImageUrl(user.id); if (this.userHasBackground(displayProfile?.userId)) return this.getImageUrl(displayProfile?.userId);
}, },
premiumHook({ userId }: any) { patchPremiumType({ userId }: any) {
if (this.userHasBackground(userId)) return 2; if (this.userHasBackground(userId)) return 2;
}, },

View file

@ -184,7 +184,7 @@ export default definePlugin({
patches: [ patches: [
// Profiles Modal pfp // Profiles Modal pfp
...["User Profile Modal - Context Menu", ".UserProfileTypes.FULL_SIZE,hasProfileEffect:"].map(find => ({ ...[".MODAL,hasProfileEffect", ".FULL_SIZE,hasProfileEffect:"].map(find => ({
find, find,
replacement: { replacement: {
match: /\{src:(\i)(?=,avatarDecoration)/, match: /\{src:(\i)(?=,avatarDecoration)/,
@ -192,7 +192,7 @@ export default definePlugin({
} }
})), })),
// Banners // Banners
...[".NITRO_BANNER,", /overrideBannerSrc:\i,overrideBannerWidth:/].map(find => ({ ...[".NITRO_BANNER,", "=!1,canUsePremiumCustomization:"].map(find => ({
find, find,
replacement: { replacement: {
// style: { backgroundImage: shouldShowBanner ? "url(".concat(bannerUrl, // style: { backgroundImage: shouldShowBanner ? "url(".concat(bannerUrl,
@ -222,7 +222,7 @@ export default definePlugin({
{ {
find: /\.recipients\.length>=2(?!<isMultiUserDM.{0,50})/, find: /\.recipients\.length>=2(?!<isMultiUserDM.{0,50})/,
replacement: { replacement: {
match: /null==\i\.icon\?.+?src:(\(0,\i\.getChannelIconURL\).+?\))(?=[,}])/, match: /null==\i\.icon\?.+?src:(\(0,\i\.\i\).+?\))(?=[,}])/,
replace: (m, iconUrl) => `${m},onClick:()=>$self.openImage(${iconUrl})` replace: (m, iconUrl) => `${m},onClick:()=>$self.openImage(${iconUrl})`
} }
}, },

View file

@ -197,8 +197,8 @@ export default definePlugin({
{ {
find: '"MediaEngineWebRTC");', find: '"MediaEngineWebRTC");',
replacement: { replacement: {
match: /supports\(\i\)\{switch\(\i\)\{case (\i).Features/, match: /supports\(\i\)\{switch\(\i\)\{(case (\i).\i)/,
replace: "$&.DISABLE_VIDEO:return true;case $1.Features" replace: "$&.DISABLE_VIDEO:return true;$1"
} }
} }
], ],

View file

@ -9,11 +9,11 @@ import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types"; import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByCodeLazy, findLazy } from "@webpack";
import { ChannelStore, GuildStore, UserStore } from "@webpack/common"; import { ChannelStore, GuildStore, UserStore } from "@webpack/common";
import type { Channel, Embed, GuildMember, MessageAttachment, User } from "discord-types/general"; import type { Channel, Embed, GuildMember, MessageAttachment, User } from "discord-types/general";
const { ChannelTypes } = findByPropsLazy("ChannelTypes"); const ChannelTypes = findLazy(m => m.ANNOUNCEMENT_THREAD === 10);
interface Message { interface Message {
guild_id: string, guild_id: string,
@ -68,7 +68,7 @@ interface Call {
ringing: string[]; ringing: string[];
} }
const Notifs = findByPropsLazy("makeTextChatNotification"); const notificationsShouldNotify = findByCodeLazy(".SUPPRESS_NOTIFICATIONS))return!1");
const XSLog = new Logger("XSOverlay"); const XSLog = new Logger("XSOverlay");
const settings = definePluginSettings({ const settings = definePluginSettings({
@ -304,7 +304,7 @@ function shouldNotify(message: Message, channel: string) {
const currentUser = UserStore.getCurrentUser(); const currentUser = UserStore.getCurrentUser();
if (message.author.id === currentUser.id) return false; if (message.author.id === currentUser.id) return false;
if (message.author.bot && !settings.store.botNotifications) return false; if (message.author.bot && !settings.store.botNotifications) return false;
return Notifs.shouldNotify(message, channel); return notificationsShouldNotify(message, channel);
} }
function calculateHeight(content: string) { function calculateHeight(content: string) {

View file

@ -38,7 +38,7 @@ export const Devs = /* #__PURE__*/ Object.freeze({
id: 0n, id: 0n,
}, },
Ven: { Ven: {
name: "Vendicated", name: "Vee",
id: 343383572805058560n id: 343383572805058560n
}, },
Arjix: { Arjix: {
@ -327,7 +327,7 @@ export const Devs = /* #__PURE__*/ Object.freeze({
id: 305288513941667851n id: 305288513941667851n
}, },
ImLvna: { ImLvna: {
name: "Luna <3", name: "lillith <3",
id: 799319081723232267n id: 799319081723232267n
}, },
rad: { rad: {

View file

@ -120,6 +120,8 @@ export function openImageModal(url: string, props?: Partial<React.ComponentProps
placeholder={url} placeholder={url}
src={url} src={url}
renderLinkComponent={props => <MaskedLink {...props} />} renderLinkComponent={props => <MaskedLink {...props} />}
// FIXME: wtf is this? do we need to pass some proper component??
renderForwardComponent={() => null}
shouldHideMediaOptions={false} shouldHideMediaOptions={false}
shouldAnimate shouldAnimate
{...props} {...props}

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { findByPropsLazy, findExportedComponentLazy } from "@webpack"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react"; import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react";
import { LazyComponent } from "./react"; import { LazyComponent } from "./react";
@ -111,6 +111,7 @@ export type ImageModal = ComponentType<{
animated?: boolean; animated?: boolean;
responsive?: boolean; responsive?: boolean;
renderLinkComponent(props: any): ReactNode; renderLinkComponent(props: any): ReactNode;
renderForwardComponent(props: any): ReactNode;
maxWidth?: number; maxWidth?: number;
maxHeight?: number; maxHeight?: number;
shouldAnimate?: boolean; shouldAnimate?: boolean;
@ -118,7 +119,7 @@ export type ImageModal = ComponentType<{
shouldHideMediaOptions?: boolean; shouldHideMediaOptions?: boolean;
}>; }>;
export const ImageModal = findExportedComponentLazy("ImageModal") as ImageModal; export const ImageModal = findComponentByCodeLazy(".MEDIA_MODAL_CLOSE", "responsive") as ImageModal;
export const ModalRoot = LazyComponent(() => Modals.ModalRoot); export const ModalRoot = LazyComponent(() => Modals.ModalRoot);
export const ModalHeader = LazyComponent(() => Modals.ModalHeader); export const ModalHeader = LazyComponent(() => Modals.ModalHeader);

View file

@ -17,12 +17,16 @@
*/ */
// eslint-disable-next-line path-alias/no-relative // eslint-disable-next-line path-alias/no-relative
import { findByPropsLazy, waitFor } from "../webpack"; import { filters, mapMangledModuleLazy, waitFor } from "../webpack";
import type * as t from "./types/menu"; import type * as t from "./types/menu";
export let Menu = {} as t.Menu; export let Menu = {} as t.Menu;
waitFor(["MenuItem", "MenuSliderControl"], m => Menu = m); waitFor(["MenuItem", "MenuSliderControl"], m => Menu = m);
export const ContextMenuApi: t.ContextMenuApi = findByPropsLazy("closeContextMenu", "openContextMenu"); export const ContextMenuApi: t.ContextMenuApi = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN', {
closeContextMenu: filters.byCode("CONTEXT_MENU_CLOSE"),
openContextMenu: filters.byCode("renderLazy:"),
openContextMenuLazy: e => typeof e === "function" && e.toString().length < 100
});

View file

@ -4,12 +4,9 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { findByPropsLazy } from "@webpack"; import { findLazy } from "@webpack";
import * as t from "./types/settingsStores"; export const UserSettingsActionCreators = {
FrecencyUserSettingsActionCreators: findLazy(m => m.typeName?.endsWith(".FrecencyUserSettings")),
PreloadedUserSettingsActionCreators: findLazy(m => m.typeName?.endsWith(".PreloadedUserSettings")),
export const TextAndImagesSettingsStores = findByPropsLazy("MessageDisplayCompact") as Record<string, t.SettingsStore>; };
export const StatusSettingsStores = findByPropsLazy("ShowCurrentGame") as Record<string, t.SettingsStore>;
export const UserSettingsActionCreators = findByPropsLazy("PreloadedUserSettingsActionCreators");

View file

@ -19,7 +19,7 @@
import type * as Stores from "discord-types/stores"; import type * as Stores from "discord-types/stores";
// eslint-disable-next-line path-alias/no-relative // eslint-disable-next-line path-alias/no-relative
import { findByPropsLazy } from "../webpack"; import { findByCodeLazy, findByPropsLazy } from "../webpack";
import { waitForStore } from "./internal"; import { waitForStore } from "./internal";
import * as t from "./types/stores"; import * as t from "./types/stores";
@ -27,7 +27,7 @@ export const Flux: t.Flux = findByPropsLazy("connectStores");
export type GenericStore = t.FluxStore & Record<string, any>; export type GenericStore = t.FluxStore & Record<string, any>;
export const { DraftType }: { DraftType: typeof t.DraftType; } = findByPropsLazy("DraftType"); export const DraftType = findByPropsLazy("ChannelMessage", "SlashCommand");
export let MessageStore: Omit<Stores.MessageStore, "getMessages"> & { export let MessageStore: Omit<Stores.MessageStore, "getMessages"> & {
getMessages(chanId: string): any; getMessages(chanId: string): any;
@ -67,7 +67,7 @@ export let DraftStore: t.DraftStore;
* @example const user = useStateFromStores([UserStore], () => UserStore.getCurrentUser(), null, (old, current) => old.id === current.id); * @example const user = useStateFromStores([UserStore], () => UserStore.getCurrentUser(), null, (old, current) => old.id === current.id);
*/ */
// eslint-disable-next-line prefer-destructuring // eslint-disable-next-line prefer-destructuring
export const useStateFromStores: t.useStateFromStores = findByPropsLazy("useStateFromStores").useStateFromStores; export const useStateFromStores: t.useStateFromStores = findByCodeLazy("useStateFromStores");
waitForStore("DraftStore", s => DraftStore = s); waitForStore("DraftStore", s => DraftStore = s);
waitForStore("UserStore", s => UserStore = s); waitForStore("UserStore", s => UserStore = s);

View file

@ -39,6 +39,8 @@ export class FluxStore {
syncWith: GenericFunction; syncWith: GenericFunction;
waitFor: GenericFunction; waitFor: GenericFunction;
__getLocalVars(): Record<string, any>; __getLocalVars(): Record<string, any>;
static getAll(): FluxStore[];
} }
export class FluxEmitter { export class FluxEmitter {

View file

@ -168,17 +168,8 @@ export interface Clipboard {
export interface NavigationRouter { export interface NavigationRouter {
back(): void; back(): void;
forward(): void; forward(): void;
hasNavigated(): boolean;
getHistory(): {
action: string;
length: 50;
[key: string]: any;
};
transitionTo(path: string, ...args: unknown[]): void; transitionTo(path: string, ...args: unknown[]): void;
transitionToGuild(guildId: string, ...args: unknown[]): void; transitionToGuild(guildId: string, ...args: unknown[]): void;
replaceWith(...args: unknown[]): void;
getLastRouteChangeSource(): any;
getLastRouteChangeSourceLocationStack(): any;
} }
export interface IconUtils { export interface IconUtils {
@ -224,3 +215,8 @@ export interface IconUtils {
getApplicationIconSource: any; getApplicationIconSource: any;
getAnimatableSourceWithFallback: any; getAnimatableSourceWithFallback: any;
} }
export interface Constants {
Endpoints: Record<string, any>;
UserFlags: Record<string, number>;
}

View file

@ -16,10 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import type { Channel, User } from "discord-types/general"; import type { Channel } from "discord-types/general";
// eslint-disable-next-line path-alias/no-relative // eslint-disable-next-line path-alias/no-relative
import { _resolveReady, filters, findByCodeLazy, findByProps, findByPropsLazy, findLazy, proxyLazyWebpack, waitFor } from "../webpack"; import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "../webpack";
import type * as t from "./types/utils"; import type * as t from "./types/utils";
export let FluxDispatcher: t.FluxDispatcher; export let FluxDispatcher: t.FluxDispatcher;
@ -36,15 +36,14 @@ waitFor(["dispatch", "subscribe"], m => {
}); });
export let ComponentDispatch; export let ComponentDispatch;
waitFor(["ComponentDispatch", "ComponentDispatcher"], m => ComponentDispatch = m.ComponentDispatch); waitFor(["dispatchToLastSubscribed"], m => ComponentDispatch = m);
export const Constants: t.Constants = mapMangledModuleLazy('ME:"/users/@me"', {
export const Constants = findByPropsLazy("Endpoints"); Endpoints: filters.byProps("USER", "ME"),
UserFlags: filters.byProps("STAFF", "SPAMMER")
export const RestAPI: t.RestAPI = proxyLazyWebpack(() => {
const mod = findByProps("getAPIBaseURL");
return mod.HTTP ?? mod;
}); });
export const RestAPI: t.RestAPI = findLazy(m => typeof m === "object" && m.del && m.put);
export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYear"); export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYear");
export const hljs: typeof import("highlight.js") = findByPropsLazy("highlight", "registerLanguage"); export const hljs: typeof import("highlight.js") = findByPropsLazy("highlight", "registerLanguage");
@ -118,30 +117,39 @@ export function showToast(message: string, type = ToastType.MESSAGE) {
}); });
} }
export const UserUtils = findByPropsLazy("getUser", "fetchCurrentUser") as { getUser: (id: string) => Promise<User>; }; export const UserUtils = {
getUser: findByCodeLazy(".USER(")
};
export const UploadManager = findByPropsLazy("clearAll", "addFile"); export const UploadManager = findByPropsLazy("clearAll", "addFile");
export const UploadHandler = findByPropsLazy("showUploadFileSizeExceededError", "promptToUpload") as { export const UploadHandler = {
promptToUpload: (files: File[], channel: Channel, draftType: Number) => void; promptToUpload: findByCodeLazy(".ATTACHMENT_TOO_MANY_ERROR_TITLE,") as (files: File[], channel: Channel, draftType: Number) => void
}; };
export const ApplicationAssetUtils = findByPropsLazy("fetchAssetIds", "getAssetImage") as { export const ApplicationAssetUtils = findByPropsLazy("fetchAssetIds", "getAssetImage") as {
fetchAssetIds: (applicationId: string, e: string[]) => Promise<string[]>; fetchAssetIds: (applicationId: string, e: string[]) => Promise<string[]>;
}; };
export const Clipboard: t.Clipboard = findByPropsLazy("SUPPORTS_COPY", "copy"); export const Clipboard: t.Clipboard = mapMangledModuleLazy('queryCommandEnabled("copy")', {
copy: filters.byCode(".copy("),
SUPPORTS_COPY: e => typeof e === "boolean"
});
export const NavigationRouter: t.NavigationRouter = findByPropsLazy("transitionTo", "replaceWith", "transitionToGuild"); export const NavigationRouter: t.NavigationRouter = mapMangledModuleLazy("Transitioning to ", {
transitionTo: filters.byCode("transitionTo -"),
transitionToGuild: filters.byCode("transitionToGuild -"),
back: filters.byCode("goBack()"),
forward: filters.byCode("goForward()"),
});
export let SettingsRouter: any; export let SettingsRouter: any;
waitFor(["open", "saveAccountChanges"], m => SettingsRouter = m); waitFor(["open", "saveAccountChanges"], m => SettingsRouter = m);
export const { Permissions: PermissionsBits } = findLazy(m => typeof m.Permissions?.ADMINISTRATOR === "bigint") as { Permissions: t.PermissionsBits; }; export const PermissionsBits: t.PermissionsBits = findLazy(m => typeof m.ADMINISTRATOR === "bigint");
export const zustandCreate = findByCodeLazy("will be removed in v4"); export const zustandCreate = findByCodeLazy("will be removed in v4");
const persistFilter = filters.byCode("[zustand persist middleware]"); export const zustandPersist = findByCodeLazy("[zustand persist middleware]");
export const { persist: zustandPersist } = findLazy(m => m.persist && persistFilter(m.persist));
export const MessageActions = findByPropsLazy("editMessage", "sendMessage"); export const MessageActions = findByPropsLazy("editMessage", "sendMessage");
export const MessageCache = findByPropsLazy("clearCache", "_channelMessages"); export const MessageCache = findByPropsLazy("clearCache", "_channelMessages");

View file

@ -209,14 +209,33 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
// There are (at the time of writing) 11 modules exporting the window // There are (at the time of writing) 11 modules exporting the window
// Make these non enumerable to improve webpack search performance // Make these non enumerable to improve webpack search performance
if (require.c && (exports === window || exports?.default === window)) { if (require.c) {
Object.defineProperty(require.c, id, { let foundWindow = false;
value: require.c[id],
enumerable: false, if (exports === window) {
configurable: true, foundWindow = true;
writable: true } else if (typeof exports === "object") {
}); if (exports?.default === window) {
return; foundWindow = true;
} else {
for (const nested in exports) if (nested.length <= 3) {
if (exports[nested] === window) {
foundWindow = true;
}
}
}
}
if (foundWindow) {
Object.defineProperty(require.c, id, {
value: require.c[id],
enumerable: false,
configurable: true,
writable: true
});
return;
}
} }
for (const callback of moduleListeners) { for (const callback of moduleListeners) {
@ -232,9 +251,18 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
if (exports && filter(exports)) { if (exports && filter(exports)) {
subscriptions.delete(filter); subscriptions.delete(filter);
callback(exports, id); callback(exports, id);
} else if (exports.default && filter(exports.default)) { } else if (typeof exports === "object") {
subscriptions.delete(filter); if (exports.default && filter(exports.default)) {
callback(exports.default, id); subscriptions.delete(filter);
callback(exports.default, id);
} else {
for (const nested in exports) if (nested.length <= 3) {
if (exports[nested] && filter(exports[nested])) {
subscriptions.delete(filter);
callback(exports[nested], id);
}
}
}
} }
} catch (err) { } catch (err) {
logger.error("Error while firing callback for Webpack subscription:\n", err, filter, callback); logger.error("Error while firing callback for Webpack subscription:\n", err, filter, callback);

View file

@ -112,10 +112,20 @@ export const find = traceFunction("find", function find(filter: FilterFn, { isIn
return isWaitFor ? [mod.exports, key] : mod.exports; return isWaitFor ? [mod.exports, key] : mod.exports;
} }
if (typeof mod.exports !== "object") continue;
if (mod.exports.default && filter(mod.exports.default)) { if (mod.exports.default && filter(mod.exports.default)) {
const found = mod.exports.default; const found = mod.exports.default;
return isWaitFor ? [found, key] : found; return isWaitFor ? [found, key] : found;
} }
// the length check makes search about 20% faster
for (const nestedMod in mod.exports) if (nestedMod.length <= 3) {
const nested = mod.exports[nestedMod];
if (nested && filter(nested)) {
return isWaitFor ? [nested, key] : nested;
}
}
} }
if (!isIndirect) { if (!isIndirect) {
@ -136,9 +146,15 @@ export function findAll(filter: FilterFn) {
if (filter(mod.exports)) if (filter(mod.exports))
ret.push(mod.exports); ret.push(mod.exports);
else if (typeof mod.exports !== "object")
continue;
if (mod.exports.default && filter(mod.exports.default)) if (mod.exports.default && filter(mod.exports.default))
ret.push(mod.exports.default); ret.push(mod.exports.default);
else for (const nestedMod in mod.exports) if (nestedMod.length <= 3) {
const nested = mod.exports[nestedMod];
if (nested && filter(nested)) ret.push(nested);
}
} }
return ret; return ret;
@ -188,12 +204,26 @@ export const findBulk = traceFunction("findBulk", function findBulk(...filterFns
break; break;
} }
if (typeof mod.exports !== "object")
continue;
if (mod.exports.default && filter(mod.exports.default)) { if (mod.exports.default && filter(mod.exports.default)) {
results[j] = mod.exports.default; results[j] = mod.exports.default;
filters[j] = undefined; filters[j] = undefined;
if (++found === length) break outer; if (++found === length) break outer;
break; break;
} }
for (const nestedMod in mod.exports)
if (nestedMod.length <= 3) {
const nested = mod.exports[nestedMod];
if (nested && filter(nested)) {
results[j] = nested;
filters[j] = undefined;
if (++found === length) break outer;
continue outer;
}
}
} }
} }
@ -402,6 +432,60 @@ export function findExportedComponentLazy<T extends object = any>(...props: stri
}); });
} }
/**
* 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 The code to look for
* @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<S extends string>(code: string, mappers: Record<S, FilterFn>): Record<S, any> {
const exports = {} as Record<S, any>;
const id = findModuleId(code);
if (id === null)
return exports;
const mod = wreq(id as any);
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;
});
/**
* {@link mapMangledModule}, lazy.
* 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 The code to look for
* @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 function mapMangledModuleLazy<S extends string>(code: string, mappers: Record<S, FilterFn>): Record<S, any> {
return proxyLazy(() => mapMangledModule(code, mappers));
}
export const DefaultExtractAndLoadChunksRegex = /(?:(?:Promise\.all\(\[)?(\i\.e\("[^)]+?"\)[^\]]*?)(?:\]\))?|Promise\.resolve\(\))\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/; export const DefaultExtractAndLoadChunksRegex = /(?:(?:Promise\.all\(\[)?(\i\.e\("[^)]+?"\)[^\]]*?)(?:\]\))?|Promise\.resolve\(\))\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/;
export const ChunkIdsRegex = /\("([^"]+?)"\)/g; export const ChunkIdsRegex = /\("([^"]+?)"\)/g;