Beispiel #1
0
async def login() -> Any:
    if await current_user.is_authenticated:
        return redirect(url_for("ux.index"))

    if request.method == "GET":
        fluent = get_injector(["login"])
        return await render_template("login.html", **{"_": fluent.format_value})
    else:
        resources = [
            "login"
        ]
        fluent = get_injector(resources)

        form = await request.form

        username = form.get("username")
        password = form.get("password")
        if username is None or password is None:
            await flash(fluent._("form-missing-data"))
            return redirect(url_for("ux.login"))

        async with app.acquire_db() as con:
            await con.execute("""
                SELECT
                    id,
                    password_hash
                FROM
                    users
                WHERE LOWER(username) = ?;
            """, username.lower())
            user_data = await con.fetchone()

        if not user_data:
            await flash(fluent._("invalid-credentials"))
            return redirect(url_for("ux.login"))

        try:
            hasher.verify(user_data["password_hash"], password)
        except VerifyMismatchError:
            await flash(fluent._("invalid-credentials"))
            return redirect(url_for("ux.login"))

        if hasher.check_needs_rehash(user_data["password_hash"]):
            async with app.acquire_db() as con:
                await con.execute("""
                    UPDATE
                        users
                    SET
                        password_hash=?
                    WHERE username=?;
                """, hasher.hash(password), username)

        remember = form.get("remember", False)

        login_user(User(user_data["id"]), remember=remember)

        return redirect(url_for("ux.index"))
Beispiel #2
0
async def config_encode() -> APIResponse:
    async with app.acquire_db() as con:
        await con.execute("""
            INSERT OR IGNORE INTO
                encode_config (
                    id
                )
            VALUES (0);
            """)

    if request.method == "PATCH":
        arguments = await request.get_json()

        CFG_KEYS = ("enabled", "quality_preset", "speed_preset",
                    "maximum_encodes", "retry_on_fail")
        if any(k not in CFG_KEYS for k in arguments.keys()):
            return APIResponse(status=400, error="Invalid configuration keys.")
        elif arguments.get("speed_preset"
                           ) and arguments["speed_preset"] not in VALID_SPEEDS:
            return APIResponse(status=400, error="Invalid speed preset.")
        elif arguments.get(
                "quality_preset") and arguments["quality_preset"] not in (
                    "high", "low", "moderate"):
            return APIResponse(status=400, error="Invalid quality preset.")
        elif arguments.get("maximum_encodes") and int(
                arguments["maximum_encodes"]) <= 0:
            arguments["maximum_encodes"] = 1

        async with app.acquire_db() as con:
            sets = ", ".join(f"{col} = ?" for col in arguments.keys())
            await con.execute(
                f"""
                UPDATE
                    encode_config
                SET
                    {sets}
                WHERE id = 0;
            """, *arguments.values())

    async with app.acquire_db() as con:
        await con.execute("""
            SELECT
                enabled,
                quality_preset,
                speed_preset,
                maximum_encodes,
                retry_on_fail
            FROM
                encode_config;
        """)
        cfg = await con.fetchone()

    cfg = dict(cfg)
    cfg["has_ffmpeg"] = await app.encoder.has_ffmpeg()

    return APIResponse(status=200, result=cfg)
Beispiel #3
0
    async def get(self, show_id: int, entry_id: Optional[int]) -> APIResponse:
        """
        Retrieve all entries or a single entry
        for a specified show.

        Returns
        -------
        APIResponse
            A dict or a list of dict containing
            the requested entry information.
        """
        if entry_id is None:
            async with app.acquire_db() as con:
                await con.execute("""
                    SELECT
                        id,
                        episode,
                        current_state,
                        torrent_hash
                    FROM
                        show_entry
                    WHERE show_id=?;
                """, show_id)
                entries = await con.fetchall()

            return APIResponse(
                result=[dict(record) for record in entries]
            )
        else:
            async with app.acquire_db() as con:
                await con.execute("""
                    SELECT
                        id,
                        episode,
                        current_state,
                        torrent_hash
                    FROM
                        show_entry
                    WHERE id=?;
                """, entry_id)
                entry = await con.fetchone()

            if entry is None:
                return APIResponse(
                    status=404,
                    error="Entry with specified ID does not exist."
                )

            return APIResponse(
                result=dict(entry)
            )
