diff --git a/src/equicordplugins/autoJump/README.md b/src/equicordplugins/autoJump/README.md
deleted file mode 100644
index e361254ca..000000000
--- a/src/equicordplugins/autoJump/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# AutoJump
-
-Jumps to the first message in new messages for a channel
diff --git a/src/equicordplugins/autoJump/index.tsx b/src/equicordplugins/autoJump/index.tsx
deleted file mode 100644
index b05ba1e64..000000000
--- a/src/equicordplugins/autoJump/index.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { NavContextMenuPatchCallback } from "@api/ContextMenu";
-import { definePluginSettings } from "@api/Settings";
-import { EquicordDevs } from "@utils/constants";
-import definePlugin, { OptionType } from "@utils/types";
-import { ChannelStore, Menu, MessageActions, NavigationRouter } from "@webpack/common";
-
-interface ChannelSelectEvent {
- type: "CHANNEL_SELECT";
- channelId: string | null;
- guildId: string | null;
-}
-
-let lastChannelId = "0";
-
-function autoJump({ guild_id, id: channelId }) {
- const guildId = guild_id ?? "@me";
-
- lastChannelId = channelId;
- NavigationRouter.transitionTo(`/channels/${guildId}/${channelId}`);
- MessageActions.jumpToPresent(channelId, { limit: null });
-}
-
-const MenuPatch: NavContextMenuPatchCallback = (children, { channel }) => {
- children.push(
-
{
- autoJump(channel);
- }}
- />
- );
-};
-
-const settings = definePluginSettings({
- autoJumping: {
- type: OptionType.BOOLEAN,
- description: "Automatically jump to the last message in the channel when switching channels",
- default: false
- }
-});
-
-export default definePlugin({
- name: "AutoJump",
- description: "Jumps to Last Message in Channel",
- authors: [EquicordDevs.omaw],
- settings,
- contextMenus: {
- "channel-context": MenuPatch,
- "user-context": MenuPatch,
- "thread-context": MenuPatch
- },
- flux: {
- async CHANNEL_SELECT({ guildId, channelId }: ChannelSelectEvent) {
- if (!settings.store.autoJumping || !channelId) return;
-
- const channel = ChannelStore.getChannel(channelId);
- if (!channel || channel.id === lastChannelId) return;
-
- autoJump({ guild_id: guildId, id: channelId });
- }
- }
-});
diff --git a/src/equicordplugins/channelTabs/index.tsx b/src/equicordplugins/channelTabs/index.tsx
index 2a693b24a..b699f2d21 100644
--- a/src/equicordplugins/channelTabs/index.tsx
+++ b/src/equicordplugins/channelTabs/index.tsx
@@ -80,8 +80,8 @@ export default definePlugin({
{
find: "(this,\"handleMessageClick\"",
replacement: {
- match: /(?<=(\i)\.isSearchHit\));(?=null!=(\i))/,
- replace: ";if ($1.ctrlKey) return $self.open($2);"
+ match: /(\i)\.stopPropagation.{0,50}(?=null!=(\i))/,
+ replace: "$&if ($1.ctrlKey) return $self.open($2);"
}
},
// prevent issues with the pins/inbox popouts being too tall
diff --git a/src/equicordplugins/chatButtonsPlus/index.tsx b/src/equicordplugins/chatButtonsPlus/index.tsx
index 3634d67d1..5a8836acf 100644
--- a/src/equicordplugins/chatButtonsPlus/index.tsx
+++ b/src/equicordplugins/chatButtonsPlus/index.tsx
@@ -8,7 +8,7 @@ import "./styles.css";
import { ChatBarButton } from "@api/ChatButtons";
import { DataStore } from "@api/index";
-import { definePluginSettings, Settings } from "@api/Settings";
+import { definePluginSettings, migratePluginSettings, Settings } from "@api/Settings";
import { EquicordDevs } from "@utils/constants";
import { getCurrentChannel, sendMessage } from "@utils/discord";
import { useForceUpdater } from "@utils/react";
@@ -223,6 +223,7 @@ const settings = definePluginSettings({
}
});
+migratePluginSettings("ChatButtonsPlus", "Meow", "Woof");
export default definePlugin({
name: "ChatButtonsPlus",
description: "Add custom chat buttons with personalized + messages and SVG icons",
diff --git a/src/equicordplugins/customTimestamps/index.tsx b/src/equicordplugins/customTimestamps/index.tsx
index 087e36700..f9eab235d 100644
--- a/src/equicordplugins/customTimestamps/index.tsx
+++ b/src/equicordplugins/customTimestamps/index.tsx
@@ -7,12 +7,14 @@
import "./style.css";
import { definePluginSettings, useSettings } from "@api/Settings";
+import ErrorBoundary from "@components/ErrorBoundary";
import { Link } from "@components/Link";
import { Devs, EquicordDevs } from "@utils/constants";
import { Margins } from "@utils/margins";
import { useForceUpdater } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types";
-import { Forms, moment, TextInput, useEffect, useState } from "@webpack/common";
+import { findByCodeLazy, findComponentByCodeLazy } from "@webpack";
+import { Forms, moment, TextInput, useEffect, useRef, UserStore, useState } from "@webpack/common";
type TimeFormat = {
name: string;
@@ -66,9 +68,9 @@ const timeFormats: Record = {
},
lastWeekFormat: {
name: "Last week",
- description: "[calendar] format for last week",
+ description: "[calendar] format for within the last week",
default: "ddd DD.MM.YYYY HH:mm:ss",
- offset: -1000 * 60 * 60 * 24 * 7,
+ offset: -1000 * 60 * 60 * 24 * 6, // setting an offset of a week exactly pushes it into "older date" territory as soon as a second passes
},
sameElseFormat: {
name: "Older date",
@@ -102,31 +104,80 @@ const format = (date: Date, formatTemplate: string): string => {
const TimeRow = (props: TimeRowProps) => {
const [state, setState] = useState(props.pluginSettings?.[props.id] || props.format.default);
- const [preview, setPreview] = useState("");
const handleChange = (value: string) => {
setState(value);
props.onChange(props.id, value);
};
- const updatePreview = () => setPreview(format(new Date(Date.now() + props.format.offset), state || props.format.default));
-
- useEffect(() => {
- updatePreview();
- const interval = setInterval(updatePreview, 1000);
- return () => clearInterval(interval);
- }, [state]);
-
return (
<>
{props.format.name}
{props.format.description}
- {preview}
>
);
};
+const MessagePreview = findComponentByCodeLazy<{
+ author: any,
+ message: any,
+ compact: boolean,
+ isGroupStart: boolean,
+ className: string,
+ hideSimpleEmbedContent: boolean;
+}>(/previewGuildId:\i,preview:\i,/);
+const createBotMessage = findByCodeLazy('username:"Clyde"');
+const populateMessagePrototype = findByCodeLazy("isProbablyAValidSnowflake", "messageReference:");
+
+const DemoMessage = (props: { msgId, compact, message, date: Date | undefined, isGroupStart?: boolean; }) => {
+ const message = createBotMessage({ content: props.message, channelId: "1337", embeds: [] });
+ message.author = UserStore.getCurrentUser();
+ message.id = props.msgId;
+ message.timestamp = moment(props.date ?? new Date());
+ const user = UserStore.getCurrentUser();
+ const populatedMessage = message && populateMessagePrototype(message);
+ return populatedMessage ? (
+
+
+
+ ) :
+
+ {/* @ts-ignore */}
+ Preview: {Vencord.Plugins.plugins.CustomTimestamps.renderTimestamp(date, "cozy")}
+
+
;
+};
+
+const DemoMessageContainer = ErrorBoundary.wrap(() => {
+ const [isCompact, setIsCompact] = useState(false);
+ const today = useRef(new Date());
+ const yesterday = useRef(new Date(Date.now() + timeFormats.lastDayFormat.offset));
+ const lastWeek = useRef(new Date(Date.now() + timeFormats.lastWeekFormat.offset));
+ const aMonthAgo = useRef(new Date(Date.now() + timeFormats.sameElseFormat.offset));
+
+ return (
+ setIsCompact(!isCompact)}>
+
+
+
+
+
+ );
+}, { noop: true });
+
const settings = definePluginSettings({
formats: {
type: OptionType.COMPONENT,
@@ -140,25 +191,29 @@ const settings = definePluginSettings({
componentProps.setValue(newSettings);
};
- return Object.entries(timeFormats).map(([key, value]) => (
-
- {key === "sameDayFormat" && (
-
-
- Calendar formats
-
- How to format the [calendar] value if used in the above timestamps.
-
-
- )}
-
-
- ));
+ return (
+ <>
+
+ {Object.entries(timeFormats).map(([key, value]) => (
+
+ {key === "sameDayFormat" && (
+
+
+ Calendar formats
+
+ How to format the [calendar] value if used in the above timestamps.
+
+
+ )}
+
+
+ ))}
+ >);
}
}
}).withPrivateSettings<{
@@ -230,16 +285,16 @@ export default definePlugin({
switch (type) {
case "cozy":
- formatTemplate = settings.use(["formats"]).formats?.cozyFormat || timeFormats.cozyFormat.default;
+ formatTemplate = settings.store.formats?.cozyFormat || timeFormats.cozyFormat.default;
break;
case "compact":
- formatTemplate = settings.use(["formats"]).formats?.compactFormat || timeFormats.compactFormat.default;
+ formatTemplate = settings.store.formats?.compactFormat || timeFormats.compactFormat.default;
break;
case "tooltip":
- formatTemplate = settings.use(["formats"]).formats?.tooltipFormat || timeFormats.tooltipFormat.default;
+ formatTemplate = settings.store.formats?.tooltipFormat || timeFormats.tooltipFormat.default;
break;
case "ariaLabel":
- formatTemplate = settings.use(["formats"]).formats?.ariaLabelFormat || timeFormats.ariaLabelFormat.default;
+ formatTemplate = settings.store.formats?.ariaLabelFormat || timeFormats.ariaLabelFormat.default;
}
useEffect(() => {
diff --git a/src/equicordplugins/customTimestamps/style.css b/src/equicordplugins/customTimestamps/style.css
index 0314d70ab..b6102466c 100644
--- a/src/equicordplugins/customTimestamps/style.css
+++ b/src/equicordplugins/customTimestamps/style.css
@@ -6,7 +6,12 @@
margin: 10px 0;
}
-.vc-cmt-preview-text {
- color: yellow;
- margin: 8px 0;
+.vc-cmt-demo-message-container{
+ position: sticky;
+ background: var(--background-base-lowest);
+ padding: 10px 0;
+ cursor: pointer;
+ top: -8px;
+ border: solid grey 1px;
+ border-radius: 5px;
}
diff --git a/src/equicordplugins/equicordToolbox/index.tsx b/src/equicordplugins/equicordToolbox/index.tsx
index ea175d513..84b8c6884 100644
--- a/src/equicordplugins/equicordToolbox/index.tsx
+++ b/src/equicordplugins/equicordToolbox/index.tsx
@@ -152,9 +152,10 @@ export default definePlugin({
patches: [
{
- find: '"M9 3v18"',
+ find: '?"BACK_FORWARD_NAVIGATION":',
replacement: {
- match: /focusSectionProps:"HELP".{0,20},className:(\i\.button)\}\),/,
+ // TODO: (?:\.button) is for stable compat and should be removed soon:tm:
+ match: /focusSectionProps:"HELP".{0,20},className:(\i(?:\.button)?)\}\),/,
replace: "$& $self.renderVencordPopoutButton($1),"
}
}
diff --git a/src/equicordplugins/inviteDefaults/index.ts b/src/equicordplugins/inviteDefaults/index.ts
index 1cb5baf1d..288622324 100644
--- a/src/equicordplugins/inviteDefaults/index.ts
+++ b/src/equicordplugins/inviteDefaults/index.ts
@@ -52,7 +52,7 @@ export default definePlugin({
find: ".GUILD_CREATE_INVITE_SUGGESTION,defaultMaxAge",
replacement: [
{
- match: /(?<=maxAge:)null!=\(\i=null!=\i\?\i:\i\)\?\i:\i.value/,
+ match: /(?<=maxAge:)null!=\(\i=null!=\i\?\i:\i\)\?\i:\i.\i/,
replace: "$self.settings.store.inviteDuration"
},
{
diff --git a/src/equicordplugins/mediaPlaybackSpeed/components/SpeedIcon.tsx b/src/equicordplugins/mediaPlaybackSpeed/components/SpeedIcon.tsx
index c6d4a907d..b23a358bb 100644
--- a/src/equicordplugins/mediaPlaybackSpeed/components/SpeedIcon.tsx
+++ b/src/equicordplugins/mediaPlaybackSpeed/components/SpeedIcon.tsx
@@ -13,7 +13,7 @@ function SpeedIcon() {
fill="currentColor"
viewBox="0 -960 960 960"
>
-
+
);
}
diff --git a/src/equicordplugins/mediaPlaybackSpeed/index.tsx b/src/equicordplugins/mediaPlaybackSpeed/index.tsx
index b2de586c1..39aa162aa 100644
--- a/src/equicordplugins/mediaPlaybackSpeed/index.tsx
+++ b/src/equicordplugins/mediaPlaybackSpeed/index.tsx
@@ -11,7 +11,7 @@ import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { makeRange, OptionType } from "@utils/types";
-import { ContextMenuApi, FluxDispatcher, Heading, Menu, React, Tooltip, useEffect } from "@webpack/common";
+import { ContextMenuApi, FluxDispatcher, Menu, React, Tooltip, useEffect } from "@webpack/common";
import { RefObject } from "react";
import SpeedIcon from "./components/SpeedIcon";
@@ -23,15 +23,6 @@ const max = 3.5;
const speeds = makeRange(min, max, 0.25);
const settings = definePluginSettings({
- test: {
- type: OptionType.COMPONENT,
- description: "",
- component() {
- return
- Default playback speeds
- ;
- }
- },
defaultVoiceMessageSpeed: {
type: OptionType.SLIDER,
default: 1,
@@ -61,7 +52,7 @@ export default definePlugin({
settings,
- PlaybackSpeedComponent({ mediaRef }: { mediaRef: MediaRef; }) {
+ renderPlaybackSpeedComponent: ErrorBoundary.wrap(({ mediaRef }: { mediaRef: MediaRef; }) => {
const changeSpeed = (speed: number) => {
const media = mediaRef?.current;
if (media) {
@@ -70,11 +61,16 @@ export default definePlugin({
};
useEffect(() => {
- if (!mediaRef?.current) return;
- const media = mediaRef.current;
+ const media = mediaRef?.current;
+ if (!media) return;
if (media.tagName === "AUDIO") {
const isVoiceMessage = media.className.includes("audioElement_");
- changeSpeed(isVoiceMessage ? settings.store.defaultVoiceMessageSpeed : settings.store.defaultAudioSpeed);
+ if (isVoiceMessage) {
+ // Workaround because Discord seems to override it somewhere
+ media.addEventListener("play", () => { changeSpeed(settings.store.defaultVoiceMessageSpeed); }, { once: true });
+ } else {
+ changeSpeed(settings.store.defaultAudioSpeed);
+ }
} else if (media.tagName === "VIDEO") {
changeSpeed(settings.store.defaultVideoSpeed);
}
@@ -113,21 +109,15 @@ export default definePlugin({
)}
);
- },
-
- renderComponent(mediaRef: MediaRef) {
- return
-
- ;
- },
+ }),
patches: [
- // voice message embeds
+ // replace voice message embed speed control because ours provides more speeds
{
find: "\"--:--\"",
replacement: {
- match: /onVolumeShow:\i,onVolumeHide:\i\}\)(?<=useCallback\(\(\)=>\{let \i=(\i).current;.+?)/,
- replace: "$&,$self.renderComponent($1)"
+ match: /\(0,\i\.jsxs?\)\(.{0,50}\.playbackRateContainer.+?}\)}\)(?<=playbackCacheKey:\i\}=\i,(\i).+?)/,
+ replace: "$self.renderPlaybackSpeedComponent({mediaRef:$1})"
}
},
// audio & video embeds
@@ -142,8 +132,8 @@ export default definePlugin({
{
find: "AUDIO:\"AUDIO\"",
replacement: {
- match: /onVolumeHide:\i,iconClassName:\i.controlIcon,iconColor:"currentColor",sliderWrapperClassName:\i.volumeSliderWrapper\}\)\}\),/,
- replace: "$&$self.renderComponent(this.props.mediaRef),"
+ match: /\i.volumeSliderWrapper\}\)\}\),/,
+ replace: "$&$self.renderPlaybackSpeedComponent({mediaRef:this?.props?.mediaRef}),"
}
}
]
diff --git a/src/equicordplugins/mediaPlaybackSpeed/styles.css b/src/equicordplugins/mediaPlaybackSpeed/styles.css
index 816190a48..e14279a86 100644
--- a/src/equicordplugins/mediaPlaybackSpeed/styles.css
+++ b/src/equicordplugins/mediaPlaybackSpeed/styles.css
@@ -7,4 +7,4 @@
.vc-media-playback-speed-icon:hover {
color: var(--interactive-active);
-}
+}
\ No newline at end of file
diff --git a/src/equicordplugins/rpcEditor/index.tsx b/src/equicordplugins/rpcEditor/index.tsx
index 59c56ec78..b623caacf 100644
--- a/src/equicordplugins/rpcEditor/index.tsx
+++ b/src/equicordplugins/rpcEditor/index.tsx
@@ -109,7 +109,7 @@ export default definePlugin({
{
find: '"LocalActivityStore"',
replacement: {
- match: /\i\(\i\)\{.{0,25}activity:(\i)\}=\i;/,
+ match: /\i\(\i\)\{.{0,25}activity:(\i).*?\}=\i;/,
replace: "$&$self.patchActivity($1);",
}
}
diff --git a/src/equicordplugins/showMessageEmbeds/index.tsx b/src/equicordplugins/showMessageEmbeds/index.tsx
new file mode 100644
index 000000000..d224923d3
--- /dev/null
+++ b/src/equicordplugins/showMessageEmbeds/index.tsx
@@ -0,0 +1,212 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { findGroupChildrenByChildId } from "@api/ContextMenu";
+import { updateMessage } from "@api/MessageUpdater";
+import { ImageInvisible, ImageVisible } from "@components/Icons";
+import { EquicordDevs } from "@utils/constants";
+import { Logger } from "@utils/Logger";
+import { parseUrl } from "@utils/misc";
+import definePlugin from "@utils/types";
+import { Message } from "@vencord/discord-types";
+import { findByCodeLazy } from "@webpack";
+import { ChannelStore, Constants, Menu, MessageStore, React, RestAPI, showToast, Toasts } from "@webpack/common";
+
+const logger = new Logger("ShowMessageEmbeds");
+
+const addShowEmbedButton = (children, props) => {
+ if (props.itemSrc || !props.itemHref || !props.message) return; // itemSrc means the right clicked item is an image/attachment
+
+ const group = findGroupChildrenByChildId("copy-native-link", children);
+ if (!group) return;
+
+ const { message } = props;
+
+ addButton(group, message, props.itemHref);
+};
+
+const addShowAttachmentEmbedButton = (children, props) => {
+ if (!props.attachmentUrl) return;
+
+ const message = MessageStore.getMessage(props.channelId, props.messageId);
+ if (!message) return;
+
+ addButton(children, message, props.attachmentUrl);
+};
+
+const addButton = (children, message, url) => {
+ url = normaliseUrl(url);
+
+ if (!isEmbedInMessage(message, url)) {
+ children.splice(0, 0,
+ unfurlEmbed(url, message)}
+ icon={ImageVisible}
+ key="vc-sme-show" />);
+ } else if (isUrlInMessage(message, url)) { // check the url is actually in the message text so we know it's one people can actually add back
+ children.splice(0, 0,
+ removeEmbed(url, message)}
+ icon={ImageInvisible}
+ key="vc-sme-remove" />);
+ }
+};
+
+function isEmbedInMessage(message: Message, url: string): boolean {
+ return message?.embeds?.some((embed: any) => {
+ return embed?.url === url;
+ }) || message?.attachments?.some((attachment: any) => {
+ return attachment?.url === url;
+ });
+}
+
+function isUrlInMessage(message: Message, url: string): boolean {
+ const urls = message?.content?.match(/https?:\/\/[^\s]+/g) || [];
+ for (const u of urls) {
+ if (u === url || normaliseUrl(u) === url) {
+ return true;
+ }
+ }
+ for (const embed of message.embeds) {
+ // check if the embed content contains the url but not the embed.url property itself
+ if (embed.url !== url && (embed.rawDescription?.includes(url) || embed.fields.some((field: any) => field.value.includes(url)))) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// special cases where the unfurl api endpoint returns an embed with a different url than the one we requested
+// e.g. you request an embed for a youtu.be link and the returned object has { ... url: youtube.com }
+// this is not an exhaustive list, may need to add more cases in the future
+function normaliseUrl(url: string): string {
+ // normalise youtube urls to the /watch?v= format (t param is replaced with start, v always comes first)
+ const youtubeRegex = /(https?:\/\/)?(?:m\.|www\.)?(youtu\.be|youtube\.com)\/(embed\/|v\/|watch\?v=|watch\?.+&v=|shorts\/)?((\w|-){11})(?:\S+)?/;
+
+ if (youtubeRegex.test(url)) {
+ const urlObj = new URL(url);
+ const params = new URLSearchParams(urlObj.search);
+ let start = 0;
+
+ if (params.has("t") || params.has("start")) {
+ const startParam = params.get("start") || params.get("t");
+ if (startParam && startParam.match(/^(?:(\d+h)?(\d+m)?(\d+s)?(\d+)?)?$/)) {
+ const hours = startParam.match(/(\d+)h/);
+ const minutes = startParam.match(/(\d+)m/);
+ const seconds = startParam.match(/(\d+)s/);
+ const unqualifiedSeconds = startParam.match(/(\d+)$/);
+ if (hours) start += parseInt(hours[1]) * 3600;
+ if (minutes) start += parseInt(minutes[1]) * 60;
+ if (seconds) start += parseInt(seconds[1]);
+ if (unqualifiedSeconds) start += parseInt(unqualifiedSeconds[1]);
+ }
+ }
+ url = url.replace(youtubeRegex, `https://www.youtube.com/watch?v=$4${start ? "&start=" + start : ""}`);
+ }
+
+ // x.com links still return an embed with url twitter.com at the moment
+ const xDotComRegex = /(https?:\/\/(?:www\.)?)x\.com(\/.*)?/;
+
+ if (xDotComRegex.test(url)) {
+ url = url.replace(xDotComRegex, (match, p1, p2) => p1 + "twitter.com" + (p2 || ""));
+ }
+
+ // tiktok adds ?enable_tiktok_webview=true
+ const tiktokRegex = /(https?:\/\/(?:www\.)?)tiktok\.com(\/.*)?/;
+ const searchParams = url.includes("?") ? new URLSearchParams(url.split("?")[1]) : new URLSearchParams();
+ if (tiktokRegex.test(url) && !searchParams.has("enable_tiktok_webview")) {
+ searchParams.append("enable_tiktok_webview", "true");
+ const newSearch = searchParams.toString();
+ url = url.split("?")[0] + (newSearch ? "?" + newSearch : "");
+ }
+
+ return url;
+}
+
+const convertEmbed = findByCodeLazy(".uniqueId(\"embed_\")");
+
+async function unfurlEmbed(url: string, message: Message) {
+ const channel = ChannelStore.getChannel(message.channel_id);
+
+ if (!parseUrl(url)) {
+ return;
+ }
+
+ const resp = await RestAPI.post({
+ url: Constants.Endpoints.UNFURL_EMBED_URLS,
+ body: {
+ urls: [url]
+ }
+ }).catch(e => {
+ showFailureToast("Failed to get embed");
+ logger.error("Failed to get embed", e);
+ });
+
+ if (!resp?.body?.embeds || resp.body.embeds.length === 0) {
+ showFailureToast("No embeds found");
+ return;
+ }
+
+ const { embeds } = resp.body;
+ const convertedEmbeds: any = [];
+
+ for (const embed of embeds) {
+ try {
+ const convertedEmbed = convertEmbed(channel.id, message.id, embed);
+ if (!convertedEmbed) {
+ showFailureToast("Failed to get embed");
+ logger.error("embed object couldn't be converted", embed);
+ continue;
+ }
+ convertedEmbeds.push(convertedEmbed);
+ } catch (e) {
+ showFailureToast("Failed to get embed");
+ logger.error("Failed to convert embed", e);
+ }
+ }
+
+ const newEmbeds = [...message.embeds, ...convertedEmbeds];
+
+ newEmbeds.sort((a: any, b: any) => {
+ return message.content.indexOf(a.url) - message.content.indexOf(b.url);
+ });
+
+ updateMessage(message.channel_id, message.id, { embeds: newEmbeds });
+}
+
+function removeEmbed(url: string, message: Message) {
+ const newEmbeds = message.embeds.filter((embed: any) => embed.url !== url);
+ updateMessage(message.channel_id, message.id, { embeds: newEmbeds });
+}
+
+function showFailureToast(message: string) {
+ showToast(message, Toasts.Type.FAILURE, { position: Toasts.Position.BOTTOM });
+}
+
+export default definePlugin({
+ name: "ShowMessageEmbeds",
+ description: "Adds a context menu option to show embeds for links that don't have one",
+ authors: [EquicordDevs.Suffocate],
+
+ patches: [
+ {
+ find: "className:\"attachmentLink\",",
+ replacement: {
+ match: /(?:(\i).noStyleAndInteraction.*?)attachmentName:\i.attachmentName/,
+ replace: "$&,channelId:$1.channelId,messageId:$1.messageId",
+ }
+ }
+ ],
+
+ contextMenus: {
+ "message": addShowEmbedButton,
+ "attachment-link-context": addShowAttachmentEmbedButton
+ }
+});
diff --git a/src/equicordplugins/spotifyActivityToggle/index.tsx b/src/equicordplugins/spotifyActivityToggle/index.tsx
index a9f3b2831..ebee29f76 100644
--- a/src/equicordplugins/spotifyActivityToggle/index.tsx
+++ b/src/equicordplugins/spotifyActivityToggle/index.tsx
@@ -4,12 +4,11 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
-import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { EquicordDevs } from "@utils/constants";
-import definePlugin, { OptionType } from "@utils/types";
+import definePlugin from "@utils/types";
import { findComponentByCodeLazy } from "@webpack";
-import { Constants, React, RestAPI } from "@webpack/common";
+import { Constants, React, RestAPI, useEffect, useState } from "@webpack/common";
const Button = findComponentByCodeLazy(".NONE,disabled:", ".PANEL_BUTTON");
@@ -35,55 +34,54 @@ function makeSpotifyIcon(enabled: boolean) {
);
}
-async function setSpotifyActivity(value: boolean) {
- const { body } = await RestAPI.get({
- url: Constants.Endpoints.CONNECTIONS
- });
-
- if (!body) return;
- const spotifyId = body.find(conn => conn.type === "spotify")?.id;
-
- await RestAPI.patch({
- url: Constants.Endpoints.CONNECTION("spotify", spotifyId),
- body: {
- show_activity: value,
- },
- });
-}
-
function SpotifyActivityToggleButton() {
- const { spotifyConnection } = settings.store;
+ const [loading, setLoading] = useState(true);
+ const [id, setId] = useState(null);
+ const [showActivity, setShowActivity] = useState(null);
+
+ useEffect(() => {
+ (async () => {
+ const { body } = await RestAPI.get({
+ url: Constants.Endpoints.CONNECTIONS,
+ });
+ if (!body) return;
+
+ const spotifyConn = body.find(c => c.type === "spotify");
+ if (spotifyConn) {
+ setId(spotifyConn.id);
+ setShowActivity(spotifyConn.show_activity);
+ }
+ setLoading(false);
+ })();
+ }, []);
+
+ if (loading || !id || showActivity === null) return null;
return (