Merge remote-tracking branch 'upstream/dev'

This commit is contained in:
thororen1234 2024-05-17 09:40:30 -04:00
commit 314b042532
17 changed files with 403 additions and 91 deletions

View file

@ -0,0 +1,5 @@
# AutomodContext
Allows you to jump to the messages surrounding an automod hit
![Visualization](https://github.com/Vendicated/Vencord/assets/61953774/d13740c8-2062-4553-b975-82fd3d6cc08b)

View file

@ -0,0 +1,73 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { Button, ChannelStore, Text } from "@webpack/common";
const { selectChannel } = findByPropsLazy("selectChannel", "selectVoiceChannel");
function jumpToMessage(channelId: string, messageId: string) {
const guildId = ChannelStore.getChannel(channelId)?.guild_id;
selectChannel({
guildId,
channelId,
messageId,
jumpType: "INSTANT"
});
}
function findChannelId(message: any): string | null {
const { embeds: [embed] } = message;
const channelField = embed.fields.find(({ rawName }) => rawName === "channel_id");
if (!channelField) {
return null;
}
return channelField.rawValue;
}
export default definePlugin({
name: "AutomodContext",
description: "Allows you to jump to the messages surrounding an automod hit.",
authors: [Devs.JohnyTheCarrot],
patches: [
{
find: ".Messages.GUILD_AUTOMOD_REPORT_ISSUES",
replacement: {
match: /\.Messages\.ACTIONS.+?}\)(?=,(\(0.{0,40}\.dot.*?}\)),)/,
replace: (m, dot) => `${m},${dot},$self.renderJumpButton({message:arguments[0].message})`
}
}
],
renderJumpButton: ErrorBoundary.wrap(({ message }: { message: any; }) => {
const channelId = findChannelId(message);
if (!channelId) {
return null;
}
return (
<Button
style={{ padding: "2px 8px" }}
look={Button.Looks.LINK}
size={Button.Sizes.SMALL}
color={Button.Colors.LINK}
onClick={() => jumpToMessage(channelId, message.id)}
>
<Text color="text-link" variant="text-xs/normal">
Jump to Surrounding
</Text>
</Button>
);
}, { noop: true })
});

View file

