commit 233b64a51d89874e7b3477d25cd044fcfccf0c64 Author: lily Date: Sun Sep 13 14:22:34 2026 -0400 first commit diff --git a/grab.py b/grab.py new file mode 100644 index 0000000..7913f3b --- /dev/null +++ b/grab.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +import argparse +import os +import re +import shutil +import subprocess +import sys +from pathlib import Path + +SHOWS_ROOT = Path(__file__).resolve().parent +DOWNLOADS = SHOWS_ROOT / ".downloads" +SEED_RATIO = 2.0 +SEED_TIME_MIN = 1440 +PORT = 6881 +VIDEO_EXTS = {'.mkv', '.mp4', '.avi', '.mov', '.m4v', '.ts', '.webm'} + +def extract_ep(name): + stem = Path(name).stem + stem = re.sub(r'^\[[^\]]+\]\s*', '', stem) + stem = re.sub(r'\([^)]*\)', '', stem) + stem = re.sub(r'\[[^\]]*\]', '', stem) + if re.search(r'\b(NCOP|NCED|OP|ED|Menu|Extra|Preview|PV|SP|Special)\b', stem, re.I): + return None + m = re.search(r'(?:^|\s|-)\s*(\d{1,3})(?:v\d)?(?=\s|$|\.)', stem) + return int(m.group(1)) if m else None + +def organize(stage, name, year, season): + show_dir = SHOWS_ROOT / f"{name} ({year})" + season_dir = show_dir / f"Season {int(season):02d}" + specials_dir = show_dir / "Specials" + season_dir.mkdir(parents=True, exist_ok=True) + for f in Path(stage).rglob('*'): + if not f.is_file() or f.suffix.lower() not in VIDEO_EXTS: + continue + ep = extract_ep(f.name) + if ep is None: + specials_dir.mkdir(parents=True, exist_ok=True) + dst = specials_dir / f.name + else: + dst = season_dir / f"{name} - S{int(season):02d}E{ep:02d}{f.suffix.lower()}" + if dst.exists(): + continue + try: + os.link(f, dst) + except OSError: + shutil.copy2(f, dst) + print(f"[organize] {dst.relative_to(SHOWS_ROOT)}", flush=True) + +def cmd_organize(a): + organize(a.stage, a.name, a.year, a.season) + +def cmd_download(a): + torrent = Path(a.torrent).expanduser().resolve() + if not torrent.exists(): + sys.exit(f"not found: {torrent}") + stage = DOWNLOADS / torrent.stem + stage.mkdir(parents=True, exist_ok=True) + hook = stage / "_hook.sh" + hook.write_text( + f'#!/bin/bash\n' + f'python3 "{Path(__file__).resolve()}" organize ' + f'--stage "{stage}" --name "{a.name}" --year "{a.year}" --season {a.season}\n' + ) + hook.chmod(0o755) + cmd = [ + "aria2c", str(torrent), + f"--dir={stage}", + f"--listen-port={PORT}", f"--dht-listen-port={PORT}", + "--enable-dht=true", "--enable-peer-exchange=true", "--bt-enable-lpd=true", + "--follow-torrent=mem", "--check-integrity=true", + "--summary-interval=30", "--console-log-level=warn", + f"--on-bt-download-complete={hook}", + ] + if a.no_seed: + cmd.append("--seed-time=0") + else: + cmd += [f"--seed-ratio={SEED_RATIO}", f"--seed-time={SEED_TIME_MIN}"] + subprocess.run(cmd) + +def main(): + ap = argparse.ArgumentParser() + sub = ap.add_subparsers(dest="cmd", required=True) + d = sub.add_parser("download") + d.add_argument("torrent") + d.add_argument("--name", required=True) + d.add_argument("--year", required=True) + d.add_argument("--season", type=int, default=1) + d.add_argument("--no-seed", action="store_true") + d.set_defaults(func=cmd_download) + o = sub.add_parser("organize") + o.add_argument("--stage", required=True) + o.add_argument("--name", required=True) + o.add_argument("--year", required=True) + o.add_argument("--season", type=int, default=1) + o.set_defaults(func=cmd_organize) + a = ap.parse_args() + a.func(a) + +if __name__ == "__main__": + main() diff --git a/nyaa-tools-old/README.md b/nyaa-tools-old/README.md new file mode 100644 index 0000000..32049ba --- /dev/null +++ b/nyaa-tools-old/README.md @@ -0,0 +1,13 @@ +# [nyaa-tools](https://sr.ht/~truita/nyaa-tools) +`nyaa-tools` is a set of scripts for automating downloads from [Nyaa.si](https://nyaa.si). + +It is currently comprised of: +- `fetch`: intended to be run as a cron job, will download files through a +torrent daemon and link them in a [Jellyfin style](https://jellyfin.org/docs/general/server/media/shows) organization +- `helper`: provides a CLI for adding items to the tracking list which `fetch` employs + +### Dependencies +- toml +- requests +- transmission-rpc (fetch only) +- inquirer (helper only) diff --git a/nyaa-tools-old/batch.py b/nyaa-tools-old/batch.py new file mode 100644 index 0000000..2b0ab78 --- /dev/null +++ b/nyaa-tools-old/batch.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +import argparse +import os +from time import sleep + +import toml +import transmission_rpc + +# https://franklingu.github.io/programming/2016/03/01/creating-daemon-process-python-example-explanation/ +def daemonize(): + try: + pid = os.fork() + if pid > 0: + # exit first parent + exit(0) + except OSError: + exit(1) + + # decouple from parent environment + os.chdir("/") + os.setsid() + + # do second fork + try: + pid = os.fork() + if pid > 0: + exit(0) + except OSError: + exit(1) + +config = toml.load("/etc/nyaa-tools/config.toml") + +def jelly_path(name: str, season: int, episode=None, offset=0) -> str: + if episode is None: + return "{base}/{name}/Season {season:02}".format( + base=config["library"]["base_path"], + name=name, + season=season, + ) + else: + return "{base}/{name}/Season {season:02}/Episode S{season:02}E{episode:02}.mkv".format( + base=config["library"]["base_path"], + name=name, + season=season, + episode=episode + offset, + ) + + +parser = argparse.ArgumentParser( + prog="nyaa-batch" +) +parser.add_argument('-t', '--title') +parser.add_argument('-s', '--season') +parser.add_argument('-l', '--link') +args = parser.parse_args() + +try: + season = int(args.season) +except TypeError: + exit(1) + + +client = transmission_rpc.Client(host=config["transmission"]["host"]) +torrent = client.add_torrent(args.link, files_wanted=None, paused=True) +torrent = client.get_torrent(torrent.id) # Don't ask +files = torrent.get_files() +files.sort() +wanted = [] +for file in files: + # Only get files in root + if file.name.count('/') < 2: + wanted.append((file.id, len(wanted) + 1)) + +client.change_torrent(torrent.id, files_wanted=map(lambda f: f[0], wanted)) +client.start_torrent(torrent.id) +daemonize() + +while len(wanted) > 0: + torrent = client.get_torrent(torrent.id) + get_files = torrent.get_files() + for want in wanted: + file = next(x for x in get_files if x.id == want[0]) + if file.size == file.completed: + if not os.path.exists(jelly_path(args.title, season)): + os.makedirs(jelly_path(args.title, season)) + os.link( + config["transmission"]["download_path"] + "/" + file.name, + jelly_path(args.title, season, want[1]) + ) + wanted.remove(want) + sleep(5) diff --git a/nyaa-tools-old/fetch.py b/nyaa-tools-old/fetch.py new file mode 100755 index 0000000..25d9fa8 --- /dev/null +++ b/nyaa-tools-old/fetch.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +from time import sleep +import requests +import toml +import logging +import xml.dom.minidom +import re +import transmission_rpc +import os +import sys +from dataclasses import dataclass + +logging.basicConfig(format="%(name)s - %(levelname)s - %(message)s", level=logging.INFO) +logger = logging.getLogger(__name__) + +config = toml.load("/etc/nyaa-tools/config.toml") +series = toml.load("/etc/nyaa-tools/series.toml") +release_groups = toml.load("/etc/nyaa-tools/release-groups.toml") + + +def jelly_path(name: str, season: int, episode=None, offset=0) -> str: + if episode is None: + return "{base}/{name}/Season {season:02}".format( + base=config["library"]["base_path"], + name=name, + season=season, + ) + else: + return "{base}/{name}/Season {season:02}/Episode S{season:02}E{episode:02}.mkv".format( + base=config["library"]["base_path"], + name=name, + season=season, + episode=episode + offset, + ) + + +if len(sys.argv) > 1: + logger.info("Searching for {}".format(" ".join(sys.argv[1:]))) + nyaa_body = requests.get( + "https://nyaa.si/?page=rss&q={}".format(" ".join(sys.argv[1:])) + ).text +else: + nyaa_body = requests.get("https://nyaa.si/?page=rss").text +items = xml.dom.minidom.parseString(nyaa_body).getElementsByTagName("item") + + +@dataclass +class DownloadItem: + torrent_id: int + name: str + season: int + episode: int + offset: int + + +downloading: list[DownloadItem] = [] +client: transmission_rpc.Client = None + + +for item in items: + title: str = item.getElementsByTagName("title")[0].firstChild.data + link: str = item.getElementsByTagName("link")[0].firstChild.data + + logger.debug("Checking {}".format(title)) + + for season in series["tracking"]: + regex = re.search(release_groups[season["release_group"]], title) + if regex is None: + continue + if season["name"] in regex.group("title"): + logger.debug("Title matched for {}".format(season["name"])) + # Extract chapter number + chapter = int(regex.group("episode")) + logger.debug("Chapter extracted: {}".format(chapter)) + + # Check if chapter already exists + path = jelly_path( + season["name"], season["season"], chapter, season.get("offset", 0) + ) + if os.path.exists(path): + logger.debug( + 'Chapter {} for "{}" already exists. Skipping'.format( + chapter, season["name"] + ) + ) + continue + + # Sign in to Transmission + client = transmission_rpc.Client(host=config["transmission"]["host"]) + torrent = client.add_torrent(link) + downloading.append( + DownloadItem( + torrent.id, + season["name"], + season["season"], + chapter, + season.get("offset", 0), + ) + ) + logger.info("Downloading {}".format(title)) + +while len(downloading) > 0: + for item in downloading: + torrent = client.get_torrent(item.torrent_id) + if torrent.left_until_done == 0: + if not os.path.exists(jelly_path(item.name, item.season)): + logger.warning( + 'Season folder for "{}" did not exist, creating recursively'.format( + item.name + ) + ) + os.makedirs(jelly_path(item.name, item.season)) + + os.link( + config["transmission"]["download_path"] + + "/" + + torrent.get_files()[0].name, + jelly_path(item.name, item.season, item.episode, item.offset), + ) + logger.info( + 'Linked "{}" season {} chapter {}'.format( + item.name, item.season, item.episode + item.offset + ) + ) + downloading.remove(item) + sleep(5) diff --git a/nyaa-tools-old/helper.py b/nyaa-tools-old/helper.py new file mode 100755 index 0000000..165fc10 --- /dev/null +++ b/nyaa-tools-old/helper.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +import requests +import toml +import xml.dom.minidom +from dataclasses import dataclass +import re +import inquirer + +release_groups = toml.load("/etc/nyaa-tools/release-groups.toml") +release_group_names = release_groups.keys() + +query = inquirer.text("Name of the anime") +nyaa_search = requests.get("https://nyaa.si/?page=rss&q={}".format(query)).text +items = xml.dom.minidom.parseString(nyaa_search).getElementsByTagName("item") + + +@dataclass +class Episode: + title: str + link: str + release_group: str + + +def episode_info(ep: Episode, season: int, offset: int) -> str: + return ( + "Title: {}\n".format(ep.title) + + "Season: {}\n".format(season) + + "Chapter offset: {}\n".format(offset) + + "Release Group: {}".format(ep.release_group) + ) + + +matches: list[Episode] = [] + +for item in items: + title: str = item.getElementsByTagName("title")[0].firstChild.data + link: str = item.getElementsByTagName("link")[0].firstChild.data + + regex = re.search("\\[([^ ]+)\\].+", title) + if regex is not None: + matches.append(Episode(title, link, regex.group(1))) + +if not matches: + print("No matching entries") + exit(255) + +known_matches = filter(lambda ep: ep.release_group in release_group_names, matches) +parsed_matches: list[Episode] = [] + +for match in known_matches: + rg_regex = re.search(release_groups[match.release_group], match.title) + if rg_regex is not None: + title = rg_regex.group(1) + parsed_matches.append(Episode(title, match.link, match.release_group)) + +if not parsed_matches: + print("No known release groups were found, however the following were identified:") + for match in set(map(lambda m: m.release_group, matches)): + print("-", match) + print( + "Please add one of them to the release-groups.toml or wait for a release from a known group" + ) + exit(255) + +choices = set(map(lambda m: m.release_group, parsed_matches)) +if len(choices) > 1: + chosen_rg = inquirer.list_input( + "Choose a release group", + choices=sorted(set(map(lambda m: m.release_group, parsed_matches))), + ) +else: + chosen_rg = choices.pop() + +final_match = next(m for m in parsed_matches if m.release_group == chosen_rg) +season = 1 +offset = 0 +while True: + print(episode_info(final_match, season, offset)) + ans = inquirer.list_input( + "The following entry will be added", + choices=["Confirm", "Edit", "Cancel"], + ) + if ans == "Confirm": + break + elif ans == "Cancel": + exit(0) + while True: + print(episode_info(final_match, season, offset)) + ans = inquirer.list_input( + "What do you want to edit", choices=["Title", "Season", "Offset", "Done"] + ) + if ans == "Done": + break + if ans == "Title": + final_match.title = inquirer.text("New title", default=final_match.title) + elif ans == "Season": + season = int(inquirer.text( + "Season number", validate=lambda _, x: re.match("\\d+", x) + )) + elif ans == "Offset": + offset = int(inquirer.text( + "Chapter offset", validate=lambda _, x: re.match("\\d+", x) + )) + +series = toml.load("/etc/nyaa-tools/series.toml") +if "tracking" not in series.keys(): + series["tracking"] = [] + +entry = { + "name": final_match.title, + "season": season, + "release_group": final_match.release_group, +} +if offset != 0: + entry["offset"] = offset +series["tracking"].append(entry) + +with open("/etc/nyaa-tools/series.toml", "w") as f: + toml.dump(series, f) +print( + "Done! Please run `nyaa-fetch '[{}] {}'` to download previous episodes".format( + final_match.release_group, final_match.title + ) +) diff --git a/nyaa-tools-old/web/server.py b/nyaa-tools-old/web/server.py new file mode 100644 index 0000000..beb8c51 --- /dev/null +++ b/nyaa-tools-old/web/server.py @@ -0,0 +1,253 @@ +#!/bin/env python3 +import os +import re +import secrets +import xml.dom.minidom +from dataclasses import dataclass +from datetime import datetime, timedelta + +import requests +import toml +from flask import ( + Flask, + make_response, + redirect, + render_template, + request, + url_for, +) + +app = Flask(__name__) + +release_groups = toml.load("/etc/nyaa-tools/release-groups.toml") +batch_groups = toml.load("/etc/nyaa-tools/batch-groups.toml") + +DEV_ENV = bool(os.environ.get("DEV_ENV", default=False)) +if not DEV_ENV: + PASSWORD = os.environ["PASSWORD"] + + +active_sessions = {} + + +def is_token_valid(token): + return ( + token and token in active_sessions and datetime.now() < active_sessions[token] + ) + + +# Creates a token and adds it to the "database" +def create_valid_token(): + token = secrets.token_urlsafe(16) + + current_time = datetime.now() + expiration_time = current_time + timedelta(days=1) + + active_sessions[token] = expiration_time + + return token + + +def remove_token(token): + del active_sessions[token] + + +@app.before_request +def before_request(): + if not DEV_ENV: + if request.path == "/login": + return login() + else: + if not request.path.startswith("/static"): + token = request.cookies.get("session_token") + if not is_token_valid(token): + return redirect(url_for("login")) + + +@dataclass +class SearchItem: + title: str + link: str + +def search(query) -> list[SearchItem]: + nyaa_search = requests.get("https://nyaa.si/?page=rss&q={}".format(query)).text + items = xml.dom.minidom.parseString(nyaa_search).getElementsByTagName("item") + + result = [] + + for item in items: + title: str = item.getElementsByTagName("title")[0].firstChild.data # pyright: ignore[reportOptionalMemberAccess,reportAttributeAccessIssue] + link: str = item.getElementsByTagName("link")[0].firstChild.data # pyright: ignore[reportOptionalMemberAccess,reportAttributeAccessIssue] + + result.append(SearchItem(title, link)) + return result + + +@app.route("/") +def index(): + return render_template("index.html") + + +@app.route("/login", methods=["GET", "POST"]) +def login(): + if request.method == "POST": + password = request.form["password"] + + if password == PASSWORD: + token = create_valid_token() + + # Set token in a cookie + resp = make_response(redirect(url_for("index"))) + resp.set_cookie("session_token", token, max_age=60 * 60 * 24) + + return resp + else: + return "Invalid credentials. Try again." + + return render_template("login.html") + + +@app.route("/logout") +def logout(): + # Get the session token + token = request.cookies.get("session_token") + + remove_token(token) + + # Clear the session and cookie + resp = make_response(redirect(url_for("login"))) + resp.set_cookie("session_token", "", expires=0) # Remove cookie + return resp + + +@dataclass +class SeriesInfo: + release_group: str + title: str + season: int | None + + +# Returns the search page if q is empty, else it shows results +@app.route("/series", methods=["GET", "POST"]) +def series(): + if request.method == "GET": + query = request.args.get("q", default="", type=str) + + if query == "": + return render_template("series/search.html") + + results = search(query) + parsed_results = [] + for item in results: + title = item.title + rg = re.search("\\[([^ ]+)\\].+", title) + if rg is None or rg.group(1) not in release_groups.keys(): + continue + info = re.search(release_groups[rg.group(1)], title) + # TODO: Do not append duplicated entries + if info is None: + continue + try: + season = int(info.group("season")) + except IndexError: + season = None + parsed_results.append( + SeriesInfo( + release_group=rg.group(1), + title=info.group("title"), + season=season + ) + ) + return render_template("series/results.html", results=parsed_results) + elif request.method == "POST": + release_group = request.form["release-group"] + title = request.form["title"] + season = request.form["season"] + if release_group and title and season: + try: + season = int(season) + except ValueError: + return "Invalid season", 400 + + series = toml.load("/etc/nyaa-tools/series.toml") + if "tracking" not in series.keys(): + series["tracking"] = [] + + # TODO: Do not allow duplicates + entry = {"name": title, "release_group": release_group, "season": season} + series["tracking"].append(entry) + with open("/etc/nyaa-tools/series.toml", "w") as f: + toml.dump(series, f) + return "OK", 200 + else: + return "Missing parameters", 400 + + +@dataclass +class BatchInfo: + release_group: str + title: str + season: int | None + episodes: int | None + link: str + +@app.route("/batch", methods=["GET", "POST"]) +def batch(): + if request.method == "GET": + query = request.args.get("q", default="", type=str) + + if query == "": + return render_template("batch/search.html") + + results = search(query) + parsed_results = [] + for item in results: + title = item.title + rg = re.search("\\[([^ ]+)\\].+", title) + if rg is None or rg.group(1) not in batch_groups.keys(): + continue + info = re.search(batch_groups[rg.group(1)], title) + # TODO: Do not append duplicated entries + if info is None: + continue + try: + season = int(info.group("season")) + except IndexError: + season = None + + try: + episodes = int(info.group("last")) - int(info.group("first")) + 1 + except IndexError: + #TODO: Get episodes from file list (requires lxml & cssselector) + episodes = None + + parsed_results.append( + BatchInfo( + release_group=rg.group(1), + title=info.group("title"), + season=season, + episodes=episodes, + link=item.link + ) + ) + return render_template("batch/results.html", results=parsed_results) + elif request.method == "POST": + title = request.form["title"] + season = request.form["season"] + link = request.form["link"] + if title and season and link: + try: + season = int(season) + except ValueError: + return "Invalid season", 400 + + ret = os.system(" ".join(["nyaa-batch", "-t", "'{}'".format(title), "-s", str(season), "-l", "'{}'".format(link)])) + if ret == 0: + return "OK", 200 + else: + return "Error", 500 + else: + return "Missing parameters", 400 + +if __name__ == "__main__": + app.run(debug=DEV_ENV) diff --git a/nyaa-tools-old/web/static/style.css b/nyaa-tools-old/web/static/style.css new file mode 100644 index 0000000..b55c812 --- /dev/null +++ b/nyaa-tools-old/web/static/style.css @@ -0,0 +1,110 @@ +body { + font-family: Arial, sans-serif; + background-color: #1e1e1e; + color: #f5f5f5; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + margin: 0; +} + +div { + background-color: #2b2b2b; + padding: 20px; + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); + display: flex; + flex-direction: column; + width: 300px; + text-align: center; +} + +.full_form { + background-color: #2b2b2b; + padding: 20px; + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); + display: flex; + flex-direction: column; + width: 300px; + text-align: center; +} + +.button_form { + margin-left: auto; + align-self: center; +} + +input[type="text"], +input[type="password"] { + background-color: #333; + border: 1px solid #555; + padding: 10px; + border-radius: 5px; + color: #fff; + margin-bottom: 15px; +} + +input:focus { + outline: none; + border-color: #4CAF50; +} + + +button { + background-color: #4CAF50; + border: none; + padding: 10px; + border-radius: 5px; + color: white; + cursor: pointer; + transition: background-color 0.3s ease; +} + +button:hover { + background-color: #359039; +} + + +h1 { + color: #f5f5f5; + margin-bottom: 20px; + text-align: center; +} + +a { + color: #f5f5f5; + text-decoration: none; + display: inline-block; + padding: 10px; +} + +a:hover { + text-decoration: underline; +} + +label { + padding: 10px; +} + +ul { + list-style-type: none; + padding: 0; + width: 100%; + max-width: 60vw; +} + +li { + background-color: #2b2b2b; + padding: 15px; + margin-bottom: 10px; + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); + transition: background-color 0.3s ease; + display: flex; +} + +li:hover { + background-color: #333; +} diff --git a/nyaa-tools-old/web/templates/batch/results.html b/nyaa-tools-old/web/templates/batch/results.html new file mode 100644 index 0000000..cdd35a0 --- /dev/null +++ b/nyaa-tools-old/web/templates/batch/results.html @@ -0,0 +1,28 @@ +{% import 'macros.html' as macros %} +{{ macros.header('Search') }} + + +