Beispiel #4
0
async def update_context() -> dict:
    async with app.acquire_db() as con:
        await con.execute("""
            SELECT
                COUNT(*)
            FROM
                shows;
        """)
        shows = await con.fetchval()
        await con.execute("""
            SELECT
                COUNT(*)
            FROM
                show_entry;
        """)
        entries = await con.fetchval()

    stats = {
        "shows": shows,
        "entries": entries,
        "seen": len(app.seen_titles),
        "version": version
    }

    return {
        "stats": stats,
        "updates": app.update_info,
        "docker": os.environ.get("IS_DOCKER", False)
    }
Beispiel #5
0
async def nyaa_search() -> str:
    ctx = {}

    resources = [
        "base"
    ]

    fluent = get_injector(resources)
    ctx["_"] = fluent.format_value

    async with app.acquire_db() as con:
        await con.execute("""
            SELECT
                id,
                title
            FROM
                shows
            ORDER BY title;
        """)
        shows = await con.fetchall()
        ctx["shows"] = [dict(s) for s in shows]

    ctx["seen_titles"] = list(app.seen_titles)

    return await render_template("nyaa_search.html", **ctx)
Beispiel #6
0
async def ensure_auth() -> Optional[APIResponse]:
    authed = False
    if request.headers.get("Authorization"):
        token = request.headers["Authorization"]
        async with app.acquire_db() as con:
            try:
                await con.execute(
                    """
                    SELECT
                        id
                    FROM
                        users
                    WHERE
                        api_key=?;
                """, token)
                user = await con.fetchval()

                if user:
                    authed = True
            except Exception:
                pass
    if not authed and await current_user.is_authenticated:
        authed = True

    if not authed:
        return APIResponse(
            status=401,
            result="You are not authorized to access this resource.")

    return None
Beispiel #7
0
    async def post(self) -> APIResponse:
        """
        Adds a search result to Tsundoku.

        .. :quickref: Nyaa; Add search result

        :status 200: task performed succesfully
        :status 400: invalid parameters
        :status 404: show id passed not found

        :form integer show_id: The show to add the result to.
        :form string torrent_link: A comma-separated list of webhook triggers.

        :returns: :class:`dict`
        """
        arguments = await request.get_json()

        show_id = arguments.get("show_id")
        torrent_link = arguments.get("torrent_link")
        overwrite = arguments.get("overwrite")

        if not show_id or not torrent_link:
            return APIResponse(status=400,
                               error="Missing required parameters.")

        try:
            show_id = int(show_id)
        except ValueError:
            return APIResponse(status=400,
                               error="Show ID passed is not an integer.")

        async with app.acquire_db() as con:
            await con.execute(
                """
                SELECT
                    id
                FROM
                    shows
                WHERE id=?;
            """, show_id)
            show_id = await con.fetchval()

        if not show_id:
            return APIResponse(status=404,
                               error="Show ID does not exist in the database.")

        search_result = SearchResult.from_necessary(app, show_id, torrent_link)

        logger.info(f"Processing new search result for Show <s{show_id}>")

        entries = await search_result.process(overwrite=overwrite)

        return APIResponse(result=[e.to_dict() for e in entries])
Beispiel #8
0
async def config_token() -> APIResponse:
    api_key = request.headers.get(
        "Authorization") or await current_user.api_key
    if request.method == "POST":
        async with app.acquire_db() as con:
            new_key = str(uuid4())
            await con.execute(
                """
                UPDATE
                    users
                SET
                    api_key = ?
                WHERE
                    api_key = ?;
            """, new_key, api_key)
    else:
        new_key = api_key

    return APIResponse(status=200, result=str(new_key))
Beispiel #9
0
    async def delete(self, show_id: int) -> APIResponse:
        """
        Deletes a show with the specified ID.

        This will delete all entries of that show as well.

        .. :quickref: Shows; Delete a show.

        :status 200: deletion request received

        :returns: :class:`bool`
        """
        async with app.acquire_db() as con:
            await con.execute(
                """
                DELETE FROM
                    shows
                WHERE id=?;
            """, show_id)

        logger.info(f"Show Deleted - <s{show_id}>")

        return APIResponse(result=True)
