| import os |
| import aiohttp |
| import asyncio |
| import logging |
| from flask import Flask, render_template, request, jsonify |
|
|
| app = Flask(__name__) |
|
|
| |
| logging.basicConfig(level=logging.INFO) |
| logger = logging.getLogger("web_app") |
|
|
| |
| BASE_URL = os.getenv("BASE_URL") |
| MANGA_PATH = os.getenv("MANGA_API_PATH") |
| CHAPTER_PATH = os.getenv("CHAPTER_API_PATH") |
|
|
| PORT = int(os.getenv("PORT", 7860)) |
|
|
| |
| HEADERS = { |
| "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", |
| "Accept": "application/json, text/plain, */*", |
| "Accept-Language": "ar,en-US;q=0.9,en;q=0.8", |
| "Referer": "https://utoon.net/", |
| "Origin": "https://utoon.net", |
| "Sec-Fetch-Dest": "empty", |
| "Sec-Fetch-Mode": "cors", |
| "Sec-Fetch-Site": "same-origin", |
| } |
|
|
| async def get_chapters(manga_url): |
| slug = manga_url.strip().rstrip("/").split("/")[-1] |
| try: |
| api_url = f"{BASE_URL}{MANGA_PATH.format(slug=slug)}" |
| logger.info(f"🔍 Fetching Manga: {api_url}") |
| |
| |
| async with aiohttp.ClientSession(headers=HEADERS) as session: |
| async with session.get(api_url, timeout=15) as resp: |
| logger.info(f"📡 API Response Status: {resp.status}") |
| if resp.status == 403: |
| logger.error("🚫 Access Denied (403). Site is blocking Hugging Face.") |
| return None |
| if resp.status != 200: |
| return None |
| data = await resp.json() |
| mangas = data.get("mangas", []) |
| if not mangas: return None |
| m = mangas[0] |
| return { |
| "title": m.get("post_title", slug), |
| "thumbnail": m.get("thumbnail", ""), |
| "chapters": [ |
| {"id": c["id_capitulo"], "title": c["nombre"], "slug": c["slug"]} |
| for c in m.get("capitulos", []) |
| ] |
| } |
| except Exception as e: |
| logger.error(f"💥 Scraper Error: {str(e)}") |
| return None |
|
|
| async def get_images(chapter_id): |
| try: |
| api_url = f"{BASE_URL}{CHAPTER_PATH.format(id=chapter_id)}" |
| async with aiohttp.ClientSession(headers=HEADERS) as session: |
| async with session.get(api_url, timeout=15) as resp: |
| if resp.status != 200: return [] |
| data = await resp.json() |
| imgs = data.get("imagenes") or [] |
| return [i["src"] if isinstance(i, dict) else i for i in imgs] |
| except Exception as e: |
| return [] |
|
|
| @app.route("/") |
| def index(): |
| return render_template("index.html") |
|
|
| @app.route("/api/manga") |
| def api_manga(): |
| url = request.args.get("url") |
| if not url: return jsonify({"error": "Missing URL"}), 400 |
| loop = asyncio.new_event_loop() |
| asyncio.set_event_loop(loop) |
| data = loop.run_until_complete(get_chapters(url)) |
| if not data: return jsonify({"error": "Access Denied by Site or Manga not found"}), 404 |
| return jsonify(data) |
|
|
| @app.route("/api/chapter/<int:chapter_id>") |
| def api_chapter(chapter_id): |
| loop = asyncio.new_event_loop() |
| asyncio.set_event_loop(loop) |
| images = loop.run_until_complete(get_images(chapter_id)) |
| return jsonify({"images": images}) |
|
|
| if __name__ == "__main__": |
| app.run(host="0.0.0.0", port=PORT) |
|
|