ex510 commited on
Commit
eba17c1
·
1 Parent(s): b12fd64

تصحيح مسار الرفع

Browse files
Files changed (6) hide show
  1. Dockerfile +23 -0
  2. README.md +4 -8
  3. main.py +96 -0
  4. requirements.txt +23 -0
  5. scraper.py +132 -0
  6. 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`).