neop3 commited on
Commit
95a621e
·
verified ·
1 Parent(s): 8d28ca5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +257 -99
app.py CHANGED
@@ -1,7 +1,9 @@
1
  import collections
2
  import collections.abc
3
- # Monkeypatch for pySmartDL compatibility with Python 3.10+
4
- collections.Iterable = collections.abc.Iterable
 
 
5
 
6
  import os
7
  import json
@@ -9,198 +11,354 @@ import logging
9
  import tempfile
10
  import asyncio
11
  import threading
 
 
 
12
  from http.server import HTTPServer, BaseHTTPRequestHandler
13
  from datetime import datetime, timedelta
 
14
  from pySmartDL import SmartDL
15
  from pydrive2.auth import GoogleAuth
16
  from pydrive2.drive import GoogleDrive
17
  from oauth2client.service_account import ServiceAccountCredentials
 
18
  from telegram import Update
19
- from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, filters, ContextTypes
 
 
 
 
 
 
20
  from apscheduler.schedulers.asyncio import AsyncIOScheduler
21
 
22
- # Logging setup
 
 
23
  logging.basicConfig(
24
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
25
- level=logging.INFO
26
  )
27
  logger = logging.getLogger(__name__)
28
 
29
- # Environment Variables
 
 
30
  BOT_TOKEN = os.getenv("BOT_TOKEN")
31
  GDRIVE_SERVICE_ACCOUNT_JSON = os.getenv("GDRIVE_SERVICE_ACCOUNT_JSON")
32
  GDRIVE_FOLDER_ID = os.getenv("GDRIVE_FOLDER_ID")
33
  AUTHORIZED_USER_ID = os.getenv("AUTHORIZED_USER_ID")
34
 
35
  if AUTHORIZED_USER_ID:
36
- AUTHORIZED_USER_ID = int(AUTHORIZED_USER_ID)
37
-
38
- # Global Drive Client variable and processing lock
 
 
 
 
 
 
39
  drive_client = None
40
- processing_lock = asyncio.Lock()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
- # Google Drive Authentication
 
 
 
43
  def get_gdrive_client():
44
  global drive_client
 
45
  if not GDRIVE_SERVICE_ACCOUNT_JSON:
46
- logger.error("GDRIVE_SERVICE_ACCOUNT_JSON not found in environment variables.")
47
  return None
48
 
49
  try:
50
- # Save JSON to a temporary file
51
- with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.json') as temp_json:
52
- temp_json.write(GDRIVE_SERVICE_ACCOUNT_JSON)
53
- temp_json_path = temp_json.name
54
-
55
- scope = ['https://www.googleapis.com/auth/drive']
 
 
56
  gauth = GoogleAuth()
57
- gauth.auth_method = 'service'
58
- gauth.credentials = ServiceAccountCredentials.from_json_keyfile_name(temp_json_path, scope)
 
 
59
 
60
  drive_client = GoogleDrive(gauth)
 
61
 
62
- # Clean up the temporary file after initializing
63
- os.unlink(temp_json_path)
64
  return drive_client
 
65
  except Exception as e:
66
- logger.error(f"Error authenticating with Google Drive: {e}")
67
  return None
68
 
69
- # Scheduler for automatic deletion
70
- scheduler = AsyncIOScheduler()
71
- scheduler.start()
72
 
73
- async def delete_from_drive(file_id):
 
 
 
 
74
  try:
75
  global drive_client
76
  if not drive_client:
77
  drive_client = await asyncio.to_thread(get_gdrive_client)
78
 
79
  if drive_client:
80
- file = await asyncio.to_thread(drive_client.CreateFile, {'id': file_id})
81
- await asyncio.to_thread(file.Delete)
82
- logger.info(f"File {file_id} deleted successfully after 2 hours.")
 
 
83
  except Exception as e:
84
- logger.error(f"Error deleting file {file_id}: {e}")
85
 
