player filters! favourites! stats!
also, small fixes/improvements, new ryuji feature: "if in array", "if not in array"
This commit is contained in:
@@ -25,10 +25,12 @@ Then to actually run:
|
|||||||
bash tor_prebuilt.sh
|
bash tor_prebuilt.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Make sure to set a master password! See `.env.example`. Enter in the master password at /password to get the current day and the next day's password.
|
||||||
|
|
||||||
# Tips
|
# Tips
|
||||||
|
|
||||||
## Adding media
|
## Adding media
|
||||||
Add it to the relevant static directory (`/static_assets/anime_assets`, `/static_assets/manga_assets`, or `/static_assets/music_assets`), and create an entry for it in `host_info.json`.
|
Add it to the relevant static directory (`/static_assets/anime_assets`, `/static_assets/manga_assets`, or `/static_assets/music_assets`), and create an entry for it in `host_info.json`. See `host_info.json.example` for an example.
|
||||||
|
|
||||||
## Hosting Multiple TOR Hidden Services
|
## Hosting Multiple TOR Hidden Services
|
||||||
If you are running multiple TOR hidden services, you will need to modify the [.torrc file](https://stackoverflow.com/questions/14321214/how-to-run-multiple-tor-processes-at-once-with-different-exit-ips#18895491).
|
If you are running multiple TOR hidden services, you will need to modify the [.torrc file](https://stackoverflow.com/questions/14321214/how-to-run-multiple-tor-processes-at-once-with-different-exit-ips#18895491).
|
||||||
|
|||||||
46
build.ts
46
build.ts
@@ -9,6 +9,10 @@ import _host_info from './host_info.json';
|
|||||||
interface Listing {
|
interface Listing {
|
||||||
name: string;
|
name: string;
|
||||||
type: "anime" | "manga" | "music";
|
type: "anime" | "manga" | "music";
|
||||||
|
favourites: {
|
||||||
|
listing: boolean; //whether to mark entire listing as favourite
|
||||||
|
chapters: string[]; //favourite chapters
|
||||||
|
}; //marked as not optional here, but in the actual host_info.json file, it is optional
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DirectoryVars {
|
interface DirectoryVars {
|
||||||
@@ -33,10 +37,23 @@ interface MangaVars {
|
|||||||
prev_chapter?: string | boolean;
|
prev_chapter?: string | boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const listings: Listing[] = _host_info.listings.filter((listing: any): listing is Listing => typeof listing.name === "string" && (listing?.type === "anime" || listing?.type === "manga" || listing?.type === "music"));
|
const listings: Listing[] = _host_info.listings.map(
|
||||||
|
(listing: any) =>
|
||||||
|
//add empty "favourites" if not present in the json
|
||||||
|
listing.favourites ? listing : {
|
||||||
|
...listing,
|
||||||
|
favourites: {
|
||||||
|
listing: false,
|
||||||
|
chapters: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).filter(
|
||||||
|
(listing: any): listing is Listing =>
|
||||||
|
typeof listing.name === "string" && (listing?.type === "anime" || listing?.type === "manga" || listing?.type === "music")
|
||||||
|
);
|
||||||
|
|
||||||
let renderer: Renderer = new Renderer("templates", "components");
|
let renderer: Renderer = new Renderer("templates", "components");
|
||||||
let builder: Builder = new Builder("/build");;
|
let builder: Builder = new Builder("/build");
|
||||||
|
|
||||||
//static page
|
//static page
|
||||||
builder.serve_static_folder("static");
|
builder.serve_static_folder("static");
|
||||||
@@ -60,6 +77,7 @@ let music_serve_paths: string[] = [];
|
|||||||
let music_vars: MusicVars[] = [];
|
let music_vars: MusicVars[] = [];
|
||||||
|
|
||||||
let songs: string[] = [];
|
let songs: string[] = [];
|
||||||
|
let manga_pages_count: number = 0;
|
||||||
|
|
||||||
for (let i = 0; i < listings.length; i++) {
|
for (let i = 0; i < listings.length; i++) {
|
||||||
const listing: Listing = listings[i];
|
const listing: Listing = listings[i];
|
||||||
@@ -85,6 +103,7 @@ for (let i = 0; i < listings.length; i++) {
|
|||||||
const chapter: string = chapters[j];
|
const chapter: string = chapters[j];
|
||||||
manga_serve_paths.push(`/${listing.type}/${listing.name}/${chapter}`);
|
manga_serve_paths.push(`/${listing.type}/${listing.name}/${chapter}`);
|
||||||
const images: string[] = readdirSync(path.join(__dirname, `/static_assets/${listing.type}_assets/${listing.name}/${chapter}`), { withFileTypes: true }).map((d) => d.name);
|
const images: string[] = readdirSync(path.join(__dirname, `/static_assets/${listing.type}_assets/${listing.name}/${chapter}`), { withFileTypes: true }).map((d) => d.name);
|
||||||
|
manga_pages_count += images.length;
|
||||||
manga_vars.push({
|
manga_vars.push({
|
||||||
listing,
|
listing,
|
||||||
chapter,
|
chapter,
|
||||||
@@ -113,13 +132,34 @@ builder.serve_templates(renderer, anime_serve_paths, "anime", anime_vars);
|
|||||||
builder.serve_templates(renderer, manga_serve_paths, "manga", manga_vars);
|
builder.serve_templates(renderer, manga_serve_paths, "manga", manga_vars);
|
||||||
builder.serve_templates(renderer, music_serve_paths, "music", music_vars);
|
builder.serve_templates(renderer, music_serve_paths, "music", music_vars);
|
||||||
|
|
||||||
|
builder.serve_template(renderer, "/stats", "stats", {
|
||||||
|
manga_series_count: listings.filter((l) => l.type === "manga").length,
|
||||||
|
manga_chapters_count: manga_serve_paths.length,
|
||||||
|
manga_pages_count,
|
||||||
|
anime_series_count: listings.filter((l) => l.type === "anime").length,
|
||||||
|
anime_episodes_count: anime_serve_paths.length,
|
||||||
|
artists_count: listings.filter((l) => l.type === "music").length,
|
||||||
|
songs_count: songs.length,
|
||||||
|
});
|
||||||
|
|
||||||
builder.serve_template(renderer, "/player", "player", {
|
builder.serve_template(renderer, "/player", "player", {
|
||||||
songs,
|
songs,
|
||||||
artists: listings.filter((l) => l.type === "music").map(
|
artists: listings.filter((l) => l.type === "music").map(
|
||||||
(l) => (
|
(l) => (
|
||||||
{
|
{
|
||||||
name: l.name,
|
name: l.name,
|
||||||
songs: songs.filter((s) => s.startsWith(`${l.name}/`)).map((song) => song.slice(`${l.name}/`.length)),
|
sanitized_name: l.name.replaceAll("\"", "\\\""),
|
||||||
|
songs: songs.filter((s) => s.startsWith(`${l.name}/`)).map(
|
||||||
|
(song) => (
|
||||||
|
{
|
||||||
|
name: song.slice(`${l.name}/`.length),
|
||||||
|
//I don't think " can be in file names... but just in case
|
||||||
|
sanitized_name: song.slice(`${l.name}/`.length).replaceAll("\"", "\\\""),
|
||||||
|
favourite: l.favourites.chapters.includes(song.slice(`${l.name}/`.length)),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
favourite: l.favourites.listing,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|||||||
44
host_info.json.example
Normal file
44
host_info.json.example
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"listings": [
|
||||||
|
{
|
||||||
|
"name": "senpai-wa-otokonoko",
|
||||||
|
"type": "manga",
|
||||||
|
"favourites": {
|
||||||
|
"listing": true,
|
||||||
|
"chapters": ["c001"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "shiitake-simulation",
|
||||||
|
"type": "manga"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bocchi-the-boulder",
|
||||||
|
"type": "anime",
|
||||||
|
"favourites": {
|
||||||
|
"listing": true,
|
||||||
|
"chapters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "yoroshiku",
|
||||||
|
"type": "music"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "not-buna",
|
||||||
|
"type": "music",
|
||||||
|
"favourites": {
|
||||||
|
"listing": false,
|
||||||
|
"chapters": ["aira"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "arizona-philips",
|
||||||
|
"type": "music",
|
||||||
|
"favourites": {
|
||||||
|
"listing": true,
|
||||||
|
"chapters": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
11
ryuji.ts
11
ryuji.ts
@@ -1,6 +1,6 @@
|
|||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
|
|
||||||
export const SYNTAX_REGEX = /\[\[ [a-zA-Z0-9.:/\-_!]+ \]\]/g;
|
export const SYNTAX_REGEX = /\[\[ [a-zA-Z0-9.:/\*\-_!]+ \]\]/g;
|
||||||
|
|
||||||
export type file_extension = `.${string}`;
|
export type file_extension = `.${string}`;
|
||||||
|
|
||||||
@@ -185,12 +185,21 @@ export class Renderer {
|
|||||||
} else {
|
} else {
|
||||||
//compare with second var
|
//compare with second var
|
||||||
let var_name2: string = exp_parts[2];
|
let var_name2: string = exp_parts[2];
|
||||||
|
let if_in: boolean = false;
|
||||||
let if_not: boolean = false;
|
let if_not: boolean = false;
|
||||||
|
//*! is valid
|
||||||
|
if (var_name2.startsWith("*")) {
|
||||||
|
var_name2 = var_name2.slice(1, var_name2.length);
|
||||||
|
if_in = true;
|
||||||
|
}
|
||||||
if (var_name2.startsWith("!")) {
|
if (var_name2.startsWith("!")) {
|
||||||
var_name2 = var_name2.slice(1, var_name2.length);
|
var_name2 = var_name2.slice(1, var_name2.length);
|
||||||
if_not = true;
|
if_not = true;
|
||||||
}
|
}
|
||||||
let var_value2 = Renderer.get_var(var_name2, vars);
|
let var_value2 = Renderer.get_var(var_name2, vars);
|
||||||
|
if (if_in) {
|
||||||
|
var_value2 = var_value2.find((ele) => ele === var_value);
|
||||||
|
}
|
||||||
if (if_not) {
|
if (if_not) {
|
||||||
//make sure the two compared variables are NOT equal
|
//make sure the two compared variables are NOT equal
|
||||||
if (var_value !== var_value2) {
|
if (var_value !== var_value2) {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<li><a href="/[[ listing.type ]]/[[ listing.name ]]/[[ chapter ]]">[[ chapter ]]</a></li>
|
<li><a href="/[[ listing.type ]]/[[ listing.name ]]/[[ chapter ]]">[[ if:chapter:*listing.favourites.chapters ]]★[[ endif ]][[ chapter ]]</a></li>
|
||||||
@@ -1 +1 @@
|
|||||||
<li class="listing [[ listing.type ]]-listing"><a href="/[[ listing.type ]]/[[ listing.name ]]">[[ listing.name ]] ([[ listing.type ]])</a></li>
|
<li class="listing [[ listing.type ]]-listing"><a href="/[[ listing.type ]]/[[ listing.name ]]">[[ if:listing.favourites ]][[ if:listing.favourites.listing ]]★[[ endif ]][[ endif ]][[ listing.name ]] ([[ listing.type ]])</a></li>
|
||||||
@@ -4,11 +4,13 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>these r not the droids you u looking 4</title>
|
<title>these r not the droids you u looking 4</title>
|
||||||
<style>
|
|
||||||
/* */
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div style="position: absolute; float: right; right: 10px;">
|
||||||
|
<a href="/player">Player</a>
|
||||||
|
-
|
||||||
|
<a href="/stats">Stats</a>
|
||||||
|
</div>
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<label for="show">Show:</label>
|
<label for="show">Show:</label>
|
||||||
<select id="show" onchange="show_change()">
|
<select id="show" onchange="show_change()">
|
||||||
|
|||||||
@@ -19,6 +19,10 @@
|
|||||||
.manga img {
|
.manga img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
#scroll-to-bottom:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
@media only screen and (max-width: 600px) {
|
@media only screen and (max-width: 600px) {
|
||||||
.manga {
|
.manga {
|
||||||
margin: 10px 5vw;
|
margin: 10px 5vw;
|
||||||
@@ -32,7 +36,7 @@
|
|||||||
<div id="main">
|
<div id="main">
|
||||||
<div class="manga">
|
<div class="manga">
|
||||||
<h2 style="display: inline-block;">[[ listing.name ]] [[ chapter ]]</h2>
|
<h2 style="display: inline-block;">[[ listing.name ]] [[ chapter ]]</h2>
|
||||||
<span style="cursor: pointer;" onclick="document.getElementById('nav').scrollIntoView({ behavior: 'smooth' })">Scroll to Bottom</span>
|
<span id="scroll-to-bottom" onclick="document.getElementById('nav').scrollIntoView({ behavior: 'smooth' })">Scroll to Bottom</span>
|
||||||
<br>
|
<br>
|
||||||
[[ for:images:image ]]
|
[[ for:images:image ]]
|
||||||
[[ component:page ]]
|
[[ component:page ]]
|
||||||
|
|||||||
@@ -11,8 +11,11 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
#main {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
.player {
|
.player {
|
||||||
margin: 25px;
|
margin: 15px;
|
||||||
}
|
}
|
||||||
#filters-container {
|
#filters-container {
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
@@ -24,6 +27,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="main">
|
<div id="main">
|
||||||
|
<a href="/">Front page</a>
|
||||||
<div class="player">
|
<div class="player">
|
||||||
<h2 style="display: inline-block;">shuffling</h2>
|
<h2 style="display: inline-block;">shuffling</h2>
|
||||||
<b>Current Song <span id="current-song"></span></b>
|
<b>Current Song <span id="current-song"></span></b>
|
||||||
@@ -40,12 +44,12 @@
|
|||||||
<input type="button" value="Deselect All" onclick="filter_deselect_all()"/>
|
<input type="button" value="Deselect All" onclick="filter_deselect_all()"/>
|
||||||
[[ for:artists:artist ]]
|
[[ for:artists:artist ]]
|
||||||
<div class="artist-filter">
|
<div class="artist-filter">
|
||||||
<input type="checkbox" id="[[ artist.name ]]-checkbox" onchange="artist_filter_toggle('[[ artist.name ]]')"/>
|
<input type="checkbox" id="[[ artist.sanitized_name ]]-checkbox" onchange="artist_filter_toggle('[[ artist.sanitized_name ]]')"/>
|
||||||
<label for="[[ artist.name ]]-checkbox">[[ artist.name ]]</label>
|
<label for="[[ artist.sanitized_name ]]-checkbox">[[ if:artist.favourite ]]★[[ endif ]][[ artist.name ]]</label>
|
||||||
<div id="[[ artist.name ]]-song-filter" class="artist-song-filter">
|
<div id="[[ artist.sanitized_name ]]-song-filter" class="artist-song-filter">
|
||||||
[[ for:artist.songs:song ]]
|
[[ for:artist.songs:song ]]
|
||||||
<input type="checkbox" id="[[ artist.name ]]-[[ song ]]-checkbox" onchange="artist_song_filter_toggle('[[ artist.name ]]', '[[ song ]]')"/>
|
<input type="checkbox" id="[[ artist.sanitized_name ]]-[[ song.sanitized_name ]]-checkbox" onchange="update_playable_songs()"/>
|
||||||
<label for="[[ artist.name ]]-[[ song ]]-checkbox">[[ song ]]</label>
|
<label for="[[ artist.sanitized_name ]]-[[ song.sanitized_name ]]-checkbox">[[ if:song.favourite ]]★[[ endif ]][[ song.name ]]</label>
|
||||||
[[ endfor ]]
|
[[ endfor ]]
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -81,15 +85,15 @@
|
|||||||
});
|
});
|
||||||
//show filters toggle
|
//show filters toggle
|
||||||
const show_filters = document.getElementById("show-filters");
|
const show_filters = document.getElementById("show-filters");
|
||||||
function filter_toggle() {
|
function filter_check() {
|
||||||
if (show_filters.checked) {
|
if (show_filters.checked) {
|
||||||
document.getElementById("filters-container").style.display = "block";
|
document.getElementById("filters-container").style.display = "block";
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("filters-container").style.display = "none";
|
document.getElementById("filters-container").style.display = "none";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
show_filters.onchange = filter_toggle;
|
show_filters.onchange = filter_check;
|
||||||
filter_toggle();
|
filter_check();
|
||||||
//artist and artist song filter toggle
|
//artist and artist song filter toggle
|
||||||
function artist_filter_toggle(artist) {
|
function artist_filter_toggle(artist) {
|
||||||
document.querySelectorAll(`#${artist}-song-filter > input[type=\"checkbox\"]`).forEach((c) => {
|
document.querySelectorAll(`#${artist}-song-filter > input[type=\"checkbox\"]`).forEach((c) => {
|
||||||
@@ -97,8 +101,8 @@
|
|||||||
c.onchange();
|
c.onchange();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function artist_song_filter_toggle(artist, song) {
|
function update_playable_songs() {
|
||||||
playable_songs = songs.filter((song) => document.getElementById(`${song.replace("/", "-")}-checkbox`).checked);
|
playable_songs = songs.filter((song) => document.getElementById(`${song.replace("/", "-").replaceAll("\"", "\\\"")}-checkbox`).checked);
|
||||||
}
|
}
|
||||||
function filter_select_all() {
|
function filter_select_all() {
|
||||||
document.querySelectorAll(".artist-filter > input[type=\"checkbox\"]").forEach((c) => {
|
document.querySelectorAll(".artist-filter > input[type=\"checkbox\"]").forEach((c) => {
|
||||||
|
|||||||
22
templates/stats.html
Normal file
22
templates/stats.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>stats</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="main">
|
||||||
|
<a href="/">Front page</a>
|
||||||
|
<div>
|
||||||
|
<h3>Manga series: [[ manga_series_count ]]</h3>
|
||||||
|
<h3>Manga chapters: [[ manga_chapters_count ]]</h3>
|
||||||
|
<h3>Manga pages: [[ manga_pages_count ]]</h3>
|
||||||
|
<h3>Anime series: [[ anime_series_count ]]</h3>
|
||||||
|
<h3>Anime episodes: [[ anime_episodes_count ]]</h3>
|
||||||
|
<h3>Music Artists: [[ artists_count ]]</h3>
|
||||||
|
<h3>Songs: [[ songs_count ]]</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user