AnesKAM commited on
Commit
d83ed68
·
verified ·
1 Parent(s): 562584f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +218 -129
app.py CHANGED
@@ -1,165 +1,254 @@
1
- from flask import Flask, render_template_string, request, jsonify
2
- import requests
3
- from bs4 import BeautifulSoup
 
 
 
 
4
 
5
  app = Flask(__name__)
6
 
7
- # --- واجهة HTML (نفس التصميم الجميل، لاحظ تعديل JavaScript) ---
8
- HTML_TEMPLATE = """
9
  <!DOCTYPE html>
10
- <html lang="ar" dir="rtl">
11
  <head>
12
  <meta charset="UTF-8">
13
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
14
- <title>محرك البحث الذكي</title>
15
  <style>
16
  * { margin: 0; padding: 0; box-sizing: border-box; }
17
- body {
18
- font-family: 'Segoe UI', Tahoma, sans-serif;
19
- background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
20
- min-height: 100vh; display: flex; flex-direction: column; align-items: center;
21
- justify-content: center; padding: 20px;
22
  }
23
- .container { width: 100%; max-width: 700px; text-align: center; }
24
- .logo { font-size: 3rem; color: #e94560; margin-bottom: 10px; font-weight: bold; letter-spacing: 2px; }
25
- .subtitle { color: #a0a0b0; margin-bottom: 30px; font-size: 0.9rem; }
26
- .search-box {
27
- display: flex; gap: 10px; background: rgba(255,255,255,0.1);
28
- backdrop-filter: blur(10px); border-radius: 30px; padding: 5px 10px;
29
- box-shadow: 0 8px 32px rgba(0,0,0,0.3); margin-bottom: 20px;
 
30
  }
31
- .search-box input {
32
- flex: 1; padding: 15px 20px; border: none; background: transparent;
33
- color: white; font-size: 1rem; outline: none;
 
34
  }
35
- .search-box input::placeholder { color: #8a8a9a; }
36
- .search-box button {
37
- padding: 12px 25px; background: #e94560; color: white; border: none;
38
- border-radius: 25px; font-size: 1rem; cursor: pointer; font-weight: bold;
39
- transition: background 0.3s;
 
 
 
 
40
  }
41
- .search-box button:hover { background: #c23152; }
42
- #results { width: 100%; margin-top: 20px; }
43
- .result-item {
44
- background: rgba(255,255,255,0.05); backdrop-filter: blur(5px);
45
- border-radius: 12px; padding: 15px; margin-bottom: 10px; text-align: right;
46
- border-right: 3px solid #e94560; transition: background 0.3s;
 
 
 
 
47
  }
48
- .result-item:hover { background: rgba(255,255,255,0.1); }
49
- .result-item a {
50
- color: #4fc3f7; text-decoration: none; font-size: 1.1rem; font-weight: bold;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  }
52
- .result-item a:hover { text-decoration: underline; }
53
- .result-item .url { color: #66bb6a; font-size: 0.8rem; margin: 5px 0; word-break: break-all; }
54
- .result-item p { color: #ccc; font-size: 0.9rem; margin-top: 8px; line-height: 1.5; }
55
- .loading { color: white; margin: 20px 0; display: none; }
56
- .spinner { display: inline-block; width: 20px; height: 20px; border: 3px solid rgba(255,255,255,0.3); border-radius: 50%; border-top-color: #e94560; animation: spin 1s linear infinite; margin-left: 8px; }
57
- @keyframes spin { to { transform: rotate(360deg); } }
58
  </style>
59
  </head>
60
  <body>
61
  <div class="container">
62
- <div class="logo">🔎 باحث</div>
63
- <p class="subtitle">محرك بحث مفتوح المصدر - ابحث في الإنترنت بحرية</p>
64
-
65
- <div class="search-box">
66
- <input type="text" id="searchInput" placeholder="ماذا تريد أن تبحث؟" dir="auto">
67
- <button onclick="performSearch()">🔍 بحث</button>
 
 
 
 
 
 
 
 
 
68
  </div>
69
 
70
- <div class="loading" id="loading">
71
- <span class="spinner"></span> جاري البحث...
72
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
- <div id="results"></div>
 
 
 
75
  </div>
76
-
77
  <script>
78
- async function performSearch() {
79
- const query = document.getElementById('searchInput').value.trim();
80
- if (!query) return;
81
-
82
- const loading = document.getElementById('loading');
83
- const results = document.getElementById('results');
84
-
85
- loading.style.display = 'block';
86
- results.innerHTML = '';
87
-
88
- try {
89
- // نطلب الآن من تطبيق Flask نفسه، مش من DuckDuckGo مباشرة
90
- const response = await fetch(`/search?q=${encodeURIComponent(query)}`);
91
- const data = await response.json();
92
-
93
- loading.style.display = 'none';
94
-
95
- if (data.results && data.results.length > 0) {
96
- let html = '';
97
- data.results.forEach((res, index) => {
98
- html += `
99
- <div class="result-item">
100
- <a href="${res.link}" target="_blank" rel="noopener">${index + 1}. ${res.title}</a>
101
- <div class="url">${res.link.substring(0, 80)}${res.link.length > 80 ? '...' : ''}</div>
102
- <p>${res.snippet}</p>
103
- </div>
104
- `;
105
- });
106
- results.innerHTML = html;
107
- } else {
108
- results.innerHTML = '<p style="color: #ccc; text-align: center;">لم يتم العثور على نتائج. جرب كلمات مختلفة.</p>';
109
- }
110
- } catch (error) {
111
- loading.style.display = 'none';
112
- results.innerHTML = `<p style='color: #ff6b6b;'>حدث خطأ: ${error.message}</p>`;
113
- }
114
  }
115
-
116
- document.getElementById('searchInput').addEventListener('keypress', function(e) {
117
- if (e.key === 'Enter') performSearch();
118
- });
119
  </script>
120
  </body>
121
  </html>
122
- """
123
 