86
- async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
87
- if not AUTHORIZED_USER_ID or update.effective_user.id != AUTHORIZED_USER_ID:
88
- return
89
- await update.message.reply_text("Merhaba! Bana bir direkt indirme linki gönder, senin için Google Drive'a yükleyeyim.")
90
 
91
- def download_file(url, dest):
92
- obj = SmartDL(url, dest, progress_bar=False, timeout=30)
 
 
 
93
  obj.start()
94
  return obj
95
 
96
- def upload_file(file_path, file_name):
 
97
  global drive_client
98
  if not drive_client:
99
  drive_client = get_gdrive_client()
 
 
100
 
101
- metadata = {'title': file_name}
102
  if GDRIVE_FOLDER_ID:
103
- metadata['parents'] = [{'id': GDRIVE_FOLDER_ID}]
104
 
105
  gfile = drive_client.CreateFile(metadata)
106
  gfile.SetContentFile(file_path)
107
  gfile.Upload()
108
  return gfile
109
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
111
- if not AUTHORIZED_USER_ID or update.effective_user.id != AUTHORIZED_USER_ID:
112
- logger.warning(f"Unauthorized access attempt by user {update.effective_user.id}")
 
 
 
 
 
113
  return
114
 
115
- url = update.message.text
116
  if not url.startswith(("http://", "https://")):
117
- await update.message.reply_text("Geçersiz link. Lütfen geçerli bir HTTP veya HTTPS linki gönder.")
 
 
118
  return
119
 
 
120
  async with processing_lock:
121
- status_message = await update.message.reply_text("⏳ İşlem sıraya alındı ve başlatılıyor...")
 
 
122
 
 
123
  try:
124
- # Download to current directory instead of /tmp for HF Spaces compatibility with large files
125
- dest = os.getcwd()
126
- await status_message.edit_text("📥 Dosya sunucuya indiriliyor...")
127
 
 
128
  obj = await asyncio.to_thread(download_file, url, dest)
129
 
130
- if not obj.is_successful():
131
- await status_message.edit_text("❌ İndirme başarısız oldu. Linkin doğruluğunu kontrol edin.")
 
 
132
  return
133
 
134
  file_path = obj.get_dest()
135
  file_name = os.path.basename(file_path)
136
-
137
- # Size check (Approx 5GB)
138
  file_size = os.path.getsize(file_path)
139
- if file_size > 5 * 1024 * 1024 * 1024:
140
- await status_message.edit_text(f"⚠️ Dosya çok büyük ({file_size / (1024**3):.2f} GB). Maksimum limit 5GB.")
 
 
 
 
 
 
141
  os.remove(file_path)
 
142
  return
143
 
144
- await status_message.edit_text(f"✅ İndirme tamamlandı: `{file_name}`\n📤 Google Drive'a yükleniyor...", parse_mode='Markdown')
 
 
 
 
 
 
 
 
 
 
145
 
146
- # Upload to Google Drive
147
  gfile = await asyncio.to_thread(upload_file, file_path, file_name)
148
 
149
- file_id = gfile['id']
150
- drive_link = gfile['alternateLink']
 
 
 
151
 
152
- await status_message.edit_text(
153
  f"🚀 Yükleme başarılı!\n\n"
154
  f"📁 Dosya: `{file_name}`\n"
 
155
  f"🔗 Link: {drive_link}\n\n"
156
- f"⏱ Bu dosya 2 saat sonra otomatik olarak silinecektir.",
157
- parse_mode='Markdown'
158
  )
159
 
160
- # Clean up local file
161
- if os.path.exists(file_path):
162
  os.remove(file_path)
 
163
 
164
- # Schedule deletion
165
  run_date = datetime.now() + timedelta(hours=2)
166
- scheduler.add_job(delete_from_drive, 'date', run_date=run_date, args=[file_id])
 
 
167
 
168
  except Exception as e:
