aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/spotifyControls/SpotifyStore.ts
diff options
context:
space:
mode:
authorVen <vendicated@riseup.net>2022-11-07 22:28:29 +0100
committerGitHub <noreply@github.com>2022-11-07 22:28:29 +0100
commit6a8564089bea162d9c4d52925eb1239b6b270fa4 (patch)
treeb411e223ffab2752175e2e883908cd814f73455d /src/plugins/spotifyControls/SpotifyStore.ts
parent7d5ade21fc9b56d21e2eb9e5b0d35502432adaa2 (diff)
downloadVencord-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.ts203
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;
+});