124
- # --- الدالة السحرية اللي تطلب من DuckDuckGo من السيرفر مباشرة ---
125
- def search_duckduckgo(query, max_results=10):
126
- url = "https://html.duckduckgo.com/html/"
127
- headers = {
128
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
129
- }
130
- params = {'q': query}
131
 
132
- try:
133
- # ملاحظة: استخدمنا requests.post هنا لأنها الطريقة الصحيحة مع هذا الرابط
134
- response = requests.post(url, data=params, headers=headers, timeout=10)
135
- soup = BeautifulSoup(response.text, 'html.parser')
136
- results = []
137
- for item in soup.select('.result')[:max_results]:
138
- title_element = item.select_one('.result__title a')
139
- snippet_element = item.select_one('.result__snippet')
140
- link_element = item.select_one('.result__url')
141
-
142
- if title_element:
143
- title = title_element.get_text(strip=True)
144
- link = link_element.get_text(strip=True) if link_element else title_element.get('href', '')
145
- snippet = snippet_element.get_text(strip=True) if snippet_element else ''
146
- results.append({'title': title, 'link': link, 'snippet': snippet})
147
- return results
148
- except Exception as e:
149
- print(f"Error during DuckDuckGo search: {e}")
150
- return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
  @app.route('/')
153
- def home():
154
- return render_template_string(HTML_TEMPLATE)
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
- @app.route('/search')
157
- def search():
 
158
  query = request.args.get('q', '')
159
- if not query:
160
- return jsonify({'results': []})
161
- results = search_duckduckgo(query)
162
- return jsonify({'results': results})
163
 
164
  if __name__ == '__main__':
165
- app.run(host='0.0.0.0', port=7860)
 
 
 
 
 
 
1
+ from flask import Flask, request, render_template_string, jsonify
2
+ import sqlite3
3
+ from datetime import datetime
4
+ import threading
5
+ import time
6
+ from crawler import fetch_news_from_aljazeera
7
+ from indexer import save_news_to_db
8
 
9
  app = Flask(__name__)
10
 
