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 definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack"; 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 { 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 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";
@ -54,16 +54,22 @@ const ClientThemeSettingsActionsCreators = proxyLazyWebpack(() => searchProtoCla
const enum EmojiIntentions { const enum EmojiIntentions {
REACTION = 0, REACTION,
STATUS = 1, STATUS,
COMMUNITY_CONTENT = 2, COMMUNITY_CONTENT,
CHAT = 3, CHAT,
GUILD_STICKER_RELATED_EMOJI = 4, GUILD_STICKER_RELATED_EMOJI,
GUILD_ROLE_BENEFIT_EMOJI = 5, GUILD_ROLE_BENEFIT_EMOJI,
COMMUNITY_CONTENT_ONLY = 6, COMMUNITY_CONTENT_ONLY,
SOUNDBOARD = 7 SOUNDBOARD,
VOICE_CHANNEL_TOPIC,
GIFT,
AUTO_SUGGESTION,
POLLS
} }
const IS_BYPASSEABLE_INTENTION = `[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)`;
const enum StickerType { const enum StickerType {
PNG = 1, PNG = 1,
APNG = 2, APNG = 2,
@ -198,37 +204,43 @@ export default definePlugin({
patches: [ patches: [
{ {
find: ".PREMIUM_LOCKED;", find: ".PREMIUM_LOCKED;",
group: true,
predicate: () => settings.store.enableEmojiBypass, predicate: () => settings.store.enableEmojiBypass,
replacement: [ replacement: [
{ {
// Create a variable for the intention of listing the emoji // Create a variable for the intention of using the emoji
match: /(?<=,intention:(\i).+?;)/, match: /(?<=\.USE_EXTERNAL_EMOJIS.+?;)(?<=intention:(\i).+?)/,
replace: (_, intention) => `let fakeNitroIntention=${intention};` replace: (_, intention) => `const fakeNitroIntention=${intention};`
}, },
{ {
// Send the intention of listing the emoji to the nitro permission check functions // Disallow the emoji for external if the intention doesn't allow it
match: /\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i(?=\))/g, match: /&&!\i&&!\i(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/,
replace: '$&,typeof fakeNitroIntention!=="undefined"?fakeNitroIntention:void 0' replace: m => `${m}&&!${IS_BYPASSEABLE_INTENTION}`
}, },
{ {
// Disallow the emoji if the intention doesn't allow it // Disallow the emoji for unavailable if the intention doesn't allow it
match: /(&&!\i&&)!(\i)(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/, match: /!\i\.available(?=\)return \i\.\i\.GUILD_SUBSCRIPTION_UNAVAILABLE;)/,
replace: (_, rest, canUseExternal) => `${rest}(!${canUseExternal}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)))` replace: m => `${m}&&!${IS_BYPASSEABLE_INTENTION}`
}, },
{ {
// Make the emoji always available if the intention allows it // Disallow the emoji for premium locked if the intention doesn't allow it
match: /if\(!\i\.available/, match: /!\i\.\i\.canUseEmojisEverywhere\(\i\)/,
replace: m => `${m}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention))` 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", find: "isUnusableRoleSubscriptionEmoji:function",
predicate: () => settings.store.enableEmojiBypass,
replacement: { replacement: {
match: /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))(?=})/g, match: /isUnusableRoleSubscriptionEmoji:function/,
replace: (_, rest, premiumCheck) => `${rest},fakeNitroIntention){${premiumCheck}||fakeNitroIntention==null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)` // 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 // Allow stickers to be sent everywhere
@ -242,10 +254,10 @@ export default definePlugin({
}, },
// Make stickers always available // Make stickers always available
{ {
find: "\"SENDABLE\"", find: '"SENDABLE"',
predicate: () => settings.store.enableStickerBypass, predicate: () => settings.store.enableStickerBypass,
replacement: { replacement: {
match: /(\w+)\.available\?/, match: /\i\.available\?/,
replace: "true?" replace: "true?"
} }
}, },
@ -408,15 +420,6 @@ export default definePlugin({
match: /canUseCustomNotificationSounds:function\(\i\){/, match: /canUseCustomNotificationSounds:function\(\i\){/,
replace: "$&return true;" 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); UploadHandler.promptToUpload([file], ChannelStore.getChannel(channelId), DraftType.ChannelMessage);
}, },
canUseEmote(e: CustomEmoji, channelId: string) { canUseEmote(e: Emoji, channelId: string) {
if (e.require_colons === false) return true; if (e.type === "UNICODE") return true;
if (e.available === false) return false; if (e.available === false) return false;
const isUnusableRoleSubEmoji = RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmojiOriginal ?? RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmoji; const isUnusableRoleSubEmoji = RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmojiOriginal ?? RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmoji;

View file

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

View file

@ -22,9 +22,10 @@ interface Diff {
hours: number, hours: number,
minutes: number, minutes: number,
seconds: number; seconds: number;
milliseconds: number;
} }
const DISCORD_KT_DELAY = 1471228.928; const DISCORD_KT_DELAY = 1471228928;
const HiddenVisually = findExportedComponentLazy("HiddenVisually"); const HiddenVisually = findExportedComponentLazy("HiddenVisually");
export default definePlugin({ export default definePlugin({
@ -42,6 +43,11 @@ export default definePlugin({
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Detect old Discord Android clients", description: "Detect old Discord Android clients",
default: true 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 = { const diff: Diff = {
days: Math.round(delta / (60 * 60 * 24)), days: Math.round(delta / (60 * 60 * 24 * 1000)),
hours: Math.round((delta / (60 * 60)) % 24), hours: Math.round((delta / (60 * 60 * 1000)) % 24),
minutes: Math.round((delta / (60)) % 60), minutes: Math.round((delta / (60 * 1000)) % 60),
seconds: Math.round(delta % 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; 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 + ( return prev + (
isNonNullish(s) isNonNullish(s)
? (prev !== "" ? (prev !== ""
? k === "seconds" ? (showMillis ? k === "milliseconds" : k === "seconds")
? " and " ? " and "
: " " : " "
: "") + s : "") + s
@ -84,18 +91,21 @@ export default definePlugin({
}, },
latencyTooltipData(message: Message) { latencyTooltipData(message: Message) {
const { latency, detectDiscordKotlin } = this.settings.store; const { latency, detectDiscordKotlin, showMillis } = this.settings.store;
const { id, nonce } = message; const { id, nonce } = message;
// Message wasn't received through gateway // Message wasn't received through gateway
if (!isNonNullish(nonce)) return null; if (!isNonNullish(nonce)) return null;
let isDiscordKotlin = false; 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 // Old Discord Android clients have a delay of around 17 days
// This is a workaround for that // 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; isDiscordKotlin = detectDiscordKotlin;
delta += DISCORD_KT_DELAY; delta += DISCORD_KT_DELAY;
} }
@ -105,22 +115,23 @@ export default definePlugin({
// Can't do anything if the clock is behind // Can't do anything if the clock is behind
const abs = Math.abs(delta); const abs = Math.abs(delta);
const ahead = 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 // Also thanks dziurwa
// 2 minutes // 2 minutes
const TROLL_LIMIT = 2 * 60; const TROLL_LIMIT = 2 * 60 * 1000;
const fill: Fill = isDiscordKotlin const fill: Fill = isDiscordKotlin
? ["status-positive", "status-positive", "text-muted"] ? ["status-positive", "status-positive", "text-muted"]
: delta >= TROLL_LIMIT || ahead : delta >= TROLL_LIMIT || ahead
? ["text-muted", "text-muted", "text-muted"] ? ["text-muted", "text-muted", "text-muted"]
: delta >= (latency * 2) : delta >= (latencyMillis * 2)
? ["status-danger", "text-muted", "text-muted"] ? ["status-danger", "text-muted", "text-muted"]
: ["status-warning", "status-warning", "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() { 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/>. * 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 { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { FluxDispatcher } from "@webpack/common"; import { FluxDispatcher } from "@webpack/common";
@ -41,8 +41,9 @@ const settings = definePluginSettings({
}, },
}); });
migratePluginSettings("PartyMode", "Party mode 🎉");
export default definePlugin({ export default definePlugin({
name: "Party mode 🎉", name: "PartyMode",
description: "Allows you to use party mode cause the party never ends ✨", description: "Allows you to use party mode cause the party never ends ✨",
authors: [Devs.UwUDev], authors: [Devs.UwUDev],
settings, settings,

View file

@ -22,14 +22,34 @@ import { addServerListElement, removeServerListElement, ServerListRenderPosition
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs, EquicordDevs } from "@utils/constants"; import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findStoreLazy } from "@webpack";
import { Button, FluxDispatcher, GuildChannelStore, GuildStore, React, ReadStateStore } from "@webpack/common"; 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() { function onClick() {
const channels: Array<any> = []; const channels: Array<any> = [];
Object.values(GuildStore.getGuilds()).forEach(guild => { Object.values(GuildStore.getGuilds()).forEach(guild => {
GuildChannelStore.getChannels(guild.id).SELECTABLE GuildChannelStore.getChannels(guild.id).SELECTABLE // Array<{ channel, comparator }>
.concat(GuildChannelStore.getChannels(guild.id).VOCAL) .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; }; }) => { .forEach((c: { channel: { id: string; }; }) => {
if (!ReadStateStore.hasUnread(c.channel.id)) return; 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 { 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 definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findComponentLazy } from "@webpack"; 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 { Message } from "discord-types/general";
import { FunctionComponent, ReactNode } from "react";
const CountDown = findComponentLazy(m => m.prototype?.render?.toString().includes(".MAX_AGE_NEVER")); const CountDown = findComponentLazy(m => m.prototype?.render?.toString().includes(".MAX_AGE_NEVER"));
@ -26,7 +26,6 @@ const settings = definePluginSettings({
displayStyle: { displayStyle: {
description: "How to display the timeout duration", description: "How to display the timeout duration",
type: OptionType.SELECT, type: OptionType.SELECT,
restartNeeded: true,
options: [ options: [
{ label: "In the Tooltip", value: DisplayStyle.Tooltip }, { label: "In the Tooltip", value: DisplayStyle.Tooltip },
{ label: "Next to the timeout icon", value: DisplayStyle.Inline, default: true }, { label: "Next to the timeout icon", value: DisplayStyle.Inline, default: true },
@ -60,7 +59,7 @@ function renderTimeout(message: Message, inline: boolean) {
export default definePlugin({ export default definePlugin({
name: "ShowTimeoutDuration", name: "ShowTimeoutDuration",
description: "Shows how much longer a user's timeout will last, either in the timeout icon tooltip or next to it", 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, settings,
@ -70,33 +69,20 @@ export default definePlugin({
replacement: [ replacement: [
{ {
match: /(\i)\.Tooltip,{(text:.{0,30}\.Messages\.GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY)/, match: /(\i)\.Tooltip,{(text:.{0,30}\.Messages\.GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY)/,
get replace() { replace: "$self.TooltipWrapper,{message:arguments[0].message,$2"
if (settings.store.displayStyle === DisplayStyle.Inline)
return "$self.TooltipWrapper,{vcProps:arguments[0],$2";
return "$1.Tooltip,{text:$self.renderTimeoutDuration(arguments[0])";
}
} }
] ]
} }
], ],
renderTimeoutDuration: ErrorBoundary.wrap(({ message }: { message: Message; }) => { TooltipWrapper: ErrorBoundary.wrap(({ message, children, text }: { message: Message; children: FunctionComponent<any>; text: ReactNode; }) => {
return ( if (settings.store.displayStyle === DisplayStyle.Tooltip) return <Tooltip
<> children={children}
<Forms.FormText>{i18n.Messages.GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}</Forms.FormText> text={renderTimeout(message, false)}
<Forms.FormText className={Margins.top8}> />;
{renderTimeout(message, false)}
</Forms.FormText>
</>
);
}, { noop: true }),
TooltipWrapper: ErrorBoundary.wrap(({ vcProps: { message }, ...tooltipProps }: { vcProps: { message: Message; }; }) => {
return ( return (
<div className="vc-std-wrapper"> <div className="vc-std-wrapper">
<Tooltip {...tooltipProps as any} /> <Tooltip text={text} children={children} />
<Text variant="text-md/normal" color="status-danger"> <Text variant="text-md/normal" color="status-danger">
{renderTimeout(message, true)} timeout remaining {renderTimeout(message, true)} timeout remaining
</Text> </Text>

View file

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

View file

@ -36,6 +36,10 @@ interface GuildContextProps {
guild?: Guild; guild?: Guild;
} }
interface GroupDMContextProps {
channel: Channel;
}
const settings = definePluginSettings({ const settings = definePluginSettings({
format: { format: {
type: OptionType.SELECT, 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({ export default definePlugin({
name: "ViewIcons", name: "ViewIcons",
authors: [Devs.Ven, Devs.TheKodeToad, Devs.Nuckyz], authors: [Devs.Ven, Devs.TheKodeToad, Devs.Nuckyz, Devs.nyx],
description: "Makes avatars and banners in user profiles clickable, and adds View Icon/Banner entries in the user and server context menu", 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"], tags: ["ImageUtilities"],
settings, settings,
@ -157,11 +178,12 @@ export default definePlugin({
contextMenus: { contextMenus: {
"user-context": UserContext, "user-context": UserContext,
"guild-context": GuildContext "guild-context": GuildContext,
"gdm-context": GroupDMContext
}, },
patches: [ patches: [
// Make pfps clickable // Profiles Modal pfp
{ {
find: "User Profile Modal - Context Menu", find: "User Profile Modal - Context Menu",
replacement: { replacement: {
@ -169,7 +191,7 @@ export default definePlugin({
replace: "{src:$1,onClick:()=>$self.openImage($1)" replace: "{src:$1,onClick:()=>$self.openImage($1)"
} }
}, },
// Make banners clickable // Banners
{ {
find: ".NITRO_BANNER,", find: ".NITRO_BANNER,",
replacement: { replacement: {
@ -180,12 +202,38 @@ export default definePlugin({
'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,'
} }
}, },
// User DMs "User Profile" popup in the right
{ {
find: ".avatarPositionPanel", find: ".avatarPositionPanel",
replacement: { replacement: {
match: /(?<=avatarWrapperNonUserBot.{0,50})onClick:(\i\|\|\i)\?void 0(?<=,avatarSrc:(\i).+?)/, match: /(?<=avatarWrapperNonUserBot.{0,50})onClick:(\i\|\|\i)\?void 0(?<=,avatarSrc:(\i).+?)/,
replace: "style:($1)?{cursor:\"pointer\"}:{},onClick:$1?()=>{$self.openImage($2)}" 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", name: "newwares",
id: 421405303951851520n id: 421405303951851520n
}, },
JohnyTheCarrot: {
name: "JohnyTheCarrot",
id: 132819036282159104n
},
puv: { puv: {
name: "puv", name: "puv",
id: 469441552251355137n id: 469441552251355137n
@ -492,6 +496,18 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "ScattrdBlade", name: "ScattrdBlade",
id: 678007540608532491n id: 678007540608532491n
}, },
Moxxie: {
name: "Moxxie",
id: 712653921692155965n,
},
Ethan: {
name: "Ethan",
id: 721717126523781240n,
},
nyx: {
name: "verticalsync",
id: 328165170536775680n
},
} satisfies Record<string, Dev>); } satisfies Record<string, Dev>);
export const EquicordDevs = Object.freeze({ export const EquicordDevs = Object.freeze({

View file

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

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 { proxyLazy } from "@utils/lazy"; import { makeLazy, proxyLazy } from "@utils/lazy";
import { LazyComponent } from "@utils/lazyReact"; import { LazyComponent } from "@utils/lazyReact";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { canonicalizeMatch } from "@utils/patches"; 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) { export function extractAndLoadChunksLazy(code: string[], matcher: RegExp = DefaultExtractAndLoadChunksRegex) {
if (IS_DEV) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]); if (IS_DEV) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]);
return () => extractAndLoadChunks(code, matcher); return makeLazy(() => extractAndLoadChunks(code, matcher));
} }
/** /**