Merge branch 'dev' into immediate-finds

This commit is contained in:
Nuckyz 2024-05-22 01:01:58 -03:00
commit c9d9d53fb1
No known key found for this signature in database
GPG key ID: 440BF8296E1C4AD9
14 changed files with 250 additions and 51 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.8.4", "version": "1.8.5",
"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

@ -243,19 +243,27 @@ page.on("console", async e => {
} }
} }
if (isDebug) { async function getText() {
console.error(e.text());
} else if (level === "error") {
const text = await Promise.all(
e.args().map(async a => {
try { try {
return await Promise.all(
e.args().map(async a => {
return await maybeGetError(a) || await a.jsonValue(); return await maybeGetError(a) || await a.jsonValue();
} catch (e) {
return a.toString();
}
}) })
).then(a => a.join(" ").trim()); ).then(a => a.join(" ").trim());
} catch {
return e.text();
}
}
if (isDebug) {
const text = await getText();
console.error(text);
if (text.includes("A fatal error occurred:")) {
process.exit(1);
}
} else if (level === "error") {
const text = await getText();
if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of") && !text.includes("Webpack")) { if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of") && !text.includes("Webpack")) {
console.error("[Unexpected Error]", text); console.error("[Unexpected Error]", text);
@ -322,22 +330,31 @@ async function runtime(token: string) {
const validChunks = new Set<string>(); const validChunks = new Set<string>();
const invalidChunks = new Set<string>(); const invalidChunks = new Set<string>();
const deferredRequires = new Set<string>();
let chunksSearchingResolve: (value: void | PromiseLike<void>) => void; let chunksSearchingResolve: (value: void | PromiseLike<void>) => void;
const chunksSearchingDone = new Promise<void>(r => chunksSearchingResolve = r); const chunksSearchingDone = new Promise<void>(r => chunksSearchingResolve = r);
// True if resolved, false otherwise // True if resolved, false otherwise
const chunksSearchPromises = [] as Array<() => boolean>; const chunksSearchPromises = [] as Array<() => boolean>;
const lazyChunkRegex = canonicalizeMatch(/Promise\.all\((\[\i\.\i\(".+?"\).+?\])\).then\(\i\.bind\(\i,"(.+?)"\)\)/g);
const chunkIdsRegex = canonicalizeMatch(/\("(.+?)"\)/g); const LazyChunkRegex = canonicalizeMatch(/(?:Promise\.all\(\[(\i\.\i\("[^)]+?"\)[^\]]+?)\]\)|(\i\.\i\("[^)]+?"\)))\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/g);
async function searchAndLoadLazyChunks(factoryCode: string) { async function searchAndLoadLazyChunks(factoryCode: string) {
const lazyChunks = factoryCode.matchAll(lazyChunkRegex); const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>(); const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>();
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => { // Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
const chunkIds = Array.from(rawChunkIds.matchAll(chunkIdsRegex)).map(m => m[1]); // the chunk containing the component
if (chunkIds.length === 0) return; const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIdsArray, rawChunkIdsSingle, entryPoint]) => {
const rawChunkIds = rawChunkIdsArray ?? rawChunkIdsSingle;
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Vencord.Webpack.ChunkIdsRegex)).map(m => m[1]) : [];
if (chunkIds.length === 0) {
return;
}
let invalidChunkGroup = false; let invalidChunkGroup = false;
@ -373,6 +390,11 @@ async function runtime(token: string) {
// Requires the entry points for all valid chunk groups // Requires the entry points for all valid chunk groups
for (const [, entryPoint] of validChunkGroups) { for (const [, entryPoint] of validChunkGroups) {
try { try {
if (shouldForceDefer) {
deferredRequires.add(entryPoint);
continue;
}
if (wreq.m[entryPoint]) wreq(entryPoint as any); if (wreq.m[entryPoint]) wreq(entryPoint as any);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@ -435,6 +457,11 @@ async function runtime(token: string) {
await chunksSearchingDone; await chunksSearchingDone;
// Require deferred entry points
for (const deferredRequire of deferredRequires) {
wreq!(deferredRequire as any);
}
// All chunks Discord has mapped to asset files, even if they are not used anymore // All chunks Discord has mapped to asset files, even if they are not used anymore
const allChunks = [] as string[]; const allChunks = [] as string[];
@ -557,7 +584,6 @@ async function runtime(token: string) {
setTimeout(() => console.log("[PUPPETEER_TEST_DONE_SIGNAL]"), 1000); setTimeout(() => console.log("[PUPPETEER_TEST_DONE_SIGNAL]"), 1000);
} catch (e) { } catch (e) {
console.log("[PUP_DEBUG]", "A fatal error occurred:", e); console.log("[PUP_DEBUG]", "A fatal error occurred:", e);
process.exit(1);
} }
} }

View file

@ -56,7 +56,6 @@ export default definePlugin({
} }
] ]
}, },
// Discord Canary
{ {
find: "Messages.ACTIVITY_SETTINGS", find: "Messages.ACTIVITY_SETTINGS",
replacement: { replacement: {

View file

@ -1,6 +1,6 @@
# BetterRoleContext # BetterRoleContext
Adds options to copy role color and edit role when right clicking roles in the user profile Adds options to copy role color, edit role and view role icon when right clicking roles in the user profile
![](https://github.com/Vendicated/Vencord/assets/45497981/d1765e9e-7db2-4a3c-b110-139c59235326) ![](https://github.com/Vendicated/Vencord/assets/45497981/354220a4-09f3-4c5f-a28e-4b19ca775190)

View file

@ -4,9 +4,11 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { definePluginSettings } from "@api/Settings";
import { ImageIcon } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getCurrentGuild } from "@utils/discord"; import { getCurrentGuild, openImageModal } from "@utils/discord";
import definePlugin from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByProps } from "@webpack"; import { findByProps } from "@webpack";
import { Clipboard, GuildStore, Menu, PermissionStore, TextAndImagesSettingsStores } from "@webpack/common"; import { Clipboard, GuildStore, Menu, PermissionStore, TextAndImagesSettingsStores } from "@webpack/common";
@ -34,10 +36,34 @@ function AppearanceIcon() {
); );
} }
const settings = definePluginSettings({
roleIconFileFormat: {
type: OptionType.SELECT,
description: "File format to use when viewing role icons",
options: [
{
label: "png",
value: "png",
default: true
},
{
label: "webp",
value: "webp",
},
{
label: "jpg",
value: "jpg"
}
]
}
});
export default definePlugin({ export default definePlugin({
name: "BetterRoleContext", name: "BetterRoleContext",
description: "Adds options to copy role color / edit role 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], authors: [Devs.Ven, Devs.goodbee],
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
@ -63,6 +89,20 @@ export default definePlugin({
); );
} }
if (role.icon) {
children.push(
<Menu.MenuItem
id="vc-view-role-icon"
label="View Role Icon"
action={() => {
openImageModal(`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`);
}}
icon={ImageIcon}
/>
);
}
if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) { if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) {
children.push( children.push(
<Menu.MenuItem <Menu.MenuItem

View file

@ -0,0 +1,3 @@
.vc-fpt-preview * {
pointer-events: none;
}

View file

@ -17,13 +17,17 @@
*/ */
// This plugin is a port from Alyxia's Vendetta plugin // This plugin is a port from Alyxia's Vendetta plugin
import "./index.css";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { copyWithToast } from "@utils/misc"; import { classes, copyWithToast } from "@utils/misc";
import { useAwaiter } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { Button, Forms } from "@webpack/common"; import { extractAndLoadChunksLazy, findComponentByCode } from "@webpack";
import { Button, Flex, Forms, React, Text, UserProfileStore, UserStore, useState } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
import virtualMerge from "virtual-merge"; import virtualMerge from "virtual-merge";
@ -81,6 +85,34 @@ const settings = definePluginSettings({
} }
}); });
interface ColorPickerProps {
color: number | null;
label: React.ReactElement;
showEyeDropper?: boolean;
suggestedColors?: string[];
onChange(value: number | null): void;
}
// I can't be bothered to figure out the semantics of this component. The
// functions surely get some event argument sent to them and they likely aren't
// all required. If anyone who wants to use this component stumbles across this
// code, you'll have to do the research yourself.
interface ProfileModalProps {
user: User;
pendingThemeColors: [number, number];
onAvatarChange: () => void;
onBannerChange: () => void;
canUsePremiumCustomization: boolean;
hideExampleButton: boolean;
hideFakeActivity: boolean;
isTryItOutFlow: boolean;
}
const ColorPicker = findComponentByCode<ColorPickerProps>(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
const ProfileModal = findComponentByCode<ProfileModalProps>('"ProfileCustomizationPreview"');
const requireColorPicker = extractAndLoadChunksLazy(["USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON.format"], /createPromise:\(\)=>\i\.\i\("(.+?)"\).then\(\i\.bind\(\i,"(.+?)"\)\)/);
export default definePlugin({ export default definePlugin({
name: "FakeProfileThemes", name: "FakeProfileThemes",
description: "Allows profile theming by hiding the colors in your bio thanks to invisible 3y3 encoding", description: "Allows profile theming by hiding the colors in your bio thanks to invisible 3y3 encoding",
@ -101,21 +133,98 @@ export default definePlugin({
} }
} }
], ],
settingsAboutComponent: () => ( settingsAboutComponent: () => {
const existingColors = decode(
UserProfileStore.getUserProfile(UserStore.getCurrentUser().id).bio
) ?? [0, 0];
const [color1, setColor1] = useState(existingColors[0]);
const [color2, setColor2] = useState(existingColors[1]);
const [, , loadingColorPickerChunk] = useAwaiter(requireColorPicker);
return (
<Forms.FormSection> <Forms.FormSection>
<Forms.FormTitle tag="h3">Usage</Forms.FormTitle> <Forms.FormTitle tag="h3">Usage</Forms.FormTitle>
<Forms.FormText> <Forms.FormText>
After enabling this plugin, you will see custom colors in the profiles of other people using compatible plugins. <br /> After enabling this plugin, you will see custom colors in
the profiles of other people using compatible plugins.{" "}
<br />
To set your own colors: To set your own colors:
<ul> <ul>
<li> go to your profile settings</li> <li>
<li> choose your own colors in the Nitro preview</li> use the color pickers below to choose your colors
</li>
<li> click the "Copy 3y3" button</li> <li> click the "Copy 3y3" button</li>
<li> paste the invisible text anywhere in your bio</li> <li> paste the invisible text anywhere in your bio</li>
</ul><br /> </ul><br />
<b>Please note:</b> if you are using a theme which hides nitro ads, you should disable it temporarily to set colors. <Forms.FormDivider
className={classes(Margins.top8, Margins.bottom8)}
/>
<Forms.FormTitle tag="h3">Color pickers</Forms.FormTitle>
{!loadingColorPickerChunk && (
<Flex
direction={Flex.Direction.HORIZONTAL}
style={{ gap: "1rem" }}
>
<ColorPicker
color={color1}
label={
<Text
variant={"text-xs/normal"}
style={{ marginTop: "4px" }}
>
Primary
</Text>
}
onChange={(color: number) => {
setColor1(color);
}}
/>
<ColorPicker
color={color2}
label={
<Text
variant={"text-xs/normal"}
style={{ marginTop: "4px" }}
>
Accent
</Text>
}
onChange={(color: number) => {
setColor2(color);
}}
/>
<Button
onClick={() => {
const colorString = encode(color1, color2);
copyWithToast(colorString);
}}
color={Button.Colors.PRIMARY}
size={Button.Sizes.XLARGE}
>
Copy 3y3
</Button>
</Flex>
)}
<Forms.FormDivider
className={classes(Margins.top8, Margins.bottom8)}
/>
<Forms.FormTitle tag="h3">Preview</Forms.FormTitle>
<div className="vc-fpt-preview">
<ProfileModal
user={UserStore.getCurrentUser()}
pendingThemeColors={[color1, color2]}
onAvatarChange={() => { }}
onBannerChange={() => { }}
canUsePremiumCustomization={true}
hideExampleButton={true}
hideFakeActivity={true}
isTryItOutFlow={true}
/>
</div>
</Forms.FormText> </Forms.FormText>
</Forms.FormSection>), </Forms.FormSection>);
},
settings, settings,
colorDecodeHook(user: UserProfile) { colorDecodeHook(user: UserProfile) {
if (user) { if (user) {

View file

@ -376,6 +376,9 @@ export default definePlugin({
if (!messageLinkRegex.test(props.message.content)) if (!messageLinkRegex.test(props.message.content))
return null; return null;
// need to reset the regex because it's global
messageLinkRegex.lastIndex = 0;
return ( return (
<ErrorBoundary> <ErrorBoundary>
<MessageEmbedAccessory <MessageEmbedAccessory

View file

@ -8,7 +8,7 @@
.emoji, .emoji,
[data-type="sticker"], [data-type="sticker"],
iframe, iframe,
.messagelogger-deleted-attachment, .messagelogger-deleted-attachment:not([class*="hiddenAttachment_"]),
[class|="inlineMediaEmbed"] [class|="inlineMediaEmbed"]
) { ) {
filter: grayscale(1) !important; filter: grayscale(1) !important;

View file

@ -24,6 +24,7 @@ import { ChannelStore, FluxDispatcher as Dispatcher, MessageStore, PermissionsBi
import { Message } from "discord-types/general"; import { Message } from "discord-types/general";
const Kangaroo = findByProps("jumpToMessage"); const Kangaroo = findByProps("jumpToMessage");
const RelationshipStore = findByProps("getRelationships", "isBlocked");
const isMac = navigator.platform.includes("Mac"); // bruh const isMac = navigator.platform.includes("Mac"); // bruh
let replyIdx = -1; let replyIdx = -1;
@ -139,6 +140,10 @@ function getNextMessage(isUp: boolean, isReply: boolean) {
messages = messages.filter(m => m.author.id === meId); messages = messages.filter(m => m.author.id === meId);
} }
if (Vencord.Plugins.isPluginEnabled("NoBlockedMessages")) {
messages = messages.filter(m => !RelationshipStore.isBlocked(m.author.id));
}
const mutate = (i: number) => isUp const mutate = (i: number) => isUp
? Math.min(messages.length - 1, i + 1) ? Math.min(messages.length - 1, i + 1)
: Math.max(-1, i - 1); : Math.max(-1, i - 1);

View file

@ -80,11 +80,19 @@ export default definePlugin({
} }
}, },
{ {
find: "auto_removed:", find: "prod_discoverable_guilds",
predicate: () => settings.store.disableDiscoveryFilters, predicate: () => settings.store.disableDiscoveryFilters,
replacement: { replacement: {
match: /filters:\i\.join\(" AND "\),facets:\[/, match: /\{"auto_removed:.*?\}/,
replace: "facets:[" replace: "{}"
}
},
{
find: "MINIMUM_MEMBER_COUNT:",
predicate: () => settings.store.disableDiscoveryFilters,
replacement: {
match: /MINIMUM_MEMBER_COUNT:function\(\)\{return \i}/,
replace: "MINIMUM_MEMBER_COUNT:() => \">0\""
} }
}, },
{ {

View file

@ -28,7 +28,7 @@ export default definePlugin({
patches: [{ patches: [{
find: "Messages.ACTIVITY_SETTINGS", find: "Messages.ACTIVITY_SETTINGS",
replacement: { replacement: {
match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+}\))/, match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+}\)}\))/,
replace: (_, commaOrSemi, settings, elements) => "" + replace: (_, commaOrSemi, settings, elements) => "" +
`${commaOrSemi}${settings}?.[0]==="CHANGELOG"` + `${commaOrSemi}${settings}?.[0]==="CHANGELOG"` +
`&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})` `&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`

View file

@ -498,6 +498,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "ScattrdBlade", name: "ScattrdBlade",
id: 678007540608532491n id: 678007540608532491n
}, },
goodbee: {
name: "goodbee",
id: 658968552606400512n
},
Moxxie: { Moxxie: {
name: "Moxxie", name: "Moxxie",
id: 712653921692155965n, id: 712653921692155965n,

View file

@ -654,7 +654,8 @@ export const findAll = deprecatedRedirect("findAll", "cacheFindAll", cacheFindAl
*/ */
export const findBulk = deprecatedRedirect("findBulk", "cacheFindBulk", cacheFindBulk); export const findBulk = deprecatedRedirect("findBulk", "cacheFindBulk", cacheFindBulk);
export const DefaultExtractAndLoadChunksRegex = /(?:Promise\.all\((\[\i\.\i\(".+?"\).+?\])\)|Promise\.resolve\(\)).then\(\i\.bind\(\i,"(.+?)"\)\)/; export const DefaultExtractAndLoadChunksRegex = /(?:Promise\.all\(\[(\i\.\i\("[^)]+?"\)[^\]]+?)\]\)|(\i\.\i\("[^)]+?"\))|Promise\.resolve\(\))\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/;
export const ChunkIdsRegex = /\("(.+?)"\)/g;
/** /**
* Extract and load chunks using their entry point. * Extract and load chunks using their entry point.
@ -683,7 +684,7 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
return; return;
} }
const [, rawChunkIds, entryPointId] = match; const [, rawChunkIdsArray, rawChunkIdsSingle, entryPointId] = match;
if (Number.isNaN(Number(entryPointId))) { if (Number.isNaN(Number(entryPointId))) {
const err = new Error("extractAndLoadChunks: Matcher didn't return a capturing group with the chunk ids array, or the entry point id returned as the second group wasn't a number"); const err = new Error("extractAndLoadChunks: Matcher didn't return a capturing group with the chunk ids array, or the entry point id returned as the second group wasn't a number");
logger.warn(err, "Code:", code, "Matcher:", matcher); logger.warn(err, "Code:", code, "Matcher:", matcher);
@ -695,8 +696,9 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
return; return;
} }
const rawChunkIds = rawChunkIdsArray ?? rawChunkIdsSingle;
if (rawChunkIds) { if (rawChunkIds) {
const chunkIds = Array.from(rawChunkIds.matchAll(/\("(.+?)"\)/g)).map((m: any) => m[1]); const chunkIds = Array.from(rawChunkIds.matchAll(ChunkIdsRegex)).map((m: any) => m[1]);
await Promise.all(chunkIds.map(id => wreq.e(id))); await Promise.all(chunkIds.map(id => wreq.e(id)));
} }