169
- logger.error(f"Error during processing: {e}")
170
- await update.message.reply_text(f"❌ Bir hata oluştu: {str(e)}")
171
-
172
- # Simple health check server for Hugging Face Spaces
173
- class HealthCheckHandler(BaseHTTPRequestHandler):
174
- def do_GET(self):
175
- self.send_response(200)
176
- self.end_headers()
177
- self.wfile.write(b"Bot is running!")
178
-
179
- def run_health_check_server():
180
- port = int(os.environ.get("PORT", 7860))
181
- server_address = ('', port)
182
- httpd = HTTPServer(server_address, HealthCheckHandler)
183
- logger.info(f"Starting health check server on port {port}...")
184
- httpd.serve_forever()
185
-
 
186
  def main():
187
- if not BOT_TOKEN:
188
- logger.error("BOT_TOKEN not found!")
189
- return
190
 
191
- # Start health check server in a separate thread
192
- threading.Thread(target=run_health_check_server, daemon=True).start()
193
-
194
- # Initialize Drive Client
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  get_gdrive_client()
196
 
197
- application = ApplicationBuilder().token(BOT_TOKEN).build()
198
-
199
- application.add_handler(CommandHandler("start", start))
200
- application.add_handler(MessageHandler(filters.TEXT & (~filters.COMMAND), handle_message))
201
-
202
- logger.info("Bot started...")
203
- application.run_polling()
204
-
205
- if __name__ == '__main__':
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  main()
 
1
  import collections
2
  import collections.abc
3
+
4
+ # ─── Monkeypatch: pySmartDL Python 3.10+ uyumluluğu ───
5
+ if not hasattr(collections, "Iterable"):
6
+ collections.Iterable = collections.abc.Iterable
7
 
8
  import os
9
  import json
 
11
  import tempfile
12
  import asyncio
13
  import threading
14
+ import signal
15
+ import sys
16
+ import time
17
  from http.server import HTTPServer, BaseHTTPRequestHandler
18
  from datetime import datetime, timedelta
19
+
20
  from pySmartDL import SmartDL
21
  from pydrive2.auth import GoogleAuth
22
  from pydrive2.drive import GoogleDrive
23
  from oauth2client.service_account import ServiceAccountCredentials
24
+
25
  from telegram import Update
26
+ from telegram.ext import (
27
+ ApplicationBuilder,
28
+ CommandHandler,
29
+ MessageHandler,
30
+ filters,
31
+ ContextTypes,
32
+ )
33
  from apscheduler.schedulers.asyncio import AsyncIOScheduler
34
 
35
+ # ──────────────────────────────────────────────
36
+ # LOGGING
37
+ # ──────────────────────────────────────────────
38
  logging.basicConfig(
39
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
40
+ level=logging.INFO,
41
  )
42
  logger = logging.getLogger(__name__)
43
 
44
+ # ──────────────────────────────────────────────
45
+ # ENVIRONMENT VARIABLES
46
+ # ──────────────────────────────────────────────
47
  BOT_TOKEN = os.getenv("BOT_TOKEN")
48
  GDRIVE_SERVICE_ACCOUNT_JSON = os.getenv("GDRIVE_SERVICE_ACCOUNT_JSON")
49
  GDRIVE_FOLDER_ID = os.getenv("GDRIVE_FOLDER_ID")
50
  AUTHORIZED_USER_ID = os.getenv("AUTHORIZED_USER_ID")
51
 
52
  if AUTHORIZED_USER_ID:
53
+ try:
54
+ AUTHORIZED_USER_ID = int(AUTHORIZED_USER_ID)
55
+ except ValueError:
56
+ logger.error("AUTHORIZED_USER_ID geçerli bir sayı değil!")
57
+ AUTHORIZED_USER_ID = None
58
+
59
+ # ──────────────────────────────────────────────
60
+ # GLOBAL STATE
61
+ # ──────────────────────────────────────────────
62
  drive_client = None