11
+ # قالب HTML متجاوب وجميل
12
+ HTML_TEMPLATE = '''
13
  <!DOCTYPE html>
14
+ <html dir="rtl">
15
  <head>
16
  <meta charset="UTF-8">
17
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
18
+ <title>محرك بحث الأخبار - البحث في الصور والأخبار والفيديوهات</title>
19
  <style>
20
  * { margin: 0; padding: 0; box-sizing: border-box; }
21
+ body {
22
+ font-family: 'Tahoma', Arial, sans-serif;
23
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
24
+ min-height: 100vh;
 
25
  }
26
+ .container { max-width: 900px; margin: auto; padding: 20px; }
27
+ .header {
28
+ background: white;
29
+ border-radius: 15px;
30
+ padding: 30px;
31
+ margin-bottom: 30px;
32
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
33
+ text-align: center;
34
  }
35
+ h1 {
36
+ color: #333;
37
+ margin-bottom: 10px;
38
+ font-size: 2.5em;
39
  }
40
+ .subtitle { color: #666; margin-bottom: 30px; }
41
+ .search-form { display: flex; gap: 10px; flex-wrap: wrap; }
42
+ .search-box {
43
+ flex: 1;
44
+ padding: 15px;
45
+ border: 2px solid #ddd;
46
+ border-radius: 10px;
47
+ font-size: 16px;
48
+ font-family: inherit;
49
  }
50
+ .search-box:focus { outline: none; border-color: #667eea; }
51
+ button {
52
+ padding: 15px 30px;
53
+ background: #667eea;
54
+ color: white;
55
+ border: none;
56
+ border-radius: 10px;
57
+ cursor: pointer;
58
+ font-size: 16px;
59
+ transition: transform 0.2s;
60
  }
61
+ button:hover { transform: scale(1.05); background: #5a67d8; }
62
+ .filters {
63
+ display: flex;
64
+ gap: 10px;
65
+ margin: 20px 0;
66
+ justify-content: center;
67
+ }
68
+ .filter-btn {
69
+ padding: 8px 20px;
70
+ background: #f0f0f0;
71
+ border: none;
72
+ border-radius: 20px;
73
+ cursor: pointer;
74
+ }
75
+ .filter-btn.active { background: #667eea; color: white; }
76
+ .results-count {
77
+ background: white;
78
+ padding: 10px;
79
+ border-radius: 10px;
80
+ margin-bottom: 20px;
81
+ text-align: center;
82
+ }
83
+ .result {
84
+ background: white;
85
+ padding: 20px;
86
+ margin-bottom: 15px;
87
+ border-radius: 10px;
88
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
89
+ transition: transform 0.2s;
90
+ }
91
+ .result:hover { transform: translateX(-5px); }
92
+ .title {
93
+ color: #1a0dab;
94
+ text-decoration: none;
95
+ font-size: 18px;
96
+ font-weight: bold;
97
+ display: block;
98
+ margin-bottom: 8px;
99
+ }
100
+ .title:hover { text-decoration: underline; }
101
+ .source { color: #006621; font-size: 14px; }
102
+ .date { color: #808080; font-size: 12px; }
103
+ .summary { color: #545454; margin-top: 10px; line-height: 1.5; }
104
+ .footer {
105
+ text-align: center;
106
+ margin-top: 30px;
107
+ color: white;
108
+ padding: 20px;
109
+ }
110
+ @media (max-width: 600px) {
111
+ .search-form { flex-direction: column; }
112
+ .container { padding: 10px; }
113
  }
 
 
 
 
 
 
114
  </style>
115
  </head>
116
  <body>
117
  <div class="container">
118
+ <div class="header">
119
+ <h1>📰 🔍 🎥 محرك البحث المتكامل</h1>
120
+ <p class="subtitle">بحث في الأخبار - الصور - الفيديوهات</p>
121
+
122
+ <form method="get" class="search-form">
123
+ <input type="text" name="q" class="search-box" placeholder="ابحث عن أخبار، صور، فيديوهات..." value="{{ query }}">
124
+ <button type="submit">🔍 بحث</button>
125
+ </form>
126
+
127
+ <div class="filters">
128
+ <button class="filter-btn {% if filter_type == 'all' %}active{% endif %}" onclick="filterResults('all')">الكل</button>
129
+ <button class="filter-btn {% if filter_type == 'news' %}active{% endif %}" onclick="filterResults('news')">📰 أخبار</button>
130
+ <button class="filter-btn {% if filter_type == 'images' %}active{% endif %}" onclick="filterResults('images')">🖼️ صور</button>
131
+ <button class="filter-btn {% if filter_type == 'videos' %}active{% endif %}" onclick="filterResults('videos')">🎥 فيديوهات</button>
132
+ </div>
133
  </div>
134
 
135
+ {% if query %}
136
+ <div class="results-count">
137
+ 📊 عرض {{ results|length }} نتيجة من أصل {{ total }} عن "{{ query }}"
138
+ </div>
139
+
140
+ {% for result in results %}
141
+ <div class="result">
142
+ <a href="{{ result.link }}" class="title" target="_blank">
143
+ {% if result.type == 'news' %}📰{% elif result.type == 'image' %}🖼️{% elif result.type == 'video' %}🎥{% endif %}
144
+ {{ result.title }}
145
+ </a>
146
+ <div>
147
+ <span class="source">{{ result.source }}</span>
148
+ <span class="date"> - {{ result.date }}</span>
149
+ </div>
150
+ {% if result.thumbnail %}
151
+ <img src="{{ result.thumbnail }}" style="max-width: 100px; float: left; margin-left: 10px;">
152
+ {% endif %}
153
+ <div class="summary">{{ result.summary }}</div>
154
+ </div>
155
+ {% endfor %}
156
+ {% endif %}
157
 
158
+ <div class="footer">
159
+ <p>🚀 تم النشر على Hugging Face Spaces باستخدام Docker</p>
160
+ <p>🔄 يتم تحديث الأخبار تلقائيًا كل ساعة</p>
161
+ </div>
162
  </div>
163
+
164
  <script>
165
+ function filterResults(type) {
166
+ const url = new URL(window.location.href);
167
+ url.searchParams.set('type', type);
168
+ window.location.href = url.toString();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  }
 
 
 
 
170
  </script>
171
  </body>
172
  </html>
173
+ '''
174
 
