media no longer copied twice
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,6 @@
|
||||
.env
|
||||
node_modules
|
||||
static
|
||||
static_assets
|
||||
host_info.json
|
||||
*.js
|
||||
package-lock.json
|
||||
|
||||
23
README.md
23
README.md
@@ -1,8 +1,8 @@
|
||||
The most legalest private file (specifically for manga, anime, music) hosting ever, on TOR. Includes a manga viewer. Nothing fancy. Uses HTTP Basic Authentication with daily rotating password derived from a master password.
|
||||
|
||||
Most of the code is a modification of [hedgeblog](https://github.com/jetstream0/hedgeblog), albeit with some pretty significant modifications in places (templates, host.ts, build.ts, saki.ts).
|
||||
Most of the code is a modification of [hedgeblog](https://github.com/jetstream0/hedgeblog), albeit with some pretty significant modifications in places (templates, host.ts, build.ts).
|
||||
|
||||
Since TOR is pretty slow to download large files (eg, videos), the videos are streamed so load time isn't as bad as one might expect (HTTP protocol is amazing, browsers are amazing!).
|
||||
Since TOR is pretty slow to download large files (eg, videos), the videos are streamed so load time isn't as bad as one might expect (HTTP protocol is amazing, browsers are amazing).
|
||||
|
||||
# Running
|
||||
After first cloning or downloading the repository:
|
||||
@@ -25,4 +25,23 @@ Then to actually run:
|
||||
bash tor_prebuilt.sh
|
||||
```
|
||||
|
||||
# Tips
|
||||
|
||||
## 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`.
|
||||
|
||||
## 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).
|
||||
|
||||
## Master Password
|
||||
To set the master password, create a `.env` file like so:
|
||||
|
||||
```
|
||||
master_password=example_password_do_not_actually_use_this_as_your_password_obviously
|
||||
```
|
||||
|
||||
## Username
|
||||
The username doesn't matter. Only the password is important.
|
||||
|
||||
# Disclaimer
|
||||
blah blah blah meant for private use, I would never dream of breaking copyright law blah blah blah not the intended purpose.
|
||||
|
||||
13
build.ts
13
build.ts
@@ -36,17 +36,10 @@ interface MangaVars {
|
||||
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"));
|
||||
|
||||
let renderer: Renderer = new Renderer("templates", "components");
|
||||
let builder: Builder;
|
||||
if (process.argv[2] === "--quick") {
|
||||
builder = new Builder("/build", ["anime_assets", "manga_assets", "music_assets", "password"]); //password is included since it is static
|
||||
} else {
|
||||
builder = new Builder();
|
||||
}
|
||||
let builder: Builder = new Builder("/build");;
|
||||
|
||||
//static page
|
||||
if (process.argv[2] !== "--quick") {
|
||||
builder.serve_static_folder("static");
|
||||
}
|
||||
|
||||
//main page
|
||||
builder.serve_template(renderer, "/", "index", {
|
||||
@@ -71,7 +64,7 @@ let songs: string[] = [];
|
||||
for (let i = 0; i < listings.length; i++) {
|
||||
const listing: Listing = listings[i];
|
||||
directory_serve_paths.push(`/${listing.type}/${listing.name}`);
|
||||
const chapters: string[] = readdirSync(path.join(__dirname, `/static/${listing.type}_assets/${listing.name}`), { withFileTypes: true }).map((d) => d.name.replace(".mp4", "").replace(".mp3", ""));
|
||||
const chapters: string[] = readdirSync(path.join(__dirname, `/static_assets/${listing.type}_assets/${listing.name}`), { withFileTypes: true }).map((d) => d.name.replace(".mp4", "").replace(".mp3", ""));
|
||||
directory_vars.push({
|
||||
listing,
|
||||
chapters,
|
||||
@@ -91,7 +84,7 @@ for (let i = 0; i < listings.length; i++) {
|
||||
for (let j = 0; j < chapters.length; j++) {
|
||||
const chapter: string = chapters[j];
|
||||
manga_serve_paths.push(`/${listing.type}/${listing.name}/${chapter}`);
|
||||
const images: string[] = readdirSync(path.join(__dirname, `/static/${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_vars.push({
|
||||
listing,
|
||||
chapter,
|
||||
|
||||
10
host.ts
10
host.ts
@@ -28,12 +28,17 @@ createServer((req, res) => {
|
||||
//end response
|
||||
return res.end();
|
||||
}
|
||||
const url_obj = new URL(req.url, `http://${req.headers.host}`);
|
||||
if (!req.url.includes(".")) {
|
||||
req_path = path.join(__dirname, "build", decodeURI(req.url), "index.html");
|
||||
} else {
|
||||
//is file
|
||||
if (url_obj.pathname.startsWith("/anime_assets") || url_obj.pathname.startsWith("/manga_assets") || url_obj.pathname.startsWith("/music_assets")) {
|
||||
req_path = path.join(__dirname, "static_assets", decodeURI(req.url));
|
||||
} else {
|
||||
req_path = path.join(__dirname, "build", decodeURI(req.url));
|
||||
}
|
||||
const url_obj = new URL(req.url, `http://${req.headers.host}`);
|
||||
}
|
||||
//check for auth
|
||||
//hopefully no security vulnerabilities. please look away
|
||||
if (url_obj.pathname !== "/password") {
|
||||
@@ -64,10 +69,11 @@ createServer((req, res) => {
|
||||
res.write("404");
|
||||
return res.end();
|
||||
}
|
||||
const file_ext: string = req_path.split(".")[1];
|
||||
//set content type
|
||||
let non_utf8_content_types: string[] = ["image/png", "image/gif", "image/jpeg", "audio/mpeg", "video/mp4"];
|
||||
let content_type: string;
|
||||
switch (req_path.split(".")[1]) {
|
||||
switch (file_ext) {
|
||||
case "html":
|
||||
content_type = "text/html; charset=utf-8";
|
||||
break;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"scripts": {
|
||||
"compile": "tsc -p .",
|
||||
"build": "node build.js",
|
||||
"build-quick": "node build.js --quick",
|
||||
"host": "node host.js"
|
||||
},
|
||||
"repository": {
|
||||
|
||||
20
saki.ts
20
saki.ts
@@ -4,33 +4,19 @@ import type { Renderer } from './ryuji.js';
|
||||
|
||||
export class Builder {
|
||||
build_dir: string;
|
||||
ignore?: string[]; //top level directories/files to not wipe
|
||||
|
||||
constructor(build_dir: string="/build", ignore?: string[]) {
|
||||
constructor(build_dir: string="/build") {
|
||||
this.build_dir = path.join(__dirname, build_dir);
|
||||
this.ignore = ignore;
|
||||
if (existsSync(this.build_dir)) {
|
||||
//wipe the build directory
|
||||
if (!this.ignore || this.ignore?.length === 0) {
|
||||
rmSync(this.build_dir, {
|
||||
recursive: true,
|
||||
});
|
||||
} else {
|
||||
//do not wipe certain directories/files
|
||||
readdirSync(this.build_dir).forEach((member: string) => {
|
||||
if (!this.ignore.includes(member)) {
|
||||
rmSync(path.join(this.build_dir, member), {
|
||||
recursive: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!this.ignore || this.ignore?.length === 0) {
|
||||
mkdirSync(this.build_dir);
|
||||
}
|
||||
}
|
||||
|
||||
//todo: option where if path already exists, let it be (much faster build times)
|
||||
static copy_folder(folder_path: string, dest_path: string) {
|
||||
let children: string[] = readdirSync(folder_path);
|
||||
for (let i=0; i < children.length; i++) {
|
||||
@@ -42,7 +28,7 @@ export class Builder {
|
||||
copyFileSync(child_path, copy_path);
|
||||
} else {
|
||||
//directory, make directory and recursively copy
|
||||
mkdirSync(copy_path);
|
||||
if (!existsSync(copy_path)) mkdirSync(copy_path);
|
||||
Builder.copy_folder(child_path, path.join(dest_path, child));
|
||||
}
|
||||
}
|
||||
|
||||
37
static/password/index.html
Normal file
37
static/password/index.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>holaa</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
<input id="master-password" type="password">
|
||||
<button onclick="get_passwords()">get</button>
|
||||
<br>
|
||||
<span id="computed"></span>
|
||||
<br>
|
||||
<span id="computed2"></span>
|
||||
</div>
|
||||
<script>
|
||||
function get_passwords() {
|
||||
get_password(new Date(), "computed");
|
||||
get_password(new Date((new Date()).getTime() + 24 * 60 * 60 * 1000), "computed2");
|
||||
}
|
||||
async function get_password(date, el) {
|
||||
//password changes every day
|
||||
const master_bytes = new TextEncoder().encode(`${document.getElementById("master-password").value}${date.getUTCFullYear()}-${date.getUTCMonth()}-${date.getUTCDate()}`);
|
||||
const hash_bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", master_bytes));
|
||||
//uint8array into hex
|
||||
let hex_chars = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
|
||||
let hash_hex = "";
|
||||
for (let i = 0; i < hash_bytes.length; i++) {
|
||||
hash_hex += hex_chars[Math.floor(hash_bytes[i] / 16)];
|
||||
hash_hex += hex_chars[hash_bytes[i] % 16];
|
||||
}
|
||||
document.getElementById(el).innerText = hash_hex.toLowerCase();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1 +1 @@
|
||||
<li><a href="/[[ listing.type ]]/[[ listing.name ]]">[[ listing.name ]] ([[ listing.type ]])</a></li>
|
||||
<li class="listing [[ listing.type ]]-listing"><a href="/[[ listing.type ]]/[[ listing.name ]]">[[ listing.name ]] ([[ listing.type ]])</a></li>
|
||||
@@ -10,6 +10,13 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
<label for="show">Show:</label>
|
||||
<select id="show" onchange="show_change()">
|
||||
<option value="all" selected>All</option>
|
||||
<option value="anime">Anime</option>
|
||||
<option value="manga">Manga</option>
|
||||
<option value="music">Music</option>
|
||||
</select>
|
||||
<div>
|
||||
<ul>
|
||||
[[ for:listings:listing ]]
|
||||
@@ -18,5 +25,40 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function show_change() {
|
||||
const show_value = document.getElementById("show").value;
|
||||
if (show_value === "all") {
|
||||
document.querySelectorAll(".listing").forEach((l) => {
|
||||
l.style.display = "list-item";
|
||||
});
|
||||
} else if (show_value === "anime") {
|
||||
document.querySelectorAll(".listing").forEach((l) => {
|
||||
if (l.classList.contains("anime-listing")) {
|
||||
l.style.display = "list-item";
|
||||
} else {
|
||||
l.style.display = "none";
|
||||
}
|
||||
});
|
||||
} else if (show_value === "manga") {
|
||||
document.querySelectorAll(".listing").forEach((l) => {
|
||||
if (l.classList.contains("manga-listing")) {
|
||||
l.style.display = "list-item";
|
||||
} else {
|
||||
l.style.display = "none";
|
||||
}
|
||||
});
|
||||
} else if (show_value === "music") {
|
||||
document.querySelectorAll(".listing").forEach((l) => {
|
||||
if (l.classList.contains("music-listing")) {
|
||||
l.style.display = "list-item";
|
||||
} else {
|
||||
l.style.display = "none";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
show_change();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
npm run compile
|
||||
npm run build-quick
|
||||
tor -f .torrc & npm run host
|
||||
Reference in New Issue
Block a user