2022-11-07 21:28:29 +00:00
|
|
|
/*
|
|
|
|
* 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/>.
|
|
|
|
*/
|
|
|
|
|
2023-05-05 23:36:00 +00:00
|
|
|
import { Settings } from "@api/Settings";
|
2023-11-25 00:32:21 +00:00
|
|
|
import { findByProps, proxyLazyWebpack } from "@webpack";
|
2022-11-28 12:37:55 +00:00
|
|
|
import { Flux, FluxDispatcher } from "@webpack/common";
|
2022-11-07 21:28:29 +00:00
|
|
|
|
|
|
|
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!
|
2023-11-25 00:32:21 +00:00
|
|
|
export const SpotifyStore = proxyLazyWebpack(() => {
|
2022-11-07 21:28:29 +00:00
|
|
|
// For some reason ts hates extends Flux.Store
|
|
|
|
const { Store } = Flux;
|
|
|
|
|
2023-11-25 00:32:21 +00:00
|
|
|
const SpotifySocket = findByProps("getActiveSocketAndDevice");
|
|
|
|
const SpotifyUtils = findByProps("SpotifyAPI");
|
2022-11-07 21:28:29 +00:00
|
|
|
|
|
|
|
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) {
|
2023-06-29 14:06:21 +00:00
|
|
|
const url = Settings.plugins.SpotifyControls.useSpotifyUris || Vencord.Plugins.isPluginEnabled("OpenInApp")
|
2023-03-29 23:29:34 +00:00
|
|
|
? "spotify:" + path.replaceAll("/", (_, idx) => idx === 0 ? "" : ":")
|
|
|
|
: "https://open.spotify.com" + path;
|
|
|
|
|
2023-05-02 00:50:51 +00:00
|
|
|
VencordNative.native.openExternal(url);
|
2022-11-07 21:28:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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();
|
2023-10-25 17:28:07 +00:00
|
|
|
return SpotifyUtils.SpotifyAPI[method](socket.accountId, socket.accessToken, {
|
2022-11-07 21:28:29 +00:00
|
|
|
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;
|
|
|
|
});
|