diff --git a/src/equicordplugins/characterCounter/index.tsx b/src/equicordplugins/characterCounter/index.tsx index 656709a94..9a24931db 100644 --- a/src/equicordplugins/characterCounter/index.tsx +++ b/src/equicordplugins/characterCounter/index.tsx @@ -7,6 +7,7 @@ import "./style.css"; import { definePluginSettings } from "@api/Settings"; +import ErrorBoundary from "@components/ErrorBoundary"; import { Devs, EquicordDevs } from "@utils/constants"; import { classNameFactory } from "@utils/css"; import definePlugin, { OptionType } from "@utils/types"; @@ -33,7 +34,7 @@ export default definePlugin({ replacement: [ { match: /(textValue:.{0,50}channelId:\i\.id\}\),)\i/, - replace: "$1$self.getCharCounter(arguments[0].textValue)" + replace: "$1$self.renderCharCounter(arguments[0].textValue)" } ] }, @@ -45,8 +46,8 @@ export default definePlugin({ } } ], - getCharCounter(text: string) { - const premiumType = (UserStore.getCurrentUser().premiumType ?? 0); + renderCharCounter: ErrorBoundary.wrap(text => { + const premiumType = (UserStore.getCurrentUser()?.premiumType ?? 0); const charMax = premiumType === 2 ? 4000 : 2000; const { length } = text; @@ -65,5 +66,5 @@ export default definePlugin({ {charMax} ); - } + }, { noop: true }) }); diff --git a/src/equicordplugins/musicControls/hoverOnly.css b/src/equicordplugins/musicControls/hoverOnly.css index 14f7402f7..9df7594ae 100644 --- a/src/equicordplugins/musicControls/hoverOnly.css +++ b/src/equicordplugins/musicControls/hoverOnly.css @@ -14,17 +14,8 @@ transition-property: height; } -.vc-ytm-button-row { - height: 0; - opacity: 0; - pointer-events: none; - transition: 0.2s; - transition-property: height; -} - #eq-tdl-player:hover .eq-tdl-button-row, -#vc-spotify-player:hover .vc-spotify-button-row, -#vc-ytm-player:hover .vc-ytm-button-row { +#vc-spotify-player:hover .vc-spotify-button-row { opacity: 1; height: 32px; pointer-events: auto; diff --git a/src/equicordplugins/musicControls/index.tsx b/src/equicordplugins/musicControls/index.tsx index 642a67cfb..3fb672401 100644 --- a/src/equicordplugins/musicControls/index.tsx +++ b/src/equicordplugins/musicControls/index.tsx @@ -28,7 +28,6 @@ import { SpotifyLyrics } from "./spotify/lyrics/components/lyrics"; import { SpotifyPlayer } from "./spotify/PlayerComponent"; import { TidalLyrics } from "./tidal/lyrics/components/lyrics"; import { TidalPlayer } from "./tidal/TidalPlayer"; -import { YtmPlayer } from "./youtubeMusic/PlayerComponent"; export default definePlugin({ name: "MusicControls", @@ -44,10 +43,6 @@ export default definePlugin({ "Tidal", "TidalControls", "TidalLyrics", - // Youtube - "Youtube", - "YoutubeMusic", - "YoutubeMusicControls" ], patches: [ @@ -91,7 +86,7 @@ export default definePlugin({ ], PanelWrapper({ VencordOriginal, ...props }) { - const { showTidalControls, showTidalLyrics, showSpotifyLyrics, showSpotifyControls, LyricsPosition, showYoutubeMusicControls } = settings.store; + const { showTidalControls, showTidalLyrics, showSpotifyLyrics, showSpotifyControls, lyricsPosition } = settings.store; return ( <> )} > - {showTidalLyrics && LyricsPosition === "above" && } + {showTidalLyrics && lyricsPosition === "above" && } {showTidalControls && } - {showTidalLyrics && LyricsPosition === "below" && } - {showSpotifyLyrics && LyricsPosition === "above" && } + {showTidalLyrics && lyricsPosition === "below" && } + {showSpotifyLyrics && lyricsPosition === "above" && } {showSpotifyControls && } - {showSpotifyLyrics && LyricsPosition === "below" && } - {showYoutubeMusicControls && } + {showSpotifyLyrics && lyricsPosition === "below" && } diff --git a/src/equicordplugins/musicControls/settings.tsx b/src/equicordplugins/musicControls/settings.tsx index b3bda9c26..d79fe73dd 100644 --- a/src/equicordplugins/musicControls/settings.tsx +++ b/src/equicordplugins/musicControls/settings.tsx @@ -43,12 +43,12 @@ export const settings = definePluginSettings({ default: false, onChange: v => toggleHoverControls(v) }, - ShowMusicNoteOnNoLyrics: { + showMusicNoteOnNoLyrics: { description: "Show a music note icon when no lyrics are found", type: OptionType.BOOLEAN, default: true, }, - LyricsPosition: { + lyricsPosition: { description: "Position of the lyrics", type: OptionType.SELECT, options: [ @@ -56,7 +56,7 @@ export const settings = definePluginSettings({ { value: "below", label: "Below Player(s)", default: true }, ], }, - LyricsProvider: { + lyricsProvider: { description: "Where lyrics are fetched from", type: OptionType.SELECT, options: [ @@ -64,7 +64,7 @@ export const settings = definePluginSettings({ { value: Provider.Lrclib, label: "LRCLIB" }, ], }, - TranslateTo: { + translateTo: { description: "Translate lyrics to - Changing this will clear existing translations", type: OptionType.SELECT, options: languages, @@ -73,7 +73,7 @@ export const settings = definePluginSettings({ showToast("Translations cleared", Toasts.Type.SUCCESS); } }, - LyricsConversion: { + lyricsConversion: { description: "Automatically translate or romanize lyrics", type: OptionType.SELECT, options: [ @@ -82,23 +82,23 @@ export const settings = definePluginSettings({ { value: Provider.Romanized, label: "Romanize" }, ] }, - FallbackProvider: { + fallbackProvider: { description: "When a lyrics provider fails, try other providers", type: OptionType.BOOLEAN, default: true, }, - ShowFailedToasts: { + showFailedToasts: { description: "Hide toasts when lyrics fail to fetch", type: OptionType.BOOLEAN, default: true, }, - LyricDelay: { + lyricDelay: { description: "", type: OptionType.SLIDER, default: 0, ...sliderOptions }, - PurgeLyricsCache: { + purgeLyricsCache: { description: "Purge the lyrics cache", type: OptionType.COMPONENT, component: () => ( @@ -113,7 +113,7 @@ export const settings = definePluginSettings({ ), }, - SpotifySectionTitle: { + spotifySectionTitle: { type: OptionType.COMPONENT, component: () => (
@@ -142,7 +142,7 @@ export const settings = definePluginSettings({ default: true }, - TidalSectionTitle: { + tdalSectionTitle: { type: OptionType.COMPONENT, component: () => (
@@ -164,36 +164,4 @@ export const settings = definePluginSettings({ type: OptionType.BOOLEAN, default: false, }, - - YtmSectionTitle: { - type: OptionType.COMPONENT, - component: () => ( -
- Youtube Music - - Install Pear Desktop from here, then go to Plugins → API Server → Enabled - -
- ) - }, - showYoutubeMusicControls: { - description: "Show Youtube Music Controls", - type: OptionType.BOOLEAN, - default: false - }, - YoutubeMusicApiUrl: { - description: "Custom URL for the Api Server plugin", - type: OptionType.STRING, - default: "http://localhost:26538", - placeholder: "http://localhost:26538", - onChange: (value: string) => { - if (URL.canParse(value)) { - settings.store.YoutubeMusicApiUrl = value; - } else { - showToast("Invalid URL format for Custom Api Server URL: " + value, Toasts.Type.FAILURE); - settings.store.YoutubeMusicApiUrl = settings.def.YoutubeMusicApiUrl.default; - } - } - } - }); diff --git a/src/equicordplugins/musicControls/spotify/lyrics/api.tsx b/src/equicordplugins/musicControls/spotify/lyrics/api.tsx index c785790bf..ef73bf706 100644 --- a/src/equicordplugins/musicControls/spotify/lyrics/api.tsx +++ b/src/equicordplugins/musicControls/spotify/lyrics/api.tsx @@ -41,8 +41,8 @@ export async function getLyrics(track: Track | null): Promise const nullCacheEntry = nullLyricCache.get(cacheKey); if (nullCacheEntry) { - const provider = settings.store.LyricsProvider; - if (!settings.store.FallbackProvider && nullCacheEntry[provider]) { + const provider = settings.store.lyricsProvider; + if (!settings.store.fallbackProvider && nullCacheEntry[provider]) { return null; } @@ -51,7 +51,7 @@ export async function getLyrics(track: Track | null): Promise } } - const providersToTry = [settings.store.LyricsProvider, ...providers.filter(p => p !== settings.store.LyricsProvider)]; + const providersToTry = [settings.store.lyricsProvider, ...providers.filter(p => p !== settings.store.lyricsProvider)]; for (const provider of providersToTry) { const lyricsInfo = await lyricFetchers[provider](track); diff --git a/src/equicordplugins/musicControls/spotify/lyrics/components/lyrics.tsx b/src/equicordplugins/musicControls/spotify/lyrics/components/lyrics.tsx index aafdd0193..c4b15fa64 100644 --- a/src/equicordplugins/musicControls/spotify/lyrics/components/lyrics.tsx +++ b/src/equicordplugins/musicControls/spotify/lyrics/components/lyrics.tsx @@ -21,7 +21,7 @@ const nextCl = cl("next"); const currentCl = cl("current"); function LyricsDisplay({ scroll = true }: { scroll?: boolean; }) { - const { ShowMusicNoteOnNoLyrics } = settings.use(["ShowMusicNoteOnNoLyrics"]); + const { showMusicNoteOnNoLyrics } = settings.use(["showMusicNoteOnNoLyrics"]); const { lyricsInfo, lyricRefs, currLrcIndex } = useLyrics({ scroll }); const currentLyrics = lyricsInfo?.lyricsVersions[lyricsInfo.useLyric] || null; @@ -50,7 +50,7 @@ function LyricsDisplay({ scroll = true }: { scroll?: boolean; }) { {line.text || NoteSvg()} - )) : ShowMusicNoteOnNoLyrics ? ( + )) : showMusicNoteOnNoLyrics ? ( diff --git a/src/equicordplugins/musicControls/spotify/lyrics/components/util.tsx b/src/equicordplugins/musicControls/spotify/lyrics/components/util.tsx index e5c3271d7..a489a58ff 100644 --- a/src/equicordplugins/musicControls/spotify/lyrics/components/util.tsx +++ b/src/equicordplugins/musicControls/spotify/lyrics/components/util.tsx @@ -66,7 +66,7 @@ export function useLyrics({ scroll = true }: { scroll?: boolean; } = {}) { ]); const lyricsInfo = useStateFromStores([SpotifyLrcStore], () => SpotifyLrcStore.lyricsInfo); - const { LyricDelay } = settings.use(["LyricDelay"]); + const { lyricDelay } = settings.use(["lyricDelay"]); const [currLrcIndex, setCurrLrcIndex] = useState(null); const [nextLyric, setNextLyric] = useState(null); @@ -83,14 +83,14 @@ export function useLyrics({ scroll = true }: { scroll?: boolean; } = {}) { useEffect(() => { if (currentLyrics && position != null) { - const [currentIndex, nextLyricIndex] = getIndexes(currentLyrics, position, LyricDelay); + const [currentIndex, nextLyricIndex] = getIndexes(currentLyrics, position, lyricDelay); setCurrLrcIndex(currentIndex); setNextLyric(nextLyricIndex); } else { setCurrLrcIndex(null); setNextLyric(null); } - }, [currentLyrics, position, LyricDelay]); + }, [currentLyrics, position, lyricDelay]); useEffect(() => { if (scroll && currLrcIndex !== null) { diff --git a/src/equicordplugins/musicControls/spotify/lyrics/providers/store.ts b/src/equicordplugins/musicControls/spotify/lyrics/providers/store.ts index 971ba1535..61b7efe1a 100644 --- a/src/equicordplugins/musicControls/spotify/lyrics/providers/store.ts +++ b/src/equicordplugins/musicControls/spotify/lyrics/providers/store.ts @@ -17,7 +17,7 @@ import { LyricsData, Provider } from "./types"; export const lyricsAlternative = [Provider.Translated, Provider.Romanized]; function showNotif(title: string, body: string) { - if (settings.store.ShowFailedToasts) { + if (settings.store.showFailedToasts) { showNotification({ color: "#ee2902", title, @@ -44,12 +44,12 @@ export const SpotifyLrcStore = proxyLazyWebpack(() => { fetchingsTracks.push(e.track?.id ?? ""); lyricsInfo = await getLyrics(e.track); - const { LyricsConversion } = settings.store; - if (LyricsConversion !== Provider.None) { + const { lyricsConversion } = settings.store; + if (lyricsConversion !== Provider.None) { FluxDispatcher.dispatch({ // @ts-ignore type: "SPOTIFY_LYRICS_PROVIDER_CHANGE", - provider: LyricsConversion + provider: lyricsConversion }); } @@ -74,7 +74,7 @@ export const SpotifyLrcStore = proxyLazyWebpack(() => { } if (provider === Provider.Translated || provider === Provider.Romanized) { - const originalLyrics = currentInfo?.lyricsVersions[settings.store.LyricsProvider] || + const originalLyrics = currentInfo?.lyricsVersions[settings.store.lyricsProvider] || providers.map(p => currentInfo?.lyricsVersions[p]).find(Boolean); if (!originalLyrics || !currentInfo) { @@ -87,7 +87,7 @@ export const SpotifyLrcStore = proxyLazyWebpack(() => { if (provider === Provider.Romanized && !/[^\u0000-\u007F]/.test(lyricsCheckText)) { lyricsInfo = { ...currentInfo, - useLyric: settings.store.LyricsProvider, + useLyric: settings.store.lyricsProvider, lyricsVersions: { ...currentInfo.lyricsVersions, }, diff --git a/src/equicordplugins/musicControls/tidal/lyrics/components/lyrics.tsx b/src/equicordplugins/musicControls/tidal/lyrics/components/lyrics.tsx index 903565367..702393a4f 100644 --- a/src/equicordplugins/musicControls/tidal/lyrics/components/lyrics.tsx +++ b/src/equicordplugins/musicControls/tidal/lyrics/components/lyrics.tsx @@ -17,7 +17,7 @@ import { LyricsModal } from "./modal"; import { cl, NoteSvg, useLyrics } from "./util"; function LyricsDisplay({ scroll = true }: { scroll?: boolean; }) { - const { ShowMusicNoteOnNoLyrics } = settings.use(["ShowMusicNoteOnNoLyrics"]); + const { showMusicNoteOnNoLyrics } = settings.use(["showMusicNoteOnNoLyrics"]); const { lyrics, lyricRefs } = useLyrics({ scroll }); const currentLyrics = lyrics || null; const NoteElement = NoteSvg(cl("music-note")); @@ -37,7 +37,7 @@ function LyricsDisplay({ scroll = true }: { scroll?: boolean; }) { }; if (!currentLyrics) { - return ShowMusicNoteOnNoLyrics ? ( + return showMusicNoteOnNoLyrics ? (
ContextMenuApi.openContextMenu(e, () => )} > diff --git a/src/equicordplugins/musicControls/tidal/lyrics/components/util.tsx b/src/equicordplugins/musicControls/tidal/lyrics/components/util.tsx index 2002388bc..b40aca319 100644 --- a/src/equicordplugins/musicControls/tidal/lyrics/components/util.tsx +++ b/src/equicordplugins/musicControls/tidal/lyrics/components/util.tsx @@ -40,7 +40,7 @@ export function useLyrics({ scroll = true }: { scroll?: boolean; } = {}) { ]); const lyrics = useStateFromStores([TidalLrcStore], () => TidalLrcStore.lyrics); - const { LyricDelay } = settings.use(["LyricDelay"]); + const { lyricDelay } = settings.use(["lyricDelay"]); const [currLrcIndex, setCurrLrcIndex] = useState(null); const [nextLyric, setNextLyric] = useState(null); @@ -57,7 +57,7 @@ export function useLyrics({ scroll = true }: { scroll?: boolean; } = {}) { useEffect(() => { if (currentLyrics && position) { - const [currentIndex, nextLyric] = calculateIndexes(currentLyrics, position, LyricDelay); + const [currentIndex, nextLyric] = calculateIndexes(currentLyrics, position, lyricDelay); setCurrLrcIndex(currentIndex); setNextLyric(nextLyric); } diff --git a/src/equicordplugins/musicControls/tidal/lyrics/providers/store.ts b/src/equicordplugins/musicControls/tidal/lyrics/providers/store.ts index 81bb2bb12..a51293445 100644 --- a/src/equicordplugins/musicControls/tidal/lyrics/providers/store.ts +++ b/src/equicordplugins/musicControls/tidal/lyrics/providers/store.ts @@ -13,7 +13,7 @@ import { proxyLazyWebpack } from "@webpack"; import { Flux, FluxDispatcher } from "@webpack/common"; function showNotif(title: string, body: string) { - if (settings.store.ShowFailedToasts) { + if (settings.store.showFailedToasts) { showNotification({ color: "#ee2902", title, diff --git a/src/equicordplugins/musicControls/youtubeMusic/PlayerComponent.tsx b/src/equicordplugins/musicControls/youtubeMusic/PlayerComponent.tsx deleted file mode 100644 index e0a71e61f..000000000 --- a/src/equicordplugins/musicControls/youtubeMusic/PlayerComponent.tsx +++ /dev/null @@ -1,371 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import "./ytmStyles.css"; - -import { BaseText } from "@components/BaseText"; -import { Flex } from "@components/Flex"; -import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons"; -import { SeekBar } from "@equicordplugins/musicControls/spotify/SeekBar"; -import { debounce } from "@shared/debounce"; -import { copyWithToast, openImageModal } from "@utils/discord"; -import { classes } from "@utils/misc"; -import { ContextMenuApi, FluxDispatcher, Menu, React, useEffect, useState, useStateFromStores } from "@webpack/common"; - -import { type Repeat, type Song, YoutubeMusicStore } from "./YtmStore"; - -const cl = (className: string) => `vc-ytm-${className}`; - -function msToHuman(ms: number) { - const minutes = ms / 1000 / 60; - const m = Math.floor(minutes); - const s = Math.floor((minutes - m) * 60); - return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`; -} - -function Svg(path: string, label: string) { - return () => ( - - {label} - - - ); -} - -// KraXen's icons :yesyes: -// from https://fonts.google.com/icons?icon.style=Rounded&icon.set=Material+Icons -// older material icon style, but still really good -const PlayButton = Svg("M8 6.82v10.36c0 .79.87 1.27 1.54.84l8.14-5.18c.62-.39.62-1.29 0-1.69L9.54 5.98C8.87 5.55 8 6.03 8 6.82z", "play"); -const PauseButton = Svg("M8 19c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2s-2 .9-2 2v10c0 1.1.9 2 2 2zm6-12v10c0 1.1.9 2 2 2s2-.9 2-2V7c0-1.1-.9-2-2-2s-2 .9-2 2z", "pause"); -const SkipPrev = Svg("M7 6c.55 0 1 .45 1 1v10c0 .55-.45 1-1 1s-1-.45-1-1V7c0-.55.45-1 1-1zm3.66 6.82l5.77 4.07c.66.47 1.58-.01 1.58-.82V7.93c0-.81-.91-1.28-1.58-.82l-5.77 4.07c-.57.4-.57 1.24 0 1.64z", "previous"); -const SkipNext = Svg("M7.58 16.89l5.77-4.07c.56-.4.56-1.24 0-1.63L7.58 7.11C6.91 6.65 6 7.12 6 7.93v8.14c0 .81.91 1.28 1.58.82zM16 7v10c0 .55.45 1 1 1s1-.45 1-1V7c0-.55-.45-1-1-1s-1 .45-1 1z", "next"); -const Repeat = Svg("M7 7h10v1.79c0 .45.54.67.85.35l2.79-2.79c.2-.2.2-.51 0-.71l-2.79-2.79c-.31-.31-.85-.09-.85.36V5H6c-.55 0-1 .45-1 1v4c0 .55.45 1 1 1s1-.45 1-1V7zm10 10H7v-1.79c0-.45-.54-.67-.85-.35l-2.79 2.79c-.2.2-.2.51 0 .71l2.79 2.79c.31.31.85.09.85-.36V19h11c.55 0 1-.45 1-1v-4c0-.55-.45-1-1-1s-1 .45-1 1v3z", "repeat"); -const Shuffle = Svg("M10.59 9.17L6.12 4.7c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41l4.46 4.46 1.42-1.4zm4.76-4.32l1.19 1.19L4.7 17.88c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0L17.96 7.46l1.19 1.19c.31.31.85.09.85-.36V4.5c0-.28-.22-.5-.5-.5h-3.79c-.45 0-.67.54-.36.85zm-.52 8.56l-1.41 1.41 3.13 3.13-1.2 1.2c-.31.31-.09.85.36.85h3.79c.28 0 .5-.22.5-.5v-3.79c0-.45-.54-.67-.85-.35l-1.19 1.19-3.13-3.14z", "shuffle"); - -function Button(props: React.ButtonHTMLAttributes) { - return ( - - ); -} - -function CopyContextMenu({ name, path }: { name: string; path: string; }) { - const copyId = `ytm-copy-${name}`; - const openId = `ytm-open-${name}`; - - return ( - FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })} - aria-label={`YouTube Music ${name} Menu`} - > - copyWithToast(`https://music.youtube.com${path}`)} - icon={LinkIcon} - /> - YoutubeMusicStore.openExternal(path)} - icon={OpenExternalIcon} - /> - - ); -} - -function makeContextMenu(name: string, path: string) { - return (e: React.MouseEvent) => - ContextMenuApi.openContextMenu(e, () => ); -} - -function Controls() { - const [isPlaying, isShuffled, repeat] = useStateFromStores<[boolean, boolean, Repeat]>( - [YoutubeMusicStore], - () => [YoutubeMusicStore.isPlaying, YoutubeMusicStore.isShuffled, YoutubeMusicStore.repeat] - ); - - const [nextRepeat, repeatClassName] = (() => { - switch (repeat) { - case "NONE": return ["context", "repeat-off"] as const; - case "ALL": return ["track", "repeat-context"] as const; - case "ONE": return ["off", "repeat-track"] as const; - default: throw new Error(`Invalid repeat state ${repeat}`); - } - })(); - - // the 1 is using position absolute so it does not make the button jump around - return ( - - - - - - - - ); -} - -const seek = debounce((v: number) => { - YoutubeMusicStore.seek(v); -}); - -function YtmSeekBar() { - const { songDuration } = YoutubeMusicStore.song!; - - const [storePosition, isSettingPosition, isPlaying] = useStateFromStores( - [YoutubeMusicStore], - () => [YoutubeMusicStore.mPosition, YoutubeMusicStore.isSettingPosition, YoutubeMusicStore.isPlaying] - ); - - const [position, setPosition] = useState(storePosition); - - useEffect(() => { - setPosition(YoutubeMusicStore.position); - }, [storePosition]); - - const onChange = (v: number) => { - if (isSettingPosition) return; - setPosition(v); - seek(v); - }; - - return ( -
- - {msToHuman(position)} - - - - {msToHuman(songDuration * 1000)} - -
- ); -} - -function AlbumContextMenu({ track }: { track: Song; }) { - const [volume, muted] = useStateFromStores([YoutubeMusicStore], () => [YoutubeMusicStore.volume, YoutubeMusicStore.muted]); - - return ( - FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })} - aria-label="YouTube Music Album Menu" - > - {/* YoutubeMusicStore.openExternal(`/album/${track?.album}`)} - icon={OpenExternalIcon} - /> */} - track?.imageSrc && openImageModal({ url: track.imageSrc })} - icon={ImageIcon} - /> - ( - YoutubeMusicStore.setVolume(v))} - /> - )} - /> - YoutubeMusicStore.toggleMute()} - /> - - ); -} - -function makeLinkProps(name: string, condition: unknown, path: string) { - if (!condition) return {}; - - return { - role: "link", - onClick: () => YoutubeMusicStore.openExternal(path), - onContextMenu: makeContextMenu(name, path) - } satisfies React.HTMLAttributes; -} - -function Info({ track }: { track: NonNullable; }) { - const img = track?.imageSrc; - - const [coverExpanded, setCoverExpanded] = useState(false); - - const i = ( - <> - {img && ( - Album Image setCoverExpanded(!coverExpanded)} - onContextMenu={e => { - ContextMenuApi.openContextMenu(e, () => ); - }} - /> - )} - - ); - - if (coverExpanded && img) return ( -
- {i} -
- ); - - return ( -
- {i} -
- - {track?.title} - - {track.artist && ( - - by  - - {track.artist} - - - )} - {track.album && ( - - on  - - {track.album} - - - )} -
-
- ); -} - -export function YtmPlayer() { - const track = useStateFromStores( - [YoutubeMusicStore], - () => YoutubeMusicStore.song, - null, - (prev, next) => prev?.videoId ? (prev.videoId === next?.videoId) : prev?.title === next?.title - ); - - const isPlaying = useStateFromStores([YoutubeMusicStore], () => YoutubeMusicStore.isPlaying); - const [shouldHide, setShouldHide] = useState(false); - - React.useEffect(() => { - setShouldHide(false); - if (!isPlaying) { - const timeout = setTimeout(() => setShouldHide(true), 1000 * 60 * 5); - return () => clearTimeout(timeout); - } - }, [isPlaying]); - - if (!track || shouldHide) return; - - const exportTrackImageStyle = { - "--vc-ytm-track-image": `url(${track?.imageSrc || ""})`, - } as React.CSSProperties; - - return ( -
- - - -
- ); -} diff --git a/src/equicordplugins/musicControls/youtubeMusic/YtmStore.ts b/src/equicordplugins/musicControls/youtubeMusic/YtmStore.ts deleted file mode 100644 index 22256b63c..000000000 --- a/src/equicordplugins/musicControls/youtubeMusic/YtmStore.ts +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { isPluginEnabled } from "@api/PluginManager"; -import { Logger } from "@utils/Logger"; -import { proxyLazyWebpack } from "@webpack"; -import { Flux, FluxDispatcher } from "@webpack/common"; -import { Settings } from "Vencord"; - -enum MediaType { - /** - * Audio uploaded by the original artist - */ - Audio = "AUDIO", - /** - * Official music video uploaded by the original artist - */ - OriginalMusicVideo = "ORIGINAL_MUSIC_VIDEO", - /** - * Normal YouTube video uploaded by a user - */ - UserGeneratedContent = "USER_GENERATED_CONTENT", - /** - * Podcast episode - */ - PodcastEpisode = "PODCAST_EPISODE", - OtherVideo = "OTHER_VIDEO", -} - -export type RepeatMode = "NONE" | "ONE" | "ALL"; - -export interface Song { - title: string; - artist: string; - views: number; - uploadDate?: string; - imageSrc?: string | null; - isPaused?: boolean; - songDuration: number; - elapsedSeconds?: number; - url?: string; - album?: string | null; - videoId: string; - playlistId?: string; - mediaType: MediaType; -} - -enum DataTypes { - PlayerInfo = "PLAYER_INFO", - VideoChanged = "VIDEO_CHANGED", - PlayerStateChanged = "PLAYER_STATE_CHANGED", - PositionChanged = "POSITION_CHANGED", - VolumeChanged = "VOLUME_CHANGED", - RepeatChanged = "REPEAT_CHANGED", - ShuffleChanged = "SHUFFLE_CHANGED", -} - -type PlayerInfo = { - type: DataTypes.PlayerInfo; - song: Song | undefined; - volume: number, - muted: boolean; - repeat: RepeatMode; - position: number; - isPlaying: boolean; - shuffle: boolean; -}; - -type VideoChanged = { - type: DataTypes.VideoChanged; - song: Song; - position: number; -}; - -type PlayerStateChanged = { - type: DataTypes.PlayerStateChanged; - isPlaying: boolean, - position: number; -}; - -type PositionChanged = { - type: DataTypes.PositionChanged, - position: number; -}; - -type VolumeChanged = { - type: DataTypes.VolumeChanged; - volume: number; - muted: boolean; -}; - -type RepeatChanged = { - type: DataTypes.RepeatChanged; - repeat: RepeatMode; -}; - -type ShuffleChanged = { - type: DataTypes.ShuffleChanged; - shuffle: boolean; -}; - -export type Repeat = "NONE" | "ONE" | "ALL"; - -export const logger = new Logger("MusicControls-Ytm"); - -type Message = PlayerInfo | VideoChanged | PlayerStateChanged | PositionChanged | VolumeChanged | RepeatChanged | ShuffleChanged; - -type PlayerState = Partial>; - -class YoutubeMusicSocket { - public onChange: (e: PlayerState) => void; - private ready = false; - private connecting = false; - - private socket: WebSocket | undefined; - - constructor(onChange: typeof this.onChange) { - this.reconnect(); - this.onChange = onChange; - } - - public scheduleReconnect(ms: number) { - setTimeout(() => this.reconnect(), ms); - } - - public reconnect() { - if (this.ready || this.connecting) return; - this.connecting = true; - this.initWs(); - } - - private async initWs() { - const url = Settings.plugins.MusicControls.YoutubeMusicApiUrl; - if (!url) { - this.connecting = false; - return; - } - - try { - this.socket = new WebSocket(new URL("/api/v1/ws", url)); - } catch (e) { - logger.error("Connection failed"); - return; - } - - this.socket.addEventListener("open", () => { - this.ready = true; - this.connecting = false; - }); - - this.socket.addEventListener("error", e => { - this.ready = false; - this.connecting = false; - if (!this.ready) this.scheduleReconnect(5_000); - this.onChange({ position: 0, isPlaying: false, song: undefined }); - - }); - - this.socket.addEventListener("close", e => { - this.ready = false; - this.connecting = false; - if (!this.ready) this.scheduleReconnect(10_000); - this.onChange({ position: 0, isPlaying: false, song: undefined }); - }); - - this.socket.addEventListener("message", e => { - let message: Message; - try { - message = JSON.parse(e.data) as Message; - this.onChange(message); - } catch (err) { - logger.error("Invalid JSON:", err, `\n${e.data}`); - return; - } - }); - } -} - -export const YoutubeMusicStore = proxyLazyWebpack(() => { - const { Store } = Flux; - - class YoutubeMusicStore extends Store { - public mPosition = 0; - public start = 0; - - public song: Song | null = null; - public isPlaying = false; - public isShuffled = false; - public repeat: Repeat = "NONE"; - public volume = 0; - public muted = false; - - public isSettingPosition = false; - - public socket = new YoutubeMusicSocket((message: PlayerState) => { - if (message.song) { - store.song = message.song; - store.isPlaying = !(message.song?.isPaused ?? false); - } - if (message.isPlaying != null && !message.song) store.isPlaying = message.isPlaying; - if (message.shuffle != null) store.isShuffled = message.shuffle; - if (message.position && message.position !== store.position) store.position = message.position ?? 0; - if (message.volume) store.volume = message.volume ?? 0; - if (message.repeat) store.repeat = message.repeat; - if (message.muted != null) store.muted = message.muted; - - store.isSettingPosition = false; - store.emitChange(); - }); - - public openExternal(path: string) { - const videoId = path.match(/watch\?v=([\w-]+)/); - - const url = isPluginEnabled("OpenInApp") && videoId - ? encodeURI("youtubemusic://openVideo " + videoId[1]) - : "https://music.youtube.com" + path; - - logger.info("Open", url); - - // https://music.youtube.com/watch?v=BSHYPb15W-Y - VencordNative.native.openExternal(url); - } - - set position(p: number) { - this.mPosition = p * 1000; - this.start = Date.now(); - } - - get position(): number { - let pos = this.mPosition; - if (this.isPlaying) { - pos += Date.now() - this.start; - } - return pos; - } - - prev() { - this.req("post", "/api/v1/previous"); - } - next() { - this.req("post", "/api/v1/next"); - } - setVolume(percent: number) { - this.req("post", "/api/v1/volume", { - body: { - volume: Math.floor(percent) - }, - }); - } - setPlaying(playing: boolean) { - if (playing) { - this.req("post", "/api/v1/play"); - } else { - this.req("post", "/api/v1/pause"); - } - } - switchRepeat() { - this.req("post", "/api/v1/switch-repeat", { - body: { - iteration: 1 - } - }); - } - shuffle() { - this.req("post", "/api/v1/shuffle"); - } - seek(ms: number) { - this.req("post", "/api/v1/seek-to", { - body: { - seconds: Math.floor(ms / 1000) - } - }); - } - toggleMute() { - this.req("post", "/api/v1/toggle-mute"); - } - - public req(method: "post" | "get" | "put", route: string, data: any = {}) { - const apiServerUrl = Settings.plugins.MusicControls.YoutubeMusicApiUrl; - if (apiServerUrl === "") return; - const url = apiServerUrl + route; - - fetch(url, { - method, - ...data, - ...(data.body && { body: JSON.stringify(data.body), headers: { "Content-Type": "application/json" } }) - }); - } - } - - const store = new YoutubeMusicStore(FluxDispatcher); - - return store; -}); diff --git a/src/equicordplugins/musicControls/youtubeMusic/ytmStyles.css b/src/equicordplugins/musicControls/youtubeMusic/ytmStyles.css deleted file mode 100644 index 793225120..000000000 --- a/src/equicordplugins/musicControls/youtubeMusic/ytmStyles.css +++ /dev/null @@ -1,208 +0,0 @@ -#vc-ytm-player { - padding: 12px; - background: var(--background-base-low); - margin: 0; - border-top-left-radius: 10px; - border-top-right-radius: 10px; - - --vc-ytm-slider-gradient: linear-gradient(to right, #f03 80%, #ff2791 100%); - --vc-ytm-slider-grabber: #f03; - - /* so custom themes can easily change it */ - --vc-ytm-red-90: color-mix(in hsl, var(--vc-ytm-slider-grabber), transparent 90%); - --vc-ytm-red-80: color-mix(in hsl, var(--vc-ytm-slider-grabber), transparent 80%); -} - -.vc-ytm-song-info-prefix { - display: none; -} - -.vc-ytm-artist, -.vc-ytm-album { - color: var(--text-strong); -} - -.vc-ytm-secondary-song-info { - font-size: 12px; -} - -#vc-ytm-progress-bar { - position: relative; - color: var(--text-default); - width: 100%; - margin: 0.5em 0; - margin-bottom: 5px; -} - -#vc-ytm-progress-bar > [class*="slider"] { - flex-grow: 1; - width: 100%; - padding: 0 !important; -} - -#vc-ytm-progress-bar > [class*="slider"] [class*="bar"] { - height: 3px !important; - top: calc(12px - 4px / 2 + var(--bar-offset)); -} - -#vc-ytm-progress-bar > [class*="slider"] [class*="barFill"] { - background-image: var(--vc-ytm-slider-gradient); -} - -#vc-ytm-progress-bar > [class*="slider"]:hover [class*="barFill"] { - background-image: var(--vc-ytm-slider-gradient); -} - -#vc-ytm-progress-bar > [class*="slider"] [class*="grabber"] { - background-color: var(--vc-ytm-slider-grabber); - width: 12px !important; - height: 12px !important; - margin-top: calc(13px/-2 + var(--bar-offset)/2); - margin-left: -0.5px; - border: unset; - color: var(--interactive-icon-default); - opacity: 0; - transition: opacity 0.1s; -} - -#vc-ytm-progress-bar:hover > [class*="slider"] [class*="grabber"] { - opacity: 1; -} - -.vc-ytm-progress-time { - margin-top: 8px; - font-family: var(--font-code); - font-size: 12px; - top: 10px; - position: absolute; -} - -.vc-ytm-button-row { - justify-content: center; - margin-top: 14px; -} - -.vc-ytm-button { - margin: 0 2px; - border-radius: var(--radius-sm); - background: none; - color: var(--interactive-icon-default); - padding: 0; - width: 32px; - height: 32px; - display: flex; - justify-content: center; - align-items: center; - position: relative; -} - -.vc-ytm-button:hover { - color: var(--interactive-icon-hover); - background-color: var(--background-mod-strong); -} - -.vc-ytm-button svg { - height: 24px; - width: 24px; -} - -[class*="shuffle"] > svg, -[class*="repeat"] > svg { - width: 22px; - height: 22px; -} - -.vc-ytm-button svg path { - width: 100%; - height: 100%; -} - - -.vc-ytm-repeat-context, -.vc-ytm-repeat-track, -.vc-ytm-shuffle-on { - background-color: var(--vc-ytm-red-90); -} - -.vc-ytm-repeat-context:hover, -.vc-ytm-repeat-track:hover, -.vc-ytm-shuffle-on:hover { - background-color: var(--vc-ytm-red-80); -} - -.vc-ytm-repeat-1 { - font-size: 70%; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} - -#vc-ytm-info-wrapper { - display: flex; - flex-direction: row; - align-items: center; - height: 3em; - gap: 0.5em; -} - -#vc-ytm-info-wrapper img { - height: 90%; - object-fit: contain; -} - -#vc-ytm-album-expanded-wrapper img { - width: 100%; - object-fit: contain; -} - -#vc-ytm-titles { - display: flex; - flex-direction: column; - padding: 0.2rem; - align-items: flex-start; - place-content: flex-start center; - overflow: hidden; -} - -#vc-ytm-song-title { - color: var(--text-strong); - font-size: 14px; - font-weight: 600; -} - -.vc-ytm-ellipoverflow { - white-space: nowrap; - overflow: hidden; - width: 100%; - text-overflow: ellipsis; -} - -.vc-ytm-artist[role="link"]:hover, -#vc-ytm-album-title[role="link"]:hover, -#vc-ytm-song-title[role="link"]:hover { - text-decoration: underline; - cursor: pointer; -} - -#vc-ytm-album-image { - border-radius: 3px; - transition: filter 0.2s; -} - -#vc-ytm-album-image:hover { - filter: brightness(1.2); - cursor: pointer; -} - -#vc-ytm-progress-text { - margin: 0; -} - -.vc-ytm-time-left { - left: 0; -} - -.vc-ytm-time-right { - right: 0; -} \ No newline at end of file diff --git a/src/equicordplugins/splitLargeMessages/index.ts b/src/equicordplugins/splitLargeMessages/index.ts index 58609b569..5fef6839e 100644 --- a/src/equicordplugins/splitLargeMessages/index.ts +++ b/src/equicordplugins/splitLargeMessages/index.ts @@ -19,7 +19,7 @@ const canSplit: () => boolean = () => { }; const autoMaxLength = () => { - const hasNitro = UserStore.getCurrentUser().premiumType === 2; + const hasNitro = UserStore.getCurrentUser()?.premiumType === 2; return hasNitro ? 4000 : 2000; };