Compare commits

...

No commits in common. "master" and "vanity" have entirely different histories.

658 changed files with 381 additions and 385932 deletions

14
.gitignore vendored
View file

@ -1,6 +1,6 @@
# generated types
source/.astro
source/node_modules
web/.astro
web/node_modules
.minpluto/generated
# dependencies
@ -13,16 +13,16 @@ yarn-error.log*
pnpm-debug.log*
# environment variables
source/.env
source/.env.production
web/.env
web/.env.production
# macOS-specific files
.DS_Store
# i18n
/source/src/pages/en/
/source/src/pages/jp/
/source/src/pages/ru/
/web/src/pages/en/
/web/src/pages/jp/
/web/src/pages/ru/
# other
.minpluto/docker/supabase/.env

View file

@ -1,107 +0,0 @@
###########
# Docker Volumes
# All folders provided in `./supabase-volume/` must be included
###########
SUPABASE_ENTIRE_VOLUME="./supabase-volume/"
############
# Secrets
# YOU MUST CHANGE THESE BEFORE GOING INTO PRODUCTION
############
POSTGRES_PASSWORD=5lgHamV44d8D1GN9LRS6b44VxREi4692
JWT_SECRET=paxDX2xE00qFa4I1r6PKe15nIkB089I4
ANON_KEY=9X43H6LKq3115zmZhZj95f2IJ104a603
SERVICE_ROLE_KEY=K2G792rYBZR0kZvw9Zp6182zAwzxsdas
DASHBOARD_USERNAME=mp_admin
DASHBOARD_PASSWORD=ez116oqVWd4wHZUQNbgW3fA0m958FN09
############
# Database - You can change these to any PostgreSQL database that has logical replication enabled.
############
# default user is postgres
POSTGRES_HOST=db
POSTGRES_DB=postgres
POSTGRES_PORT=1945
############
# API Proxy - Configuration for the Kong Reverse proxy.
############
KONG_HTTP_PORT=1942
KONG_HTTPS_PORT=1943
############
# API - Configuration for PostgREST.
############
PGRST_DB_SCHEMAS=public,storage,graphql_public
############
# Auth - Configuration for the GoTrue authentication server.
############
## General
SITE_URL=http://localhost:1930
ADDITIONAL_REDIRECT_URLS=
JWT_EXPIRY=3600
DISABLE_SIGNUP=false
API_EXTERNAL_URL=https://db.minpluto.org
## Mailer Config
MAILER_URLPATHS_CONFIRMATION="/auth/v1/verify"
MAILER_URLPATHS_INVITE="/auth/v1/verify"
MAILER_URLPATHS_RECOVERY="/auth/v1/verify"
MAILER_URLPATHS_EMAIL_CHANGE="/auth/v1/verify"
## Email auth
ENABLE_EMAIL_SIGNUP=true
ENABLE_EMAIL_AUTOCONFIRM=false
SMTP_ADMIN_EMAIL=no-reply@sudovanilla.org
SMTP_HOST=smtp.resend.com
SMTP_PORT=587
SMTP_USER=resend
SMTP_PASS=re_XLbiDxHd_9Yucx4y9EwiacKgHrRowfJVU
SMTP_SENDER_NAME=MinPluto
ENABLE_ANONYMOUS_USERS=true
## Phone auth
ENABLE_PHONE_SIGNUP=false
ENABLE_PHONE_AUTOCONFIRM=false
############
# Studio - Configuration for the Dashboard
############
STUDIO_DEFAULT_ORGANIZATION=Default Organization
STUDIO_DEFAULT_PROJECT=Default Project
STUDIO_PORT=1944
SUPABASE_PUBLIC_URL=http://localhost:8000
# Enable webp support
IMGPROXY_ENABLE_WEBP_DETECTION=true
############
# Functions - Configuration for Functions
############
# NOTE: VERIFY_JWT applies to all functions. Per-function VERIFY_JWT is not supported yet.
FUNCTIONS_VERIFY_JWT=false
############
# Logs - Configuration for Logflare
# Please refer to https://supabase.com/docs/reference/self-hosting-analytics/introduction
############
LOGFLARE_LOGGER_BACKEND_API_KEY=your-super-secret-and-long-logflare-key
# Change vector.toml sinks to reflect this change
LOGFLARE_API_KEY=your-super-secret-and-long-logflare-key
# Docker socket location - this value will differ depending on your OS
DOCKER_SOCKET_LOCATION=/var/run/docker.sock
# Google Cloud Project details
GOOGLE_PROJECT_ID=GOOGLE_PROJECT_ID
GOOGLE_PROJECT_NUMBER=GOOGLE_PROJECT_NUMBER

View file

@ -50,6 +50,7 @@ Cloudflare Pages | 🔘 | 🔘 |
| Mullvad | ✅ | ❌ | ✅ | ✅ | 🔘 | 🔘 |
| Tor | 🔘 | 🔘 | 🔘 | 🔘 | 🔘 | 🔘 |
| Waterfox | ✅ | ✅ | ✅ | ✅ | 🔘 | 🔘 |
| Zen | ✅ | ✅ | ✅ | ✅ | 🔘 | 🔘 |
| **Outdated Browsers**|
| Internet Explorer | ❌ | ✅ | ❌ | ✅ | 🔘 | 🔘 |

View file

