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 { var req: Promise | 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 { 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 { 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( endpoint: string, method: "GET" | "POST" | "PUT", params: any ): Promise { 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 { return myradioApiRequest( `/timeslot/${showId.toString(10)}/showplan`, "GET", {} ).then((res) => { return Object.keys(res).map((x) => res[x]); }); } function wrapPromise(factory: (...args: TArgs[]) => Promise) { let status = "pending"; let result: T; let suspender: Promise; 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(getShowplan); export function searchForTracks( artist: string, title: string ): Promise> { 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> { 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> { return myradioApiRequest( "/nipswebUserPlaylist/allmanageduserplaylists", "GET", {} ); } // Gets the list of managed music playlists. export function getManagedPlaylists(): Promise> { 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> { 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 { 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 { 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 { return myradioApiRequest("/timeslotItem/" + timeslotItemId + "/cue", "PUT", { start_time: secs, }); } export function setTrackIntro(trackId: number, secs: number): Promise { return myradioApiRequest("/track/" + trackId + "/intro", "PUT", { duration: secs, }); } export function setTrackOutro(trackId: number, secs: number): Promise { 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 { 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 { 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 { return myradioApiRequest(`/user/currentuser`, "GET", {}).then((res) => { return res; }); } export function doesCurrentUserHavePermission(id: number): Promise { 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 { return myradioApiRequest(`/news/latestnewsitem/${newsFeedId}`, "GET", {}); }