Beispiel #10
0
    async def delete(self, show_id: int, entry_id: int) -> APIResponse:
        """
        Deletes a single entry from a show.

        .. :quickref: Entries; Delete an entry.

        :status 200: entry successfully deleted
        :status 404: entry with passed id not found

        :returns: :class:`bool`
        """
        async with app.acquire_db() as con:
            await con.execute("""
                DELETE FROM
                    show_entry
                WHERE id=?;
            """, entry_id)

        logger.info(f"Entry Deleted - <e{entry_id}>")

        return APIResponse(
            result=True
        )
Beispiel #11
0
    async def post(self, show_id: None) -> APIResponse:
        """
        Adds a new show to the database.

        .. :quickref: Shows; Add a new show.

        :status 200: show added successfully
        :status 400: bad or missing arguments
        :status 500: unexpected server error

        :form string title: the new show's title
        :form string desired_format: the new show's desired file format
        :form string desired_folder: the new show's target folder for moving
        :form integer season: the season to use when naming the show
        :form integer episode_offset: the episode offset to use when renaming (default :code:`0`)

        :returns: :class:`dict`
        """
        # show_id here will always be None. Having it as a parameter
        # is required due to how the defaults are handled with GET
        # and POST methods on the routing table.
        arguments = await request.get_json()

        # patch for ajax call
        if not arguments:
            await request.get_data()
            arguments = await request.form

        desired_format = arguments.get("desired_format")
        desired_folder = arguments.get("desired_folder")

        season = arguments.get("season")
        if season is None:
            return APIResponse(status=400, error="Missing season argument.")

        try:
            season = int(season)
        except ValueError:
            return APIResponse(status=400, error="Season is not an integer.")

        episode_offset = arguments.get("episode_offset")
        if episode_offset is None:
            episode_offset = 0
        else:
            try:
                episode_offset = int(episode_offset)
            except ValueError:
                return APIResponse(status=400,
                                   error="Episode offset is not an integer.")

        show = await Show.insert(title=arguments["title"],
                                 desired_format=desired_format,
                                 desired_folder=desired_folder,
                                 season=season,
                                 episode_offset=episode_offset,
                                 watch=arguments.get("watch", True))

        async with app.acquire_db() as con:
            await con.execute(
                """
                INSERT OR IGNORE INTO
                    webhook
                    (show_id, base)
                SELECT (?), id FROM webhook_base;
            """, show.id_)

        logger.info(
            f"New Show Added, <s{show.id_}> - Preparing to Check for New Releases"
        )
        app.poller.reset_rss_cache()
        await app.poller.poll()

        show = await Show.from_id(show.id_)

        return APIResponse(result=show.to_dict())
Beispiel #12
0
    async def post(self, show_id: int, entry_id: int = None) -> APIResponse:
        """
        Manually begins handling of an entry for a specified show.
        Handling involves downloading, moving, and renaming.

        If an empty string is passed for a magnet URL, nothing will
        be downloaded and the entry will be marked as complete.

        .. :quickref: Entries; Add an entry.

        :status 200: entry added successfully
        :status 400: invalid arguments

        :form integer episode: the entry's episode
        :form string magnet: the entry's magnet url

        :returns: :class:`dict`
        """
        arguments = await request.get_json()
        required_arguments = {"episode", "magnet"}

        if all(arg not in arguments.keys() for arg in required_arguments):
            return APIResponse(
                status=400,
                error="Too many arguments or missing required argument."
            )

        try:
            episode = int(arguments["episode"])
        except ValueError:
            return APIResponse(
                status=400,
                error="Episode argument must be an integer."
            )

        if arguments["magnet"]:
            magnet = await app.dl_client.get_magnet(arguments["magnet"])
            entry_id = await app.downloader.begin_handling(show_id, episode, magnet)
        else:
            async with app.acquire_db() as con:
                await con.execute("""
                    INSERT INTO
                        show_entry (
                            show_id,
                            episode,
                            current_state,
                            torrent_hash
                        )
                    VALUES
                        (?, ?, ?, ?);
                """, show_id, episode, "completed", "")
                entry_id = con.lastrowid

        async with app.acquire_db() as con:
            await con.execute("""
                SELECT
                    id,
                    show_id,
                    episode,
                    current_state,
                    torrent_hash,
                    file_path,
                    last_update
                FROM
                    show_entry
                WHERE id=?;
            """, entry_id)
            new_entry = await con.fetchone()

        entry = Entry(app, new_entry)

        logger.info(f"Entry Manually Added - <e{entry.id}>")

        await entry._handle_webhooks()

        return APIResponse(
            result=entry.to_dict()
        )