63
+ processing_lock = None # event loop varken oluşturulacak
64
+ scheduler = None # event loop varken başlatılacak
65
+ health_server_ready = False # health check hazır mı?
66
+
67
+
68
+ # ══════════════════════════════════════════════
69
+ # HEALTH CHECK SERVER (ayrı thread)
70
+ # ══════════════════════════════════════════════
71
+ class HealthCheckHandler(BaseHTTPRequestHandler):
72
+ """HuggingFace Spaces bu endpoint'e bakarak 'Running' durumuna geçirir."""
73
+
74
+ def do_GET(self):
75
+ self.send_response(200)
76
+ self.send_header("Content-Type", "text/plain")
77
+ self.end_headers()
78
+ self.wfile.write(b"OK - Bot is running!")
79
+
80
+ def do_HEAD(self):
81
+ self.send_response(200)
82
+ self.send_header("Content-Type", "text/plain")
83
+ self.end_headers()
84
+
85
+ # Logları sustur (her 30 sn health check loglamasın)
86
+ def log_message(self, format, *args):
87
+ return
88
+
89
+
90
+ def run_health_check_server():
91
+ """Port 7860'da basit HTTP sunucusu başlatır."""
92
+ global health_server_ready
93
+
94
+ port = int(os.environ.get("PORT", 7860))
95
+ server = HTTPServer(("0.0.0.0", port), HealthCheckHandler)
96
+ health_server_ready = True
97
+ logger.info(f"✅ Health-check sunucusu 0.0.0.0:{port} üzerinde çalışıyor")
98
+ server.serve_forever()
99
 
100
+
101
+ # ══════════════════════════════════════════════
102
+ # GOOGLE DRIVE
103
+ # ══════════════════════════════════════════════
104
  def get_gdrive_client():
105
  global drive_client
106
+
107
  if not GDRIVE_SERVICE_ACCOUNT_JSON:
108
+ logger.error("GDRIVE_SERVICE_ACCOUNT_JSON ortam değişkeni bulunamadı.")
109
  return None
110
 
111
  try:
112
+ # JSON geçici dosyaya yaz
113
+ with tempfile.NamedTemporaryFile(
114
+ mode="w", delete=False, suffix=".json"
115
+ ) as tmp:
116
+ tmp.write(GDRIVE_SERVICE_ACCOUNT_JSON)
117
+ tmp_path = tmp.name
118
+
119
+ scope = ["https://www.googleapis.com/auth/drive"]
120
  gauth = GoogleAuth()
121
+ gauth.auth_method = "service"
122
+ gauth.credentials = ServiceAccountCredentials.from_json_keyfile_name(
123
+ tmp_path, scope
124
+ )
125
 
126
  drive_client = GoogleDrive(gauth)
127
+ os.unlink(tmp_path)
128
 
129
+ logger.info("✅ Google Drive bağlantısı başarılı.")
 
130
  return drive_client
131
+
132
  except Exception as e:
133
+ logger.error(f"Google Drive kimlik doğrulama hatası: {e}")
134
  return None
135
 
 
 
 
136
 
137
+ # ══════════════════════════════════════════════
138
+ # SCHEDULER – OTOMATİK SİLME
139
+ # ══════════════════════════════════════════════
140
+ async def delete_from_drive(file_id: str):
141
+ """2 saat sonra çağrılır, dosyayı Drive'dan siler."""
142
  try:
143
  global drive_client
144
  if not drive_client:
145
  drive_client = await asyncio.to_thread(get_gdrive_client)
146
 
147
  if drive_client:
148
+ gfile = await asyncio.to_thread(
149
+ drive_client.CreateFile, {"id": file_id}
150
+ )
151
+ await asyncio.to_thread(gfile.Delete)
152
+ logger.info(f"🗑 Dosya {file_id} başarıyla silindi (2 saat doldu).")
153
  except Exception as e:
154
+ logger.error(f"Dosya silme hatası ({file_id}): {e}")
155
 
 
 
 
 
156
 
157
+ # ══════════════════════════════════════════════
158
+ # DOWNLOAD & UPLOAD HELPERS
159
+ # ══════════════════════════════════════════════
160
+ def download_file(url: str, dest_dir: str) -> SmartDL:
161
+ obj = SmartDL(url, dest_dir, progress_bar=False, timeout=120)
162
  obj.start()
