Radio and Files services are deleted.
This commit is contained in:
parent
909c096c21
commit
08e6a17aa1
@ -1,3 +0,0 @@
|
|||||||
exports.host = "0.0.0.0";
|
|
||||||
exports.port = 32202;
|
|
||||||
exports.share_path = "/srv/ftp";
|
|
117
files/index.js
117
files/index.js
@ -1,117 +0,0 @@
|
|||||||
const fs = require("fs");
|
|
||||||
const path = require("path");
|
|
||||||
const { ENOENT, ECONNRESET } = require("constants");
|
|
||||||
|
|
||||||
const Koa = require("koa");
|
|
||||||
const koaPug = require("koa-pug");
|
|
||||||
const koaRouter = require("koa-router");
|
|
||||||
|
|
||||||
const config = require("./config");
|
|
||||||
const util = require("../shared/util");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const SIZE_UNITS = [ "B", "KiB", "MiB", "GiB" ];
|
|
||||||
|
|
||||||
function addFileSizeUnit(size) {
|
|
||||||
let i = 0;
|
|
||||||
for (; size > 1024; size /= 1024) ++i;
|
|
||||||
return [i > 0 ? size.toFixed(2) : size, SIZE_UNITS[i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function buildCurrentPathLink(path) {
|
|
||||||
let currentPath = `<a href="/">root</a>`;
|
|
||||||
|
|
||||||
if (path.endsWith("/"))
|
|
||||||
path = path.slice(1, path.length-1);
|
|
||||||
|
|
||||||
const path_parts = path.split("/");
|
|
||||||
|
|
||||||
for (let i = 0; i < path_parts.length; ++i) {
|
|
||||||
let lnk = "";
|
|
||||||
|
|
||||||
for (let j = 0; j < i+1; ++j)
|
|
||||||
lnk += `/${path_parts[j]}`;
|
|
||||||
|
|
||||||
currentPath += `/<a href="${encodeURI(lnk)}/">${path_parts[i]}</a>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return currentPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortFuncByNameField(a, b) {
|
|
||||||
return (a.name > b.name) ? 1 : (a.name < b.name) ? -1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getDirectoryList(dir_path, orig_url) {
|
|
||||||
let directories = [];
|
|
||||||
let files = [];
|
|
||||||
let total_files_size = 0;
|
|
||||||
let directory = await fs.promises.opendir(dir_path);
|
|
||||||
|
|
||||||
for await (const dirent of directory) {
|
|
||||||
const stat = await fs.promises.stat(path.join(dir_path, dirent.name));
|
|
||||||
const [s, u] = addFileSizeUnit(stat.size);
|
|
||||||
|
|
||||||
if (stat.isDirectory())
|
|
||||||
directories.push({
|
|
||||||
name: dirent.name,
|
|
||||||
link: `${encodeURIComponent(dirent.name)}/`,
|
|
||||||
datetime: stat.mtime,
|
|
||||||
size: "DIR" });
|
|
||||||
else {
|
|
||||||
total_files_size += stat.size;
|
|
||||||
files.push({
|
|
||||||
name: dirent.name,
|
|
||||||
link: `/files${orig_url}${encodeURIComponent(dirent.name)}`,
|
|
||||||
datetime: stat.mtime,
|
|
||||||
size: `${s} ${u}` });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
directories.sort(sortFuncByNameField);
|
|
||||||
files.sort(sortFuncByNameField);
|
|
||||||
|
|
||||||
return [directories.concat(files), directories.length, files.length,
|
|
||||||
addFileSizeUnit(total_files_size).join(' ')];
|
|
||||||
}
|
|
||||||
|
|
||||||
function setRoutes() {
|
|
||||||
return koaRouter().get('/(.*)?', async ctx => {
|
|
||||||
const file_path = path.join(config.share_path, decodeURI(ctx.originalUrl));
|
|
||||||
|
|
||||||
let stat = await fs.promises.stat(file_path);
|
|
||||||
|
|
||||||
if (stat.isDirectory()) {
|
|
||||||
const [items, total_directories, total_files, total_files_size]
|
|
||||||
= await getDirectoryList(file_path, decodeURI(ctx.originalUrl));
|
|
||||||
|
|
||||||
await ctx.render('index', {
|
|
||||||
clientTZ: util.getClientTimezone(ctx),
|
|
||||||
main_site: util.getServiceByHost(ctx.header.host),
|
|
||||||
current_path: buildCurrentPathLink(decodeURI(ctx.originalUrl)),
|
|
||||||
total_files: total_files,
|
|
||||||
total_files_size: total_files_size,
|
|
||||||
total_directories: total_directories,
|
|
||||||
items: items });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const app = new Koa();
|
|
||||||
const pug = new koaPug({
|
|
||||||
viewPath: path.join(__dirname, "views"),
|
|
||||||
locals: {
|
|
||||||
moment: (date, tz) => util.datetime(date, util.date_formats.file_date, tz), },
|
|
||||||
app: app
|
|
||||||
});
|
|
||||||
|
|
||||||
app.proxy = true;
|
|
||||||
app
|
|
||||||
.use(setRoutes().routes())
|
|
||||||
// .on("error", err => { if (!err.code == ECONNRESET || !err.code == ENOENT) console.log(err.code); })
|
|
||||||
.listen(config.port, config.host);
|
|
||||||
|
|
||||||
console.log("Arav's dwelling / Files is up.");
|
|
@ -1,170 +0,0 @@
|
|||||||
@font-face {
|
|
||||||
font-family: 'Roboto Condensed';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: local('RobotoCondensed'), local('RobotoCondensed-Regular'),
|
|
||||||
url(/shared/fonts/RobotoCondensed-Regular.ttf); }
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--background-color: #0a0a0a;
|
|
||||||
--primary-color: #cd2682;
|
|
||||||
--secondary-color: #9f2b68;
|
|
||||||
--text-color: #f5f5f5;
|
|
||||||
--text-indent: 1.6rem;
|
|
||||||
--overlay-background-color: #f5f5f574;
|
|
||||||
scrollbar-color: var(--primary-color) var(--background-color); }
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
|
||||||
:root {
|
|
||||||
--background-color: #f5f5f5;
|
|
||||||
--primary-color: #9f2b68;
|
|
||||||
--secondary-color: #cd2682;
|
|
||||||
--text-color: #0a0a0a;
|
|
||||||
--overlay-background-color: #0a0a0a74; } }
|
|
||||||
|
|
||||||
* { margin: 0; }
|
|
||||||
|
|
||||||
::selection {
|
|
||||||
background-color: var(--secondary-color);
|
|
||||||
color: var(--background-color); }
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
text-decoration: none; }
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: var(--secondary-color);
|
|
||||||
text-decoration: underline;
|
|
||||||
text-decoration-style: dotted;
|
|
||||||
transition: .5s; }
|
|
||||||
|
|
||||||
p {
|
|
||||||
text-align: justify;
|
|
||||||
line-height: var(--text-indent);
|
|
||||||
text-indent: var(--text-indent); }
|
|
||||||
|
|
||||||
p:not(:last-child) { margin-bottom: .1rem; }
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2 {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
font-variant: small-caps;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 1rem; }
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
margin: 1rem 0; }
|
|
||||||
|
|
||||||
html { margin-left: calc(100vw - 100%); }
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: var(--background-color);
|
|
||||||
color: var(--text-color);
|
|
||||||
font-family: 'Roboto Condensed', Roboto, sans-serif;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 960px;
|
|
||||||
width: 98%; }
|
|
||||||
|
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between; }
|
|
||||||
|
|
||||||
#logo {
|
|
||||||
display: block;
|
|
||||||
width: 360px; }
|
|
||||||
|
|
||||||
#logo text { fill: var(--text-color); }
|
|
||||||
|
|
||||||
#logo .logo {
|
|
||||||
font-size: 2rem;
|
|
||||||
font-variant-caps: small-caps;
|
|
||||||
font-weight: bold; }
|
|
||||||
|
|
||||||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
|
||||||
#logo .logo { font-size: 2.082rem; } }
|
|
||||||
|
|
||||||
@-moz-document url-prefix() {
|
|
||||||
#logo .logo { font-size: 2rem; } }
|
|
||||||
|
|
||||||
#logo .under { font-size: .88rem; }
|
|
||||||
|
|
||||||
nav { margin-top: .5rem; }
|
|
||||||
|
|
||||||
nav a { font-variant: small-caps; }
|
|
||||||
|
|
||||||
nav h1 {
|
|
||||||
color: var(--secondary-color);
|
|
||||||
margin: 0; }
|
|
||||||
|
|
||||||
section { margin-top: 1rem; }
|
|
||||||
|
|
||||||
#overlay {
|
|
||||||
align-items: center;
|
|
||||||
background-color: var(--overlay-background-color);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
left: 0;
|
|
||||||
max-height: 100%;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
width: 100%; }
|
|
||||||
|
|
||||||
#overlay video,
|
|
||||||
#overlay audio,
|
|
||||||
#overlay img {
|
|
||||||
margin: auto;
|
|
||||||
max-height: 100%;
|
|
||||||
max-width: 86%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#overlay span {
|
|
||||||
color: var(--background-color);
|
|
||||||
text-shadow: 0 0 .3rem var(--secondary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
table { overflow-y: scroll; width: 100%; }
|
|
||||||
|
|
||||||
tr { vertical-align: top; }
|
|
||||||
|
|
||||||
tr:hover { background-color: var(--primary-color); color: white; }
|
|
||||||
|
|
||||||
tr:hover a { color: white; }
|
|
||||||
|
|
||||||
th { text-align: left; }
|
|
||||||
|
|
||||||
th:nth-child(2),
|
|
||||||
th:last-child { width: 1%; white-space: nowrap; }
|
|
||||||
|
|
||||||
th,
|
|
||||||
td { line-break: strict; }
|
|
||||||
|
|
||||||
th:nth-child(2),
|
|
||||||
td:nth-child(2) { padding: 0 1rem; }
|
|
||||||
|
|
||||||
td a { display: block; width: 100%; }
|
|
||||||
|
|
||||||
td a:hover { transition: none; }
|
|
||||||
|
|
||||||
td:nth-child(2),
|
|
||||||
td:last-child { white-space: nowrap; }
|
|
||||||
|
|
||||||
footer {
|
|
||||||
font-size: .8rem;
|
|
||||||
text-align: center;
|
|
||||||
padding: 1rem 0; }
|
|
||||||
|
|
||||||
@media screen and (max-width: 640px) {
|
|
||||||
header { display: block; }
|
|
||||||
|
|
||||||
#logo {
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 100%; }
|
|
||||||
|
|
||||||
nav {
|
|
||||||
width: 100%;
|
|
||||||
text-align: center; } }
|
|
@ -1,56 +0,0 @@
|
|||||||
const video_formats = ["webm", "mp4"];
|
|
||||||
const audio_formats = ["mp3", "flac", "opus", "ogg", "m4a"];
|
|
||||||
const image_formats = ["jpg", "jpeg", "gif", "png", "bmp", "webp"];
|
|
||||||
|
|
||||||
const overlay = document.getElementById("overlay");
|
|
||||||
let g_scale = 1;
|
|
||||||
let g_volume = 1.0;
|
|
||||||
|
|
||||||
function mousescroll(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
g_scale = Math.min(Math.max(0.25, g_scale + (e.deltaY * -0.001)), 4);
|
|
||||||
e.target.style.transform = `scale(${g_scale})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onvolumechange(e) {
|
|
||||||
g_volume = e.target.volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ext_filter = (ext, pathname) => pathname.toLowerCase().endsWith(ext);
|
|
||||||
|
|
||||||
function to_overlay(eltyp, pathname) {
|
|
||||||
const el = document.createElement(eltyp);
|
|
||||||
const el_label = document.createElement("span");
|
|
||||||
el_label.textContent = decodeURI(pathname.substr(pathname.lastIndexOf("/") + 1));
|
|
||||||
if (eltyp !== "audio") el.addEventListener('wheel', mousescroll);
|
|
||||||
if (eltyp !== "img") {
|
|
||||||
el.autoplay = el.controls = true;
|
|
||||||
el.addEventListener("volumechange", onvolumechange);
|
|
||||||
el.volume = g_volume;
|
|
||||||
}
|
|
||||||
el.src = pathname;
|
|
||||||
overlay.appendChild(el);
|
|
||||||
overlay.appendChild(el_label);
|
|
||||||
overlay.style.visibility = "visible";
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById("overlay").addEventListener("click", e => {
|
|
||||||
e.target.firstChild.remove();
|
|
||||||
e.target.firstChild.remove();
|
|
||||||
e.target.style.visibility = "hidden";
|
|
||||||
g_scale = 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
const file_links = Array.from(document.getElementsByTagName('tr')).slice(2).filter(e => e.lastChild.innerHTML != "DIR").map(l => l.firstChild.firstChild);
|
|
||||||
|
|
||||||
file_links.forEach(f => f.addEventListener('click', e => {
|
|
||||||
const pathname = e.target.pathname;
|
|
||||||
if (video_formats.some(ext => ext_filter(ext, pathname)))
|
|
||||||
to_overlay("video", pathname);
|
|
||||||
else if (audio_formats.some(ext => ext_filter(ext, pathname)))
|
|
||||||
to_overlay("audio", pathname);
|
|
||||||
else if (image_formats.some(ext => ext_filter(ext, pathname)))
|
|
||||||
to_overlay("img", pathname);
|
|
||||||
if (overlay.firstChild != null)
|
|
||||||
e.preventDefault();
|
|
||||||
}));
|
|
@ -1,2 +0,0 @@
|
|||||||
User-agent: *
|
|
||||||
Disallow: /assets/
|
|
@ -1,44 +0,0 @@
|
|||||||
doctype html
|
|
||||||
html(lang='en')
|
|
||||||
head
|
|
||||||
title Arav's dwelling / Files
|
|
||||||
meta(charset='utf-8')
|
|
||||||
meta(http-equiv='X-UA-Compatible' content='IE=edge')
|
|
||||||
meta(name='viewport' content='width=device-width, initial-scale=1.0')
|
|
||||||
meta(name='theme-color' content='#cd2682')
|
|
||||||
meta(name='description' content='My file share.')
|
|
||||||
link(rel='icon' type='image/svg+xml' href='/shared/img/favicon.svg' sizes='any')
|
|
||||||
link(href='/assets/css/main.css' rel='stylesheet')
|
|
||||||
script(src='/assets/js/main.js' defer)
|
|
||||||
body
|
|
||||||
header
|
|
||||||
svg#logo(viewBox='0 -25 216 40')
|
|
||||||
text.logo Arav's dwelling
|
|
||||||
text.under(y='11') Welcome to my sacred place, wanderer
|
|
||||||
nav
|
|
||||||
a(href=main_site) Back to main website
|
|
||||||
h1 Files
|
|
||||||
section#files
|
|
||||||
span#current-path!= current_path
|
|
||||||
p Files: #{total_files} (#{total_files_size}); Directories: #{total_directories}.
|
|
||||||
table
|
|
||||||
thead
|
|
||||||
tr
|
|
||||||
th Name
|
|
||||||
th Date
|
|
||||||
th Size
|
|
||||||
tbody
|
|
||||||
tr
|
|
||||||
td #[a(href="../") ../]
|
|
||||||
each item in items
|
|
||||||
tr
|
|
||||||
td #[a(href=item.link)= item.name]
|
|
||||||
td!= moment(item.datetime, clientTZ)
|
|
||||||
td= item.size
|
|
||||||
section#privacy
|
|
||||||
h2 Privacy statements
|
|
||||||
p I collect access logs that include access date and time, IP-address, User-Agent, referer URL that tells me where have you came from, request that you sent to me. In addition there are GeoIP information added based on your IP-address that includes country, region, and city for my convenience.
|
|
||||||
p This site makes use of JavaScript purely for convenient functionality, like being able to watch video, listen to music, and look images in an overlay without the need to open a file in a new tab or return back.
|
|
||||||
footer
|
|
||||||
| 2017—2022 Arav <#[a(href='mailto:me@arav.top') me@arav.top]>
|
|
||||||
div#overlay
|
|
@ -1,2 +0,0 @@
|
|||||||
exports.port = 32251;
|
|
||||||
exports.host = "127.0.0.1";
|
|
@ -1,79 +0,0 @@
|
|||||||
const fs = require("fs");
|
|
||||||
const path = require("path");
|
|
||||||
const uti = require("util");
|
|
||||||
const exec = uti.promisify(require("child_process").exec);
|
|
||||||
|
|
||||||
const Koa = require("koa");
|
|
||||||
const koaPug = require("koa-pug");
|
|
||||||
const koaRouter = require("koa-router");
|
|
||||||
const fetch = require("node-fetch");
|
|
||||||
const moment = require("moment-timezone");
|
|
||||||
|
|
||||||
const config = require("./config");
|
|
||||||
const util = require("../shared/util");
|
|
||||||
|
|
||||||
async function getRadioStatus() {
|
|
||||||
try {
|
|
||||||
let status = await fetch('http://radio.arav.home.arpa/status-json.xsl').then(r => r.json());
|
|
||||||
return {
|
|
||||||
server_start_iso8601: status.icestats.source.stream_start_iso8601,
|
|
||||||
server_start_date: util.datetime(status.icestats.source.stream_start_iso8601, util.date_formats.post_date),
|
|
||||||
song: `${status.icestats.source.artist} - ${status.icestats.source.title}`,
|
|
||||||
listener_peak: status.icestats.source.listener_peak,
|
|
||||||
listeners: status.icestats.source.listeners
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
return {
|
|
||||||
server_start_iso8601: "n/a",
|
|
||||||
server_start_date: "n/a",
|
|
||||||
song: "n/a",
|
|
||||||
listener_peak: "n/a",
|
|
||||||
listeners: "n/a"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getLastPlayedSongs(count, tz) {
|
|
||||||
try {
|
|
||||||
const { stdout, _ } = await exec(`tail -n${count} /var/log/icecast/playlist.log | head -n-1 | cut -d"|" -f1,4`);
|
|
||||||
let songs = stdout.trim().split("\n");
|
|
||||||
for (let i = 0; i < songs.length; ++i) {
|
|
||||||
let [t, s] = songs[i].split('|');
|
|
||||||
t = moment(t, "DD/MMM/YYYY:HH:mm:ss ZZ").tz(tz).format("HH:mm");
|
|
||||||
let song = s.split(' - ');
|
|
||||||
songs[i] = { "start_time_local": t, "artist": song[0], "title": song[1] };
|
|
||||||
}
|
|
||||||
return songs;
|
|
||||||
} catch {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setRoutes() {
|
|
||||||
return koaRouter().get('/', async ctx => {
|
|
||||||
await ctx.render('index', {
|
|
||||||
main_site: util.getServiceByHost(ctx.header.host),
|
|
||||||
radio_status: await getRadioStatus(),
|
|
||||||
last_songs: await getLastPlayedSongs(11, util.getClientTimezone(ctx))
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.get('/stats', async ctx => {
|
|
||||||
ctx.body = await getRadioStatus();
|
|
||||||
})
|
|
||||||
.get('/lastsong', async ctx => {
|
|
||||||
ctx.body = (await getLastPlayedSongs(2, util.getClientTimezone(ctx)))[0];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const app = new Koa();
|
|
||||||
const pug = new koaPug({
|
|
||||||
viewPath: path.join(__dirname, "views"),
|
|
||||||
locals: {
|
|
||||||
moment: util.datetime },
|
|
||||||
app: app
|
|
||||||
});
|
|
||||||
|
|
||||||
app.proxy = true;
|
|
||||||
app
|
|
||||||
.use(setRoutes().routes())
|
|
||||||
.listen(config.port, config.host);
|
|
@ -1,154 +0,0 @@
|
|||||||
@font-face {
|
|
||||||
font-family: 'Roboto Condensed';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: local('RobotoCondensed'), local('RobotoCondensed-Regular'),
|
|
||||||
url(/shared/fonts/RobotoCondensed-Regular.ttf); }
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--background-color: #0a0a0a;
|
|
||||||
--primary-color: #cd2682;
|
|
||||||
--secondary-color: #9f2b68;
|
|
||||||
--text-color: #f5f5f5;
|
|
||||||
--text-indent: 1.6rem;
|
|
||||||
scrollbar-color: var(--primary-color) var(--background-color); }
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
|
||||||
:root {
|
|
||||||
--background-color: #f5f5f5;
|
|
||||||
--primary-color: #9f2b68;
|
|
||||||
--secondary-color: #cd2682;
|
|
||||||
--text-color: #0a0a0a; } }
|
|
||||||
|
|
||||||
* { margin: 0; }
|
|
||||||
|
|
||||||
::selection {
|
|
||||||
background-color: var(--secondary-color);
|
|
||||||
color: var(--background-color); }
|
|
||||||
|
|
||||||
a,
|
|
||||||
button {
|
|
||||||
color: var(--primary-color);
|
|
||||||
text-decoration: none; }
|
|
||||||
|
|
||||||
a:hover,
|
|
||||||
button:hover {
|
|
||||||
color: var(--secondary-color);
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: underline dotted;
|
|
||||||
transition: .5s; }
|
|
||||||
|
|
||||||
button {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font: inherit;
|
|
||||||
padding: 0; }
|
|
||||||
|
|
||||||
p {
|
|
||||||
text-align: justify;
|
|
||||||
line-height: var(--text-indent);
|
|
||||||
text-indent: var(--text-indent); }
|
|
||||||
|
|
||||||
p:not(:last-child) { margin-bottom: .1rem; }
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2 {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
font-variant: small-caps;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 1rem; }
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
margin: 1rem 0; }
|
|
||||||
|
|
||||||
small { font-size: .8rem; }
|
|
||||||
|
|
||||||
small.player-links a { margin: 0 .2rem; }
|
|
||||||
|
|
||||||
audio {
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
box-shadow: 5px 5px var(--primary-color);
|
|
||||||
width: 100%; }
|
|
||||||
|
|
||||||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
|
||||||
audio::-webkit-media-controls-panel {
|
|
||||||
background-color: var(--secondary-color); }
|
|
||||||
|
|
||||||
audio { border-radius: 1.6rem; } }
|
|
||||||
|
|
||||||
@-moz-document url-prefix() {
|
|
||||||
audio { border-radius: 0; } }
|
|
||||||
|
|
||||||
html { margin-left: calc(100vw - 100%); }
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: var(--background-color);
|
|
||||||
color: var(--text-color);
|
|
||||||
font-family: 'Roboto Condensed', Roboto, sans-serif;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 960px;
|
|
||||||
width: 98%; }
|
|
||||||
|
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between; }
|
|
||||||
|
|
||||||
#logo {
|
|
||||||
display: block;
|
|
||||||
width: 360px; }
|
|
||||||
|
|
||||||
#logo text { fill: var(--text-color); }
|
|
||||||
|
|
||||||
#logo .logo {
|
|
||||||
font-size: 2rem;
|
|
||||||
font-variant-caps: small-caps;
|
|
||||||
font-weight: bold; }
|
|
||||||
|
|
||||||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
|
||||||
#logo .logo { font-size: 2.082rem; } }
|
|
||||||
|
|
||||||
@-moz-document url-prefix() {
|
|
||||||
#logo .logo { font-size: 2rem; } }
|
|
||||||
|
|
||||||
#logo .under { font-size: .88rem; }
|
|
||||||
|
|
||||||
nav { margin-top: .5rem; }
|
|
||||||
|
|
||||||
nav a { font-variant: small-caps; }
|
|
||||||
|
|
||||||
nav h1 {
|
|
||||||
color: var(--secondary-color);
|
|
||||||
margin: 0; }
|
|
||||||
|
|
||||||
section { margin-top: 1rem; }
|
|
||||||
|
|
||||||
#last-played {
|
|
||||||
margin: 0 auto;
|
|
||||||
min-width: 80%;
|
|
||||||
width: 80%; }
|
|
||||||
|
|
||||||
#last-played tbody tr {
|
|
||||||
display: grid;
|
|
||||||
gap: .5rem;
|
|
||||||
grid-template-columns: 3rem 1fr 1fr; }
|
|
||||||
|
|
||||||
#last-played tbody tr td:nth-child(2) { text-align: right; }
|
|
||||||
|
|
||||||
footer {
|
|
||||||
font-size: .8rem;
|
|
||||||
text-align: center;
|
|
||||||
padding: 1rem 0; }
|
|
||||||
|
|
||||||
@media screen and (max-width: 640px) {
|
|
||||||
header { display: block; }
|
|
||||||
|
|
||||||
#logo {
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 100%; }
|
|
||||||
|
|
||||||
nav {
|
|
||||||
width: 100%;
|
|
||||||
text-align: center; } }
|
|
@ -1,9 +0,0 @@
|
|||||||
#EXTM3U
|
|
||||||
#EXTINF:-1,Arav's dwelling / Radio
|
|
||||||
http://radio.arav.top:8000/stream.ogg
|
|
||||||
#EXTINF:-1,Arav's dwelling / Radio (HTTPS)
|
|
||||||
https://radio.arav.top/live/stream.ogg
|
|
||||||
#EXTINF:-1,Arav's dwelling / Radio on Tor
|
|
||||||
http://wsmkgnmhmzqm7kyzv7jnzzafvgm7xlmlfvzhgorpapd5or2arnhuktqd.onion/live/stream.ogg
|
|
||||||
#EXTINF:-1,Arav's dwelling / Radio on I2P
|
|
||||||
http://radio.arav.i2p/live/stream.ogg
|
|
@ -1,48 +0,0 @@
|
|||||||
function $(id) { return document.getElementById(id); }
|
|
||||||
|
|
||||||
function updateRadioStatus() {
|
|
||||||
fetch("/stats")
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(r => {
|
|
||||||
$("radio-status").innerHTML =
|
|
||||||
`On-air since <time datetime="${r.server_start_iso8601}">${r.server_start_date}</time>`;
|
|
||||||
$("radio-song").textContent = r.song;
|
|
||||||
$("radio-listeners").textContent = r.listeners;
|
|
||||||
$("radio-listener-peak").textContent = r.listener_peak;
|
|
||||||
}).catch(() => {
|
|
||||||
$("radio-status").textContent = "Radio is offline.";
|
|
||||||
$("radio-song").textContent =
|
|
||||||
$("radio-listeners").textContent =
|
|
||||||
$("radio-listener-peak").textContent = "n/a";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateLastPlayedSong() {
|
|
||||||
fetch('/lastsong')
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(last_played => {
|
|
||||||
let cur_artist = $('last-played').firstChild.lastChild.children[1].innerText;
|
|
||||||
let cur_title = $('last-played').firstChild.lastChild.lastChild.innerText;
|
|
||||||
|
|
||||||
if (last_played.artist == cur_artist && last_played.title == cur_title)
|
|
||||||
return;
|
|
||||||
|
|
||||||
$('last-played').firstChild.firstChild.remove();
|
|
||||||
|
|
||||||
let row = $('last-played').insertRow();
|
|
||||||
let start_time = row.insertCell();
|
|
||||||
start_time.appendChild(document.createTextNode(last_played.start_time_local));
|
|
||||||
let artist_cell = row.insertCell();
|
|
||||||
artist_cell.appendChild(document.createTextNode(last_played.artist));
|
|
||||||
let title_cell = row.insertCell();
|
|
||||||
title_cell.appendChild(document.createTextNode(last_played.title));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById("btn-update").addEventListener("click", () => {
|
|
||||||
updateLastPlayedSong();
|
|
||||||
updateRadioStatus();
|
|
||||||
})
|
|
||||||
|
|
||||||
setInterval(updateRadioStatus, 45000);
|
|
||||||
setInterval(updateLastPlayedSong, 45000);
|
|
@ -1,3 +0,0 @@
|
|||||||
User-agent: *
|
|
||||||
Disallow: /assets/
|
|
||||||
Disallow: /live/
|
|
@ -1,60 +0,0 @@
|
|||||||
mixin radioStatus(date, iso)
|
|
||||||
if (date != "n/a")
|
|
||||||
p #[span#radio-status On-air since #[time(datetime=iso)= date]]
|
|
||||||
else
|
|
||||||
p #[span#radio-status Radio is offline.]
|
|
||||||
|
|
||||||
doctype html
|
|
||||||
html(lang='en')
|
|
||||||
head
|
|
||||||
title Arav's dwelling / Radio
|
|
||||||
meta(charset='utf-8')
|
|
||||||
meta(http-equiv='X-UA-Compatible' content='IE=edge')
|
|
||||||
meta(name='viewport' content='width=device-width, initial-scale=1.0')
|
|
||||||
meta(name='theme-color' content='#cd2682')
|
|
||||||
meta(name='description' content='Internet-radio broadcasting from under my desk.')
|
|
||||||
link(rel='icon' href='/shared/img/favicon.svg' sizes='any' type='image/svg+xml')
|
|
||||||
link(href='/assets/css/main.css' rel='stylesheet')
|
|
||||||
script(src='/assets/js/main.js' defer)
|
|
||||||
body
|
|
||||||
header
|
|
||||||
svg#logo(viewBox='0 -25 216 40')
|
|
||||||
text.logo Arav's dwelling
|
|
||||||
text.under(y='11') Welcome to my sacred place, wanderer
|
|
||||||
nav
|
|
||||||
a(href=main_site) Back to main website
|
|
||||||
h1 Radio
|
|
||||||
section
|
|
||||||
small.player-links
|
|
||||||
a(href='/filelist') filelist
|
|
||||||
a(href='/playlist') playlist (.m3u)
|
|
||||||
a(href='/live/stream.ogg') direct link
|
|
||||||
a(href='http://radio.arav.top:8000/stream.ogg') direct link (http)
|
|
||||||
a(href='http://wsmkgnmhmzqm7kyzv7jnzzafvgm7xlmlfvzhgorpapd5or2arnhuktqd.onion/live/stream.ogg') direct link (Tor)
|
|
||||||
a(href='http://radio.arav.i2p/live/stream.ogg') direct link (I2P)
|
|
||||||
a(href='https://dir.xiph.org/search?q=arav\'s+dwelling') Xiph
|
|
||||||
| OGG 128 Kb/s
|
|
||||||
audio(preload='none' controls)
|
|
||||||
source(src='/live/stream.ogg' type='audio/ogg')
|
|
||||||
| Your browser doesn't support an audio element, it's sad... But you always can take the #[a(href='/playlist') playlist]!
|
|
||||||
+radioStatus(radio_status.server_start_date, radio_status.server_start_iso8601)
|
|
||||||
p Now playing: #[span#radio-song= radio_status.song]
|
|
||||||
p Current/peak listeners: #[span#radio-listeners= radio_status.listeners] / #[span#radio-listener-peak= radio_status.listener_peak]
|
|
||||||
p
|
|
||||||
small Notice: information updates every 45 seconds. But you can #[button(id='btn-update') update] it forcibly.
|
|
||||||
if (last_songs)
|
|
||||||
section
|
|
||||||
h2 Last 10 songs
|
|
||||||
table#last-played
|
|
||||||
each song in last_songs
|
|
||||||
tr
|
|
||||||
td= song.start_time_local
|
|
||||||
td= song.artist
|
|
||||||
td= song.title
|
|
||||||
section
|
|
||||||
p The largest number of simultaneous listeners was #[b 7] at #[time(datetime='2022-02-19') 19 February 2022], and the song was "Röyksopp - 49 Percent".
|
|
||||||
section
|
|
||||||
h2 Privacy statements
|
|
||||||
p Logs are collected and include access date and time, IP-address, User-Agent, referer URL, request. This website makes use of JavaScript to update a radio status and last 10 songs list.
|
|
||||||
footer
|
|
||||||
| 2017—2022 Arav <#[a(href='mailto:me@arav.top') me@arav.top]>
|
|
Loading…
Reference in New Issue
Block a user