Equicord/src/equicordplugins/songSpotlight.desktop/lib/api.ts
nexpid df890ac455
SongSpotlight: Show off songs on your profile (#853)
* feat(SongSpotlight): port from vencord PR

* feat(SongSpotlight): some slight code improvements

* fix(components): match Link's props with vencord

* fix(SongSpotlight): ditch React.memo & csp
2026-03-02 11:06:18 -05:00

136 lines
4 KiB
TypeScript

/*
* Vencord, a Discord client mod
* Copyright (c) 2026 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import type { UserData } from "@song-spotlight/api/structs";
import { showToast, Toasts, UserStore } from "@webpack/common";
import { useAuthorizationStore } from "./stores/AuthorizationStore";
import { useSongStore } from "./stores/SongStore";
import { logger } from "./utils";
export const apiConstants = {
api: "https://dc.songspotlight.nexpid.xyz/",
oauth2: {
clientId: "1157745434140344321",
redirectURL: "https://dc.songspotlight.nexpid.xyz/api/auth/authorize",
},
songLimit: 6,
};
export async function authFetch(_url: string | URL, options?: RequestInit) {
const url = new URL(_url);
try {
const res = await fetch(url, {
...options,
headers: {
...options?.headers,
Authorization: useAuthorizationStore.getState().getToken()!,
},
});
if (res.ok) return res;
// not modified
if (res.status === 304) return null;
// unauthorized
if (res.status === 401) {
useAuthorizationStore.getState().deleteToken();
}
const text = await res.text();
showToast(
!text.includes("<body>") && res.status >= 400 && res.status <= 599
? `Song Spotlight: ${text}`
: `Song Spotlight fetch error at ${url.pathname}`,
Toasts.Type.FAILURE,
);
logger.error(
"Got an authFetch response error",
options?.method ?? "GET",
url.toString(),
res.status,
text,
);
throw new Error(text);
} catch (error) {
showToast(`Song Spotlight: ${error}`, Toasts.Type.FAILURE);
logger.error(
"Got an authFetch request error",
options?.method ?? "GET",
url.toString(),
error,
);
throw error;
}
}
export async function getData(): Promise<UserData | undefined> {
return await authFetch(new URL("api/data", apiConstants.api), {
headers: {
"If-Modified-Since": useSongStore.getState().self?.at,
} as HeadersInit,
}).then(async res => {
if (!res) return useSongStore.getState().self?.data;
const data = await res.json();
useSongStore.getState().update({
data,
at: res.headers.get("Last-Modified") || undefined,
});
return data;
});
}
export async function listData(userId: string): Promise<UserData | undefined> {
if (userId === UserStore.getCurrentUser()?.id) return await getData();
return await authFetch(new URL(`api/data/${userId}`, apiConstants.api), {
headers: {
"If-Modified-Since": useSongStore.getState().users[userId]?.at,
} as HeadersInit,
}).then(async res => {
if (!res) return useSongStore.getState().users[userId]?.data;
const data = await res.json();
useSongStore.getState().update({
userId,
data,
at: res.headers.get("Last-Modified") || undefined,
});
return data;
});
}
export async function saveData(data: UserData): Promise<true> {
return await authFetch(new URL("api/data", apiConstants.api), {
method: "PUT",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
},
})
.then(res => res?.json())
.then(json => {
useSongStore
.getState().update({
data,
at: new Date().toUTCString(),
});
return json;
});
}
export async function deleteData(): Promise<true> {
return await authFetch(new URL("api/data", apiConstants.api), {
method: "DELETE",
})
.then(res => res?.json())
.then(json => {
useSongStore.getState().delete();
return json;
});
}