163
  return obj
164
 
165
+
166
+ def upload_file(file_path: str, file_name: str):
167
  global drive_client
168
  if not drive_client:
169
  drive_client = get_gdrive_client()
170
+ if not drive_client:
171
+ raise RuntimeError("Google Drive istemcisi başlatılamadı!")
172
 
173
+ metadata = {"title": file_name}
174
  if GDRIVE_FOLDER_ID:
175
+ metadata["parents"] = [{"id": GDRIVE_FOLDER_ID}]
176
 
177
  gfile = drive_client.CreateFile(metadata)
178
  gfile.SetContentFile(file_path)
179
  gfile.Upload()
180
  return gfile
181
 
182
+
183
+ # ══════════════════════════════════════════════
184
+ # TELEGRAM HANDLERS
185
+ # ══════════════════════════════════════════════
186
+ async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE):
187
+ if AUTHORIZED_USER_ID and update.effective_user.id != AUTHORIZED_USER_ID:
188
+ return
189
+ await update.message.reply_text(
190
+ "Merhaba! Bana bir direkt indirme linki gönder, "
191
+ "senin için Google Drive'a yükleyeyim. 🚀"
192
+ )
193
+
194
+
195
  async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
196
+ global processing_lock, scheduler
197
+
198
+ # Yetki kontrolü
199
+ if AUTHORIZED_USER_ID and update.effective_user.id != AUTHORIZED_USER_ID:
200
+ logger.warning(
201
+ f"Yetkisiz erişim denemesi: user_id={update.effective_user.id}"
202
+ )
203
  return
204
 
205
+ url = update.message.text.strip()
206
  if not url.startswith(("http://", "https://")):
207
+ await update.message.reply_text(
208
+ "⛔ Geçersiz link. Lütfen geçerli bir HTTP/HTTPS linki gönder."
209
+ )
210
  return
211
 
212
+ # Lock: aynı anda tek dosya işlensin
213
  async with processing_lock:
214
+ status_msg = await update.message.reply_text(
215
+ "⏳ İşlem sıraya alındı, başlatılıyor..."
216
+ )
217
 
218
+ file_path = None
219
  try:
220
+ # ── İndirme ──
221
+ dest = os.path.join(os.getcwd(), "downloads")
222
+ os.makedirs(dest, exist_ok=True)
223
 
224
+ await status_msg.edit_text("📥 Dosya sunucuya indiriliyor...")
225
  obj = await asyncio.to_thread(download_file, url, dest)
226
 
227
+ if not obj.isSuccessful():
228
+ await status_msg.edit_text(
229
+ "❌ İndirme başarısız oldu. Linkin geçerliliğini kontrol edin."
230
+ )
231
  return
232
 
233
  file_path = obj.get_dest()
234
  file_name = os.path.basename(file_path)
 
 
235
  file_size = os.path.getsize(file_path)
236
+
237
+ # 5 GB sınır
238
+ max_size = 5 * 1024 * 1024 * 1024
239
+ if file_size > max_size:
240
+ await status_msg.edit_text(
241
+ f"⚠️ Dosya çok büyük ({file_size / (1024**3):.2f} GB). "
242
+ f"Maksimum 5 GB desteklenir."
243
+ )
244
  os.remove(file_path)
245
+ file_path = None
246
  return
247
 
248
+ size_str = (
249
+ f"{file_size / (1024**2):.1f} MB"
250
+ if file_size > 1024 * 1024
251
+ else f"{file_size / 1024:.1f} KB"
252
+ )
253
+
254
+ await status_msg.edit_text(
255
+ f"✅ İndirme tamamlandı: `{file_name}` ({size_str})\n"
256
+ f"📤 Google Drive'a yükleniyor...",
257
+ parse_mode="Markdown",
258
+ )
259
 
260
+ # ── Yükleme ──
261
  gfile = await asyncio.to_thread(upload_file, file_path, file_name)