@ -25,7 +25,7 @@ import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack";
import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, IconUtils, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
import type { CustomEmoji } from "@webpack/types";
import type { Emoji } from "@webpack/types";
import type { Message } from "discord-types/general";
import { applyPalette, GIFEncoder, quantize } from "gifenc";
import type { ReactElement, ReactNode } from "react";
@ -54,16 +54,22 @@ const ClientThemeSettingsActionsCreators = proxyLazyWebpack(() => searchProtoCla
const enum EmojiIntentions {
REACTION = 0,
STATUS = 1,
COMMUNITY_CONTENT = 2,
CHAT = 3,
GUILD_STICKER_RELATED_EMOJI = 4,
GUILD_ROLE_BENEFIT_EMOJI = 5,
COMMUNITY_CONTENT_ONLY = 6,
SOUNDBOARD = 7
REACTION,
STATUS,
COMMUNITY_CONTENT,
CHAT,
GUILD_STICKER_RELATED_EMOJI,
GUILD_ROLE_BENEFIT_EMOJI,
COMMUNITY_CONTENT_ONLY,
SOUNDBOARD,
VOICE_CHANNEL_TOPIC,
GIFT,
AUTO_SUGGESTION,
POLLS
}
const IS_BYPASSEABLE_INTENTION = `[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)`;
const enum StickerType {
PNG = 1,
APNG = 2,
@ -198,37 +204,43 @@ export default definePlugin({
patches: [
{
find: ".PREMIUM_LOCKED;",
group: true,
predicate: () => settings.store.enableEmojiBypass,
replacement: [
{
// Create a variable for the intention of listing the emoji
match: /(?<=,intention:(\i).+?;)/,
replace: (_, intention) => `let fakeNitroIntention=${intention};`
// Create a variable for the intention of using the emoji
match: /(?<=\.USE_EXTERNAL_EMOJIS.+?;)(?<=intention:(\i).+?)/,
replace: (_, intention) => `const fakeNitroIntention=${intention};`
},
{
// Send the intention of listing the emoji to the nitro permission check functions
match: /\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i(?=\))/g,
replace: '$&,typeof fakeNitroIntention!=="undefined"?fakeNitroIntention:void 0'
// Disallow the emoji for external if the intention doesn't allow it
match: /&&!\i&&!\i(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/,
replace: m => `${m}&&!${IS_BYPASSEABLE_INTENTION}`
},
{
// Disallow the emoji if the intention doesn't allow it
match: /(&&!\i&&)!(\i)(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/,
replace: (_, rest, canUseExternal) => `${rest}(!${canUseExternal}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)))`
// Disallow the emoji for unavailable if the intention doesn't allow it
match: /!\i\.available(?=\)return \i\.\i\.GUILD_SUBSCRIPTION_UNAVAILABLE;)/,
replace: m => `${m}&&!${IS_BYPASSEABLE_INTENTION}`
},
{
// Make the emoji always available if the intention allows it
match: /if\(!\i\.available/,
replace: m => `${m}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention))`
// Disallow the emoji for premium locked if the intention doesn't allow it
match: /!\i\.\i\.canUseEmojisEverywhere\(\i\)/,
replace: m => `(${m}&&!${IS_BYPASSEABLE_INTENTION})`
},
{
// Allow animated emojis to be used if the intention allows it
match: /(?<=\|\|)\i\.\i\.canUseAnimatedEmojis\(\i\)/,
replace: m => `(${m}||${IS_BYPASSEABLE_INTENTION})`
}
]
},
// Allow emojis and animated emojis to be sent everywhere
// Allows the usage of subscription-locked emojis
{
find: "canUseAnimatedEmojis:function",
predicate: () => settings.store.enableEmojiBypass,
find: "isUnusableRoleSubscriptionEmoji:function",
replacement: {
match: /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))(?=})/g,
replace: (_, rest, premiumCheck) => `${rest},fakeNitroIntention){${premiumCheck}||fakeNitroIntention==null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)`
match: /isUnusableRoleSubscriptionEmoji:function/,
// Replace the original export with a func that always returns false and alias the original
replace: "isUnusableRoleSubscriptionEmoji:()=>()=>false,isUnusableRoleSubscriptionEmojiOriginal:function"
}
},
// Allow stickers to be sent everywhere
@ -242,10 +254,10 @@ export default definePlugin({
},
// Make stickers always available
{
find: "\"SENDABLE\"",
find: '"SENDABLE"',
predicate: () => settings.store.enableStickerBypass,
replacement: {
match: /(\w+)\.available\?/,
match: /\i\.available\?/,
replace: "true?"
}
},
@ -408,15 +420,6 @@ export default definePlugin({
match: /canUseCustomNotificationSounds:function\(\i\){/,
replace: "$&return true;"
}
},
// Allows the usage of subscription-locked emojis
{
find: "isUnusableRoleSubscriptionEmoji:function",
replacement: {
match: /isUnusableRoleSubscriptionEmoji:function/,
// replace the original export with a func that always returns false and alias the original
replace: "isUnusableRoleSubscriptionEmoji:()=>()=>false,isUnusableRoleSubscriptionEmojiOriginal:function"
}
}
],
@ -812,8 +815,8 @@ export default definePlugin({
UploadHandler.promptToUpload([file], ChannelStore.getChannel(channelId), DraftType.ChannelMessage);
},
canUseEmote(e: CustomEmoji, channelId: string) {
if (e.require_colons === false) return true;
canUseEmote(e: Emoji, channelId: string) {
if (e.type === "UNICODE") return true;
if (e.available === false) return false;
const isUnusableRoleSubEmoji = RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmojiOriginal ?? RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmoji;

View file

@ -170,13 +170,14 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
}
try {
p.start();
p.started = true;
} catch (e) {
logger.error(`Failed to start ${name}\n`, e);
return false;
}
}
p.started = true;
if (commands?.length) {
logger.debug("Registering commands of plugin", name);
for (const cmd of commands) {
@ -206,6 +207,7 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
const { name, commands, flux, contextMenus } = p;
if (p.stop) {
logger.info("Stopping plugin", name);
if (!p.started) {
@ -214,13 +216,14 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
}
try {
p.stop();
p.started = false;
} catch (e) {
logger.error(`Failed to stop ${name}\n`, e);
return false;
}
}
p.started = false;
if (commands?.length) {
logger.debug("Unregistering commands of plugin", name);
for (const cmd of commands) {

View file

@ -22,9 +22,10 @@ interface Diff {
hours: number,
minutes: number,
seconds: number;
milliseconds: number;
}
const DISCORD_KT_DELAY = 1471228.928;
const DISCORD_KT_DELAY = 1471228928;
const HiddenVisually = findExportedComponentLazy("HiddenVisually");
export default definePlugin({
@ -42,6 +43,11 @@ export default definePlugin({
type: OptionType.BOOLEAN,
description: "Detect old Discord Android clients",
default: true
},
showMillis: {
type: OptionType.BOOLEAN,
description: "Show milliseconds",
default: false
}
}),
@ -55,12 +61,13 @@ export default definePlugin({
}
],
stringDelta(delta: number) {
stringDelta(delta: number, showMillis: boolean) {
const diff: Diff = {
days: Math.round(delta / (60 * 60 * 24)),
hours: Math.round((delta / (60 * 60)) % 24),
minutes: Math.round((delta / (60)) % 60),
seconds: Math.round(delta % 60),
days: Math.round(delta / (60 * 60 * 24 * 1000)),
hours: Math.round((delta / (60 * 60 * 1000)) % 24),
minutes: Math.round((delta / (60 * 1000)) % 60),
seconds: Math.round(delta / 1000 % 60),
milliseconds: Math.round(delta % 1000)
};
const str = (k: DiffKey) => diff[k] > 0 ? `${diff[k]} ${diff[k] > 1 ? k : k.substring(0, k.length - 1)}` : null;
@ -72,7 +79,7 @@ export default definePlugin({
return prev + (
isNonNullish(s)
? (prev !== ""
? k === "seconds"
? (showMillis ? k === "milliseconds" : k === "seconds")
? " and "
: " "
: "") + s
@ -84,18 +91,21 @@ export default definePlugin({
},
latencyTooltipData(message: Message) {
const { latency, detectDiscordKotlin } = this.settings.store;
const { latency, detectDiscordKotlin, showMillis } = this.settings.store;
const { id, nonce } = message;
// Message wasn't received through gateway
if (!isNonNullish(nonce)) return null;
let isDiscordKotlin = false;
let delta = Math.round((SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce)) / 1000);
let delta = SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce); // milliseconds
if (!showMillis) {
delta = Math.round(delta / 1000) * 1000;
}
// Old Discord Android clients have a delay of around 17 days
// This is a workaround for that
if (-delta >= DISCORD_KT_DELAY - 86400) { // One day of padding for good measure
if (-delta >= DISCORD_KT_DELAY - 86400000) { // One day of padding for good measure
isDiscordKotlin = detectDiscordKotlin;
delta += DISCORD_KT_DELAY;
}
@ -105,22 +115,23 @@ export default definePlugin({
// Can't do anything if the clock is behind
const abs = Math.abs(delta);
const ahead = abs !== delta;
const latencyMillis = latency * 1000;
const stringDelta = abs >= latency ? this.stringDelta(abs) : null;
const stringDelta = abs >= latencyMillis ? this.stringDelta(abs, showMillis) : null;
// Also thanks dziurwa
// 2 minutes
const TROLL_LIMIT = 2 * 60;
const TROLL_LIMIT = 2 * 60 * 1000;
const fill: Fill = isDiscordKotlin
? ["status-positive", "status-positive", "text-muted"]
: delta >= TROLL_LIMIT || ahead
? ["text-muted", "text-muted", "text-muted"]
: delta >= (latency * 2)
: delta >= (latencyMillis * 2)
? ["status-danger", "text-muted", "text-muted"]
: ["status-warning", "status-warning", "text-muted"];
return (abs >= latency || isDiscordKotlin) ? { delta: stringDelta, ahead, fill, isDiscordKotlin } : null;
return (abs >= latencyMillis || isDiscordKotlin) ? { delta: stringDelta, ahead, fill, isDiscordKotlin } : null;
},
Tooltip() {

View file

@ -0,0 +1,5 @@
# NoDefaultHangStatus
Disable the default hang status when joining voice channels
![Visualization](https://github.com/Vendicated/Vencord/assets/24937357/329a9742-236f-48f7-94ff-c3510eca505a)

View file

@ -0,0 +1,24 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "NoDefaultHangStatus",
description: "Disable the default hang status when joining voice channels",
authors: [Devs.D3SOX],
patches: [
{
find: "HangStatusTypes.CHILLING)",
replacement: {
match: /{enableHangStatus:(\i),/,
replace: "{_enableHangStatus:$1=false,"
}
}
]
});

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/Settings";
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { FluxDispatcher } from "@webpack/common";
@ -41,8 +41,9 @@ const settings = definePluginSettings({
},
});
migratePluginSettings("PartyMode", "Party mode 🎉");
export default definePlugin({
name: "Party mode 🎉",
name: "PartyMode",
description: "Allows you to use party mode cause the party never ends ✨",
authors: [Devs.UwUDev],
settings,

View file

@ -22,14 +22,34 @@ import { addServerListElement, removeServerListElement, ServerListRenderPosition
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin from "@utils/types";
import { findStoreLazy } from "@webpack";
import { Button, FluxDispatcher, GuildChannelStore, GuildStore, React, ReadStateStore } from "@webpack/common";
import { Channel } from "discord-types/general";
interface ThreadJoined {
channel: Channel;
joinTimestamp: number;
}
type ThreadsJoined = Record<string, ThreadJoined>;
type ThreadsJoinedByParent = Record<string, ThreadsJoined>;
interface ActiveJoinedThreadsStore {
getActiveJoinedThreadsForGuild(guildId: string): ThreadsJoinedByParent;
}
const ActiveJoinedThreadsStore: ActiveJoinedThreadsStore = findStoreLazy("ActiveJoinedThreadsStore");
function onClick() {
const channels: Array<any> = [];
Object.values(GuildStore.getGuilds()).forEach(guild => {
GuildChannelStore.getChannels(guild.id).SELECTABLE
.concat(GuildChannelStore.getChannels(guild.id).VOCAL)
GuildChannelStore.getChannels(guild.id).SELECTABLE // Array<{ channel, comparator }>
.concat(GuildChannelStore.getChannels(guild.id).VOCAL) // Array<{ channel, comparator }>
.concat(
Object.values(ActiveJoinedThreadsStore.getActiveJoinedThreadsForGuild(guild.id))
.flatMap(threadChannels => Object.values(threadChannels))
)
.forEach((c: { channel: { id: string; }; }) => {
if (!ReadStateStore.hasUnread(c.channel.id)) return;

View file

@ -0,0 +1,5 @@
# ReplaceGoogleSearch
Replaces the Google search with different Engines
![Visualization](https://github.com/Vendicated/Vencord/assets/61953774/8b8158d2-0407-4d7b-9dff-a8b9bdc1a122)

View file

@ -0,0 +1,107 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { Flex, Menu } from "@webpack/common";
const DefaultEngines = {
Google: "https://www.google.com/search?q=",
DuckDuckGo: "https://duckduckgo.com/",
Bing: "https://www.bing.com/search?q=",
Yahoo: "https://search.yahoo.com/search?p=",
Github: "https://github.com/search?q=",
Kagi: "https://kagi.com/search?q=",
Yandex: "https://yandex.com/search/?text=",
AOL: "https://search.aol.com/aol/search?q=",
Baidu: "https://www.baidu.com/s?wd=",
Wikipedia: "https://wikipedia.org/w/index.php?search=",
} as const;
const settings = definePluginSettings({
customEngineName: {
description: "Name of the custom search engine",
type: OptionType.STRING,
placeholder: "Google"
},
customEngineURL: {
description: "The URL of your Engine",
type: OptionType.STRING,
placeholder: "https://google.com/search?q="
}
});
function search(src: string, engine: string) {
open(engine + encodeURIComponent(src), "_blank");
}
function makeSearchItem(src: string) {
let Engines = {};
if (settings.store.customEngineName && settings.store.customEngineURL) {
Engines[settings.store.customEngineName] = settings.store.customEngineURL;
}
Engines = { ...Engines, ...DefaultEngines };
return (
<Menu.MenuItem
label="Search Text"
key="search-text"
id="vc-search-text"
>
{Object.keys(Engines).map((engine, i) => {
const key = "vc-search-content-" + engine;
return (
<Menu.MenuItem
key={key}
id={key}
label={
<Flex style={{ alignItems: "center", gap: "0.5em" }}>
<img
style={{
borderRadius: "50%"
}}
aria-hidden="true"
height={16}
width={16}
src={`https://www.google.com/s2/favicons?domain=${Engines[engine]}`}
/>
{engine}
</Flex>
}
action={() => search(src, Engines[engine])}
/>
);
})}
</Menu.MenuItem>
);
}
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, _props) => {
const selection = document.getSelection()?.toString();
if (!selection) return;
const group = findGroupChildrenByChildId("search-google", children);
if (group) {
const idx = group.findIndex(c => c?.props?.id === "search-google");
if (idx !== -1) group[idx] = makeSearchItem(selection);
}
};
export default definePlugin({
name: "ReplaceGoogleSearch",
description: "Replaces the Google search with different Engines",
authors: [Devs.Moxxie, Devs.Ethan],
settings,
contextMenus: {
"message": messageContextMenuPatch
}
});

View file

@ -9,11 +9,11 @@ import "./styles.css";
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types";
import { findComponentLazy } from "@webpack";
import { ChannelStore, Forms, GuildMemberStore, i18n, Text, Tooltip } from "@webpack/common";
import { ChannelStore, GuildMemberStore, i18n, Text, Tooltip } from "@webpack/common";
import { Message } from "discord-types/general";
import { FunctionComponent, ReactNode } from "react";
const CountDown = findComponentLazy(m => m.prototype?.render?.toString().includes(".MAX_AGE_NEVER"));
@ -26,7 +26,6 @@ const settings = definePluginSettings({
displayStyle: {
description: "How to display the timeout duration",
type: OptionType.SELECT,
restartNeeded: true,
options: [
{ label: "In the Tooltip", value: DisplayStyle.Tooltip },
{ label: "Next to the timeout icon", value: DisplayStyle.Inline, default: true },
@ -60,7 +59,7 @@ function renderTimeout(message: Message, inline: boolean) {
export default definePlugin({
name: "ShowTimeoutDuration",
description: "Shows how much longer a user's timeout will last, either in the timeout icon tooltip or next to it",
authors: [Devs.Ven],
authors: [Devs.Ven, Devs.Sqaaakoi],
settings,
@ -70,33 +69,20 @@ export default definePlugin({
replacement: [
{
match: /(\i)\.Tooltip,{(text:.{0,30}\.Messages\.GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY)/,
get replace() {
if (settings.store.displayStyle === DisplayStyle.Inline)
return "$self.TooltipWrapper,{vcProps:arguments[0],$2";
return "$1.Tooltip,{text:$self.renderTimeoutDuration(arguments[0])";
}
replace: "$self.TooltipWrapper,{message:arguments[0].message,$2"
}
]
}
],
renderTimeoutDuration: ErrorBoundary.wrap(({ message }: { message: Message; }) => {
return (
<>
<Forms.FormText>{i18n.Messages.GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}</Forms.FormText>
<Forms.FormText className={Margins.top8}>
{renderTimeout(message, false)}
</Forms.FormText>
</>
);
}, { noop: true }),
TooltipWrapper: ErrorBoundary.wrap(({ vcProps: { message }, ...tooltipProps }: { vcProps: { message: Message; }; }) => {
TooltipWrapper: ErrorBoundary.wrap(({ message, children, text }: { message: Message; children: FunctionComponent<any>; text: ReactNode; }) => {
if (settings.store.displayStyle === DisplayStyle.Tooltip) return <Tooltip
children={children}
text={renderTimeout(message, false)}
/>;
return (
<div className="vc-std-wrapper">
<Tooltip {...tooltipProps as any} />
<Tooltip text={text} children={children} />
<Text variant="text-md/normal" color="status-danger">
{renderTimeout(message, true)} timeout remaining
</Text>

View file

@ -2,3 +2,7 @@
display: flex;
align-items: center;
}
.vc-std-wrapper [class*="communicationDisabled"] {
margin-right: 0;
}

View file

@ -36,6 +36,10 @@ interface GuildContextProps {
guild?: Guild;
}
interface GroupDMContextProps {
channel: Channel;
}
const settings = definePluginSettings({
format: {
type: OptionType.SELECT,
@ -145,10 +149,27 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon
));
};
const GroupDMContext: NavContextMenuPatchCallback = (children, { channel }: GroupDMContextProps) => {
if (!channel) return;
children.splice(-1, 0, (
<Menu.MenuGroup>
<Menu.MenuItem
id="view-group-channel-icon"
label="View Icon"
action={() =>
openImage(IconUtils.getChannelIconURL(channel)!)
}
icon={ImageIcon}
/>
</Menu.MenuGroup>
));
};
export default definePlugin({
name: "ViewIcons",
authors: [Devs.Ven, Devs.TheKodeToad, Devs.Nuckyz],
description: "Makes avatars and banners in user profiles clickable, and adds View Icon/Banner entries in the user and server context menu",
authors: [Devs.Ven, Devs.TheKodeToad, Devs.Nuckyz, Devs.nyx],
description: "Makes avatars and banners in user profiles clickable, adds View Icon/Banner entries in the user, server and group channel context menu.",
tags: ["ImageUtilities"],
settings,
@ -157,11 +178,12 @@ export default definePlugin({
contextMenus: {
"user-context": UserContext,
"guild-context": GuildContext
"guild-context": GuildContext,
"gdm-context": GroupDMContext
},
patches: [
// Make pfps clickable
// Profiles Modal pfp
{
find: "User Profile Modal - Context Menu",
replacement: {
@ -169,7 +191,7 @@ export default definePlugin({
replace: "{src:$1,onClick:()=>$self.openImage($1)"
}
},
// Make banners clickable
// Banners
{
find: ".NITRO_BANNER,",
replacement: {
@ -180,12 +202,38 @@ export default definePlugin({
'onClick:ev=>$1&&ev.target.style.backgroundImage&&$self.openImage($2),style:{cursor:$1?"pointer":void 0,'
}
},
// User DMs "User Profile" popup in the right
{
find: ".avatarPositionPanel",
replacement: {
match: /(?<=avatarWrapperNonUserBot.{0,50})onClick:(\i\|\|\i)\?void 0(?<=,avatarSrc:(\i).+?)/,
replace: "style:($1)?{cursor:\"pointer\"}:{},onClick:$1?()=>{$self.openImage($2)}"
}
},
// Group DMs top small & large icon
{
find: ".recipients.length>=2",
all: true,
replacement: {
match: /null==\i\.icon\?.+?src:(\(0,\i\.getChannelIconURL\).+?\))(?=[,}])/,
replace: (m, iconUrl) => `${m},onClick:()=>$self.openImage(${iconUrl})`
}
},
// User DMs top small icon
{
find: ".cursorPointer:null,children",
replacement: {
match: /.Avatar,.+?src:(.+?\))(?=[,}])/,
replace: (m, avatarUrl) => `${m},onClick:()=>$self.openImage(${avatarUrl})`
}
},
// User Dms top large icon
{
find: 'experimentLocation:"empty_messages"',
replacement: {
match: /.Avatar,.+?src:(.+?\))(?=[,}])/,
replace: (m, avatarUrl) => `${m},onClick:()=>$self.openImage(${avatarUrl})`
}
}
]
});

View file

@ -444,6 +444,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "newwares",
id: 421405303951851520n
},
JohnyTheCarrot: {
name: "JohnyTheCarrot",
id: 132819036282159104n
},
puv: {
name: "puv",
id: 469441552251355137n
@ -492,6 +496,18 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "ScattrdBlade",
id: 678007540608532491n
},
Moxxie: {
name: "Moxxie",
id: 712653921692155965n,
},
Ethan: {
name: "Ethan",
id: 721717126523781240n,
},
nyx: {
name: "verticalsync",
id: 328165170536775680n
},
} satisfies Record<string, Dev>);
export const EquicordDevs = Object.freeze({

View file

@ -63,7 +63,7 @@ export interface CustomEmoji {
originalName?: string;
require_colons: boolean;
roles: string[];
url: string;
type: "GUILD_EMOJI";
}
export interface UnicodeEmoji {
@ -75,6 +75,7 @@ export interface UnicodeEmoji {
};
index: number;
surrogates: string;
type: "UNICODE";
uniqueName: string;
useSpriteSheet: boolean;
get allNamesString(): string;

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { proxyLazy } from "@utils/lazy";
import { makeLazy, proxyLazy } from "@utils/lazy";
import { LazyComponent } from "@utils/lazyReact";
import { Logger } from "@utils/Logger";
import { canonicalizeMatch } from "@utils/patches";
@ -462,7 +462,7 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
export function extractAndLoadChunksLazy(code: string[], matcher: RegExp = DefaultExtractAndLoadChunksRegex) {
if (IS_DEV) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]);
return () => extractAndLoadChunks(code, matcher);
return makeLazy(() => extractAndLoadChunks(code, matcher));
}
/**