Spaces:
Sleeping
Sleeping
تصحيح مسار الرفع
Browse files- Dockerfile +23 -0
- README.md +4 -8
- main.py +96 -0
- requirements.txt +23 -0
- scraper.py +132 -0
- sharh.txt +110 -0
Dockerfile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# استخدم إصدارًا حديثًا ومستقرًا من Python
|
| 2 |
+
FROM python:3.9-slim
|
| 3 |
+
|
| 4 |
+
# قم بإنشاء مستخدم غير مسؤول (non-root) كما توصي Hugging Face
|
| 5 |
+
RUN useradd -m -u 1000 user
|
| 6 |
+
USER user
|
| 7 |
+
ENV PATH="/home/user/.local/bin:$PATH"
|
| 8 |
+
|
| 9 |
+
# قم بتعيين دليل العمل داخل الحاوية
|
| 10 |
+
WORKDIR /app
|
| 11 |
+
|
| 12 |
+
# انسخ ملف المتطلبات وقم بتغيير الملكية
|
| 13 |
+
COPY --chown=user ./requirements.txt requirements.txt
|
| 14 |
+
|
| 15 |
+
# قم بتثبيت المكتبات المطلوبة
|
| 16 |
+
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
| 17 |
+
|
| 18 |
+
# انسخ جميع ملفات التطبيق إلى دليل العمل
|
| 19 |
+
COPY --chown=user . /app
|
| 20 |
+
|
| 21 |
+
# عرّف الأمر الذي سيتم تشغيله لتشغيل التطبيق باستخدام Gunicorn
|
| 22 |
+
# Hugging Face Spaces يستخدم المنفذ 7860 افتراضيًا
|
| 23 |
+
CMD ["gunicorn", "--bind", "0.0.0.0:7860", "main:app"]
|
README.md
CHANGED
|
@@ -1,11 +1,7 @@
|
|
| 1 |
---
|
| 2 |
-
title: LightNovel
|
| 3 |
-
emoji: 🌍
|
| 4 |
-
colorFrom: yellow
|
| 5 |
-
colorTo: yellow
|
| 6 |
-
sdk: docker
|
| 7 |
-
pinned: false
|
| 8 |
license: mit
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
---
|
| 10 |
-
|
| 11 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
license: mit
|
| 3 |
+
colorFrom: blue
|
| 4 |
+
colorTo: purple
|
| 5 |
+
sdk: docker
|
| 6 |
+
app_port: 7860
|
| 7 |
---
|
|
|
|
|
|
main.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import os
|
| 3 |
+
from flask import Flask, request, jsonify
|
| 4 |
+
from scraper import (
|
| 5 |
+
search_novel,
|
| 6 |
+
get_novel_content,
|
| 7 |
+
get_chapter_content,
|
| 8 |
+
get_latest_release_novels,
|
| 9 |
+
get_hot_novels,
|
| 10 |
+
get_completed_novels,
|
| 11 |
+
get_most_popular_novels,
|
| 12 |
+
get_genres,
|
| 13 |
+
get_novels_by_genre,
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
app = Flask(__name__)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@app.route("/")
|
| 20 |
+
def hello_world():
|
| 21 |
+
"""Example Hello World route."""
|
| 22 |
+
name = os.environ.get("NAME", "World")
|
| 23 |
+
return f"Hello {name}!"
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@app.route("/search")
|
| 27 |
+
def search():
|
| 28 |
+
query = request.args.get("q")
|
| 29 |
+
if not query:
|
| 30 |
+
return jsonify({"error": 'Missing query parameter "q"'}), 400
|
| 31 |
+
page = request.args.get("page", 1, type=int)
|
| 32 |
+
results = search_novel(query, page)
|
| 33 |
+
return jsonify(results)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
@app.route("/novel")
|
| 37 |
+
def novel():
|
| 38 |
+
url = request.args.get("url")
|
| 39 |
+
if not url:
|
| 40 |
+
return jsonify({"error": 'Missing query parameter "url"'}), 400
|
| 41 |
+
content = get_novel_content(url)
|
| 42 |
+
return jsonify(content)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
@app.route("/chapter")
|
| 46 |
+
def chapter():
|
| 47 |
+
url = request.args.get("url")
|
| 48 |
+
if not url:
|
| 49 |
+
return jsonify({"error": 'Missing query parameter "url"'}), 400
|
| 50 |
+
content = get_chapter_content(url)
|
| 51 |
+
return jsonify(content)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
@app.route("/latest")
|
| 55 |
+
def latest():
|
| 56 |
+
page = request.args.get("page", 1, type=int)
|
| 57 |
+
results = get_latest_release_novels(page)
|
| 58 |
+
return jsonify(results)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
@app.route("/hot")
|
| 62 |
+
def hot():
|
| 63 |
+
page = request.args.get("page", 1, type=int)
|
| 64 |
+
results = get_hot_novels(page)
|
| 65 |
+
return jsonify(results)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
@app.route("/completed")
|
| 69 |
+
def completed():
|
| 70 |
+
page = request.args.get("page", 1, type=int)
|
| 71 |
+
results = get_completed_novels(page)
|
| 72 |
+
return jsonify(results)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
@app.route("/popular")
|
| 76 |
+
def popular():
|
| 77 |
+
page = request.args.get("page", 1, type=int)
|
| 78 |
+
results = get_most_popular_novels(page)
|
| 79 |
+
return jsonify(results)
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
@app.route("/genres")
|
| 83 |
+
def genres():
|
| 84 |
+
results = get_genres()
|
| 85 |
+
return jsonify(results)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
@app.route("/genre/<genre_name>")
|
| 89 |
+
def by_genre(genre_name):
|
| 90 |
+
page = request.args.get("page", 1, type=int)
|
| 91 |
+
results = get_novels_by_genre(genre_name, page)
|
| 92 |
+
return jsonify(results)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
if __name__ == "__main__":
|
| 96 |
+
app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))
|
requirements.txt
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
autopep8==2.3.2
|
| 2 |
+
beautifulsoup4==4.14.3
|
| 3 |
+
blinker==1.9.0
|
| 4 |
+
certifi==2026.1.4
|
| 5 |
+
charset-normalizer==3.4.4
|
| 6 |
+
click==8.3.1
|
| 7 |
+
Flask==3.0.3
|
| 8 |
+
gunicorn==22.0.0
|
| 9 |
+
idna==3.11
|
| 10 |
+
iniconfig==2.3.0
|
| 11 |
+
itsdangerous==2.2.0
|
| 12 |
+
Jinja2==3.1.6
|
| 13 |
+
MarkupSafe==3.0.3
|
| 14 |
+
packaging==26.0
|
| 15 |
+
pluggy==1.6.0
|
| 16 |
+
pycodestyle==2.14.0
|
| 17 |
+
Pygments==2.19.2
|
| 18 |
+
pytest==9.0.2
|
| 19 |
+
requests==2.32.5
|
| 20 |
+
soupsieve==2.8.3
|
| 21 |
+
typing_extensions==4.15.0
|
| 22 |
+
urllib3==2.6.3
|
| 23 |
+
Werkzeug==3.0.6
|
scraper.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import requests
|
| 3 |
+
from bs4 import BeautifulSoup
|
| 4 |
+
|
| 5 |
+
HEADERS = {
|
| 6 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
def _scrape_novel_list_page(page_url):
|
| 10 |
+
response = requests.get(page_url, headers=HEADERS)
|
| 11 |
+
soup = BeautifulSoup(response.content, 'html.parser')
|
| 12 |
+
|
| 13 |
+
results = []
|
| 14 |
+
main_content = soup.find('div', class_='col-truyen-main')
|
| 15 |
+
if main_content:
|
| 16 |
+
for row in main_content.find_all('div', class_='row'):
|
| 17 |
+
title_element = row.find('h3', class_='truyen-title')
|
| 18 |
+
if title_element and title_element.find('a'):
|
| 19 |
+
title_anchor = title_element.find('a')
|
| 20 |
+
title = title_anchor.text
|
| 21 |
+
url = title_anchor['href']
|
| 22 |
+
|
| 23 |
+
author_element = row.find('span', class_='author')
|
| 24 |
+
author = author_element.text.strip() if author_element else 'N/A'
|
| 25 |
+
|
| 26 |
+
results.append({
|
| 27 |
+
'title': title,
|
| 28 |
+
'url': f"https://novelfull.net{url}",
|
| 29 |
+
'author': author
|
| 30 |
+
})
|
| 31 |
+
return results
|
| 32 |
+
|
| 33 |
+
def search_novel(query, page=1):
|
| 34 |
+
search_url = f"https://novelfull.net/search?keyword={query}&page={page}"
|
| 35 |
+
return _scrape_novel_list_page(search_url)
|
| 36 |
+
|
| 37 |
+
def get_latest_release_novels(page=1):
|
| 38 |
+
url = f"https://novelfull.net/latest-release-novel?page={page}"
|
| 39 |
+
return _scrape_novel_list_page(url)
|
| 40 |
+
|
| 41 |
+
def get_hot_novels(page=1):
|
| 42 |
+
url = f"https://novelfull.net/hot-novel?page={page}"
|
| 43 |
+
return _scrape_novel_list_page(url)
|
| 44 |
+
|
| 45 |
+
def get_completed_novels(page=1):
|
| 46 |
+
url = f"https://novelfull.net/completed-novel?page={page}"
|
| 47 |
+
return _scrape_novel_list_page(url)
|
| 48 |
+
|
| 49 |
+
def get_most_popular_novels(page=1):
|
| 50 |
+
url = f"https://novelfull.net/most-popular?page={page}"
|
| 51 |
+
return _scrape_novel_list_page(url)
|
| 52 |
+
|
| 53 |
+
def get_genres():
|
| 54 |
+
return [
|
| 55 |
+
"Shounen", "Horror", "Slice of Life",
|
| 56 |
+
"Harem", "Drama", "Seinen",
|
| 57 |
+
"Comedy", "Tragedy", "Lolicon",
|
| 58 |
+
"Martial Arts", "Supernatural", "Adult",
|
| 59 |
+
"School Life", "Ecchi", "Josei",
|
| 60 |
+
"Mystery", "Xuanhuan", "Sports",
|
| 61 |
+
"Shoujo", "Adventure", "Smut",
|
| 62 |
+
"Romance", "Action", "Mecha",
|
| 63 |
+
"Sci-fi", "Psychological", "Yaoi",
|
| 64 |
+
"Gender Bender", "Xianxia", "Shounen Ai",
|
| 65 |
+
"Mature", "Wuxia", "Magical Realism",
|
| 66 |
+
"Fantasy", "Historical", "Video Games"
|
| 67 |
+
]
|
| 68 |
+
|
| 69 |
+
def get_novels_by_genre(genre_name, page=1):
|
| 70 |
+
genre_slug = genre_name.replace(' ', '-')
|
| 71 |
+
genre_url = f"https://novelfull.net/genre/{genre_slug}?page={page}"
|
| 72 |
+
return _scrape_novel_list_page(genre_url)
|
| 73 |
+
|
| 74 |
+
def get_novel_content(url):
|
| 75 |
+
response = requests.get(url, headers=HEADERS)
|
| 76 |
+
soup = BeautifulSoup(response.content, 'html.parser')
|
| 77 |
+
|
| 78 |
+
title_element = soup.find('h3', class_='title')
|
| 79 |
+
title = title_element.text if title_element else 'N/A'
|
| 80 |
+
|
| 81 |
+
author = 'N/A'
|
| 82 |
+
info_div = soup.find('div', class_='info')
|
| 83 |
+
if info_div:
|
| 84 |
+
author_div = info_div.find('h3', text='Author:')
|
| 85 |
+
if author_div and author_div.find_next_sibling('a'):
|
| 86 |
+
author = author_div.find_next_sibling('a').text
|
| 87 |
+
|
| 88 |
+
image_url = 'N/A'
|
| 89 |
+
book_div = soup.find('div', class_='book')
|
| 90 |
+
if book_div and book_div.find('img'):
|
| 91 |
+
image_tag = book_div.find('img')
|
| 92 |
+
if 'src' in image_tag.attrs:
|
| 93 |
+
src = image_tag['src']
|
| 94 |
+
if src.startswith('/'):
|
| 95 |
+
image_url = f"https://novelfull.net{src}"
|
| 96 |
+
else:
|
| 97 |
+
image_url = src
|
| 98 |
+
|
| 99 |
+
chapters = []
|
| 100 |
+
list_chapter_div = soup.find('div', id='list-chapter')
|
| 101 |
+
if list_chapter_div:
|
| 102 |
+
chapter_lists = list_chapter_div.select('.list-chapter')
|
| 103 |
+
for chapter_list in chapter_lists:
|
| 104 |
+
for chapter_item in chapter_list.find_all('a'):
|
| 105 |
+
chapters.append({
|
| 106 |
+
'title': chapter_item.get('title', chapter_item.text.strip()),
|
| 107 |
+
'url': f"https://novelfull.net{chapter_item['href']}"
|
| 108 |
+
})
|
| 109 |
+
|
| 110 |
+
return {
|
| 111 |
+
'title': title,
|
| 112 |
+
'author': author,
|
| 113 |
+
'image_url': image_url,
|
| 114 |
+
'chapters': chapters
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
def get_chapter_content(url):
|
| 118 |
+
response = requests.get(url, headers=HEADERS)
|
| 119 |
+
soup = BeautifulSoup(response.content, 'html.parser')
|
| 120 |
+
|
| 121 |
+
content_element = soup.find('div', id='chapter-content')
|
| 122 |
+
content = ''
|
| 123 |
+
if content_element:
|
| 124 |
+
paragraphs = content_element.find_all('p')
|
| 125 |
+
if paragraphs:
|
| 126 |
+
content = '\n\n'.join(p.get_text() for p in paragraphs)
|
| 127 |
+
else:
|
| 128 |
+
content = content_element.get_text(separator='\n\n')
|
| 129 |
+
|
| 130 |
+
return {
|
| 131 |
+
'content': content.strip() if content else 'N/A'
|
| 132 |
+
}
|
sharh.txt
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
مرحبًا بك في شرح استخدام الواجهة البرمجية (API)
|
| 3 |
+
|
| 4 |
+
هذا الدليل سيساعدك على فهم كيفية استخدام نقاط النهاية (Endpoints) المختلفة لجلب معلومات عن الروايات، بدءًا من البحث وحتى قراءة المحتوى.
|
| 5 |
+
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
### الخطوة الأولى: البحث عن رواية
|
| 9 |
+
|
| 10 |
+
للبدء، تحتاج إلى العثور على الرواية التي تهمك. يمكنك القيام بذلك باستخدام نقطة النهاية التالية:
|
| 11 |
+
|
| 12 |
+
**نقطة النهاية (Endpoint):** `/search`
|
| 13 |
+
|
| 14 |
+
**الطريقة (Method):** `GET`
|
| 15 |
+
|
| 16 |
+
**المعاملات (Parameters):**
|
| 17 |
+
- `q` (إلزامي): هو مصطلح البحث الذي تريد استخدامه (مثل اسم الرواية).
|
| 18 |
+
- `page` (اختياري): رقم الصفحة في حال كانت نتائج البحث متعددة. القيمة الافتراضية هي `1`.
|
| 19 |
+
|
| 20 |
+
**مثال على الاستخدام:**
|
| 21 |
+
للبحث عن رواية تحتوي على كلمة "love"، يمكنك استخدام الرابط التالي:
|
| 22 |
+
`http://127.0.0.1:8080/search?q=love`
|
| 23 |
+
|
| 24 |
+
**النتيجة المتوقعة:**
|
| 25 |
+
ستعود لك هذه النقطة بقائمة من الروايات التي تطابق بحثك. كل عنصر في القائمة سيحتوي على:
|
| 26 |
+
- `title`: عنوان الرواية.
|
| 27 |
+
- `author`: اسم المؤلف.
|
| 28 |
+
- `url`: رابط فريد خاص بالرواية، وهو مهم جدًا للخطوة التالية.
|
| 29 |
+
|
| 30 |
+
```json
|
| 31 |
+
[
|
| 32 |
+
{
|
| 33 |
+
"author": "Nilphy",
|
| 34 |
+
"title": "I Love Destroying Worlds' Plot [BL]",
|
| 35 |
+
"url": "https://novelfull.net/i-love-destroying-worlds-plot-bl.html"
|
| 36 |
+
},
|
| 37 |
+
...
|
| 38 |
+
]
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
---
|
| 42 |
+
|
| 43 |
+
### الخطوة الثانية: الحصول على تفاصيل الرواية وفصولها
|
| 44 |
+
|
| 45 |
+
بعد أن تحصل على `url` الرواية من الخطوة الأولى، يمكنك استخدامه لجلب كافة تفاصيل الرواية، بما في ذلك قائمة بجميع فصولها.
|
| 46 |
+
|
| 47 |
+
**نقطة النهاية (Endpoint):** `/novel`
|
| 48 |
+
|
| 49 |
+
**الطريقة (Method):** `GET`
|
| 50 |
+
|
| 51 |
+
**المعاملات (Parameters):**
|
| 52 |
+
- `url` (إلزامي): هو رابط الرواية الذي حصلت عليه من نقطة النهاية `/search`.
|
| 53 |
+
|
| 54 |
+
**مثال على الاستخدام:**
|
| 55 |
+
`http://127.0.0.1:8080/novel?url=https://novelfull.net/i-love-destroying-worlds-plot-bl.html`
|
| 56 |
+
|
| 57 |
+
**النتيجة المتوقعة:**
|
| 58 |
+
ستعود لك هذه النقطة بمعلومات مفصلة عن الرواية، وأهمها قائمة الفصول `chapters`. كل فصل في القائمة سيكون له:
|
| 59 |
+
- `title`: عنوان الفصل.
|
| 60 |
+
- `url`: رابط فريد خاص بالفصل، وهو ما ستحتاجه للخطوة الأخيرة.
|
| 61 |
+
|
| 62 |
+
```json
|
| 63 |
+
{
|
| 64 |
+
"title": "I Love Destroying Worlds' Plot [BL]",
|
| 65 |
+
"author": "Nilphy",
|
| 66 |
+
"image_url": "...",
|
| 67 |
+
"chapters": [
|
| 68 |
+
{
|
| 69 |
+
"title": "Chapter 1 - 1.0 Prologue",
|
| 70 |
+
"url": "https://novelfull.net/i-love-destroying-worlds-plot-bl/chapter-1-10-prologue-in-the-system-space-before-the-first-world.html"
|
| 71 |
+
},
|
| 72 |
+
...
|
| 73 |
+
]
|
| 74 |
+
}
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
---
|
| 78 |
+
|
| 79 |
+
### الخطوة الثالثة: قراءة محتوى فصل معين
|
| 80 |
+
|
| 81 |
+
أخيرًا، بعد حصولك على رابط الفصل (`url`) من الخطوة الثانية، يمكنك استخدامه لجلب المحتوى النصي الكامل لهذا الفصل.
|
| 82 |
+
|
| 83 |
+
**نقطة النهاية (Endpoint):** `/chapter`
|
| 84 |
+
|
| 85 |
+
**الطريقة (Method):** `GET`
|
| 86 |
+
|
| 87 |
+
**المعاملات (Parameters):**
|
| 88 |
+
- `url` (إلزامي): هو رابط الفصل الذي حصلت عليه من نقطة النهاية `/novel`.
|
| 89 |
+
|
| 90 |
+
**مثال على الاستخدام:**
|
| 91 |
+
`http://127.0.0.1:8080/chapter?url=https://novelfull.net/i-love-destroying-worlds-plot-bl/chapter-1-10-prologue-in-the-system-space-before-the-first-world.html`
|
| 92 |
+
|
| 93 |
+
**النتيجة المتوقعة:**
|
| 94 |
+
ستعود لك هذه النقطة بمحتوى الفصل على شكل نص.
|
| 95 |
+
|
| 96 |
+
```json
|
| 97 |
+
{
|
| 98 |
+
"content": "Hei Anjing is a person who is extremely lazy that he dies from excessive sleeping and never wakes up anymore..."
|
| 99 |
+
}
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
---
|
| 103 |
+
### ملخص نقاط النهاية الأخرى:
|
| 104 |
+
|
| 105 |
+
- `/latest`: للحصول على أحدث الروايات المضافة.
|
| 106 |
+
- `/hot`: للحصول على الروايات الأكثر رواجًا.
|
| 107 |
+
- `/completed`: للحصول على قائمة بالروايات المكتملة.
|
| 108 |
+
- `/popular`: للحصول على الروايات الأكثر شهرة.
|
| 109 |
+
- `/genres`: لعرض جميع الأنواع الأدبية المتاحة.
|
| 110 |
+
- `/genre/<genre_name>`: لتصفح الروايات حسب نوع معين (مثال: `/genre/Shounen`).
|