diff --git a/package.json b/package.json index 92a4a33f5..12119c4f5 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/api/Settings.ts b/src/api/Settings.ts index 208c37698..f0db98933 100644 --- a/src/api/Settings.ts +++ b/src/api/Settings.ts @@ -49,6 +49,7 @@ export interface Settings { themeNames: Record; 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, diff --git a/src/components/settings/DonateButton.tsx b/src/components/settings/DonateButton.tsx index 03fad8b3b..19541d3b3 100644 --- a/src/components/settings/DonateButton.tsx +++ b/src/components/settings/DonateButton.tsx @@ -68,3 +68,22 @@ export function InviteButton({ ); } + +export function TranslateButton({ + className, + ...props +}: Partial) { + const link = "https://weblate.equicord.org/projects/equicord/"; + return ( + + ); +} diff --git a/src/components/settings/SpecialCard.css b/src/components/settings/SpecialCard.css index f037aadbd..af663475d 100644 --- a/src/components/settings/SpecialCard.css +++ b/src/components/settings/SpecialCard.css @@ -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; } \ No newline at end of file diff --git a/src/components/settings/tabs/plugins/ContributorModal.css b/src/components/settings/tabs/plugins/ContributorModal.css index f35d49f57..9cfcb4b5a 100644 --- a/src/components/settings/tabs/plugins/ContributorModal.css +++ b/src/components/settings/tabs/plugins/ContributorModal.css @@ -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"] { diff --git a/src/components/settings/tabs/sync/CloudTab.tsx b/src/components/settings/tabs/sync/CloudTab.tsx index 4c660cb3a..e848106c8 100644 --- a/src/components/settings/tabs/sync/CloudTab.tsx +++ b/src/components/settings/tabs/sync/CloudTab.tsx @@ -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. - o.value === cloud.url)} - onChange={v => changeUrl(v)} - className={Margins.bottom16} - closeOnSelect={true} - renderOptionPrefix={o => o?.value?.includes("equicord") ? : } - /> +
+ o.value === cloud.url)?.value} + onChange={v => changeUrl(v)} + closeOnSelect={true} + renderOptionPrefix={o => o?.value?.includes("equicord") ? : } + /> +
diff --git a/src/components/settings/tabs/vencord/index.tsx b/src/components/settings/tabs/vencord/index.tsx index 405f44613..23fde219c 100644 --- a/src/components/settings/tabs/vencord/index.tsx +++ b/src/components/settings/tabs/vencord/index.tsx @@ -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"), diff --git a/src/equicordplugins/_api/userArea.ts b/src/equicordplugins/_api/userArea.ts index 4f0d731c6..53cf52cde 100644 --- a/src/equicordplugins/_api/userArea.ts +++ b/src/equicordplugins/_api/userArea.ts @@ -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:\[/, diff --git a/src/equicordplugins/declutter/index.tsx b/src/equicordplugins/declutter/index.tsx index def613110..633d7b3dd 100644 --- a/src/equicordplugins/declutter/index.tsx +++ b/src/equicordplugins/declutter/index.tsx @@ -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:" diff --git a/src/equicordplugins/equicordHelper/index.tsx b/src/equicordplugins/equicordHelper/index.tsx index d87d9e54e..b4306015a 100644 --- a/src/equicordplugins/equicordHelper/index.tsx +++ b/src/equicordplugins/equicordHelper/index.tsx @@ -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", diff --git a/src/equicordplugins/exitSounds/index.tsx b/src/equicordplugins/exitSounds/index.tsx index cc110b9a1..73cc8d841 100644 --- a/src/equicordplugins/exitSounds/index.tsx +++ b/src/equicordplugins/exitSounds/index.tsx @@ -31,7 +31,7 @@ function GuildSelector() { return ( o.value === soundGuildId)} + value={options.find(o => o.value === soundGuildId)?.value} placeholder={t("equicord.exitSounds.ui.selectServer")} maxVisibleItems={6} closeOnSelect={true} diff --git a/src/equicordplugins/favouriteAnything/components.tsx b/src/equicordplugins/favouriteAnything/components.tsx index ac38c411e..0f6db374f 100644 --- a/src/equicordplugins/favouriteAnything/components.tsx +++ b/src/equicordplugins/favouriteAnything/components.tsx @@ -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("#{intl::SEARC const FavoriteButton = findComponentByCodeLazy("#{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; } = findByPropsLazy("fromServer", "sortRecipients"); -const MessageClass: Message & { new(base?: Partial): 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; @@ -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 ( diff --git a/src/equicordplugins/instantScreenshare/index.tsx b/src/equicordplugins/instantScreenshare/index.tsx index 2fe7c4f0b..8049f78ce 100644 --- a/src/equicordplugins/instantScreenshare/index.tsx +++ b/src/equicordplugins/instantScreenshare/index.tsx @@ -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; } }, diff --git a/src/equicordplugins/instantScreenshare/utils.tsx b/src/equicordplugins/instantScreenshare/utils.tsx index b6dbbece8..22ac85cea 100644 --- a/src/equicordplugins/instantScreenshare/utils.tsx +++ b/src/equicordplugins/instantScreenshare/utils.tsx @@ -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 /> diff --git a/src/equicordplugins/messagePeek/index.tsx b/src/equicordplugins/messagePeek/index.tsx index 166c158c5..5613827c5 100644 --- a/src/equicordplugins/messagePeek/index.tsx +++ b/src/equicordplugins/messagePeek/index.tsx @@ -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 {formatRelativeTime(timestamp)}; } diff --git a/src/equicordplugins/moreCommands/utils.ts b/src/equicordplugins/moreCommands/utils.ts index 38a53a517..a5a0f7815 100644 --- a/src/equicordplugins/moreCommands/utils.ts +++ b/src/equicordplugins/moreCommands/utils.ts @@ -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", diff --git a/src/equicordplugins/musicControls/index.tsx b/src/equicordplugins/musicControls/index.tsx index 05cdf703e..902378066 100644 --- a/src/equicordplugins/musicControls/index.tsx +++ b/src/equicordplugins/musicControls/index.tsx @@ -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:)/, diff --git a/src/equicordplugins/platformSpoofer/index.tsx b/src/equicordplugins/platformSpoofer/index.tsx index 572fdf7b7..f838994f2 100644 --- a/src/equicordplugins/platformSpoofer/index.tsx +++ b/src/equicordplugins/platformSpoofer/index.tsx @@ -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; } diff --git a/src/equicordplugins/songSpotlight.desktop/lib/api.ts b/src/equicordplugins/songSpotlight.desktop/lib/api.ts index 868f79132..9bad09bca 100644 --- a/src/equicordplugins/songSpotlight.desktop/lib/api.ts +++ b/src/equicordplugins/songSpotlight.desktop/lib/api.ts @@ -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 | 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("") && 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("") && 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; } } diff --git a/src/equicordplugins/songSpotlight.desktop/lib/oauth2.tsx b/src/equicordplugins/songSpotlight.desktop/lib/oauth2.tsx index 3ff3bcdf9..7bf8e2847 100644 --- a/src/equicordplugins/songSpotlight.desktop/lib/oauth2.tsx +++ b/src/equicordplugins/songSpotlight.desktop/lib/oauth2.tsx @@ -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); } }} /> diff --git a/src/equicordplugins/songSpotlight.desktop/lib/stores/AuthorizationStore.ts b/src/equicordplugins/songSpotlight.desktop/lib/stores/AuthorizationStore.ts index 48e1469d5..368166764 100644 --- a/src/equicordplugins/songSpotlight.desktop/lib/stores/AuthorizationStore.ts +++ b/src/equicordplugins/songSpotlight.desktop/lib/stores/AuthorizationStore.ts @@ -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; - getToken(): string | undefined; - setToken(token: string): void; - deleteToken(): void; + tokens: Record; + getToken(): Token | undefined; + setToken(access: string, refresh: string): void; + deleteTokens(): void; isAuthorized(): boolean; } @@ -26,17 +31,18 @@ export const useAuthorizationStore: PersistedZustandStore = 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 = })) as ZustandDefinition, { 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) { diff --git a/src/equicordplugins/songSpotlight.desktop/settings.tsx b/src/equicordplugins/songSpotlight.desktop/settings.tsx index 2819a7adb..97af8cc3f 100644 --- a/src/equicordplugins/songSpotlight.desktop/settings.tsx +++ b/src/equicordplugins/songSpotlight.desktop/settings.tsx @@ -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, diff --git a/src/equicordplugins/songSpotlight.desktop/ui/settings/index.tsx b/src/equicordplugins/songSpotlight.desktop/ui/settings/index.tsx index ef42841de..7a36d6f73 100644 --- a/src/equicordplugins/songSpotlight.desktop/ui/settings/index.tsx +++ b/src/equicordplugins/songSpotlight.desktop/ui/settings/index.tsx @@ -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) {
diff --git a/src/main/patcher.ts b/src/main/patcher.ts index 60cc515f4..911a40473 100644 --- a/src/main/patcher.ts +++ b/src/main/patcher.ts @@ -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; } diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index 6125e7667..cd366010c 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -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, () => ); }, onClick() { - return EquicordDonorModal(); + return badge.tooltip === "Equicord Translator" ? EquicordTranslatorModal() : EquicordDonorModal(); }, } satisfies ProfileBadge)); } diff --git a/src/plugins/_api/badges/modals.tsx b/src/plugins/_api/badges/modals.tsx index 275023666..135bb54ac 100644 --- a/src/plugins/_api/badges/modals.tsx +++ b/src/plugins/_api/badges/modals.tsx @@ -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 => ( { closeModal(modalKey); - // Will get my own in the future VencordNative.native.openExternal("https://github.com/sponsors/thororen1234"); }}> @@ -123,3 +122,47 @@ export function EquicordDonorModal() { )); } + +export function EquicordTranslatorModal() { + const modalKey = openModal(props => ( + { + closeModal(modalKey); + }}> + + + + + Equicord Translator + + + + + + + +
+ + Awarded to contributors who expand Equicord’s language support by translating content for the community. + +
+
+ + + + + +
+
+ )); +} diff --git a/src/plugins/_api/nicknameIcons.ts b/src/plugins/_api/nicknameIcons.ts index 81423e450..6d0c220b6 100644 --- a/src/plugins/_api/nicknameIcons.ts +++ b/src/plugins/_api/nicknameIcons.ts @@ -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]" + } + ] } ] }); diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index d5a57d933..eb87cb137 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -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: { diff --git a/src/plugins/alwaysAnimate/index.ts b/src/plugins/alwaysAnimate/index.ts index 4e04a6dc6..ec4f0ff73 100644 --- a/src/plugins/alwaysAnimate/index.ts +++ b/src/plugins/alwaysAnimate/index.ts @@ -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" }, }, diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index a800e241d..f298887fc 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -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 diff --git a/src/plugins/betterSettings/index.tsx b/src/plugins/betterSettings/index.tsx index a7df20b91..66202c558 100644 --- a/src/plugins/betterSettings/index.tsx +++ b/src/plugins/betterSettings/index.tsx @@ -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 { 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 ; }, - transformSettingsEntries(list: SettingsEntry[], keyMap: Map) { - 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( + + {children} + + ); + } else if (key.endsWith("_section") && props.label) { + items.push( + + {this.transformSettingsEntries(props.children)} + + ); + } 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) { - return this - .filter(a => a.items.length > 0) - .map(({ section, items }) => { - const children = items.map(render); - if (section) { - return ( - - {children} - - ); - } else { - return children; - } - }); - }; - - return toWrap; } }); diff --git a/src/plugins/decor/index.tsx b/src/plugins/decor/index.tsx index dc326d2db..1d03906b2 100644 --- a/src/plugins/decor/index.tsx +++ b/src/plugins/decor/index.tsx @@ -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 { diff --git a/src/plugins/experiments/index.tsx b/src/plugins/experiments/index.tsx index 304f44846..56863dc12 100644 --- a/src/plugins/experiments/index.tsx +++ b/src/plugins/experiments/index.tsx @@ -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 ", diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index 3b226836d..52b4c8011 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -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() { diff --git a/src/plugins/noDevtoolsWarning/index.ts b/src/plugins/noDevtoolsWarning/index.ts index e897e5793..5c35fa67b 100644 --- a/src/plugins/noDevtoolsWarning/index.ts +++ b/src/plugins/noDevtoolsWarning/index.ts @@ -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)" } }] diff --git a/src/plugins/noMosaic/index.ts b/src/plugins/noMosaic/index.ts index 6ff34a350..4ec64cc39 100644 --- a/src/plugins/noMosaic/index.ts +++ b/src/plugins/noMosaic/index.ts @@ -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;" diff --git a/src/utils/constants.ts b/src/utils/constants.ts index e0fef9853..34f87b66b 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -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); // iife so #__PURE__ works correctly