Vencord/src/plugins/spotifyControls/SpotifyStore.ts

198 lines
5.5 KiB
TypeScript

/*
* 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 { Settings } from "@api/Settings";
import { proxyLazy } from "@utils/lazy";
import { findByPropsLazy } 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(() => {
// For some reason ts hates extends Flux.Store
const { Store } = Flux;
const SpotifySocket = findByPropsLazy("getActiveSocketAndDevice");
const SpotifyAPI = findByPropsLazy("SpotifyAPIMarker");
const API_BASE = "https://api.spotify.com/v1/me/player";
class SpotifyStore extends Store {
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) {
const url = Settings.plugins.SpotifyControls.useSpotifyUris
? "spotify:" + path.replaceAll("/", (_, idx) => idx === 0 ? "" : ":")
: "https://open.spotify.com" + path;
VencordNative.native.openExternal(url);
}
// 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;
});