175
+ def search_all(query, filter_type='all'):
176
+ """البحث في جميع المحتويات (أخبار - صور - فيديوهات)"""
177
+ conn = sqlite3.connect('news_database.db')
178
+ cursor = conn.cursor()
179
+ query_lower = query.lower()
 
 
180
 
181
+ results = []
182
+
183
+ # 1. البحث في الأخبار
184
+ if filter_type in ['all', 'news']:
185
+ cursor.execute('''
186
+ SELECT title, link, summary, source, date, 'news' as type, '' as thumbnail
187
+ FROM news
188
+ WHERE search_terms LIKE ?
189
+ ORDER BY date DESC
190
+ LIMIT 30
191
+ ''', (f'%{query_lower}%',))
192
+
193
+ for row in cursor.fetchall():
194
+ results.append({
195
+ 'title': row[0], 'link': row[1], 'summary': row[2],
196
+ 'source': row[3], 'date': row[4], 'type': row[5], 'thumbnail': row[6]
197
+ })
198
+
199
+ # 2. صور تجريبية (يمكنك إضافة مصادر حقيقية لاحقًا)
200
+ if filter_type in ['all', 'images']:
201
+ sample_images = [
202
+ {'title': f'صورة متعلقة بـ {query}', 'link': '#', 'summary': 'نتائج صور تجريبية',
203
+ 'source': 'مكتبة الصور', 'date': datetime.now().strftime('%Y-%m-%d'), 'type': 'image',
204
+ 'thumbnail': 'https://via.placeholder.com/100'},
205
+ ]
206
+ results.extend(sample_images)
207
+
208
+ conn.close()
209
+ return results, len(results)
210
+
211
+ def update_news_background():
212
+ """تحديث الأخبار في الخلفية كل ساعة"""
213
+ while True:
214
+ try:
215
+ print("🔄 جلب آخر الأخبار...")
216
+ news_list = fetch_news_from_aljazeera()
217
+ if news_list:
218
+ save_news_to_db(news_list)
219
+ print(f"✅ تم تحديث {len(news_list)} خبر")
220
+ except Exception as e:
221
+ print(f"⚠️ خطأ في التحديث: {e}")
222
+ time.sleep(3600) # كل ساعة
223
 
224
  @app.route('/')
225
+ def index():
226
+ query = request.args.get('q', '')
227
+ filter_type = request.args.get('type', 'all')
228
+
229
+ if query:
230
+ results, total = search_all(query, filter_type)
231
+ else:
232
+ results, total = [], 0
233
+
234
+ return render_template_string(HTML_TEMPLATE,
235
+ query=query,
236
+ results=results,
237
+ total=total,
238
+ filter_type=filter_type)
239
 
240
+ @app.route('/api/search')
241
+ def api_search():
242
+ """API للبحث (JSON)"""
243
  query = request.args.get('q', '')
244
+ filter_type = request.args.get('type', 'all')
245
+ results, total = search_all(query, filter_type)
246
+ return jsonify({'results': results, 'total': total})
 
247
 
248
  if __name__ == '__main__':
249
+ # تشغيل مهمة تحديث الأخبار في خلفية منفصلة
250
+ update_thread = threading.Thread(target=update_news_background, daemon=True)
251
+ update_thread.start()
252
+
253
+ # تشغيل التطبيق
254
+ app.run(host='0.0.0.0', port=7860, debug=False)