1
0
Fork 1
mirror of https://github.com/Vendicated/Vencord.git synced 2025-01-11 02:16:23 +00:00

Merge branch 'dev' into plugin-irc-colors

Sync with dev.
This commit is contained in:
Grzesiek11 2024-02-18 03:26:05 +01:00
commit def2c36395
No known key found for this signature in database
GPG key ID: 4A5445FB68CDB5C4
23 changed files with 268 additions and 182 deletions

View file

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

View file

@ -21,7 +21,7 @@ import { Settings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { Alerts, Button, Forms, moment, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import type { DispatchWithoutAction } from "react"; import type { DispatchWithoutAction } from "react";
@ -129,7 +129,7 @@ function NotificationEntry({ data }: { data: PersistentNotificationData; }) {
richBody={ richBody={
<div className={cl("body")}> <div className={cl("body")}>
{data.body} {data.body}
<Timestamp timestamp={moment(data.timestamp)} className={cl("timestamp")} /> <Timestamp timestamp={new Date(data.timestamp)} className={cl("timestamp")} />
</div> </div>
} }
/> />

View file

@ -25,7 +25,6 @@ import definePlugin, { OptionType } from "@utils/types";
import { maybePromptToUpdate } from "@utils/updater"; import { maybePromptToUpdate } from "@utils/updater";
import { filters, findBulk, proxyLazyWebpack } from "@webpack"; import { filters, findBulk, proxyLazyWebpack } from "@webpack";
import { FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common"; import { FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common";
import type { ReactElement } from "react";
const CrashHandlerLogger = new Logger("CrashHandler"); const CrashHandlerLogger = new Logger("CrashHandler");
const { ModalStack, DraftManager, DraftType, closeExpressionPicker } = proxyLazyWebpack(() => { const { ModalStack, DraftManager, DraftType, closeExpressionPicker } = proxyLazyWebpack(() => {
@ -57,13 +56,13 @@ const settings = definePluginSettings({
} }
}); });
let crashCount: number = 0; let hasCrashedOnce = false;
let lastCrashTimestamp: number = 0; let isRecovering = false;
let shouldAttemptNextHandle = false; let shouldAttemptRecover = true;
export default definePlugin({ export default definePlugin({
name: "CrashHandler", name: "CrashHandler",
description: "Utility plugin for handling and possibly recovering from Crashes without a restart", description: "Utility plugin for handling and possibly recovering from crashes without a restart",
authors: [Devs.Nuckyz], authors: [Devs.Nuckyz],
enabledByDefault: true, enabledByDefault: true,
@ -73,61 +72,67 @@ export default definePlugin({
{ {
find: ".Messages.ERRORS_UNEXPECTED_CRASH", find: ".Messages.ERRORS_UNEXPECTED_CRASH",
replacement: { replacement: {
match: /(?=this\.setState\()/, match: /this\.setState\((.+?)\)/,
replace: "$self.handleCrash(this)||" replace: "$self.handleCrash(this,$1);"
} }
} }
], ],
handleCrash(_this: ReactElement & { forceUpdate: () => void; }) { handleCrash(_this: any, errorState: any) {
if (Date.now() - lastCrashTimestamp <= 1_000 && !shouldAttemptNextHandle) return true; _this.setState(errorState);
shouldAttemptNextHandle = false; // Already recovering, prevent error which happens more than once too fast to trigger another recover
if (isRecovering) return;
isRecovering = true;
if (++crashCount > 5) { // 1 ms timeout to avoid react breaking when re-rendering
setTimeout(() => {
try {
// Prevent a crash loop with an error that could not be handled
if (!shouldAttemptRecover) {
try { try {
showNotification({ showNotification({
color: "#eed202", color: "#eed202",
title: "Discord has crashed!", title: "Discord has crashed!",
body: "Awn :( Discord has crashed more than five times, not attempting to recover.", body: "Awn :( Discord has crashed two times rapidly, not attempting to recover.",
noPersist: true, noPersist: true
}); });
} catch { } } catch { }
lastCrashTimestamp = Date.now(); return;
return false;
} }
setTimeout(() => crashCount--, 60_000); shouldAttemptRecover = false;
// This is enough to avoid a crash loop
setTimeout(() => shouldAttemptRecover = true, 500);
} catch { }
try { try {
if (crashCount === 1) maybePromptToUpdate("Uh oh, Discord has just crashed... but good news, there is a Vencord update available that might fix this issue! Would you like to update now?", true); if (!hasCrashedOnce) {
hasCrashedOnce = true;
maybePromptToUpdate("Uh oh, Discord has just crashed... but good news, there is a Vencord update available that might fix this issue! Would you like to update now?", true);
}
} catch { }
try {
if (settings.store.attemptToPreventCrashes) { if (settings.store.attemptToPreventCrashes) {
this.handlePreventCrash(_this); this.handlePreventCrash(_this);
return true;
} }
return false;
} catch (err) { } catch (err) {
CrashHandlerLogger.error("Failed to handle crash", err); CrashHandlerLogger.error("Failed to handle crash", err);
return false;
} finally {
lastCrashTimestamp = Date.now();
} }
}, 1);
}, },
handlePreventCrash(_this: ReactElement & { forceUpdate: () => void; }) { handlePreventCrash(_this: any) {
if (Date.now() - lastCrashTimestamp >= 1_000) {
try { try {
showNotification({ showNotification({
color: "#eed202", color: "#eed202",
title: "Discord has crashed!", title: "Discord has crashed!",
body: "Attempting to recover...", body: "Attempting to recover...",
noPersist: true, noPersist: true
}); });
} catch { } } catch { }
}
try { try {
const channelId = SelectedChannelStore.getChannelId(); const channelId = SelectedChannelStore.getChannelId();
@ -176,9 +181,12 @@ export default definePlugin({
} }
} }
// Set isRecovering to false before setting the state to allow us to handle the next crash error correcty, in case it happens
setImmediate(() => isRecovering = false);
try { try {
shouldAttemptNextHandle = true; _this.setState({ error: null, info: null });
_this.forceUpdate();
} catch (err) { } catch (err) {
CrashHandlerLogger.debug("Failed to update crash handler component.", err); CrashHandlerLogger.debug("Failed to update crash handler component.", err);
} }

View file

@ -16,18 +16,27 @@
* 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 { migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
migratePluginSettings("DisableCallIdle", "DisableDMCallIdle");
export default definePlugin({ export default definePlugin({
name: "DisableDMCallIdle", name: "DisableCallIdle",
description: "Disables automatically getting kicked from a DM voice call after 3 minutes.", description: "Disables automatically getting kicked from a DM voice call after 3 minutes and being moved to an AFK voice channel.",
authors: [Devs.Nuckyz], authors: [Devs.Nuckyz],
patches: [ patches: [
{ {
find: ".Messages.BOT_CALL_IDLE_DISCONNECT", find: ".Messages.BOT_CALL_IDLE_DISCONNECT",
replacement: { replacement: {
match: /(?<=function \i\(\){)(?=.{1,120}\.Messages\.BOT_CALL_IDLE_DISCONNECT)/, match: /,?(?=this\.idleTimeout=new \i\.Timeout)/,
replace: ";return;"
}
},
{
find: "handleIdleUpdate(){",
replacement: {
match: /(?<=_initialize\(\){)/,
replace: "return;" replace: "return;"
} }
} }

View file

@ -108,6 +108,7 @@ const enum FakeNoticeType {
const fakeNitroEmojiRegex = /\/emojis\/(\d+?)\.(png|webp|gif)/; const fakeNitroEmojiRegex = /\/emojis\/(\d+?)\.(png|webp|gif)/;
const fakeNitroStickerRegex = /\/stickers\/(\d+?)\./; const fakeNitroStickerRegex = /\/stickers\/(\d+?)\./;
const fakeNitroGifStickerRegex = /\/attachments\/\d+?\/\d+?\/(\d+?)\.gif/; const fakeNitroGifStickerRegex = /\/attachments\/\d+?\/\d+?\/(\d+?)\.gif/;
const hyperLinkRegex = /\[.+?\]\((https?:\/\/.+?)\)/;
const settings = definePluginSettings({ const settings = definePluginSettings({
enableEmojiBypass: { enableEmojiBypass: {
@ -156,6 +157,11 @@ const settings = definePluginSettings({
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
default: true, default: true,
restartNeeded: true restartNeeded: true
},
useHyperLinks: {
description: "Whether to use hyperlinks when sending fake emojis and stickers",
type: OptionType.BOOLEAN,
default: true
} }
}); });
@ -345,7 +351,7 @@ export default definePlugin({
predicate: () => settings.store.transformEmojis, predicate: () => settings.store.transformEmojis,
replacement: { replacement: {
// Add the fake nitro emoji notice // Add the fake nitro emoji notice
match: /(?<=isDiscoverable:\i,emojiComesFromCurrentGuild:\i,.+?}=(\i).+?;)(.+?return )(.{0,1000}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?)(?=},)/, match: /(?<=isDiscoverable:\i,emojiComesFromCurrentGuild:\i,.+?}=(\i).+?;)(.*?return )(.{0,1000}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?)(?=},)/,
replace: (_, props, rest, reactNode) => `let{fakeNitroNode}=${props};${rest}$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!fakeNitroNode?.fake)` replace: (_, props, rest, reactNode) => `let{fakeNitroNode}=${props};${rest}$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!fakeNitroNode?.fake)`
} }
}, },
@ -447,13 +453,23 @@ export default definePlugin({
trimContent(content: Array<any>) { trimContent(content: Array<any>) {
const firstContent = content[0]; const firstContent = content[0];
if (typeof firstContent === "string") content[0] = firstContent.trimStart(); if (typeof firstContent === "string") {
if (content[0] === "") content.shift(); content[0] = firstContent.trimStart();
content[0] || content.shift();
} else if (firstContent?.type === "span") {
firstContent.props.children = firstContent.props.children.trimStart();
firstContent.props.children || content.shift();
}
const lastIndex = content.length - 1; const lastIndex = content.length - 1;
const lastContent = content[lastIndex]; const lastContent = content[lastIndex];
if (typeof lastContent === "string") content[lastIndex] = lastContent.trimEnd(); if (typeof lastContent === "string") {
if (content[lastIndex] === "") content.pop(); content[lastIndex] = lastContent.trimEnd();
content[lastIndex] || content.pop();
} else if (lastContent?.type === "span") {
lastContent.props.children = lastContent.props.children.trimEnd();
lastContent.props.children || content.pop();
}
}, },
clearEmptyArrayItems(array: Array<any>) { clearEmptyArrayItems(array: Array<any>) {
@ -465,7 +481,7 @@ export default definePlugin({
}, },
patchFakeNitroEmojisOrRemoveStickersLinks(content: Array<any>, inline: boolean) { patchFakeNitroEmojisOrRemoveStickersLinks(content: Array<any>, inline: boolean) {
// If content has more than one child or it's a single ReactElement like a header or list // If content has more than one child or it's a single ReactElement like a header, list or span
if ((content.length > 1 || typeof content[0]?.type === "string") && !settings.store.transformCompoundSentence) return content; if ((content.length > 1 || typeof content[0]?.type === "string") && !settings.store.transformCompoundSentence) return content;
let nextIndex = content.length; let nextIndex = content.length;
@ -574,7 +590,7 @@ export default definePlugin({
itemsToMaybePush.push(...message.attachments.filter(attachment => attachment.content_type === "image/gif").map(attachment => attachment.url)); itemsToMaybePush.push(...message.attachments.filter(attachment => attachment.content_type === "image/gif").map(attachment => attachment.url));
for (const item of itemsToMaybePush) { for (const item of itemsToMaybePush) {
if (!settings.store.transformCompoundSentence && !item.startsWith("http")) continue; if (!settings.store.transformCompoundSentence && !item.startsWith("http") && !hyperLinkRegex.test(item)) continue;
const imgMatch = item.match(fakeNitroStickerRegex); const imgMatch = item.match(fakeNitroStickerRegex);
if (imgMatch) { if (imgMatch) {
@ -619,8 +635,7 @@ export default definePlugin({
case "image": { case "image": {
if ( if (
!settings.store.transformCompoundSentence !settings.store.transformCompoundSentence
&& !contentItems.includes(embed.url!) && !contentItems.some(item => item === embed.url! || item.match(hyperLinkRegex)?.[1] === embed.url!)
&& !contentItems.includes(embed.image?.proxyURL!)
) return false; ) return false;
if (settings.store.transformEmojis) { if (settings.store.transformEmojis) {
@ -698,7 +713,7 @@ export default definePlugin({
}, },
getStickerLink(stickerId: string) { getStickerLink(stickerId: string) {
return `https://media.discordapp.net/stickers/${stickerId}.png?size=${Settings.plugins.FakeNitro.stickerSize}`; return `https://media.discordapp.net/stickers/${stickerId}.png?size=${settings.store.stickerSize}`;
}, },
async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) { async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) {
@ -795,12 +810,16 @@ export default definePlugin({
if (sticker.format_type === StickerType.GIF && link.includes(".png")) { if (sticker.format_type === StickerType.GIF && link.includes(".png")) {
link = link.replace(".png", ".gif"); link = link.replace(".png", ".gif");
} }
if (sticker.format_type === StickerType.APNG) { if (sticker.format_type === StickerType.APNG) {
this.sendAnimatedSticker(link, sticker.id, channelId); this.sendAnimatedSticker(link, sticker.id, channelId);
return { cancel: true }; return { cancel: true };
} else { } else {
const url = new URL(link);
url.searchParams.set("name", sticker.name);
messageObj.content += `${getWordBoundary(messageObj.content, messageObj.content.length - 1)}${s.useHyperLinks ? `[${sticker.name}](${url})` : url}`;
extra.stickers!.length = 0; extra.stickers!.length = 0;
messageObj.content += ` ${link}&name=${encodeURIComponent(sticker.name)}`;
} }
} }
@ -813,12 +832,13 @@ export default definePlugin({
if (emoji.guildId === guildId && !emoji.animated) continue; if (emoji.guildId === guildId && !emoji.animated) continue;
const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`; const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`;
const url = emoji.url.replace(/\?size=\d+/, "?" + new URLSearchParams({
size: Settings.plugins.FakeNitro.emojiSize, const url = new URL(emoji.url);
name: encodeURIComponent(emoji.name) url.searchParams.set("size", s.emojiSize.toString());
})); url.searchParams.set("name", emoji.name);
messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => { messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => {
return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + match.length)}`; return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${emoji.name}](${url})` : url}${getWordBoundary(origStr, offset + match.length)}`;
}); });
} }
} }
@ -840,11 +860,11 @@ export default definePlugin({
if (emoji.available !== false && canUseEmotes) return emojiStr; if (emoji.available !== false && canUseEmotes) return emojiStr;
if (emoji.guildId === guildId && !emoji.animated) return emojiStr; if (emoji.guildId === guildId && !emoji.animated) return emojiStr;
const url = emoji.url.replace(/\?size=\d+/, "?" + new URLSearchParams({ const url = new URL(emoji.url);
size: Settings.plugins.FakeNitro.emojiSize, url.searchParams.set("size", s.emojiSize.toString());
name: encodeURIComponent(emoji.name) url.searchParams.set("name", emoji.name);
}));
return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + emojiStr.length)}`; return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${emoji.name}](${url})` : url}${getWordBoundary(origStr, offset + emojiStr.length)}`;
}); });
}); });
}, },

View file

@ -17,7 +17,7 @@ app.on("browser-window-created", (_, win) => {
frame.executeJavaScript(` frame.executeJavaScript(`
new MutationObserver(() => { new MutationObserver(() => {
if( if(
document.querySelector('div.ytp-error-content-wrap-subreason a[href^="https://www.youtube.com/watch?v="]') document.querySelector('div.ytp-error-content-wrap-subreason a[href*="www.youtube.com/watch?v="]')
) location.reload() ) location.reload()
}).observe(document.body, { childList: true, subtree:true }); }).observe(document.body, { childList: true, subtree:true });
`); `);

View file

@ -123,14 +123,13 @@ export const Magnifier: React.FC<MagnifierProps> = ({ instance, size: initialSiz
waitFor(() => instance.state.readyState === "READY", () => { waitFor(() => instance.state.readyState === "READY", () => {
const elem = document.getElementById(ELEMENT_ID) as HTMLDivElement; const elem = document.getElementById(ELEMENT_ID) as HTMLDivElement;
element.current = elem; element.current = elem;
elem.firstElementChild!.setAttribute("draggable", "false"); elem.querySelector("img,video")?.setAttribute("draggable", "false");
if (instance.props.animated) { if (instance.props.animated) {
originalVideoElementRef.current = elem!.querySelector("video")!; originalVideoElementRef.current = elem!.querySelector("video")!;
originalVideoElementRef.current.addEventListener("timeupdate", syncVideos); originalVideoElementRef.current.addEventListener("timeupdate", syncVideos);
setReady(true);
} else {
setReady(true);
} }
setReady(true);
}); });
document.addEventListener("keydown", onKeyDown); document.addEventListener("keydown", onKeyDown);
document.addEventListener("keyup", onKeyUp); document.addEventListener("keyup", onKeyUp);
@ -155,7 +154,9 @@ export const Magnifier: React.FC<MagnifierProps> = ({ instance, size: initialSiz
if (!ready) return null; if (!ready) return null;
const box = element.current!.getBoundingClientRect(); const box = element.current?.getBoundingClientRect();
if (!box) return null;
return ( return (
<div <div

View file

@ -171,7 +171,7 @@ export default definePlugin({
find: "handleImageLoad=", find: "handleImageLoad=",
replacement: [ replacement: [
{ {
match: /showThumbhashPlaceholder:\i,/, match: /placeholderVersion:\i,/,
replace: "...$self.makeProps(this),$&" replace: "...$self.makeProps(this),$&"
}, },

View file

@ -29,15 +29,17 @@ import {
ChannelStore, ChannelStore,
FluxDispatcher, FluxDispatcher,
GuildStore, GuildStore,
IconUtils,
MessageStore, MessageStore,
Parser, Parser,
PermissionsBits,
PermissionStore, PermissionStore,
RestAPI, RestAPI,
Text, Text,
TextAndImagesSettingsStores, TextAndImagesSettingsStores,
UserStore UserStore
} from "@webpack/common"; } from "@webpack/common";
import { Channel, Guild, Message } from "discord-types/general"; import { Channel, Message } from "discord-types/general";
const messageCache = new Map<string, { const messageCache = new Map<string, {
message?: Message; message?: Message;
@ -49,8 +51,9 @@ const AutoModEmbed = findComponentByCodeLazy(".withFooter]:", "childrenMessageCo
const ChannelMessage = findComponentByCodeLazy("renderSimpleAccessories)"); const ChannelMessage = findComponentByCodeLazy("renderSimpleAccessories)");
const SearchResultClasses = findByPropsLazy("message", "searchResult"); const SearchResultClasses = findByPropsLazy("message", "searchResult");
const EmbedClasses = findByPropsLazy("embedAuthorIcon", "embedAuthor", "embedAuthor");
const messageLinkRegex = /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(\d{17,20}|@me)\/(\d{17,20})\/(\d{17,20})/g; const messageLinkRegex = /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(?:\d{17,20}|@me)\/(\d{17,20})\/(\d{17,20})/g;
const tenorRegex = /^https:\/\/(?:www\.)?tenor\.com\//; const tenorRegex = /^https:\/\/(?:www\.)?tenor\.com\//;
interface Attachment { interface Attachment {
@ -63,7 +66,6 @@ interface Attachment {
interface MessageEmbedProps { interface MessageEmbedProps {
message: Message; message: Message;
channel: Channel; channel: Channel;
guildID: string;
} }
const messageFetchQueue = new Queue(); const messageFetchQueue = new Queue();
@ -226,19 +228,19 @@ function MessageEmbedAccessory({ message }: { message: Message; }) {
let match = null as RegExpMatchArray | null; let match = null as RegExpMatchArray | null;
while ((match = messageLinkRegex.exec(message.content!)) !== null) { while ((match = messageLinkRegex.exec(message.content!)) !== null) {
const [_, guildID, channelID, messageID] = match; const [_, channelID, messageID] = match;
if (embeddedBy.includes(messageID)) { if (embeddedBy.includes(messageID)) {
continue; continue;
} }
const linkedChannel = ChannelStore.getChannel(channelID); const linkedChannel = ChannelStore.getChannel(channelID);
if (!linkedChannel || (guildID !== "@me" && !PermissionStore.can(1024n /* view channel */, linkedChannel))) { if (!linkedChannel || (!linkedChannel.isPrivate() && !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, linkedChannel))) {
continue; continue;
} }
const { listMode, idList } = settings.store; const { listMode, idList } = settings.store;
const isListed = [guildID, channelID, message.author.id].some(id => id && idList.includes(id)); const isListed = [linkedChannel.guild_id, channelID, message.author.id].some(id => id && idList.includes(id));
if (listMode === "blacklist" && isListed) continue; if (listMode === "blacklist" && isListed) continue;
if (listMode === "whitelist" && !isListed) continue; if (listMode === "whitelist" && !isListed) continue;
@ -265,8 +267,7 @@ function MessageEmbedAccessory({ message }: { message: Message; }) {
const messageProps: MessageEmbedProps = { const messageProps: MessageEmbedProps = {
message: withEmbeddedBy(linkedMessage, [...embeddedBy, message.id]), message: withEmbeddedBy(linkedMessage, [...embeddedBy, message.id]),
channel: linkedChannel, channel: linkedChannel
guildID
}; };
const type = settings.store.automodEmbeds; const type = settings.store.automodEmbeds;
@ -280,28 +281,28 @@ function MessageEmbedAccessory({ message }: { message: Message; }) {
return accessories.length ? <>{accessories}</> : null; return accessories.length ? <>{accessories}</> : null;
} }
function ChannelMessageEmbedAccessory({ message, channel, guildID }: MessageEmbedProps): JSX.Element | null { function getChannelLabelAndIconUrl(channel: Channel) {
const isDM = guildID === "@me"; if (channel.isDM()) return ["Direct Message", IconUtils.getUserAvatarURL(UserStore.getUser(channel.recipients[0]))];
if (channel.isGroupDM()) return ["Group DM", IconUtils.getChannelIconURL(channel)];
return ["Server", IconUtils.getGuildIconURL(GuildStore.getGuild(channel.guild_id))];
}
const guild = !isDM && GuildStore.getGuild(channel.guild_id); function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps): JSX.Element | null {
const dmReceiver = UserStore.getUser(ChannelStore.getChannel(channel.id).recipients?.[0]); const dmReceiver = UserStore.getUser(ChannelStore.getChannel(channel.id).recipients?.[0]);
const [channelLabel, iconUrl] = getChannelLabelAndIconUrl(channel);
return <Embed return (
<Embed
embed={{ embed={{
rawDescription: "", rawDescription: "",
color: "var(--background-secondary)", color: "var(--background-secondary)",
author: { author: {
name: <Text variant="text-xs/medium" tag="span"> name: <Text variant="text-xs/medium" tag="span">
<span>{isDM ? "Direct Message - " : (guild as Guild).name + " - "}</span> <span>{channelLabel} - </span>
{isDM {Parser.parse(channel.isDM() ? `<@${dmReceiver.id}>` : `<#${channel.id}>`)}
? Parser.parse(`<@${dmReceiver.id}>`)
: Parser.parse(`<#${channel.id}>`)
}
</Text>, </Text>,
iconProxyURL: guild iconProxyURL: iconUrl
? `https://${window.GLOBAL_ENV.CDN_HOST}/icons/${guild.id}/${guild.icon}.png`
: `https://${window.GLOBAL_ENV.CDN_HOST}/avatars/${dmReceiver.id}/${dmReceiver.avatar}`
} }
}} }}
renderDescription={() => ( renderDescription={() => (
@ -314,25 +315,30 @@ function ChannelMessageEmbedAccessory({ message, channel, guildID }: MessageEmbe
/> />
</div> </div>
)} )}
/>; />
);
} }
function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null { function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
const { message, channel, guildID } = props; const { message, channel } = props;
const compact = TextAndImagesSettingsStores.MessageDisplayCompact.useSetting(); const compact = TextAndImagesSettingsStores.MessageDisplayCompact.useSetting();
const isDM = guildID === "@me";
const images = getImages(message); const images = getImages(message);
const { parse } = Parser; const { parse } = Parser;
const [channelLabel, iconUrl] = getChannelLabelAndIconUrl(channel);
return <AutoModEmbed return <AutoModEmbed
channel={channel} channel={channel}
childrenAccessories={ childrenAccessories={
<Text color="text-muted" variant="text-xs/medium" tag="span"> <Text color="text-muted" variant="text-xs/medium" tag="span" className={`${EmbedClasses.embedAuthor} ${EmbedClasses.embedMargin}`}>
{isDM {iconUrl && <img src={iconUrl} className={EmbedClasses.embedAuthorIcon} alt="" />}
? parse(`<@${ChannelStore.getChannel(channel.id).recipients[0]}>`) <span>
: parse(`<#${channel.id}>`) <span>{channelLabel} - </span>
{channel.isDM()
? Parser.parse(`<@${ChannelStore.getChannel(channel.id).recipients[0]}>`)
: Parser.parse(`<#${channel.id}>`)
} }
<span>{isDM ? " - Direct Message" : " - " + GuildStore.getGuild(channel.guild_id)?.name}</span> </span>
</Text> </Text>
} }
compact={compact} compact={compact}

View file

@ -26,7 +26,7 @@ import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { ChannelStore, FluxDispatcher, i18n, Menu, moment, Parser, Timestamp, UserStore } from "@webpack/common"; import { ChannelStore, FluxDispatcher, i18n, Menu, Parser, Timestamp, UserStore } from "@webpack/common";
import overlayStyle from "./deleteStyleOverlay.css?managed"; import overlayStyle from "./deleteStyleOverlay.css?managed";
import textStyle from "./deleteStyleText.css?managed"; import textStyle from "./deleteStyleText.css?managed";
@ -122,7 +122,7 @@ export default definePlugin({
makeEdit(newMessage: any, oldMessage: any): any { makeEdit(newMessage: any, oldMessage: any): any {
return { return {
timestamp: moment?.call(newMessage.edited_timestamp), timestamp: new Date(newMessage.edited_timestamp),
content: oldMessage.content content: oldMessage.content
}; };
}, },

View file

@ -20,11 +20,10 @@ import { Devs } from "@utils/constants";
import { isNonNullish } from "@utils/guards"; import { isNonNullish } from "@utils/guards";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { Avatar, ChannelStore, Clickable, RelationshipStore, ScrollerThin, UserStore } from "@webpack/common"; import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, UserStore } from "@webpack/common";
import { Channel, User } from "discord-types/general"; import { Channel, User } from "discord-types/general";
const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel"); const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel");
const AvatarUtils = findByPropsLazy("getChannelIconURL");
const UserUtils = findByPropsLazy("getGlobalName"); const UserUtils = findByPropsLazy("getGlobalName");
const ProfileListClasses = findByPropsLazy("emptyIconFriends", "emptyIconGuilds"); const ProfileListClasses = findByPropsLazy("emptyIconFriends", "emptyIconGuilds");
@ -71,7 +70,7 @@ export default definePlugin({
}} }}
> >
<Avatar <Avatar
src={AvatarUtils.getChannelIconURL({ id: c.id, icon: c.icon, size: 32 })} src={IconUtils.getChannelIconURL({ id: c.id, icon: c.icon, size: 32 })}
size="SIZE_40" size="SIZE_40"
className={ProfileListClasses.listAvatar} className={ProfileListClasses.listAvatar}
> >

View file

@ -104,6 +104,7 @@ function UserPermissionsComponent({ guild, guildMember, showBorder }: { guild: G
guildMember.nick || UserStore.getUser(guildMember.userId).username guildMember.nick || UserStore.getUser(guildMember.userId).username
) )
} }
onDropDownClick={state => settings.store.defaultPermissionsDropdownState = !state}
defaultState={settings.store.defaultPermissionsDropdownState} defaultState={settings.store.defaultPermissionsDropdownState}
buttons={[ buttons={[
(<Tooltip text={`Sorting by ${stns.permissionsSortOrder === PermissionsSortOrder.HighestRole ? "Highest Role" : "Lowest Role"}`}> (<Tooltip text={`Sorting by ${stns.permissionsSortOrder === PermissionsSortOrder.HighestRole ? "Highest Role" : "Lowest Role"}`}>

View file

@ -20,7 +20,7 @@ import { openUserProfile } from "@utils/discord";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { LazyComponent } from "@utils/react"; import { LazyComponent } from "@utils/react";
import { filters, findBulk } from "@webpack"; import { filters, findBulk } from "@webpack";
import { Alerts, moment, Parser, Timestamp, useState } from "@webpack/common"; import { Alerts, Parser, Timestamp, useState } from "@webpack/common";
import { Auth, getToken } from "../auth"; import { Auth, getToken } from "../auth";
import { Review, ReviewType } from "../entities"; import { Review, ReviewType } from "../entities";
@ -163,7 +163,7 @@ export default LazyComponent(() => {
{ {
!settings.store.hideTimestamps && review.type !== ReviewType.System && ( !settings.store.hideTimestamps && review.type !== ReviewType.System && (
<Timestamp timestamp={moment(review.timestamp * 1000)} > <Timestamp timestamp={new Date(review.timestamp * 1000)} >
{dateFormat.format(review.timestamp * 1000)} {dateFormat.format(review.timestamp * 1000)}
</Timestamp>) </Timestamp>)
} }

View file

@ -12,10 +12,9 @@ import { classes } from "@utils/misc";
import { ModalRoot, ModalSize, openModal } from "@utils/modal"; import { ModalRoot, ModalSize, openModal } from "@utils/modal";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { findByPropsLazy, findExportedComponentLazy } from "@webpack"; import { findByPropsLazy, findExportedComponentLazy } from "@webpack";
import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, moment, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common"; import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, IconUtils, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common";
import { Guild, User } from "discord-types/general"; import { Guild, User } from "discord-types/general";
const IconUtils = findByPropsLazy("getGuildBannerURL");
const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper"); const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper");
const FriendRow = findExportedComponentLazy("FriendRow"); const FriendRow = findExportedComponentLazy("FriendRow");
@ -50,7 +49,7 @@ const fetched = {
function renderTimestamp(timestamp: number) { function renderTimestamp(timestamp: number) {
return ( return (
<Timestamp timestamp={moment(timestamp)} /> <Timestamp timestamp={new Date(timestamp)} />
); );
} }
@ -65,10 +64,7 @@ function GuildProfileModal({ guild }: GuildProps) {
const [currentTab, setCurrentTab] = useState(Tabs.ServerInfo); const [currentTab, setCurrentTab] = useState(Tabs.ServerInfo);
const bannerUrl = guild.banner && IconUtils.getGuildBannerURL({ const bannerUrl = guild.banner && IconUtils.getGuildBannerURL(guild, true)!.replace(/\?size=\d+$/, "?size=1024");
id: guild.id,
banner: guild.banner
}, true).replace(/\?size=\d+$/, "?size=1024");
const iconUrl = guild.icon && IconUtils.getGuildIconURL({ const iconUrl = guild.icon && IconUtils.getGuildIconURL({
id: guild.id, id: guild.id,
@ -89,7 +85,7 @@ function GuildProfileModal({ guild }: GuildProps) {
)} )}
<div className={cl("header")}> <div className={cl("header")}>
{guild.icon {iconUrl
? <img ? <img
src={iconUrl} src={iconUrl}
alt="" alt=""
@ -150,7 +146,7 @@ function Owner(guildId: string, owner: User) {
avatar: guildAvatar, avatar: guildAvatar,
guildId, guildId,
canAnimate: true canAnimate: true
}, true) })
: IconUtils.getUserAvatarURL(owner, true); : IconUtils.getUserAvatarURL(owner, true);
return ( return (

View file

@ -20,7 +20,7 @@ import { Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { formatDuration } from "@utils/text"; import { formatDuration } from "@utils/text";
import { findByPropsLazy, findComponentByCodeLazy, findComponentLazy } from "@webpack"; import { findByPropsLazy, findComponentByCodeLazy, findComponentLazy } from "@webpack";
import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common"; import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common";
import type { Channel } from "discord-types/general"; import type { Channel } from "discord-types/general";
import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "../../permissionsViewer/components/RolesAndUsersPermissions"; import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "../../permissionsViewer/components/RolesAndUsersPermissions";
@ -120,7 +120,7 @@ const VideoQualityModesToNames = {
const HiddenChannelLogo = "/assets/433e3ec4319a9d11b0cbe39342614982.svg"; const HiddenChannelLogo = "/assets/433e3ec4319a9d11b0cbe39342614982.svg";
function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) { function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
const [viewAllowedUsersAndRoles, setViewAllowedUsersAndRoles] = useState(settings.store.defaultAllowedUsersAndRolesDropdownState); const { defaultAllowedUsersAndRolesDropdownState } = settings.use(["defaultAllowedUsersAndRolesDropdownState"]);
const [permissions, setPermissions] = useState<RoleOrUserPermission[]>([]); const [permissions, setPermissions] = useState<RoleOrUserPermission[]>([]);
const { const {
@ -216,12 +216,12 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
{lastMessageId && {lastMessageId &&
<Text variant="text-md/normal"> <Text variant="text-md/normal">
Last {channel.isForumChannel() ? "post" : "message"} created: Last {channel.isForumChannel() ? "post" : "message"} created:
<Timestamp timestamp={moment(SnowflakeUtils.extractTimestamp(lastMessageId))} /> <Timestamp timestamp={new Date(SnowflakeUtils.extractTimestamp(lastMessageId))} />
</Text> </Text>
} }
{lastPinTimestamp && {lastPinTimestamp &&
<Text variant="text-md/normal">Last message pin: <Timestamp timestamp={moment(lastPinTimestamp)} /></Text> <Text variant="text-md/normal">Last message pin: <Timestamp timestamp={new Date(lastPinTimestamp)} /></Text>
} }
{(rateLimitPerUser ?? 0) > 0 && {(rateLimitPerUser ?? 0) > 0 &&
<Text variant="text-md/normal">Slowmode: {formatDuration(rateLimitPerUser!, "seconds")}</Text> <Text variant="text-md/normal">Slowmode: {formatDuration(rateLimitPerUser!, "seconds")}</Text>
@ -301,19 +301,19 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
</Tooltip> </Tooltip>
)} )}
<Text variant="text-lg/bold">Allowed users and roles:</Text> <Text variant="text-lg/bold">Allowed users and roles:</Text>
<Tooltip text={viewAllowedUsersAndRoles ? "Hide Allowed Users and Roles" : "View Allowed Users and Roles"}> <Tooltip text={defaultAllowedUsersAndRolesDropdownState ? "Hide Allowed Users and Roles" : "View Allowed Users and Roles"}>
{({ onMouseLeave, onMouseEnter }) => ( {({ onMouseLeave, onMouseEnter }) => (
<button <button
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
className="shc-lock-screen-allowed-users-and-roles-container-toggle-btn" className="shc-lock-screen-allowed-users-and-roles-container-toggle-btn"
onClick={() => setViewAllowedUsersAndRoles(v => !v)} onClick={() => settings.store.defaultAllowedUsersAndRolesDropdownState = !defaultAllowedUsersAndRolesDropdownState}
> >
<svg <svg
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
transform={viewAllowedUsersAndRoles ? "scale(1 -1)" : "scale(1 1)"} transform={defaultAllowedUsersAndRolesDropdownState ? "scale(1 -1)" : "scale(1 1)"}
> >
<path fill="currentColor" d="M16.59 8.59003L12 13.17L7.41 8.59003L6 10L12 16L18 10L16.59 8.59003Z" /> <path fill="currentColor" d="M16.59 8.59003L12 13.17L7.41 8.59003L6 10L12 16L18 10L16.59 8.59003Z" />
</svg> </svg>
@ -321,7 +321,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
)} )}
</Tooltip> </Tooltip>
</div> </div>
{viewAllowedUsersAndRoles && <ChannelBeginHeader channel={channel} />} {defaultAllowedUsersAndRolesDropdownState && <ChannelBeginHeader channel={channel} />}
</div> </div>
</div> </div>
</div> </div>

View file

@ -29,7 +29,7 @@ import type { Channel, Role } from "discord-types/general";
import HiddenChannelLockScreen from "./components/HiddenChannelLockScreen"; import HiddenChannelLockScreen from "./components/HiddenChannelLockScreen";
const ChannelListClasses = findByPropsLazy("channelEmoji", "unread", "icon"); const ChannelListClasses = findByPropsLazy("modeMuted", "modeSelected", "unread", "icon");
const enum ShowMode { const enum ShowMode {
LockIcon, LockIcon,
@ -162,7 +162,7 @@ export default definePlugin({
}, },
// Add the hidden eye icon if the channel is hidden // Add the hidden eye icon if the channel is hidden
{ {
match: /\i\.children.+?:null(?<=,channel:(\i).+?)/, match: /\.name\),.{0,120}\.children.+?:null(?<=,channel:(\i).+?)/,
replace: (m, channel) => `${m},$self.isHiddenChannel(${channel})?$self.HiddenChannelIcon():null` replace: (m, channel) => `${m},$self.isHiddenChannel(${channel})?$self.HiddenChannelIcon():null`
}, },
// Make voice channels also appear as muted if they are muted // Make voice channels also appear as muted if they are muted

View file

@ -133,7 +133,7 @@ export default definePlugin({
{ {
find: "UNREAD_IMPORTANT:", find: "UNREAD_IMPORTANT:",
replacement: { replacement: {
match: /channel:(\i).{0,100}?channelEmoji,.{0,250}?\.children.{0,50}?:null/, match: /\.name\),.{0,120}\.children.+?:null(?<=,channel:(\i).+?)/,
replace: "$&,$self.TypingIndicator($1.id)" replace: "$&,$self.TypingIndicator($1.id)"
} }
}, },

View file

@ -96,7 +96,7 @@ export default definePlugin({
patches: [ patches: [
// above message box // above message box
{ {
find: ".lastEditedByContainer", find: ".popularApplicationCommandIds,",
replacement: { replacement: {
match: /\(0,\i\.jsx\)\(\i\.\i,{user:\i,setNote/, match: /\(0,\i\.jsx\)\(\i\.\i,{user:\i,setNote/,
replace: "$self.patchPopout(arguments[0]),$&", replace: "$self.patchPopout(arguments[0]),$&",

View file

@ -22,11 +22,9 @@ import { ImageIcon } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { openImageModal } from "@utils/discord"; import { openImageModal } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { GuildMemberStore, IconUtils, Menu } from "@webpack/common";
import { GuildMemberStore, Menu } from "@webpack/common";
import type { Channel, Guild, User } from "discord-types/general"; import type { Channel, Guild, User } from "discord-types/general";
const BannerStore = findByPropsLazy("getGuildBannerURL");
interface UserContextProps { interface UserContextProps {
channel: Channel; channel: Channel;
@ -91,19 +89,19 @@ const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: U
<Menu.MenuItem <Menu.MenuItem
id="view-avatar" id="view-avatar"
label="View Avatar" label="View Avatar"
action={() => openImage(BannerStore.getUserAvatarURL(user, true))} action={() => openImage(IconUtils.getUserAvatarURL(user, true))}
icon={ImageIcon} icon={ImageIcon}
/> />
{memberAvatar && ( {memberAvatar && (
<Menu.MenuItem <Menu.MenuItem
id="view-server-avatar" id="view-server-avatar"
label="View Server Avatar" label="View Server Avatar"
action={() => openImage(BannerStore.getGuildMemberAvatarURLSimple({ action={() => openImage(IconUtils.getGuildMemberAvatarURLSimple({
userId: user.id, userId: user.id,
avatar: memberAvatar, avatar: memberAvatar,
guildId, guildId: guildId!,
canAnimate: true canAnimate: true
}, true))} }))}
icon={ImageIcon} icon={ImageIcon}
/> />
)} )}
@ -124,11 +122,11 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon
id="view-icon" id="view-icon"
label="View Icon" label="View Icon"
action={() => action={() =>
openImage(BannerStore.getGuildIconURL({ openImage(IconUtils.getGuildIconURL({
id, id,
icon, icon,
canAnimate: true canAnimate: true
})) })!)
} }
icon={ImageIcon} icon={ImageIcon}
/> />
@ -138,10 +136,7 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon
id="view-banner" id="view-banner"
label="View Banner" label="View Banner"
action={() => action={() =>
openImage(BannerStore.getGuildBannerURL({ openImage(IconUtils.getGuildBannerURL(guild, true)!)
id,
banner,
}, true))
} }
icon={ImageIcon} icon={ImageIcon}
/> />

View file

@ -47,18 +47,23 @@ const settings = definePluginSettings({
}); });
const MEDIA_PROXY_URL = "https://media.discordapp.net"; const MEDIA_PROXY_URL = "https://media.discordapp.net";
const CDN_URL = "https://cdn.discordapp.com"; const CDN_URL = "cdn.discordapp.com";
function fixImageUrl(urlString: string, explodeWebp: boolean) { function fixImageUrl(urlString: string) {
const url = new URL(urlString); const url = new URL(urlString);
if (url.origin === CDN_URL) return urlString; if (url.host === CDN_URL) return urlString;
if (url.origin === MEDIA_PROXY_URL) return CDN_URL + url.pathname;
url.searchParams.delete("width"); url.searchParams.delete("width");
url.searchParams.delete("height"); url.searchParams.delete("height");
if (url.origin === MEDIA_PROXY_URL) {
url.host = CDN_URL;
url.searchParams.delete("size");
url.searchParams.delete("quality");
url.searchParams.delete("format");
} else {
url.searchParams.set("quality", "lossless"); url.searchParams.set("quality", "lossless");
if (explodeWebp && url.searchParams.get("format") === "webp") }
url.searchParams.set("format", "png");
return url.toString(); return url.toString();
} }
@ -199,7 +204,7 @@ export default definePlugin({
], ],
async copyImage(url: string) { async copyImage(url: string) {
url = fixImageUrl(url, true); url = fixImageUrl(url);
let imageData = await fetch(url).then(r => r.blob()); let imageData = await fetch(url).then(r => r.blob());
if (imageData.type !== "image/png") { if (imageData.type !== "image/png") {
@ -231,7 +236,7 @@ export default definePlugin({
}, },
async saveImage(url: string) { async saveImage(url: string) {
url = fixImageUrl(url, false); url = fixImageUrl(url);
const data = await fetchImage(url); const data = await fetchImage(url);
if (!data) return; if (!data) return;

View file

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import type { Moment } from "moment";
import type { ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, KeyboardEvent, MouseEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react"; import type { ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, KeyboardEvent, MouseEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react";
export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code"; export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code";
@ -154,7 +153,7 @@ export type Switch = ComponentType<PropsWithChildren<{
}>>; }>>;
export type Timestamp = ComponentType<PropsWithChildren<{ export type Timestamp = ComponentType<PropsWithChildren<{
timestamp: Moment; timestamp: Date;
isEdited?: boolean; isEdited?: boolean;
className?: string; className?: string;

View file

@ -16,6 +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 { Guild, GuildMember } from "discord-types/general";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import type { FluxEvents } from "./fluxEvents"; import type { FluxEvents } from "./fluxEvents";
@ -182,3 +183,47 @@ export interface NavigationRouter {
getLastRouteChangeSource(): any; getLastRouteChangeSource(): any;
getLastRouteChangeSourceLocationStack(): any; getLastRouteChangeSourceLocationStack(): any;
} }
export interface IconUtils {
getUserAvatarURL(user: User, canAnimate?: boolean, size?: number, format?: string): string;
getDefaultAvatarURL(id: string, discriminator?: string): string;
getUserBannerURL(data: { id: string, banner: string, canAnimate?: boolean, size: number; }): string | undefined;
getAvatarDecorationURL(dara: { avatarDecoration: string, size: number; canCanimate?: boolean; }): string | undefined;
getGuildMemberAvatarURL(member: GuildMember, canAnimate?: string): string | null;
getGuildMemberAvatarURLSimple(data: { guildId: string, userId: string, avatar: string, canAnimate?: boolean; size?: number; }): string;
getGuildMemberBannerURL(data: { id: string, guildId: string, banner: string, canAnimate?: boolean, size: number; }): string | undefined;
getGuildIconURL(data: { id: string, icon?: string, size?: number, canAnimate?: boolean; }): string | undefined;
getGuildBannerURL(guild: Guild, canAnimate?: boolean): string | null;
getChannelIconURL(data: { id: string; icon?: string; applicationId?: string; size?: number; }): string | undefined;
getEmojiURL(data: { id: string, animated: boolean, size: number, forcePNG?: boolean; }): string;
hasAnimatedGuildIcon(guild: Guild): boolean;
isAnimatedIconHash(hash: string): boolean;
getGuildSplashURL: any;
getGuildDiscoverySplashURL: any;
getGuildHomeHeaderURL: any;
getResourceChannelIconURL: any;
getNewMemberActionIconURL: any;
getGuildTemplateIconURL: any;
getApplicationIconURL: any;
getGameAssetURL: any;
getVideoFilterAssetURL: any;
getGuildMemberAvatarSource: any;
getUserAvatarSource: any;
getGuildSplashSource: any;
getGuildDiscoverySplashSource: any;
makeSource: any;
getGameAssetSource: any;
getGuildIconSource: any;
getGuildTemplateIconSource: any;
getGuildBannerSource: any;
getGuildHomeHeaderSource: any;
getChannelIconSource: any;
getApplicationIconSource: any;
getAnimatableSourceWithFallback: any;
}

View file

@ -137,3 +137,5 @@ export const { persist: zustandPersist }: typeof import("zustand/middleware") =
export const MessageActions = findByPropsLazy("editMessage", "sendMessage"); export const MessageActions = findByPropsLazy("editMessage", "sendMessage");
export const UserProfileActions = findByPropsLazy("openUserProfileModal", "closeUserProfileModal"); export const UserProfileActions = findByPropsLazy("openUserProfileModal", "closeUserProfileModal");
export const InviteActions = findByPropsLazy("resolveInvite"); export const InviteActions = findByPropsLazy("resolveInvite");
export const IconUtils: t.IconUtils = findByPropsLazy("getGuildBannerURL", "getUserAvatarURL");