Merge branch 'refs/heads/main' into plugin/memberListActivities

This commit is contained in:
D3SOX 2024-04-15 00:16:39 +02:00
commit 0753b51104
No known key found for this signature in database
GPG key ID: 39EC1673FC37B048
30 changed files with 349 additions and 103 deletions

View file

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

@ -315,7 +315,6 @@ export default function PluginSettings() {
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} className={Margins.bottom20} /> <TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} className={Margins.bottom20} />
<div className={InputStyles.inputWrapper}> <div className={InputStyles.inputWrapper}>
<Select <Select
className={InputStyles.inputDefault}
options={[ options={[
{ label: "Show All", value: SearchStatus.ALL, default: true }, { label: "Show All", value: SearchStatus.ALL, default: true },
{ label: "Show Enabled", value: SearchStatus.ENABLED }, { label: "Show Enabled", value: SearchStatus.ENABLED },

View file

@ -28,7 +28,7 @@ const VENCORD_SRC_DIR = join(__dirname, "..");
const execFile = promisify(cpExecFile); const execFile = promisify(cpExecFile);
const isFlatpak = process.platform === "linux" && Boolean(process.env.FLATPAK_ID?.includes("discordapp") || process.env.FLATPAK_ID?.includes("Discord")); const isFlatpak = process.platform === "linux" && !!process.env.FLATPAK_ID;
if (process.platform === "darwin") process.env.PATH = `/usr/local/bin:${process.env.PATH}`; if (process.platform === "darwin") process.env.PATH = `/usr/local/bin:${process.env.PATH}`;
@ -60,7 +60,8 @@ async function calculateGitChanges() {
return commits ? commits.split("\n").map(line => { return commits ? commits.split("\n").map(line => {
const [author, hash, ...rest] = line.split("/"); const [author, hash, ...rest] = line.split("/");
return { return {
hash, author, message: rest.join("/") hash, author,
message: rest.join("/").split("\n")[0]
}; };
}) : []; }) : [];
} }

View file

@ -53,7 +53,7 @@ async function calculateGitChanges() {
// github api only sends the long sha // github api only sends the long sha
hash: c.sha.slice(0, 7), hash: c.sha.slice(0, 7),
author: c.author.login, author: c.author.login,
message: c.commit.message.substring(c.commit.message.indexOf("\n") + 1) message: c.commit.message.split("\n")[0]
})); }));
} }

View file

@ -54,9 +54,9 @@ const StickerExt = [, "png", "png", "json", "gif"] as const;
function getUrl(data: Data) { function getUrl(data: Data) {
if (data.t === "Emoji") if (data.t === "Emoji")
return `${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/emojis/${data.id}.${data.isAnimated ? "gif" : "png"}`; return `${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/emojis/${data.id}.${data.isAnimated ? "gif" : "png"}?size=4096&lossless=true`;
return `${window.GLOBAL_ENV.MEDIA_PROXY_ENDPOINT}/stickers/${data.id}.${StickerExt[data.format_type]}`; return `${window.GLOBAL_ENV.MEDIA_PROXY_ENDPOINT}/stickers/${data.id}.${StickerExt[data.format_type]}?size=4096&lossless=true`;
} }
async function fetchSticker(id: string) { async function fetchSticker(id: string) {
@ -130,7 +130,8 @@ function getGuildCandidates(data: Data) {
let count = 0; let count = 0;
for (const emoji of emojis) for (const emoji of emojis)
if (emoji.animated === isAnimated) count++; if (emoji.animated === isAnimated && !emoji.managed)
count++;
return count < emojiSlots; return count < emojiSlots;
}).sort((a, b) => a.name.localeCompare(b.name)); }).sort((a, b) => a.name.localeCompare(b.name));
} }

View file