Search Results

+ + + + + + + + + {% for item in results %} + + + + + + + + + {% endfor %} +
Release groupNameSeasonEpisodes
{{ item.release_group }}{% if item.episodes %}{{item.episodes}}{% else %}?{% endif %}
+ Home + + +{{ macros.footer() }} diff --git a/nyaa-tools-old/web/templates/batch/search.html b/nyaa-tools-old/web/templates/batch/search.html new file mode 100644 index 0000000..46f1152 --- /dev/null +++ b/nyaa-tools-old/web/templates/batch/search.html @@ -0,0 +1,6 @@ +{% import 'macros.html' as macros %} +{{ macros.header('Search') }} + +{{ macros.search('batch', "add 'batch' to the search terms to find batches") }} + +{{ macros.footer() }} diff --git a/nyaa-tools-old/web/templates/index.html b/nyaa-tools-old/web/templates/index.html new file mode 100644 index 0000000..c3fd408 --- /dev/null +++ b/nyaa-tools-old/web/templates/index.html @@ -0,0 +1,12 @@ +{% import 'macros.html' as macros %} +{{ macros.header('Hub')}} + + +
+ Add series + Download batch + Logout +
+ + +{{ macros.footer() }} diff --git a/nyaa-tools-old/web/templates/login.html b/nyaa-tools-old/web/templates/login.html new file mode 100644 index 0000000..0a7b020 --- /dev/null +++ b/nyaa-tools-old/web/templates/login.html @@ -0,0 +1,13 @@ +{% import 'macros.html' as macros %} +{{ macros.header('Login')}} + + +

