from asyncio import SelectorEventLoop, get_event_loop, set_event_loop from functools import wraps from operator import attrgetter, itemgetter from flask import abort, redirect, render_template, request, url_for from . import utils from .config import ( ADMIN_PASSWORD, ADMIN_USERNAME, APP_PORT, DB_ENABLED, IS_DEBUG, TRANSMISSION_ENABLED, app, auth, ) from .connectors import Nyaa, get_instance, run_all from .connectors.core import ConnectorLang, ConnectorReturn from .forms import DeleteForm, EditForm, FolderDeleteForm, FolderEditForm, SearchForm if DB_ENABLED: from .config import db from .models import AnimeFolder, AnimeTitle, AnimeLink if TRANSMISSION_ENABLED: from .config import transmission def db_required(f): @wraps(f) def decorated_function(*args, **kwargs): if not DB_ENABLED: return abort(404) return f(*args, **kwargs) return decorated_function def clean_titles(): db.engine.execute( """ DELETE FROM anime_title WHERE id IN ( SELECT anime_title.id FROM anime_title LEFT JOIN anime_link ON anime_title.id = anime_link.title_id WHERE anime_link.id IS NULL ) """ ) @auth.verify_password def verify_password(username, password): return username == ADMIN_USERNAME and ADMIN_PASSWORD == password @app.template_filter("boldify") def boldify(name): query = request.args.get("q", "") name = utils.boldify(name, query) if DB_ENABLED: for keyword in db.session.query(AnimeTitle.keyword.distinct()).all(): if keyword[0].lower() != query.lower(): name = utils.boldify(name, keyword[0]) return name @app.template_filter("flagify") def flagify(is_vf): return ConnectorLang.FR.value if is_vf else ConnectorLang.JP.value @app.template_filter("colorify") def colorify(model): return get_instance(model.link, model.title.keyword).color @app.context_processor def inject_user(): return dict(db_disabled=not DB_ENABLED) @app.route("/") def home(): return render_template( "layout.html", search_form=SearchForm(), title="Anime torrents search engine" ) @app.route("/search") def search(): query = request.args.get("q") if not query: return redirect(url_for("home")) set_event_loop(SelectorEventLoop()) torrents = get_event_loop().run_until_complete(run_all(query)) return render_template("search.html", search_form=SearchForm(), connectors=torrents) @app.route("/latest") @app.route("/latest/") def latest(page=1): set_event_loop(SelectorEventLoop()) torrents = get_event_loop().run_until_complete( run_all("", return_type=ConnectorReturn.HISTORY, page=page) ) results = [] for torrent in torrents: results = results + torrent.data for result in results: result["self"] = get_instance(result["href"]) results.sort(key=itemgetter("date"), reverse=True) return render_template( "latest.html", search_form=SearchForm(), torrents=results, page=page ) @app.route("/list") @app.route("/list/") @db_required def list_animes(url_filters="nyaa,yggtorrent"): filters = None for i, to_filter in enumerate(url_filters.split(",")): if not i: filters = AnimeLink.link.contains(to_filter) else: filters = filters | AnimeLink.link.contains(to_filter) titles = ( db.session.query(AnimeTitle, AnimeLink) .join(AnimeLink) .filter(filters) .order_by(AnimeTitle.name) .all() ) results = {} for title, link in titles: if title.id not in results: results[title.id] = [link] else: results[title.id].append(link) return render_template("list.html", search_form=SearchForm(), titles=results) @app.route("/admin", methods=["GET", "POST"]) @db_required @auth.login_required def admin(): form = DeleteForm(request.form) if form.validate_on_submit(): link = AnimeLink.query.filter_by(id=int(form.id.data)).first() if link: form.message = "%s (%s) has been successfully deleted" % ( link.title.name, link.season, ) db.session.delete(link) db.session.commit() title = link.title if title and not len(title.links): db.session.delete(title) db.session.commit() else: form._errors = { "id": ["Id %s was not found in the database" % form.id.data] } folders = AnimeFolder.query.all() for folder in folders: for title in folder.titles: title.links.sort(key=attrgetter("season")) folder.titles.sort(key=attrgetter("name")) return render_template( "admin/list.html", search_form=SearchForm(), folders=folders, action_form=form ) @app.route("/admin/folder", methods=["GET", "POST"]) @db_required @auth.login_required def folder_list(): form = FolderDeleteForm(request.form) if form.validate_on_submit(): folder = AnimeFolder.query.filter_by(id=int(form.id.data)).first() if folder: form.message = "%s has been successfully deleted" % folder.name db.session.delete(folder) db.session.commit() else: form._errors = { "id": ["Id %s was not found in the database" % form.id.data] } folders = AnimeFolder.query.all() return render_template( "admin/folder/list.html", search_form=SearchForm(), folders=folders, action_form=form, ) @app.route("/admin/folder/edit", methods=["GET", "POST"]) @app.route("/admin/folder/edit/", methods=["GET", "POST"]) @db_required @auth.login_required def folder_edit(folder_id=None): folder = AnimeFolder.query.filter_by(id=folder_id).first() folder = folder if folder else AnimeFolder() form = FolderEditForm( request.form, id=folder.id, name=folder.name, path=folder.path ) if form.validate_on_submit(): # Folder folder.name = form.name.data folder.path = form.path.data db.session.add(folder) db.session.commit() return redirect(url_for("folder_list")) return render_template( "admin/folder/edit.html", search_form=SearchForm(), action_form=form ) @app.route("/admin/edit", methods=["GET", "POST"]) @app.route("/admin/edit/", methods=["GET", "POST"]) @db_required @auth.login_required def admin_edit(link_id=None): link = AnimeLink.query.filter_by(id=link_id).first() link = link if link else AnimeLink() folders = AnimeFolder.query.all() form = EditForm( request.form, id=link.id, folder=link.title.folder.id if link.title else None, name=link.title.name if link.title else None, link=link.link, season=link.season, comment=link.comment, keyword=link.title.keyword if link.title else None, ) form.folder.choices = [("", "")] + [(g.id, g.name) for g in folders] if form.validate_on_submit(): # Instance for VF tag instance = get_instance(form.link.data) # Title title = AnimeTitle.query.filter_by(id=link.title_id).first() title = ( title if title else AnimeTitle.query.filter_by(name=form.name.data).first() ) title = title if title else AnimeTitle() title.folder_id = form.folder.data title.name = form.name.data title.keyword = form.keyword.data.lower() db.session.add(title) db.session.commit() # Link link.title_id = title.id link.link = form.link.data link.season = form.season.data link.comment = form.comment.data link.vf = instance.is_vf(form.link.data) # Database db.session.add(link) db.session.commit() clean_titles() # Transmission if TRANSMISSION_ENABLED and isinstance(instance, Nyaa): if title.folder.path is not None and title.folder.path != "": download_url = link.link.replace("/view/", "/download/") + ".torrent" torrent_path = "%s/%s" % (title.folder.path, title.name) torrent = transmission.add_torrent( download_url, download_dir=torrent_path ) transmission.move_torrent_data(torrent.id, torrent_path) transmission.start_torrent(torrent.id) return redirect(url_for("admin")) return render_template( "admin/edit.html", search_form=SearchForm(), folders=folders, action_form=form ) def run(): app.run("0.0.0.0", APP_PORT, IS_DEBUG)