diff options
author | Ven <vendicated@riseup.net> | 2022-11-07 22:28:29 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-07 22:28:29 +0100 |
commit | 6a8564089bea162d9c4d52925eb1239b6b270fa4 (patch) | |
tree | b411e223ffab2752175e2e883908cd814f73455d /src/plugins/spotifyControls/SpotifyStore.ts | |
parent | 7d5ade21fc9b56d21e2eb9e5b0d35502432adaa2 (diff) | |
download | Vencord-6a8564089bea162d9c4d52925eb1239b6b270fa4.tar.gz Vencord-6a8564089bea162d9c4d52925eb1239b6b270fa4.tar.bz2 Vencord-6a8564089bea162d9c4d52925eb1239b6b270fa4.zip |
SpotifyControls plugin (#190)
Diffstat (limited to 'src/plugins/spotifyControls/SpotifyStore.ts')
-rw-r--r-- | src/plugins/spotifyControls/SpotifyStore.ts | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/src/plugins/spotifyControls/SpotifyStore.ts b/src/plugins/spotifyControls/SpotifyStore.ts new file mode 100644 index 0000000..d7d52bc --- /dev/null +++ b/src/plugins/spotifyControls/SpotifyStore.ts @@ -0,0 +1,203 @@ +/* + * 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 <https://www.gnu.org/licenses/>. +*/ + +import cssText from "~fileContent/styles.css"; + +import { IpcEvents, lazyWebpack, proxyLazy } from "../../utils"; +import { filters } from "../../webpack"; +import { Flux, FluxDispatcher } from "../../webpack/common"; + +export interface Track { + id: string; + name: string; + duration: number; + isLocal: boolean; + album: { + id: string; + name: string; + image: { + height: number; + width: number; + url: string; + }; + }; + artists: { + id: string; + href: string; + name: string; + type: string; + uri: string; + }[]; +} + +interface PlayerState { + accountId: string; + track: Track | null; + volumePercent: number, + isPlaying: boolean, + repeat: boolean, + position: number, + context?: any; + device?: Device; + + // added by patch + actual_repeat: Repeat; +} + +interface Device { + id: string; + is_active: boolean; +} + +type Repeat = "off" | "track" | "context"; + +// Don't wanna run before Flux and Dispatcher are ready! +export const SpotifyStore = proxyLazy(() => { + // TODO: Move this elsewhere + const style = document.createElement("style"); + style.innerText = cssText; + document.head.appendChild(style); + + // For some reason ts hates extends Flux.Store + const { Store } = Flux; + + const SpotifySocket = lazyWebpack(filters.byProps("getActiveSocketAndDevice")); + const SpotifyAPI = lazyWebpack(filters.byProps("SpotifyAPIMarker")); + + const API_BASE = "https://api.spotify.com/v1/me/player"; + + class SpotifyStore extends Store { + constructor(dispatcher: any, handlers: any) { + super(dispatcher, handlers); + } + + public mPosition = 0; + private start = 0; + + public track: Track | null = null; + public device: Device | null = null; + public isPlaying = false; + public repeat: Repeat = "off"; + public shuffle = false; + public volume = 0; + + public isSettingPosition = false; + + public openExternal(path: string) { + VencordNative.ipc.invoke(IpcEvents.OPEN_EXTERNAL, "https://open.spotify.com" + path); + } + + // Need to keep track of this manually + public get position(): number { + let pos = this.mPosition; + if (this.isPlaying) { + pos += Date.now() - this.start; + } + return pos; + } + + public set position(p: number) { + this.mPosition = p; + this.start = Date.now(); + } + + prev() { + this.req("post", "/previous"); + } + + next() { + this.req("post", "/next"); + } + + setVolume(percent: number) { + this.req("put", "/volume", { + query: { + volume_percent: Math.round(percent) + } + + }).then(() => { + this.volume = percent; + this.emitChange(); + }); + } + + setPlaying(playing: boolean) { + this.req("put", playing ? "/play" : "/pause"); + } + + setRepeat(state: Repeat) { + this.req("put", "/repeat", { + query: { state } + }); + } + + setShuffle(state: boolean) { + this.req("put", "/shuffle", { + query: { state } + }).then(() => { + this.shuffle = state; + this.emitChange(); + }); + } + + seek(ms: number) { + if (this.isSettingPosition) return Promise.resolve(); + + this.isSettingPosition = true; + + return this.req("put", "/seek", { + query: { + position_ms: Math.round(ms) + } + }).catch((e: any) => { + console.error("[VencordSpotifyControls] Failed to seek", e); + this.isSettingPosition = false; + }); + } + + private req(method: "post" | "get" | "put", route: string, data: any = {}) { + if (this.device?.is_active) + (data.query ??= {}).device_id = this.device.id; + + const { socket } = SpotifySocket.getActiveSocketAndDevice(); + return SpotifyAPI[method](socket.accountId, socket.accessToken, { + url: API_BASE + route, + ...data + }); + } + } + + const store = new SpotifyStore(FluxDispatcher, { + SPOTIFY_PLAYER_STATE(e: PlayerState) { + store.track = e.track; + store.device = e.device ?? null; + store.isPlaying = e.isPlaying ?? false; + store.volume = e.volumePercent ?? 0; + store.repeat = e.actual_repeat || "off"; + store.position = e.position ?? 0; + store.isSettingPosition = false; + store.emitChange(); + }, + SPOTIFY_SET_DEVICES({ devices }: { devices: Device[]; }) { + store.device = devices.find(d => d.is_active) ?? devices[0] ?? null; + store.emitChange(); + } + }); + + return store; +}); |