Merge branch 'dev' into translation
This commit is contained in:
commit
6498cd1787
41 changed files with 324 additions and 224 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "equicord",
|
||||
"private": "true",
|
||||
"version": "1.14.3.1",
|
||||
"version": "1.14.4.0",
|
||||
"description": "The other cutest Discord client mod",
|
||||
"homepage": "https://github.com/Equicord/Equicord#readme",
|
||||
"bugs": {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ export interface Settings {
|
|||
themeNames: Record<string, string>;
|
||||
enableReactDevtools: boolean;
|
||||
themeLinks: string[];
|
||||
mainWindowFrameless: boolean;
|
||||
frameless: boolean;
|
||||
transparent: boolean;
|
||||
winCtrlQ: boolean;
|
||||
|
|
@ -116,6 +117,7 @@ const DefaultSettings: Settings = {
|
|||
pinnedThemes: [],
|
||||
themeNames: {},
|
||||
enableReactDevtools: false,
|
||||
mainWindowFrameless: false,
|
||||
frameless: false,
|
||||
transparent: false,
|
||||
winCtrlQ: false,
|
||||
|
|
|
|||
|
|
@ -68,3 +68,22 @@ export function InviteButton({
|
|||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export function TranslateButton({
|
||||
className,
|
||||
...props
|
||||
}: Partial<ButtonProps>) {
|
||||
const link = "https://weblate.equicord.org/projects/equicord/";
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
variant="none"
|
||||
size="medium"
|
||||
type="button"
|
||||
onClick={() => VencordNative.native.openExternal(link)}
|
||||
className={className || "vc-translate-button"}
|
||||
>
|
||||
Translate Here
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,18 @@
|
|||
background-color: color-mix(in srgb, #c3a3ce 50%, rgb(240 178 240));
|
||||
}
|
||||
|
||||
.vc-translate-button {
|
||||
overflow: visible !important;
|
||||
color: black;
|
||||
background-color: color-mix(in srgb, #575757 50%, rgb(255 255 255));
|
||||
}
|
||||
|
||||
.vc-translate-button:hover {
|
||||
overflow: visible !important;
|
||||
color: black;
|
||||
background-color: color-mix(in srgb, #575757 50%, rgb(201 201 201));
|
||||
}
|
||||
|
||||
.vc-invite-link {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
|
@ -142,4 +154,14 @@
|
|||
|
||||
.vc-special-image {
|
||||
width: 65%;
|
||||
}
|
||||
|
||||
.vc-translate-modal-icon {
|
||||
margin: auto;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.vc-translate-modal-paragraph {
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -5,17 +5,19 @@
|
|||
.vc-author-modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.vc-author-modal-name {
|
||||
text-transform: none;
|
||||
margin: 0;
|
||||
flex-grow: 0;
|
||||
background: var(--background-base-lowest);
|
||||
border-radius: 0 9999px 9999px 0;
|
||||
padding: 6px 0.8em 6px 0.5em;
|
||||
font-size: 20px;
|
||||
height: 20px;
|
||||
line-height: 1;
|
||||
position: relative;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
|
@ -35,9 +37,17 @@
|
|||
}
|
||||
|
||||
.vc-author-modal-avatar {
|
||||
flex-shrink: 0;
|
||||
transform: translateX(4px);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--background-primary);
|
||||
box-shadow:
|
||||
0 0 0 1px rgb(255 255 255 / 18%),
|
||||
0 2px 6px rgb(0 0 0 / 35%);
|
||||
}
|
||||
|
||||
.vc-author-modal-root+[class*="footer"] {
|
||||
|
|
|
|||
|
|
@ -123,14 +123,15 @@ function CloudTab() {
|
|||
Choose which cloud backend to use for storing your settings. You can switch between Equicord's and Vencord's cloud services, or use a self-hosted instance.
|
||||
</Paragraph>
|
||||
|
||||
<SearchableSelect
|
||||
options={cloudBackendOptions}
|
||||
value={cloudBackendOptions.find(o => o.value === cloud.url)}
|
||||
onChange={v => changeUrl(v)}
|
||||
className={Margins.bottom16}
|
||||
closeOnSelect={true}
|
||||
renderOptionPrefix={o => o?.value?.includes("equicord") ? <EquicordIcon /> : <VencordIcon />}
|
||||
/>
|
||||
<div className={Margins.bottom8}>
|
||||
<SearchableSelect
|
||||
options={cloudBackendOptions}
|
||||
value={cloudBackendOptions.find(o => o.value === cloud.url)?.value}
|
||||
onChange={v => changeUrl(v)}
|
||||
closeOnSelect={true}
|
||||
renderOptionPrefix={o => o?.value?.includes("equicord") ? <EquicordIcon /> : <VencordIcon />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Flex gap="8px" alignItems="center">
|
||||
<div style={{ flex: 1 }}>
|
||||
|
|
|
|||
|
|
@ -82,6 +82,13 @@ function EquicordSettings() {
|
|||
restartRequired: true,
|
||||
warning: { enabled: false },
|
||||
},
|
||||
(!IS_WEB && !IS_DISCORD_DESKTOP || !IS_WINDOWS) && {
|
||||
key: "mainWindowFrameless",
|
||||
title: t("equicord.mainWindowFrameless.title"),
|
||||
description: t("equicord.mainWindowFrameless.description"),
|
||||
restartRequired: true,
|
||||
warning: { enabled: false },
|
||||
},
|
||||
!IS_WEB &&
|
||||
(!IS_DISCORD_DESKTOP || !IS_WINDOWS
|
||||
? {
|
||||
|
|
@ -97,7 +104,8 @@ function EquicordSettings() {
|
|||
description: t("vencord.settings.winNativeTitleBar.description"),
|
||||
restartRequired: true,
|
||||
warning: { enabled: false },
|
||||
}),
|
||||
}
|
||||
),
|
||||
!IS_WEB && {
|
||||
key: "transparent",
|
||||
title: t("vencord.settings.transparent.title"),
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export default definePlugin({
|
|||
|
||||
patches: [
|
||||
{
|
||||
find: ".WIDGETS_RTC_UPSELL_COACHMARK),",
|
||||
find: ".DISPLAY_NAME_STYLES_COACHMARK),",
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=className:(\i)\.\i,style:\i,)children:\[/,
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ export default definePlugin({
|
|||
},
|
||||
{
|
||||
// Always show username
|
||||
find: ".NITRO_PRIVACY_PERK_BETA_COACHMARK));",
|
||||
find: ".DISPLAY_NAME_STYLES_COACHMARK),",
|
||||
replacement: {
|
||||
match: /hoverText:(\i),forceHover:\i,children:/g,
|
||||
replace: "hoverText:$1,forceHover:!0,children:"
|
||||
|
|
|
|||
|
|
@ -189,16 +189,6 @@ export default definePlugin({
|
|||
replace: "{_enableHangStatus:$1=false,"
|
||||
}
|
||||
},
|
||||
// Always show open legacy settings
|
||||
{
|
||||
find: ".DEVELOPER_SECTION,",
|
||||
replacement: [
|
||||
{
|
||||
match: /\i\.\i\.isDeveloper/,
|
||||
replace: "true"
|
||||
},
|
||||
]
|
||||
},
|
||||
// Force Role Icon
|
||||
{
|
||||
find: "Message Username",
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ function GuildSelector() {
|
|||
return (
|
||||
<SearchableSelect
|
||||
options={options}
|
||||
value={options.find(o => o.value === soundGuildId)}
|
||||
value={options.find(o => o.value === soundGuildId)?.value}
|
||||
placeholder={t("equicord.exitSounds.ui.selectServer")}
|
||||
maxVisibleItems={6}
|
||||
closeOnSelect={true}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
import { BaseText } from "@components/BaseText";
|
||||
import { Button } from "@components/Button";
|
||||
import { LazyComponentWrapper } from "@utils/lazyReact";
|
||||
import { Channel, Message, MessageAttachment, ScrollerBaseRef } from "@vencord/discord-types";
|
||||
import { Message, MessageAttachment, ScrollerBaseRef } from "@vencord/discord-types";
|
||||
import { ChannelType } from "@vencord/discord-types/enums";
|
||||
import { findByCodeLazy, findByPropsLazy, findComponentByCode, findComponentByCodeLazy, findCssClassesLazy, proxyLazyWebpack } from "@webpack";
|
||||
import { findByCodeLazy, findComponentByCode, findComponentByCodeLazy, findCssClassesLazy, proxyLazyWebpack } from "@webpack";
|
||||
import { ChannelStore, ExpressionPickerStore, ListScrollerThin, lodash, PermissionsBits, PermissionStore, React, useCallback, useEffect, useMemo, useRef, useState, useStateFromStores } from "@webpack/common";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
|
|
@ -22,11 +22,24 @@ const ManaSearchBar = findComponentByCodeLazy<ManaSearchBarProps>("#{intl::SEARC
|
|||
const FavoriteButton = findComponentByCodeLazy<FavoriteButtonProps>("#{intl::GIF_TOOLTIP_ADD_TO_FAVORITES}");
|
||||
const SendIcon = findComponentByCodeLazy("M6.6 10.02 14 11.4a.6.6");
|
||||
|
||||
const DmChannel: Channel & { new(base?: Partial<Channel>): Channel; } = findByPropsLazy("fromServer", "sortRecipients");
|
||||
const MessageClass: Message & { new(base?: Partial<Message>): Message; } = findByCodeLazy(";mentionChannels;mentionGames;");
|
||||
const createChannelRecordFromServer = findByCodeLazy(".GUILD_TEXT]", "fromServer)");
|
||||
const createMessageRecord = findByCodeLazy(".createFromServer(", ".isBlockedForMessage", "messageReference:");
|
||||
|
||||
const Classes = findCssClassesLazy("gifFavoriteButton", "ctaButtonContainer");
|
||||
|
||||
function createPreviewMessage(attachment: MessageAttachment, channelId: string) {
|
||||
const previewMessage = {
|
||||
id: `favourite-anything-preview-${attachment.id}`,
|
||||
attachments: [attachment],
|
||||
channel_id: channelId,
|
||||
content: "",
|
||||
type: 0,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
return createMessageRecord(previewMessage) as Message;
|
||||
}
|
||||
|
||||
export const AttachmentPreview = proxyLazyWebpack(() => {
|
||||
// findComponentByCodeLazy doesn't work properly with component classes, this must be kept within the lazy scope
|
||||
const MessageComponent = findComponentByCode("this.renderAttachments") as LazyComponentWrapper<MessageComponentClass>;
|
||||
|
|
@ -37,12 +50,12 @@ export const AttachmentPreview = proxyLazyWebpack(() => {
|
|||
}
|
||||
}
|
||||
|
||||
const channel = Object.freeze(new DmChannel({ id: "0", type: ChannelType.GUILD_TEXT }));
|
||||
const channel = Object.freeze(createChannelRecordFromServer({ id: "0", type: ChannelType.GUILD_TEXT }));
|
||||
|
||||
return function AttachmentPreview({ attachment }: AttachmentsComponentProps) {
|
||||
const message = useMemo(
|
||||
() => new MessageClass({ attachments: [attachment], channel_id: channel.id }),
|
||||
[attachment]
|
||||
() => createPreviewMessage(attachment, channel.id),
|
||||
[attachment, channel.id]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -16,8 +16,9 @@ import { ChannelStore, MediaEngineStore, PermissionsBits, PermissionStore, Selec
|
|||
|
||||
import { getCurrentMedia, settings } from "./utils";
|
||||
|
||||
let hasStreamed;
|
||||
let hasStreamed, isStreaming, streamKey;
|
||||
const startStream = findByCodeLazy('type:"STREAM_START"');
|
||||
const stopStream = findByCodeLazy('type:"STREAM_STOP"');
|
||||
const StreamPreviewSettings = getUserSettingLazy("voiceAndVideo", "disableStreamPreviews")!;
|
||||
const ApplicationStreamingSettingsStore = findStoreLazy("ApplicationStreamingSettingsStore");
|
||||
|
||||
|
|
@ -45,14 +46,18 @@ async function autoStartStream(instant = true) {
|
|||
let sourceId = streamMedia.id;
|
||||
if (streamMedia.type === "video_device") sourceId = `camera:${streamMedia.id}`;
|
||||
|
||||
startStream(channel.guild_id ?? null, selected, {
|
||||
"pid": null,
|
||||
"sourceId": sourceId,
|
||||
"sourceName": streamMedia.name,
|
||||
"audioSourceId": streamMedia.name,
|
||||
"sound": soundshareEnabled,
|
||||
"previewDisabled": preview
|
||||
});
|
||||
if (isStreaming && streamKey.endsWith(UserStore.getCurrentUser().id)) {
|
||||
stopStream(streamKey);
|
||||
} else {
|
||||
startStream(channel.guild_id ?? null, selected, {
|
||||
"pid": null,
|
||||
"sourceId": sourceId,
|
||||
"sourceName": streamMedia.name,
|
||||
"audioSourceId": streamMedia.name,
|
||||
"sound": soundshareEnabled,
|
||||
"previewDisabled": preview
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
|
|
@ -123,6 +128,14 @@ export default definePlugin({
|
|||
|
||||
break;
|
||||
}
|
||||
},
|
||||
STREAM_CREATE: d => {
|
||||
streamKey = d;
|
||||
isStreaming = true;
|
||||
},
|
||||
STREAM_DELETE: d => {
|
||||
streamKey = d;
|
||||
isStreaming = false;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ function StreamSimplePicker({ streamMediaSelection, streamMedia }: PickerProps)
|
|||
placeholder="Select a media source to stream "
|
||||
maxVisibleItems={5}
|
||||
options={options}
|
||||
value={options.find(o => o.value === streamMedia)}
|
||||
value={options.find(o => o.value === streamMedia)?.value}
|
||||
onChange={v => settings.store.streamMedia = v}
|
||||
closeOnSelect
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ function Timestamp({ channel }: { channel: Channel; }) {
|
|||
if (!lastMessage) return null;
|
||||
|
||||
const timestamp = SnowflakeUtils.extractTimestamp(lastMessage.id);
|
||||
const className = ExperimentStore.getUserExperimentBucket("2026-01-favorites-server") >= 1 ? cl("timestamp-favorites") : cl("timestamp");
|
||||
const className = ExperimentStore.getUserExperimentBucket("2026-01-favorites-server") > 0 ? cl("timestamp-favorites") : cl("timestamp");
|
||||
return <span className={className}>{formatRelativeTime(timestamp)}</span>;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export const settings = definePluginSettings({
|
|||
addFreakyEnding: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Add 👅 or ❤️ at the end",
|
||||
default: true
|
||||
default: false
|
||||
},
|
||||
uwuEveryMessage: {
|
||||
description: "Make every single message uwuified",
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export default definePlugin({
|
|||
|
||||
patches: [
|
||||
{
|
||||
find: ".WIDGETS_RTC_UPSELL_COACHMARK),",
|
||||
find: ".DISPLAY_NAME_STYLES_COACHMARK),",
|
||||
replacement: {
|
||||
// react.jsx)(AccountPanel, { ..., showTaglessAccountPanel: blah })
|
||||
match: /(?<=\i\.jsxs?\)\()(\i),{(?=[^}]*?userTag:\i,occluded:)/,
|
||||
|
|
|
|||
|
|
@ -68,13 +68,6 @@ export default definePlugin({
|
|||
replace: "$1{...$2,...$self.getPlatform(true)},presence"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "#{intl::POPOUT_STAY_ON_TOP}),icon:",
|
||||
replacement: {
|
||||
match: /(?<=CallTile.{0,15}\.memo\((\i)=>\{)/,
|
||||
replace: "$1.platform = $self.getPlatform(false, $1?.participantUserId)?.vcIcon || $1?.platform;"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: '("AppSkeleton");',
|
||||
replacement: {
|
||||
|
|
@ -89,19 +82,19 @@ export default definePlugin({
|
|||
if (bypass || userId === UserStore.getCurrentUser().id) {
|
||||
switch (platform) {
|
||||
case "desktop":
|
||||
return { browser: "Discord Client", vcIcon: 0 };
|
||||
return { browser: "Discord Client" };
|
||||
case "web":
|
||||
return { browser: "Discord Web", vcIcon: 0 };
|
||||
return { browser: "Discord Web" };
|
||||
case "ios":
|
||||
return { browser: "Discord iOS", vcIcon: 1 };
|
||||
return { browser: "Discord iOS" };
|
||||
case "android":
|
||||
return { browser: "Discord Android", vcIcon: 1 };
|
||||
return { browser: "Discord Android" };
|
||||
case "xbox":
|
||||
return { browser: "Discord Embedded", vcIcon: 2 };
|
||||
return { browser: "Discord Embedded" };
|
||||
case "playstation":
|
||||
return { browser: "Discord Embedded", vcIcon: 3 };
|
||||
return { browser: "Discord Embedded" };
|
||||
case "vr":
|
||||
return { browser: "Discord VR", vcIcon: 4 };
|
||||
return { browser: "Discord VR" };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import { showToast, Toasts, UserStore } from "@webpack/common";
|
|||
|
||||
import { useAuthorizationStore } from "./stores/AuthorizationStore";
|
||||
import { useSongStore } from "./stores/SongStore";
|
||||
import { logger } from "./utils";
|
||||
|
||||
export const apiConstants = {
|
||||
api: "https://dc.songspotlight.nexpid.xyz/",
|
||||
|
|
@ -20,16 +19,36 @@ export const apiConstants = {
|
|||
songLimit: 6,
|
||||
};
|
||||
|
||||
export async function authFetch(_url: string | URL, options?: RequestInit) {
|
||||
const url = new URL(_url);
|
||||
let refreshPromise: Promise<boolean> | undefined;
|
||||
async function refreshAccessToken() {
|
||||
const token = useAuthorizationStore.getState().getToken();
|
||||
if (!token) return false;
|
||||
|
||||
return refreshPromise ??= fetch(new URL("api/auth/refresh", apiConstants.api), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-Refresh-Token": token.refresh,
|
||||
},
|
||||
body: token.access,
|
||||
}).then(async res => {
|
||||
if (!res.ok) return false;
|
||||
|
||||
const access = await res.text();
|
||||
useAuthorizationStore.getState().setToken(access, token.refresh);
|
||||
return true;
|
||||
}).finally(() => refreshPromise = undefined);
|
||||
}
|
||||
|
||||
export async function authFetch(url: string | URL, options?: RequestInit, retried = false) {
|
||||
url = new URL(url);
|
||||
try {
|
||||
const token = useAuthorizationStore.getState().getToken();
|
||||
const res = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
...options?.headers,
|
||||
Authorization: useAuthorizationStore.getState().getToken()!,
|
||||
},
|
||||
Authorization: token?.access,
|
||||
} as HeadersInit,
|
||||
});
|
||||
|
||||
if (res.ok) return res;
|
||||
|
|
@ -37,36 +56,29 @@ export async function authFetch(_url: string | URL, options?: RequestInit) {
|
|||
// not modified
|
||||
if (res.status === 304) return null;
|
||||
|
||||
const text = await res.text();
|
||||
|
||||
// unauthorized
|
||||
if (res.status === 401) {
|
||||
useAuthorizationStore.getState().deleteToken();
|
||||
const retry = !retried && await refreshAccessToken();
|
||||
if (retry) return await authFetch(url, options, true);
|
||||
else {
|
||||
useAuthorizationStore.getState().deleteTokens();
|
||||
showToast("You have been signed out from Song Spotlight. Please sign in again.", Toasts.Type.FAILURE);
|
||||
}
|
||||
} else {
|
||||
showToast(
|
||||
!text.includes("<body>") && res.status >= 400 && res.status <= 599
|
||||
? `Song Spotlight: ${text}`
|
||||
: `Song Spotlight fetch error at ${url.pathname}`,
|
||||
Toasts.Type.FAILURE,
|
||||
);
|
||||
}
|
||||
|
||||
const text = await res.text();
|
||||
showToast(
|
||||
!text.includes("<body>") && res.status >= 400 && res.status <= 599
|
||||
? `Song Spotlight: ${text}`
|
||||
: `Song Spotlight fetch error at ${url.pathname}`,
|
||||
Toasts.Type.FAILURE,
|
||||
);
|
||||
|
||||
logger.error(
|
||||
"Got an authFetch response error",
|
||||
options?.method ?? "GET",
|
||||
url.toString(),
|
||||
res.status,
|
||||
text,
|
||||
);
|
||||
throw new Error(text);
|
||||
} catch (error) {
|
||||
showToast(`Song Spotlight: ${error}`, Toasts.Type.FAILURE);
|
||||
|
||||
logger.error(
|
||||
"Got an authFetch request error",
|
||||
options?.method ?? "GET",
|
||||
url.toString(),
|
||||
error,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,15 +30,22 @@ export function presentOAuth2Modal() {
|
|||
const url = new URL(location);
|
||||
url.searchParams.append("whois", "equicord");
|
||||
|
||||
const token = await (await authFetch(url))?.text();
|
||||
if (!token) return;
|
||||
const res = await authFetch(url);
|
||||
if (!res) throw "Response wasn't ok";
|
||||
|
||||
useAuthorizationStore.getState().setToken(token);
|
||||
const access = await res.text();
|
||||
if (!access) throw "Access token is missing";
|
||||
|
||||
const refresh = res.headers.get("X-Refresh-Token");
|
||||
if (!refresh) throw "Refresh token is missing";
|
||||
|
||||
useAuthorizationStore.getState().setToken(access, refresh);
|
||||
getData();
|
||||
|
||||
showToast("Successfully authorized!", Toasts.Type.SUCCESS);
|
||||
} catch (error) {
|
||||
logger.error("Got an error during OAuth2", error);
|
||||
if (typeof error === "string") showToast(error, Toasts.Type.FAILURE);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -10,11 +10,16 @@ import { UserStore, zustandCreate, zustandPersist } from "@webpack/common";
|
|||
|
||||
import { PersistedZustandStore, ZustandDefinition } from "./zustand";
|
||||
|
||||
export interface Token {
|
||||
access: string;
|
||||
refresh: string;
|
||||
}
|
||||
|
||||
interface AuthorizationState {
|
||||
tokens: Record<string, string>;
|
||||
getToken(): string | undefined;
|
||||
setToken(token: string): void;
|
||||
deleteToken(): void;
|
||||
tokens: Record<string, Token>;
|
||||
getToken(): Token | undefined;
|
||||
setToken(access: string, refresh: string): void;
|
||||
deleteTokens(): void;
|
||||
isAuthorized(): boolean;
|
||||
}
|
||||
|
||||
|
|
@ -26,17 +31,18 @@ export const useAuthorizationStore: PersistedZustandStore<AuthorizationState> =
|
|||
getToken() {
|
||||
return get().tokens[UserStore.getCurrentUser()?.id];
|
||||
},
|
||||
setToken(token) {
|
||||
setToken(access, refresh) {
|
||||
const userId = UserStore.getCurrentUser()?.id;
|
||||
if (userId) {
|
||||
set({
|
||||
tokens: Object.assign(get().tokens, {
|
||||
[userId]: token,
|
||||
}),
|
||||
tokens: {
|
||||
...get().tokens,
|
||||
[userId]: { access, refresh },
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
deleteToken() {
|
||||
deleteTokens() {
|
||||
set({ tokens: {} });
|
||||
},
|
||||
isAuthorized() {
|
||||
|
|
@ -45,11 +51,24 @@ export const useAuthorizationStore: PersistedZustandStore<AuthorizationState> =
|
|||
})) as ZustandDefinition<AuthorizationState>,
|
||||
{
|
||||
name: "songspotlight-auth",
|
||||
version: 1,
|
||||
migrate(persisted: any, version: number) {
|
||||
if (version === 0) {
|
||||
persisted.tokens = Object.fromEntries(
|
||||
Object.entries(persisted.tokens).map(([userId, access]) => [userId, {
|
||||
access,
|
||||
refresh: "",
|
||||
}]),
|
||||
);
|
||||
}
|
||||
|
||||
return persisted;
|
||||
},
|
||||
storage: {
|
||||
async getItem(name: string) {
|
||||
return (await DataStore.get(name)) ?? null;
|
||||
},
|
||||
async setItem(name: string, value: string) {
|
||||
async setItem(name: string, value: unknown) {
|
||||
return await DataStore.set(name, value);
|
||||
},
|
||||
async removeItem(name: string) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
import { definePluginSettings } from "@api/Settings";
|
||||
import { makeRange, OptionType } from "@utils/types";
|
||||
|
||||
import { apiConstants } from "./lib/api";
|
||||
import Settings from "./ui/settings";
|
||||
|
||||
export default definePluginSettings({
|
||||
|
|
@ -18,8 +19,8 @@ export default definePluginSettings({
|
|||
profileSongsLimit: {
|
||||
type: OptionType.SLIDER,
|
||||
description: "How many songs are shown when initially clicking on a user",
|
||||
default: 6,
|
||||
markers: makeRange(1, 6),
|
||||
default: apiConstants.songLimit,
|
||||
markers: makeRange(1, apiConstants.songLimit),
|
||||
},
|
||||
manager: {
|
||||
type: OptionType.COMPONENT,
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ interface SettingsProps {
|
|||
}
|
||||
|
||||
export default function Settings({ templateData }: SettingsProps) {
|
||||
const { isAuthorized, deleteToken } = useAuthorizationStore();
|
||||
const { isAuthorized, deleteTokens } = useAuthorizationStore();
|
||||
const { self } = useSongStore();
|
||||
|
||||
const ticked = useRef(false);
|
||||
|
|
@ -172,7 +172,7 @@ export default function Settings({ templateData }: SettingsProps) {
|
|||
<Button
|
||||
variant="dangerPrimary"
|
||||
onClick={() => {
|
||||
deleteToken();
|
||||
deleteTokens();
|
||||
showToast("Successfully signed out!", Toasts.Type.SUCCESS);
|
||||
}}
|
||||
disabled={pending}
|
||||
|
|
@ -189,7 +189,7 @@ export default function Settings({ templateData }: SettingsProps) {
|
|||
setPending(true);
|
||||
try {
|
||||
await deleteData();
|
||||
deleteToken();
|
||||
deleteTokens();
|
||||
|
||||
showToast("Successfully deleted songs!", Toasts.Type.SUCCESS);
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,12 @@
|
|||
|
||||
import { BaseText } from "@components/index";
|
||||
import { Native } from "@equicordplugins/songSpotlight.desktop/service";
|
||||
import { CardClasses, ContainerClasses, OverlayClasses, Spinner } from "@equicordplugins/songSpotlight.desktop/ui/common";
|
||||
import {
|
||||
CardClasses,
|
||||
ContainerClasses,
|
||||
OverlayClasses,
|
||||
Spinner,
|
||||
} from "@equicordplugins/songSpotlight.desktop/ui/common";
|
||||
import { RenderSongInfo } from "@song-spotlight/api/handlers";
|
||||
import { UserData } from "@song-spotlight/api/structs";
|
||||
import { sid } from "@song-spotlight/api/util";
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export function SetTimezoneModal({ userId, modalProps, database }: { userId: str
|
|||
|
||||
<SearchableSelect
|
||||
options={options}
|
||||
value={options.find(o => o.value === currentValue)}
|
||||
value={options.find(o => o.value === currentValue)?.value}
|
||||
placeholder={"Select a Timezone"}
|
||||
maxVisibleItems={5}
|
||||
closeOnSelect={true}
|
||||
|
|
|
|||
|
|
@ -795,7 +795,7 @@ function VoiceSelectModal({ modalProps, user }: { modalProps: ModalProps; user:
|
|||
<HeadingSecondary>Select voice for {user.username}</HeadingSecondary>
|
||||
<SearchableSelect
|
||||
options={options}
|
||||
value={options.find(o => o.value === currentValue)}
|
||||
value={options.find(o => o.value === currentValue)?.value}
|
||||
placeholder="Select a voice"
|
||||
maxVisibleItems={6}
|
||||
closeOnSelect={true}
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ function LanguageSelectionModal(props: { modalProps: ModalProps, src: string; })
|
|||
</BaseText>
|
||||
<SearchableSelect
|
||||
options={languageOptions}
|
||||
value={languageOptions.find(o => o.value === language)}
|
||||
value={languageOptions.find(o => o.value === language)?.value}
|
||||
onChange={setLanguage}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ if (!IS_VANILLA) {
|
|||
constructor(options: BrowserWindowConstructorOptions) {
|
||||
if (options?.webPreferences?.preload && options.title) {
|
||||
const original = options.webPreferences.preload;
|
||||
const isMainWindow = options.title === "Discord";
|
||||
options.webPreferences.preload = join(__dirname, "preload.js");
|
||||
options.webPreferences.sandbox = false;
|
||||
// work around discord unloading when in background
|
||||
|
|
@ -79,6 +80,8 @@ if (!IS_VANILLA) {
|
|||
|
||||
if (settings.frameless) {
|
||||
options.frame = false;
|
||||
} else if (settings.mainWindowFrameless && isMainWindow) {
|
||||
options.frame = false;
|
||||
} else if (process.platform === "win32" && settings.winNativeTitleBar) {
|
||||
delete options.frame;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import { ContextMenuApi, Menu, Toasts, UserStore } from "@webpack/common";
|
|||
|
||||
import Plugins, { PluginMeta } from "~plugins";
|
||||
|
||||
import { EquicordDonorModal, VencordDonorModal } from "./modals";
|
||||
import { EquicordDonorModal, EquicordTranslatorModal, VencordDonorModal } from "./modals";
|
||||
|
||||
const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/emojis/1092089799109775453.png?size=64";
|
||||
const EQUICORD_CONTRIBUTOR_BADGE = "https://equicord.org/assets/favicon.png";
|
||||
|
|
@ -253,7 +253,7 @@ export default definePlugin({
|
|||
ContextMenuApi.openContextMenu(event, () => <BadgeContextMenu badge={badge} />);
|
||||
},
|
||||
onClick() {
|
||||
return EquicordDonorModal();
|
||||
return badge.tooltip === "Equicord Translator" ? EquicordTranslatorModal() : EquicordDonorModal();
|
||||
},
|
||||
} satisfies ProfileBadge));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { Flex } from "@components/Flex";
|
|||
import { HeadingPrimary } from "@components/Heading";
|
||||
import { Heart } from "@components/Heart";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { DonateButton } from "@components/settings";
|
||||
import { DonateButton, TranslateButton } from "@components/settings";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal";
|
||||
|
||||
|
|
@ -72,7 +72,6 @@ export function EquicordDonorModal() {
|
|||
const modalKey = openModal(props => (
|
||||
<ErrorBoundary noop onError={() => {
|
||||
closeModal(modalKey);
|
||||
// Will get my own in the future
|
||||
VencordNative.native.openExternal("https://github.com/sponsors/thororen1234");
|
||||
}}>
|
||||
<ModalRoot {...props}>
|
||||
|
|
@ -123,3 +122,47 @@ export function EquicordDonorModal() {
|
|||
</ErrorBoundary>
|
||||
));
|
||||
}
|
||||
|
||||
export function EquicordTranslatorModal() {
|
||||
const modalKey = openModal(props => (
|
||||
<ErrorBoundary noop onError={() => {
|
||||
closeModal(modalKey);
|
||||
}}>
|
||||
<ModalRoot {...props}>
|
||||
<ModalHeader>
|
||||
<HeadingPrimary
|
||||
style={{
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
margin: 0
|
||||
}}
|
||||
>
|
||||
<Flex justifyContent="center" alignItems="center" gap="0.5em">
|
||||
Equicord Translator
|
||||
</Flex>
|
||||
</HeadingPrimary>
|
||||
</ModalHeader>
|
||||
<ModalContent>
|
||||
<Flex>
|
||||
<img
|
||||
className="vc-translate-modal-icon"
|
||||
role="presentation"
|
||||
src="https://badge.equicord.org/translator.png"
|
||||
alt=""
|
||||
/>
|
||||
</Flex>
|
||||
<div className="vc-translate-modal-paragraph">
|
||||
<Paragraph>
|
||||
Awarded to contributors who expand Equicord’s language support by translating content for the community.
|
||||
</Paragraph>
|
||||
</div>
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<Flex justifyContent="center" style={{ width: "100%" }}>
|
||||
<TranslateButton />
|
||||
</Flex>
|
||||
</ModalFooter>
|
||||
</ModalRoot>
|
||||
</ErrorBoundary>
|
||||
));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,10 +14,16 @@ export default definePlugin({
|
|||
patches: [
|
||||
{
|
||||
find: "#{intl::USER_PROFILE_PRONOUNS}",
|
||||
replacement: {
|
||||
match: /(?<=shouldUnderlineOnHover:null.{0,200})children:(\i)/,
|
||||
replace: "children:[...Vencord.Api.NicknameIcons._renderIcons({userId:arguments[0].user?.id}),$1]"
|
||||
}
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=,\i\]\}\)\),)null!=\i/,
|
||||
replace: "($&||Vencord.Api.NicknameIcons._renderIcons({userId:arguments[0].user?.id})?.length)"
|
||||
},
|
||||
{
|
||||
match: /(?<=shouldUnderlineOnHover:null.{0,300})children:(\i)/,
|
||||
replace: "children:[...Vencord.Api.NicknameIcons._renderIcons({userId:arguments[0].user?.id}),$1]"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
|
|||
|
|
@ -152,14 +152,6 @@ export default definePlugin({
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
|
||||
replacement: {
|
||||
// Skip the check Discord performs to make sure the section being selected in the user settings context menu is valid
|
||||
match: /null!=\(\i=Object.values\(\i\.\i\).{0,50}?&&(?=\(0,\i\.openUserSettings\)\(\i,\{section:\i)/,
|
||||
replace: ""
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".buildLayout().map",
|
||||
replacement: {
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ export default definePlugin({
|
|||
find: ".MINI_PREVIEW,[",
|
||||
predicate: () => settings.store.nameplates,
|
||||
replacement: {
|
||||
match: /animate:\i,loop:.{0,15}===\i/,
|
||||
match: /animate:\i,loop:(?:!0===\i(?:&&)?){2}/,
|
||||
replace: "animate:true,loop:true"
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ export default definePlugin({
|
|||
// Create the isBetterFolders and betterFoldersExpandedIds variables in the GuildsBar component
|
||||
// Needed because we access this from a non-arrow closure so we can't use arguments[0]
|
||||
{
|
||||
match: /let{disableAppDownload:\i=\i\.isPlatformEmbedded,isOverlay:.+?(?=}=\i,)/,
|
||||
match: /let{disableAppDownload:\i=\i\.isPlatformEmbedded,isOverlay:.+?(?=}=\i)/,
|
||||
replace: "$&,isBetterFolders,betterFoldersExpandedIds"
|
||||
},
|
||||
// Export the isBetterFolders and betterFoldersExpandedIds variable to the Guild List component
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
import { definePluginSettings } from "@api/Settings";
|
||||
import { disableStyle, enableStyle } from "@api/Styles";
|
||||
import { buildPluginMenuEntries, buildThemeMenuEntries } from "@equicordplugins/equicordToolbox/menu";
|
||||
import SettingsPlugin from "@plugins/_core/settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { classNameFactory } from "@utils/css";
|
||||
import { Logger } from "@utils/Logger";
|
||||
|
|
@ -15,22 +14,10 @@ import { t } from "@utils/translation";
|
|||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findCssClassesLazy } from "@webpack";
|
||||
import { ComponentDispatch, FocusLock, Menu, useEffect, useRef } from "@webpack/common";
|
||||
import type { HTMLAttributes, ReactElement } from "react";
|
||||
import type { HTMLAttributes, ReactNode } from "react";
|
||||
|
||||
import fullHeightStyle from "./fullHeightContext.css?managed";
|
||||
|
||||
type SettingsEntry = {
|
||||
ariaLabel?: string;
|
||||
element?: any;
|
||||
label?: string;
|
||||
newIndicator?: any;
|
||||
newIndicatorDismissibleContentTypes?: number[];
|
||||
predicate?: () => boolean;
|
||||
searchableTitles?: string[];
|
||||
section: string,
|
||||
tabPredicate?: () => boolean;
|
||||
};
|
||||
|
||||
const cl = classNameFactory("");
|
||||
const Classes = findCssClassesLazy("animating", "baseLayer", "bg", "layer", "layers");
|
||||
|
||||
|
|
@ -55,11 +42,6 @@ const settings = definePluginSettings({
|
|||
}
|
||||
});
|
||||
|
||||
interface TransformedSettingsEntry {
|
||||
section: string;
|
||||
items: SettingsEntry[];
|
||||
}
|
||||
|
||||
interface LayerProps extends HTMLAttributes<HTMLDivElement> {
|
||||
mode: "SHOWN" | "HIDDEN";
|
||||
baseLayer?: boolean;
|
||||
|
|
@ -170,15 +152,12 @@ export default definePlugin({
|
|||
},
|
||||
{ // Settings cog context menu
|
||||
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
|
||||
predicate: () => settings.store.organizeMenu,
|
||||
replacement: [
|
||||
{
|
||||
match: /=\[\];(\i)(?=\.forEach.{0,200}?"logout"===\i.{0,100}?(\i)\.get\(\i\))/,
|
||||
replace: "=$self.wrapMap([]);$self.transformSettingsEntries($1,$2)",
|
||||
match: /children:\[(\i),(?<=\1=.{0,30}\.openUserSettings.+?)/,
|
||||
replace: "children:[$self.transformSettingsEntries($1),",
|
||||
},
|
||||
{
|
||||
match: /case \i\.\i\.DEVELOPER_OPTIONS:return \i;/,
|
||||
replace: "$&case 'EquicordPlugins':return $self.buildPluginMenuEntries(true);case 'EquicordThemes':return $self.buildThemeMenuEntries();"
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
|
|
@ -201,72 +180,34 @@ export default definePlugin({
|
|||
return <Layer {...props} />;
|
||||
},
|
||||
|
||||
transformSettingsEntries(list: SettingsEntry[], keyMap: Map<string, string>) {
|
||||
const items: any[] = [];
|
||||
const equicordItems: SettingsEntry[] = [
|
||||
{ section: "EquicordSettings", label: "Settings" },
|
||||
{ section: "EquicordPlugins", label: "Plugins" },
|
||||
{ section: "EquicordThemes", label: "Themes" },
|
||||
{ section: "EquicordUpdater", label: "Updater" },
|
||||
{ section: "EquicordChangelog", label: "Changelog" },
|
||||
{ section: "EquicordCloud", label: "Cloud" },
|
||||
{ section: "EquicordBackupAndRestore", label: "Backup & Restore" },
|
||||
{ section: "EquicordPatchHelper", label: "Patch Helper" },
|
||||
];
|
||||
transformSettingsEntries(list) {
|
||||
const items: ReactNode[] = [];
|
||||
|
||||
for (const [section, key] of SettingsPlugin.settingsSectionMap) {
|
||||
const entry = SettingsPlugin.customEntries.find(e => key.endsWith(e.key));
|
||||
if (entry) equicordItems.push({ section, label: entry.title });
|
||||
}
|
||||
for (const item of list) {
|
||||
const { key, props } = item;
|
||||
if (!props) continue;
|
||||
|
||||
if (settings.store.organizeMenu) {
|
||||
for (const item of list) {
|
||||
if (!item.label || item.predicate != null && !item.predicate()) continue;
|
||||
if (key === "equicord_plugins" || key === "equicord_themes") {
|
||||
const children = key === "equicord_plugins"
|
||||
? buildPluginMenuEntries()
|
||||
: buildThemeMenuEntries();
|
||||
|
||||
if (item.section === "HEADER") {
|
||||
keyMap.set(item.label, item.label);
|
||||
items.push({ section: item.label, items: [] });
|
||||
} else if (item.section !== "DIVIDER" && keyMap.has(item.section)) {
|
||||
items.at(-1)?.items.push(item);
|
||||
}
|
||||
items.push(
|
||||
<Menu.MenuItem key={key} label={props.label} id={props.label} {...props}>
|
||||
{children}
|
||||
</Menu.MenuItem>
|
||||
);
|
||||
} else if (key.endsWith("_section") && props.label) {
|
||||
items.push(
|
||||
<Menu.MenuItem key={key} label={props.label} id={props.label}>
|
||||
{this.transformSettingsEntries(props.children)}
|
||||
</Menu.MenuItem>
|
||||
);
|
||||
} else {
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
keyMap.set("Equicord", "Equicord");
|
||||
items.push({
|
||||
section: "Equicord",
|
||||
items: equicordItems
|
||||
});
|
||||
} else {
|
||||
items.push(...list, ...equicordItems);
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
|
||||
wrapMap(toWrap: TransformedSettingsEntry[]) {
|
||||
if (!settings.store.organizeMenu) return toWrap;
|
||||
// @ts-expect-error
|
||||
toWrap.map = function (render: (item: SettingsEntry) => ReactElement<any>) {
|
||||
return this
|
||||
.filter(a => a.items.length > 0)
|
||||
.map(({ section, items }) => {
|
||||
const children = items.map(render);
|
||||
if (section) {
|
||||
return (
|
||||
<Menu.MenuItem
|
||||
key={section}
|
||||
id={section.replace(/\W/, "_")}
|
||||
label={section}
|
||||
>
|
||||
{children}
|
||||
</Menu.MenuItem>
|
||||
);
|
||||
} else {
|
||||
return children;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return toWrap;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ export default definePlugin({
|
|||
},
|
||||
// Current user area, at bottom of channels/dm list
|
||||
{
|
||||
find: ".WIDGETS_RTC_UPSELL_COACHMARK),",
|
||||
find: ".DISPLAY_NAME_STYLES_COACHMARK),",
|
||||
replacement: [
|
||||
// Use Decor avatar decoration hook
|
||||
{
|
||||
|
|
|
|||
|
|
@ -88,14 +88,6 @@ export default definePlugin({
|
|||
replace: (_, rest) => `${rest}onClick:()=>{}`
|
||||
}
|
||||
},
|
||||
// Make the Favourites Server experiment allow favouriting DMs and threads
|
||||
{
|
||||
find: "useCanFavoriteChannel",
|
||||
replacement: {
|
||||
match: /(?<=isFavorite\(\i\.id\).{0,5})\i\.isThread\(\)/,
|
||||
replace: "false",
|
||||
}
|
||||
},
|
||||
// Enable experiment embed on sent experiment links
|
||||
{
|
||||
find: "Clear Treatment ",
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ const settings = definePluginSettings({
|
|||
description: t("vencord.fakeNitro.settings.disableEmbedPermissionCheck"),
|
||||
type: OptionType.BOOLEAN,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function hasPermission(channelId: string, permission: bigint) {
|
||||
|
|
@ -377,7 +377,7 @@ export default definePlugin({
|
|||
match: /(?<=type:"(?:SOUNDBOARD_SOUNDS_RECEIVED|GUILD_SOUNDBOARD_SOUND_CREATE|GUILD_SOUNDBOARD_SOUND_UPDATE|GUILD_SOUNDBOARD_SOUNDS_UPDATE)".+?available:)\i\.available/g,
|
||||
replace: "true"
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
|
||||
get guildId() {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export default definePlugin({
|
|||
patches: [{
|
||||
find: "setDevtoolsCallbacks",
|
||||
replacement: {
|
||||
match: /if\(null!=\i&&"0.0.0"===\i\.remoteApp\.getVersion\(\)\)/,
|
||||
match: /if\(null!=\i&&"0.0.0"===\i\.app\.getVersion\(\)\)/,
|
||||
replace: "if(true)"
|
||||
}
|
||||
}]
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export default definePlugin({
|
|||
|
||||
patches: [
|
||||
{
|
||||
find: '=>"IMAGE"===',
|
||||
find: '"PLAINTEXT_PREVIEW":"OTHER"',
|
||||
replacement: {
|
||||
match: /=>"IMAGE"===\i\|\|"VIDEO"===\i(?:\|\|("VISUAL_PLACEHOLDER"===\i))?;/,
|
||||
replace: (_, visualPlaceholderPred) => visualPlaceholderPred != null ? `=>${visualPlaceholderPred};` : "=>false;"
|
||||
|
|
|
|||
|
|
@ -1263,7 +1263,15 @@ export const EquicordDevs = Object.freeze({
|
|||
Leon135: {
|
||||
name: "Leon135",
|
||||
id: 309275452231385088n,
|
||||
}
|
||||
},
|
||||
bbpltergiest: {
|
||||
name: "bbpltergiest",
|
||||
id: 279448683672502274n,
|
||||
},
|
||||
mshl: {
|
||||
name: "m.shl",
|
||||
id: 1025245410224263258n,
|
||||
},
|
||||
} satisfies Record<string, Dev>);
|
||||
|
||||
// iife so #__PURE__ works correctly
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue