From 8081d419cc3d610806891cfe9072c035e7ae9ebe Mon Sep 17 00:00:00 2001 From: WorldTeacher Date: Mon, 5 May 2025 09:00:53 +0200 Subject: [PATCH 1/8] . --- .gitea/ISSUE_TEMPLATE/bug.yml | 57 +++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .gitea/ISSUE_TEMPLATE/bug.yml diff --git a/.gitea/ISSUE_TEMPLATE/bug.yml b/.gitea/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000..8837f36 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,57 @@ +name: Bug Report +description: Report a bug in this project +title: "[Bug]: " +labels: + - kind/bug + - triage/needs triage +body: + + - type: textarea + id: bug + attributes: + label: Describe the bug + description: | + A clear and concise description of what the bug is. + What did you expect to happen? What happened instead? + Include screenshots if applicable. + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: Steps to reproduce + description: | + A clear and concise description of how to reproduce the bug. + Include steps, code snippets, or screenshots if applicable. + validations: + required: true + - type: textarea + id: additional-info + attributes: + label: Additional information + description: | + Add any other context or screenshots about the bug here. + - type: dropdown + id: browsers + attributes: + label: What browsers are you seeing the problem on? + multiple: true + options: + - Firefox + - Chrome + - Safari + - Microsoft Edge + + - type: dropdown + id: operating-systems + attributes: + label: What operating systems are you seeing the problem on? + multiple: true + options: + - Windows + - macOS + - Linux + - iOS + - Android + + \ No newline at end of file From 65b929a1ca7f898a71ade8bba02e40c81e9a9010 Mon Sep 17 00:00:00 2001 From: WorldTeacher Date: Mon, 5 May 2025 12:03:53 +0200 Subject: [PATCH 2/8] feat: add ability to display info even if title in komga, fix requests, fix not all results showing up, fixes #11 fixes #10 fixes #8 --- src/app.py | 15 +++++-------- src/static/style.css | 10 ++++++++- src/templates/index.html | 47 ++++++++++++++++++++++++++++++++++------ 3 files changed, 55 insertions(+), 17 deletions(-) diff --git a/src/app.py b/src/app.py index 7ab03e8..11f2a9a 100644 --- a/src/app.py +++ b/src/app.py @@ -70,7 +70,6 @@ async def fetch_data(data: Dict[str, Any]) -> List[Dict[str, Any]]: in_komga = komga.getSeries( manga.title.english if manga.title.english else manga.title.romaji ) - print(in_komga, manga.title.english) results.append( { "id": manga.id, @@ -81,7 +80,7 @@ async def fetch_data(data: Dict[str, Any]) -> List[Dict[str, Any]]: if manga.coverImage else "https://demofree.sirv.com/nope-not-here.jpg", "status": manga.status, - "type": manga.type, + "type": manga.format, "genres": manga.genres or [], "tags": [tag.name for tag in (manga.tags or [])], "description": manga.description.replace("
", "\n") @@ -106,7 +105,7 @@ async def get_genres(): async with httpx.AsyncClient() as client: try: response = await client.post( - f"https://graphql.anilist.co", + "https://graphql.anilist.co", json={"query": GENRES_QUERY}, ) response.raise_for_status() @@ -124,7 +123,7 @@ async def get_tags(): async with httpx.AsyncClient() as client: try: response = await client.post( - f"https://graphql.anilist.co", + "https://graphql.anilist.co", json={"query": TAGS_QUERY}, ) response.raise_for_status() @@ -139,7 +138,6 @@ async def get_tags(): @app.route("/search", methods=["POST"]) async def search(): data = await request.get_json() - print(data) results = await fetch_data(data) if not results: return jsonify({"error": "No results found"}), 404 @@ -154,13 +152,12 @@ async def index(): @app.route("/request", methods=["POST"]) async def log_request(): data = await request.get_json() - item = data.get("title") + item = data.get("item") if item: asynccache = KomCache() - manga_title = data.get("title") asynccache.insert( - "INSERT INTO manga_requests (manga_id, manga_title) VALUES (?, ?)", - (item, manga_title), + "INSERT INTO manga_requests (manga_id) VALUES (?)", + (item,), ) return jsonify({"status": "success"}) return jsonify({"status": "failed"}), 400 diff --git a/src/static/style.css b/src/static/style.css index e5f0eb2..4291f69 100644 --- a/src/static/style.css +++ b/src/static/style.css @@ -155,11 +155,19 @@ body.nsfw-disabled .image-container.nsfw:hover img { /* Implement design here */ } +/* ========== KOMGA SPECIFIC STYLES ========== */ + + .card.komga { border: 3px solid green; box-sizing: border-box; /* disable the request button for entries in Komga */ + /* only allow the info button to be clicked */ + opacity: 0.3; + +} + +.card.komga .request { pointer-events: none; opacity: 0.5; - } \ No newline at end of file diff --git a/src/templates/index.html b/src/templates/index.html index b5ad9cc..4086836 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -37,7 +37,13 @@ Show NSFW content + + + {% if results %} @@ -45,7 +51,7 @@
{% for result in results %} -
+
Cover @@ -56,7 +62,7 @@

{{ result.title }}

- + {% if not result.in_komga %} @@ -86,6 +92,10 @@
+ + + + + \ No newline at end of file From 49871c1d6eedfee8b8d689a334b9a1320320e327 Mon Sep 17 00:00:00 2001 From: WorldTeacher Date: Sun, 11 May 2025 13:49:05 +0200 Subject: [PATCH 5/8] add latest updates --- src/app.py | 131 +++++++++++++++++++++++++++--------- src/static/style.css | 66 ++++++++++++++++-- src/templates/index.html | 46 +++---------- src/templates/requests.html | 11 ++- 4 files changed, 176 insertions(+), 78 deletions(-) diff --git a/src/app.py b/src/app.py index 51efd62..d58b301 100644 --- a/src/app.py +++ b/src/app.py @@ -1,11 +1,17 @@ from quart import Quart, render_template, request, jsonify import httpx -from anilistapi.queries.manga import REQUESTS_QUERY, TAGS_QUERY, GENRES_QUERY +from anilistapi.queries.manga import ( + REQUESTS_QUERY, + TAGS_QUERY, + GENRES_QUERY, + REQUESTED_QUERY, +) from anilistapi.schemas.manga import Manga from komconfig import KomConfig from komcache import KomCache from komgapi import komgapi as KOMGAPI from typing import Any, Dict, List +from limit import limit app = Quart(__name__) @@ -13,9 +19,6 @@ cache = KomCache() cache.create_table( "CREATE TABLE IF NOT EXISTS manga_requests (id INTEGER PRIMARY KEY, manga_id INTEGER, grabbed BOOLEAN DEFAULT 0)" ) -cache.create_table( - "CREATE TABLE IF NOT EXISTS manga_titles (id INTEGER PRIMARY KEY, anilist_id INTEGER DEFAULT 0, komga_title UNIQUE)" -) settings = KomConfig() @@ -25,35 +28,20 @@ komga = KOMGAPI( password=settings.komga.password, ) komga_series = komga.seriesList() -# store the entries in the database table manga_titles, komga_series is a list of strings of the series names -for series in komga_series: - # check if the series is already in the database - existing_series = cache.fetch_one( - query="SELECT komga_title FROM manga_titles WHERE komga_title = ?", - args=(series,), - ) - if existing_series: - # series already exists, skip - continue - else: - cache.insert( - # insert into if not in database - query="INSERT OR IGNORE INTO manga_titles (komga_title) VALUES (?)", - args=(series,), - ) -komga_series = [series.lower() for series in komga_series] -# Update type annotations for fetch_data -async def fetch_data(data: Dict[str, Any]) -> List[Dict[str, Any]]: +@limit(90, 60) +async def fetch_data( + data: Dict[str, Any], query: str = REQUESTS_QUERY +) -> List[Dict[str, Any]]: async with httpx.AsyncClient() as client: try: - variables: Dict[str, Any] = {"search": data["query"]} - if len(data["genres"]) > 0: + variables: Dict[str, Any] = {"search": data["query"], "type": "MANGA"} + if data.get("genres"): variables["genres"] = data["genres"] - if len(data["tags"]) > 0: + if data.get("tags"): variables["tags"] = data["tags"] - print(data["query"], variables) + # print(data["query"], variables) response = await client.post( "https://graphql.anilist.co", json={ @@ -62,6 +50,7 @@ async def fetch_data(data: Dict[str, Any]) -> List[Dict[str, Any]]: }, ) response.raise_for_status() + data = response.json() results: List[Dict[str, Any]] = [] @@ -74,9 +63,7 @@ async def fetch_data(data: Dict[str, Any]) -> List[Dict[str, Any]]: query="SELECT manga_id, grabbed FROM manga_requests WHERE manga_id = ?", args=(manga.id,), ) - komga_request = False - if requested: - komga_request = True + komga_request = bool(requested) results.append( { @@ -109,6 +96,73 @@ async def fetch_data(data: Dict[str, Any]) -> List[Dict[str, Any]]: return [] +async def fetch_requested_data(data: list[int]) -> List[Dict[str, Any]]: + requested_data: list[dict[str, Any]] = [] + for manga_id in data: + async with httpx.AsyncClient() as client: + try: + variables: Dict[str, Any] = { + "search": manga_id, + "type": "MANGA", + } # TODO: replace the type once the UI has been updated + # print(data, variables) + response = await client.post( + "https://graphql.anilist.co", + json={ + "query": REQUESTED_QUERY, + "variables": variables, + }, + ) + response.raise_for_status() + data = response.json() + + # print(data) + entry = data.get("data", {}).get("Media", {}) + if entry: + manga = Manga(**entry) + + in_komga = komga.getSeries( + manga.title.english + if manga.title.english + else manga.title.romaji + ) + requested = cache.fetch_one( + query="SELECT manga_id, grabbed FROM manga_requests WHERE manga_id = ?", + args=(manga.id,), + ) + komga_request = bool(requested) + + requested_data.append( + { + "id": manga.id, + "title": manga.title.english + if manga.title.english + else manga.title.romaji, + "image": manga.coverImage.get("large") + if manga.coverImage + else "https://demofree.sirv.com/nope-not-here.jpg", + "status": manga.status, + "type": manga.format, + "genres": manga.genres or [], + "tags": [tag.name for tag in (manga.tags or [])], + "description": manga.description.replace("
", "\n") + if manga.description + else "No description available", + "isAdult": manga.isAdult, + "in_komga": in_komga, + "requested": komga_request, + } + ) + + except httpx.RequestError as e: + print(f"An error occurred while requesting data: {e}") + requested_data.append({"id": manga_id}) + except Exception as e: + print(f"Unexpected error: {e}") + requested_data.append({"id": manga_id}) + return requested_data + + @app.route("/api/genres") async def get_genres(): async with httpx.AsyncClient() as client: @@ -147,6 +201,7 @@ async def get_tags(): @app.route("/search", methods=["POST"]) async def search(): data = await request.get_json() + print(data) results = await fetch_data(data) if not results: return jsonify({"error": "No results found"}), 404 @@ -172,5 +227,21 @@ async def log_request(): return jsonify({"status": "failed"}), 400 +@app.route("/requests", methods=["GET"]) +async def requests_page(): + requests = ( + cache.fetch_all(query="SELECT manga_id, grabbed FROM manga_requests") or [] + ) + entries: List[Dict[str, Any]] = [] + req_ids = [req[0] for req in requests] + if req_ids: + entries = await fetch_requested_data(req_ids) + else: + entries = [] + print(entries) + + return await render_template("requests.html", requests=entries) + + if __name__ == "__main__": app.run(debug=True, host="0.0.0.0", port=5001) diff --git a/src/static/style.css b/src/static/style.css index 7ce6d90..cc986aa 100644 --- a/src/static/style.css +++ b/src/static/style.css @@ -1,8 +1,9 @@ +/* other styles are in magic.css */ /* ========== GRID LAYOUT ========== */ .results { display: grid; - grid-template-columns: repeat(5, 1fr); - gap: 20px; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 1em; margin-top: 20px; } @@ -20,11 +21,16 @@ border: 1px solid #ccc; background: #fafafa; text-align: center; - transition: box-shadow 0.2s; + transition: box-shadow 0.2s transform 0.2s; + border-radius: 10px; } .card:hover { box-shadow: 0 0 10px #aaa; + transform: scale(1.1); + /* pop out by 10% */ + z-index: 2; + /* ensure it appears above others */ } .card img { @@ -39,6 +45,32 @@ justify-content: space-around; } +.actions .info { + background-color: #007bff; + color: white; + border: none; + padding: 0.5em 1em; + cursor: pointer; + border-radius: 5px; + margin-right: 0.5em; +} + +.actions .info:hover { + background-color: #0056b3; +} + +.actions .request { + background-color: #28a745; + color: white; + border: none; + padding: 0.5em 1em; + cursor: pointer; + border-radius: 5px; +} + +.actions .request:hover { + background-color: #1e7e34; +} /* ========== MODAL ========== */ .modal { position: fixed; @@ -84,12 +116,15 @@ /* ========== BLUR CONTROL VIA BODY CLASS ========== */ body.nsfw-disabled .image-container.nsfw img { - filter: blur(8px); + filter: grayscale(100%) blur(5px) !important; + /* Ensure the blur is applied */ + transition: filter 0.3s ease; + /* Add smooth transition */ } body.nsfw-disabled .image-container.nsfw:hover img { - filter: none; - /* Remove blur on hover, set as important */ + filter: none !important; + /* Remove blur on hover */ } @@ -145,10 +180,23 @@ body.nsfw-disabled .image-container.nsfw:hover img { .reset { /* Implement design here */ + background-color: #f44336; + /* round the edges of the button */ + border-radius: 9px; + + } + + .selectors { + display: flex; + gap: 10px; + margin-bottom: 8px; + margin-top: 5px; + flex-wrap: wrap; } .info { /* Implement design here */ + background-color: #4CAF50; } .request { @@ -183,4 +231,10 @@ body.nsfw-disabled .image-container.nsfw:hover img { .card.komga .request { pointer-events: none; opacity: 0.5; +} +.card.komga:hover, +.card.requested:hover { + transform: none !important; + box-shadow: none !important; + z-index: auto !important; } \ No newline at end of file diff --git a/src/templates/index.html b/src/templates/index.html index b850101..6b66161 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -4,7 +4,6 @@ Anime Search and Request Page - @@ -45,17 +44,15 @@
- +
- {% if results %} - +
+ {% if results %} {% for result in results %} -
- +
Cover @@ -74,38 +71,11 @@
{% endfor %} + {% endif %}
- {% endif %} - {% if results %} -
- {% for result in results %} - -
- -
- - Cover - {% if result.isAdult %} -
18+
- {% endif %} -
- -

{{ result.title }}

-
- - - {% if not result.in_komga %} - - {% endif %} -
-
- {% endfor %} -
- {% endif %} - {% endif %} {% if requests %} From 1d0f293e19279d2250d00cbf32ee2775bea8b5de Mon Sep 17 00:00:00 2001 From: WorldTeacher Date: Sun, 11 May 2025 14:16:56 +0200 Subject: [PATCH 6/8] feat: add type selector and limit results based on set type --- pyproject.toml | 1 + src/app.py | 7 +++++-- src/templates/index.html | 16 +++++++++++++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2640fc7..85fada5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ dependencies = [ "httpx>=0.28.1", "jinja2>=3.1.6", "komgapi", + "limit>=0.2.3", "quart>=0.20.0", ] diff --git a/src/app.py b/src/app.py index d58b301..41d80d9 100644 --- a/src/app.py +++ b/src/app.py @@ -36,12 +36,15 @@ async def fetch_data( ) -> List[Dict[str, Any]]: async with httpx.AsyncClient() as client: try: - variables: Dict[str, Any] = {"search": data["query"], "type": "MANGA"} + variables: Dict[str, Any] = {"search": data["query"]} if data.get("genres"): variables["genres"] = data["genres"] if data.get("tags"): variables["tags"] = data["tags"] - # print(data["query"], variables) + if data.get("type"): + if data["type"] in ["MANGA", "NOVEL"]: + variables["format"] = data["type"] + print(data["query"], variables) response = await client.post( "https://graphql.anilist.co", json={ diff --git a/src/templates/index.html b/src/templates/index.html index 6b66161..7387e41 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -14,6 +14,7 @@
+ @@ -30,6 +31,14 @@
+
+ + +
@@ -256,13 +265,14 @@ const searchTerm = document.getElementById("searchInput").value.trim(); const selectedGenres = document.getElementById("genreInput").value.split(',').map(v => v.trim()).filter(v => v); const selectedTags = document.getElementById("tagInput").value.split(',').map(v => v.trim()).filter(v => v); - - + const selectedType = document.getElementById("typeSelect").value; const query = { query: searchTerm, genres: selectedGenres, - tags: selectedTags + tags: selectedTags, + type: selectedType // Include the selected type in the query + }; const loadingElement = document.getElementById("loading"); From 10b47d2f13dc91cf623ba36f47f64bcf3d7755a8 Mon Sep 17 00:00:00 2001 From: WorldTeacher Date: Fri, 23 May 2025 15:45:10 +0200 Subject: [PATCH 7/8] update files, add release workflow --- .bumpversion.toml | 21 ++++++ .gitea/workflows/release.yml | 120 +++++++++++++++++++++++++++++++++++ .gitignore | 1 - pyproject.toml | 14 ++-- requirements.txt | 88 +++++++++++++++++++++++++ src/app.py | 112 ++++++++++++++++++++++++++------ src/static/style.css | 7 -- src/templates/index.html | 2 + src/templates/requests.html | 84 ++++++++++++------------ 9 files changed, 372 insertions(+), 77 deletions(-) create mode 100644 .bumpversion.toml create mode 100644 .gitea/workflows/release.yml create mode 100644 requirements.txt diff --git a/.bumpversion.toml b/.bumpversion.toml new file mode 100644 index 0000000..894b569 --- /dev/null +++ b/.bumpversion.toml @@ -0,0 +1,21 @@ +[tool.bumpversion] +current_version = "0.1.0" +parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)" +serialize = ["{major}.{minor}.{patch}"] +search = "{current_version}" +replace = "{new_version}" +regex = false +ignore_missing_version = false +ignore_missing_files = false +tag = false +sign_tags = false +tag_name = "v{new_version}" +tag_message = "Bump version: {current_version} → {new_version}" +allow_dirty = false +commit = false +message = "Bump version: {current_version} → {new_version}" +moveable_tags = [] +commit_args = "" +setup_hooks = [] +pre_commit_hooks = [] +post_commit_hooks = [] diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..d0d5e93 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,120 @@ +on: + workflow_dispatch: + inputs: + release_notes: + description: Release notes (use \n for newlines) + type: string + required: false + github_release: + description: 'Create Gitea Release' + default: true + type: boolean + docker_release: + description: 'Push Docker images' + default: true + type: boolean + bump: + description: 'Bump type' + required: true + default: 'patch' + type: choice + options: + - 'major' + - 'minor' + - 'patch' + + + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{ secrets.REGISTRY }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.TOKEN }} + + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: Set up Python + run: uv python install + with: + python-version-file: "pyproject.toml" + + - name: Install the project + run: uv sync --locked --all-extras --dev + - name: Set Git identity + run: | + git config user.name "Gitea CI" + git config user.email "ci@git.theprivateserver.de" + - name: Bump version + id: bump + run: | + uv tool install bump-my-version + uv tool run bump-my-version bump ${{ github.event.inputs.bump }} + # echo the version to github env, the version is shown by using uv tool run bump-my-version show current_version + echo "VERSION<> $GITHUB_ENV + echo "$(uv tool run bump-my-version show current_version)" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + - name: Check version + run: echo ${{ env.VERSION }} + - name: Push changes + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ github.ref }} + + - name: Add release notes to environment + id: add_release_notes + run: | + echo "RELEASE_NOTES<> $GITHUB_ENV + echo "${{ github.event.inputs.release_notes }}" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + - name: Build and store Docker image + if: ${{ github.event.inputs.docker_release == 'true' }} + env: + TAG: ${{ github.sha }} + run: | + REPO_NAME=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') + docker buildx build \ + --platform linux/amd64 \ + --tag ${{ secrets.REGISTRY }}/${REPO_NAME}:latest \ + --tag ${{ secrets.REGISTRY }}/${REPO_NAME}:${TAG} \ + --push . + - name: Generate changelog + id: changelog + uses: metcalfc/changelog-generator@v4.6.2 + with: + token: ${{ secrets.TOKEN }} + - name: Get the changelog + run: | + cat << "EOF" + ${{ steps.changelog.outputs.changelog }} + EOF + + - name: Create release + id: create_release + if: ${{ github.event.inputs.github_release == 'true' }} + uses: softprops/action-gh-release@master + with: + tag_name: ${{ env.VERSION }} + release_name: Release ${{ env.VERSION }} + body: ${{ steps.changelog.outputs.changelog }} + draft: false + prerelease: false + make_latest: true + env: + GITHUB_TOKEN: ${{ secrets.TOKEN }} + diff --git a/.gitignore b/.gitignore index ed468fc..7037468 100644 --- a/.gitignore +++ b/.gitignore @@ -227,7 +227,6 @@ docs/ config.yaml **/tempCodeRunnerFile.py -uv.lock .history .venv venv diff --git a/pyproject.toml b/pyproject.toml index 85fada5..1b6eb5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,16 +5,22 @@ description = "Add your description here" readme = "README.md" requires-python = ">=3.13" dependencies = [ - "flask>=3.1.0", + "anilistapi", + "flask>=3.1.1", "httpx>=0.28.1", + "httpx-retries>=0.3.2", "jinja2>=3.1.6", + "komcache", + "komconfig", "komgapi", + "komsuite-nyaapy", "limit>=0.2.3", "quart>=0.20.0", ] [tool.uv.sources] -anilistapi = { workspace = true } -komconfig = { workspace = true } -komcache = { workspace = true } +komsuite-nyaapy = { workspace = true } komgapi = { workspace = true } +komcache = { workspace = true } +komconfig = { workspace = true } +anilistapi = { workspace = true } diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..fed2509 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,88 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile pyproject.toml -o requirements.txt +-e file:///home/alexander/GitHub/KomSuite/packages/KomConfig + # via komgapi +-e file:///home/alexander/GitHub/KomSuite/packages/komgAPI + # via kompage (pyproject.toml) +aiofiles==24.1.0 + # via quart +antlr4-python3-runtime==4.9.3 + # via omegaconf +anyio==4.9.0 + # via httpx +blinker==1.9.0 + # via + # flask + # quart +certifi==2025.4.26 + # via + # httpcore + # httpx +click==8.2.0 + # via + # flask + # quart +flask==3.1.1 + # via + # kompage (pyproject.toml) + # quart +h11==0.16.0 + # via + # httpcore + # hypercorn + # wsproto +h2==4.2.0 + # via hypercorn +hpack==4.1.0 + # via h2 +httpcore==1.0.9 + # via httpx +httpx==0.28.1 + # via + # kompage (pyproject.toml) + # komgapi +hypercorn==0.17.3 + # via quart +hyperframe==6.1.0 + # via h2 +idna==3.10 + # via + # anyio + # httpx +itsdangerous==2.2.0 + # via + # flask + # quart +jinja2==3.1.6 + # via + # kompage (pyproject.toml) + # flask + # quart +limit==0.2.3 + # via kompage (pyproject.toml) +loguru==0.7.3 + # via komgapi +markupsafe==3.0.2 + # via + # flask + # jinja2 + # quart + # werkzeug +omegaconf==2.3.0 + # via komconfig +priority==2.0.0 + # via hypercorn +pyyaml==6.0.2 + # via omegaconf +quart==0.20.0 + # via kompage (pyproject.toml) +sniffio==1.3.1 + # via anyio +typing-extensions==4.13.2 + # via komgapi +werkzeug==3.1.3 + # via + # flask + # quart +wsproto==1.2.0 + # via hypercorn diff --git a/src/app.py b/src/app.py index 41d80d9..766d15f 100644 --- a/src/app.py +++ b/src/app.py @@ -12,7 +12,14 @@ from komcache import KomCache from komgapi import komgapi as KOMGAPI from typing import Any, Dict, List from limit import limit +import loguru +import sys +from komsuite_nyaapy import Nyaa +log = loguru.logger +log.remove() +log.add("application.log", rotation="1 week", retention="1 month") +log.add(sys.stdout) app = Quart(__name__) cache = KomCache() @@ -32,19 +39,28 @@ komga_series = komga.seriesList() @limit(90, 60) async def fetch_data( - data: Dict[str, Any], query: str = REQUESTS_QUERY + inputdata: Dict[str, Any], check_downloads: bool = False ) -> List[Dict[str, Any]]: + log.error(f"fetch_data called with data: {inputdata}") async with httpx.AsyncClient() as client: try: - variables: Dict[str, Any] = {"search": data["query"]} - if data.get("genres"): - variables["genres"] = data["genres"] - if data.get("tags"): - variables["tags"] = data["tags"] - if data.get("type"): - if data["type"] in ["MANGA", "NOVEL"]: - variables["format"] = data["type"] - print(data["query"], variables) + # Log the incoming data for debugging + + # Ensure 'query' key exists in the data dictionary + if "query" not in inputdata or not inputdata["query"]: + raise ValueError("Missing or empty 'query' key in data") + + variables: Dict[str, Any] = {"search": inputdata["query"]} + if inputdata.get("genres"): + variables["genres"] = inputdata["genres"] + if inputdata.get("tags"): + variables["tags"] = inputdata["tags"] + if inputdata.get("type"): + if inputdata["type"] in ["MANGA", "NOVEL"]: + variables["format"] = inputdata["type"] + + log.debug(f"GraphQL variables: {variables}") + response = await client.post( "https://graphql.anilist.co", json={ @@ -55,6 +71,7 @@ async def fetch_data( response.raise_for_status() data = response.json() + log.debug(f"GraphQL response: {data}") results: List[Dict[str, Any]] = [] for item in data.get("data", {}).get("Page", {}).get("media", []): @@ -67,7 +84,6 @@ async def fetch_data( args=(manga.id,), ) komga_request = bool(requested) - results.append( { "id": manga.id, @@ -87,18 +103,30 @@ async def fetch_data( "isAdult": manga.isAdult, "in_komga": in_komga, "requested": komga_request, + "siteUrl": manga.siteUrl, } ) + if check_downloads: + for result in results: + downloads = Nyaa().search(result.get("title"), 3, 1) + else: + downloads = [] + result["download"] = len(downloads) if downloads else 0 + log.debug(f"Fetched {len(results)} results for query: {inputdata['query']}") + log.error(f"Results: {results}") return results + except ValueError as ve: + log.error(f"ValueError in fetch_data: {ve}") + return [] except httpx.RequestError as e: - print(f"An error occurred while requesting data: {e}") + log.error(f"HTTP request error in fetch_data: {e}") return [] except Exception as e: - print(f"Unexpected error: {e}") + log.error(f"Unexpected error in fetch_data: {e}") return [] - +@limit(90, 60) async def fetch_requested_data(data: list[int]) -> List[Dict[str, Any]]: requested_data: list[dict[str, Any]] = [] for manga_id in data: @@ -130,7 +158,7 @@ async def fetch_requested_data(data: list[int]) -> List[Dict[str, Any]]: else manga.title.romaji ) requested = cache.fetch_one( - query="SELECT manga_id, grabbed FROM manga_requests WHERE manga_id = ?", + query="SELECT grabbed FROM manga_requests WHERE manga_id = ?", args=(manga.id,), ) komga_request = bool(requested) @@ -143,19 +171,22 @@ async def fetch_requested_data(data: list[int]) -> List[Dict[str, Any]]: else manga.title.romaji, "image": manga.coverImage.get("large") if manga.coverImage - else "https://demofree.sirv.com/nope-not-here.jpg", + else None, "status": manga.status, "type": manga.format, "genres": manga.genres or [], "tags": [tag.name for tag in (manga.tags or [])], "description": manga.description.replace("
", "\n") if manga.description - else "No description available", + else None, "isAdult": manga.isAdult, - "in_komga": in_komga, - "requested": komga_request, + "in_komga": bool(in_komga), + "requested": bool(komga_request), } ) + log.debug( + requested_data, + ) except httpx.RequestError as e: print(f"An error occurred while requesting data: {e}") @@ -211,6 +242,43 @@ async def search(): return jsonify(results) +@app.route("/api/search", methods=["GET"]) +async def api_search(): + """ + API endpoint for searching manga. + + Parameters: + - q: Query string to search for + + Returns: + - JSON array of matching manga entries + """ + try: + query = request.args.get("q") + + if not query: + return jsonify({"error": "Missing required parameter: q"}), 400 + + # Build search parameters + search_data: Dict[str, Any] = {"query": query} + + # Add optional filters if provided + + log.info(f"API search request: {search_data}") + + # Fetch results using existing fetch_data function + results = await fetch_data(search_data, check_downloads=False) + + if not results: + return jsonify({"results": [], "count": 0}), 404 + + return jsonify({"results": results, "count": len(results)}) + + except Exception as e: + log.error(f"Error in API search: {e}") + return jsonify({"error": "An unexpected error occurred"}), 500 + + @app.route("/", methods=["GET"]) async def index(): return await render_template("index.html", komga_series=komga_series) @@ -241,9 +309,11 @@ async def requests_page(): entries = await fetch_requested_data(req_ids) else: entries = [] - print(entries) + data = [] + for entry in entries: + data.append(dict(entry)) - return await render_template("requests.html", requests=entries) + return await render_template("requests.html", requests=data) if __name__ == "__main__": diff --git a/src/static/style.css b/src/static/style.css index cc986aa..514b1e3 100644 --- a/src/static/style.css +++ b/src/static/style.css @@ -194,14 +194,7 @@ body.nsfw-disabled .image-container.nsfw:hover img { flex-wrap: wrap; } -.info { - /* Implement design here */ - background-color: #4CAF50; -} -.request { - /* Implement design here */ -} /* ========== KOMGA SPECIFIC STYLES ========== */ diff --git a/src/templates/index.html b/src/templates/index.html index 7387e41..dd30f1c 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -46,6 +46,8 @@ Unblur NSFW images + +
- -
- {% if results %} - {% for result in results %} - -
- -
- - Cover - {% if result.isAdult %} -
18+
- {% endif %} -
- -

{{ result.title }}

-
- - - -
-
- {% endfor %} - {% endif %} -
- - {% if requests %} -
+ {% if requests %} {% for request in requests %} - -
+
- Cover {% if request.isAdult %}
18+
@@ -57,16 +28,17 @@

{{ request.title }}

- +
{% endfor %} + {% endif %}
- {% else %} + {% if not requests %}

No requests found.

{% endif %} - +