262
 
263
+ file_id = gfile["id"]
264
+ drive_link = gfile.get(
265
+ "alternateLink",
266
+ f"https://drive.google.com/file/d/{file_id}/view",
267
+ )
268
 
269
+ await status_msg.edit_text(
270
  f"🚀 Yükleme başarılı!\n\n"
271
  f"📁 Dosya: `{file_name}`\n"
272
+ f"📦 Boyut: {size_str}\n"
273
  f"🔗 Link: {drive_link}\n\n"
274
+ f"⏱ Bu dosya **2 saat** sonra otomatik olarak silinecektir.",
275
+ parse_mode="Markdown",
276
  )
277
 
278
+ # Lokal dosyayı temizle
279
+ if file_path and os.path.exists(file_path):
280
  os.remove(file_path)
281
+ file_path = None
282
 
283
+ # 2 saat sonra sil
284
  run_date = datetime.now() + timedelta(hours=2)
285
+ scheduler.add_job(
286
+ delete_from_drive, "date", run_date=run_date, args=[file_id]
287
+ )
288
 
289
  except Exception as e:
290
+ logger.error(f"İşlem hatası: {e}", exc_info=True)
291
+ try:
292
+ await status_msg.edit_text(f"❌ Bir hata oluştu:\n`{e}`", parse_mode="Markdown")
293
+ except Exception:
294
+ pass
295
+
296
+ finally:
297
+ # Hata olsa bile lokal dosyayı temizle
298
+ if file_path and os.path.exists(file_path):
299
+ try:
300
+ os.remove(file_path)
301
+ except OSError:
302
+ pass
303
+
304
+
305
+ # ══════════════════════════════════════════════
306
+ # MAIN
307
+ # ══════════════════════════════════════════════
308
  def main():
309
+ global processing_lock, scheduler
 
 
310
 
311
+ if not BOT_TOKEN:
312
+ logger.error("❌ BOT_TOKEN ortam değişkeni bulunamadı! Çıkılıyor.")
313
+ sys.exit(1)
314
+
315
+ # ── 1) Health-check sunucusunu HEMEN başlat ──
316
+ # HuggingFace bunu görünce "Running" yapar
317
+ health_thread = threading.Thread(target=run_health_check_server, daemon=True)
318
+ health_thread.start()
319
+
320
+ # Health check'in gerçekten bind olmasını bekle
321
+ for _ in range(50):
322
+ if health_server_ready:
323
+ break
324
+ time.sleep(0.1)
325
+ logger.info("Health-check sunucusu hazır.")
326
+
327
+ # ── 2) Google Drive istemcisini başlat ──
328
  get_gdrive_client()
329
 
330
+ # ── 3) Telegram Application'ı oluştur ──
331
+ application = (
332
+ ApplicationBuilder()
333
+ .token(BOT_TOKEN)
334
+ .connect_timeout(30)
335
+ .read_timeout(30)
336
+ .write_timeout(30)
337
+ .build()
338
+ )
339
+
340
+ application.add_handler(CommandHandler("start", cmd_start))
341
+ application.add_handler(
342
+ MessageHandler(filters.TEXT & (~filters.COMMAND), handle_message)
343
+ )
344
+
345
+ # ── 4) Post-init: event loop var, şimdi lock ve scheduler başlat ──
346
+ async def post_init(app):
347
+ global processing_lock, scheduler
348
+ processing_lock = asyncio.Lock()
349
+ scheduler = AsyncIOScheduler()
350
+ scheduler.start()
351
+ logger.info("✅ Scheduler ve processing lock başlatıldı.")
352
+
353
+ application.post_init = post_init
354
+
355
+ # ── 5) Bot'u çalıştır ──
356
+ logger.info("🤖 Bot başlatılıyor (polling)...")
357
+ application.run_polling(
358
+ allowed_updates=Update.ALL_TYPES,
359
+ drop_pending_updates=True,
360
+ )
361
+
362
+
363
+ if __name__ == "__main__":
364
  main()