@ -1,41 +1,35 @@
## To Do
- [ ] i18n
- [x] API
- [ ] API
- [ ] Languages
- [x] English
- [ ] English
- [ ] Japanese
- [ ] French
- [ ] Spanish
- [ ] Russian
- [x] Data
- [x] Track Events (Users should be opted-out by default, OpenPanel will be used)
- [x] Make privacy policy adaptive
- [x] Mobile Support
- [ ] Server Configuration (.env)
- [ ] Quality
- [ ] Allow 1080p
- [ ] Allow 4K
- [ ] Allow 8K
- [ ] Account System (Based on [Account System Demo](https://ark.sudovanilla.org/MinPluto/Account-System-Demo))
- [x] Use Supabase Library
- [ ] Data
- [ ] Track Events
- [ ] Make privacy policy adaptive
- [ ] Account System
- [ ] Use Supabase Library
- [ ] Create Pages:
- [ ] Subscription Feed
- [ ] History (Maybe, maybe not)
- [x] Login
- [x] Register
- [x] Account
- [ ] History
- [ ] Login
- [ ] Register
- [ ] Account
- [ ] Preferences
- [ ] Delete
- [ ] Anomymous Account Creation
- [x] Email Confirmation Code
- [ ] Email Confirmation Code
- [ ] Ability to:
- [ ] Update Data
- [x] Username
- [ ] Username
- [ ] Email
- [ ] Pasword
- [ ] Delete Account
- [ ] API
- [x] `/api/update/name`
- [ ] `/api/update/name`
- [ ] `/api/update/email`
- [ ] `/api/update/password`
- [ ] `/api/update/preference/ui/theme`
@ -48,17 +42,17 @@
- [ ] `/api/update/preference/instance/invidious/data`
- [ ] `/api/update/preference/instance/safetwitch/media`
- [ ] `/api/update/preference/instance/safetwitch/data`
- [x] `/api/auth/login`
- [x] `/api/auth/register`
- [ ] `/api/auth/login`
- [ ] `/api/auth/register`
- [ ] `/api/auth/delete`
- [x] `/api/auth/confirm`
- [x] `/api/auth/logout`
- [ ] `/api/auth/confirm`
- [ ] `/api/auth/logout`
- [ ] `/api/anon/create`
- [ ] `/api/anon/delete`
- [ ] `/api/anon/signout`
- [ ] `/api/subscription/add`
- [ ] `/api/subscription/remove`
- [ ] Revamp Design and Layout ([UI Library Repo](https://ark.sudovanilla.org/MinPluto/UI-Library/))
- [ ] Revamp Design and Layout
- [ ] Use Header over Sidebar
- [ ] Generic
- [ ] Dropdown
@ -68,7 +62,7 @@
- [ ] Radio Buttons
- [ ] Toast
- [ ] Tooltip
- [ ] Hovercard (For Creators) [Example](https://www.radix-vue.com/components/hover-card)
- [ ] Hovercard (For Creators)
- [ ] Scrollable Areas
- [ ] KBD
- [ ] Empty State
@ -91,24 +85,24 @@
- [ ] Discovery Pages
- [ ] Animation
- [ ] Automotive
- [x] Comedy
- [ ] Comedy
- [ ] Courses
- [ ] Educational
- [ ] Family Friendly
- [ ] Fashion
- [ ] Fitness
- [ ] Food
- [x] Games
- [ ] Games
- [ ] Music
- [ ] News
- [ ] Podcasts
- [ ] Science
- [ ] Sports
- [x] Tech
- [ ] Tech
- [ ] Web Series
- [ ] Twitch Support
- [x] API
- [x] Video Player HLS Support (Required to play streams)
- [ ] API
- [ ] Video Player HLS Support
- [ ] Polycentric Chat
- [ ] Categories
- [ ] Games
@ -128,12 +122,12 @@
- [ ] Search
- [ ] Revamp Experience
- [ ] Filters
- [x] Auto Complete
- [ ] Auto Complete
- [ ] Video Player
- [x] Dash Format (1080p/4K/8K)
- [ ] Dash Format
- [ ] 360° Support
- [ ] Mobile Gestures
- [x] Embed Page
- [ ] Embed Page
- [ ] Download
- [ ] Share
- [ ] Report
@ -146,8 +140,6 @@
- [ ] Theater Mode
- [ ] Cast
- [ ] Video Page
- [ ] ~~Important Infomation Card ([Example](https://img.sudovanilla.org/pXqzT10.png))~~ Controversial, do not proceed
- [ ] Viewers Note (Like Community Notes, in [experimental phase at YouTube](https://blog.youtube/news-and-events/new-ways-to-offer-viewers-more-context/))
- [ ] Toggle:
- [ ] Audio Only
- [ ] Autoplay
@ -168,4 +160,14 @@
- [ ] Import/Export MinPluto User Settings
- [ ] Feed Page
- [ ] Universal Feed (YouTube and Twitch)
- [ ] Subscription Management
- [ ] Subscription Management
- [ ] Frontend Support (Replace official links to frontend alternatives)
- [ ] YouTube (Just use current instance)
- [ ] Twitch (Just use current instance)
- [ ] X
- [ ] Reddit
- [ ] Medium
- [ ] Quora
- [ ] StackOverflow
- [ ] Wikipedia
- [ ] Imgur

View file

@ -6,14 +6,14 @@ MinPluto is a modern privacy frontend for YouTube and Twitch giving your persona
___
## Docs
- [FAQ](/.minpluto/docs/FAQ.md)
- [API](/.minpluto/docs/API.md)
- [Requirements](/.minpluto/docs/Requirements.md)
- [Compatibility](/.minpluto/docs/Compatibility.md)
- [FAQ](./.minpluto/docs/FAQ.md)
- [API](./.minpluto/docs/API.md)
- [Requirements](./.minpluto/docs/Requirements.md)
- [Compatibility](./.minpluto/docs/Compatibility.md)
- Develop, Build, Run
- Selfhosting
- Player
___
MinPluto is inspired by [Poke](https://poketube.fun/), Poke is a project by [Ashley](https://codeberg.org/ashley).
MinPluto is inspired by [Poke](https://poketube.fun/), a project by [Ashley](https://codeberg.org/ashley).

BIN
bun.lockb

Binary file not shown.

3
bunfig.toml Normal file
View file

@ -0,0 +1,3 @@
# Flurry and Zorn are on SudoVanilla Packages
[install]
registry = "https://npm.sudovanilla.org"

View file

@ -1,12 +1,13 @@
{
"name": "minpluto",
"version": "24.10.30",
"version": "24.09.07",
"description": "An open source frontend alternative to YouTube and Twitch.",
"repository": "https://ark.sudovanilla.org/MinPluto/MinPluto",
"author": "Korbs",
"author": "Korbs <korbs@sudovanilla.org>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://ark.sudovanilla.org/MinPluto/MinPluto/issues"
"url": "https://ark.sudovanilla.org/MinPluto/MinPluto/issues",
"email": "support@minpluto.org"
},
"funding": [
{
@ -17,38 +18,32 @@
"keywords": [
"privacy",
"frontend",
"proxy",
"downloader",
"ytdl",
"invidious",
"safetwitch",
"youtube",
"twitch",
"live",
"stream"
"astro",
"proxy",
"anonymous"
],
"scripts": {
"start": "astro dev --config ./source/astro.mjs --host",
"translate": "astro-i18next --config ./source/astro-i18next.config.mjs generate",
"build": "astro build --config ./source/astro.js"
"start": "astro dev --config ./web/astro.mjs --host",
"translate": "astro-i18next --config ./web/astro-i18next.config.mjs generate",
"build": "astro build --config ./web/astro.mjs"
},
"dependencies": {
"@astrojs/mdx": "^3.1.8",
"@astrojs/node": "^8.3.4",
"@astrojs/vue": "^4.5.2",
"@iconoir/vue": "^7.9.0",
"@astrojs/mdx": "^3.1.5",
"@astrojs/node": "^8.3.3",
"@astrojs/vue": "^4.5.0",
"@iconoir/vue": "^7.8.0",
"@minpluto/polestar": "^0.0.54-1",
"@minpluto/zorn": "^0.4.51",
"@nurodev/astro-bun": "^1.1.5",
"@openpanel/sdk": "^1.0.0",
"@supabase/supabase-js": "^2.45.6",
"@xexiu/astro-modal": "^0.5.9",
"astro": "^4.16.7",
"astro-analytics": "^2.7.0",
"astro-i18next": "^1.0.0-beta.21",
"@supabase/supabase-js": "^2.45.3",
"astro": "^4.15.4",
"astro-tooltips": "^0.6.2",
"astro-useragent": "^4.0.2",
"rss-to-json": "^2.1.1",
"undici": "^6.20.1"
},
"devDependencies": {
"sass": "^1.80.4"
"sass": "^1.78.0"
}
}
}

View file

@ -1,4 +0,0 @@
/** @type {import('astro-i18next').AstroI18nextConfig} */
export default {
locales: ["en"]
}

View file

@ -1,49 +0,0 @@
import { defineConfig } from 'astro/config'
import vue from '@astrojs/vue'
import astroI18next from "astro-i18next"
import mdx from '@astrojs/mdx'
import bun from "@nurodev/astro-bun";
// https://astro.build/config
export default defineConfig({
// Project Structure
cacheDir: './.minpluto/generated/astro/cache/',
outDir: './.minpluto/generated/astro/dist/',
publicDir: './source/src/public',
root: './source',
srcDir: './source/src',
// Integrations and Plugins
integrations: [mdx(), vue(), astroI18next()],
// Security
security: {
checkOrigin: true
},
// Server Options
server: {
port: 1930,
host: true
},
// Use Server-Side Rendering
output: 'server',
adapter: bun(),
// Vite
vite: {
server: {
hmr: true // Auto Reload
}
},
// Experimental
experimental: {
directRenderScript: true,
clientPrerender: true,
serverIslands: true
},
prefetch: {
prefetchAll: true,
defaultStrategy: "viewport"
},
// Others
devToolbar: {
enabled: false
}
})

6
source/env.d.ts vendored
View file

@ -1,6 +0,0 @@
/// <reference types="astro/client" />
declare namespace App {
interface Locals {
email: string
}
}

View file

@ -1,17 +0,0 @@
---
const {
Text,
Color
} = Astro.props
---
<p style={'background: ' + Color + ';'} class="chip">{Text}</p>
<style lang="scss">
.chip {
font-size: 12px;
border-radius: 3rem;
padding: 4px 12px;
width: max-content;
}
</style>

View file

@ -1,44 +0,0 @@
---
// Properties
const {
ChannelId
} = Astro.props
// Configuration
import { DEFAULT_MEDIA_DATA_PROXY, DEFAULT_IMAGE_PROXY} from '@utils/GetConfig'
// Fetch
const channel = await fetch(DEFAULT_MEDIA_DATA_PROXY + "/api/v1/channels/" + ChannelId).then((response) => response.json());
---
<a class="sidebar-channel" href={'/channel/' + channel.authorId}>
<img src={DEFAULT_IMAGE_PROXY + '/' + channel.authorThumbnails[5].url}/>
<p>{channel.author}</p>
</a>
<style lang="scss">
.sidebar-channel {
color: white;
text-decoration: none;
margin-bottom: 6px;
border-radius: 6px;
padding: 4px 24px;
background: transparent;
border: 2px transparent solid;
display: flex;
align-items: center;
gap: 14px;
font-size: 14px;
&:hover {
background: rgba(255, 255, 255, 0.05);
border: 2px rgba(255, 255, 255, 0.05) solid;
}
img {
width: 32px;
height: 32px;
object-fit: cover;
border-radius: 3rem;
font-weight: bold;
}
}
</style>

View file

@ -1,83 +0,0 @@
---
import { Xmark } from "@iconoir/vue"
const {
Title,
Description,
Closable,
CloseOnclick
} = Astro.props
---
<div class="dialog-backdrop"></div>
<div id={'dialog-' + Title} class="dialog">
<div class="dialog-header">
<div class="dialog-header-title">
<h2>{Title}</h2>
<p>{Description}</p>
</div>
<div class="dialog-header-action">
{Closable ? <button onclick={CloseOnclick}><Xmark/></button> : null}
</div>
</div>
<div class="dialog-content">
<slot/>
</div>
</div>
<style lang="scss">
.dialog-backdrop {
display: none;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
z-index: 6;
backdrop-filter: blur(10px) brightness(0.5) contrast(0.9);
-webkit-backdrop-filter: blur(10px) brightness(0.5) contrast(0.9);
}
.dialog {
display: none;
position: fixed;
z-index: 7;
width: 100%;
top: 25%;
left: 50%;
transform: translate(-50%, -50%);
background: rgb(24 24 24);
border: 2px rgba(255,255,255,0.05) solid;
margin: auto;
max-width: 600px;
border-radius: 10px;
flex-direction: column;
padding-bottom: 24px;
.dialog-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 24px;
.dialog-header-title {
h2 {
font-size: 24px;
margin: 0px;
}
p {
font-size: 14px;
margin: 0px;
color: gray;
}
}
.dialog-header-action button {
background: transparent;
color: white;
border: none;
aspect-ratio: 1;
cursor: pointer;
}
}
.dialog-content {
padding: 0px 24px;
}
}
</style>

View file

@ -1,50 +0,0 @@
---
// Properties
const {
ChannelId
} = Astro.props
// Configuration
import { DEFAULT_MEDIA_DATA_PROXY, DEFAULT_IMAGE_PROXY} from '@utils/GetConfig'
// Fetch
const channel = await fetch(DEFAULT_MEDIA_DATA_PROXY + "/api/v1/channels/" + ChannelId).then((response) => response.json());
---
<a href={'/channel/' + channel.authorId} style={"background: url('" + DEFAULT_IMAGE_PROXY + '/' + channel.authorThumbnails[5].url} class="discovery-channel">
<div class="dc-c">
<img src={DEFAULT_IMAGE_PROXY + '/' + channel.authorThumbnails[5].url}/>
<p>{channel.author}</p>
</div>
</a>
<style lang="scss">
.discovery-channel {
text-decoration: none;
border-radius: 10px;
font-weight: bold;
background-position: center !important;
background-size: cover !important;
.dc-c {
border-radius: 8px;
display: flex;
flex-direction: row;
align-items: center;
text-align: center;
backdrop-filter: blur(24px) contrast(0.7) brightness(0.4);
-webkit-backdrop-filter: blur(24px) contrast(0.7) brightness(0.4);
img {
width: 24%;
align-self: center;
border-radius: 10rem;
padding: 30px;
}
p {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
width: 160px;
}
}
}
</style>

View file

@ -1,23 +0,0 @@
---
const { Name, OnClick } = Astro.props
---
<div class="tr-dropdown">
<a style="cursor: pointer;" onclick={OnClick} id={Name + '-dropdown-button'}>{Name}</a>
<div style="display: none;" id={Name + '-dropdown-menu'} class="comp-dropdown">
<slot />
</div>
</div>
<style lang="scss">
.comp-dropdown {
position: absolute;
background: #0d0d0d;
border-radius: 6px;
border: 2px #464646 solid;
width: max-content;
a {
padding: 12px 48px 12px 24px;
}
}
</style>

View file

@ -1,67 +0,0 @@
---
// Properties
const {
ID,
Title,
Creator,
Views,
UploadDate,
Length
} = Astro.props
// Configuration
import {
DEFAULT_MEDIA_DATA_PROXY,
DEFAULT_IMAGE_PROXY
} from '@utils/GetConfig'
// i18n
import i18next, { t } from "i18next";
// Format Published Date
const DateFormat = new Date(UploadDate * 1000).toLocaleDateString()
// Format Video Length
// Thanks to "mingjunlu" for helping out with the time format
const LengthFormat = new Date(Length * 1000).toISOString().slice(14, 19).split(':').map(Number).join(':')
// Format Views
const ViewsConversion = Intl.NumberFormat('en', { notation: 'compact'})
const ViewsFormat = ViewsConversion.format(Views)
---
<a href={'/watch?v=' + ID} class="music-item">
<img src={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + ID + '/maxresdefault.jpg'}/>
<p id="title">{Title}</p>
<p id="artist">{Creator}</p>
<p id="date">{DateFormat}</p>
<p id="duration">{LengthFormat}</p>
</a>
<style lang="scss">
.music-item {
display: grid;
grid-auto-flow: column;
align-items: center;
text-decoration: none;
gap: 12px;
grid-template-columns: 60px auto auto 80px 64px;
#duration, #date {
color: gray;
}
#artist, #duration, #date {
text-align: right;
}
#title {
font-weight: bold;
}
img {
width: 60px;
height: 60px;
object-fit: cover;
object-position: center;
border-radius: 10px;
}
}
</style>

View file

@ -1,94 +0,0 @@
---
import { Search } from "@iconoir/vue";
import { t } from "i18next";
---
<div class="search-bar">
<form onsubmit="return Search()">
<input
type="search"
placeholder={t("HEADER.SEARCH")}
aria-label="Search for a video or stream"
/>
</form>
<button onclick="Search()"><Search /></button>
<div style="opacity: 0;" class="suggestions"></div>
</div>
<!-- Search Scripts -->
<script is:inline>
/*
@licstart The following is the entire license notice for the
JavaScript code in this page.
Copyright (C) 2024 SudoVanilla
The JavaScript code in this page is free software: you can
redistribute it and/or modify it under the terms of the GNU
General Public License (GNU GPL) as published by the Free Software
Foundation, either version 3 of the License, or (at your option)
any later version. The code is distributed WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
As additional permission under GNU GPL version 3 section 7, you
may distribute non-source (e.g., minimized or compacted) forms of
that code without the copy of the GNU GPL normally required by
section 4, provided you include this license notice and a URL
through which recipients can access the Corresponding Source.
@licend The above is the entire license notice
for the JavaScript code in this page.
*/
// Focus input
function FocusSearch() {
document.querySelector('input[type="search"]').focus();
}
// Trigger Search
function Search() {
var SearchQuery = document.querySelector('input[type="search"]').value;
location.href = `/search?query=${SearchQuery}`;
}
///// Suggestions /////
// Dismiss when the end-user clicks else where
document.body.addEventListener("click", function (evt) {
document.querySelector(".suggestions").style.opacity = "0";
});
// When the end-user starts typing, trigget the fetch function
document
.querySelector('input[type="search"]')
.addEventListener("input", function (e) {
if (e.target.value !== "") {
document.querySelector(".suggestions").style.opacity = "1";
GetResults();
} else {
null;
}
});
// Fetch
function GetResults() {
var SearchValue = document.querySelector('input[type="search"]').value;
var YouTubeSuggestions = document.querySelector(".suggestions");
fetch(
`https://yt.sudovanilla.org/api/v1/search/suggestions?q=${SearchValue}`,
)
.then((response) => response.json())
.then((data) => {
YouTubeSuggestions.innerHTML = ListOfSuggestionsYT(data);
});
}
// Create List
function ListOfSuggestionsYT(data) {
const text = data.suggestions
.map((data) => `<a href="/search?query=${data}">${data}</a>`)
.join("\n");
return `${text}`;
}
</script>

View file

@ -1,84 +0,0 @@
---
// Properties
const {
ID,
Title,
Creator,
Views,
UploadDate,
Length
} = Astro.props
// Configuration
import {
DEFAULT_MEDIA_DATA_PROXY,
DEFAULT_IMAGE_PROXY
} from '@utils/GetConfig'
// i18n
import i18next, { t } from "i18next";
// Format Published Date
const DateFormat = new Date(UploadDate * 1000).toLocaleDateString()
// Format Video Length
// Thanks to "mingjunlu" for helping out with the time format
const LengthFormat = null
// Format Views
const ViewsConversion = Intl.NumberFormat('en', { notation: 'compact'})
const ViewsFormat = ViewsConversion.format(Views)
---
<a href={'/watch?v=' + ID} class="video-item">
<div class="video-item-thumbnail">
<img onload="this.style.opacity = '1'" src={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + ID + '/hqdefault.jpg'} loading="lazy"/>
<p id="vi-length">{LengthFormat}</p>
</div>
<div class="video-item-details">
<p id="vi-title">{Title}</p>
<p id="vi-author">{t("WATCH.BY")} {Creator}</p>
<p id="vi-viewCount">{ViewsFormat} {t("WATCH.VIEWS")} - {DateFormat}</p>
</div>
</a>
<style lang="scss">
.video-item {
display: flex;
flex-direction: column;
color: white;
text-decoration: none;
.video-item-thumbnail {
img {
width: 100%;
border-radius: 6px;
aspect-ratio: 16/9;
object-fit: cover;
opacity: 0;
transition: 0.3s opacity;
}
p#vi-length {
margin: -41px 0px 0px 0px;
background: rgba(0,0,0,0.75);
padding: 12px 6px 6px 16px;
width: max-content;
border-radius: 50px 0px 6px 0px;
position: relative;
float: right;
}
}
.video-item-details {
display: flex;
flex-direction: column;
p {
margin: 0px;
&#vi-title {
font-weight: bold;
}
&#vi-author {
color: darkgrey;
}
}
}
}
</style>

View file

@ -1,17 +0,0 @@
---
import i18next,{ t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
if (Astro.cookies.get("Telemtry").value === "Enabled") {
var UserIsOptedIn = true
}
else if (Astro.cookies.get("Telemtry").value === "Disabled") {
var UserIsOptedIn = false
}
---
{UserIsOptedIn ?
<a style="color: white;text-decoration: none;margin-bottom: 6px;border-radius: 6px;padding: 8px 24px;background: #1a1a1a;font-size: 14px;border: 2px rgba(255, 255, 255, 0.25) solid;" href="/api/telemtry/disable">{t("TELEMTRY.OPTOUT")}</a>
:
<a style="color: white;text-decoration: none;margin-bottom: 6px;border-radius: 6px;padding: 8px 24px;background: #1a1a1a;font-size: 14px;border: 2px rgba(255, 255, 255, 0.25) solid;" href="/api/telemtry/enable">{t("TELEMTRY.OPTIN")}</a>
}

View file

@ -1,80 +0,0 @@
---
const {
Link,
Name,
Platform,
Thumbnail,
Ratio = "16:9" // "16:9" or "9:16"
} = Astro.props
---
<a href={Link} style={"background-image: url('" + Thumbnail + "')"} class="goin-card">
{
()=> {
if (Platform === "YouTube") {
return <span><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M549.7 124.1c-6.3-23.7-24.8-42.3-48.3-48.6C458.8 64 288 64 288 64S117.2 64 74.6 75.5c-23.5 6.3-42 24.9-48.3 48.6-11.4 42.9-11.4 132.3-11.4 132.3s0 89.4 11.4 132.3c6.3 23.7 24.8 41.5 48.3 47.8C117.2 448 288 448 288 448s170.8 0 213.4-11.5c23.5-6.3 42-24.2 48.3-47.8 11.4-42.9 11.4-132.3 11.4-132.3s0-89.4-11.4-132.3zm-317.5 213.5V175.2l142.7 81.2-142.7 81.2z"/></svg></span>
} else if (Platform === "Twitch") {
return <span><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M391.2 103.5H352.5v109.7h38.6zM285 103H246.4V212.8H285zM120.8 0 24.3 91.4V420.6H140.1V512l96.5-91.4h77.3L487.7 256V0zM449.1 237.8l-77.2 73.1H294.6l-67.6 64v-64H140.1V36.6H449.1z"/></svg></span>
}
}
}
<div class="goin-card-content">
<p>{Name}</p>
</div>
</a>
<style lang="scss">
.goin-card {
text-decoration: none;
border-radius: 10px;
background-size: cover !important;
background-position: center !important;
position: relative;
span {
svg {
fill: white;
width: 30px;
position: absolute;
z-index: 1;
margin: 18px;
}
}
.goin-card-content {
display: flex;
align-items: center;
gap: 12px;
height: 100%;
place-content: center;
border-radius: 10px;
backdrop-filter: blur(3px) brightness(0.3) contrast(0.8);
-webkit-backdrop-filter: blur(3px) brightness(0.3) contrast(0.8);
p {
font-size: 18px;
font-weight: bold;
position: absolute;
left: 0px;
bottom: 0px;
margin: 12px;
}
}
}
</style>
{
()=> {
if (Ratio === "16:9") {
return <style is:inline>
.goin-card {
height: 170px !important
}
</style>
} else if (Ratio === "9:16") {
return <style is:inline>
.goin-card {
height: 328px !important
}
</style>
}
}
}

View file

@ -1,28 +0,0 @@
---
// Properties
const {
FetchData,
CategoryName,
CategoryDescription,
GradientHero
} = Astro.props
// Configuration
import {
DEFAULT_MEDIA_DATA_PROXY,
DEFAULT_IMAGE_PROXY
} from '@utils/GetConfig'
// Fetch
const fetchFrom = DEFAULT_MEDIA_DATA_PROXY + '/api/v1/trending' + FetchData
const response = await fetch(fetchFrom)
const data = await response.json()
---
<div class="Td-row">
{data.map((data) =>
<img src={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + data.videoId + '/maxresdefault.jpg'}/>
)}
</div>

View file

@ -1,66 +0,0 @@
---
// Environment Variables
import {
ANALYTICS,
MATOMO_ID,
MATOMO_SRC,
PLAUSIBLE_DOMAIN,
PLAUSIBLE_SRC,
UMAMI_SRC,
AMPLITUDE_APIKEY,
METRICAL_APP,
FATHOM_SITE,
FATHOM_SRC,
MINIAML_ID,
SWETRIX_SRC,
SWETRIX_API,
SWETRIX_PROJECT_ID,
SIMPLEANALYTICS_DOMAIN
} from '@utils/GetConfig'
// Get Astro Analytics
import {
Fathom,
Metrical,
Plausible,
Umami,
Amplitude,
Matomo,
MinimalAnalytics
} from 'astro-analytics'
---
<!-- https://gist.sudovanilla.org/Korbs/fac0f5b99a6e43679c1d38d614721b5e -->
{
()=> {
if (ANALYTICS === "None") {
return null
} else if (ANALYTICS === "Plausible") {
<Plausible domain={PLAUSIBLE_DOMAIN} src={PLAUSIBLE_SRC + "/yoursript.js"} />
} else if (ANALYTICS === "Umami") {
<Umami id="4fb7fa4c-5b46-438d-94b3-3a8fb9bc2e8b" src={UMAMI_SRC + "/umami.js"} />
} else if (ANALYTICS === "Amplitude") {
<Amplitude apiKey={AMPLITUDE_APIKEY} />
} else if (ANALYTICS === "Matomo") {
<Matomo id={MATOMO_ID} src={MATOMO_SRC} />
} else if (ANALYTICS === "Metrical") {
<Metrical app={METRICAL_APP} />
} else if (ANALYTICS === "Fathom") {
<Fathom site={FATHOM_SITE} src={FATHOM_SRC} />
} else if (ANALYTICS === "MinimalAnalytics") {
<MinimalAnalytics id={MINIAML_ID} />
} else if (ANALYTICS === "Swetrix") {
<script is:inline src={SWETRIX_SRC} defer></script>
<script is:inline>
document.addEventListener('DOMContentLoaded', function () {
swetrix.init({SWETRIX_PROJECT_ID})
swetrix.trackViews()
})
</script>
<noscript><img src={SWETRIX_API + '/log/noscript?pid=' + SWETRIX_PROJECT_ID} alt="" referrerpolicy="no-referrer-when-downgrade" /></noscript>
} else if (ANALYTICS === "Simple Analytics") {
<script is:inline async defer data-hostname={SIMPLEANALYTICS_DOMAIN} src="https://scripts.simpleanalyticscdn.com/latest.js"></script>
<noscript><img src={'https://queue.simpleanalyticscdn.com/noscript.gif?hostname=' + SIMPLEANALYTICS_DOMAIN} alt="" referrerpolicy="no-referrer-when-downgrade" /></noscript>
}
}
}

View file

@ -1,19 +0,0 @@
---
// Configuration
import {
DEFAULT_PLAYER
} from '@utils/GetConfig'
---
{
() => {
if (DEFAULT_PLAYER === "Browser") {
return (null)
}
if (DEFAULT_PLAYER === "Zorn") {
return (
<script is:inline src="/scripts/zorn.js"></script>
)
}
}
}

View file

@ -1,103 +0,0 @@
---
// i18n
import i18next, { t } from "i18next";
// Cookies
/// Set Default
if (Astro.cookies.get("Language") === undefined) {
Astro.cookies.set("Language", "EN", {path: "/",sameSite: 'strict'})
} else {
var UserLanguage = Astro.cookies.get("Language").value
if (UserLanguage === "JP") {i18next.changeLanguage("jp")}
else if (UserLanguage === "EN") {i18next.changeLanguage("en")}
}
if (Astro.cookies.get("Milieu") === undefined) {
Astro.cookies.set("Milieu", "Enabled", {path: "/",sameSite: 'strict'})
} else {
var UserLanguage = Astro.cookies.get("Language").value
if (UserLanguage === "JP") {i18next.changeLanguage("jp")}
else if (UserLanguage === "EN") {i18next.changeLanguage("en")}
}
/// Telemtry
//// Users should be opted-out by default
if (Astro.cookies.get("Telemtry") === undefined) {
Astro.cookies.set("Telemtry", "Disabled", {path: "/",sameSite: 'strict'})
}
// Properties
const {
Title,
Description,
EmbedId,
EmbedVideo,
EmbedImage,
EmbedTitle,
} = Astro.props
// Components
import Analytics from "@components/global/Analytics.astro";
import { Tooltips } from 'astro-tooltips';
// Configuration
import {SERVER_DOMAIN} from '@utils/GetConfig'
// Embed
const SWV = Astro.url.href.split("embed/").pop();
if (Astro.url.href.match('watch')) {
var IsVideo = true
} else if (Astro.url.href.match('embed')) {
var IsVideo = false
} else {
var IsVideo = false
}
// Track Events
// TODO
// Before re-enabling, create an "opt-in" solution
// first for the end-user, as asking for consent
// would be ideal. Astro Cookies can be used to
// toggle the option, make sure end-users are
// opted-out by default for privacy purposes.
// import { OpenpanelSdk } from '@openpanel/sdk';
// const op = new OpenpanelSdk({
// clientId: 'b4c27f56-18f5-4d66-bb62-cbf7f7161812',
// clientSecret: 'sec_107558407af59a591b50',
// });
---
<head>
<!-- Metadata -->
<title>{Title}</title>
<meta name="description" content={Description}>
<!-- Properties -->
<meta charset="UTF-8">
<meta name="theme-color" content="#111">
<meta name="viewport" content="width=device-1200px, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, shrink-to-fit=yes, viewport-fit=cover, width=device-width, height=device-height, target-densitydpi=device-dpi">
<meta http-equiv="content-language" content="en-us">
<link rel="manifest" href="/manifest.json" />
<script is:inline src="/service-worker.js"></script>
<Tooltips interactive={false} delay={[15, 12]} />
<!-- Favicon -->
<link rel="apple-touch-icon" type="image/png" sizes="256x256" href="/images/logo/Favicon.png">
<link rel="icon" type="image/png" sizes="256x256" href="/images/logo/Favicon.png">
<!-- Video Embed -->
{IsVideo ?
<meta property="og:type" content="video.other">
<meta property="og:video:url" content={SERVER_DOMAIN + '/embed/' + EmbedId}>
<meta property="og:video:type" content="text/html">
<meta property="twitter:player" content={SERVER_DOMAIN + '/embed/' + EmbedId}>
<meta property="og:video" content={EmbedVideo} />
<meta property="og:title" content={EmbedTitle}>
<meta property="og:image" content={EmbedImage}/>
<meta property="og:site_name" content={'MinPluto - Privacy YouTube Frontend'}>
:
null
}
</head>
<Analytics/>

View file

@ -1,13 +0,0 @@
---
import { HomeSimple, InputSearch, MediaVideoList, MoreHoriz, ProfileCircle, Safari, Settings } from "@iconoir/vue";
---
<div class="mobile-navigation">
<a href="/"><HomeSimple/> <p>Home</p></a>
<a href="#"><InputSearch/> <p>Search</p></a>
<a href="#"><Safari/> <p>Explore</p></a>
<a href="#"><MediaVideoList/> <p>Subscription</p></a>
<a href="#"><Settings/> <p>Settings</p></a>
<a href="#"><MoreHoriz/> <p>More</p></a>
</div>

View file

@ -1,253 +0,0 @@
---
// Configuration
import {
SIDEBAR_DISCOVER,
SIDEBAR_CATEGORIES
} from '@utils/GetConfig'
import {
version
} from '@root/package.json'
// Components
import CreatorInSidebar from '@components/CreatorInSidebar.astro'
import { ViewTransitions } from 'astro:transitions';
import { slide } from "astro/virtual-modules/transitions.js";
// Icons
import {
GraphUp,
Movie,
MusicDoubleNote,
Gamepad,
AppleImac2021Side,
EmojiTalkingHappy,
PeaceHand,
PlanetAlt,
InputSearch,
Settings,
LogIn,
LogOut
} from '@iconoir/vue'
// i18n
import i18next, { t } from "i18next"
// Supabase Data
import { supabase } from "@library/supabase"
const { data: { user } } = await supabase.auth.getUser()
const id = user?.id
// Is the user logged in?
if (Astro.cookies.get('sb-access-token') === undefined) {
var Guest = true
} else {
var Guest = false
}
// Get Channels
const { data: channels, error } = await supabase
.from('channels')
.select('*')
let { data: subs } = await supabase
.from('subs')
.select("*")
.eq('UserSubscribed', id)
---
<ViewTransitions transition:animate={slide} />
<div class="sidebar">
<div class="sidebar-top">
<div class="sidebar-top-start">
<a href='/'><img src="/images/logo/MinPluto - Logo.png"/></a>
<div>
<!-- {Guest ?
<a title="Login" href="/login"><LogIn/></a>
:
<a title="Log Out" href="/api/auth/logout"><LogOut/></a>
} -->
<a title="Settings" href="#"><Settings/></a>
</div>
</div>
<div class="sidebar-top-end">
<a href={'/'}><PlanetAlt/> {t("SIDEBAR.HOME")}</a>
<a href="#"><InputSearch/> {t("HEADER.SEARCH")}</a>
{SIDEBAR_CATEGORIES ?
<h2>{t("SIDEBAR.TRENDING")}</h2>
<a href={'/discover?category=trending&?platform=youtube'}><GraphUp/> {t("SIDEBAR.CATEGORY_LIST.POPULAR")}</a>
<a href={'/discover?category=movies&?platform=youtube'}><Movie/> {t("SIDEBAR.CATEGORY_LIST.TRAILERS")}</a>
<a href={'/discover?category=music&?platform=youtube'}><MusicDoubleNote/> {t("SIDEBAR.CATEGORY_LIST.MUSIC")}</a>
<a href={'/discover?category=gaming&?platform=youtube'}><Gamepad/> {t("SIDEBAR.CATEGORY_LIST.GAMES")}</a>
:
null
}
{SIDEBAR_DISCOVER ?
<h2>{t("SIDEBAR.DISCOVER")}</h2>
<a href={'/discover/tech'}><AppleImac2021Side/> {t("SIDEBAR.DISCOVER_LIST.TECH")}</a>
<a href="/discover/comedy"><EmojiTalkingHappy/> {t("SIDEBAR.DISCOVER_LIST.COMEDY")}</a>
<a href="/discover/gaming"><Gamepad/> {t("SIDEBAR.DISCOVER_LIST.GAMES")}</a>
:
null
}
{Guest ?
null
:
<h2>Following</h2>
<span>
{subs.map((channel) =>
<CreatorInSidebar ChannelId={channel.Id}/>
)}
</span>
}
</div>
</div>
<div class="sidebar-bottom">
<div class="sidebar-bottom-start">
<a style="pointer-events: none;"><PeaceHand/> {t("SIDEBAR.FOOTER.ALPHA")}</a>
</div>
<div class="sidebar-bottom-end">
<p id="version">v{version}</p>
<a href="https://status.minpluto.org/" target="_blank">{t("SIDEBAR.FOOTER.STATUS")}</a>
</div>
</div>
</div>
<style lang="scss">
.sidebar {
position: fixed;
top: 0px;
left: 0px;
width: 224px;
background: linear-gradient(315deg, #0e0e0e, #161616);
color: white;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 0px 12px;
hr {
width: 32%;
border: none;
background: rgba(255, 255, 255, 0.1);
height: 4px;
border-radius: 1rem;
}
h2 {
font-size: 16px;
padding-left: 24px;
}
form {
margin-bottom: 12px;
input {
background: transparent;
border: 2px transparent solid;
padding: 10px 24px;
border-radius: 6px;
color: white;
transition: 0.3s background, 0.3s border;
&:hover {
background: rgb(255 255 255 / 05%);
border: 2px rgba(255, 255, 255, 0.05) solid;
transition: 0.3s background, 0.3s border;
}
&:focus {
outline: none;
background: rgb(255 255 255 / 07%);
border: 2px rgba(255, 255, 255, 0.07) solid;
transition: 0.3s background, 0.3s border;
}
svg {
width: 18px;
}
}
}
.sidebar-top {
.sidebar-top-start {
display: flex;
align-items: center;
gap: 24px;
padding: 32px 24px;
justify-content: space-between;
div {
display: flex;
gap: 6px;
a {
background: transparent;
border-radius: 6px;
padding: 5px 6px 3px 6px;
aspect-ratio: 1;
svg {
aspect-ratio: 1;
}
&:hover {
background: #2d2d2d;
}
}
}
img {
height: 24px;
}
}
.sidebar-top-end {
display: flex;
flex-direction: column;
cursor: default;
}
}
.sidebar-bottom {
display: flex;
flex-direction: column;
justify-content: space-between;
.sidebar-bottom-end {
text-align: center;
padding-bottom: 12px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0px 18px;
}
a {
margin: 0px 4px;
color: white;
text-decoration: none;
font-size: 14px;
&:hover {
text-decoration: underline;
}
}
p#version {
background: rgb(48, 48, 48);
border-radius: 3rem;
padding: 6px 12px;
}
}
.sidebar-top-end a, .sidebar-bottom-start a {
color: white;
text-decoration: none;
margin-bottom: 6px;
border-radius: 6px;
padding: 8px 24px;
background: transparent;
border: 2px transparent solid;
display: flex;
align-items: center;
gap: 14px;
font-size: 14px;
&:hover {
background: rgb(255 255 255 / 05%);
border: 2px rgba(255, 255, 255, 0.05) solid;
}
svg {
width: 18px;
}
&#sidebar-disable {
filter: brightness(0.4);
cursor: not-allowed;
&:hover {
background: transparent;
border: 2px transparent solid;
}
}
}
}
</style>

View file

@ -1,50 +0,0 @@
---
const InstanceList = await fetch('https://codeberg.org/MinPluto/MinPluto/raw/branch/master/instances.json').then((response) => response.json());
---
<table>
<thead>
<tr>
<th>URL</th>
<th>Official</th>
<th>Region</th>
<th>Uses Cloudflare</th>
</tr>
</thead>
<tbody>
{InstanceList.map((server) =>
<tr>
<td><a href={server[1].uri}>{server[1].uri}</a></td>
<td>{server[1].official ? <p>Yes</p> : <p>No</p>}</td>
<td>{server[1].region}</td>
<td>{server[1].CLOUDFLARE ? <p>Yes</p> : <p>No</p>}</td>
</tr>
)}
</tbody>
</table>
<style>
table {
border: 1px solid #3d3846;
height: 100%;
width: 100%;
table-layout: auto;
border-collapse: collapse;
border-spacing: 1px;
text-align: left;
}
th {
border: 1px solid #3d3846;
background-color: #000000;
color: #ffffff;
padding: 6px;
}
td {
border: 1px solid #3d3846;
background-color: #241f31;
color: #f6f5f4;
padding: 6px;
}
</style>

View file

@ -1,13 +0,0 @@
---
import {
DEFAULT_MEDIA_PROXY,
DEFAULT_MEDIA_DATA_PROXY,
} from '@utils/GetConfig'
const InvidiousDataInfomation = await fetch(DEFAULT_MEDIA_DATA_PROXY + '/api/v1/stats').then((response) => response.json());
---
{InvidiousDataInfomation.map((server) =>
<p>Version:</p>
)}

View file

@ -1,78 +0,0 @@
---
const {
Name,
Avatar,
Link,
Platform,
Banner,
Followers
} = Astro.props
---
<a href={Link} class={'search-creator'}>
<div class="search-creator-media">
<p>
{
()=> {
if (Platform === "YouTube") {
return <span>Subs: </span>
} else if (Platform === "Twitch") {
return <span>Followers: </span>
}
}
}
{Followers}
</p>
<img class="scm-banner" alt={Name + "'s banner"} src={Banner}/>
<img class="scm-avatar" alt={Name + "'s avatar"} src={Avatar}/>
</div>
<div class="search-creator-meta">
<p><strong>{Name}</strong></p>
</div>
</a>
<style lang="scss">
.search-creator {
background: #181818;
border-radius: 12px;
padding: 4px;
text-decoration: none;
* {cursor: var(--pointer-cursor) !important}
.search-creator-media {
position: relative;
p {
position: absolute;
right: 24px;
background: black;
border-radius: 1rem;
padding: 6px 12px;
}
.scm-banner {
aspect-ratio: 16/9;
width: 360px;
height: 140px;
object-fit: cover;
border-radius: 12px;
}
.scm-avatar {
position: absolute;
left: 12px;
bottom: -32px;
border-radius: 3rem;
border: 2px white solid;
width: 80px;
}
}
.search-creator-meta {
padding-left: 104px;
p {
// Truncate
max-width: 250px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inherit;
}
}
}
</style>

View file

@ -1,73 +0,0 @@
---
const {
Title,
Creator,
Avatar,
Link,
Thumbnail,
Views
} = Astro.props
---
<a href={Link} class='search-stream search-stream-platfom-twitch'>
<div class="search-stream-media">
<!-- <p>{Views}</p> -->
<img onload="this.style.opacity = '1'" class="ssm-banner" alt='Stream Thumbnail' src={Thumbnail} loading="lazy"/>
</div>
<div class="search-stream-meta">
<img onload="this.style.opacity = '1'" alt={Creator + "'s avatar"} src={Avatar} loading="lazy"/>
<div>
<p><strong>{Title}</strong></p>
<p>{Creator}</p>
</div>
</div>
</a>
<style lang="scss">
.search-stream {
background: #181818;
border-radius: 12px;
padding: 4px;
text-decoration: none;
* {cursor: var(--pointer-cursor) !important}
.search-stream-media {
position: relative;
p {
position: absolute;
right: 24px;
background: black;
border-radius: 1rem;
padding: 6px 12px;
}
.ssm-banner {
aspect-ratio: 16/9;
width: 100%;
object-fit: cover;
border-radius: 12px;
opacity: 0;
transition: 0.3s opacity;
}
}
.search-stream-meta {
display: flex;
align-items: center;
gap: 12px;
p {
margin: 6px 0px;
// Truncate
max-width: 250px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inherit;
}
img {
width: 42px;
height: 42px;
border-radius: 3rem;
opacity: 0;
transition: 0.3s opacity;
}
}
}
</style>

View file

@ -1,19 +0,0 @@
---
import {
ANALYTICS
} from '@utils/GetConfig'
---
<!-- This can't be done in Markdown or MDX, hence why it's an Astro component -->
{
()=> {
if (ANALYTICS === "false") {
return null
} else {
return
<h2>Information Collection and Use</h2>
<p>This instance of MinPluto uses {ANALYTICS} for analytics, no personal informatin is collected about you. Data that is collected can not be used to identify you, along with devices or any other factors. </p>
<p>All data collected with {ANALYTICS} is anonymous.</p>
}
}
}

View file

@ -1,28 +0,0 @@
---
import {
CLOUDFLARE
} from '@utils/GetConfig'
---
<!-- This can't be done in Markdown or MDX, hence why it's an Astro component -->
{CLOUDFLARE ?
<h2>Cloudflare Usage</h2>
<p>This instance uses Cloudflare which means the following information is collected:</p>
<ul>
<li>Refers</li>
<li>Paths</li>
<li>Hosts</li>
<li>Browser</li>
<li>Operating System</li>
<li>Device Type</li>
<li>ASN</li>
<li>User Agent</li>
<li>Data Center</li>
<li>Status Code</li>
<li>IP Address</li>
<li>HTTP Version</li>
</ul>
<p>This is shown in the "Analytics & Logs" portion in Cloudflare's Dashboard, this can't be turned off by the instance operator.</p>
:
null
}

View file

@ -1,148 +0,0 @@
---
// Cookies
if (Astro.cookies.get("Milieu").value === "Enabled") {
var Milieu = true
} else {
var Milieu = false
}
// Icons
import {
ArrowUpRight,
Backward15Seconds,
Enlarge,
Forward15Seconds,
NavArrowRight,
PlaySolid,
Settings,
SwitchOff,
SwitchOn,
} from "@iconoir/vue";
---
<div class="video-controls">
<div class="vc-top">
<p>The Mark On The Wall</p>
</div>
<div class="vc-bottom">
<div class="vc-start">
<button id="vc-playpause"><PlaySolid /></button>
<button id="vc-backwards"><Backward15Seconds /></button>
<button id="vc-forwards"><Forward15Seconds /></button>
</div>
<div class="vc-center">
<div class="vc-seek">
<span class="vc-progress-bar"></span>
<input class="seek" id="seek" value="0" min="0" type="range" step="1">
</div>
<p class="timestamp">
<span class="seek-tooltip" id="seek-tooltip">00:00</span>
<span id="current">00:00</span>
<span id="duration">00:00</span>
</p>
</div>
<div class="vc-end">
<button id="vc-settings" onclick="PlayerMenu_Settings()"><Settings /></button>
<!-- <button><SoundHighSolid /></button> Should affect <audio>, not <video> -->
<button id="vc-fullscreen"><Enlarge /></button>
</div>
</div>
<!-- Menus -->
<div id="settings" class="vc-menu">
<button>Stats for Geeks</button>
<hr/>
<button>Open Video URL <ArrowUpRight/></button>
<button>Download <ArrowUpRight/></button>
<button>Embed <ArrowUpRight/></button>
<hr/>
{Milieu ?
<button onclick="location.href = '/api/player/milieu/disable'" id="has-switch">Milieu <SwitchOn/></button>
:
<button onclick="location.href = '/api/player/milieu/enable'" id="has-switch">Milieu <SwitchOff/></button>
}
<!-- <button>Close Captions <NavArrowRight/></button> -->
<button onclick="PlayerMenu_Quality()">Quality <NavArrowRight/></button>
</div>
<div id="quality-changer" class="vc-menu">
<button onclick="ChangeQualtiy_1080()">1080p</button>
<button onclick="ChangeQualtiy_720()">720p</button>
<button onclick="ChangeQualtiy_360()">360p</button>
</div>
</div>
<script is:inline>
function PlayerMenu_HideAll() {
document.querySelector('.vc-menu#settings').style.display = 'none'
document.querySelector('.vc-menu#quality-changer').style.display = 'none'
}
function PlayerMenu_Settings() {
PlayerMenu_HideAll()
document.querySelector('.vc-menu#settings').style.display = 'flex'
}
function PlayerMenu_Quality() {
PlayerMenu_HideAll()
document.querySelector('.vc-menu#quality-changer').style.display = 'flex'
}
</script>
<script is:inline>
function ChangeQualtiy_1080() {
// Hide Menu
PlayerMenu_HideAll()
// Get Video
var video = document.querySelector('video')
var audio = document.querySelector('audio')
// Get Source
video.setAttribute('src', 'http://catactyl.home.ro:5006/The%20Mark%20On%20The%20Wall/1080.mp4')
// Get Current Timestamp
var CurrentTimestamp = document.querySelector("#seek").getAttribute('data-seek')
// Go To Timestamp
setTimeout(() => {
video.pause();
audio.pause();
video.currentTime = CurrentTimestamp;
audio.currentTime = CurrentTimestamp;
video.play();
audio.play();
}, 0o100);
}
function ChangeQualtiy_720() {
// Hide Menu
PlayerMenu_HideAll()
// Get Video
var video = document.querySelector('video')
var audio = document.querySelector('audio')
// Get Source
video.setAttribute('src', 'http://catactyl.home.ro:5006/The%20Mark%20On%20The%20Wall/720.mp4')
// Get Current Timestamp
var CurrentTimestamp = document.querySelector("#seek").getAttribute('data-seek')
// Go To Timestamp
setTimeout(() => {
video.pause();
audio.pause();
video.currentTime = CurrentTimestamp;
audio.currentTime = CurrentTimestamp;
video.play();
audio.play();
}, 0o100);
}
function ChangeQualtiy_360() {
// Hide Menu
PlayerMenu_HideAll()
// Get Video
var video = document.querySelector('video')
var audio = document.querySelector('audio')
// Get Source
video.setAttribute('src', 'http://catactyl.home.ro:5006/The%20Mark%20On%20The%20Wall/360.mp4')
// Get Current Timestamp
var CurrentTimestamp = document.querySelector("#seek").getAttribute('data-seek')
// Go To Timestamp
setTimeout(() => {
video.pause();
audio.pause();
video.currentTime = CurrentTimestamp;
audio.currentTime = CurrentTimestamp;
video.play();
audio.play();
}, 0o100);
}
</script>

View file

@ -1,101 +0,0 @@
---
// Properties
const { Poster, Video, Audio } = Astro.props
// Cookies
if (Astro.cookies.get("Milieu").value === "Enabled") {
var Milieu = true
} else {
var Milieu = false
}
// Components
import Controls from '@components/video-player/Controls.astro'
// Styles
import '@styles/player.scss'
---
<div class="video-container">
<canvas id="ambient-canvas-1"/>
<canvas id="ambient-canvas-2"/>
<video class="main-video" muted src={Video} poster={Poster} preload="auto"></video>
<audio class="main-audio"><source src={Audio} type="audio/mp3"/></audio>
<Controls/>
</div>
<!-- Player Functions -->
<script is:inline src="/player/controller.js"></script>
<script is:inline src="/player/seek.js"></script>
<script is:inline src="/player/sync.js"></script>
{Milieu ?
<script is:inline>
const AMvideo = document.querySelector("video")
const oddCanvas = document.getElementById("ambient-canvas-1")
const evenCanvas = document.getElementById("ambient-canvas-2")
const oddCtx = oddCanvas.getContext("2d")
const evenCtx = evenCanvas.getContext("2d")
const frameIntervalMs = 2000
const canvasOpacity = "0.4"
let intervalId
let oddFrame = true
const drawFrame = () => {
if (oddFrame) {
oddCtx.drawImage(AMvideo, 0, 0, oddCanvas.width, oddCanvas.height)
transitionToOddCanvas()
} else {
evenCtx.drawImage(AMvideo, 0, 0, evenCanvas.width, evenCanvas.height)
transitionToEvenCanvas()
}
oddFrame = !oddFrame
};
const transitionToOddCanvas = () => {
oddCanvas.style.opacity = canvasOpacity
evenCanvas.style.opacity = "0"
}
const transitionToEvenCanvas = () => {
evenCanvas.style.opacity = canvasOpacity
oddCanvas.style.opacity = "0"
}
const drawStart = () => {
intervalId = window.setInterval(drawFrame, frameIntervalMs)
}
const drawPause = () => {
if (intervalId) window.clearInterval(intervalId)
}
const init = () => {
//fixes a issue where firefox/chromium fails to load the ambinet mode and doesnt load it. - please dont remove this line lmao
AMvideo.pause();AMvideo.play();
// DO NOT REMOVE
AMvideo.addEventListener("play", drawStart, false)
AMvideo.addEventListener("pause", drawPause, false)
AMvideo.addEventListener("ended", drawPause, false)
oddCanvas.style.transition = evenCanvas.style.transition = `opacity ${frameIntervalMs}ms`
}
const cleanup = () => {
AMvideo.removeEventListener("play", drawStart)
AMvideo.removeEventListener("pause", drawPause)
AMvideo.removeEventListener("ended", drawPause)
drawPause();
}
window.addEventListener("load", init)
window.addEventListener("unload", cleanup)
</script>
:
null
}

2
source/src/env.d.ts vendored
View file

@ -1,2 +0,0 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

View file

@ -1,58 +0,0 @@
---
// Properties
const { Title, Description } = Astro.props
// Components
import Head from '@components/global/Head.astro'
// Styles
import '@styles/index.scss'
import '@styles/mobile.scss'
---
<Head Title='MinPluto' Description={Description}/>
<div class="api-wait">
<center><img src="/images/logo/MinPluto - Logo.png"/></center>
<div class="progress-bar"><div class="progress-bar-value"></div></div>
<slot/>
</div>
<style>
.api-wait {
position: fixed;
top: 30%;
left: 50%;
transform: translate(-50%, -50%);
display: grid;
gap: 64px;
width: 360px;
}
.progress-bar {
height: 6px;
background-color: rgba(5, 114, 206, 0.2);
width: 100%;
overflow: hidden;
border-radius: 1rem;
}
.progress-bar-value {
width: 100%;
height: 100%;
background-color: rgb(5, 114, 206);
animation: indeterminateAnimation 1s infinite linear;
transform-origin: 0% 50%;
}
@keyframes indeterminateAnimation {
0% {
transform: translateX(0) scaleX(0);
}
40% {
transform: translateX(0) scaleX(0.4);
}
100% {
transform: translateX(100%) scaleX(0.5);
}
}
</style>

View file

@ -1,22 +0,0 @@
---
// Properties
const { Title, Description } = Astro.props;
// Components
import Head from "@components/global/Head.astro";
import MobileNav from "@components/global/MobileNav.astro";
import Sidebar from "@components/global/Sidebar.astro";
import Footer from "@components/global/Footer.astro";
// Styles
import "@styles/index.scss";
import "@styles/mobile.scss";
---
<Head Title={Title} Description={Description} />
<MobileNav />
<Sidebar />
<div class="content">
<slot />
</div>
<Footer />

View file

@ -1,29 +0,0 @@
---
// Properties
const {
Title,
Description,
EmbedId,
EmbedVideo,
EmbedImage,
EmbedTitle
} = Astro.props
// Components
import Head from '@components/global/Head.astro'
import Footer from '@components/global/Footer.astro'
// Styles
import '@styles/embed.scss'
---
<Head
Title={Title}
Description={Description}
EmbedId={EmbedId}
EmbedVideo={EmbedVideo}
EmbedImage={EmbedImage}
EmbedTitle={EmbedTitle}
/>
<slot/>
<Footer/>

View file

@ -1,34 +0,0 @@
---
// Properties
const {
Title,
Message
} = Astro.props
---
<div class="error">
<a href='/'><img src="/images/logo/MinPluto - Logo.png"/></a>
<hr/>
<div>
<h2>{Title}</h2>
<p>{Message}</p>
</div>
</div>
<style is:inline>
body {
background: black;
color: white;
font-family: Arial, Helvetica, sans-serif;
}
.error {
position: fixed;
max-width: 500px;
margin: auto;
margin-top: 90px;
text-align: center;
left: 50%;
transform: translate(-50%);
}
</style>

View file

@ -1,34 +0,0 @@
---
// Properties
const { Title, Description } = Astro.props
// Components
import Head from '@components/global/Head.astro'
import MobileNav from '@components/global/MobileNav.astro'
import Sidebar from '@components/global/Sidebar.astro'
import Footer from '@components/global/Footer.astro'
// Styles
import '@styles/index.scss'
import '@styles/mobile.scss'
---
<Head Title={Title} Description={Description}/>
<MobileNav/>
<Sidebar/>
<div class="markdown">
<slot/>
</div>
<Footer/>
<style lang="scss">
.markdown {
background: transparent;
color: white;
max-width: 700px;
margin: auto;
margin-top: 32px;
margin-bottom: 32px;
padding: 6px 24px;
}
</style>

View file

@ -1,115 +0,0 @@
---
// Properties
const { Title, Description } = Astro.props
// Components
import Head from '@components/global/Head.astro'
import MobileNav from '@components/global/MobileNav.astro'
import Sidebar from '@components/global/Sidebar.astro'
import Footer from '@components/global/Footer.astro'
// Styles
import '@styles/index.scss'
import '@styles/mobile.scss'
import { CodeBrackets, EditPencil, EvPlugXmark, FloppyDisk, HelpCircle, InfoCircle, LogOut, Play, PrivacyPolicy, ProfileCircle, Server, UnionAlt, XmarkCircle } from '@iconoir/vue'
---
<Head Title={Title} Description={Description}/>
<MobileNav/>
<Sidebar/>
<div class="content settings-area">
<div class="settings-sidebar">
<div>
<h2>General</h2>
<a id="sidebar-disable" href="#"><EditPencil/> Customization</a>
<a id="sidebar-disable" href="#"><Play/> Video Player</a>
<a id="sidebar-disable" href="#"><CodeBrackets/> CSS/JS</a>
<h2>You</h2>
<a href="#"><ProfileCircle/> Account</a>
<h2>Advanced</h2>
<a id="sidebar-disable" href="#"><EvPlugXmark/> Reset</a>
<a id="sidebar-disable" href="#"><FloppyDisk/> Backup & Restore</a>
<a id="sidebar-disable" href="#"><XmarkCircle/> Delete Account</a>
</div>
<div>
<a id="sidebar-disable" href="#"><HelpCircle/> Help</a>
<a href="#"><UnionAlt/> Telemtry</a>
<a href="#"><Server/> Instance</a>
<a href="#"><PrivacyPolicy/> Privacy Policy</a>
<hr/>
<a href="#"><LogOut/> Log Out</a>
</div>
</div>
<div class="settings-content">
<slot/>
</div>
</div>
<Footer/>
<style is:inline>a[href="/settings"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style lang="scss">
.settings-area {
display: flex;
gap: 24px;
position: relative;
flex-wrap: wrap;
align-items: stretch;
min-height: 100vh;
.settings-content {
max-width: 750px;
margin: 0px auto;
}
.settings-sidebar {
background: #161616;
width: 224px;
color: white;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 0px 12px;
div {
position: sticky;
}
div:nth-child(1) {
position: sticky;
top: 0px;
}
div:nth-child(2) {
position: sticky;
bottom: 0px;
}
h2 {
font-size: 16px;
padding-left: 12px;
}
a {
color: white;
text-decoration: none;
margin-bottom: 6px;
border-radius: 6px;
padding: 4px 12px;
background: transparent;
border: 2px transparent solid;
display: flex;
align-items: center;
gap: 14px;
font-size: 14px;
&:hover {
background: rgb(255 255 255 / 05%);
border: 2px rgba(255, 255, 255, 0.05) solid;
}
svg {
width: 18px;
}
&#sidebar-disable {
filter: brightness(0.4);
cursor: not-allowed;
&:hover {
background: transparent;
border: 2px transparent solid;
}
}
}
}
}
</style>

View file

@ -1,20 +0,0 @@
---
import { changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<h2>Error 404: Page Not Found</h2>
<p>The page you've requested has either been moved or never existed. Double check the URL you're viewing.</p>
</div>
</Base>
<style>
.force-center {
text-align: center;
padding-top: 10%;
}
</style>

View file

@ -1,13 +0,0 @@
---
import ErrorLayout from '@layouts/Error.astro'
// Fetch Error Output
// https://github.com/withastro/astro/blob/main/packages/astro/src/core/errors/errors.ts#L5-L11
interface Props {
error: unknown
}
const { error } = Astro.props
---
<ErrorLayout Title={error.name} Message={error instanceof Error ? error.message : 'Unknown error'}/>

View file

@ -1,53 +0,0 @@
---
import i18next,{ t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import '@styles/form.scss'
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<img src="/images/logo/MinPluto - Image Logo Full with Shadow.png"/>
<hr/>
<img src="/images/backgrounds/1.webp"/>
</div>
<form action="/api/auth/confirm" method="post">
<div class="form-field">
<label>Email</label>
<input name="email" type="email" autocomplete="off" required/>
</div>
<div class="form-field">
<label>Code</label>
<input name="code" type="text" autocomplete="off" required/>
</div>
<div class="form-actions">
<div></div>
<div>
<span></span>
<button type="submit">Confirm</button>
</div>
</div>
</form>
</Base>
<style is:global>
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
}
.force-center {
text-align: center;
padding-top: 10%;
}
.search-bar {
display: none !important;
}
</style>

View file

@ -1,59 +0,0 @@
---
// i18n
import i18next,{ t, changeLanguage } from "i18next";
import Settings from "@layouts/Settings.astro";
// Supabase Data
import { supabase } from "@library/supabase"
const { data: { user } } = await supabase.auth.getUser()
const ID = user?.id
const Name = user?.user_metadata.name
const Email = user?.email
// Styles
import '@styles/form.scss'
---
<Settings Title="Account" Description="Manage your account">
<p>Account ID: {ID}</p>
<form action="/api/update/name" method="post">
<div class="form-field">
<label>Username</label>
<input name="name" type="text" autocomplete="off" value={Name} required/>
</div>
<div class="form-actions"><div></div><div><span></span><button type="submit">Update</button></div></div>
</form>
<form>
<div class="form-field">
<label>Email</label>
<input name="email" type="email" autocomplete="off" value={Email} readonly/>
</div>
<div class="form-actions"><div></div><div><span></span><button type="submit">Update</button></div></div>
</form>
<form onkeypress="CheckPasswordConfirmation()">
<div class="form-field">
<label>Password</label>
<input id="password" name="password" type="password" autocomplete="off" required/>
</div>
<div class="form-field">
<label>Confirm Password</label>
<input id="confirm_password" name="password" type="password" autocomplete="off" required/>
</div>
<div class="form-actions">
<div>
<a href="#">Forgot Password</a>
</div>
<div><span></span><button type="submit">Update</button></div>
</div>
</form>
</Settings>
<script is:inline>
function CheckPasswordConfirmation() {
if (document.getElementById('confirm_password').value !== document.getElementById('password').value) {
document.getElementById('confirm_password').setCustomValidity('Password Must be Matching.');
} else {
document.getElementById('confirm_password').setCustomValidity('');
}
}
</script>

View file

@ -1,20 +0,0 @@
import type { APIRoute } from "astro"
import { supabase } from "@library/supabase"
export const POST: APIRoute = async ({ request, redirect }) => {
const formData = await request.formData()
const EMAIL = formData.get("email")?.toString()
const OTP = formData.get("code")?.toString()
if (!OTP) {
return new Response("Code required", { status: 400 })
}
const { error } = await supabase.auth.verifyOtp({type: 'email', token: OTP, email: EMAIL})
if (error) {
return new Response(error.message, { status: 500 })
}
return redirect("/login?=confirmed")
}

View file

@ -1,35 +0,0 @@
import type { APIRoute } from "astro"
import { supabase } from "@library/supabase"
export const POST: APIRoute = async ({ request, cookies, redirect }) => {
const formData = await request.formData()
const email = formData.get("email")?.toString()
const password = formData.get("password")?.toString()
if (!email || !password) {
return new Response("Email and password are required", { status: 400 })
}
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
})
if (error) {
return new Response(error.message, { status: 500 })
}
const { access_token, refresh_token } = data.session
cookies.set("sb-access-token", access_token, {
sameSite: "strict",
path: "/",
secure: true,
})
cookies.set("sb-refresh-token", refresh_token, {
sameSite: "strict",
path: "/",
secure: true,
})
return redirect("/")
}

View file

@ -1,15 +0,0 @@
import type { APIRoute } from "astro"
import { supabase } from "@library/supabase"
import type { Provider } from "@supabase/supabase-js"
export const GET: APIRoute = async ({ cookies, redirect }) => {
if(cookies.get('anonymous-session')) {
return redirect('/account/anon/end')
} else {
cookies.delete("sb-access-token", { path: "/" })
cookies.delete("sb-refresh-token", { path: "/" })
const { error } = await supabase.auth.signOut()
}
return redirect("/")
}

View file

@ -1,40 +0,0 @@
import type { APIRoute } from "astro"
import { supabase } from "@library/supabase"
export const POST: APIRoute = async ({ request, redirect }) => {
const formData = await request.formData()
const name = formData.get("username")?.toString()
const email = formData.get("email")?.toString()
const password = formData.get("password")?.toString()
if (!email || !password || !name) {
return new Response("Email and password are required", { status: 400 })
}
const { error } = await supabase.auth.signUp({
options: {
emailRedirectTo: "http://localhost:1930/?=welcome",
data: {
name: name,
ui_theme: "Default",
ui_color: "Default",
ui_zen: "false",
ui_sidebar_size: "Normal",
invidous_data: "https://yt.sudovanilla.org",
invidous_media: "https://yt.sudovanilla.org",
safetwitch_data: "https://twitch.sudovanilla.org",
safetwitch_media: "https://twitch.sudovanilla.org",
image_proxy: "https://ipx.sudovanilla.org",
player_type: "Zorn"
}
},
email,
password,
})
if (error) {
return new Response(error.message, { status: 500 })
}
return redirect("/account/confirm-your-email")
}

View file

@ -1,27 +0,0 @@
---
// Layout
import API from '@layouts/API.astro'
// API Action
Astro.cookies.set("Language", "EN", {
path: "/",
sameSite: "strict"
});
// Track Event
import { OpenpanelSdk } from '@openpanel/sdk';
const op = new OpenpanelSdk({
clientId: 'b4c27f56-18f5-4d66-bb62-cbf7f7161812',
clientSecret: 'sec_107558407af59a591b50',
});
if (Astro.cookies.get("Telemtry").value === "Enabled") {
op.event('Language', { Language: 'English' });
}
else if (Astro.cookies.get("Telemtry").value === "Disabled") {
null
}
// Return
return Astro.redirect("/");
---
<API Title="MinPluto" Description=""></API>

View file

@ -1,27 +0,0 @@
---
// Layout
import API from '@layouts/API.astro'
// API Action
Astro.cookies.set("Language", "JP", {
path: "/",
sameSite: "strict"
});
// Track Event
import { OpenpanelSdk } from '@openpanel/sdk';
const op = new OpenpanelSdk({
clientId: 'b4c27f56-18f5-4d66-bb62-cbf7f7161812',
clientSecret: 'sec_107558407af59a591b50',
});
if (Astro.cookies.get("Telemtry").value === "Enabled") {
op.event('Language', { Language: 'Japanese' });
}
else if (Astro.cookies.get("Telemtry").value === "Disabled") {
null
}
// Return
return Astro.redirect("/");
---
<API Title="MinPluto" Description=""></API>

View file

@ -1,9 +0,0 @@
---
import API from '@layouts/API.astro'
Astro.cookies.set("Milieu", "Disabled", {
path: "/",
sameSite: "strict"
});
return Astro.redirect("/");
---
<API Title="MinPluto" Description=""></API>

View file

@ -1,14 +0,0 @@
---
// Layout
import API from '@layouts/API.astro'
// API Action
Astro.cookies.set("Milieu", "Enabled", {
path: "/",
sameSite: "strict"
});
// Return
return Astro.redirect("/telemtry");
---
<API Title="MinPluto" Description=""></API>

View file

@ -1,24 +0,0 @@
---
import Base from "@layouts/Default.astro"
const CreatorId = Astro.url.href.split("add?=").pop()
import { supabase } from "@library/supabase"
const { data: { user } } = await supabase.auth.getUser()
const { data, error } = await supabase
.from('subs')
.insert([
{
UserSubscribed: user?.id,
Id: CreatorId,
Platform: "YouTube"
},
])
.select()
return Astro.redirect("/channel/" + CreatorId)
---
<Base Title="MinPluto">
<div class="content">
<p>One moment...</p>
</div>
</Base>

View file

@ -1,21 +0,0 @@
---
import Base from "@layouts/Default.astro"
const CreatorId = Astro.url.href.split("remove?=").pop()
import { supabase } from "@library/supabase"
const { data: { user } } = await supabase.auth.getUser()
const id = user?.id
const { data, error } = await supabase
.from('subs')
.delete()
.eq('UserSubscribed', id)
.eq('Id', CreatorId)
.eq('Platform', 'YouTube')
return Astro.redirect("/channel/" + CreatorId)
---
<Base Title="MinPluto">
<div class="content">
<p>One moment...</p>
</div>
</Base>

View file

@ -1,24 +0,0 @@
---
import API from '@layouts/API.astro'
Astro.cookies.set("Telemtry", "Disabled", {
path: "/",
sameSite: "strict"
});
return Astro.redirect("/telemtry");
// SDK
import { OpenpanelSdk } from '@openpanel/sdk';
const op = new OpenpanelSdk({
clientId: 'b4c27f56-18f5-4d66-bb62-cbf7f7161812',
clientSecret: 'sec_107558407af59a591b50',
});
// Track Event
if (Astro.cookies.get("Telemtry").value === "Enabled") {
op.event('Language', { Language: 'English' });
}
else if (Astro.cookies.get("Telemtry").value === "Disabled") {
null
}
---
<API Title="MinPluto" Description=""></API>

View file

@ -1,36 +0,0 @@
---
// Layout
import API from '@layouts/API.astro'
// API Action
Astro.cookies.set("Telemtry", "Enabled", {
path: "/",
sameSite: "strict"
});
// SDK
import { OpenpanelSdk } from '@openpanel/sdk';
const op = new OpenpanelSdk({
clientId: 'b4c27f56-18f5-4d66-bb62-cbf7f7161812',
clientSecret: 'sec_107558407af59a591b50',
});
// Track Event
import { useUserAgent } from "astro-useragent";
const uaString = Astro.request.headers.get("user-agent");
const {
os,
browser,
browserVersion,
isDesktop,
isMobile
} = useUserAgent(uaString);
op.event('Operating System', {OS: os})
op.event('Browser', {Browser: browser + ' v' + browserVersion})
op.event('Desktop', {Desktop: isDesktop})
op.event('Mobile', {Mobile: isMobile})
// Return
return Astro.redirect("/telemtry");
---
<API Title="MinPluto" Description=""></API>

View file

@ -1,22 +0,0 @@
import type { APIRoute } from "astro"
import { supabase } from "@library/supabase"
export const POST: APIRoute = async ({ request, redirect }) => {
const formData = await request.formData()
const NewEmail = formData.get("email")?.toString()
if (!NewEmail) {
return new Response("Error.", { status: 400 })
}
const { error } = await supabase.auth.resend({
type: 'email_change',
email: NewEmail
})
if (error) {
return new Response(error.message, { status: 500 })
}
return redirect("/account?=CheckEmail")
}

View file

@ -1,21 +0,0 @@
import type { APIRoute } from "astro"
import { supabase } from "@library/supabase"
export const POST: APIRoute = async ({ request, redirect }) => {
const formData = await request.formData()
const name = formData.get("name")?.toString()
if (!name) {
return new Response("Error.", { status: 400 })
}
const { error } = await supabase.auth.updateUser({
data: {name: name}
})
if (error) {
return new Response(error.message, { status: 500 })
}
return redirect("/account?=NameUpdated")
}

View file

@ -1,57 +0,0 @@
---
import Base from "@layouts/Default.astro";
import Search from "@components/Search.astro";
---
<Base Title="MinPluto" Description="">
<br />
<Search />
</Base>
<style lang="scss" is:global is:inline>
.search-bar {
margin: auto;
max-width: 500px;
position: relative;
input {
color: white;
background: #121212;
border: 2px #242424 solid;
padding: 6px 12px;
border-radius: 6px;
font-size: 16px;
width: 100%;
&:focus {
outline: none;
}
}
button {
position: absolute;
right: 0px;
top: 0px;
color: #acacac;
background: #242424;
border: none;
border-radius: 4px;
padding: 0px 12px;
margin: 5px;
font-size: 0px;
}
}
.suggestions {
display: grid;
border-radius: 6px;
border: 2px #242424 solid;
background: #121212;
margin-top: 6px;
a {
color: white;
text-decoration: none;
padding: 4px 12px;
font-size: 14px;
&:hover {
opacity: 0.5;
}
}
}
</style>

View file

@ -1,9 +0,0 @@
---
import { t, changeLanguage } from "i18next";
import Category from "@layouts/Category.astro";
---
<Category GradientHero="#269753" FetchData="?type=gaming" CategoryName={t("SIDEBAR.CATEGORY_LIST.GAMES")} CategoryDescription={t("SIDEBAR.CATEGORY_LIST.GAMES_DESCRIPTION")}></Category>
<style is:inline>a[href="/category/gaming"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>

View file

@ -1,10 +0,0 @@
---
import { t, changeLanguage } from "i18next";
import Category from "@layouts/Category.astro";
---
<Category GradientHero="#ff4f4f" FetchData="?type=movies" CategoryName={t("SIDEBAR.CATEGORY_LIST.MOVIES")} CategoryDescription={t("SIDEBAR.CATEGORY_LIST.MOVIES_DESCRIPTION")}>
</Category>
<style is:inline>a[href="/category/movies"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>

View file

@ -1,111 +0,0 @@
---
import { t, changeLanguage } from "i18next";
import Default from "@layouts/Default.astro";
// Configuration
import {
DEFAULT_MEDIA_DATA_PROXY
} from '@utils/GetConfig'
// Components
import MusicItem from '@components/MusicItem.astro'
// Fetch
const fetchFrom = DEFAULT_MEDIA_DATA_PROXY + '/api/v1/trending?type=music'
const response = await fetch(fetchFrom)
const data = await response.json()
const heroItem = data.slice(0,1)
---
<Default FetchData="?type=movies" Title={t("SIDEBAR.CATEGORY_LIST.MUSIC")} Description={t("SIDEBAR.CATEGORY_LIST.MUSIC_DESCRIPTION")}>
<div class="category-hero">
<div class="c-hero-content">
<div style="width: 25%;">
<h2>Music</h2>
<p>Listen to the latest hits</p>
</div>
<div class="c-hero-video">
{heroItem.map((data) =>
<video autoplay muted src={DEFAULT_MEDIA_DATA_PROXY + '/latest_version?id=' + data.videoId + '&itag=22&local=true'}></video>
)}
</div>
</div>
</div>
<span id="gradient-header"></span>
<div class="or-mu">
<span></span>
<p id="title">{t("MUSIC.TITLE")}</p>
<p id="artist">{t("MUSIC.ARTIST")}</p>
<p id="date">{t("MUSIC.UPLOADED")}</p>
<p id="duration">{t("MUSIC.DURATION")}</p>
</div>
<div class="music-list">
{data.map((data) =>
<MusicItem
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
)}
</div>
</Default>
<style is:inline>a[href="/en /category/music"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style lang="scss">
.category-hero {
margin-top: -80px;
background: linear-gradient(180deg, #502969, transparent);
padding-top: 80px;
.c-hero-content {
display: flex;
align-items: center;
margin: auto;
max-width: 1000px;
justify-content: space-between;
.c-hero-video {
-webkit-mask-box-image: radial-gradient(rgb(0 0 0 / 50%), transparent);
-webkit-mask-box-image: linear-gradient(90deg, black, rgba(0, 0, 0, 0));
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
video {
object-fit: cover;
object-position: center;
width: 75%;
height: 100%;
float: right;
-webkit-mask-box-image: linear-gradient(270deg, black, transparent);
}
}
}
}
.or-mu {
display: grid;
grid-auto-flow: column;
align-items: center;
text-decoration: none;
gap: 12px;
grid-template-columns: 60px auto auto 80px 64px;
max-width: 1000px;
margin: auto;
border-bottom: 1px #252525 solid;
margin-bottom: 24px;
position: sticky;
top: 58px;
background: black;
#artist, #duration, #date {
text-align: right;
}
#title {
font-weight: bold;
}
}
</style>

View file

@ -1,9 +0,0 @@
---
import { t, changeLanguage } from "i18next";
import Category from "@layouts/Category.astro";
---
<Category GradientHero="#ff650b" FetchData="" CategoryName={t("SIDEBAR.CATEGORY_LIST.TRENDING")} CategoryDescription={t("SIDEBAR.CATEGORY_LIST.TRENDING_DESCRIPTION")}></Category>
<style is:inline>a[href="/category/trending"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>

View file

@ -1,183 +0,0 @@
---
import Base from "@layouts/Default.astro";
// i18n
import i18next, { t, changeLanguage } from "i18next";
// Configuration
import { DEFAULT_MEDIA_DATA_PROXY, DEFAULT_IMAGE_PROXY, SERVER_DOMAIN } from '@utils/GetConfig'
import { BrightStar, Donate, Download, ShareIos, ThumbsUp } from "@iconoir/vue";
// Components
import Video from '@components/VideoItem.astro'
// Fetch
const CreatorId = Astro.url.href.split("channel/").pop();
const channel = await fetch(DEFAULT_MEDIA_DATA_PROXY + "/api/v1/channels/" + CreatorId).then((response) => response.json());
const DescriptionFormat = channel.descriptionHtml.replaceAll("\n", " <br/> ");
// Is the user logged in?
if (Astro.cookies.get('sb-access-token') === undefined) {
var Guest = true
} else {
var Guest = false
}
// User Subscription
import { supabase } from "@library/supabase"
const { data: { user } } = await supabase.auth.getUser()
const id = user?.id
let { data: subs } = await supabase
.from('subs')
.select("*")
.eq('UserSubscribed', id)
.eq('Id', CreatorId)
if (Guest === false) {
if (subs[0] === undefined) {
var Subbed = false
} else {
var Subbed = true
}
}
else {
var Subbed = "NotLoggedIn"
}
---
<Base Title="MinPluto" Description="">
<div class="channel-backdrop">
<!-- <img src={channel.authorBanners[0].url}/> --> <!-- BROKEN -->
</div>
{Subbed ?
<a href={'/api/subscription/remove?=' + CreatorId}>Unfollow</a>
:
<a href={'/api/subscription/add?=' + CreatorId}>Follow</a>
}
{
()=> {
if (Subbed === true) {
return <a href={'/api/subscription/remove?=' + CreatorId}>Unfollow</a>
} else if (Subbed === false) {
return <a href={'/api/subscription/add?=' + CreatorId}>Follow</a>
} else if(Subbed === "NotLoggedIn") {
return null
}
}
}
<div class="channel">
<div class="channel-header">
<div class="channel-banner">
<!-- <img src={channel.authorBanners[0].url}/> --> <!-- BROKEN -->
</div>
<div class="channel-meta">
<div>
<img src={channel.authorThumbnails[1].url}/>
<h2>{channel.author}</h2>
<p>{channel.subCountText}</p>
</div>
<div>
<!-- {channel.isFamilyFriendly ? <p id="family-friendly"><BrightStar/> {t("CHANNEL.FAMILY_FRIENDLY")}</p> : null} -->
</div>
</div>
</div>
<div class="channel-content">
<!-- <div class="channel-tabs">
<a href="#">{t("CHANNEL.HOME")}</a>
<a href="#">{t("CHANNEL.VIDEOS")}</a>
<a href="#">{t("CHANNEL.COMMUNITY")}</a>
</div> -->
<div class="channel-tab-content">
<p><Fragment set:html={DescriptionFormat}/></p>
<hr/>
<h2>Latest Videos</h2>
<div class="video-grid">
{channel.latestVideos.map((data) =>
<Video
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
)}
<!-- <a>View All Latest Videos</a> -->
</div>
</div>
</div>
</div>
</Base>
<style lang="scss">
.channel-backdrop img {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: max-content;
filter: blur(30px) contrast(0.8);
z-index: -1;
transform: scale(1.4);
opacity: 0.3;
display: none;
}
.channel {
max-width: 1000px;
margin: auto;
.channel-header {
.channel-banner {
margin: 12px 0px;
img {
width: 100%;
border-radius: 10px;
}
}
.channel-meta {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
#family-friendly {
color: white;
background: #3b003b;
border: 1px #a500a5 solid;
border-radius: 3rem;
display: flex;
align-items: center;
padding: 6px 12px;
gap: 12px;
pointer-events: none;
}
div:nth-child(1) {
display: flex;
flex-direction: row;
gap: 12px;
img {
border-radius: 3rem;
width: 64px;
height: 64px;
}
}
}
}
.channel-content {
.channel-tabs {
padding-top: 24px;
padding-bottom: 12px;
cursor: default;
a {
text-decoration: none;
font-size: 18px;
padding: 12px 24px;
border-radius: 24px;
&:hover {
background: rgba(41, 41, 41, 0.5);
}
}
}
}
}
</style>

View file

@ -1,112 +0,0 @@
---
// Layout
import Base from "@layouts/Default.astro"
// i18n
import { t, changeLanguage } from "i18next"
// Configuration
import { DEFAULT_MEDIA_DATA_PROXY, DEFAULT_IMAGE_PROXY, DEFAULT_STREAM_DATA_PROXY } from '@utils/GetConfig'
// Components
import Video from '@components/VideoItem.astro'
import Stream from "@components/search/Stream.astro"
// Get URL Strings
const SearchQueryWithParameters = Astro.url.href.split("&?").shift()
const SearchQuery = SearchQueryWithParameters.split("discover?category=").pop()
if (Astro.url.href.includes('?platform=youtube')) {var SelectedPlatform = "YouTube"}
else if (Astro.url.href.includes('?platform=twitch')) {var SelectedPlatform = "Twitch"}
var FullCategoryName = SearchQuery.charAt(0).toUpperCase() + SearchQuery.slice(1)
// Fetch
if (Astro.url.href.includes('?platform=youtube')) {
var PlatformYouTube = true
var YouTubeCategory = DEFAULT_MEDIA_DATA_PROXY + '/api/v1/trending?type=' + SearchQuery
var YouTubeFetch = await fetch(YouTubeCategory)
var YouTubeData = await YouTubeFetch.json()
}
else if (Astro.url.href.includes('?platform=twitch')) {
var PlatformTwitch = true
var TwitchFetch = await fetch('https://twitch-backend.sudovanilla.org/api/discover/' + SearchQuery)
var TwitchData = await TwitchFetch.json()
}
---
<Base Title='MinPluto'>
<div class="discover-heading">
{PlatformTwitch ?
<div>
<h2>{TwitchData.data.displayName}</h2>
<p>{TwitchData.data.viewers} Viewers</p>
</div>
<p>{TwitchData.data.description}</p>
<ul>
<p>Tags: </p>
{TwitchData.data.tags.map((tag) =>
<li>{tag}</li>
)}
</ul>
:
null
}
</div>
<hr/>
<div class="video-grid">
{PlatformYouTube ?
YouTubeData.map((video) =>
<Video server:defer
ID={video.videoId}
Title={video.title}
Creator={video.author}
Views={video.viewCount}
UploadDate={video.published}
Length={video.lengthSeconds}
/>
)
:
null
}
{PlatformTwitch ?
TwitchData.data.streams.map((channel) =>
<Stream server:defer
Title={channel.title}
Creator={channel.username}
Avatar={channel.streamer.pfp}
Link={'/live?=' + channel.streamer.name}
Thumbnail={channel.preview}
View={channel.viewers}
/>
)
:
null
}
</div>
</Base>
<style lang="scss">
.discover-heading {
display: flex;
flex-direction: column;
padding: 0px 24px;
div {
display: flex;
justify-content: space-between;
align-items: center;
}
ul {
display: flex;
align-items: center;
padding: 0px;
gap: 12px;
li {
background: #181818;
list-style: none;
padding: 12px 24px;
border-radius: 3rem;
cursor: default;
}
}
}
</style>

View file

@ -1,129 +0,0 @@
---
import { t, changeLanguage } from "i18next";
import Embed from "@layouts/Embed.astro";
import "@styles/video.scss";
// Configuration
import { DEFAULT_MEDIA_DATA_PROXY, DEFAULT_IMAGE_PROXY, SERVER_DOMAIN } from '@utils/GetConfig'
// Components
import { Zorn } from "@minpluto/zorn"
// Fetch
const SWV = Astro.url.href.split("embed/").pop();
const video = await fetch(DEFAULT_MEDIA_DATA_PROXY + "/api/v1/videos/" + SWV).then((response) => response.json());
// Quality Check
const EightKCheck = await fetch(DEFAULT_MEDIA_DATA_PROXY + '/latest_version?id=' + video.videoId + '&itag=571')
if (EightKCheck.status == 200) {
var EightK = true
} else {
var EightK = false
}
const FourKCheck = await fetch(DEFAULT_MEDIA_DATA_PROXY + '/latest_version?id=' + video.videoId + '&itag=313')
if (FourKCheck.status == 200) {
var FourK = true
} else {
var FourK = false
}
const TenEightyCheck = await fetch(DEFAULT_MEDIA_DATA_PROXY + '/latest_version?id=' + video.videoId + '&itag=303')
if (TenEightyCheck.status == 200) {
var TenEighty = true
} else {
var TenEighty = false
}
const ThreeSixtyCheck = await fetch(DEFAULT_MEDIA_DATA_PROXY + '/latest_version?id=' + video.videoId + '&itag=134')
if (ThreeSixtyCheck.status == 200) {var ThreeSixty = true}
if (EightK === true) { // 571
var Quality = '571'
} else if (FourK === true) { // 313
var Quality = '313'
} else if (TenEighty === true) { // 137
var Quality = '303'
} else if (ThreeSixty === true) { // 134
var Quality = '134'
}
---
<Embed
Title={video.title}
EmbedId={video.videoId}
EmbedVideo={DEFAULT_MEDIA_DATA_PROXY + '/latest_version?id=' + video.videoId + '&itag=22&local=true'}
EmbedImage={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + video.videoId + '/maxresdefault.jpg'}
EmbedTitle={video.title}
>
<div class="video-container">
<Zorn
Poster={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + video.videoId + '/maxresdefault.jpg'}
Video={DEFAULT_MEDIA_DATA_PROXY + '/latest_version?id=' + video.videoId + '&itag=' + Quality + '&local=true'}
Audio={DEFAULT_MEDIA_DATA_PROXY + '/latest_version?id=' + video.videoId + '&itag=140'}
VideoAttributes="muted"
AudioAttributes=""
/>
</div>
</Embed>
<script is:inline>
function DownloadDialogShow() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function DownloadDialogHide() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
function ShareDialogShow() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function ShareDialogHide() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
</script>
<style lang="scss">
.video-container {
max-width: 1000px;
margin: auto;
.zorn-player {
border-radius: 10px;
}
.zorn-player-controls {
border-radius: 0px 0px 10px 10px;
}
}
.video-item {
margin-bottom: 24px;
}
.hidden {
display: none;
}
.dialog-downloads-list {
display: grid;
grid-gap: 12px;
a {
background: rgb(51 51 51);
border: 2px rgba(255,255,255,0.05) solid;
font-size: 18px;
text-decoration: none;
border-radius: 4px;
padding: 9px 16px;
}
}
</style>

View file

@ -1,5 +0,0 @@
---
// Components
import Trending from "@components/category/trending.astro";
---
<Trending FetchData=""/>

View file

@ -1,158 +0,0 @@
P---
import i18next,{ t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
// Configuration
import {
DEFAULT_MEDIA_DATA_PROXY,
DEFAULT_IMAGE_PROXY,
DEFAULT_STREAM_DATA_PROXY
} from '@utils/GetConfig'
import { FireFlame, Frame, Gamepad, GraphUp, Movie, MusicDoubleNote } from "@iconoir/vue";
// Components
import Trending from "@components/category/trending.astro";
import Chip from "@components/Chip.astro";
import CategoryCard from "@components/category/CategoryCard.astro";
// Fetch
const TrendingFetch = DEFAULT_MEDIA_DATA_PROXY + '/api/v1/trending'
const TrendingResponse = await fetch(TrendingFetch)
const TrendingData = await TrendingResponse.json()
const TrendingSplit = TrendingData.slice(0, 1)
const MoviesFetch = DEFAULT_MEDIA_DATA_PROXY + '/api/v1/trending?type=movies'
const MoviesResponse = await fetch(MoviesFetch)
const MoviesData = await MoviesResponse.json()
const MoviesSplit = MoviesData.slice(0, 1)
const MusicFetch = DEFAULT_MEDIA_DATA_PROXY + '/api/v1/trending?type=music'
const MusicResponse = await fetch(MusicFetch)
const MusicData = await MusicResponse.json()
const MusicSplit = MusicData.slice(0, 1)
const GamingFetch = DEFAULT_MEDIA_DATA_PROXY + '/api/v1/trending?type=gaming'
const GamingResponse = await fetch(GamingFetch)
const GamingData = await GamingResponse.json()
const GamingSplit = GamingData.slice(0, 1)
/// Twitch (/api/discover/)
const TwitchDiscoverFetch = await fetch(DEFAULT_STREAM_DATA_PROXY + '/api/discover')
const TwitchDiscoverData = await TwitchDiscoverFetch.json()
---
<Base Title="MinPluto" Description="">
<br/>
<div class="category-select-grid" style="grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) !important;">
{TrendingSplit.map((data) =>
<CategoryCard
Link={'/discover?category=trending&?platform=youtube'}
Name={t("SIDEBAR.CATEGORY_LIST.POPULAR")}
Platform="YouTube"
Thumbnail={DEFAULT_IMAGE_PROXY + '/' + 'https://img.youtube.com/' + '/vi/' + data.videoId + '/mqdefault.jpg'}
Ratio="16:9"
server:defer
>
<div slot="fallback" class="fl-sk-video" style="height: 328px;"></div>
</CategoryCard>
)}
{MoviesSplit.map((data) =>
<CategoryCard
Link={'/discover?category=movies&?platform=youtube'}
Name={t("SIDEBAR.CATEGORY_LIST.TRAILERS")}
Platform="YouTube"
Thumbnail={DEFAULT_IMAGE_PROXY + '/' + 'https://img.youtube.com/' + '/vi/' + data.videoId + '/mqdefault.jpg'}
Ratio="16:9"
server:defer
>
<div slot="fallback" class="fl-sk-video" style="height: 328px;"></div>
</CategoryCard>
)}
{MusicSplit.map((data) =>
<CategoryCard
Link={'/discover?category=music&?platform=youtube'}
Name={t("SIDEBAR.CATEGORY_LIST.MUSIC")}
Platform="YouTube"
Thumbnail={DEFAULT_IMAGE_PROXY + '/' + 'https://img.youtube.com/' + '/vi/' + data.videoId + '/mqdefault.jpg'}
Ratio="16:9"
server:defer
>
<div slot="fallback" class="fl-sk-video" style="height: 328px;"></div>
</CategoryCard>
)}
{GamingSplit.map((data) =>
<CategoryCard
Link={'/discover?category=gaming&?platform=youtube'}
Name={t("SIDEBAR.CATEGORY_LIST.GAMES")}
Platform="YouTube"
Thumbnail={DEFAULT_IMAGE_PROXY + '/' + 'https://img.youtube.com/' + '/vi/' + data.videoId + '/mqdefault.jpg'}
Ratio="16:9"
server:defer
>
<div slot="fallback" class="fl-sk-video" style="height: 328px;"></div>
</CategoryCard>
)}
</div>
<br/>
<div id="twitch" class="category-select-grid">
{TwitchDiscoverData.data.map((data) =>
<CategoryCard
Link={'/discover?category=' + data.name + '&?platform=twitch'}
Name={data.displayName}
Platform="Twitch"
Thumbnail={DEFAULT_IMAGE_PROXY + '/' + data.image}
Ratio="9:16"
server:defer
>
<div slot="fallback" class="fl-sk-video" style="height: 328px;"></div>
</CategoryCard>
)}
</div>
<br/>
</Base>
<style is:inline>.sidebar-top-end a[href="/"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style is:global lang="scss">
@keyframes load {
0% { background-position-x: -100px; }
100% { background-position-x: -100%; }
}
.fl-sk-video {
height: 183px;
width: 100%;
background-image: linear-gradient(90deg, #323232 0px, #4c4c4c 40px, #323232 80px);
background-size: 600px;
animation: load 2s linear infinite;
border-radius: 6px;
}
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
.category-select-grid {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) !important;
}
}
.force-center {
text-align: center;
padding-top: 10%;
}
.category-select-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-gap: 25px;
max-width: 1600px;
margin: auto;
padding: 0px 24px;
}
</style>

View file

@ -1,49 +0,0 @@
---
layout: '@layouts/Settings.astro'
---
import {
SERVER_ADMIN,
SERVER_LOCATION,
DEFAULT_MEDIA_PROXY,
DEFAULT_MEDIA_DATA_PROXY,
DEFAULT_STREAM_PROXY,
DEFAULT_STREAM_DATA_PROXY,
DEFAULT_IMAGE_PROXY,
MODIFIED,
CUSTOM_SOURCE_CODE,
ANALYTICS,
} from '@utils/GetConfig'
import Invidous from '@components/information/Invidious.astro'
import InstanceList from '@components/information/InstanceList.astro'
MinPluto Instance Information
-----------------------------
Server Operator: {SERVER_ADMIN}
Location: {SERVER_LOCATION}
Invidious Media Proxy: {DEFAULT_MEDIA_PROXY}
Invidious Data Proxy: {DEFAULT_MEDIA_DATA_PROXY}
SafeTwitch Data Proxy: {DEFAULT_STREAM_PROXY}
SafeTwitch Media Proxy: {DEFAULT_STREAM_DATA_PROXY}
Image Proxy: {DEFAULT_IMAGE_PROXY}
Modified: {MODIFIED}
{MODIFIED ? <p>Modified Source Code: {CUSTOM_SOURCE_CODE}</p> : null}
Analytics Software: {ANALYTICS}
___
MinPluto Instances
---------------------------
<InstanceList/>

View file

@ -1,191 +0,0 @@
---
// Layout
import Base from "@layouts/Default.astro";
import "@styles/video.scss";
// Environment Variables
// const DEFAULT_IMAGE_PROXY = import.meta.env.DEFAULT_IMAGE_PROXY
// const DEFAULT_STREAM_PROXY = import.meta.env.DEFAULT_STREAM_PROXY
// const DEFAULT_STREAM_DATA_PROXY = import.meta.env.DEFAULT_STREAM_DATA_PROXY
// Components
import { Zorn } from "@minpluto/zorn";
import { ArrowDown } from "@iconoir/vue";
// Fetch
const CreatorName = Astro.url.href.split("live?=").pop();
const Creator = await fetch("https://twitch-backend.sudovanilla.org" + "/api/users/" + CreatorName,).then((response) => response.json());
// Check if the Creator is live
if(Creator.data.isLive == true) {
var IsLive = true
} else if(Creator.data.isLive == false) {
var IsLive = false
}
---
<Base>
<noscript>
<p>
In order to watch a Twitch live stream on MinPluto, your browser is
required to have JavaScript enabled.
</p>
<p>
If your browser does not support JavaScript, try a modern web browser such
as Firefox.
</p>
</noscript>
<div class="creator-tw-backdrop" style={"background-image: radial-gradient(ellipse farthest-side at center top, #" + Creator.data.colorHex + ", hsla(0, 0%, 0%, 0) 100%);"}></div>
<div class="creator-tw">
<div class="creator-tw-start">
<img class="creator-tw-banner" src={"https://ipx.sudovanilla.org" + "/" + Creator.data.banner} />
<div class="creator-tw-start-top">
{IsLive ? <span id="streamer-online" class="tw-status">Online</span> : <span id="streamer-offline" class="tw-status">Offline</span>}
<h2 style="margin: 24px 0px 6px 0px;">{Creator.data.username}</h2>
<p style="margin: 0px;font-size: 12px;color: gray;">{Creator.data.followers} Followers</p>
<br/>
<p>{Creator.data.about}</p>
<div class="creator-tw-socials">
{Creator.data.socials.map((social) =>
<a style={"background: #" + Creator.data.colorHex} href={social.url}>{social.name}</a>
)}
</div>
</div>
<div class="creator-tw-start-bottom"></div>
</div>
<div class="creator-tw-end">
{IsLive ?
<Zorn
Live
Video={"https://twitch-backend.sudovanilla.org" +
"/proxy/stream/" +
CreatorName +
"/hls.m3u8"}
/>
:
<div class="creator-is-offline">
<p>Offline</p>
</div>
}
</div>
</div>
</Base>
{IsLive ?
<style>
.creator-tw {
backdrop-filter: blur(24px) contrast(0.8) brightness(0.8);
}
.creator-tw-start::before, .creator-tw-banner {
border-radius: 12px 0px 0px 12px;
}
</style>
:
<style>
.creator-tw-start::before, .creator-tw-banner {
border-radius: 12px;
}
</style>
}
<style is:global lang="scss">
.creator-tw-backdrop {
background-color: transparent;
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
z-index: -1;
}
.creator-tw {
border-radius: 12px;
margin: 24px;
display: grid;
grid-template-columns: 300px auto;
min-height: 500px;
.creator-tw-start {
position: relative;
display: flex;
flex-direction: column;
padding: 24px;
&::before {
content: "";
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
z-index: -1;
backdrop-filter: blur(24px) contrast(0.8) brightness(0.8);
}
.creator-tw-banner {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
object-fit: cover;
z-index: -2;
}
.tw-status {
background: transparent;
border-radius: 3rem;
padding: 6px 12px;
font-size: 12px;
font-weight: bold;
}
#streamer-online {
background: #c95050;
}
#streamer-offline {
background: #454545;
}
.creator-tw-socials {
display: flex;
gap: 6px;
flex-wrap: wrap;
a {
text-decoration: none;
font-size: 14px;
border-radius: 3rem;
padding: 6px 12px;
}
}
}
.creator-tw-end {
position: relative;
.video-container {
video {
border-radius: 0px 12px 12px 0px;
}
.video-controls {
border-radius: 0px 0px 12px 0px !important;
}
}
.creator-is-offline {
align-items: center;
display: flex;
justify-content: center;
height: 100%;
font-size: 90px;
letter-spacing: -0.014em;
font-weight: 900;
color: #ffffff4f;
animation: 4.2s TextSpacingIn ease-in-out;
}
}
}
@keyframes TextSpacingIn {
from {
letter-spacing: 0.086em;
opacity: 0.45;
}
to {
letter-spacing: -0.014em;
opacity: 1;
}
}
</style>

View file

@ -1,58 +0,0 @@
---
import i18next,{ t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import '@styles/form.scss'
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<img src="/images/logo/MinPluto - Image Logo Full with Shadow.png"/>
<hr/>
<img src="/images/backgrounds/1.webp"/>
</div>
<form action="/api/auth/login" method="post">
<div class="form-field">
<label>Email</label>
<input name="email" type="email" autocomplete="off" required/>
</div>
<div class="form-field">
<label>Password</label>
<input name="password" type="password" autocomplete="off" required/>
</div>
<div class="form-actions">
<div>
<a aria-disabled="true" href="#">Forgot Password</a>
</div>
<div>
<button onclick="location.href = '/register/'">Create Account</button>
<button type="submit">Login</button>
</div>
</div>
</form>
</Base>
<style is:inline>a[href="/login"] {background: rgb(255 255 255 / 25%) !important;border: 2px rgba(255, 255, 255, 0.25) solid !important;}</style>
<style is:global>
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
}
.force-center {
text-align: center;
padding-top: 10%;
}
.search-bar {
display: none !important;
}
form {
max-width: 500px !important;
}
</style>

View file

@ -1,66 +0,0 @@
---
import { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import { version } from "@root/package.json";
---
<Base Title="MinPluto" Description="">
<div class="ms">
<img src="/images/logo/MinPluto - Image Logo Full with Shadow.png"/>
<h2>MinPluto</h2>
<p>{t("HOME.P1")}</p>
<hr/>
<p style="font-size: 14px;"><i>{t("HOME.P2")}</i></p>
<hr/>
<div style="display: flex; justify-content: space-evenly;">
<a href="https://community.minpluto.org/" target="_blank">{t("SIDEBAR.FOOTER.STATUS")}</a>
<a href="https://status.minpluto.org/" target="_blank">{t("SIDEBAR.FOOTER.FORUM")}</a>
<a href="https://ark.sudovanilla.org/MinPluto/MinPluto" target="_blank">{t("SIDEBAR.FOOTER.SOURCE_CODE")}</a>
</div>
</div>
<p id="version">v{version}</p>
<img src="/images/backgrounds/1.webp"/>
</Base>
<style>
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
}
.ms {
margin-top: 20%;
text-align: center;
padding: 0px 24px;
}
a {
color: white;
}
hr {
width: 100px;
border: none;
background: rgba(255, 255, 255, 0.1);
height: 4px;
border-radius: 1rem;
margin: 24px auto;
}
p#version {
position: fixed;
bottom: 88px;
left: 0px;
width: 100%;
background: #232323;
color: white;
padding: 24px 0px;
text-align: center;
}
</style>

View file

@ -1,40 +0,0 @@
---
import { t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import { HomeSimple, GraphUp, Movie, MusicDoubleNote, Gamepad, AppleImac2021Side, EmojiTalkingHappy, PizzaSlice, Treadmill, PeaceHand } from "@iconoir/vue";
---
<Base Title="MinPluto" Description="">
<div style="padding: 0px 24px;">
<h2>Categories</h2>
<div id="c" style="display: flex;flex-direction: column;gap: 24px;">
<a href="/category/trending"><GraphUp/> {t("CATEGORY_LIST.TRENDING")}</a>
<a href="/category/movies"><Movie/> {t("CATEGORY_LIST.MOVIES")}</a>
<a href="/category/music"><MusicDoubleNote/> {t("CATEGORY_LIST.MUSIC")}</a>
<a href="/category/gaming"><Gamepad/> {t("CATEGORY_LIST.GAMES")}</a>
</div>
</div>
</Base>
<style lang="scss">
#c a {
color: white;
text-decoration: none;
background: linear-gradient(45deg, #636363, #181818);
padding: 48px 24px;
font-size: 24px;
display: flex;
align-items: center;
gap: 24px;
border-radius: 10px;
svg {
position: absolute;
right: 60px;
width: 100px;
height: 70px;
opacity: 0.4;
}
}
</style>

View file

@ -1,20 +0,0 @@
---
import { changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<h2>No Internet Connection</h2>
<p>It appears that you are offline, try connecting your device to the internet, and try again.</p>
</div>
</Base>
<style>
.force-center {
text-align: center;
padding-top: 10%;
}
</style>

View file

@ -1,7 +0,0 @@
---
layout: '@layouts/Markdown.astro'
---
Error
-----------------------------
Playlists are not yet supported in MinPluto.

View file

@ -1,97 +0,0 @@
---
layout: "@layouts/Settings.astro"
---
import {
ANALYTICS
} from '@utils/GetConfig'
import PrivacyPolicyAnalytics from '@components/text/PrivacyPolicyAnalytics.astro'
import PrivacyPolicyCloudflare from '@components/text/PrivacyPolicyCloudflare.astro'
import OptButtons from '@components/buttons/Telemtry.astro'
## Privacy Policy
### 3rd Party Services are Proxied
Any service in MinPluto that is 3rd party is proxied by this instance or another instance such as images, videos, fonts, scripts, and more. No personal information information is collected and no information is sent to any 3rd party.
<PrivacyPolicyAnalytics/>
<PrivacyPolicyCloudflare/>
### We Don't Know What You're Watching
When you watch a video on a MinPluto instance with analytics enabled, the analytics software doesn't show what video are you watching. The query in the URL, which is the video ID, is not saved to analytics. What we see is only "/watch", not "/watch?=dQw4w9WgXcQ". This goes for anything else that may have a query in it.
### Telemtry
Telemtry data is used in MinPluto to see metrics of usage on different features and other aspects. This is help SudoVanilla, the developer of MinPluto, make better and informed decisions on what needs priority.
By default, all users are opted-out for privacy purposes.
If you decide to opt-in, the following is tracked:
* Language
* Browser Useragent
* Operating System
* Errors
* Location
* All settings, except for some
* Platforms you've imported from
The following will not be tracked:
* Searches
* Videos/Streams you watched
* Subscribed Channels
* Downloads
* Shares
* Embeds
* Personal Information in your account, such as name and email
* CSS/JS Customization
* Themes
Note that this can change at anytime.
It is expected by the MinPluto developer that all telemtry data is sent to SudoVanilla's OpenPanel ananlytics. Note that the instance operator can easily change the destination of this, it is not expected.
<div style="text-align: right"><OptButtons/></div>
### Liability
MinPluto and SudoVanilla take no responsibility for the use of our tool, or external instances provided by third parties. It is strongly recommended that you abide by the valid official regulations in your country. Furthermore, we refuse liability for any inappropriate use of MinPluto, such as illegal downloading.
MinPluto is licenced under AGPL v3, this software is included with a copy.
<hr/>
```
Copyright (C) 2024 SudoVanilla
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 https://www.gnu.org/licenses/.
```
<hr/>
### Links to Other Sites
Channels and videos on MinPluto may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by SudoVanilla, MinPluto, or the MinPluto instance. Therefore, I strongly advise you to review the privacy policy of these websites. I have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.
### Changes to This Privacy Policy
The developers of MinPluto, SudoVanilla, may update our privacy policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new privacy policy on this page.
This also goes for the instance itself.
This policy is effective as of March 7th, 2024.
### Contact Us
If you have any questions or suggestions about MinPluto's privacy policy, do not hesitate to contact me at hello@minpluto.org or to [submit an issue](https://ark.sudovanilla.org/MinPluto/MinPluto/issues).

View file

@ -1,66 +0,0 @@
---
import i18next,{ t, changeLanguage } from "i18next";
import Base from "@layouts/Default.astro";
import '@styles/form.scss'
---
<Base Title="MinPluto" Description="">
<div class="force-center">
<img src="/images/logo/MinPluto - Image Logo Full with Shadow.png"/>
<hr/>
<img src="/images/backgrounds/1.webp"/>
</div>
<form action="/api/auth/register" method="post">
<div class="form-field">
<label>Username</label>
<input name="username" type="text" autocomplete="off" required/>
</div>
<div class="form-field">
<label>Email</label>
<input name="email" type="email" autocomplete="off" required/>
</div>
<div style="display: flex; justify-content: space-between; gap: 6px;">
<div style="width: 100%;" class="form-field">
<label>Password</label>
<input name="password" type="password" autocomplete="off" required/>
</div>
</div>
<div class="form-actions">
<div>
<a href="/privacy">Privacy Policy</a>
</div>
<div>
<button onclick="location.href = '/login'">Login</button>
<button type="submit">Register</button>
</div>
</div>
</form>
</Base>
<style is:global>
img[src="/images/backgrounds/1.webp"] {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-57%);
-webkit-mask-image: linear-gradient(180deg, transparent 5%, rgb(0 0 0) 52%, rgb(0 0 0) 44%, transparent 95%);
z-index: -1;
opacity: 0.5;
}
@media only screen and (min-width: 875px) {
img[src="/images/backgrounds/1.webp"] {
display: none;
}
}
.force-center {
text-align: center;
padding-top: 10%;
}
.search-bar {
display: none !important;
}
form {
max-width: 500px !important;
}
</style>

View file

@ -1,142 +0,0 @@
---
// Layout
import Base from "@layouts/Default.astro"
// i18n
import { t, changeLanguage } from "i18next"
// Configuration
import { DEFAULT_MEDIA_DATA_PROXY, DEFAULT_IMAGE_PROXY, DEFAULT_STREAM_DATA_PROXY } from '@utils/GetConfig'
// Components
import Video from '@components/VideoItem.astro'
import DiscoverChannel from "@components/DiscoverChannel.astro"
import Stream from "@components/search/Stream.astro"
import { Group, Movie, Play, PlaylistPlay, ReportColumns, VideoCamera } from "@iconoir/vue"
// Fetch
const SearchQueryWithParameters = Astro.url.href.split("&?").shift()
const SearchQuery = SearchQueryWithParameters.split("search?query=").pop()
// Check Filters
if (Astro.url.href.includes('?type=channel')) {
var ShowChannels = true
}
else if (Astro.url.href.includes('?type=playlist')) {
var ShowPlaylists = true
}
else if (Astro.url.href.includes('?type=stream')) {
var ShowStreams = true
}
else if (Astro.url.href.includes('?type=all')) {
var ShowAll = true
}
else {
var ShowVideos = true
}
/// Videos (YouTube)
const VideoSearchResults = await fetch(DEFAULT_MEDIA_DATA_PROXY + "/api/v1/search?q=" + SearchQuery + '&page=1&date=none&type=video&duration=none&sort=relevance')
const VideoSearch = await VideoSearchResults.json()
/// Playlists (YouTube)
const PlaylistsSearchResults = await fetch(DEFAULT_MEDIA_DATA_PROXY + "/api/v1/search?q=" + SearchQuery + '&page=1&date=none&type=playlist&duration=none&sort=relevance')
const PlaylistsSearch = await PlaylistsSearchResults.json()
// Streams (Twitch)
const StreamSearchResults = await fetch(DEFAULT_STREAM_DATA_PROXY + "/api/search/?query=" + SearchQuery)
const StreamSearch = await StreamSearchResults.json()
/// Channels (YouTube)
const ChannelSearchResults = await fetch(DEFAULT_MEDIA_DATA_PROXY + "/api/v1/search?q=" + SearchQuery + '&page=1&date=none&type=channel&duration=none&sort=relevance')
const ChannelSearch = await ChannelSearchResults.json()
---
<Base Title='MinPluto Search'>
<div class="search-tabs">
<div>
<!-- <a id="search-tab-all" href={'/search?query=' + SearchQuery + '&?type=all'}><ReportColumns/> All</a> -->
<a id="search-tab-videos" href={'/search?query=' + SearchQuery}><Play/> Videos</a>
<a id="search-tab-playlists" href={'/search?query=' + SearchQuery + '&?type=playlist'}><PlaylistPlay/> Playlists</a>
<a id="search-tab-streams" href={'/search?query=' + SearchQuery + '&?type=stream'}><VideoCamera/> Streams</a>
<a id="search-tab-creator" href={'/search?query=' + SearchQuery + '&?type=channel'}><Group/> Creators</a>
</div>
<div>
{ShowVideos ? <a style="color: gray;" href={DEFAULT_MEDIA_DATA_PROXY + "/api/v1/search?q=" + SearchQuery + '&page=1&date=none&type=video&duration=none&sort=relevance'}>View JSON</a> : null}
{ShowPlaylists ? <a style="color: gray;" href={DEFAULT_MEDIA_DATA_PROXY + "/api/v1/search?q=" + SearchQuery + '&page=1&date=none&type=playlist&duration=none&sort=relevance'}>View JSON</a> : null}
{ShowStreams ? <a style="color: gray;" href={DEFAULT_STREAM_DATA_PROXY + "/api/search/?query=" + SearchQuery}>View JSON</a> : null}
{ShowChannels ? <a style="color: gray;" href={DEFAULT_MEDIA_DATA_PROXY + "/api/v1/search?q=" + SearchQuery + '&page=1&date=none&type=channel&duration=none&sort=relevance'}>View JSON</a> : null}
</div>
</div>
<!-- {ShowAll ? <style>#search-tab-all {background: white; color: black;}</style> : null} -->
{ShowVideos ? <style>#search-tab-videos {background: white; color: black;}</style> : null}
{ShowPlaylists ? <style>#search-tab-playlists {background: white; color: black;}</style> : null}
{ShowStreams ? <style>#search-tab-streams {background: white; color: black;}</style> : null}
{ShowChannels ? <style>#search-tab-creator {background: white; color: black;}</style> : null}
<div class="video-grid">
{ShowVideos ?
VideoSearch.map((video) =>
<Video
ID={video.videoId}
Title={video.title}
Creator={video.author}
Views={video.viewCount}
UploadDate={video.published}
Length={video.lengthSeconds}
/>
)
:
null
}
{ShowPlaylists ?
PlaylistsSearch.map((playlist) =>
<a href={'/playlist?list=' + playlist.playlistId}>{playlist.title}</a>
)
:
null
}
{ShowChannels ?
ChannelSearch.map((channel) =>
<DiscoverChannel ChannelId={channel.authorId}/>
)
:
null
}
{ShowStreams ?
StreamSearch.data.relatedChannels.map((channel) =>
<Stream
Title={channel.stream.title}
Creator={channel.username}
Avatar={channel.pfp}
Link={'/channel/twitch/' + channel.username}
Thumbnail={channel.stream.preview}
View={channel.stream.viewers}
/>
)
:
null
}
</div>
</Base>
<style lang="scss">
.search-tabs {
max-width: 1000px;
margin: 0px auto 16px auto;
display: flex;
justify-content: space-between;
div {
display: flex;
gap: 6px;
}
a {
text-decoration: none;
display: flex;
align-items: center;
gap: 6px;
background: transparent;
border-radius: 3rem;
padding: 6px 12px;
}
}
</style>

View file

@ -1,38 +0,0 @@
---
layout: '@layouts/Settings.astro'
---
import OptButtons from '@components/buttons/Telemtry.astro'
MinPluto Telemtry
-----------------
Telemtry data is used in MinPluto to see metrics of usage on different features and other aspects. This is to help SudoVanilla, the developer of MinPluto, make better and informed decisions on what needs priority.
By default, you're opted-out for privacy purposes.
If you decide to opt-in, the following is tracked:
* Language
* Browser Useragent
* Operating System
* Errors
* Location
* All settings, except for some
* Platforms you've imported from
The following will not be tracked:
* Searches
* Videos/Streams you watched
* Subscribed Channels
* Downloads
* Shares
* Embeds
* Personal Information in your account, such as name and email
* CSS/JS Customization
* Themes
Note that this can change at anytime.
<div style="text-align: right"><OptButtons/></div>

View file

@ -1,19 +0,0 @@
---
import Base from "@layouts/Default.astro";
import Player from "@components/video-player/Player.astro"
---
<Base>
<Player
Video="http://catactyl.home.ro:5006/The%20Mark%20On%20The%20Wall/1080.mp4"
Audio="http://catactyl.home.ro:5006/The%20Mark%20On%20The%20Wall/audio.mp4"
/>
</Base>
<style is:global>
.video-container {
width: 90%;
margin: auto;
margin-top: 92px;
}
</style>

View file

@ -1,272 +0,0 @@
---
import { t, changeLanguage } from "i18next"
import Base from "@layouts/Default.astro"
import "@styles/video.scss"
// Configuration
import { DEFAULT_MEDIA_PROXY, DEFAULT_MEDIA_DATA_PROXY, DEFAULT_IMAGE_PROXY, SERVER_DOMAIN } from '@utils/GetConfig'
import { Donate, Download, ShareIos, ThumbsUp, MediaVideo } from "@iconoir/vue"
// Components
import Dialog from '@components/Dialog.astro'
import Video from '@components/VideoItem.astro'
import { Zorn } from "@minpluto/zorn"
// Fetch
const SWV = Astro.url.href.split("watch?v=").pop();
const video = await fetch(DEFAULT_MEDIA_DATA_PROXY + "/api/v1/videos/" + SWV).then((response) => response.json());
const comments = await fetch(DEFAULT_MEDIA_DATA_PROXY + "/api/v1/comments/" + SWV).then((response) => response.json());
const Description = video.description;
const UploadDate = video.published;
const Views = video.viewCount;
const VideoSeconds = video.lengthSeconds;
let DescriptionFormat = Description.replaceAll("\n", " <br/> ");
var CheckComments = console.log(comments) // If not found, disable comment section
if (CheckComments = "{ error: 'Comments not found.' }") {
var EnableComments = false
} else {
var EnableComments = true
}
// Format Published Date
const DateFormat = new Date(UploadDate * 1000).toLocaleDateString();
// Format Video Length
// Thanks to "mingjunlu" for helping out with the time format
new Date(VideoSeconds * 1000)
.toISOString()
.slice(14, 19)
.split(":")
.map(Number)
.join(":");
// Format Views
const ViewsConversion = Intl.NumberFormat("en", { notation: "compact" });
let ViewsFormat = ViewsConversion.format(Views);
// Quality Check
const EightKCheck = await fetch(DEFAULT_MEDIA_DATA_PROXY + '/latest_version?id=' + video.videoId + '&itag=571')
if (EightKCheck.status == 200) {
var EightK = true
} else {
var EightK = false
}
const FourKCheck = await fetch(DEFAULT_MEDIA_DATA_PROXY + '/latest_version?id=' + video.videoId + '&itag=313')
if (FourKCheck.status == 200) {
var FourK = true
} else {
var FourK = false
}
const TenEightyCheck = await fetch(DEFAULT_MEDIA_DATA_PROXY + '/latest_version?id=' + video.videoId + '&itag=303')
if (TenEightyCheck.status == 200) {
var TenEighty = true
} else {
var TenEighty = false
}
const ThreeSixtyCheck = await fetch(DEFAULT_MEDIA_DATA_PROXY + '/latest_version?id=' + video.videoId + '&itag=134')
if (ThreeSixtyCheck.status == 200) {var ThreeSixty = true}
if (EightK === true) { // 571
var Quality = '571'
} else if (FourK === true) { // 313
var Quality = '313'
} else if (TenEighty === true) { // 137
var Quality = '303'
} else if (ThreeSixty === true) { // 134
var Quality = '134'
}
---
<Base Title={video.title}>
<Zorn
Poster={DEFAULT_IMAGE_PROXY + '/https://i.ytimg.com/vi/' + video.videoId + '/maxresdefault.jpg'}
Video={DEFAULT_MEDIA_DATA_PROXY + '/latest_version?id=' + video.videoId + '&itag=' + Quality + '&local=true'}
Audio={DEFAULT_MEDIA_DATA_PROXY + '/latest_version?id=' + video.videoId + '&itag=140'}
VideoAttributes="muted"
AudioAttributes=""
/>
<div class="video-rea">
<div class="rea-details">
<p style="font-weight: bold; font-size: 24px;">{video.title}</p>
<div class="rea-details-start">
<a style="text-decoration: none;" href={video.authorUrl} class="rea-channel">
<img src={DEFAULT_IMAGE_PROXY + "/" + video.authorThumbnails[1].url} />
<div
style="display: flex; flex-direction: column; align-items: left;"
>
<h2 style="margin: 0px; font-weight: bold; font-size: 18px;">{video.author}</h2>
<p style="margin: 0px;" id="subs">
{video.subCountText} Subscribers
</p>
</div>
</a>
<div style="display: flex; flex-direction: row; align-items: left;">
<button onclick="DownloadDialogShow()"><Download /> Download</button>
<button onclick={'location.href = "/embed/' + video.videoId + '/"'}><MediaVideo /> Embed</button>
<button onclick="ShareDialogShow()"><ShareIos /> Share</button>
</div>
</div>
<div class="rea-details-end">
<p id="views">{ViewsFormat} Views - {DateFormat}</p>
<Fragment set:html={DescriptionFormat} />
</div>
</div>
<div style="display: flex; gap: 24px;">
<div class="rea-comments">
<h2>{t("WATCH.COMMENTS")}</h2>
{EnableComments ?
comments.comments.map((comment) => (
<div class="comment">
<img src={DEFAULT_IMAGE_PROXY + "/" + comment.authorThumbnails[0].url}/>
<div>
<p><a href={comment.authorUrl}>{comment.author}</a> - 2 Months Ago</p>
<p>{comment.contentHtml}</p>
<p><ThumbsUp /> {comment.likeCount}</p>
</div>
</div>
))
:
<p>Comments are disabled on this video.</p>
<style is:global>
.rea-comments {
width: 100%;
}
</style>
}
</div>
<div class="rea-recommendations">
<h2>{t("WATCH.RELATED")}</h2>
{
video.recommendedVideos.map((data) => (
<Video
ID={data.videoId}
Title={data.title}
Creator={data.author}
Views={data.viewCount}
UploadDate={data.published}
Length={data.lengthSeconds}
/>
))
}
</div>
</div>
</div>
</Base>
<script is:inline>
function DownloadDialogShow() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function DownloadDialogHide() {
var DownloadDialog = document.querySelector('#dialog-Download')
var BackdropDialog = document.querySelector('.dialog-backdrop')
DownloadDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
function ShareDialogShow() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'flex'
BackdropDialog.style.display = 'inherit'
}
function ShareDialogHide() {
var ShareDialog = document.querySelector('#dialog-Share')
var BackdropDialog = document.querySelector('.dialog-backdrop')
ShareDialog.style.display = 'none'
BackdropDialog.style.display = 'none'
}
</script>
<script is:inline>
// https://gist.github.com/michancio/59b9f3dc54b3ff4f6a84
// Find elements
var SyncVideo = document.querySelector('.main-video');
var SyncAudio = document.querySelector('.main-audio');
// Object for synchronization of multiple media/sources
if (typeof (window.MediaController) === 'function') {
var controller = new MediaController();
SyncVideo.controller = controller;
SyncAudio.controller = controller;
}
else {controller = null}
// Run SyncAudio and SyncVideo simultaneously
SyncVideo.addEventListener('play', function () {
if (!controller && SyncAudio.paused) {
SyncAudio.play();
}
}, false);
// Pause/Play and Buffering
SyncVideo.addEventListener('waiting', () => { // If SyncVideo is buffering
SyncAudio.pause()
});
SyncVideo.addEventListener('playing', () => { // If SyncVideo is done buffering
SyncAudio.play()
SyncTimestamp()
});
SyncVideo.addEventListener('pause', function () {
if (!controller && !SyncAudio.paused) {
SyncAudio.pause();
}
}, false);
// When Media Ends
SyncVideo.addEventListener('ended', function () {
if (controller) {
controller.pause();
}
else {
SyncVideo.pause();
SyncAudio.pause();
}
}, false);
// Seekbar
function SyncTimestamp() {SyncAudio.currentTime = SyncVideo.currentTime}
</script>
<style is:global lang="scss">
.video-container {
max-width: 100%;
margin: 48px 24px 23px 24px;
}
.video-item {
margin-bottom: 24px;
}
.video-controls, video {
border-radius: 12px;
}
.hidden {
display: none;
}
.dialog-downloads-list {
display: grid;
grid-gap: 12px;
a {
background: rgb(51 51 51);
border: 2px rgba(255,255,255,0.05) solid;
font-size: 18px;
text-decoration: none;
border-radius: 4px;
padding: 9px 16px;
}
}
</style>

Binary file not shown.

Binary file not shown.

View file

@ -1,86 +0,0 @@
{
"GLOBAL": {
"MINPLUTO": "MinPluto"
},
"SIDEBAR": {
"HOME": "Home",
"TRENDING": "Trending",
"CATEGORY_LIST": {
"POPULAR": "Popular",
"TRAILERS": "Trailers",
"MUSIC": "Music",
"GAMES": "Games"
},
"DISCOVER": "Discover",
"DISCOVER_LIST": {
"TECH": "Tech",
"COMEDY": "Comedy",
"FOOD": "Food",
"GAMES": "Games",
"FITNESS": "Fitness"
},
"FOOTER": {
"ALPHA": "You're in Alpha",
"STATUS": "Status",
"FORUM": "Forum",
"SOURCE_CODE": "Source Code"
},
"ACCOUNT": {
"CUSTOMIZATION": "",
"VIDEO_PLAYER": "",
"CSS/JS": "",
"ACCOUNT": "",
"RESET": "",
"BACKUP_RESTORE": "",
"DELETE_ACCOUNT": "",
"HEADERS": {
"SETTINGS": "Settings",
"YOU": "You",
"ADVANCE": "Advance"
}
}
},
"HEADER": {
"SEARCH": "Search",
"FEEDBACK": "Feedback"
},
"FORM": {
"EMAIL": "Email",
"PASSWORD": "Password",
"USERNAME": "Username",
"CREATE_ACCOUNT": "Create Account",
"FORGOT_PASSWORD": "Forgot Password",
"LOGIN": "Login",
"REGISTER": "Register",
"UPDATE": "Update",
"SUBMIT": "Submit"
},
"TELEMTRY": {
"OPTIN": "Opt-In",
"OPTOUT": "Opt-Out"
},
"HOME": {
"P1": "Select a category or search something to get started.",
"P2": "Currently, MinPluto is in alpha. Features are currently barebones or may not work as expected."
},
"WATCH": {
"BY": "By",
"VIEWS": "Views",
"RELATED": "Related Videos",
"COMMENTS": "Comments"
},
"CHANNELS": {
"FAMILY_FRIENDLY": "Family Friendly",
"HOME": "Home",
"VIDEOS": "Videos",
"COMMUNITY": "Community",
"LATEST": "Latest Videos",
"ABOUT": "About"
},
"MUSIC": {
"TITLE": "Title",
"ARTIST": "Artist",
"UPLOADED": "Uploaded",
"DURATION": "Duration"
}
}

View file

@ -1,33 +0,0 @@
{
"name": "MinPluto",
"short_name": "MinPluto",
"description": "An open source frontend alternative to YouTube.",
"start_url": "/",
"display": "standalone",
"background_color": "#000000",
"theme_color": "#000000",
"categories": [
"entertainment",
"music"
],
"dir": "ltr",
"orientation": "any",
"display_override": [
"window-controls-overlay"
],
"icons": [
{
"src": "/images/pwa/MinPluto - PWA Logo.png",
"sizes": "512x512",
"type": "image/png"
}
],
"screenshots": [
{
"src": "/images/pwa/Mobile.png",
"type": "image/png",
"sizes": "527x1004",
"form_factor": "narrow"
}
]
}

View file

@ -1,193 +0,0 @@
var VideoContainer = document.querySelector('.video-container')
var VideoControls = document.querySelector('.video-controls')
var Player = document.querySelector('video')
// Icons
var play_solid_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff" stroke-width="1.5"><path d="M6.90588 4.53682C6.50592 4.2998 6 4.58808 6 5.05299V18.947C6 19.4119 6.50592 19.7002 6.90588 19.4632L18.629 12.5162C19.0211 12.2838 19.0211 11.7162 18.629 11.4838L6.90588 4.53682Z" fill="#ffffff" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>';
var pause_solid_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff" stroke-width="1.5" data-darkreader-inline-color="" style="--darkreader-inline-color: #e8e6e3;"><path d="M6 18.4V5.6C6 5.26863 6.26863 5 6.6 5H9.4C9.73137 5 10 5.26863 10 5.6V18.4C10 18.7314 9.73137 19 9.4 19H6.6C6.26863 19 6 18.7314 6 18.4Z" fill="#ffffff" stroke="#ffffff" stroke-width="1.5" data-darkreader-inline-fill="" data-darkreader-inline-stroke="" style="--darkreader-inline-fill: #ffffff; --darkreader-inline-stroke: #ffffff;"></path><path d="M14 18.4V5.6C14 5.26863 14.2686 5 14.6 5H17.4C17.7314 5 18 5.26863 18 5.6V18.4C18 18.7314 17.7314 19 17.4 19H14.6C14.2686 19 14 18.7314 14 18.4Z" fill="#ffffff" stroke="#ffffff" stroke-width="1.5" data-darkreader-inline-fill="" data-darkreader-inline-stroke="" style="--darkreader-inline-fill: #ffffff; --darkreader-inline-stroke: #ffffff;"></path></svg>';
var PlayIcon = play_solid_default;
var PauseIcon = pause_solid_default;
// Fullscreen
function Fullscreen() {
const Button_Fullscreen = document.getElementById("vc-fullscreen");
function Toggle_Fullscreen() {
if (document.fullscreenElement) {
document.querySelector('.vc-top').style.opacity = '0'
document.exitFullscreen();
} else if (document.webkitFullscreenElement) {
document.querySelector('.vc-top').style.opacity = '0'
document.webkitExitFullscreen();
} else if (VideoContainer.webkitRequestFullscreen) {
document.querySelector('.vc-top').style.opacity = '1'
VideoContainer.webkitRequestFullscreen();
} else {
document.querySelector('.vc-top').style.opacity = '1'
VideoContainer.requestFullscreen();
}
}
Button_Fullscreen.onclick = Toggle_Fullscreen;
function Update_FullscreenButton() {
if (document.fullscreenElement) {
Button_Fullscreen.setAttribute("data-title", "Exit full screen (f)");
} else {
Button_Fullscreen.setAttribute("data-title", "Full screen (f)");
}
}
Player.addEventListener("dblclick", () => {
Toggle_Fullscreen()
Update_FullscreenButton()
});
}
// Play/Pause
function PlayPause() {
const Button_PlayPause = document.querySelector(".video-controls #vc-playpause");
Button_PlayPause.addEventListener("click", Toggle_PlayPause);
Player.addEventListener("click", Toggle_PlayPause);
Player.addEventListener("play", Update_PlayPauseButton);
Player.addEventListener("pause", Update_PlayPauseButton);
function Toggle_PlayPause() {
if (Player.paused || Player.ended) {
Player.play();
} else {
Player.pause();
}
}
function Update_PlayPauseButton() {
if (Player.paused) {
Button_PlayPause.setAttribute("data-title", "Play (K)");
Button_PlayPause.innerHTML = `${PlayIcon}`;
} else {
Button_PlayPause.setAttribute("data-title", "Pause (K)");
Button_PlayPause.innerHTML = `${PauseIcon}`;
}
}
}
// Skip Around
function SkipAround() {
const Button_SkipBack = document.querySelector(".video-controls #vc-backwards");
const Button_SkipForth = document.querySelector(".video-controls #vc-forwards");
Button_SkipBack.addEventListener("click", Toggle_SkipBack);
Button_SkipForth.addEventListener("click", Toggle_SkipForth);
function Toggle_SkipBack() {
Skip(-10);
}
function Toggle_SkipForth() {
Skip(10);
}
function Skip(value) {
Player.currentTime += value;
}
}
// Hide Controls
function AutoToggleControls() {
function Hide_Controls2() {
if (Player.paused) {
return;
} else {
document.querySelector(".video-controls").classList.add("hide");
}
}
function Show_Controls2() {
document.querySelector(".video-controls").classList.remove("hide");
}
VideoControls.addEventListener("mouseenter", Show_Controls2);
VideoControls.addEventListener("mouseleave", Hide_Controls2);
var mouseTimer = null, cursorVisible = true;
function Hide_Cursor() {
mouseTimer = null;
VideoContainer.style.cursor = "none";
cursorVisible = false;
Hide_Controls2();
}
document.onmousemove = function () {
if (mouseTimer) {
window.clearTimeout(mouseTimer);
Show_Controls2();
}
if (!cursorVisible) {
VideoContainer.style.cursor = "default";
cursorVisible = true;
}
mouseTimer = window.setTimeout(Hide_Cursor, 3200);
};
}
// Keyboard Shortcuts
function KeyboardShortcuts(events) {
if (Player.hasAttribute("keyboard-shortcut-fullscreen")) {
var Fullscreen_KeyboardShortcut = Player.getAttribute("keyboard-shortcut-fullscreen");
} else {
var Fullscreen_KeyboardShortcut = "f";
}
if (Player.hasAttribute("keyboard-shortcut-mute")) {
var Mute_KeyboardShortcut = Player.getAttribute("keyboard-shortcut-mute");
} else {
var Mute_KeyboardShortcut = "m";
}
if (Player.hasAttribute("keyboard-shortcut-playpause")) {
var PlayPause_KeyboardShortcut = Player.getAttribute("keyboard-shortcut-playpause");
} else {
var PlayPause_KeyboardShortcut = "k";
}
if (Player.hasAttribute("keyboard-shortcut-skipback")) {
var SkipBack_KeyboardShortcut = Player.getAttribute("keyboard-shortcut-skipback");
} else {
var SkipBack_KeyboardShortcut = "j";
}
if (Player.hasAttribute("keyboard-shortcut-skipforth")) {
var SkipForth_KeyboardShortcut = Player.getAttribute("keyboard-shortcut-skipforth");
} else {
var SkipForth_KeyboardShortcut = "l";
}
function keyboardShortcuts(event) {
const { key } = event;
if (key === PlayPause_KeyboardShortcut) {
if (Player.paused || Player.ended) {
Player.play();
} else {
Player.pause();
}
if (Player.paused) {
Show_Controls();
} else {
setTimeout(() => {
Hide_Controls();
}, 1200);
}
} else if (key === Mute_KeyboardShortcut) {
Player.muted = !Player.muted;
if (Player.muted) {
volume.setAttribute("data-volume", volume.value);
volume.value = 0;
} else {
volume.value = volume.dataset.volume;
}
} else if (key === Fullscreen_KeyboardShortcut) {
if (document.fullscreenElement) {
document.exitFullscreen();
} else if (document.webkitFullscreenElement) {
document.webkitExitFullscreen();
} else if (VideoContainer.webkitRequestFullscreen) {
VideoContainer.webkitRequestFullscreen();
} else {
VideoContainer.requestFullscreen();
}
} else if (key === SkipBack_KeyboardShortcut) {
Player.currentTime += -10;
} else if (key === SkipForth_KeyboardShortcut) {
Player.currentTime += 10;
}
}
document.addEventListener("keyup", keyboardShortcuts);
}
// Init All Functions
AutoToggleControls()
Fullscreen()
KeyboardShortcuts()
PlayPause()
SkipAround()

View file

@ -1,81 +0,0 @@
function Seek() {
var Player = document.querySelector('video')
const timeElapsed = document.getElementById("current");
const duration = document.getElementById("duration");
function formatTime(timeInSeconds) {
const result = new Date(timeInSeconds * 1e3)
.toISOString()
.substr(11, 8);
return {
minutes: result.substr(3, 2),
seconds: result.substr(6, 2),
};
}
function initializeVideo() {
const videoDuration = Math.round(Player.duration);
const time = formatTime(videoDuration);
duration.innerText = `${time.minutes}:${time.seconds}`;
duration.setAttribute(
"datetime",
`${time.minutes}m ${time.seconds}s`,
);
}
Player.addEventListener("loadedmetadata", initializeVideo);
function updateTimeElapsed() {
const time = formatTime(Math.round(Player.currentTime));
timeElapsed.innerText = `${time.minutes}:${time.seconds}`;
timeElapsed.setAttribute(
"datetime",
`${time.minutes}m ${time.seconds}s`,
);
}
Player.addEventListener("timeupdate", updateTimeElapsed);
const progressBar = document.querySelector(".vc-progress-bar");
const seek = document.getElementById("seek");
function initializeVideo() {
const videoDuration = Math.round(Player.duration);
seek.setAttribute("max", videoDuration);
progressBar.setAttribute("max", videoDuration);
const time = formatTime(videoDuration);
duration.innerText = `${time.minutes}:${time.seconds}`;
duration.setAttribute(
"datetime",
`${time.minutes}m ${time.seconds}s`,
);
}
function updateProgress() {
seek.value = Math.floor(Player.currentTime);
document.querySelector('.vc-progress-bar').style.width = Player.currentTime / Player.duration * 100 + '%'
}
Player.addEventListener("timeupdate", updateProgress);
const seekTooltip = document.getElementById("seek-tooltip");
function updateSeekTooltip(event) {
const skipTo = Math.round(
(event.offsetX / event.target.clientWidth) *
parseInt(event.target.getAttribute("max"), 10),
);
seek.setAttribute("data-seek", skipTo);
const t = formatTime(skipTo);
seekTooltip.textContent = `${t.minutes}:${t.seconds}`;
const rect = Player.getBoundingClientRect();
seekTooltip.style.left = `${event.pageX - rect.left}px`;
seekTooltip.style.opacity = '1'
document.querySelector('.vc-progress-bar').style.width = Player.currentTime / Player.duration * 100 + '%'
seek.addEventListener('mouseleave', () => {
seekTooltip.style.opacity = '0'
})
}
seek.addEventListener("mousemove", updateSeekTooltip);
function skipAhead(event) {
const skipTo = event.target.dataset.seek
? event.target.dataset.seek
: event.target.value;
Player.currentTime = skipTo;
progressBar.value = skipTo;
seek.value = skipTo;
}
seek.addEventListener("input", skipAhead);
initializeVideo();
}
Seek();

View file

@ -1,64 +0,0 @@
// https://gist.github.com/michancio/59b9f3dc54b3ff4f6a84
// Find elements
var SyncVideo = document.querySelector(".main-video");
var SyncAudio = document.querySelector(".main-audio");
// Object for synchronization of multiple media/sources
if (typeof window.MediaController === "function") {
var controller = new MediaController();
SyncVideo.controller = controller;
SyncAudio.controller = controller;
} else {
controller = null;
}
// Run SyncAudio and SyncVideo simultaneously
SyncVideo.addEventListener(
"play",
function () {
if (!controller && SyncAudio.paused) {
SyncAudio.play();
}
},
false,
);
// Pause/Play and Buffering
SyncVideo.addEventListener("waiting", () => {
// If SyncVideo is buffering
SyncAudio.pause();
});
SyncVideo.addEventListener("playing", () => {
// If SyncVideo is done buffering
SyncAudio.play();
SyncTimestamp();
});
SyncVideo.addEventListener(
"pause",
function () {
if (!controller && !SyncAudio.paused) {
SyncAudio.pause();
}
},
false,
);
// When Media Ends
SyncVideo.addEventListener(
"ended",
function () {
if (controller) {
controller.pause();
} else {
SyncVideo.pause();
SyncAudio.pause();
}
},
false,
);
// Seekbar
function SyncTimestamp() {
SyncAudio.currentTime = SyncVideo.currentTime;
}

View file

@ -1,9 +0,0 @@
User-agent: Applebot
User-agent: Googlebot
User-agent: bingbot
User-agent: Yandex
User-agent: Yeti
User-agent: Baiduspider
User-agent: 360Spider
User-agent: *
Disallow: /

View file

@ -1,2 +0,0 @@
"use strict";var r=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var h=Object.getOwnPropertyNames;var w=Object.prototype.hasOwnProperty;var y=(e,n)=>{for(var t in n)r(e,t,{get:n[t],enumerable:!0})},S=(e,n,t,i)=>{if(n&&typeof n=="object"||typeof n=="function")for(let s of h(n))!w.call(e,s)&&s!==t&&r(e,s,{get:()=>n[s],enumerable:!(i=v(n,s))||i.enumerable});return e};var I=e=>S(r({},"__esModule",{value:!0}),e);var k={};y(k,{init:()=>T,trackEvent:()=>$});module.exports=I(k);var A=V(),E=N(),D=typeof window<"u"&&typeof window.fetch<"u",O=typeof chrome<"u"&&!!chrome.runtime?.id,p=u(),d=new Date,l={US:"https://us.aptabase.com",EU:"https://eu.aptabase.com",DEV:"http://localhost:3000",SH:""};function c(e){let n=new Date,t=n.getTime()-d.getTime();return Math.floor(t/1e3)>e&&(p=u()),d=n,p}function u(){let e=Math.floor(Date.now()/1e3).toString(),n=Math.floor(Math.random()*1e8).toString().padStart(8,"0");return e+n}function g(e){let n=e.split("-");return n.length!==3||l[n[1]]===void 0?(console.warn(`The Aptabase App Key "${e}" is invalid. Tracking will be disabled.`),!1):!0}function f(e,n){let t=e.split("-")[1];if(t==="SH"){if(!n?.host){console.warn("Host parameter must be defined when using Self-Hosted App Key. Tracking will be disabled.");return}return`${n.host}/api/v0/event`}return`${n?.host??l[t]}/api/v0/event`}async function b(e){if(!D&&!O){console.warn(`Aptabase: trackEvent requires a browser environment. Event "${e.eventName}" will be discarded.`);return}if(!e.appKey){console.warn(`Aptabase: init must be called before trackEvent. Event "${e.eventName}" will be discarded.`);return}try{let n=await fetch(e.apiUrl,{method:"POST",headers:{"Content-Type":"application/json","App-Key":e.appKey},credentials:"omit",body:JSON.stringify({timestamp:new Date().toISOString(),sessionId:e.sessionId,eventName:e.eventName,systemProps:{locale:e.locale??A,isDebug:e.isDebug??E,appVersion:e.appVersion??"",sdkVersion:e.sdkVersion},props:e.props})});if(n.status>=300){let t=await n.text();console.warn(`Failed to send event "${e.eventName}": ${n.status} ${t}`)}}catch(n){console.warn(`Failed to send event "${e.eventName}"`),console.warn(n)}}function V(){if(!(typeof navigator>"u"))return navigator.languages.length>0?navigator.languages[0]:navigator.language}function N(){return process.env.NODE_ENV==="development"?!0:typeof location>"u"?!1:location.hostname==="localhost"}var x=1*60*60,U="aptabase-web@0.4.2",m="",o,a;function T(e,n){g(e)&&(o=n?.apiUrl??f(e,n),m=e,a=n)}async function $(e,n){if(!o)return;let t=c(x);await b({apiUrl:o,sessionId:t,appKey:m,isDebug:a?.isDebug,appVersion:a?.appVersion,sdkVersion:U,eventName:e,props:n})}0&&(module.exports={init,trackEvent});
//# sourceMappingURL=index.cjs.map

File diff suppressed because one or more lines are too long

View file

@ -1,11 +0,0 @@
type AptabaseOptions = {
host?: string;
apiUrl?: string;
appVersion?: string;
isDebug?: boolean;
};
declare function init(appKey: string, options?: AptabaseOptions): void;
declare function trackEvent(eventName: string, props?: Record<string, string | number | boolean>): Promise<void>;
export { AptabaseOptions, init, trackEvent };

View file

@ -1,11 +0,0 @@
type AptabaseOptions = {
host?: string;
apiUrl?: string;
appVersion?: string;
isDebug?: boolean;
};
declare function init(appKey: string, options?: AptabaseOptions): void;
declare function trackEvent(eventName: string, props?: Record<string, string | number | boolean>): Promise<void>;
export { AptabaseOptions, init, trackEvent };

View file

@ -1,2 +0,0 @@
var b=w(),m=y(),v=typeof window<"u"&&typeof window.fetch<"u",h=typeof chrome<"u"&&!!chrome.runtime?.id,r=d(),o=new Date,a={US:"https://us.aptabase.com",EU:"https://eu.aptabase.com",DEV:"http://localhost:3000",SH:""};function p(e){let n=new Date,t=n.getTime()-o.getTime();return Math.floor(t/1e3)>e&&(r=d()),o=n,r}function d(){let e=Math.floor(Date.now()/1e3).toString(),n=Math.floor(Math.random()*1e8).toString().padStart(8,"0");return e+n}function l(e){let n=e.split("-");return n.length!==3||a[n[1]]===void 0?(console.warn(`The Aptabase App Key "${e}" is invalid. Tracking will be disabled.`),!1):!0}function c(e,n){let t=e.split("-")[1];if(t==="SH"){if(!n?.host){console.warn("Host parameter must be defined when using Self-Hosted App Key. Tracking will be disabled.");return}return`${n.host}/api/v0/event`}return`${n?.host??a[t]}/api/v0/event`}async function u(e){if(!v&&!h){console.warn(`Aptabase: trackEvent requires a browser environment. Event "${e.eventName}" will be discarded.`);return}if(!e.appKey){console.warn(`Aptabase: init must be called before trackEvent. Event "${e.eventName}" will be discarded.`);return}try{let n=await fetch(e.apiUrl,{method:"POST",headers:{"Content-Type":"application/json","App-Key":e.appKey},credentials:"omit",body:JSON.stringify({timestamp:new Date().toISOString(),sessionId:e.sessionId,eventName:e.eventName,systemProps:{locale:e.locale??b,isDebug:e.isDebug??m,appVersion:e.appVersion??"",sdkVersion:e.sdkVersion},props:e.props})});if(n.status>=300){let t=await n.text();console.warn(`Failed to send event "${e.eventName}": ${n.status} ${t}`)}}catch(n){console.warn(`Failed to send event "${e.eventName}"`),console.warn(n)}}function w(){if(!(typeof navigator>"u"))return navigator.languages.length>0?navigator.languages[0]:navigator.language}function y(){return process.env.NODE_ENV==="development"?!0:typeof location>"u"?!1:location.hostname==="localhost"}var S=1*60*60,I="aptabase-web@0.4.2",g="",s,i;function D(e,n){l(e)&&(s=n?.apiUrl??c(e,n),g=e,i=n)}async function O(e,n){if(!s)return;let t=p(S);await u({apiUrl:s,sessionId:t,appKey:g,isDebug:i?.isDebug,appVersion:i?.appVersion,sdkVersion:I,eventName:e,props:n})}export{D as init,O as trackEvent};
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View file

@ -1,778 +0,0 @@
(() => {
// src/assets/icons/play-solid.svg
var play_solid_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff" stroke-width="1.5"><path d="M6.90588 4.53682C6.50592 4.2998 6 4.58808 6 5.05299V18.947C6 19.4119 6.50592 19.7002 6.90588 19.4632L18.629 12.5162C19.0211 12.2838 19.0211 11.7162 18.629 11.4838L6.90588 4.53682Z" fill="#ffffff" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>';
// src/assets/icons/pause-solid.svg
var pause_solid_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff" stroke-width="1.5" data-darkreader-inline-color="" style="--darkreader-inline-color: #e8e6e3;"><path d="M6 18.4V5.6C6 5.26863 6.26863 5 6.6 5H9.4C9.73137 5 10 5.26863 10 5.6V18.4C10 18.7314 9.73137 19 9.4 19H6.6C6.26863 19 6 18.7314 6 18.4Z" fill="#ffffff" stroke="#ffffff" stroke-width="1.5" data-darkreader-inline-fill="" data-darkreader-inline-stroke="" style="--darkreader-inline-fill: #ffffff; --darkreader-inline-stroke: #ffffff;"></path><path d="M14 18.4V5.6C14 5.26863 14.2686 5 14.6 5H17.4C17.7314 5 18 5.26863 18 5.6V18.4C18 18.7314 17.7314 19 17.4 19H14.6C14.2686 19 14 18.7314 14 18.4Z" fill="#ffffff" stroke="#ffffff" stroke-width="1.5" data-darkreader-inline-fill="" data-darkreader-inline-stroke="" style="--darkreader-inline-fill: #ffffff; --darkreader-inline-stroke: #ffffff;"></path></svg>';
// src/assets/icons/maximize.svg
var maximize_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff"><path d="M7 4H4V7" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M17 4H20V7" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M7 20H4V17" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M17 20H20V17" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>';
// src/assets/icons/closed-captions-tag.svg
var closed_captions_tag_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff" data-darkreader-inline-color="" style="--darkreader-inline-color: #e8e6e3;"><path d="M1 15V9C1 5.68629 3.68629 3 7 3H17C20.3137 3 23 5.68629 23 9V15C23 18.3137 20.3137 21 17 21H7C3.68629 21 1 18.3137 1 15Z" stroke="#ffffff" stroke-width="1.5" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path><path d="M10.5 10L10.3284 9.82843C9.79799 9.29799 9.07857 9 8.32843 9V9C6.76633 9 5.5 10.2663 5.5 11.8284V12.1716C5.5 13.7337 6.76633 15 8.32843 15V15C9.07857 15 9.79799 14.702 10.3284 14.1716L10.5 14" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path><path d="M18.5 10L18.3284 9.82843C17.798 9.29799 17.0786 9 16.3284 9V9C14.7663 9 13.5 10.2663 13.5 11.8284V12.1716C13.5 13.7337 14.7663 15 16.3284 15V15C17.0786 15 17.798 14.702 18.3284 14.1716L18.5 14" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path></svg>';
// src/assets/icons/backward15-seconds.svg
var backward15_seconds_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" stroke-width="1.5" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff"><path d="M3 13C3 17.9706 7.02944 22 12 22C16.9706 22 21 17.9706 21 13C21 8.02944 16.9706 4 12 4" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M9 9L9 16" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M15 9L13 9C12.4477 9 12 9.44772 12 10L12 11.5C12 12.0523 12.4477 12.5 13 12.5L14 12.5C14.5523 12.5 15 12.9477 15 13.5L15 15C15 15.5523 14.5523 16 14 16L12 16" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M12 4L4.5 4M4.5 4L6.5 2M4.5 4L6.5 6" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>';
// src/assets/icons/forward15-seconds.svg
var forward15_seconds_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" stroke-width="1.5" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff"><path d="M21 13C21 17.9706 16.9706 22 12 22C7.02944 22 3 17.9706 3 13C3 8.02944 7.02944 4 12 4" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M12 4H19.5M19.5 4L17.5 2M19.5 4L17.5 6" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M9 9L9 16" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M15 9L13 9C12.4477 9 12 9.44772 12 10L12 11.5C12 12.0523 12.4477 12.5 13 12.5L14 12.5C14.5523 12.5 15 12.9477 15 13.5L15 15C15 15.5523 14.5523 16 14 16L12 16" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>';
// src/assets/icons/sound-high.svg
var sound_high_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff" data-darkreader-inline-color="" style="--darkreader-inline-color: #e8e6e3;"><path d="M1 13.8571V10.1429C1 9.03829 1.89543 8.14286 3 8.14286H5.9C6.09569 8.14286 6.28708 8.08544 6.45046 7.97772L12.4495 4.02228C13.1144 3.5839 14 4.06075 14 4.85714V19.1429C14 19.9392 13.1144 20.4161 12.4495 19.9777L6.45046 16.0223C6.28708 15.9146 6.09569 15.8571 5.9 15.8571H3C1.89543 15.8571 1 14.9617 1 13.8571Z" stroke="#ffffff" stroke-width="1.5" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path><path d="M17.5 7.5C17.5 7.5 19 9 19 11.5C19 14 17.5 15.5 17.5 15.5" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path><path d="M20.5 4.5C20.5 4.5 23 7 23 11.5C23 16 20.5 18.5 20.5 18.5" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path></svg>';
// src/assets/icons/sound-min.svg
var sound_min_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff" data-darkreader-inline-color="" style="--darkreader-inline-color: #e8e6e3;"><path d="M3.5 13.8571V10.1429C3.5 9.03829 4.39543 8.14286 5.5 8.14286H8.4C8.59569 8.14286 8.78708 8.08544 8.95046 7.97772L14.9495 4.02228C15.6144 3.5839 16.5 4.06075 16.5 4.85714V19.1429C16.5 19.9392 15.6144 20.4161 14.9495 19.9777L8.95046 16.0223C8.78708 15.9146 8.59569 15.8571 8.4 15.8571H5.5C4.39543 15.8571 3.5 14.9617 3.5 13.8571Z" stroke="#ffffff" stroke-width="1.5" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path><path d="M20.5 15L20.5 9" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path></svg>';
// src/assets/icons/sound-off.svg
var sound_off_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff" data-darkreader-inline-color="" style="--darkreader-inline-color: #e8e6e3;"><path d="M18 14L20.0005 12M22 10L20.0005 12M20.0005 12L18 10M20.0005 12L22 14" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path><path d="M2 13.8571V10.1429C2 9.03829 2.89543 8.14286 4 8.14286H6.9C7.09569 8.14286 7.28708 8.08544 7.45046 7.97772L13.4495 4.02228C14.1144 3.5839 15 4.06075 15 4.85714V19.1429C15 19.9392 14.1144 20.4161 13.4495 19.9777L7.45046 16.0223C7.28708 15.9146 7.09569 15.8571 6.9 15.8571H4C2.89543 15.8571 2 14.9617 2 13.8571Z" stroke="#ffffff" stroke-width="1.5" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path></svg>';
// src/assets/icons/refresh-double.svg
var refresh_double_default = '<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#ffffff" data-darkreader-inline-color="" style="--darkreader-inline-color: #e8e6e3;"><path d="M21.1679 8C19.6247 4.46819 16.1006 2 11.9999 2C6.81459 2 2.55104 5.94668 2.04932 11" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path><path d="M17 8H21.4C21.7314 8 22 7.73137 22 7.4V3" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path><path d="M2.88146 16C4.42458 19.5318 7.94874 22 12.0494 22C17.2347 22 21.4983 18.0533 22 13" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path><path d="M7.04932 16H2.64932C2.31795 16 2.04932 16.2686 2.04932 16.6V21" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: #ffffff;"></path></svg>';
// src/get.js
var ZornVideoPlayer = document.querySelector(".zorn-player");
var VideoContainer = document.querySelector(".video-container");
var VideoControls = document.querySelector(".zorn-player-controls");
var PlayIcon = play_solid_default;
var PauseIcon = pause_solid_default;
var FullcreenIcon = maximize_default;
var CaptionsIcon = closed_captions_tag_default;
var Backward15Icon = backward15_seconds_default;
var Forward15Icon = forward15_seconds_default;
var VolumeHighIcon = sound_high_default;
var VolumeMinIcon = sound_min_default;
var VolumeOffIcon = sound_off_default;
var RefreshIcon = refresh_double_default;
// src/themes/default.js
function Title() {
let VideoTitle = ZornVideoPlayer.getAttribute("video-title");
document.querySelector(".zorn-title").innerHTML = VideoTitle;
if (ZornVideoPlayer.hasAttribute("video-title")) {
document.querySelector(".zorn-title").style.display = "inherit";
} else {
document.querySelector(".zorn-title").style.display = "none";
}
}
var Controls = `
<div oncontextmenu="return false" class="zorn-player-controls">
<div class="row-2">
<div class="video-progress">
<progress id="progress-bar" value="0" min="0"></progress>
<input class="seek" id="seek" value="0" min="0" type="range" step="1">
<div class="seek-tooltip" id="seek-tooltip">00:00</div>
</div>
</div>
<div class="row-1">
<div class="row-1-start">
<button id="play-pause">${PlayIcon}</button>
<button id="skip-back">${Backward15Icon}</button>
<button id="skip-forth">${Forward15Icon}</button>
<div class="volume-controls">
<button data-title="Mute (m)" class="volume-button" id="volume-button">${VolumeHighIcon}</button>
<input class="volume" id="volume" value="1" type="range" max="1" min="0" step="0.01"/>
</div>
<div class="time">
<time id="time-elapsed">00:00</time>
<span> / </span>
<time id="duration">00:00</time>
</div>
</div>
<div class="row-1-center">
</div>
<div class="row-1-end">
<button id="subtitles">${CaptionsIcon}</button>
<button id="fullscreen">${FullcreenIcon}</button>
</div>
</div>
</div>
<style>
:root {
--zorn-progress-bar-bg: rgba(100, 100, 100, 0.5);
--zorn-progress-bar: rgba(255, 0, 0, 0.5);
--zorn-thumb: red;
--zorn-rounded-corners: 4px;
}
.zorn-context-menu {
background: linear-gradient(45deg, #0a141c 0%, rgba(10, 20, 28, 1) 100%);
border-radius: 6px;
border: 1px rgba(255, 255, 255, 0.08) solid;
}
.zorn-context-menu ul {
list-style: none;
margin: 0px;
padding: 0px;
}
.zorn-context-menu ul li {
padding: 8px 32px 8px 16px;
margin: 4px;
border-radius: 4px;
font-family: arial;
}
.zorn-context-menu ul li:hover {
background: rgba(255, 255, 255, 0.1);
cursor: pointer;
}
.zorn-context-menu ul li i {
font-size: 14px;
margin-right: 12px;
width: 12px;
}
.video-container {
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
font-family: Arial, Helvetica, sans-serif;
color: white;
}
.video-container .zorn-player-dialogs #buffering {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 0px 24px;
border-radius: 6px;
}
.video-container .zorn-player-dialogs #buffering h2 {
font-size: 52px;
}
.video-container .zorn-player-dialogs #invalid-src {
display: none;
position: absolute;
top: 50%;
left: 24px;
transform: translate(0px, -50%);
padding: 0px 24px;
border-radius: 6px;
}
.video-container .zorn-player-dialogs #invalid-src h2 {
font-size: 52px;
}
.video-container .zorn-player {
display: inline-flex;
}
.video-container .zorn-title {
position: absolute;
top: 0px;
background: rgb(0 0 0 / 50%);
width: auto;
margin: 12px;
padding: 12px 24px;
border-radius: 10px;
font-size: 18px;
}
.video-container .hide {
opacity: 0;
pointer-events: none;
}
.video-container .zorn-player-title {
position: absolute;
top: 0px;
width: 100%;
background-image: linear-gradient(to top, rgba(12, 12, 12, 0), rgba(12, 12, 12, 0.75));
padding: 12px 24px;
font-size: 18px;
font-weight: bold;
}
.video-container .subtitles-menu {
display: none;
position: absolute;
right: 60px;
bottom: 70px;
background: #000 9;
list-style: none;
padding: 6px;
border-radius: 6px;
background: rgb(0 0 0 / 50%);
}
.video-container .subtitles-menu button {
background-color: transparent;
color: white;
border: none;
border-radius: 4px;
width: 100%;
text-align: left;
padding: 6px 12px;
cursor: pointer;
}
.video-container .subtitles-menu button:hover {
background: #fff 29;
}
.video-container .subtitles-menu .hide {
opacity: 0;
pointer-events: none;
}
.video-container .zorn-player-controls {
display: inline-flex;
right: 0;
left: 0;
padding: 10px;
position: absolute;
bottom: -1px;
transition: all 0.2s ease;
background-image: linear-gradient(to bottom, rgba(12, 12, 12, 0), rgba(12, 12, 12, 0.75));
flex-direction: inherit;
}
.video-container .zorn-player-controls .row-1 {
display: flex;
justify-content: space-between;
width: 100%;
}
.video-container .zorn-player-controls .row-1-start {
display: flex;
align-items: center;
}
.video-container .zorn-player-controls .row-1-center {
display: flex;
justify-content: center;
}
.video-container .zorn-player-controls .row-1-end {
display: flex;
align-items: center;
}
.video-container .zorn-player-controls button {
aspect-ratio: 1;
height: 32px;
width: 32px;
color: white;
background-color: transparent;
border: none;
margin: 0px 6px;
}
.volume-controls {
display: flex;
align-items: center;
}
.video-container .zorn-player-controls .volume-controls:hover > #volume {
opacity: 1;
transition: 0.3s opacity, 0.3s width;
margin: 0px;
width: 72px;
}
.video-container .zorn-player-controls #volume {
opacity: 0;
transition: 0.3s opacity, 0.3s width;
margin: 0px 12px 0px -6px;
width: 0px;
}
.video-container .zorn-player-controls #volume-button svg {
aspect-ratio: 1;
height: 16px;
width: 16px;
fill: white;
padding: 3px 0px 0px 0px;
}
.video-container .zorn-player-controls .video-progress {
position: relative;
height: 6.4px;
margin: 24px 0px;
width: 100%;
}
.video-container .zorn-player-controls progress {
border-radius: 1rem;
width: 100%;
height: 8.4px;
position: absolute;
top: 0;
}
.video-container .zorn-player-controls progress::-webkit-progress-bar {
border-radius: 1rem;
background: var(--zorn-progress-bar-bg);
}
.video-container .zorn-player-controls progress::-webkit-progress-value {
background: var(--zorn-progress-bar);
border-radius: 1rem;
}
.video-container .zorn-player-controls progress::-moz-progress-bar {
border-radius: 1rem;
background: var(--zorn-progress-bar-bg);
}
.video-container .zorn-player-controls .seek {
position: absolute;
top: 0;
width: 100%;
cursor: pointer;
margin: 0;
}
.video-container .zorn-player-controls .seek:hover + .seek-tooltip {
display: block;
}
.video-container .zorn-player-controls .seek-tooltip {
display: none;
position: relative;
top: -32px;
margin-left: -30px;
font-size: 12px;
content: attr(data-title);
font-weight: bold;
color: #fff;
background-color: rgba(0, 0, 0, .5);
border-radius: 4px;
padding: 6px;
width: fit-content;
}
.video-container .zorn-player-controls input[type=range] {
height: 8.4px;
background: transparent;
cursor: pointer;
opacity: 0;
}
.video-container .zorn-player-controls input[type=range]:focus {
outline: none;
}
.video-container .zorn-player-controls input[type=range]:focus::-webkit-slider-runnable-track {
background: transparent;
}
.video-container .zorn-player-controls input[type=range]:focus::-moz-range-track {
outline: none;
}
.video-container .zorn-player-controls input[type=range]::-webkit-slider-runnable-track {
width: 100%;
cursor: pointer;
border-radius: 1.3px;
-webkit-appearance: none;
transition: all 0.4s ease;
}
.video-container .zorn-player-controls input[type=range]::-webkit-slider-thumb {
height: 12px;
width: 12px;
border-radius: 10px;
background: var(--zorn-thumb);
cursor: pointer;
-webkit-appearance: none;
margin-left: -1px;
}
.video-container .zorn-player-controls input[type=range]::-moz-range-track {
width: 100%;
height: 8.4px;
cursor: pointer;
border: 1px solid transparent;
background: transparent;
border-radius: 0;
}
.video-container .zorn-player-controls input[type=range].volume {
height: 5px;
background-color: #fff;
}
.video-container .zorn-player-controls input[type=range].volume::-webkit-slider-runnable-track {
background-color: transparent;
}
.video-container .zorn-player-controls input[type=range].volume::-webkit-slider-thumb {
margin-left: 0;
height: 14px;
width: 14px;
background: #fff;
}
.video-container .zorn-player-controls input[type=range].volume::-moz-range-thumb {
border: 1px solid #fff;
background: #fff;
}
.video-container .zorn-player-controls input[type="range"]::-moz-range-thumb {
height: 12px;
width: 12px;
border-radius: 10px;
border: none;
background: var(--zorn-thumb);
cursor: pointer;
}
.video-container .zorn-player-controls .hide {
opacity: 0;
pointer-events: none;
}
.video-container .zorn-player-controls #progress-bar {
background: var(--zorn-progress-bar-bg);
border: none;
border-radius: 10px;
}
div#buffering {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 90%;
display: flex;
align-items: center;
justify-content: center;
animation: 1s spin linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg)
}
to {
transform: rotate(360deg)
}
}
</style>
`;
ZornVideoPlayer.insertAdjacentHTML("afterend", Controls);
if (ZornVideoPlayer.getAttribute("layout") === "default") {
Title();
}
// src/events.js
function Events() {
ZornVideoPlayer.addEventListener("error", function(event) {
document.querySelector("#invalid-src").style.display = "inherit";
document.querySelector(".zorn-player-controls").style.display = "none";
videoContainer.style.backgroundColor = "#101010";
setTimeout(() => {
ZornVideoPlayer.style.opacity = "0.10";
document.querySelector("#buffering").style.display = "none";
}, 168);
}, true);
ZornVideoPlayer.onwaiting = (event) => {
document.querySelector("#buffering").style.display = "inherit";
ZornVideoPlayer.style.transition = "5s opacity";
ZornVideoPlayer.style.opacity = "0.25";
};
ZornVideoPlayer.oncanplaythrough = (event) => {
document.querySelector("#buffering").style.display = "none";
ZornVideoPlayer.style.transition = "0.3s opacity";
ZornVideoPlayer.style.opacity = "1";
};
}
// src/functions/PlayPause.js
function PlayPause() {
const Button_PlayPause = document.querySelector(".zorn-player-controls #play-pause");
Button_PlayPause.addEventListener("click", Toggle_PlayPause);
ZornVideoPlayer.addEventListener("click", Toggle_PlayPause);
ZornVideoPlayer.addEventListener("play", Update_PlayPauseButton);
ZornVideoPlayer.addEventListener("pause", Update_PlayPauseButton);
function Toggle_PlayPause() {
if (ZornVideoPlayer.paused || ZornVideoPlayer.ended) {
ZornVideoPlayer.play();
} else {
ZornVideoPlayer.pause();
}
}
function Update_PlayPauseButton() {
if (ZornVideoPlayer.paused) {
Button_PlayPause.setAttribute("data-title", "Play (K)");
Button_PlayPause.innerHTML = `${PlayIcon}`;
} else {
Button_PlayPause.setAttribute("data-title", "Pause (K)");
Button_PlayPause.innerHTML = `${PauseIcon}`;
}
}
}
// src/functions/SkipAround.js
function SkipAround() {
const Button_SkipBack = document.querySelector(".zorn-player-controls #skip-back");
const Button_SkipForth = document.querySelector(".zorn-player-controls #skip-forth");
Button_SkipBack.addEventListener("click", Toggle_SkipBack);
Button_SkipForth.addEventListener("click", Toggle_SkipForth);
function Toggle_SkipBack() {
Skip(-10);
}
function Toggle_SkipForth() {
Skip(10);
}
function Skip(value) {
ZornVideoPlayer.currentTime += value;
}
}
// src/functions/Fullscreen.js
function Fullscreen() {
const Button_Fullscreen = document.getElementById("fullscreen");
function Toggle_Fullscreen() {
if (document.fullscreenElement) {
document.exitFullscreen();
} else if (document.webkitFullscreenElement) {
document.webkitExitFullscreen();
} else if (VideoContainer.webkitRequestFullscreen) {
VideoContainer.webkitRequestFullscreen();
} else {
VideoContainer.requestFullscreen();
}
}
Button_Fullscreen.onclick = Toggle_Fullscreen;
function Update_FullscreenButton() {
if (document.fullscreenElement) {
Button_Fullscreen.setAttribute("data-title", "Exit full screen (f)");
} else {
Button_Fullscreen.setAttribute("data-title", "Full screen (f)");
}
}
ZornVideoPlayer.addEventListener("dblclick", () => {
Toggle_Fullscreen();
});
}
// src/functions/Subtitles.js
function Subtitles() {
var subtitles = document.querySelector('.zorn-player-controls #subtitles')
var subtitleMenuButtons = []
var createMenuItem = function(id, lang, label) {
var listItem = document.createElement('li')
var button = listItem.appendChild(document.createElement('button'))
button.setAttribute('id', id)
button.className = 'subtitles-button'
if (lang.length > 0) button.setAttribute('lang', lang)
button.value = label
button.setAttribute('data-state', 'inactive')
button.appendChild(document.createTextNode(label))
button.addEventListener('click', function(e) {
subtitleMenuButtons.map(function(v, i, a) {
subtitleMenuButtons[i].setAttribute('data-state', 'inactive')
})
var lang = this.getAttribute('lang')
for (var i = 0; i < ZornVideoPlayer.textTracks.length; i++) {
if (ZornVideoPlayer.textTracks[i].language == lang) {
ZornVideoPlayer.textTracks[i].mode = 'showing'
this.setAttribute('data-state', 'active')
}
else {
ZornVideoPlayer.textTracks[i].mode = 'hidden'
}
}
subtitlesMenu.style.display = 'none'
})
subtitleMenuButtons.push(button)
return listItem
}
var subtitlesMenu
if (ZornVideoPlayer.textTracks) {
var df = document.createDocumentFragment()
var subtitlesMenu = df.appendChild(document.createElement('ul'))
subtitlesMenu.className = 'subtitles-menu'
subtitlesMenu.appendChild(createMenuItem('subtitles-off', '', 'Off'))
for (var i = 0; i < ZornVideoPlayer.textTracks.length; i++) {
subtitlesMenu.appendChild(createMenuItem('subtitles-' + ZornVideoPlayer.textTracks[i].language, ZornVideoPlayer.textTracks[i].language, ZornVideoPlayer.textTracks[i].label))
}
VideoContainer.appendChild(subtitlesMenu)
}
subtitles.addEventListener('click', function(e) {
if (subtitlesMenu) {
subtitlesMenu.style.display = (subtitlesMenu.style.display == 'block' ? 'none' : 'block')
}
})
}
// src/functions/Volume.js
function Volume() {
const Button_Volume = document.getElementById("volume-button");
const volume2 = document.getElementById("volume");
function Update_Volme() {
if (ZornVideoPlayer.muted) {
ZornVideoPlayer.muted = false;
}
ZornVideoPlayer.volume = volume2.value;
}
volume2.addEventListener("input", Update_Volme);
function Update_Volume_Icon() {
Button_Volume.setAttribute("data-title", "Mute (M)");
if (ZornVideoPlayer.muted || ZornVideoPlayer.volume === 0) {
Button_Volume.innerHTML = `${VolumeOffIcon}`;
Button_Volume.setAttribute("data-title", "Unmute (M)");
} else if (ZornVideoPlayer.volume > 0 && ZornVideoPlayer.volume <= 0.5) {
Button_Volume.innerHTML = `${VolumeMinIcon}`;
} else {
Button_Volume.innerHTML = `${VolumeHighIcon}`;
}
}
ZornVideoPlayer.addEventListener("volumechange", Update_Volume_Icon);
function Toggle_Mute() {
ZornVideoPlayer.muted = !ZornVideoPlayer.muted;
if (ZornVideoPlayer.muted) {
volume2.setAttribute("data-volume", volume2.value);
volume2.value = 0;
} else {
volume2.value = volume2.dataset.volume;
}
}
Button_Volume.addEventListener("click", Toggle_Mute);
}
// src/functions/Seek.js
function Seek() {
const timeElapsed = document.getElementById("time-elapsed");
const duration = document.getElementById("duration");
function formatTime(timeInSeconds) {
const result = new Date(timeInSeconds * 1e3).toISOString().substr(11, 8);
return {
minutes: result.substr(3, 2),
seconds: result.substr(6, 2)
};
}
;
function initializeVideo() {
const videoDuration = Math.round(ZornVideoPlayer.duration);
const time = formatTime(videoDuration);
duration.innerText = `${time.minutes}:${time.seconds}`;
duration.setAttribute("datetime", `${time.minutes}m ${time.seconds}s`);
}
ZornVideoPlayer.addEventListener("loadedmetadata", initializeVideo);
function updateTimeElapsed() {
const time = formatTime(Math.round(ZornVideoPlayer.currentTime));
timeElapsed.innerText = `${time.minutes}:${time.seconds}`;
timeElapsed.setAttribute("datetime", `${time.minutes}m ${time.seconds}s`);
}
ZornVideoPlayer.addEventListener("timeupdate", updateTimeElapsed);
const progressBar = document.getElementById("progress-bar");
const seek = document.getElementById("seek");
function initializeVideo() {
const videoDuration = Math.round(ZornVideoPlayer.duration);
seek.setAttribute("max", videoDuration);
progressBar.setAttribute("max", videoDuration);
const time = formatTime(videoDuration);
duration.innerText = `${time.minutes}:${time.seconds}`;
duration.setAttribute("datetime", `${time.minutes}m ${time.seconds}s`);
}
function updateProgress() {
seek.value = Math.floor(ZornVideoPlayer.currentTime);
progressBar.value = Math.floor(ZornVideoPlayer.currentTime);
}
ZornVideoPlayer.addEventListener("timeupdate", updateProgress);
const seekTooltip = document.getElementById("seek-tooltip");
function updateSeekTooltip(event) {
const skipTo = Math.round(event.offsetX / event.target.clientWidth * parseInt(event.target.getAttribute("max"), 10));
seek.setAttribute("data-seek", skipTo);
const t = formatTime(skipTo);
seekTooltip.textContent = `${t.minutes}:${t.seconds}`;
const rect = ZornVideoPlayer.getBoundingClientRect();
seekTooltip.style.left = `${event.pageX - rect.left}px`;
}
seek.addEventListener("mousemove", updateSeekTooltip);
function skipAhead(event) {
const skipTo = event.target.dataset.seek ? event.target.dataset.seek : event.target.value;
ZornVideoPlayer.currentTime = skipTo;
progressBar.value = skipTo;
seek.value = skipTo;
}
seek.addEventListener("input", skipAhead);
initializeVideo();
}
// src/dialogs/Buffering.js
var BufferDialog = `
<div id="buffering" class="zorn-dialog">
${RefreshIcon}
</div>
`;
ZornVideoPlayer.insertAdjacentHTML("afterend", BufferDialog);
// src/functions/AutoToggleControls.js
function AutoToggleControls() {
function Hide_Controls2() {
if (ZornVideoPlayer.paused) {
return;
} else {
document.querySelector(".zorn-player-controls").classList.add("hide");
}
}
function Show_Controls2() {
document.querySelector(".zorn-player-controls").classList.remove("hide");
}
ZornVideoPlayer.addEventListener("mouseenter", Show_Controls2);
ZornVideoPlayer.addEventListener("mouseleave", Hide_Controls2);
document.querySelector(".zorn-player-controls").addEventListener("mouseenter", Show_Controls2);
document.querySelector(".zorn-player-controls").addEventListener("mouseleave", Hide_Controls2);
var mouseTimer = null, cursorVisible = true;
function Hide_Cursor() {
mouseTimer = null;
VideoContainer.style.cursor = "none";
cursorVisible = false;
Hide_Controls2();
}
document.onmousemove = function() {
if (mouseTimer) {
window.clearTimeout(mouseTimer);
Show_Controls2();
}
if (!cursorVisible) {
VideoContainer.style.cursor = "default";
cursorVisible = true;
}
mouseTimer = window.setTimeout(Hide_Cursor, 3200);
};
}
// src/functions/KeyboardShortcuts.js
function KeyboardShortcuts(events) {
if (ZornVideoPlayer.hasAttribute("keyboard-shortcut-fullscreen")) {
var Fullscreen_KeyboardShortcut = ZornVideoPlayer.getAttribute("keyboard-shortcut-fullscreen");
} else {
var Fullscreen_KeyboardShortcut = "f";
}
if (ZornVideoPlayer.hasAttribute("keyboard-shortcut-mute")) {
var Mute_KeyboardShortcut = ZornVideoPlayer.getAttribute("keyboard-shortcut-mute");
} else {
var Mute_KeyboardShortcut = "m";
}
if (ZornVideoPlayer.hasAttribute("keyboard-shortcut-playpause")) {
var PlayPause_KeyboardShortcut = ZornVideoPlayer.getAttribute("keyboard-shortcut-playpause");
} else {
var PlayPause_KeyboardShortcut = "k";
}
if (ZornVideoPlayer.hasAttribute("keyboard-shortcut-skipback")) {
var SkipBack_KeyboardShortcut = ZornVideoPlayer.getAttribute("keyboard-shortcut-skipback");
} else {
var SkipBack_KeyboardShortcut = "j";
}
if (ZornVideoPlayer.hasAttribute("keyboard-shortcut-skipforth")) {
var SkipForth_KeyboardShortcut = ZornVideoPlayer.getAttribute("keyboard-shortcut-skipforth");
} else {
var SkipForth_KeyboardShortcut = "l";
}
function keyboardShortcuts(event) {
const { key } = event;
if (key === PlayPause_KeyboardShortcut) {
if (ZornVideoPlayer.paused || ZornVideoPlayer.ended) {
ZornVideoPlayer.play();
} else {
ZornVideoPlayer.pause();
}
if (ZornVideoPlayer.paused) {
Show_Controls();
} else {
setTimeout(() => {
Hide_Controls();
}, 1200);
}
} else if (key === Mute_KeyboardShortcut) {
ZornVideoPlayer.muted = !ZornVideoPlayer.muted;
if (ZornVideoPlayer.muted) {
volume.setAttribute("data-volume", volume.value);
volume.value = 0;
} else {
volume.value = volume.dataset.volume;
}
} else if (key === Fullscreen_KeyboardShortcut) {
if (document.fullscreenElement) {
document.exitFullscreen();
} else if (document.webkitFullscreenElement) {
document.webkitExitFullscreen();
} else if (VideoContainer.webkitRequestFullscreen) {
VideoContainer.webkitRequestFullscreen();
} else {
VideoContainer.requestFullscreen();
}
} else if (key === SkipBack_KeyboardShortcut) {
ZornVideoPlayer.currentTime += -10;
} else if (key === SkipForth_KeyboardShortcut) {
ZornVideoPlayer.currentTime += 10;
}
}
document.addEventListener("keyup", keyboardShortcuts);
}
// src/index.js
Events();
KeyboardShortcuts();
PlayPause();
AutoToggleControls();
SkipAround();
Fullscreen();
Subtitles();
Volume();
Seek();
Buffering();
})();

View file

@ -1,43 +0,0 @@
// Service Worker is used to cache the offline page
// if the end-user were to ever go offline. An offline page
// should show up if their network condition is offline.
// This is best practice for the Progress Web App and
// provides a better user experience.
// Read more on this topic:
// https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/Offline_and_background_operation#offline_operation
'use strict'
var cacheVersion = 1
var currentCache = {
offline: 'offline-cache' + cacheVersion
}
const offlineUrl = 'offline'
this.addEventListener('install', event => {
event.waitUntil(
caches.open(currentCache.offline).then(function (cache) {
return cache.addAll([
offlineUrl
])
})
)
})
this.addEventListener('fetch', event => {
if (event.request.mode === 'navigate' || (event.request.method === 'GET' && event.request.headers.get('accept').includes('text/html'))) {
event.respondWith(
fetch(event.request.url).catch(error => {
return caches.match(offlineUrl)
})
)
}
else {
event.respondWith(caches.match(event.request)
.then(function (response) {
return response || fetch(event.request)
})
)
}
})

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show more