167 lines
4.7 KiB
TypeScript
167 lines
4.7 KiB
TypeScript
/**
|
|
* Copyright (C) 2019-2021 Carl Kittelberger <icedream@icedream.pw>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import { deprecate } from 'util';
|
|
import { getThumbnailURL } from './thumbnail';
|
|
import { RunnerList } from './datatypes/RunnerList';
|
|
import { VideoEntry, VideoList } from './datatypes/VideoList';
|
|
import { VideoOnDemandIndex } from './datatypes/VideoOnDemandIdentifier';
|
|
import sanitizeTitle from './sanitizeTitle';
|
|
|
|
const upstreamURL = process.env.UPSTREAM_URL;
|
|
const upstreamDirectURL = process.env.UPSTREAM_DIRECT_URL || upstreamURL;
|
|
// const apiURL = process.env.API_URL || `${upstreamURL}/api`;
|
|
const apiDirectURL = process.env.API_DIRECT_URL || `${upstreamDirectURL}/api`;
|
|
|
|
// TODO - where did the hourglass image go?
|
|
const hourglassImage: string = null;
|
|
|
|
export class HTTPError extends Error {
|
|
response: Response;
|
|
|
|
data: any;
|
|
|
|
constructor(response: Response, data: any) {
|
|
super(`HTTP server responded with error: ${response.statusText}`);
|
|
|
|
this.response = response;
|
|
this.data = data;
|
|
}
|
|
}
|
|
|
|
export async function fetchJson(
|
|
input: RequestInfo,
|
|
init?: RequestInit,
|
|
) {
|
|
try {
|
|
const response = await fetch(input, init);
|
|
|
|
// if the server replies, there's always some data in json
|
|
// if there's a network error, it will throw at the previous line
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
return data;
|
|
}
|
|
|
|
throw new HTTPError(response, data);
|
|
} catch (error) {
|
|
if (!error.data) {
|
|
error.data = { message: error.message };
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export function makeThumbnailURLSet(
|
|
thumbnailServerURL: string,
|
|
id: string,
|
|
{
|
|
downloadFileName,
|
|
thumbnails,
|
|
}: VideoEntry,
|
|
): { [srcSetSpec: string]: URL } {
|
|
// let relativePaths;
|
|
|
|
// If thumbnails were presented by the JSON API, use those
|
|
if (thumbnails) {
|
|
return thumbnails
|
|
.reduce((all, { sourceSize, fileName }) => ({
|
|
...all,
|
|
[sourceSize]: new URL(fileName, thumbnailServerURL),
|
|
}), {});
|
|
}
|
|
|
|
// Generate default set of URLs based on MP4s on the upstream server
|
|
return [96, 96 * 2, 96 * 3]
|
|
.reduce((all, width) => ({
|
|
[`${width}w`]: downloadFileName
|
|
? getThumbnailURL(thumbnailServerURL, id, downloadFileName)
|
|
: hourglassImage,
|
|
}), {});
|
|
}
|
|
deprecate(makeThumbnailURLSet, 'makeThumbnailURLSet() is deprecated.');
|
|
|
|
async function getDirect(relativeURL: string) {
|
|
return fetchJson(`${apiDirectURL}/${relativeURL}`);
|
|
}
|
|
|
|
// async function get(relativeURL: any) {
|
|
// return fetchJson(`${apiURL}/${relativeURL}`);
|
|
// }
|
|
|
|
export async function getIndex(): Promise<VideoOnDemandIndex> {
|
|
return getDirect('index.json');
|
|
}
|
|
|
|
export async function getRunners(): Promise<RunnerList> {
|
|
return getDirect('runners.json');
|
|
}
|
|
|
|
export async function getVideos(id: string): Promise<VideoList> {
|
|
const result: VideoList = await getDirect(`videos/${id}.json`);
|
|
result.videos = result.videos.reduce((all: Array<VideoEntry>, {
|
|
slug,
|
|
title,
|
|
...video
|
|
}: VideoEntry) => [
|
|
...all,
|
|
{
|
|
...video,
|
|
title,
|
|
slug: typeof slug === 'string'
|
|
? slug
|
|
: sanitizeTitle(title + (
|
|
all.find((v) => v.title === title)
|
|
? ` ${all.filter((v) => v.title === title).length + 1}`
|
|
: ''
|
|
)),
|
|
},
|
|
], []);
|
|
return result;
|
|
}
|
|
|
|
export function getDownloadURL(id: string, fileName: string): string {
|
|
return [upstreamURL, encodeURIComponent(id), encodeURIComponent(fileName)]
|
|
.join('/');
|
|
}
|
|
|
|
export function submitPreferences(data: {
|
|
enableDark?: boolean,
|
|
locale?: string,
|
|
volume?: number,
|
|
}) {
|
|
const formData = new URLSearchParams();
|
|
if (data.enableDark !== undefined && data.enableDark !== null) {
|
|
formData.append('enableDark', data.enableDark.toString());
|
|
}
|
|
if (data.locale !== undefined && data.locale !== null) {
|
|
formData.append('locale', data.locale);
|
|
}
|
|
if (data.volume !== undefined && data.volume !== null) {
|
|
formData.append('volume', data.volume.toString());
|
|
}
|
|
|
|
fetchJson('/api/changePreferences', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: formData,
|
|
});
|
|
}
|