Arm 64 support & liniting

This commit is contained in:
WardPearce
2024-04-11 18:26:47 +12:00
parent 15f55b08b6
commit 8a8f31c34d
28 changed files with 1214 additions and 1143 deletions
+5 -8
View File
@@ -13,23 +13,20 @@ jobs:
working-directory: ./materialious
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
uses: docker/build-push-action@v5
with:
context: ./materialious
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: wardpearce/materialious:latest
+1 -2
View File
@@ -10,5 +10,4 @@ declare global {
}
}
export { };
export {};
+18 -17
View File
@@ -1,20 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="%sveltekit.assets%/style.css" />
<meta name="description" content="Modern material design for Invidious." />
<meta
name="keywords"
content="invidious,materialious,proxy,youtube,yt,theme,interface,modern"
/>
<meta name="theme-color" content="#141316" />
<title>Materialious</title>
%sveltekit.head%
</head>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="%sveltekit.assets%/style.css">
<meta name="description" content="Modern material design for Invidious.">
<meta name="keywords" content="invidious,materialious,proxy,youtube,yt,theme,interface,modern">
<meta name="theme-color" content="#141316">
<title>Materialious</title>
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover" style="background-color: #141316">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
<body data-sveltekit-preload-data="hover" style="background-color: #141316">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
+202 -148
View File
@@ -1,239 +1,293 @@
import { get } from 'svelte/store';
import { auth, deArrowInstance, deArrowThumbnailInstance, returnYTDislikesInstance } from '../../store';
import type { Channel, ChannelContentPlaylists, ChannelContentVideos, ChannelPage, Comments, DeArrow, Playlist, PlaylistPage, ReturnYTDislikes, SearchSuggestion, Subscription, Video, VideoPlay } from './model';
import {
auth,
deArrowInstance,
deArrowThumbnailInstance,
returnYTDislikesInstance
} from '../../store';
import type {
Channel,
ChannelContentPlaylists,
ChannelContentVideos,
ChannelPage,
Comments,
DeArrow,
Playlist,
PlaylistPage,
ReturnYTDislikes,
SearchSuggestion,
Subscription,
Video,
VideoPlay
} from './model';
export function buildPath(path: string): string {
return `${import.meta.env.VITE_DEFAULT_INVIDIOUS_INSTANCE}/api/v1/${path}`;
return `${import.meta.env.VITE_DEFAULT_INVIDIOUS_INSTANCE}/api/v1/${path}`;
}
export async function fetchErrorHandle(response: Response): Promise<Response> {
if (!response.ok) {
let message = 'Internal error';
try {
const json = await response.json();
message = 'errorBacktrace' in json ? json.errorBacktrace : json.error;
} catch { }
throw Error(message);
}
if (!response.ok) {
let message = 'Internal error';
try {
const json = await response.json();
message = 'errorBacktrace' in json ? json.errorBacktrace : json.error;
} catch { }
throw Error(message);
}
return response;
return response;
}
export function buildAuthHeaders(): { headers: { Authorization: string; }; } {
return { headers: { Authorization: `Bearer ${get(auth)?.token}` } };
return { headers: { Authorization: `Bearer ${get(auth)?.token}` } };
}
export async function getTrending(): Promise<Video[]> {
const resp = await fetchErrorHandle(await fetch(buildPath('trending')));
return await resp.json();
const resp = await fetchErrorHandle(await fetch(buildPath('trending')));
return await resp.json();
}
export async function getPopular(): Promise<Video[]> {
const resp = await fetchErrorHandle(await fetch(buildPath('popular')));
return await resp.json();
const resp = await fetchErrorHandle(await fetch(buildPath('popular')));
return await resp.json();
}
export async function getVideo(videoId: string, local: boolean = false): Promise<VideoPlay> {
const resp = await fetchErrorHandle(await fetch(buildPath(`videos/${videoId}?local=${local}`)));
return await resp.json();
const resp = await fetchErrorHandle(await fetch(buildPath(`videos/${videoId}?local=${local}`)));
return await resp.json();
}
export async function getDislikes(videoId: string): Promise<ReturnYTDislikes> {
const resp = await fetchErrorHandle(await fetch(`${get(returnYTDislikesInstance)}/votes?videoId=${videoId}`));
return await resp.json();
const resp = await fetchErrorHandle(
await fetch(`${get(returnYTDislikesInstance)}/votes?videoId=${videoId}`)
);
return await resp.json();
}
export async function getComments(videoId: string, parameters: {
sort_by?: "top" | "new",
source?: "youtube" | "reddit",
continuation?: string;
}): Promise<Comments> {
if (typeof parameters.sort_by === "undefined") {
parameters.sort_by = "top";
}
export async function getComments(
videoId: string,
parameters: {
sort_by?: 'top' | 'new';
source?: 'youtube' | 'reddit';
continuation?: string;
}
): Promise<Comments> {
if (typeof parameters.sort_by === 'undefined') {
parameters.sort_by = 'top';
}
if (typeof parameters.source === "undefined") {
parameters.source = "youtube";
}
if (typeof parameters.source === 'undefined') {
parameters.source = 'youtube';
}
const path = new URL(buildPath(`comments/${videoId}`));
path.search = new URLSearchParams(parameters).toString();
const resp = await fetchErrorHandle(await fetch(path));
return await resp.json();
const path = new URL(buildPath(`comments/${videoId}`));
path.search = new URLSearchParams(parameters).toString();
const resp = await fetchErrorHandle(await fetch(path));
return await resp.json();
}
export async function getChannel(channelId: string): Promise<ChannelPage> {
const resp = await fetchErrorHandle(await fetch(buildPath(`channels/${channelId}`)));
return await resp.json();
const resp = await fetchErrorHandle(await fetch(buildPath(`channels/${channelId}`)));
return await resp.json();
}
export async function getChannelContent(
channelId: string,
parameters: {
type?: 'videos' | 'playlists' | 'streams' | 'shorts';
continuation?: string;
}): Promise<ChannelContentVideos | ChannelContentPlaylists> {
if (typeof parameters.type === 'undefined') parameters.type = 'videos';
channelId: string,
parameters: {
type?: 'videos' | 'playlists' | 'streams' | 'shorts';
continuation?: string;
}
): Promise<ChannelContentVideos | ChannelContentPlaylists> {
if (typeof parameters.type === 'undefined') parameters.type = 'videos';
const url = new URL(buildPath(`channels/${channelId}/${parameters.type}`));
const url = new URL(buildPath(`channels/${channelId}/${parameters.type}`));
if (typeof parameters.continuation !== 'undefined') url.searchParams.set('continuation', parameters.continuation);
if (typeof parameters.continuation !== 'undefined')
url.searchParams.set('continuation', parameters.continuation);
const resp = await fetchErrorHandle(await fetch(url.toString()));
return await resp.json();
const resp = await fetchErrorHandle(await fetch(url.toString()));
return await resp.json();
}
export async function getSearchSuggestions(search: string): Promise<SearchSuggestion> {
const path = new URL(buildPath("search/suggestions"));
path.search = new URLSearchParams({ q: search }).toString();
const resp = await fetchErrorHandle(await fetch(path));
return await resp.json();
const path = new URL(buildPath('search/suggestions'));
path.search = new URLSearchParams({ q: search }).toString();
const resp = await fetchErrorHandle(await fetch(path));
return await resp.json();
}
export async function getSearch(search: string, options: {
sort_by?: "relevance" | "rating" | "upload_date" | "view_count",
type?: "video" | "playlist" | "channel" | "all";
page?: string;
}): Promise<(Channel | Video | Playlist)[]> {
if (typeof options.sort_by === "undefined") {
options.sort_by = "relevance";
}
export async function getSearch(
search: string,
options: {
sort_by?: 'relevance' | 'rating' | 'upload_date' | 'view_count';
type?: 'video' | 'playlist' | 'channel' | 'all';
page?: string;
}
): Promise<(Channel | Video | Playlist)[]> {
if (typeof options.sort_by === 'undefined') {
options.sort_by = 'relevance';
}
if (typeof options.type === "undefined") {
options.type = "video";
}
if (typeof options.type === 'undefined') {
options.type = 'video';
}
if (typeof options.page === "undefined") {
options.page = "1";
}
if (typeof options.page === 'undefined') {
options.page = '1';
}
const path = new URL(buildPath("search"));
path.search = new URLSearchParams({ ...options, q: search }).toString();
const resp = await fetchErrorHandle(await fetch(path));
return await resp.json();
const path = new URL(buildPath('search'));
path.search = new URLSearchParams({ ...options, q: search }).toString();
const resp = await fetchErrorHandle(await fetch(path));
return await resp.json();
}
export async function getFeed(maxResults: number, page: number) {
const path = new URL(buildPath("auth/feed"));
path.search = new URLSearchParams({ max_results: maxResults.toString(), page: page.toString() }).toString();
const resp = await fetchErrorHandle(await fetch(path, buildAuthHeaders()));
return await resp.json();
const path = new URL(buildPath('auth/feed'));
path.search = new URLSearchParams({
max_results: maxResults.toString(),
page: page.toString()
}).toString();
const resp = await fetchErrorHandle(await fetch(path, buildAuthHeaders()));
return await resp.json();
}
export async function getSubscriptions(): Promise<Subscription[]> {
const resp = await fetchErrorHandle(await fetch(buildPath("auth/subscriptions"), buildAuthHeaders()));
return await resp.json();
const resp = await fetchErrorHandle(
await fetch(buildPath('auth/subscriptions'), buildAuthHeaders())
);
return await resp.json();
}
export async function amSubscribed(authorId: string): Promise<boolean> {
try {
const subscriptions = (await getSubscriptions()).filter(sub => sub.authorId === authorId);
return subscriptions.length === 1;
} catch {
return false;
}
try {
const subscriptions = (await getSubscriptions()).filter((sub) => sub.authorId === authorId);
return subscriptions.length === 1;
} catch {
return false;
}
}
export async function postSubscribe(authorId: string) {
await fetchErrorHandle(await fetch(buildPath(`auth/subscriptions/${authorId}`), {
method: "POST",
...buildAuthHeaders()
}));
await fetchErrorHandle(
await fetch(buildPath(`auth/subscriptions/${authorId}`), {
method: 'POST',
...buildAuthHeaders()
})
);
}
export async function deleteUnsubscribe(authorId: string) {
await fetchErrorHandle(await fetch(buildPath(`auth/subscriptions/${authorId}`), {
method: 'DELETE',
...buildAuthHeaders()
}));
await fetchErrorHandle(
await fetch(buildPath(`auth/subscriptions/${authorId}`), {
method: 'DELETE',
...buildAuthHeaders()
})
);
}
export async function getHistory(page: number = 1): Promise<string[]> {
const resp = await fetchErrorHandle(await fetch(buildPath(`auth/history?page=${page}`), buildAuthHeaders()));
return await resp.json();
const resp = await fetchErrorHandle(
await fetch(buildPath(`auth/history?page=${page}`), buildAuthHeaders())
);
return await resp.json();
}
export async function deleteHistory(videoId: string | undefined = undefined) {
let url = '/api/v1/auth/history';
if (typeof videoId !== 'undefined') {
url += `/${videoId}`;
}
let url = '/api/v1/auth/history';
if (typeof videoId !== 'undefined') {
url += `/${videoId}`;
}
await fetchErrorHandle(await fetch(buildPath(url), {
method: 'DELETE',
...buildAuthHeaders()
}));
await fetchErrorHandle(
await fetch(buildPath(url), {
method: 'DELETE',
...buildAuthHeaders()
})
);
}
export async function postHistory(videoId: string) {
await fetchErrorHandle(await fetch(buildPath(`auth/history/${videoId}`), {
method: 'POST',
...buildAuthHeaders()
}));
await fetchErrorHandle(
await fetch(buildPath(`auth/history/${videoId}`), {
method: 'POST',
...buildAuthHeaders()
})
);
}
export async function getPlaylist(playlistId: string, page: number = 1): Promise<PlaylistPage> {
let resp;
let resp;
if (get(auth)) {
resp = await fetch(buildPath(`auth/playlists/${playlistId}?page=${page}`), buildAuthHeaders());
} else {
resp = await fetch(buildPath(`playlists/${playlistId}?page=${page}`));
}
await fetchErrorHandle(resp);
return await resp.json();
if (get(auth)) {
resp = await fetch(buildPath(`auth/playlists/${playlistId}?page=${page}`), buildAuthHeaders());
} else {
resp = await fetch(buildPath(`playlists/${playlistId}?page=${page}`));
}
await fetchErrorHandle(resp);
return await resp.json();
}
export async function getPersonalPlaylists(): Promise<PlaylistPage[]> {
const resp = await fetchErrorHandle(await fetch(buildPath('auth/playlists'), buildAuthHeaders()));
return await resp.json();
const resp = await fetchErrorHandle(await fetch(buildPath('auth/playlists'), buildAuthHeaders()));
return await resp.json();
}
export async function deletePersonalPlaylist(playlistId: string) {
await fetchErrorHandle(await fetch(buildPath(`auth/playlists/${playlistId}`), {
method: 'DELETE',
...buildAuthHeaders()
}));
await fetchErrorHandle(
await fetch(buildPath(`auth/playlists/${playlistId}`), {
method: 'DELETE',
...buildAuthHeaders()
})
);
}
export async function postPersonalPlaylist(title: string, privacy: 'public' | 'private' | 'unlisted') {
let headers: Record<string, Record<string, string>> = buildAuthHeaders();
headers['headers']['Content-type'] = 'application/json';
export async function postPersonalPlaylist(
title: string,
privacy: 'public' | 'private' | 'unlisted'
) {
const headers: Record<string, Record<string, string>> = buildAuthHeaders();
headers['headers']['Content-type'] = 'application/json';
await fetchErrorHandle(await fetch(buildPath('auth/playlists'), {
method: 'POST',
body: JSON.stringify({
title: title,
privacy: privacy
}),
...headers
}));
await fetchErrorHandle(
await fetch(buildPath('auth/playlists'), {
method: 'POST',
body: JSON.stringify({
title: title,
privacy: privacy
}),
...headers
})
);
}
export async function addPlaylistVideo(playlistId: string, videoId: string) {
let headers: Record<string, Record<string, string>> = buildAuthHeaders();
headers['headers']['Content-type'] = 'application/json';
const headers: Record<string, Record<string, string>> = buildAuthHeaders();
headers['headers']['Content-type'] = 'application/json';
await fetchErrorHandle(await fetch(buildPath(`auth/playlists/${playlistId}/videos`), {
method: 'POST',
body: JSON.stringify({
videoId: videoId
}),
...headers
}));
await fetchErrorHandle(
await fetch(buildPath(`auth/playlists/${playlistId}/videos`), {
method: 'POST',
body: JSON.stringify({
videoId: videoId
}),
...headers
})
);
}
export async function getDeArrow(videoId: string): Promise<DeArrow> {
const resp = await fetchErrorHandle(
await fetch(`${get(deArrowInstance)}/api/branding?videoID=${videoId}`
)
);
return await resp.json();
const resp = await fetchErrorHandle(
await fetch(`${get(deArrowInstance)}/api/branding?videoID=${videoId}`)
);
return await resp.json();
}
export async function getThumbnail(videoId: string, time: number): Promise<string> {
const resp = await fetchErrorHandle(
await fetch(`${get(deArrowThumbnailInstance)}/api/v1/getThumbnail?videoID=${videoId}&time=${time}`)
);
return URL.createObjectURL(await resp.blob());
}
const resp = await fetchErrorHandle(
await fetch(
`${get(deArrowThumbnailInstance)}/api/v1/getThumbnail?videoID=${videoId}&time=${time}`
)
);
return URL.createObjectURL(await resp.blob());
}
+182 -183
View File
@@ -1,252 +1,251 @@
export interface Image {
url: string;
width: number;
height: number;
url: string;
width: number;
height: number;
}
export interface Thumbnail {
quality: string;
url: string;
width: number;
height: number;
quality: string;
url: string;
width: number;
height: number;
}
export interface VideoBase {
videoId: string;
title: string;
videoThumbnails: Thumbnail[];
author: string;
authorId: string;
lengthSeconds: number;
viewCountText: string;
videoId: string;
title: string;
videoThumbnails: Thumbnail[];
author: string;
authorId: string;
lengthSeconds: number;
viewCountText: string;
}
export interface Video extends VideoBase {
type: "video";
title: string;
authorUrl: string;
authorVerified: boolean;
description: string;
descriptionHtml: string;
viewCount: number;
published: number;
publishedText: string;
premiereTimestamp: number;
liveNow: boolean;
premium: boolean;
isUpcoming: boolean;
type: 'video';
title: string;
authorUrl: string;
authorVerified: boolean;
description: string;
descriptionHtml: string;
viewCount: number;
published: number;
publishedText: string;
premiereTimestamp: number;
liveNow: boolean;
premium: boolean;
isUpcoming: boolean;
}
export interface AdaptiveFormats {
index: string;
bitrate: string;
init: string;
url: string;
itag: string;
type: string;
clen: string;
lmt: string;
projectionType: number;
container?: string;
encoding?: string;
qualityLabel?: string;
resolution?: string;
audioQuality?: string;
index: string;
bitrate: string;
init: string;
url: string;
itag: string;
type: string;
clen: string;
lmt: string;
projectionType: number;
container?: string;
encoding?: string;
qualityLabel?: string;
resolution?: string;
audioQuality?: string;
}
export interface FormatStreams {
url: string;
itag: string;
type: string;
quality: string;
container: string;
encoding: string;
qualityLabel: string;
resolution: string;
size: string;
url: string;
itag: string;
type: string;
quality: string;
container: string;
encoding: string;
qualityLabel: string;
resolution: string;
size: string;
}
export interface Captions {
label: string;
language_code: string;
url: string;
};
label: string;
language_code: string;
url: string;
}
export interface VideoPlay extends Video {
keywords: string[];
likeCount: number;
dislikeCount: number;
subCountText: string;
allowRatings: boolean;
rating: number;
isListed: number;
isFamilyFriendly: boolean;
allowedRegions: string[];
genre: string;
genreUrl: string;
hlsUrl?: string;
dashUrl: string;
adaptiveFormats: AdaptiveFormats[];
formatStreams: FormatStreams[];
recommendedVideos: VideoBase[];
authorThumbnails: Image[];
captions: Captions[];
storyboards?: {
url: string;
templateUrl: string;
width: number;
height: number;
count: number;
interval: number;
storyboardWidth: number;
storyboardHeight: number;
storyboardCount: number;
}[];
keywords: string[];
likeCount: number;
dislikeCount: number;
subCountText: string;
allowRatings: boolean;
rating: number;
isListed: number;
isFamilyFriendly: boolean;
allowedRegions: string[];
genre: string;
genreUrl: string;
hlsUrl?: string;
dashUrl: string;
adaptiveFormats: AdaptiveFormats[];
formatStreams: FormatStreams[];
recommendedVideos: VideoBase[];
authorThumbnails: Image[];
captions: Captions[];
storyboards?: {
url: string;
templateUrl: string;
width: number;
height: number;
count: number;
interval: number;
storyboardWidth: number;
storyboardHeight: number;
storyboardCount: number;
}[];
}
export interface ReturnYTDislikes {
id: string;
dateCreated: string;
likes: number;
dislikes: number;
rating: number;
viewCount: number;
deleted: boolean;
id: string;
dateCreated: string;
likes: number;
dislikes: number;
rating: number;
viewCount: number;
deleted: boolean;
}
export interface Comment {
author: string;
authorThumbnails: Image[];
authorID: string;
authorUrl: string;
isEdited: boolean;
isPinned: boolean;
content: string;
contentHtml: string;
published: number;
publishedText: string;
likeCount: number;
authorIsChannelOwner: boolean;
creatorHeart: {
creatorThumbnail: string;
creatorName: string;
};
replies: {
replyCount: number;
continuation: string;
};
author: string;
authorThumbnails: Image[];
authorID: string;
authorUrl: string;
isEdited: boolean;
isPinned: boolean;
content: string;
contentHtml: string;
published: number;
publishedText: string;
likeCount: number;
authorIsChannelOwner: boolean;
creatorHeart: {
creatorThumbnail: string;
creatorName: string;
};
replies: {
replyCount: number;
continuation: string;
};
}
export interface Comments {
commentCount: number;
videoId: string;
continuation?: string;
comments: Comment[];
commentCount: number;
videoId: string;
continuation?: string;
comments: Comment[];
}
export interface Channel {
type: "channel";
author: string;
authorId: string;
authorUrl: string;
authorVerified: boolean;
subCount: number;
totalViews: number;
autoGenerated: boolean;
description: string;
descriptionHml: string;
authorThumbnails: Image[];
type: 'channel';
author: string;
authorId: string;
authorUrl: string;
authorVerified: boolean;
subCount: number;
totalViews: number;
autoGenerated: boolean;
description: string;
descriptionHml: string;
authorThumbnails: Image[];
}
export interface PlaylistVideo {
title: string;
videoId: string;
lengthSeconds: number;
videoThumbnails: Thumbnail[];
title: string;
videoId: string;
lengthSeconds: number;
videoThumbnails: Thumbnail[];
}
export interface Playlist {
type: "playlist";
title: string;
playlistId: string;
playlistThumbnail: string;
author: string;
authorId: string;
authorVerified: boolean;
videoCount: number;
videos: PlaylistVideo[];
type: 'playlist';
title: string;
playlistId: string;
playlistThumbnail: string;
author: string;
authorId: string;
authorVerified: boolean;
videoCount: number;
videos: PlaylistVideo[];
}
export interface PlaylistPageVideo extends PlaylistVideo {
author: string;
index: number;
authorId: string;
viewCount: number;
author: string;
index: number;
authorId: string;
viewCount: number;
}
export interface ChannelContentVideos {
videos: Video[];
continuation: string;
videos: Video[];
continuation: string;
}
export interface ChannelContentPlaylists {
playlists: PlaylistPage[];
continuation: string;
playlists: PlaylistPage[];
continuation: string;
}
export interface PlaylistPage extends Playlist {
description: string;
descriptionHtml: string;
viewCount: number;
updated: number;
isListed: boolean;
videos: PlaylistPageVideo[];
description: string;
descriptionHtml: string;
viewCount: number;
updated: number;
isListed: boolean;
videos: PlaylistPageVideo[];
}
export interface ChannelPage extends Channel {
allowedRegions: string[];
tabs: string[];
latestVideos: Video[];
isFamilyFriendly: boolean;
joined: number;
authorBanners: Image[];
allowedRegions: string[];
tabs: string[];
latestVideos: Video[];
isFamilyFriendly: boolean;
joined: number;
authorBanners: Image[];
}
export interface SearchSuggestion {
query: string;
suggestions: string[];
query: string;
suggestions: string[];
}
export interface Notification extends VideoBase {
type: "video" | "shortVideo" | "stream";
type: 'video' | 'shortVideo' | 'stream';
}
export interface Feed {
notifications: Notification[];
videos: Video[];
notifications: Notification[];
videos: Video[];
}
export interface Subscription {
author: string;
authorId: string;
author: string;
authorId: string;
}
export interface DeArrow {
titles: {
title: string;
original: boolean;
votes: number;
locked: boolean;
UUID: string;
}[];
thumbnails: {
timestamp: number | null;
original: boolean;
votes: number;
locked: boolean;
UUID: string;
}[];
randomTime: number;
videoDuration: number;
}
titles: {
title: string;
original: boolean;
votes: number;
locked: boolean;
UUID: string;
}[];
thumbnails: {
timestamp: number | null;
original: boolean;
votes: number;
locked: boolean;
UUID: string;
}[];
randomTime: number;
videoDuration: number;
}
@@ -7,8 +7,6 @@
export let channel: Channel;
let loading = true;
let loaded = false;
let failed = false;
let img: HTMLImageElement;
@@ -17,12 +15,10 @@
img.src = channel.authorThumbnails[0].url;
img.onload = () => {
loaded = true;
loading = false;
};
img.onerror = () => {
loading = false;
failed = true;
};
});
</script>
+140 -140
View File
@@ -1,154 +1,154 @@
import { page } from "$app/stores";
import { get } from "svelte/store";
import { page } from '$app/stores';
import { get } from 'svelte/store';
import {
darkMode,
deArrowEnabled,
deArrowInstance,
deArrowThumbnailInstance,
interfaceSearchSuggestions,
playerAlwaysLoop,
playerAutoPlay,
playerAutoplayNextByDefault,
playerDash,
playerListenByDefault,
playerProxyVideos,
playerSavePlaybackPosition,
playerTheatreModeByDefault,
returnYTDislikesInstance,
sponsorBlock,
sponsorBlockCategories,
sponsorBlockUrl,
themeColor
} from "../store";
darkMode,
deArrowEnabled,
deArrowInstance,
deArrowThumbnailInstance,
interfaceSearchSuggestions,
playerAlwaysLoop,
playerAutoPlay,
playerAutoplayNextByDefault,
playerDash,
playerListenByDefault,
playerProxyVideos,
playerSavePlaybackPosition,
playerTheatreModeByDefault,
returnYTDislikesInstance,
sponsorBlock,
sponsorBlockCategories,
sponsorBlockUrl,
themeColor
} from '../store';
const persistedStores = [
{
name: 'returnYTDislikesInstance',
store: returnYTDislikesInstance,
type: 'string'
},
{
name: 'darkMode',
store: darkMode,
type: 'boolean'
},
{
name: 'themeColor',
store: themeColor,
type: 'string'
},
{
name: 'autoPlay',
store: playerAutoPlay,
type: 'boolean'
},
{
name: 'alwaysLoop',
store: playerAlwaysLoop,
type: 'boolean'
},
{
name: 'proxyVideos',
store: playerProxyVideos,
type: 'boolean'
},
{
name: 'listenByDefault',
store: playerListenByDefault,
type: 'boolean'
},
{
name: 'savePlaybackPosition',
store: playerSavePlaybackPosition,
type: 'boolean'
},
{
name: 'dashEnabled',
store: playerDash,
type: 'boolean'
},
{
name: 'theatreModeByDefault',
store: playerTheatreModeByDefault,
type: 'boolean'
},
{
name: 'autoplayNextByDefault',
store: playerAutoplayNextByDefault,
type: 'boolean'
},
{
name: 'returnYtDislikes',
store: returnYTDislikesInstance,
type: 'boolean'
},
{
name: 'searchSuggestions',
store: interfaceSearchSuggestions,
type: 'boolean'
},
{
name: 'sponsorBlock',
store: sponsorBlock,
type: 'boolean'
},
{
name: 'sponsorBlockUrl',
store: sponsorBlockUrl,
type: 'string'
},
{
name: 'sponsorBlockCategories',
store: sponsorBlockCategories,
type: 'array'
},
{
name: 'deArrowInstance',
store: deArrowInstance,
type: 'string'
},
{
name: 'deArrowEnabled',
store: deArrowEnabled,
type: 'boolean'
},
{
name: 'deArrowThumbnailInstance',
store: deArrowThumbnailInstance,
type: 'string'
}
{
name: 'returnYTDislikesInstance',
store: returnYTDislikesInstance,
type: 'string'
},
{
name: 'darkMode',
store: darkMode,
type: 'boolean'
},
{
name: 'themeColor',
store: themeColor,
type: 'string'
},
{
name: 'autoPlay',
store: playerAutoPlay,
type: 'boolean'
},
{
name: 'alwaysLoop',
store: playerAlwaysLoop,
type: 'boolean'
},
{
name: 'proxyVideos',
store: playerProxyVideos,
type: 'boolean'
},
{
name: 'listenByDefault',
store: playerListenByDefault,
type: 'boolean'
},
{
name: 'savePlaybackPosition',
store: playerSavePlaybackPosition,
type: 'boolean'
},
{
name: 'dashEnabled',
store: playerDash,
type: 'boolean'
},
{
name: 'theatreModeByDefault',
store: playerTheatreModeByDefault,
type: 'boolean'
},
{
name: 'autoplayNextByDefault',
store: playerAutoplayNextByDefault,
type: 'boolean'
},
{
name: 'returnYtDislikes',
store: returnYTDislikesInstance,
type: 'boolean'
},
{
name: 'searchSuggestions',
store: interfaceSearchSuggestions,
type: 'boolean'
},
{
name: 'sponsorBlock',
store: sponsorBlock,
type: 'boolean'
},
{
name: 'sponsorBlockUrl',
store: sponsorBlockUrl,
type: 'string'
},
{
name: 'sponsorBlockCategories',
store: sponsorBlockCategories,
type: 'array'
},
{
name: 'deArrowInstance',
store: deArrowInstance,
type: 'string'
},
{
name: 'deArrowEnabled',
store: deArrowEnabled,
type: 'boolean'
},
{
name: 'deArrowThumbnailInstance',
store: deArrowThumbnailInstance,
type: 'string'
}
];
export function bookmarkletSaveToUrl(): string {
const url = new URL(import.meta.env.VITE_DEFAULT_FRONTEND_URL);
const url = new URL(import.meta.env.VITE_DEFAULT_FRONTEND_URL);
persistedStores.forEach(store => {
let value = get(store.store);
if (value !== null) {
url.searchParams.set(store.name, value.toString());
}
});
persistedStores.forEach((store) => {
let value = get(store.store);
if (value !== null) {
url.searchParams.set(store.name, value.toString());
}
});
return url.toString();
return url.toString();
}
export function bookmarkletLoadFromUrl() {
const currentPage = get(page);
const currentPage = get(page);
persistedStores.forEach(store => {
let paramValue = currentPage.url.searchParams.get(store.name);
if (paramValue) {
let value: any;
persistedStores.forEach((store) => {
let paramValue = currentPage.url.searchParams.get(store.name);
if (paramValue) {
let value: any;
if (store.type === 'array') {
value = paramValue.split(',');
} else if (store.type === 'boolean') {
value = paramValue === 'true';
} else {
value = paramValue;
}
if (store.type === 'array') {
value = paramValue.split(',');
} else if (store.type === 'boolean') {
value = paramValue === 'true';
} else {
value = paramValue;
}
store.store.set(value);
}
});
}
store.store.set(value);
}
});
}
+2 -2
View File
@@ -6,6 +6,6 @@ const defaultLocale = 'en';
register('en', () => import('./locales/en.json'));
init({
fallbackLocale: defaultLocale,
initialLocale: browser ? window.navigator.language : defaultLocale,
fallbackLocale: defaultLocale,
initialLocale: browser ? window.navigator.language : defaultLocale
});
+109 -109
View File
@@ -1,110 +1,110 @@
{
"enabled": "Enabled",
"copyUrl": "Copy URL",
"loadMore": "Load more",
"views": "views",
"videos": "videos",
"cancel": "Cancel",
"create": "Create",
"title": "Title",
"searchPlaceholder": "Search...",
"deleteAllHistory": "Delete all history",
"skipping": "Skipping",
"replies": "replies",
"videoTabs": {
"all": "All",
"videos": "Videos",
"playlists": "Playlists",
"channels": "Channels",
"shorts": "Shorts",
"streams": "Streams"
},
"subscriptions": {
"manageSubscriptions": "Manage subscriptions"
},
"playlist": {
"createPlaylist": "Create playlist",
"public": "Public",
"unlisted": "Unlisted",
"private": "Private"
},
"thumbnail": {
"live": "LIVE",
"failedToLoadImage": "Failed to load image"
},
"pages": {
"home": "Home",
"trending": "Trending",
"subscriptions": "Subscriptions",
"playlists": "Playlists",
"history": "History"
},
"subscribers": "subscribers",
"subscribe": "Subscribe",
"unsubscribe": "Unsubscribe",
"loginRequired": "Login required",
"player": {
"audioOnly": "Audio only",
"theatreMode": "Theatre mode",
"share": {
"title": "Share",
"materialiousLink": "Copy Materialious link",
"invidiousRedirect": "Copy Invidious redirect link",
"youtubeLink": "Copy Youtube link"
},
"download": "Download",
"addToPlaylist": "Add to playlist",
"noPlaylists": "No playlists",
"unableToLoadComments": "Unable to load comments"
},
"layout": {
"star": "Star us on Github!",
"syncParty": "Sync party",
"syncPartyWarning": "Please note your IP will be visible to users you invite.",
"startSyncParty": "Start sync party",
"endSyncParty": "End sync party",
"notifications": "Notifications",
"noNewNotifications": "No new notifications here",
"settings": "Settings",
"login": "Login",
"logout": "Logout",
"customize": "Customize",
"theme": {
"theme": "Theme",
"darkMode": "Dark mode",
"lightMode": "Light mode",
"color": "Color"
},
"searchSuggestions": "Search suggestions",
"dataPreferences": {
"dataPreferences": "Data preferences",
"content": "Looking to import/export subscriptions, change password or delete account? Click here and scroll to the bottom of the page."
},
"player": {
"title": "Player",
"autoPlay": "Autoplay video",
"alwaysLoopVideo": "Always loop video",
"proxyVideos": "Proxy videos",
"savePlaybackPosition": "Save playback position",
"listenByDefault": "Listen by default",
"theatreModeByDefault": "Theatre mode by default",
"autoPlayNextByDefault": "Autoplay next by default",
"dash": "Dash"
},
"instanceUrl": "Instance URL",
"sponsors": {
"sponsor": "Sponsor",
"unpaidSelfPromotion": "Unpaid/Self Promotion",
"interactionReminder": "Interaction Reminder (Subscribe)",
"intermissionIntroAnimation": "Intermission/Intro Animation",
"credits": "Endcards/Credits",
"preViewRecapHook": "Preview/Recap/Hook",
"tangentJokes": "Filler Tangent/Jokes"
},
"deArrow": {
"title": "DeArrow",
"thumbnailInstanceUrl": "Thumbnail instance URL"
},
"bookmarklet": "Bookmarklet"
}
}
"enabled": "Enabled",
"copyUrl": "Copy URL",
"loadMore": "Load more",
"views": "views",
"videos": "videos",
"cancel": "Cancel",
"create": "Create",
"title": "Title",
"searchPlaceholder": "Search...",
"deleteAllHistory": "Delete all history",
"skipping": "Skipping",
"replies": "replies",
"videoTabs": {
"all": "All",
"videos": "Videos",
"playlists": "Playlists",
"channels": "Channels",
"shorts": "Shorts",
"streams": "Streams"
},
"subscriptions": {
"manageSubscriptions": "Manage subscriptions"
},
"playlist": {
"createPlaylist": "Create playlist",
"public": "Public",
"unlisted": "Unlisted",
"private": "Private"
},
"thumbnail": {
"live": "LIVE",
"failedToLoadImage": "Failed to load image"
},
"pages": {
"home": "Home",
"trending": "Trending",
"subscriptions": "Subscriptions",
"playlists": "Playlists",
"history": "History"
},
"subscribers": "subscribers",
"subscribe": "Subscribe",
"unsubscribe": "Unsubscribe",
"loginRequired": "Login required",
"player": {
"audioOnly": "Audio only",
"theatreMode": "Theatre mode",
"share": {
"title": "Share",
"materialiousLink": "Copy Materialious link",
"invidiousRedirect": "Copy Invidious redirect link",
"youtubeLink": "Copy Youtube link"
},
"download": "Download",
"addToPlaylist": "Add to playlist",
"noPlaylists": "No playlists",
"unableToLoadComments": "Unable to load comments"
},
"layout": {
"star": "Star us on Github!",
"syncParty": "Sync party",
"syncPartyWarning": "Please note your IP will be visible to users you invite.",
"startSyncParty": "Start sync party",
"endSyncParty": "End sync party",
"notifications": "Notifications",
"noNewNotifications": "No new notifications here",
"settings": "Settings",
"login": "Login",
"logout": "Logout",
"customize": "Customize",
"theme": {
"theme": "Theme",
"darkMode": "Dark mode",
"lightMode": "Light mode",
"color": "Color"
},
"searchSuggestions": "Search suggestions",
"dataPreferences": {
"dataPreferences": "Data preferences",
"content": "Looking to import/export subscriptions, change password or delete account? Click here and scroll to the bottom of the page."
},
"player": {
"title": "Player",
"autoPlay": "Autoplay video",
"alwaysLoopVideo": "Always loop video",
"proxyVideos": "Proxy videos",
"savePlaybackPosition": "Save playback position",
"listenByDefault": "Listen by default",
"theatreModeByDefault": "Theatre mode by default",
"autoPlayNextByDefault": "Autoplay next by default",
"dash": "Dash"
},
"instanceUrl": "Instance URL",
"sponsors": {
"sponsor": "Sponsor",
"unpaidSelfPromotion": "Unpaid/Self Promotion",
"interactionReminder": "Interaction Reminder (Subscribe)",
"intermissionIntroAnimation": "Intermission/Intro Animation",
"credits": "Endcards/Credits",
"preViewRecapHook": "Preview/Recap/Hook",
"tangentJokes": "Filler Tangent/Jokes"
},
"deArrow": {
"title": "DeArrow",
"thumbnailInstanceUrl": "Thumbnail instance URL"
},
"bookmarklet": "Bookmarklet"
}
}
+75 -72
View File
@@ -1,104 +1,107 @@
import humanNumber from "human-number";
import humanNumber from 'human-number';
export function truncate(value: string, maxLength: number = 50): string {
return value.length > maxLength ? `${value.substring(0, maxLength)}...` : value;
return value.length > maxLength ? `${value.substring(0, maxLength)}...` : value;
}
export function numberWithCommas(number: number) {
if (typeof number === "undefined") return;
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
if (typeof number === 'undefined') return;
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
export function cleanNumber(number: number): string {
return humanNumber(number, (number: number) => Number.parseFloat(number.toString()).toFixed(1)).replace('.0', '');
return humanNumber(number, (number: number) =>
Number.parseFloat(number.toString()).toFixed(1)
).replace('.0', '');
}
export function videoLength(lengthSeconds: number): string {
const hours = Math.floor(lengthSeconds / 3600);
let minutes: number | string = Math.floor((lengthSeconds % 3600) / 60);
let seconds: number | string = lengthSeconds % 60;
const hours = Math.floor(lengthSeconds / 3600);
let minutes: number | string = Math.floor((lengthSeconds % 3600) / 60);
let seconds: number | string = lengthSeconds % 60;
if (minutes < 10) {
minutes = `0${minutes}`;
}
if (minutes < 10) {
minutes = `0${minutes}`;
}
if (seconds < 10) {
seconds = `0${seconds}`;
}
if (seconds < 10) {
seconds = `0${seconds}`;
}
if (hours !== 0) {
return `${hours}:${minutes}:${seconds}`;
} else {
return `${minutes}:${seconds}`;
}
if (hours !== 0) {
return `${hours}:${minutes}:${seconds}`;
} else {
return `${minutes}:${seconds}`;
}
}
export interface PhasedDescription {
description: string, timestamps: { title: string; time: number; timePretty: string; }[];
description: string;
timestamps: { title: string; time: number; timePretty: string }[];
}
export function phaseDescription(content: string): PhasedDescription {
const timestamps: { title: string; time: number; timePretty: string; }[] = [];
const lines = content.split('\n');
const timestamps: { title: string; time: number; timePretty: string }[] = [];
const lines = content.split('\n');
const urlRegex = /<a href="([^"]+)"/;
const timestampRegex = /<a href="([^"]+)" data-onclick="jump_to_time" data-jump-time="(\d+)">(\d+:\d+(?::\d+)?)<\/a>\s*(.+)/;
const urlRegex = /<a href="([^"]+)"/;
const timestampRegex =
/<a href="([^"]+)" data-onclick="jump_to_time" data-jump-time="(\d+)">(\d+:\d+(?::\d+)?)<\/a>\s*(.+)/;
let filteredLines: string[] = [];
lines.forEach(
(line) => {
const urlMatch = urlRegex.exec(line);
const timestampMatch = timestampRegex.exec(line);
let filteredLines: string[] = [];
lines.forEach((line) => {
const urlMatch = urlRegex.exec(line);
const timestampMatch = timestampRegex.exec(line);
if (urlMatch !== null && timestampMatch === null) {
// If line contains a URL but not a timestamp, modify the URL
const modifiedLine = line.replace(/<a href="([^"]+)"/, '<a href="$1" target="_blank" rel="noopener noreferrer" class="link"');
filteredLines.push(modifiedLine);
} else if (timestampMatch !== null) {
// If line contains a timestamp, extract details and push into timestamps array
const time = timestampMatch[2];
const timestamp = timestampMatch[3];
const title = timestampMatch[4] || '';
timestamps.push({
time: convertToSeconds(time),
title: title,
timePretty: timestamp
});
} else {
filteredLines.push(line);
}
}
);
if (urlMatch !== null && timestampMatch === null) {
// If line contains a URL but not a timestamp, modify the URL
const modifiedLine = line.replace(
/<a href="([^"]+)"/,
'<a href="$1" target="_blank" rel="noopener noreferrer" class="link"'
);
filteredLines.push(modifiedLine);
} else if (timestampMatch !== null) {
// If line contains a timestamp, extract details and push into timestamps array
const time = timestampMatch[2];
const timestamp = timestampMatch[3];
const title = timestampMatch[4] || '';
timestamps.push({
time: convertToSeconds(time),
title: title,
timePretty: timestamp
});
} else {
filteredLines.push(line);
}
});
const filteredContent = filteredLines.join('\n');
const filteredContent = filteredLines.join('\n');
return { description: filteredContent, timestamps: timestamps };
return { description: filteredContent, timestamps: timestamps };
}
function convertToSeconds(time: string): number {
const parts = time.split(':').map(part => parseInt(part));
let seconds = 0;
if (parts.length === 3) {
// hh:mm:ss
seconds = parts[0] * 3600 + parts[1] * 60 + parts[2];
} else if (parts.length === 2) {
// hh:ss or m:ss
seconds = parts[0] * 60 + parts[1];
} else if (parts.length === 1) {
// s
seconds = parts[0];
}
return seconds;
const parts = time.split(':').map((part) => parseInt(part));
let seconds = 0;
if (parts.length === 3) {
// hh:mm:ss
seconds = parts[0] * 3600 + parts[1] * 60 + parts[2];
} else if (parts.length === 2) {
// hh:ss or m:ss
seconds = parts[0] * 60 + parts[1];
} else if (parts.length === 1) {
// s
seconds = parts[0];
}
return seconds;
}
export function proxyVideoUrl(source: string): string {
const rawSrc = new URL(source);
rawSrc.host = import.meta.env.VITE_DEFAULT_INVIDIOUS_INSTANCE.replace(
'http://',
''
).replace('https://', '');
const rawSrc = new URL(source);
rawSrc.host = import.meta.env.VITE_DEFAULT_INVIDIOUS_INSTANCE.replace('http://', '').replace(
'https://',
''
);
return rawSrc.toString();
}
return rawSrc.toString();
}
+6 -6
View File
@@ -1,10 +1,10 @@
export interface PlayerEvent {
type: "pause" | "seek" | "change-video" | "play" | "playlist";
time?: number;
videoId?: string;
playlistId?: string;
type: 'pause' | 'seek' | 'change-video' | 'play' | 'playlist';
time?: number;
videoId?: string;
playlistId?: string;
}
export interface PlayerEvents {
events: PlayerEvent[];
}
events: PlayerEvent[];
}
+10 -10
View File
@@ -1,12 +1,12 @@
export async function getDynamicTheme(mode?: string): Promise<Record<string, string>> {
const givenSettings = await window.ui("theme") as IBeerCssTheme;
const givenSettings = (await window.ui('theme')) as IBeerCssTheme;
// @ts-ignore
const themes: string = givenSettings[mode ? mode : (ui("mode") as string)];
let themeVars: Record<string, string> = {};
themes.split(";").forEach(keyVar => {
let [key, value] = keyVar.split(":");
themeVars[key] = value;
});
return themeVars;
}
// @ts-ignore
const themes: string = givenSettings[mode ? mode : (ui('mode') as string)];
let themeVars: Record<string, string> = {};
themes.split(';').forEach((keyVar) => {
let [key, value] = keyVar.split(':');
themeVars[key] = value;
});
return themeVars;
}
+5 -5
View File
@@ -6,8 +6,8 @@ import type { LayoutLoad } from './$types';
export let ssr = false;
export const load: LayoutLoad = async () => {
if (browser) {
locale.set(window.navigator.language);
}
await waitLocale();
};
if (browser) {
locale.set(window.navigator.language);
}
await waitLocale();
};
+16 -16
View File
@@ -2,20 +2,20 @@ import { getPopular } from '$lib/Api/index.js';
import { error } from '@sveltejs/kit';
export async function load() {
let popular = undefined;
let popularDisabled: boolean = false;
let popular = undefined;
let popularDisabled: boolean = false;
try {
popular = await getPopular();
} catch (errorMessage: any) {
if (errorMessage.toString() === 'Error: Administrator has disabled this endpoint.') {
popularDisabled = true;
} else {
error(500, errorMessage);
}
}
return {
popular: popular,
popularDisabled: popularDisabled
};
}
try {
popular = await getPopular();
} catch (errorMessage: any) {
if (errorMessage.toString() === 'Error: Administrator has disabled this endpoint.') {
popularDisabled = true;
} else {
error(500, errorMessage);
}
}
return {
popular: popular,
popularDisabled: popularDisabled
};
}
+10 -10
View File
@@ -2,15 +2,15 @@ import { getChannel } from '$lib/Api/index.js';
import { error } from '@sveltejs/kit';
export async function load({ params }) {
let channel;
let channel;
try {
channel = await getChannel(params.slug);
} catch (errorMessage: any) {
error(500, errorMessage);
}
try {
channel = await getChannel(params.slug);
} catch (errorMessage: any) {
error(500, errorMessage);
}
return {
channel: channel
};
}
return {
channel: channel
};
}
+7 -7
View File
@@ -2,10 +2,10 @@ import { goto } from '$app/navigation';
import { error } from '@sveltejs/kit';
export async function load({ url }) {
const playlistId = url.searchParams.get('list');
if (playlistId) {
goto(`/playlist/${playlistId}`);
} else {
error(404);
}
}
const playlistId = url.searchParams.get('list');
if (playlistId) {
goto(`/playlist/${playlistId}`);
} else {
error(404);
}
}
@@ -2,14 +2,14 @@ import { getPlaylist } from '$lib/Api/index.js';
import { error } from '@sveltejs/kit';
export async function load({ params }) {
let playlist;
let playlist;
try {
playlist = await getPlaylist(params.slug);
} catch (errorMessage: any) {
error(500, errorMessage);
}
return {
playlist: playlist
};
}
try {
playlist = await getPlaylist(params.slug);
} catch (errorMessage: any) {
error(500, errorMessage);
}
return {
playlist: playlist
};
}
+12 -12
View File
@@ -1,14 +1,14 @@
import { getPersonalPlaylists } from "$lib/Api";
import { error } from "@sveltejs/kit";
import { getPersonalPlaylists } from '$lib/Api';
import { error } from '@sveltejs/kit';
export async function load() {
let playlists;
try {
playlists = await getPersonalPlaylists();
} catch (errorMessage: any) {
error(500, errorMessage);
}
return {
playlists: playlists
};
}
let playlists;
try {
playlists = await getPersonalPlaylists();
} catch (errorMessage: any) {
error(500, errorMessage);
}
return {
playlists: playlists
};
}
+18 -18
View File
@@ -2,26 +2,26 @@ import { getSearch } from '$lib/Api/index';
import { error } from '@sveltejs/kit';
export async function load({ params, url }) {
let type: "playlist" | "all" | "video" | "channel";
let type: 'playlist' | 'all' | 'video' | 'channel';
const queryFlag = url.searchParams.get('type');
if (queryFlag && ['playlist', 'video', 'channel', 'all'].includes(queryFlag)) {
type = queryFlag;
} else {
type = 'all';
}
const queryFlag = url.searchParams.get('type');
if (queryFlag && ['playlist', 'video', 'channel', 'all'].includes(queryFlag)) {
type = queryFlag;
} else {
type = 'all';
}
let search;
let search;
try {
search = await getSearch(params.slug, { type: type });
} catch (errorMessage: any) {
error(500, errorMessage);
}
try {
search = await getSearch(params.slug, { type: type });
} catch (errorMessage: any) {
error(500, errorMessage);
}
return {
search: search,
slug: params.slug,
searchType: type
};
return {
search: search,
slug: params.slug,
searchType: type
};
}
+10 -10
View File
@@ -2,14 +2,14 @@ import { getFeed } from '$lib/Api/index.js';
import { error } from '@sveltejs/kit';
export async function load({ params }) {
let feed;
let feed;
try {
feed = await getFeed(100, 1);
} catch (errorMessage: any) {
error(500, errorMessage);
}
return {
feed: feed
};
}
try {
feed = await getFeed(100, 1);
} catch (errorMessage: any) {
error(500, errorMessage);
}
return {
feed: feed
};
}
@@ -1,16 +1,16 @@
import { getSubscriptions } from "$lib/Api";
import { error } from "@sveltejs/kit";
import { getSubscriptions } from '$lib/Api';
import { error } from '@sveltejs/kit';
export async function load() {
let subscriptions;
let subscriptions;
try {
subscriptions = await getSubscriptions();
} catch (errorMessage: any) {
error(500, errorMessage);
}
try {
subscriptions = await getSubscriptions();
} catch (errorMessage: any) {
error(500, errorMessage);
}
return {
subscriptions: subscriptions
};
}
return {
subscriptions: subscriptions
};
}
+8 -8
View File
@@ -3,12 +3,12 @@ import type { Video } from '$lib/Api/model';
import { error } from '@sveltejs/kit';
export async function load() {
let trending: Video[];
try {
trending = await getTrending();
} catch (errorMessage: any) {
error(500, errorMessage);
}
let trending: Video[];
try {
trending = await getTrending();
} catch (errorMessage: any) {
error(500, errorMessage);
}
return { trending: trending };
}
return { trending: trending };
}
+12 -12
View File
@@ -2,16 +2,16 @@ import { goto } from '$app/navigation';
import { error } from '@sveltejs/kit';
export async function load({ url }) {
const videoId = url.searchParams.get('v');
const playlistId = url.searchParams.get('list');
if (videoId) {
let goToUrl = `/watch/${videoId}`;
const videoId = url.searchParams.get('v');
const playlistId = url.searchParams.get('list');
if (videoId) {
let goToUrl = `/watch/${videoId}`;
if (playlistId) {
goToUrl += `?playlist=${playlistId}`;
}
goto(goToUrl);
} else {
error(404);
}
}
if (playlistId) {
goToUrl += `?playlist=${playlistId}`;
}
goto(goToUrl);
} else {
error(404);
}
}
+64 -55
View File
@@ -1,4 +1,11 @@
import { amSubscribed, getComments, getDislikes, getPersonalPlaylists, getVideo, postHistory } from '$lib/Api/index.js';
import {
amSubscribed,
getComments,
getDislikes,
getPersonalPlaylists,
getVideo,
postHistory
} from '$lib/Api/index.js';
import type { PlaylistPage } from '$lib/Api/model';
import { phaseDescription } from '$lib/misc';
import { error } from '@sveltejs/kit';
@@ -6,66 +13,68 @@ import { get } from 'svelte/store';
import { auth, playerProxyVideos, returnYtDislikes } from '../../../store';
export async function load({ params, url }) {
let video;
try {
video = await getVideo(params.slug, get(playerProxyVideos));
} catch (errorMessage: any) {
error(500, errorMessage);
}
let video;
try {
video = await getVideo(params.slug, get(playerProxyVideos));
} catch (errorMessage: any) {
error(500, errorMessage);
}
let downloadOptions: { title: string; url: string; }[] = [];
let downloadOptions: { title: string; url: string }[] = [];
if (!video.hlsUrl) {
video.formatStreams.forEach(format => {
downloadOptions.push({
title: `${format.type.split(';')[0].trim()} - ${format.qualityLabel} (With audio)`,
url: format.url
});
});
if (!video.hlsUrl) {
video.formatStreams.forEach((format) => {
downloadOptions.push({
title: `${format.type.split(';')[0].trim()} - ${format.qualityLabel} (With audio)`,
url: format.url
});
});
video.adaptiveFormats.forEach(format => {
downloadOptions.push({
title: `${format.type.split(';')[0].trim()} - ${format.qualityLabel || format.bitrate + ' bitrate'}`,
url: format.url
});
});
}
video.adaptiveFormats.forEach((format) => {
downloadOptions.push({
title: `${format.type.split(';')[0].trim()} - ${format.qualityLabel || format.bitrate + ' bitrate'}`,
url: format.url
});
});
}
let personalPlaylists: PlaylistPage[] | null;
let personalPlaylists: PlaylistPage[] | null;
if (get(auth)) {
postHistory(video.videoId);
personalPlaylists = await getPersonalPlaylists();
} else {
personalPlaylists = null;
}
if (get(auth)) {
postHistory(video.videoId);
personalPlaylists = await getPersonalPlaylists();
} else {
personalPlaylists = null;
}
let comments;
try {
comments = video.liveNow ? null : await getComments(params.slug, { sort_by: "top", source: "youtube" });
} catch {
comments = null;
}
let comments;
try {
comments = video.liveNow
? null
: await getComments(params.slug, { sort_by: 'top', source: 'youtube' });
} catch {
comments = null;
}
if (comments && 'errorBacktrace' in comments) {
comments = null;
}
if (comments && 'errorBacktrace' in comments) {
comments = null;
}
let returnYTDislikes;
try {
returnYTDislikes = get(returnYtDislikes) ? await getDislikes(params.slug) : null;
} catch {
returnYTDislikes = null;
}
let returnYTDislikes;
try {
returnYTDislikes = get(returnYtDislikes) ? await getDislikes(params.slug) : null;
} catch {
returnYTDislikes = null;
}
return {
video: video,
returnYTDislikes: returnYTDislikes,
comments: comments,
subscribed: await amSubscribed(video.authorId),
content: phaseDescription(video.descriptionHtml),
playlistId: url.searchParams.get('playlist'),
personalPlaylists: personalPlaylists,
downloadOptions: downloadOptions
};
};
return {
video: video,
returnYTDislikes: returnYTDislikes,
comments: comments,
subscribed: await amSubscribed(video.authorId),
content: phaseDescription(video.descriptionHtml),
playlistId: url.searchParams.get('playlist'),
personalPlaylists: personalPlaylists,
downloadOptions: downloadOptions
};
}
+37 -22
View File
@@ -3,34 +3,49 @@ import type { DataConnection } from 'peerjs';
import { persisted } from 'svelte-persisted-store';
import { writable, type Writable } from 'svelte/store';
export const returnYTDislikesInstance = persisted('returnYTDislikesInstance', import.meta.env.VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE);
export const darkMode: Writable<null | boolean> = persisted("darkMode", null);
export const themeColor: Writable<null | string> = persisted("themeColor", null);
export const returnYTDislikesInstance = persisted(
'returnYTDislikesInstance',
import.meta.env.VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE
);
export const darkMode: Writable<null | boolean> = persisted('darkMode', null);
export const themeColor: Writable<null | string> = persisted('themeColor', null);
export const activePage: Writable<string | null> = writable("home");
export const activePage: Writable<string | null> = writable('home');
export const playerAutoPlay = persisted("autoPlay", true);
export const playerAlwaysLoop = persisted("alwaysLoop", false);
export const playerProxyVideos = persisted("proxyVideos", false);
export const playerListenByDefault = persisted("listenByDefault", false);
export const playerSavePlaybackPosition = persisted("savePlaybackPosition", true);
export const playerDash = persisted("dashEnabled", false);
export const playerTheatreModeByDefault = persisted("theatreModeByDefault", false);
export const playerAutoplayNextByDefault = persisted("autoplayNextByDefault", false);
export const playerAutoPlay = persisted('autoPlay', true);
export const playerAlwaysLoop = persisted('alwaysLoop', false);
export const playerProxyVideos = persisted('proxyVideos', false);
export const playerListenByDefault = persisted('listenByDefault', false);
export const playerSavePlaybackPosition = persisted('savePlaybackPosition', true);
export const playerDash = persisted('dashEnabled', false);
export const playerTheatreModeByDefault = persisted('theatreModeByDefault', false);
export const playerAutoplayNextByDefault = persisted('autoplayNextByDefault', false);
export const returnYtDislikes = persisted("returnYtDislikes", true);
export const returnYtDislikes = persisted('returnYtDislikes', true);
export const interfaceSearchSuggestions = persisted("searchSuggestions", true);
export const interfaceSearchSuggestions = persisted('searchSuggestions', true);
export const auth: Writable<null | { username: string, token: string; }> = persisted("authToken", null);
export const auth: Writable<null | { username: string; token: string }> = persisted(
'authToken',
null
);
export const sponsorBlock = persisted("sponsorBlock", true);
export const sponsorBlockUrl = persisted("sponsorBlockUrl", import.meta.env.VITE_DEFAULT_SPONSERBLOCK_INSTANCE);
export const sponsorBlockCategories: Writable<string[]> = persisted("sponsorBlockCategories", []);
export const sponsorBlock = persisted('sponsorBlock', true);
export const sponsorBlockUrl = persisted(
'sponsorBlockUrl',
import.meta.env.VITE_DEFAULT_SPONSERBLOCK_INSTANCE
);
export const sponsorBlockCategories: Writable<string[]> = persisted('sponsorBlockCategories', []);
export const deArrowInstance = persisted("deArrowInstance", import.meta.env.VITE_DEFAULT_DEARROW_INSTANCE);
export const deArrowEnabled = persisted("deArrowEnabled", false);
export const deArrowThumbnailInstance = persisted("deArrowThumbnailInstance", import.meta.env.VITE_DEFAULT_DEARROW_THUMBNAIL_INSTANCE);
export const deArrowInstance = persisted(
'deArrowInstance',
import.meta.env.VITE_DEFAULT_DEARROW_INSTANCE
);
export const deArrowEnabled = persisted('deArrowEnabled', false);
export const deArrowThumbnailInstance = persisted(
'deArrowThumbnailInstance',
import.meta.env.VITE_DEFAULT_DEARROW_THUMBNAIL_INSTANCE
);
export const syncPartyPeer: Writable<Peer | null> = writable(null);
export const syncPartyConnections: Writable<DataConnection[] | null> = writable();
export const syncPartyConnections: Writable<DataConnection[] | null> = writable();
+13 -13
View File
@@ -1,29 +1,29 @@
.thumbnail-corner {
padding: .1em .4em;
padding: 0.1em 0.4em;
}
.vds-chapter-radio-label {
word-wrap: break-word !important;
overflow-wrap: break-word !important;
white-space: normal !important;
word-wrap: break-word !important;
overflow-wrap: break-word !important;
white-space: normal !important;
}
.vds-chapter-radio-content {
max-width: 200px !important;
max-width: 200px !important;
}
.vds-chapter-radio {
align-items: start !important;
align-items: start !important;
}
@media screen and (max-width: 580px) {
main {
padding-left: .1em !important;
padding-right: .1em !important;
}
main {
padding-left: 0.1em !important;
padding-right: 0.1em !important;
}
}
main.root {
max-height: 100vh;
overflow-y: scroll;
}
max-height: 100vh;
overflow-y: scroll;
}
+2 -4
View File
@@ -10,12 +10,10 @@
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler",
"types": [
"vidstack/svelte"
]
"types": ["vidstack/svelte"]
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}
}
+228 -228
View File
@@ -8,462 +8,462 @@ export default defineConfig({
SvelteKitPWA({
injectRegister: 'inline',
manifest: {
description: "Modern material design for Invidious.",
description: 'Modern material design for Invidious.',
icons: [
{
"src": "windows11/SmallTile.scale-100.png",
"sizes": "71x71"
src: 'windows11/SmallTile.scale-100.png',
sizes: '71x71'
},
{
"src": "windows11/SmallTile.scale-125.png",
"sizes": "89x89"
src: 'windows11/SmallTile.scale-125.png',
sizes: '89x89'
},
{
"src": "windows11/SmallTile.scale-150.png",
"sizes": "107x107"
src: 'windows11/SmallTile.scale-150.png',
sizes: '107x107'
},
{
"src": "windows11/SmallTile.scale-200.png",
"sizes": "142x142"
src: 'windows11/SmallTile.scale-200.png',
sizes: '142x142'
},
{
"src": "windows11/SmallTile.scale-400.png",
"sizes": "284x284"
src: 'windows11/SmallTile.scale-400.png',
sizes: '284x284'
},
{
"src": "windows11/Square150x150Logo.scale-100.png",
"sizes": "150x150"
src: 'windows11/Square150x150Logo.scale-100.png',
sizes: '150x150'
},
{
"src": "windows11/Square150x150Logo.scale-125.png",
"sizes": "188x188"
src: 'windows11/Square150x150Logo.scale-125.png',
sizes: '188x188'
},
{
"src": "windows11/Square150x150Logo.scale-150.png",
"sizes": "225x225"
src: 'windows11/Square150x150Logo.scale-150.png',
sizes: '225x225'
},
{
"src": "windows11/Square150x150Logo.scale-200.png",
"sizes": "300x300"
src: 'windows11/Square150x150Logo.scale-200.png',
sizes: '300x300'
},
{
"src": "windows11/Square150x150Logo.scale-400.png",
"sizes": "600x600"
src: 'windows11/Square150x150Logo.scale-400.png',
sizes: '600x600'
},
{
"src": "windows11/Wide310x150Logo.scale-100.png",
"sizes": "310x150"
src: 'windows11/Wide310x150Logo.scale-100.png',
sizes: '310x150'
},
{
"src": "windows11/Wide310x150Logo.scale-125.png",
"sizes": "388x188"
src: 'windows11/Wide310x150Logo.scale-125.png',
sizes: '388x188'
},
{
"src": "windows11/Wide310x150Logo.scale-150.png",
"sizes": "465x225"
src: 'windows11/Wide310x150Logo.scale-150.png',
sizes: '465x225'
},
{
"src": "windows11/Wide310x150Logo.scale-200.png",
"sizes": "620x300"
src: 'windows11/Wide310x150Logo.scale-200.png',
sizes: '620x300'
},
{
"src": "windows11/Wide310x150Logo.scale-400.png",
"sizes": "1240x600"
src: 'windows11/Wide310x150Logo.scale-400.png',
sizes: '1240x600'
},
{
"src": "windows11/LargeTile.scale-100.png",
"sizes": "310x310"
src: 'windows11/LargeTile.scale-100.png',
sizes: '310x310'
},
{
"src": "windows11/LargeTile.scale-125.png",
"sizes": "388x388"
src: 'windows11/LargeTile.scale-125.png',
sizes: '388x388'
},
{
"src": "windows11/LargeTile.scale-150.png",
"sizes": "465x465"
src: 'windows11/LargeTile.scale-150.png',
sizes: '465x465'
},
{
"src": "windows11/LargeTile.scale-200.png",
"sizes": "620x620"
src: 'windows11/LargeTile.scale-200.png',
sizes: '620x620'
},
{
"src": "windows11/LargeTile.scale-400.png",
"sizes": "1240x1240"
src: 'windows11/LargeTile.scale-400.png',
sizes: '1240x1240'
},
{
"src": "windows11/Square44x44Logo.scale-100.png",
"sizes": "44x44"
src: 'windows11/Square44x44Logo.scale-100.png',
sizes: '44x44'
},
{
"src": "windows11/Square44x44Logo.scale-125.png",
"sizes": "55x55"
src: 'windows11/Square44x44Logo.scale-125.png',
sizes: '55x55'
},
{
"src": "windows11/Square44x44Logo.scale-150.png",
"sizes": "66x66"
src: 'windows11/Square44x44Logo.scale-150.png',
sizes: '66x66'
},
{
"src": "windows11/Square44x44Logo.scale-200.png",
"sizes": "88x88"
src: 'windows11/Square44x44Logo.scale-200.png',
sizes: '88x88'
},
{
"src": "windows11/Square44x44Logo.scale-400.png",
"sizes": "176x176"
src: 'windows11/Square44x44Logo.scale-400.png',
sizes: '176x176'
},
{
"src": "windows11/StoreLogo.scale-100.png",
"sizes": "50x50"
src: 'windows11/StoreLogo.scale-100.png',
sizes: '50x50'
},
{
"src": "windows11/StoreLogo.scale-125.png",
"sizes": "63x63"
src: 'windows11/StoreLogo.scale-125.png',
sizes: '63x63'
},
{
"src": "windows11/StoreLogo.scale-150.png",
"sizes": "75x75"
src: 'windows11/StoreLogo.scale-150.png',
sizes: '75x75'
},
{
"src": "windows11/StoreLogo.scale-200.png",
"sizes": "100x100"
src: 'windows11/StoreLogo.scale-200.png',
sizes: '100x100'
},
{
"src": "windows11/StoreLogo.scale-400.png",
"sizes": "200x200"
src: 'windows11/StoreLogo.scale-400.png',
sizes: '200x200'
},
{
"src": "windows11/SplashScreen.scale-100.png",
"sizes": "620x300"
src: 'windows11/SplashScreen.scale-100.png',
sizes: '620x300'
},
{
"src": "windows11/SplashScreen.scale-125.png",
"sizes": "775x375"
src: 'windows11/SplashScreen.scale-125.png',
sizes: '775x375'
},
{
"src": "windows11/SplashScreen.scale-150.png",
"sizes": "930x450"
src: 'windows11/SplashScreen.scale-150.png',
sizes: '930x450'
},
{
"src": "windows11/SplashScreen.scale-200.png",
"sizes": "1240x600"
src: 'windows11/SplashScreen.scale-200.png',
sizes: '1240x600'
},
{
"src": "windows11/SplashScreen.scale-400.png",
"sizes": "2480x1200"
src: 'windows11/SplashScreen.scale-400.png',
sizes: '2480x1200'
},
{
"src": "windows11/Square44x44Logo.targetsize-16.png",
"sizes": "16x16"
src: 'windows11/Square44x44Logo.targetsize-16.png',
sizes: '16x16'
},
{
"src": "windows11/Square44x44Logo.targetsize-20.png",
"sizes": "20x20"
src: 'windows11/Square44x44Logo.targetsize-20.png',
sizes: '20x20'
},
{
"src": "windows11/Square44x44Logo.targetsize-24.png",
"sizes": "24x24"
src: 'windows11/Square44x44Logo.targetsize-24.png',
sizes: '24x24'
},
{
"src": "windows11/Square44x44Logo.targetsize-30.png",
"sizes": "30x30"
src: 'windows11/Square44x44Logo.targetsize-30.png',
sizes: '30x30'
},
{
"src": "windows11/Square44x44Logo.targetsize-32.png",
"sizes": "32x32"
src: 'windows11/Square44x44Logo.targetsize-32.png',
sizes: '32x32'
},
{
"src": "windows11/Square44x44Logo.targetsize-36.png",
"sizes": "36x36"
src: 'windows11/Square44x44Logo.targetsize-36.png',
sizes: '36x36'
},
{
"src": "windows11/Square44x44Logo.targetsize-40.png",
"sizes": "40x40"
src: 'windows11/Square44x44Logo.targetsize-40.png',
sizes: '40x40'
},
{
"src": "windows11/Square44x44Logo.targetsize-44.png",
"sizes": "44x44"
src: 'windows11/Square44x44Logo.targetsize-44.png',
sizes: '44x44'
},
{
"src": "windows11/Square44x44Logo.targetsize-48.png",
"sizes": "48x48"
src: 'windows11/Square44x44Logo.targetsize-48.png',
sizes: '48x48'
},
{
"src": "windows11/Square44x44Logo.targetsize-60.png",
"sizes": "60x60"
src: 'windows11/Square44x44Logo.targetsize-60.png',
sizes: '60x60'
},
{
"src": "windows11/Square44x44Logo.targetsize-64.png",
"sizes": "64x64"
src: 'windows11/Square44x44Logo.targetsize-64.png',
sizes: '64x64'
},
{
"src": "windows11/Square44x44Logo.targetsize-72.png",
"sizes": "72x72"
src: 'windows11/Square44x44Logo.targetsize-72.png',
sizes: '72x72'
},
{
"src": "windows11/Square44x44Logo.targetsize-80.png",
"sizes": "80x80"
src: 'windows11/Square44x44Logo.targetsize-80.png',
sizes: '80x80'
},
{
"src": "windows11/Square44x44Logo.targetsize-96.png",
"sizes": "96x96"
src: 'windows11/Square44x44Logo.targetsize-96.png',
sizes: '96x96'
},
{
"src": "windows11/Square44x44Logo.targetsize-256.png",
"sizes": "256x256"
src: 'windows11/Square44x44Logo.targetsize-256.png',
sizes: '256x256'
},
{
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-16.png",
"sizes": "16x16"
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-16.png',
sizes: '16x16'
},
{
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-20.png",
"sizes": "20x20"
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-20.png',
sizes: '20x20'
},
{
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-24.png",
"sizes": "24x24"
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-24.png',
sizes: '24x24'
},
{
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-30.png",
"sizes": "30x30"
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-30.png',
sizes: '30x30'
},
{
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-32.png",
"sizes": "32x32"
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-32.png',
sizes: '32x32'
},
{
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-36.png",
"sizes": "36x36"
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-36.png',
sizes: '36x36'
},
{
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-40.png",
"sizes": "40x40"
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-40.png',
sizes: '40x40'
},
{
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-44.png",
"sizes": "44x44"
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-44.png',
sizes: '44x44'
},
{
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-48.png",
"sizes": "48x48"
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-48.png',
sizes: '48x48'
},
{
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-60.png",
"sizes": "60x60"
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-60.png',
sizes: '60x60'
},
{
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-64.png",
"sizes": "64x64"
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-64.png',
sizes: '64x64'
},
{
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-72.png",
"sizes": "72x72"
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-72.png',
sizes: '72x72'
},
{
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-80.png",
"sizes": "80x80"
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-80.png',
sizes: '80x80'
},
{
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-96.png",
"sizes": "96x96"
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-96.png',
sizes: '96x96'
},
{
"src": "windows11/Square44x44Logo.altform-unplated_targetsize-256.png",
"sizes": "256x256"
src: 'windows11/Square44x44Logo.altform-unplated_targetsize-256.png',
sizes: '256x256'
},
{
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-16.png",
"sizes": "16x16"
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-16.png',
sizes: '16x16'
},
{
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-20.png",
"sizes": "20x20"
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-20.png',
sizes: '20x20'
},
{
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-24.png",
"sizes": "24x24"
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-24.png',
sizes: '24x24'
},
{
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-30.png",
"sizes": "30x30"
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-30.png',
sizes: '30x30'
},
{
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-32.png",
"sizes": "32x32"
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-32.png',
sizes: '32x32'
},
{
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-36.png",
"sizes": "36x36"
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-36.png',
sizes: '36x36'
},
{
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-40.png",
"sizes": "40x40"
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-40.png',
sizes: '40x40'
},
{
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-44.png",
"sizes": "44x44"
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-44.png',
sizes: '44x44'
},
{
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-48.png",
"sizes": "48x48"
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-48.png',
sizes: '48x48'
},
{
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-60.png",
"sizes": "60x60"
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-60.png',
sizes: '60x60'
},
{
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-64.png",
"sizes": "64x64"
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-64.png',
sizes: '64x64'
},
{
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-72.png",
"sizes": "72x72"
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-72.png',
sizes: '72x72'
},
{
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-80.png",
"sizes": "80x80"
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-80.png',
sizes: '80x80'
},
{
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-96.png",
"sizes": "96x96"
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-96.png',
sizes: '96x96'
},
{
"src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-256.png",
"sizes": "256x256"
src: 'windows11/Square44x44Logo.altform-lightunplated_targetsize-256.png',
sizes: '256x256'
},
{
"src": "android/android-launchericon-512-512.png",
"sizes": "512x512"
src: 'android/android-launchericon-512-512.png',
sizes: '512x512'
},
{
"src": "android/android-launchericon-192-192.png",
"sizes": "192x192"
src: 'android/android-launchericon-192-192.png',
sizes: '192x192'
},
{
"src": "android/android-launchericon-144-144.png",
"sizes": "144x144"
src: 'android/android-launchericon-144-144.png',
sizes: '144x144'
},
{
"src": "android/android-launchericon-96-96.png",
"sizes": "96x96"
src: 'android/android-launchericon-96-96.png',
sizes: '96x96'
},
{
"src": "android/android-launchericon-72-72.png",
"sizes": "72x72"
src: 'android/android-launchericon-72-72.png',
sizes: '72x72'
},
{
"src": "android/android-launchericon-48-48.png",
"sizes": "48x48"
src: 'android/android-launchericon-48-48.png',
sizes: '48x48'
},
{
"src": "ios/16.png",
"sizes": "16x16"
src: 'ios/16.png',
sizes: '16x16'
},
{
"src": "ios/20.png",
"sizes": "20x20"
src: 'ios/20.png',
sizes: '20x20'
},
{
"src": "ios/29.png",
"sizes": "29x29"
src: 'ios/29.png',
sizes: '29x29'
},
{
"src": "ios/32.png",
"sizes": "32x32"
src: 'ios/32.png',
sizes: '32x32'
},
{
"src": "ios/40.png",
"sizes": "40x40"
src: 'ios/40.png',
sizes: '40x40'
},
{
"src": "ios/50.png",
"sizes": "50x50"
src: 'ios/50.png',
sizes: '50x50'
},
{
"src": "ios/57.png",
"sizes": "57x57"
src: 'ios/57.png',
sizes: '57x57'
},
{
"src": "ios/58.png",
"sizes": "58x58"
src: 'ios/58.png',
sizes: '58x58'
},
{
"src": "ios/60.png",
"sizes": "60x60"
src: 'ios/60.png',
sizes: '60x60'
},
{
"src": "ios/64.png",
"sizes": "64x64"
src: 'ios/64.png',
sizes: '64x64'
},
{
"src": "ios/72.png",
"sizes": "72x72"
src: 'ios/72.png',
sizes: '72x72'
},
{
"src": "ios/76.png",
"sizes": "76x76"
src: 'ios/76.png',
sizes: '76x76'
},
{
"src": "ios/80.png",
"sizes": "80x80"
src: 'ios/80.png',
sizes: '80x80'
},
{
"src": "ios/87.png",
"sizes": "87x87"
src: 'ios/87.png',
sizes: '87x87'
},
{
"src": "ios/100.png",
"sizes": "100x100"
src: 'ios/100.png',
sizes: '100x100'
},
{
"src": "ios/114.png",
"sizes": "114x114"
src: 'ios/114.png',
sizes: '114x114'
},
{
"src": "ios/120.png",
"sizes": "120x120"
src: 'ios/120.png',
sizes: '120x120'
},
{
"src": "ios/128.png",
"sizes": "128x128"
src: 'ios/128.png',
sizes: '128x128'
},
{
"src": "ios/144.png",
"sizes": "144x144"
src: 'ios/144.png',
sizes: '144x144'
},
{
"src": "ios/152.png",
"sizes": "152x152"
src: 'ios/152.png',
sizes: '152x152'
},
{
"src": "ios/167.png",
"sizes": "167x167"
src: 'ios/167.png',
sizes: '167x167'
},
{
"src": "ios/180.png",
"sizes": "180x180"
src: 'ios/180.png',
sizes: '180x180'
},
{
"src": "ios/192.png",
"sizes": "192x192"
src: 'ios/192.png',
sizes: '192x192'
},
{
"src": "ios/256.png",
"sizes": "256x256"
src: 'ios/256.png',
sizes: '256x256'
},
{
"src": "ios/512.png",
"sizes": "512x512"
src: 'ios/512.png',
sizes: '512x512'
},
{
"src": "ios/1024.png",
"sizes": "1024x1024"
src: 'ios/1024.png',
sizes: '1024x1024'
}
],
background_color: "#22005d",
theme_color: "#efb0ff"
background_color: '#22005d',
theme_color: '#efb0ff'
}
}),
vidstack(),
sveltekit()
],
]
});