Login

+
+ +

+ +
+ + +{{ macros.footer() }} diff --git a/nyaa-tools-old/web/templates/macros.html b/nyaa-tools-old/web/templates/macros.html new file mode 100644 index 0000000..94daa70 --- /dev/null +++ b/nyaa-tools-old/web/templates/macros.html @@ -0,0 +1,29 @@ +{% macro header(title) %} + + + + + + + {{title}} + + + +{% endmacro %} + +{% macro footer() %} + +{% endmacro %} + +{% macro search(path, tip) %} + +

Search

+
+ +
+ {%- if tip %}Tip: {{ tip }}
{% endif -%} + +
+ Home + +{% endmacro %} diff --git a/nyaa-tools-old/web/templates/series/results.html b/nyaa-tools-old/web/templates/series/results.html new file mode 100644 index 0000000..bf491a7 --- /dev/null +++ b/nyaa-tools-old/web/templates/series/results.html @@ -0,0 +1,25 @@ +{% import 'macros.html' as macros %} +{{ macros.header('Search') }} + + +

Search Results

+ + + + + + + + {% for item in results %} + + + + + + + {% endfor %} +
Release groupNameSeason
{{item.release_group}}
+ Home + + +{{ macros.footer() }} diff --git a/nyaa-tools-old/web/templates/series/search.html b/nyaa-tools-old/web/templates/series/search.html new file mode 100644 index 0000000..23a5e9e --- /dev/null +++ b/nyaa-tools-old/web/templates/series/search.html @@ -0,0 +1,6 @@ +{% import 'macros.html' as macros %} +{{ macros.header('Search') }} + +{{ macros.search('series', "if a series' season is not autodetected you may need to remove it from the title") }} + +{{ macros.footer() }}