@ -25,6 +25,7 @@ import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack"; import { findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack";
import { Alerts, ChannelStore, EmojiStore, FluxDispatcher, Forms, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common"; import { Alerts, ChannelStore, EmojiStore, FluxDispatcher, Forms, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
import type { CustomEmoji } from "@webpack/types";
import type { Message } from "discord-types/general"; import type { Message } from "discord-types/general";
import { applyPalette, GIFEncoder, quantize } from "gifenc"; import { applyPalette, GIFEncoder, quantize } from "gifenc";
import type { ReactElement, ReactNode } from "react"; import type { ReactElement, ReactNode } from "react";
@ -784,6 +785,16 @@ export default definePlugin({
UploadHandler.promptToUpload([file], ChannelStore.getChannel(channelId), DRAFT_TYPE); UploadHandler.promptToUpload([file], ChannelStore.getChannel(channelId), DRAFT_TYPE);
}, },
canUseEmote(e: CustomEmoji, channelId: string) {
if (e.require_colons === false) return true;
if (e.available === false) return false;
if (this.canUseEmotes)
return e.guildId === this.guildId || hasExternalEmojiPerms(channelId);
else
return !e.animated && e.guildId === this.guildId;
},
start() { start() {
const s = settings.store; const s = settings.store;
@ -882,12 +893,8 @@ export default definePlugin({
} }
if (s.enableEmojiBypass) { if (s.enableEmojiBypass) {
const canUseEmotes = this.canUseEmotes && hasExternalEmojiPerms(channelId);
for (const emoji of messageObj.validNonShortcutEmojis) { for (const emoji of messageObj.validNonShortcutEmojis) {
if (!emoji.require_colons) continue; if (this.canUseEmote(emoji, channelId)) continue;
if (emoji.available !== false && canUseEmotes) continue;
if (emoji.guildId === guildId && !emoji.animated) continue;
hasBypass = true; hasBypass = true;
@ -917,18 +924,12 @@ export default definePlugin({
this.preEdit = addPreEditListener(async (channelId, __, messageObj) => { this.preEdit = addPreEditListener(async (channelId, __, messageObj) => {
if (!s.enableEmojiBypass) return; if (!s.enableEmojiBypass) return;
const { guildId } = this;
let hasBypass = false; let hasBypass = false;
const canUseEmotes = this.canUseEmotes && hasExternalEmojiPerms(channelId);
messageObj.content = messageObj.content.replace(/(?<!\\)<a?:(?:\w+):(\d+)>/ig, (emojiStr, emojiId, offset, origStr) => { messageObj.content = messageObj.content.replace(/(?<!\\)<a?:(?:\w+):(\d+)>/ig, (emojiStr, emojiId, offset, origStr) => {
const emoji = EmojiStore.getCustomEmojiById(emojiId); const emoji = EmojiStore.getCustomEmojiById(emojiId);
if (emoji == null) return emojiStr; if (emoji == null) return emojiStr;
if (!emoji.require_colons) return emojiStr; if (this.canUseEmote(emoji, channelId)) return emojiStr;
if (emoji.available !== false && canUseEmotes) return emojiStr;
if (emoji.guildId === guildId && !emoji.animated) return emojiStr;
hasBypass = true; hasBypass = true;

View file

@ -6,6 +6,7 @@
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getCurrentChannel } from "@utils/discord";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { React, RelationshipStore } from "@webpack/common"; import { React, RelationshipStore } from "@webpack/common";
@ -49,6 +50,18 @@ export default definePlugin({
</Heading> </Heading>
<div className={container.memberSinceContainer}> <div className={container.memberSinceContainer}>
{!!getCurrentChannel()?.guild_id && (
<svg
aria-hidden="true"
width="16"
height="16"
viewBox="0 0 24 24"
fill="var(--interactive-normal)"
>
<path d="M13 10a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z" />
<path d="M3 5v-.75C3 3.56 3.56 3 4.25 3s1.24.56 1.33 1.25C6.12 8.65 9.46 12 13 12h1a8 8 0 0 1 8 8 2 2 0 0 1-2 2 .21.21 0 0 1-.2-.15 7.65 7.65 0 0 0-1.32-2.3c-.15-.2-.42-.06-.39.17l.25 2c.02.15-.1.28-.25.28H9a2 2 0 0 1-2-2v-2.22c0-1.57-.67-3.05-1.53-4.37A15.85 15.85 0 0 1 3 5Z" />
</svg>
)}
<Text variant="text-sm/normal" className={clydeMoreInfo.body}> <Text variant="text-sm/normal" className={clydeMoreInfo.body}>
{getCreatedAtDate(friendsSince, locale.getLocale())} {getCreatedAtDate(friendsSince, locale.getLocale())}
</Text> </Text>

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 { addAccessory } from "@api/MessageAccessories"; import { addAccessory, removeAccessory } from "@api/MessageAccessories";
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.js"; import { Devs } from "@utils/constants.js";
@ -389,4 +389,8 @@ export default definePlugin({
); );
}, 4 /* just above rich embeds */); }, 4 /* just above rich embeds */);
}, },
stop() {
removeAccessory("messageLinkEmbed");
}
}); });

View file

@ -45,7 +45,7 @@ export default definePlugin({
patches: [ patches: [
{ {
find: ".Messages.USER_PROFILE_MODAL", // Note: the module is lazy-loaded find: ".Messages.MUTUAL_GUILDS_WITH_END_COUNT", // Note: the module is lazy-loaded
replacement: { replacement: {
match: /(?<=\.tabBarItem.{0,50}MUTUAL_GUILDS.+?}\),)(?=.+?(\(0,\i\.jsxs?\)\(.{0,100}id:))/, match: /(?<=\.tabBarItem.{0,50}MUTUAL_GUILDS.+?}\),)(?=.+?(\(0,\i\.jsxs?\)\(.{0,100}id:))/,
replace: '(arguments[0].user.bot||arguments[0].isCurrentUser)?null:$1"MUTUAL_GDMS",children:"Mutual Groups"}),' replace: '(arguments[0].user.bot||arguments[0].isCurrentUser)?null:$1"MUTUAL_GDMS",children:"Mutual Groups"}),'

View file

@ -11,21 +11,15 @@ import definePlugin, { OptionType } from "@utils/types";
import style from "./styles.css?managed"; import style from "./styles.css?managed";
const MAX_WIDTH = 550;
const MAX_HEIGHT = 350;
const settings = definePluginSettings({ const settings = definePluginSettings({
inlineVideo: { inlineVideo: {
description: "Play videos without carousel modal", description: "Play videos without carousel modal",
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
default: true, default: true,
restartNeeded: true restartNeeded: true
},
mediaLayoutType: {
description: "Choose media layout type",
type: OptionType.SELECT,
restartNeeded: true,
options: [
{ label: "STATIC, render loading image but image isn't resposive, no problem unless discord window width is too small", value: "STATIC", default: true },
{ label: "RESPONSIVE, image is responsive but not render loading image, cause messages shift when loaded", value: "RESPONSIVE" },
]
} }
}); });
@ -42,7 +36,7 @@ export default definePlugin({
find: ".oneByTwoLayoutThreeGrid", find: ".oneByTwoLayoutThreeGrid",
replacement: [{ replacement: [{
match: /mediaLayoutType:\i\.\i\.MOSAIC/, match: /mediaLayoutType:\i\.\i\.MOSAIC/,
replace: "mediaLayoutType:$self.mediaLayoutType()", replace: "mediaLayoutType:'RESPONSIVE'",
}, },
{ {
match: /null!==\(\i=\i\.get\(\i\)\)&&void 0!==\i\?\i:"INVALID"/, match: /null!==\(\i=\i\.get\(\i\)\)&&void 0!==\i\?\i:"INVALID"/,
@ -59,15 +53,43 @@ export default definePlugin({
}, },
{ {
find: "Messages.REMOVE_ATTACHMENT_TOOLTIP_TEXT", find: "Messages.REMOVE_ATTACHMENT_TOOLTIP_TEXT",
replacement: { replacement: [{
match: /\i===\i\.\i\.MOSAIC/, match: /\i===\i\.\i\.MOSAIC/,
replace: "true" replace: "true"
},
{
match: /\i!==\i\.\i\.MOSAIC/,
replace: "false"
}]
},
{
find: ".messageAttachment,",
replacement: {
match: /\{width:\i,height:\i\}=(\i).*?(?=className:\i\(\)\(\i\.messageAttachment,)/,
replace: "$&style:$self.style($1),"
} }
} }
], ],
mediaLayoutType() { style({ width, height }) {
return settings.store.mediaLayoutType; if (!width || !height) return {};
if (width > MAX_WIDTH || height > MAX_HEIGHT) {
if (width / height > MAX_WIDTH / MAX_HEIGHT) {
height = Math.ceil(MAX_WIDTH / (width / height));
width = MAX_WIDTH;
} else {
width = Math.ceil(MAX_HEIGHT * (width / height));
height = MAX_HEIGHT;
}
}
return {
maxWidth: width,
width: "100%",
aspectRatio: `${width} / ${height}`
};
}, },
start() { start() {

View file

@ -1,3 +1,8 @@
[class^="nonMediaAttachmentsContainer_"] [class*="messageAttachment_"] { [class^="nonMediaAttachmentsContainer_"] [class*="messageAttachment_"] {
position: relative; position: relative;
} }
[class^="nonMediaAttachmentsContainer_"],
[class^="nonMediaAttachmentItem_"]:has([class^="messageAttachment_"][style^="max-width"]) {
width: 100%;
}

View file

@ -9,7 +9,7 @@ import { Settings } from "@api/Settings";
import { UserStore } from "@webpack/common"; import { UserStore } from "@webpack/common";
import { DEFAULT_COLOR } from "./constants"; import { DEFAULT_COLOR } from "./constants";
import { forceUpdate } from "./index"; import { forceUpdate, PinOrder, PrivateChannelSortStore, settings } from "./index";
export interface Category { export interface Category {
id: string; id: string;
@ -106,7 +106,12 @@ export function categoryLen() {
} }
export function getAllUncollapsedChannels() { export function getAllUncollapsedChannels() {
return categories.filter(c => !c.collapsed).map(c => c.channels).flat(); if (settings.store.pinOrder === PinOrder.LastMessage) {
const sortedChannels = PrivateChannelSortStore.getPrivateChannelIds();
return categories.filter(c => !c.collapsed).flatMap(c => sortedChannels.filter(channel => c.channels.includes(channel)));
}
return categories.filter(c => !c.collapsed).flatMap(c => c.channels);
} }
export function getSections() { export function getSections() {

View file

@ -29,7 +29,7 @@ interface ChannelComponentProps {
const headerClasses = findByPropsLazy("privateChannelsHeaderContainer"); const headerClasses = findByPropsLazy("privateChannelsHeaderContainer");
const PrivateChannelSortStore = findStoreLazy("PrivateChannelSortStore") as { getPrivateChannelIds: () => string[]; }; export const PrivateChannelSortStore = findStoreLazy("PrivateChannelSortStore") as { getPrivateChannelIds: () => string[]; };
export let instance: any; export let instance: any;
export const forceUpdate = () => instance?.props?._forceUpdate?.(); export const forceUpdate = () => instance?.props?._forceUpdate?.();
@ -236,7 +236,7 @@ export default definePlugin({
const category = categories[categoryIndex - 1]; const category = categories[categoryIndex - 1];
if (!category) return false; if (!category) return false;
return category.collapsed && this.instance.props.selectedChannelId !== category.channels[channelIndex]; return category.collapsed && this.instance.props.selectedChannelId !== this.getCategoryChannels(category)[channelIndex];
}, },
getScrollOffset(channelId: string, rowHeight: number, padding: number, preRenderedChildren: number, originalOffset: number) { getScrollOffset(channelId: string, rowHeight: number, padding: number, preRenderedChildren: number, originalOffset: number) {

View file

@ -36,25 +36,24 @@ export default definePlugin({
authors: [Devs.Tyman, Devs.TheKodeToad, Devs.Ven], authors: [Devs.Tyman, Devs.TheKodeToad, Devs.Ven],
description: "Adds pronouns to user messages using pronoundb", description: "Adds pronouns to user messages using pronoundb",
patches: [ patches: [
// Add next to username (compact mode)
{ {
find: "showCommunicationDisabledStyles", find: "showCommunicationDisabledStyles",
replacement: { replacement: [
match: /("span",{id:\i,className:\i,children:\i}\))/, // Add next to username (compact mode)
replace: "$1, $self.CompactPronounsChatComponentWrapper(arguments[0])" {
} match: /("span",{id:\i,className:\i,children:\i}\))/,
}, replace: "$1, $self.CompactPronounsChatComponentWrapper(arguments[0])"
// Patch the chat timestamp element (normal mode) },
{ // Patch the chat timestamp element (normal mode)
find: "showCommunicationDisabledStyles", {
replacement: { match: /(?<=return\s*\(0,\i\.jsxs?\)\(.+!\i&&)(\(0,\i.jsxs?\)\(.+?\{.+?\}\))/,
match: /(?<=return\s*\(0,\i\.jsxs?\)\(.+!\i&&)(\(0,\i.jsxs?\)\(.+?\{.+?\}\))/, replace: "[$1, $self.PronounsChatComponentWrapper(arguments[0])]"
replace: "[$1, $self.PronounsChatComponentWrapper(arguments[0])]" }
} ]
}, },
// Patch the profile popout username header to use our pronoun hook instead of Discord's pronouns // Patch the profile popout username header to use our pronoun hook instead of Discord's pronouns
{ {
find: ".userTagNoNickname", find: ".pronouns,children",
replacement: [ replacement: [
{ {
match: /{user:(\i),[^}]*,pronouns:(\i),[^}]*}=\i;/, match: /{user:(\i),[^}]*,pronouns:(\i),[^}]*}=\i;/,

View file

@ -18,9 +18,68 @@
import { findGroupChildrenByChildId } from "@api/ContextMenu"; import { findGroupChildrenByChildId } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
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 { Menu } from "@webpack/common"; 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({ const settings = definePluginSettings({
forceServerHome: { forceServerHome: {
@ -30,12 +89,6 @@ const settings = definePluginSettings({
} }
}); });
function useForceServerHome() {
const { forceServerHome } = settings.use(["forceServerHome"]);
return forceServerHome;
}
export default definePlugin({ export default definePlugin({
name: "ResurrectHome", 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 the Server Guide.", 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 the Server Guide.",
@ -89,17 +142,40 @@ export default definePlugin({
{ {
find: "61eef9_2", find: "61eef9_2",
replacement: { replacement: {
match: /(?<=getMutableGuildChannelsForGuild\(\i\)\);)(?=if\(null==\i\|\|)/, match: /getMutableGuildChannelsForGuild\(\i\);return\(0,\i\.useStateFromStores\).+?\]\)(?=}function)/,
replace: "if($self.useForceServerHome())return false;" 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: "badge:$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, useForceServerHome,
useDisableViewServerHome,
contextMenus: { contextMenus: {
"guild-context"(children, props) { "guild-context"(children, props) {
const forceServerHome = useForceServerHome(); const { forceServerHome } = settings.use(["forceServerHome"]);
if (!props?.guild) return; if (!props?.guild) return;

View file

@ -71,7 +71,7 @@ export const settings = definePluginSettings({
</Button> </Button>
<Button onClick={async () => { <Button onClick={async () => {
let url = "https://reviewdb.mantikafasi.dev/"; let url = "https://reviewdb.mantikafasi.dev";
const token = await getToken(); const token = await getToken();
if (token) if (token)
url += "/api/redirect?token=" + encodeURIComponent(token); url += "/api/redirect?token=" + encodeURIComponent(token);

View file

@ -452,7 +452,7 @@ export default definePlugin({
{ {
// Filter hidden channels from GuildChannelStore.getChannels unless told otherwise // Filter hidden channels from GuildChannelStore.getChannels unless told otherwise
match: /(?<=getChannels\(\i)(\){.+?)return (.+?)}/, match: /(?<=getChannels\(\i)(\){.+?)return (.+?)}/,
replace: (_, rest, channels) => `,shouldIncludeHidden${rest}return $self.resolveGuildChannels(${channels},shouldIncludeHidden??false);}` replace: (_, rest, channels) => `,shouldIncludeHidden${rest}return $self.resolveGuildChannels(${channels},shouldIncludeHidden??arguments[0]==="@favorites");}`
} }
] ]
}, },

View file

@ -49,7 +49,7 @@ export default definePlugin({
{ {
find: ".useCanSeeRemixBadge)", find: ".useCanSeeRemixBadge)",
replacement: { replacement: {
match: /(?<=onContextMenu:\i,children:).*?\}/, match: /(?<=onContextMenu:\i,children:).*?\)}/,
replace: "$self.renderUsername(arguments[0])}" replace: "$self.renderUsername(arguments[0])}"
} }
}, },

View file

@ -18,7 +18,6 @@
import "./spotifyStyles.css"; import "./spotifyStyles.css";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons"; import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons";
import { debounce } from "@shared/debounce"; import { debounce } from "@shared/debounce";
@ -376,17 +375,10 @@ export function Player() {
} as React.CSSProperties; } as React.CSSProperties;
return ( return (
<ErrorBoundary fallback={() => ( <div id={cl("player")} style={exportTrackImageStyle}>
<div className="vc-spotify-fallback"> <Info track={track} />
<p>Failed to render Spotify Modal :(</p> <SeekBar />
<p >Check the console for errors</p> <Controls />
</div> </div>
)}>
<div id={cl("player")} style={exportTrackImageStyle}>
<Info track={track} />
<SeekBar />
<Controls />
</div>
</ErrorBoundary>
); );
} }

View file

@ -18,6 +18,7 @@
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 { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
@ -49,10 +50,10 @@ export default definePlugin({
{ {
find: "showTaglessAccountPanel:", find: "showTaglessAccountPanel:",
replacement: { replacement: {
// return React.createElement(AccountPanel, { ..., showTaglessAccountPanel: blah }) // react.jsx)(AccountPanel, { ..., showTaglessAccountPanel: blah })
match: /return ?(.{0,30}\(.{1,3},\{[^}]+?,showTaglessAccountPanel:.+?\}\))/, match: /(?<=\i\.jsxs?\)\()(\i),{(?=[^}]*?showTaglessAccountPanel:)/,
// return [Player, Panel] // react.jsx(WrapperComponent, { VencordOriginal: AccountPanel, ...
replace: "return [$self.renderPlayer(),$1]" replace: "$self.PanelWrapper,{VencordOriginal:$1,"
} }
}, },
{ {
@ -78,6 +79,25 @@ export default definePlugin({
} }
} }
], ],
start: () => toggleHoverControls(Settings.plugins.SpotifyControls.hoverControls), start: () => toggleHoverControls(Settings.plugins.SpotifyControls.hoverControls),
renderPlayer: () => <Player />
PanelWrapper({ VencordOriginal, ...props }) {
return (
<>
<ErrorBoundary
fallback={() => (
<div className="vc-spotify-fallback">
<p>Failed to render Spotify Modal :(</p>
<p >Check the console for errors</p>
</div>
)}
>
<Player />
</ErrorBoundary>
<VencordOriginal {...props} />
</>
);
}
}); });

View file

@ -17,9 +17,10 @@
*/ */
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { LazyComponent } from "@utils/react";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import StartupTimingPage from "./StartupTimingPage";
export default definePlugin({ export default definePlugin({
name: "StartupTimings", name: "StartupTimings",
description: "Adds Startup Timings to the Settings menu", description: "Adds Startup Timings to the Settings menu",
@ -31,5 +32,5 @@ export default definePlugin({
replace: '{section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage},$&' replace: '{section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage},$&'
} }
}], }],
StartupTimingPage: LazyComponent(() => require("./StartupTimingPage").default) StartupTimingPage
}); });

View file

@ -16,20 +16,28 @@
* 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 "./style.css";
import { definePluginSettings, Settings } from "@api/Settings"; import { definePluginSettings, Settings } from "@api/Settings";
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 { findExportedComponentLazy, findStoreLazy } from "@webpack"; import { findComponentByCodeLazy, findExportedComponentLazy, findStoreLazy } from "@webpack";
import { ChannelStore, GuildMemberStore, i18n, RelationshipStore, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; import { ChannelStore, GuildMemberStore, i18n, RelationshipStore, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
import { buildSeveralUsers } from "../typingTweaks"; import { buildSeveralUsers } from "../typingTweaks";
const ThreeDots = findExportedComponentLazy("Dots", "AnimatedDots"); const ThreeDots = findExportedComponentLazy("Dots", "AnimatedDots");
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
const TypingStore = findStoreLazy("TypingStore"); const TypingStore = findStoreLazy("TypingStore");
const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore"); const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore");
const enum IndicatorMode {
Dots = 1 << 0,
Avatars = 1 << 1
}
function getDisplayName(guildId: string, userId: string) { function getDisplayName(guildId: string, userId: string) {
const user = UserStore.getUser(userId); const user = UserStore.getUser(userId);
return GuildMemberStore.getNick(guildId, userId) ?? (user as any).globalName ?? user.username; return GuildMemberStore.getNick(guildId, userId) ?? (user as any).globalName ?? user.username;
@ -90,11 +98,24 @@ function TypingIndicator({ channelId }: { channelId: string; }) {
return ( return (
<Tooltip text={tooltipText!}> <Tooltip text={tooltipText!}>
{props => ( {props => (
<div <div className="vc-typing-indicator" {...props}>
{...props} {((settings.store.indicatorMode & IndicatorMode.Avatars) === IndicatorMode.Avatars) && (
style={{ marginLeft: 6, height: 16, display: "flex", alignItems: "center", zIndex: 0, cursor: "pointer" }} <UserSummaryItem
> users={typingUsersArray.map(id => UserStore.getUser(id))}
<ThreeDots dotRadius={3} themed={true} /> guildId={guildId}
renderIcon={false}
max={3}
showDefaultAvatarsForNullUsers
showUserPopout
size={16}
className="vc-typing-indicator-avatars"
/>
)}
{((settings.store.indicatorMode & IndicatorMode.Dots) === IndicatorMode.Dots) && (
<div className="vc-typing-indicator-dots">
<ThreeDots dotRadius={3} themed={true} />
</div>
)}
</div> </div>
)} )}
</Tooltip> </Tooltip>
@ -119,13 +140,22 @@ const settings = definePluginSettings({
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Whether to show the typing indicator for blocked users.", description: "Whether to show the typing indicator for blocked users.",
default: false default: false
},
indicatorMode: {
type: OptionType.SELECT,
description: "How should the indicator be displayed?",
options: [
{ label: "Avatars and animated dots", value: IndicatorMode.Dots | IndicatorMode.Avatars, default: true },
{ label: "Animated dots", value: IndicatorMode.Dots },
{ label: "Avatars", value: IndicatorMode.Avatars },
],
} }
}); });
export default definePlugin({ export default definePlugin({
name: "TypingIndicator", name: "TypingIndicator",
description: "Adds an indicator if someone is typing on a channel.", description: "Adds an indicator if someone is typing on a channel.",
authors: [Devs.Nuckyz, Devs.fawn], authors: [Devs.Nuckyz, Devs.fawn, Devs.Sqaaakoi],
settings, settings,
patches: [ patches: [

View file

@ -0,0 +1,18 @@
.vc-typing-indicator {
display: flex;
align-items: center;
height: 20px;
}
.vc-typing-indicator-avatars {
margin-left: 6px;
}
.vc-typing-indicator-dots {
margin-left: 6px;
height: 16px;
display: flex;
align-items: center;
z-index: 0;
cursor: pointer;
}

View file

@ -0,0 +1,5 @@
# UnlockedAvatarZoom
Allows you to zoom in further in the image crop tool when changing your avatar
![](https://raw.githubusercontent.com/Vencord/plugin-assets/main/UnlockedAvatarZoom/demo.avif)

View file

@ -0,0 +1,35 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { definePluginSettings } from "@api/Settings";
import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
const settings = definePluginSettings({
zoomMultiplier: {
type: OptionType.SLIDER,
description: "Zoom multiplier",
markers: makeRange(2, 16),
default: 4,
},
});
export default definePlugin({
name: "UnlockedAvatarZoom",
description: "Allows you to zoom in further in the image crop tool when changing your avatar",
authors: [Devs.nakoyasha],
settings,
patches: [
{
find: ".Messages.AVATAR_UPLOAD_EDIT_MEDIA",
replacement: {
match: /maxValue:\d/,
replace: "maxValue:$self.settings.store.zoomMultiplier",
}
}
]
});

View file

@ -104,7 +104,7 @@ export default definePlugin({
}, },
// below username // below username
{ {
find: ".USER_PROFILE_MODAL", find: ".Messages.MUTUAL_GUILDS_WITH_END_COUNT", // Lazy-loaded
replacement: { replacement: {
match: /\.body.+?displayProfile:\i}\),/, match: /\.body.+?displayProfile:\i}\),/,
replace: "$&$self.patchModal(arguments[0]),", replace: "$&$self.patchModal(arguments[0]),",

View file

@ -174,7 +174,7 @@ export default definePlugin({
find: ".NITRO_BANNER,", find: ".NITRO_BANNER,",
replacement: { replacement: {
// style: { backgroundImage: shouldShowBanner ? "url(".concat(bannerUrl, // style: { backgroundImage: shouldShowBanner ? "url(".concat(bannerUrl,
match: /style:\{(?=backgroundImage:(\i)\?"url\("\.concat\((\i),)/, match: /style:\{(?=backgroundImage:(null!=\i)\?"url\("\.concat\((\i),)/,
replace: replace:
// onClick: () => shouldShowBanner && ev.target.style.backgroundImage && openImage(bannerUrl), style: { cursor: shouldShowBanner ? "pointer" : void 0, // onClick: () => shouldShowBanner && ev.target.style.backgroundImage && openImage(bannerUrl), style: { cursor: shouldShowBanner ? "pointer" : void 0,
'onClick:ev=>$1&&ev.target.style.backgroundImage&&$self.openImage($2),style:{cursor:$1?"pointer":void 0,' 'onClick:ev=>$1&&ev.target.style.backgroundImage&&$self.openImage($2),style:{cursor:$1?"pointer":void 0,'

View file

@ -155,7 +155,7 @@ export const Devs = /* #__PURE__*/ Object.freeze({
}, },
kemo: { kemo: {
name: "kemo", name: "kemo",
id: 299693897859465228n id: 715746190813298788n
}, },
dzshn: { dzshn: {
name: "dzshn", name: "dzshn",
@ -425,6 +425,18 @@ export const Devs = /* #__PURE__*/ Object.freeze({
newwares: { newwares: {
name: "newwares", name: "newwares",
id: 421405303951851520n id: 421405303951851520n
},
nakoyasha: {
name: "nakoyasha",
id: 222069018507345921n
},
Sqaaakoi: {
name: "Sqaaakoi",
id: 259558259491340288n
},
Byron: {
name: "byeoon",
id: 1167275288036655133n
} }
} satisfies Record<string, Dev>); } satisfies Record<string, Dev>);

View file

@ -116,8 +116,11 @@ export function proxyLazy<T>(factory: () => T, attempts = 5, isChild = false): T
attempts, attempts,
true true
); );
const lazyTarget = target[kGET]();
return Reflect.get(target[kGET](), p, receiver); if (typeof lazyTarget === "object" || typeof lazyTarget === "function") {
return Reflect.get(lazyTarget, p, receiver);
}
throw new Error("proxyLazy called on a primitive value");
} }
}) as any; }) as any;
} }

View file

@ -406,13 +406,15 @@ export function findExportedComponentLazy<T extends object = any>(...props: stri
}); });
} }
const DefaultExtractAndLoadChunksRegex = /(?:Promise\.all\((\[\i\.\i\(".+?"\).+?\])\)|Promise\.resolve\(\)).then\(\i\.bind\(\i,"(.+?)"\)\)/;
/** /**
* Extract and load chunks using their entry point * Extract and load chunks using their entry point
* @param code An array of all the code the module factory containing the lazy chunk loading must include * @param code An array of all the code the module factory containing the lazy chunk loading must include
* @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the lazy chunk loading found in the module factory * @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the lazy chunk loading found in the module factory
* @returns A promise that resolves when the chunks were loaded * @returns A promise that resolves when the chunks were loaded
*/ */
export async function extractAndLoadChunks(code: string[], matcher: RegExp = /Promise\.all\((\[\i\.\i\(".+?"\).+?\])\).then\(\i\.bind\(\i,"(.+?)"\)\)/) { export async function extractAndLoadChunks(code: string[], matcher: RegExp = DefaultExtractAndLoadChunksRegex) {
const module = findModuleFactory(...code); const module = findModuleFactory(...code);
if (!module) { if (!module) {
const err = new Error("extractAndLoadChunks: Couldn't find module factory"); const err = new Error("extractAndLoadChunks: Couldn't find module factory");
@ -434,7 +436,7 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = /Pr
} }
const [, rawChunkIds, entryPointId] = match; const [, rawChunkIds, entryPointId] = match;
if (!rawChunkIds || Number.isNaN(entryPointId)) { if (Number.isNaN(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);
@ -445,9 +447,11 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = /Pr
return; return;
} }
const chunkIds = Array.from(rawChunkIds.matchAll(/\("(.+?)"\)/g)).map((m: any) => m[1]); if (rawChunkIds) {
const chunkIds = Array.from(rawChunkIds.matchAll(/\("(.+?)"\)/g)).map((m: any) => m[1]);
await Promise.all(chunkIds.map(id => wreq.e(id)));
}
await Promise.all(chunkIds.map(id => wreq.e(id)));
wreq(entryPointId); wreq(entryPointId);
} }
@ -459,7 +463,7 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = /Pr
* @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the lazy chunk loading found in the module factory * @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the lazy chunk loading found in the module factory
* @returns A function that returns a promise that resolves when the chunks were loaded, on first call * @returns A function that returns a promise that resolves when the chunks were loaded, on first call
*/ */
export function extractAndLoadChunksLazy(code: string[], matcher: RegExp = /Promise\.all\((\[\i\.\i\(".+?"\).+?\])\).then\(\i\.bind\(\i,"(.+?)"\)\)/) { export function extractAndLoadChunksLazy(code: string[], matcher = DefaultExtractAndLoadChunksRegex) {
if (IS_DEV) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]); if (IS_DEV) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]);
return () => extractAndLoadChunks(code, matcher); return () => extractAndLoadChunks(code, matcher);