387 lines
9.1 KiB
TypeScript
387 lines
9.1 KiB
TypeScript
import qs from "qs";
|
|
import { getUserError, setCurrentUser } from "./session/state";
|
|
import store from "./store";
|
|
|
|
export const MYRADIO_NON_API_BASE = process.env.REACT_APP_MYRADIO_NONAPI_BASE!;
|
|
export const MYRADIO_BASE_URL = process.env.REACT_APP_MYRADIO_BASE!;
|
|
export const BROADCAST_API_BASE_URL = process.env.REACT_APP_BROADCAST_API_BASE!;
|
|
|
|
export class ApiException extends Error {}
|
|
|
|
export async function apiRequest(
|
|
url: string,
|
|
method: "GET" | "POST" | "PUT",
|
|
params: any,
|
|
need_auth: boolean = true
|
|
): Promise<Response> {
|
|
var req: Promise<Response> | null = null;
|
|
if (method === "GET") {
|
|
req = fetch(url + qs.stringify(params, { addQueryPrefix: true }), {
|
|
credentials: need_auth ? "include" : "omit",
|
|
});
|
|
} else {
|
|
const body = JSON.stringify(params);
|
|
console.log(body);
|
|
req = fetch(url, {
|
|
method,
|
|
body,
|
|
headers: {
|
|
"Content-Type": "application/json; charset=UTF-8",
|
|
},
|
|
credentials: need_auth ? "include" : "omit",
|
|
});
|
|
}
|
|
return await req;
|
|
}
|
|
|
|
export async function myradioApiRequest(
|
|
endpoint: string,
|
|
method: "GET" | "POST" | "PUT",
|
|
params: any
|
|
): Promise<any> {
|
|
const res = await apiRequest(MYRADIO_BASE_URL + endpoint, method, params);
|
|
const json = await res.json();
|
|
if (json.status === "OK") {
|
|
return json.payload;
|
|
} else {
|
|
if (res.status === 401) {
|
|
// We've logged out! Oh no!
|
|
store.dispatch(setCurrentUser({ user: null, canBroadcast: false }));
|
|
store.dispatch(getUserError("User is no longer logged in."));
|
|
} else {
|
|
console.error(json.payload);
|
|
throw new ApiException(json.payload);
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function bapsicleApiRequest(
|
|
endpoint: string,
|
|
method: "GET" | "POST" | "PUT",
|
|
params: any
|
|
): Promise<any> {
|
|
let server = store.getState().bapsSession.currentServer;
|
|
|
|
if (!server) {
|
|
throw new Error("Trying to call BAPSicle server without connection.");
|
|
}
|
|
const url = `${server.ui_protocol}://${server.hostname}:${server.ui_port}${endpoint}`;
|
|
const res = await apiRequest(url, method, params);
|
|
const json = await res.json();
|
|
return json;
|
|
}
|
|
|
|
export async function broadcastApiRequest<TRes = any>(
|
|
endpoint: string,
|
|
method: "GET" | "POST" | "PUT",
|
|
params: any
|
|
): Promise<TRes> {
|
|
const res = await apiRequest(
|
|
BROADCAST_API_BASE_URL + endpoint,
|
|
method,
|
|
params,
|
|
false
|
|
);
|
|
const json = await res.json();
|
|
if (json.status === "OK") {
|
|
return json.payload;
|
|
} else {
|
|
console.error(json.reason);
|
|
throw new ApiException(json.reason);
|
|
}
|
|
}
|
|
|
|
interface Album {
|
|
title: string;
|
|
recordid: number;
|
|
artist: string;
|
|
cdid: number | null;
|
|
date_added: string;
|
|
date_released: string;
|
|
// TODO
|
|
}
|
|
|
|
export interface TimeslotItemBase {
|
|
timeslotitemid: string;
|
|
channel: number;
|
|
weight: number;
|
|
title: string;
|
|
length: string;
|
|
clean: boolean;
|
|
cue: number;
|
|
}
|
|
|
|
export interface TimeslotItemCentral {
|
|
type: "central";
|
|
trackid: number;
|
|
artist: string;
|
|
intro: number;
|
|
outro: number;
|
|
clean: boolean;
|
|
digitised: boolean;
|
|
album: Album;
|
|
}
|
|
|
|
export interface AuxItem {
|
|
type: "aux";
|
|
summary: string | number;
|
|
title: string | number;
|
|
managedid: number;
|
|
length: string;
|
|
expirydate: boolean | string;
|
|
expired: boolean;
|
|
auxid: string;
|
|
}
|
|
|
|
interface TimeslotItemAux extends AuxItem {
|
|
type: "aux";
|
|
}
|
|
|
|
export type TimeslotItem = TimeslotItemBase &
|
|
(TimeslotItemCentral | TimeslotItemAux);
|
|
|
|
export type Showplan = TimeslotItem[][];
|
|
|
|
export function getShowplan(showId: number): Promise<Showplan> {
|
|
return myradioApiRequest(
|
|
`/timeslot/${showId.toString(10)}/showplan`,
|
|
"GET",
|
|
{}
|
|
).then((res) => {
|
|
return Object.keys(res).map((x) => res[x]);
|
|
});
|
|
}
|
|
|
|
function wrapPromise<T, TArgs>(factory: (...args: TArgs[]) => Promise<T>) {
|
|
let status = "pending";
|
|
let result: T;
|
|
let suspender: Promise<void>;
|
|
return {
|
|
read(...args: TArgs[]) {
|
|
if (!(suspender instanceof Promise)) {
|
|
suspender = factory(...args).then(
|
|
(r) => {
|
|
status = "success";
|
|
result = r;
|
|
},
|
|
(e) => {
|
|
status = "error";
|
|
result = e;
|
|
}
|
|
);
|
|
}
|
|
if (status === "pending") {
|
|
throw suspender;
|
|
} else if (status === "error") {
|
|
throw result;
|
|
} else if (status === "success") {
|
|
return result;
|
|
} else {
|
|
throw new Error("Can't happen.");
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
export interface Track {
|
|
type: "central";
|
|
title: string;
|
|
artist: string;
|
|
album: Album;
|
|
trackid: number;
|
|
length: string;
|
|
intro: number;
|
|
outro: number;
|
|
clean: boolean;
|
|
digitised: boolean;
|
|
}
|
|
|
|
export const showPlanResource = wrapPromise<Showplan, number>(getShowplan);
|
|
|
|
export function searchForTracks(
|
|
artist: string,
|
|
title: string
|
|
): Promise<Array<Track>> {
|
|
if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
|
|
return bapsicleApiRequest("/library/search/track", "GET", {
|
|
artist,
|
|
title,
|
|
});
|
|
}
|
|
return myradioApiRequest("/track/search", "GET", {
|
|
artist,
|
|
title,
|
|
limit: 100,
|
|
digitised: true,
|
|
});
|
|
}
|
|
|
|
export function getTimeslots(): Promise<Array<Timeslot>> {
|
|
return bapsicleApiRequest("/plan/list", "GET", {});
|
|
}
|
|
|
|
export interface NipswebPlaylist {
|
|
type: "userPlaylist";
|
|
title: string;
|
|
managedid: string;
|
|
folder: string;
|
|
}
|
|
export interface ManagedPlaylist {
|
|
type: "managedPlaylist";
|
|
title: string;
|
|
playlistid: string;
|
|
folder: string;
|
|
}
|
|
|
|
export function getUserPlaylists(): Promise<Array<NipswebPlaylist>> {
|
|
return myradioApiRequest(
|
|
"/nipswebUserPlaylist/allmanageduserplaylists",
|
|
"GET",
|
|
{}
|
|
);
|
|
}
|
|
|
|
// Gets the list of managed music playlists.
|
|
export function getManagedPlaylists(): Promise<Array<ManagedPlaylist>> {
|
|
if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
|
|
return bapsicleApiRequest("/library/playlists/music", "GET", {});
|
|
}
|
|
return myradioApiRequest("/playlist/allitonesplaylists", "GET", {});
|
|
}
|
|
|
|
// Gets the list of managed aux playlists (sfx, beds etc.)
|
|
export function getAuxPlaylists(): Promise<Array<NipswebPlaylist>> {
|
|
if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
|
|
return bapsicleApiRequest("/library/playlists/aux", "GET", {});
|
|
}
|
|
return myradioApiRequest("/nipswebPlaylist/allmanagedplaylists", "GET", {});
|
|
}
|
|
|
|
// Loads the playlist items for a certain aux playlist
|
|
export function loadAuxLibrary(libraryId: string): Promise<AuxItem[]> {
|
|
if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
|
|
return bapsicleApiRequest("/library/playlist/aux/" + libraryId, "GET", {});
|
|
}
|
|
|
|
return apiRequest(MYRADIO_NON_API_BASE + "/NIPSWeb/load_aux_lib", "GET", {
|
|
libraryid: libraryId,
|
|
}).then((res) => res.json());
|
|
}
|
|
|
|
export function loadPlaylistLibrary(libraryId: string): Promise<Track[]> {
|
|
if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
|
|
return bapsicleApiRequest(
|
|
"/library/playlist/music/" + libraryId,
|
|
"GET",
|
|
{}
|
|
);
|
|
}
|
|
return myradioApiRequest("/playlist/" + libraryId + "/tracks", "GET", {});
|
|
}
|
|
|
|
export function setTimeslotItemCue(
|
|
timeslotItemId: string,
|
|
secs: number
|
|
): Promise<null> {
|
|
return myradioApiRequest("/timeslotItem/" + timeslotItemId + "/cue", "PUT", {
|
|
start_time: secs,
|
|
});
|
|
}
|
|
|
|
export function setTrackIntro(trackId: number, secs: number): Promise<null> {
|
|
return myradioApiRequest("/track/" + trackId + "/intro", "PUT", {
|
|
duration: secs,
|
|
});
|
|
}
|
|
|
|
export function setTrackOutro(trackId: number, secs: number): Promise<null> {
|
|
return myradioApiRequest("/track/" + trackId + "/outro", "PUT", {
|
|
duration: secs,
|
|
});
|
|
}
|
|
|
|
export type UpdateOp =
|
|
| {
|
|
op: "MoveItem";
|
|
timeslotitemid: string;
|
|
oldchannel: number;
|
|
oldweight: number;
|
|
channel: number;
|
|
weight: number;
|
|
}
|
|
| {
|
|
op: "AddItem";
|
|
channel: number;
|
|
weight: number;
|
|
id: string;
|
|
}
|
|
| {
|
|
op: "RemoveItem";
|
|
timeslotitemid: string;
|
|
channel: number;
|
|
weight: number;
|
|
};
|
|
|
|
interface OpResult {
|
|
status: boolean;
|
|
timeslotitemid?: string;
|
|
}
|
|
|
|
export function updateShowplan(
|
|
timeslotid: number,
|
|
ops: UpdateOp[]
|
|
): Promise<OpResult[]> {
|
|
return myradioApiRequest(`/timeslot/${timeslotid}/updateshowplan`, "PUT", {
|
|
set: ops,
|
|
});
|
|
}
|
|
|
|
export interface Timeslot {
|
|
timeslot_id: number;
|
|
time: number;
|
|
start_time: string;
|
|
title: string;
|
|
duration: string;
|
|
credits_string: string;
|
|
}
|
|
|
|
export function getCurrentApiTimeslot(): Promise<Timeslot> {
|
|
return myradioApiRequest(`/timeslot/userselectedtimeslot`, "GET", {}).then(
|
|
(res) => {
|
|
return res;
|
|
}
|
|
);
|
|
}
|
|
|
|
export interface User {
|
|
memberid: number;
|
|
fname: string;
|
|
sname: string;
|
|
url: string;
|
|
photo: string;
|
|
public_email?: string;
|
|
}
|
|
|
|
export function getCurrentApiUser(): Promise<User> {
|
|
return myradioApiRequest(`/user/currentuser`, "GET", {}).then((res) => {
|
|
return res;
|
|
});
|
|
}
|
|
|
|
export function doesCurrentUserHavePermission(id: number): Promise<boolean> {
|
|
return myradioApiRequest("/auth/haspermission/" + id.toString(10), "GET", {});
|
|
}
|
|
|
|
export interface NewsEntry {
|
|
newsentryid: string;
|
|
author: string;
|
|
posted: string;
|
|
content: string;
|
|
seen?: boolean;
|
|
}
|
|
|
|
export function getLatestNewsItem(
|
|
newsFeedId: number
|
|
): Promise<NewsEntry | null> {
|
|
return myradioApiRequest(`/news/latestnewsitem/${newsFeedId}`, "GET", {});
|
|
}
|