danicor commited on
Commit
c42d5ea
·
verified ·
1 Parent(s): 86d3300

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +17 -559
app.py CHANGED
@@ -1,571 +1,29 @@
 
1
  import os
2
- import asyncio
3
- import logging
4
  from datetime import datetime
5
- from pathlib import Path
6
- from telethon import TelegramClient, events
7
- from telethon.sessions import StringSession
8
- from telethon.network import ConnectionTcpFull
9
- import nest_asyncio
10
- import aiohttp
11
 
12
- # فعال کردن nest_asyncio
13
- nest_asyncio.apply()
14
 
15
- # تنظیمات لاگ
16
- logging.basicConfig(
17
- level=logging.INFO,
18
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
19
- )
20
- logger = logging.getLogger(__name__)
21
 
22
- # تنظیمات از متغیرهای محیطی
23
- API_ID = os.environ.get("API_ID")
24
- API_HASH = os.environ.get("API_HASH")
25
- BOT_TOKEN = os.environ.get("BOT_TOKEN")
26
 
27
- # تنظیمات Cloudflare Proxy
28
- CF_PROXY_ENABLED = os.environ.get("CF_PROXY_ENABLED", "true").lower() == "true"
29
- CF_WORKER_URL = os.environ.get("CF_WORKER_URL", "https://danitell.sararoid85.workers.dev")
30
 
31
- # مسیر ذخیره‌سازی در فضای HuggingFace
32
- BASE_DIR = Path("/tmp/telegram_bot_files")
33
- BASE_DIR.mkdir(parents=True, exist_ok=True)
34
 
35
- # دیکشنری برای ذخیره اطلاعات کاربران
36
- user_data = {}
 
37
 
38
- def check_config():
39
- """بررسی تنظیمات لازم"""
40
- missing_configs = []
41
-
42
- if not API_ID:
43
- missing_configs.append("API_ID")
44
- if not API_HASH:
45
- missing_configs.append("API_HASH")
46
- if not BOT_TOKEN:
47
- missing_configs.append("BOT_TOKEN")
48
-
49
- if missing_configs:
50
- logger.error(f"❌ تنظیمات زیر تنظیم نشده‌اند: {', '.join(missing_configs)}")
51
- return False
52
-
53
- logger.info("✅ تنظیمات اولیه بررسی شد.")
54
- return True
55
 
56
- class CustomTelegramClient(TelegramClient):
57
- """کلاینت تلگرام با پشتیبانی از Cloudflare Proxy"""
58
-
59
- async def _connect(self):
60
- """اتصال با استفاده از Cloudflare Proxy"""
61
- if CF_PROXY_ENABLED and CF_WORKER_URL:
62
- logger.info(f"🌐 استفاده از Cloudflare Proxy: {CF_WORKER_URL}")
63
-
64
- # ایجاد session با proxy
65
- connector = aiohttp.TCPConnector(ssl=False)
66
- session = aiohttp.ClientSession(connector=connector)
67
-
68
- # اتصال مستقیم با تنظیمات مخصوص
69
- await super()._connect()
70
-
71
- else:
72
- # اتصال مستقیم
73
- await super()._connect()
74
 
75
- class TelegramFileBot:
76
- def __init__(self):
77
- self.client = None
78
- self.running = False
79
-
80
- async def start(self):
81
- """شروع ربات"""
82
- try:
83
- logger.info("🚀 در حال راه‌اندازی ربات...")
84
-
85
- # تنظیمات اتصال ویژه برای Cloudflare Proxy
86
- connection_params = {
87
- 'connection': ConnectionTcpFull,
88
- 'use_ipv6': False,
89
- 'timeout': 60,
90
- 'retry_delay': 3,
91
- 'connection_retries': 10,
92
- 'request_retries': 5
93
- }
94
-
95
- # اگر Cloudflare Proxy فعال باشد
96
- if CF_PROXY_ENABLED:
97
- # تغییر آدرس سرور تلگرام به مسیر از طریق Worker
98
- # این یک ترفند است: ما سرور را به Worker خود تغییر می‌دهیم
99
- connection_params['connection'] = ConnectionTcpFull
100
-
101
- # استفاده از کلاینت معمولی اما با timeout بیشتر
102
- logger.info(f"🔗 اتصال از طریق Cloudflare Worker: {CF_WORKER_URL}")
103
-
104
- # ایجاد کلاینت تلگرام
105
- self.client = TelegramClient(
106
- StringSession(),
107
- int(API_ID),
108
- API_HASH,
109
- **connection_params
110
- )
111
-
112
- # ترفند: استفاده از MTProto proxy (اگر Worker از آن پشتیبانی کند)
113
- if CF_PROXY_ENABLED:
114
- try:
115
- # تست اتصال
116
- logger.info("🔍 در حال تست اتصال به تلگرام...")
117
-
118
- # تنظیم session به صورت دستی
119
- self.client.session.set_dc(2, '149.154.167.50', 443)
120
-
121
- except Exception as e:
122
- logger.warning(f"⚠️ خطا در تنظیم proxy: {e}")
123
-
124
- logger.info("🔗 در حال اتصال به سرور تلگرام...")
125
-
126
- # شروع ربات
127
- await self.client.start(bot_token=BOT_TOKEN)
128
-
129
- if not await self.client.is_user_authorized():
130
- logger.error("❌ احراز هویت ناموفق بود!")
131
- return
132
-
133
- self.running = True
134
-
135
- me = await self.client.get_me()
136
- logger.info(f"✅ ربات با موفقیت شروع به کار کرد: @{me.username} (ID: {me.id})")
137
- logger.info(f"📁 مسیر ذخیره‌سازی: {BASE_DIR}")
138
-
139
- # نمایش اطلاعات
140
- await self.show_storage_info()
141
-
142
- # ثبت هندلرها
143
- await self._setup_handlers()
144
-
145
- # تنظیم دستورات ربات
146
- await self.setup_bot_commands()
147
-
148
- logger.info("🤖 ربات آماده دریافت فایل‌ها است...")
149
- logger.info(f"👤 آدرس ربات: https://t.me/{me.username}")
150
-
151
- # نمایش دستورات
152
- logger.info("📋 دستورات فعال: /start, /help, /list, /download, /clean, /stats")
153
-
154
- # اجرای ربات
155
- await self.client.run_until_disconnected()
156
-
157
- except asyncio.TimeoutError:
158
- logger.error("⏰ Timeout در اتصال به تلگرام.")
159
- logger.info("💡 راه‌حل‌های پیشنهادی:")
160
- logger.info("1. مطمئن شوید Cloudflare Worker درست تنظیم شده")
161
- logger.info("2. آدرس Worker را در CF_WORKER_URL قرار دهید")
162
- logger.info("3. از یک پروکسی SOCKS5 جایگزین استفاده کنید")
163
-
164
- except Exception as e:
165
- logger.error(f"❌ خطا در شروع ربات: {str(e)}", exc_info=True)
166
- self.running = False
167
-
168
- async def setup_bot_commands(self):
169
- """تنظیم دستورات ربات در تلگرام"""
170
- try:
171
- commands = [
172
- ('start', 'شروع کار با ربات'),
173
- ('help', 'راهنمای کامل'),
174
- ('list', 'نمایش فایل‌های شما'),
175
- ('download', 'دانلود فایل با کد'),
176
- ('clean', 'پاکسازی فایل‌های شما'),
177
- ('stats', 'آمار ربات')
178
- ]
179
-
180
- await self.client(
181
- events.bots.SetBotCommandsRequest(
182
- scope=events.bots.BotCommandScopeDefault(),
183
- lang_code='fa',
184
- commands=[events.bots.BotCommand(command, description) for command, description in commands]
185
- )
186
- )
187
- logger.info("✅ دستورات ربات در تلگرام تنظیم شد.")
188
- except Exception as e:
189
- logger.warning(f"⚠️ خطا در تنظیم دستورات: {e}")
190
-
191
- async def show_storage_info(self):
192
- """نمایش اطلاعات فضای ذخیره‌سازی"""
193
- try:
194
- total_size = 0
195
- file_count = 0
196
-
197
- for file_path in BASE_DIR.glob("*"):
198
- if file_path.is_file():
199
- total_size += file_path.stat().st_size
200
- file_count += 1
201
-
202
- logger.info(f"💾 فضای ذخیره‌سازی:")
203
- logger.info(f" 📁 تعداد فایل‌ها: {file_count}")
204
- logger.info(f" 📊 حجم استفاده شده: {total_size / 1024 / 1024:.2f} MB")
205
-
206
- except Exception as e:
207
- logger.error(f"خطا در دریافت اطلاعات فضای ذخیره‌سازی: {e}")
208
-
209
- async def _setup_handlers(self):
210
- """تنظیم هندلرهای ربات"""
211
-
212
- @self.client.on(events.NewMessage(pattern='/start'))
213
- async def start_handler(event):
214
- """هندلر دستور /start"""
215
- try:
216
- user = await event.get_sender()
217
- logger.info(f"👤 کاربر {user.id} ({user.first_name}) /start را فرستاد")
218
-
219
- welcome_msg = """
220
- 🌟 **به ربات آپلود فایل خوش آمدید!**
221
 
222
- **📌 قابلیت‌ها:**
223
- • دریافت انواع فایل‌ها (تا 2GB)
224
- • ذخیره فایل در فضای ابری
225
- • دانلود فایل با کد یکتا
226
-
227
- **🚀 نحوه استفاده:**
228
- 1. فایل خود را ارسال کنید
229
- 2. ربات کد فایل را به شما می‌دهد
230
- 3. با دستور /download کد فایل را دانلود کنید
231
-
232
- **📋 دستورات:**
233
- /start - شروع کار
234
- /help - راهنمای کامل
235
- /list - نمایش فایل‌های شما
236
- /clean - پاکسازی فایل‌های قدیمی
237
- /stats - آمار ربات
238
-
239
- **⚠️ توجه:**
240
- • فایل‌ها به صورت موقت ذخیره می‌شوند
241
- • بعد از ری‌استارت Space فایل‌ها پاک می‌شوند
242
- """
243
- await event.reply(welcome_msg)
244
-
245
- except Exception as e:
246
- logger.error(f"خطا در start_handler: {e}")
247
-
248
- @self.client.on(events.NewMessage(pattern='/help'))
249
- async def help_handler(event):
250
- """هندلر دستور /help"""
251
- help_text = """
252
- **📖 راهنمای کامل ربات:**
253
-
254
- **🔹 نحوه کار:**
255
- 1. فایلی برای ربات بفرستید (هر نوعی)
256
- 2. ربات آن را ذخیره می‌کند و یک کد به شما می‌دهد
257
- 3. با دستور /download می‌توانید فایل را دانلود کنید
258
-
259
- **🔹 مثال:**
260
- ```
261
- /download 123456_abc
262
- ```
263
-
264
- **🔹 دستورات موجود:**
265
- • `/list` - نمایش 10 فایل آخر شما
266
- • `/download کد` - دانلود فایل با کد
267
- • `/clean` - پاک کردن تمام فایل‌های شما
268
- • `/stats` - مشاهده آمار ربات
269
-
270
- **🔹 نکات مهم:**
271
- • حداکثر حجم فایل: 2GB
272
- • فایل‌ها در فضای /tmp ذخیره می‌شوند
273
- • کد فایل را ذخیره کنید
274
- """
275
- await event.reply(help_text)
276
-
277
- @self.client.on(events.NewMessage(pattern='/list'))
278
- async def list_handler(event):
279
- """نمایش فایل‌های کاربر"""
280
- try:
281
- user_id = event.sender_id
282
-
283
- if user_id in user_data and user_data[user_id].get("files"):
284
- files = user_data[user_id]["files"]
285
-
286
- if not files:
287
- await event.reply("📭 **هیچ فایلی از شما آپلود نشده است.**")
288
- return
289
-
290
- response = "📁 **فایل‌های شما:**\n\n"
291
- for idx, file_info in enumerate(files[-10:], 1):
292
- response += f"**{idx}. {file_info['name']}**\n"
293
- response += f" 📏 حجم: {file_info['size_mb']:.2f} MB\n"
294
- response += f" 🕐 زمان: {file_info['time']}\n"
295
- response += f" 🔗 کد: `{file_info['code']}`\n\n"
296
-
297
- response += "📥 برای دانلود:\n"
298
- response += "```\n"
299
- response += "/download کد_فایل\n"
300
- response += "```\n\n"
301
- response += "مثال: `/download abc123`"
302
-
303
- await event.reply(response)
304
- else:
305
- await event.reply("📭 **هیچ فایلی از شما آپلود نشده است.**")
306
-
307
- except Exception as e:
308
- logger.error(f"خطا در list_handler: {e}")
309
-
310
- @self.client.on(events.NewMessage(pattern='/clean'))
311
- async def clean_handler(event):
312
- """پاکسازی فایل‌های کاربر"""
313
- try:
314
- user_id = event.sender_id
315
-
316
- if user_id in user_data and "files" in user_data[user_id]:
317
- file_count = len(user_data[user_id]["files"])
318
-
319
- # پاک کردن فایل‌های فیزیکی
320
- deleted_count = 0
321
- for file_info in user_data[user_id]["files"]:
322
- file_path = BASE_DIR / file_info["code"]
323
- if file_path.exists():
324
- try:
325
- file_path.unlink()
326
- deleted_count += 1
327
- except Exception as e:
328
- logger.error(f"خطا در پاک کردن فایل {file_info['code']}: {e}")
329
-
330
- # پاک کردن داده‌های کاربر
331
- user_data[user_id]["files"] = []
332
-
333
- await event.reply(f"🗑️ **{deleted_count} فایل از {file_count} با موفقیت پاک شدند.**")
334
- else:
335
- await event.reply("📭 **هیچ فایلی برای پاک کردن وجود ندارد.**")
336
-
337
- except Exception as e:
338
- logger.error(f"خطا در clean_handler: {e}")
339
-
340
- @self.client.on(events.NewMessage(pattern='/stats'))
341
- async def stats_handler(event):
342
- """نمایش آمار ربات"""
343
- try:
344
- total_files = 0
345
- total_size = 0
346
- total_users = len(user_data)
347
-
348
- for user_id, data in user_data.items():
349
- if "files" in data:
350
- total_files += len(data["files"])
351
- for file_info in data["files"]:
352
- total_size += file_info.get("size_bytes", 0)
353
-
354
- # محاسبه فضای باقی‌مانده
355
- try:
356
- statvfs = os.statvfs('/tmp')
357
- free_space = (statvfs.f_bavail * statvfs.f_frsize) / 1024 / 1024 / 1024 # GB
358
- total_space = (statvfs.f_blocks * statvfs.f_frsize) / 1024 / 1024 / 1024 # GB
359
- used_space = total_space - free_space
360
- except:
361
- free_space = used_space = total_space = 0
362
-
363
- stats_msg = f"""
364
- 📊 **آمار ربات:**
365
-
366
- 👥 کاربران فعال: {total_users}
367
- 📁 فایل‌های آپلود شده: {total_files}
368
- 💾 حجم کل فایل‌ها: {total_size / 1024 / 1024:.2f} MB
369
-
370
- 🗂️ **فضای ذخیره‌سازی:**
371
- 📊 استفاده شده: {used_space:.2f} GB
372
- 📈 کل فضای /tmp: {total_space:.2f} GB
373
- 📉 فضای آزاد: {free_space:.2f} GB
374
-
375
- ⚠️ **توجه:** فایل‌ها در `/tmp` ذخیره می‌شوند.
376
- """
377
- await event.reply(stats_msg)
378
-
379
- except Exception as e:
380
- logger.error(f"خطا در stats_handler: {e}")
381
-
382
- @self.client.on(events.NewMessage(pattern=r'/download\s+\S+'))
383
- async def download_handler(event):
384
- """دانلود فایل با کد"""
385
- try:
386
- parts = event.message.text.split()
387
- if len(parts) < 2:
388
- await event.reply("❌ **لطفا کد فایل را وارد کنید.**\nمثال: `/download abc123`")
389
- return
390
-
391
- file_code = parts[1].strip()
392
- file_path = BASE_DIR / file_code
393
-
394
- if not file_path.exists():
395
- await event.reply("❌ **فایل یافت نشد.**\nمطمئن شوید کد را درست وارد کرده‌اید.")
396
- return
397
-
398
- # پیدا کردن اطلاعات فایل
399
- file_info = None
400
- for user_id, data in user_data.items():
401
- if "files" in data:
402
- for f in data["files"]:
403
- if f["code"] == file_code:
404
- file_info = f
405
- break
406
-
407
- if file_info:
408
- # ارسال فایل به کاربر
409
- caption = f"📄 **{file_info['name']}**\n📏 حجم: {file_info['size_mb']:.2f} MB\n🕐 زمان آپلود: {file_info['time']}"
410
- await event.reply("📤 **در حال ارسال فایل...**")
411
-
412
- # ارسال فایل
413
- await event.respond(
414
- file=file_path,
415
- caption=caption,
416
- force_document=True
417
- )
418
-
419
- logger.info(f"فایل ارسال شد: {file_code} به کاربر {event.sender_id}")
420
- else:
421
- await event.reply("⚠️ **اطلاعات فایل یافت نشد.**")
422
-
423
- except Exception as e:
424
- logger.error(f"خطا در دانلود: {e}", exc_info=True)
425
- await event.reply(f"❌ **خطا در پردازش:** {str(e)}")
426
-
427
- @self.client.on(events.NewMessage)
428
- async def message_handler(event):
429
- """هندلر پیام‌های معمولی و فایل‌ها"""
430
- # اگر پیام دستور باشد، نادیده بگیر
431
- if event.message.text and event.message.text.startswith('/'):
432
- return
433
-
434
- user_id = event.sender_id
435
-
436
- # اگر پیام حاوی فایل باشد
437
- if event.file:
438
- await self._handle_file(event, user_id)
439
- else:
440
- # پاسخ به پیام متنی
441
- await event.reply("📝 **لطفا یک فایل ارسال کنید.**\nبرای راهنمایی /help را ارسال کنید.")
442
-
443
- async def _handle_file(self, event, user_id):
444
- """مدیریت دریافت و ذخیره فایل"""
445
- try:
446
- # اطلاعات فایل
447
- file_name = event.file.name or f"file_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
448
- file_size = event.file.size
449
-
450
- # بررسی حجم فایل (حداکثر 2GB)
451
- max_size = 2 * 1024 * 1024 * 1024 # 2GB
452
- if file_size > max_size:
453
- await event.reply(f"❌ **حجم فایل بسیار بزرگ است!**\nحداکثر حجم مجاز: 2GB\nحجم فایل شما: {file_size / 1024 / 1024 / 1024:.2f} GB")
454
- return
455
-
456
- # ایجاد کد یکتا برای فایل
457
- timestamp = int(datetime.now().timestamp())
458
- file_code = f"{user_id}_{timestamp}"
459
- save_path = BASE_DIR / file_code
460
-
461
- # اطلاع‌رسانی شروع دریافت
462
- status_msg = await event.reply(
463
- f"📥 **دریافت فایل...**\n\n"
464
- f"📄 نام: `{file_name}`\n"
465
- f"📏 حجم: {file_size / 1024 / 1024:.2f} MB\n"
466
- f"⏳ لطفا صبر کنید..."
467
- )
468
-
469
- # دانلود فایل
470
- download_path = await event.download_media(file=str(save_path))
471
-
472
- # ذخیره اطلاعات فایل
473
- file_info = {
474
- "name": file_name,
475
- "code": file_code,
476
- "path": str(download_path),
477
- "size_bytes": file_size,
478
- "size_mb": file_size / 1024 / 1024,
479
- "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
480
- "message_id": event.id
481
- }
482
-
483
- # ایجاد یا به‌روزرسانی داده کاربر
484
- if user_id not in user_data:
485
- user_data[user_id] = {"files": []}
486
-
487
- user_data[user_id]["files"].append(file_info)
488
-
489
- # فقط آخرین 20 فایل را نگه دار
490
- if len(user_data[user_id]["files"]) > 20:
491
- # حذف قدیمی‌ترین فایل
492
- old_file = user_data[user_id]["files"].pop(0)
493
- old_path = BASE_DIR / old_file["code"]
494
- if old_path.exists():
495
- old_path.unlink()
496
- logger.info(f"فایل قدیمی پاک شد: {old_file['code']}")
497
-
498
- # پاسخ موفقیت‌آمیز
499
- success_msg = (
500
- f"✅ **فایل با موفقیت دریافت شد!**\n\n"
501
- f"📄 نام فایل: `{file_name}`\n"
502
- f"📏 حجم فایل: {file_size / 1024 / 1024:.2f} MB\n"
503
- f"🔢 کد فایل: `{file_code}`\n\n"
504
- f"📥 **برای دانلود:**\n"
505
- f"```\n"
506
- f"/download {file_code}\n"
507
- f"```\n\n"
508
- f"📋 **تعداد فایل‌های شما:** {len(user_data[user_id]['files'])}"
509
- )
510
-
511
- await status_msg.edit(success_msg)
512
-
513
- logger.info(f"فایل دریافت شد: {file_name} ({file_code}) توسط کاربر {user_id}")
514
-
515
- except Exception as e:
516
- logger.error(f"خطا در دریافت فایل: {e}", exc_info=True)
517
- try:
518
- await event.reply(f"❌ **خطا در دریافت فایل:**\n{str(e)}")
519
- except:
520
- pass
521
-
522
- async def main():
523
- """تابع اصلی اجرای ربات"""
524
- # بررسی تنظیمات
525
- if not check_config():
526
- logger.error("❌ لطفاً تنظیمات را در Secrets تنظیم کنید.")
527
- return
528
-
529
- # ایجاد مسیر ذخیره‌سازی
530
- BASE_DIR.mkdir(parents=True, exist_ok=True)
531
-
532
- # نمایش اطلاعات راه‌اندازی
533
- logger.info("=" * 60)
534
- logger.info("🤖 ربات آپلود فایل تلگرام با Cloudflare Proxy")
535
- logger.info(f"📁 مسیر ذخیره‌سازی: {BASE_DIR}")
536
- logger.info(f"🌐 Cloudflare Proxy: {'فعال ✅' if CF_PROXY_ENABLED else 'غیرفعال ❌'}")
537
-
538
- if CF_PROXY_ENABLED and CF_WORKER_URL:
539
- logger.info(f" 🔗 Worker URL: {CF_WORKER_URL}")
540
-
541
- # محاسبه فضای موجود
542
- try:
543
- statvfs = os.statvfs('/tmp')
544
- free_gb = (statvfs.f_bavail * statvfs.f_frsize) / 1024 / 1024 / 1024
545
- total_gb = (statvfs.f_blocks * statvfs.f_frsize) / 1024 / 1024 / 1024
546
- logger.info(f"💾 فضای /tmp: {free_gb:.2f} GB آزاد از {total_gb:.2f} GB")
547
- except:
548
- logger.info("💾 فضای /tmp: نامشخص")
549
-
550
- logger.info("=" * 60)
551
-
552
- # ایجاد و اجرای ربات
553
- bot = TelegramFileBot()
554
-
555
- try:
556
- await bot.start()
557
- except KeyboardInterrupt:
558
- logger.info("🛑 ربات متوقف شد.")
559
- except Exception as e:
560
- logger.error(f"❌ خطای غیرمنتظره: {str(e)}", exc_info=True)
561
-
562
- # راه‌حل‌های پیشنهادی
563
- logger.info("💡 **راه‌حل‌های پیشنهادی:**")
564
- logger.info("1. مطمئن شوید Cloudflare Worker درست تنظیم شده است")
565
- logger.info("2. Worker باید به api.telegram.org پروکسی کند")
566
- logger.info("3. می‌توانید از پروکسی SOCKS5 جایگزین استفاده کنید")
567
- logger.info("4. API credentials را دوباره بررسی کنید")
568
-
569
- # اجرای ربات
570
- if __name__ == "__main__":
571
- asyncio.run(main())
 
1
+ from fastapi import FastAPI, Request
2
  import os
 
 
3
  from datetime import datetime
 
 
 
 
 
 
4
 
5
+ app = FastAPI()
 
6
 
7
+ SAVE_DIR = "files"
8
+ os.makedirs(SAVE_DIR, exist_ok=True)
 
 
 
 
9
 
 
 
 
 
10
 
11
+ @app.get("/")
12
+ def home():
13
+ return {"status": "running"}
14
 
 
 
 
15
 
16
+ @app.post("/upload")
17
+ async def upload(request: Request):
18
+ data = await request.body()
19
 
20
+ filename = request.headers.get("X-Filename")
21
+ if not filename:
22
+ filename = f"file_{datetime.now().timestamp()}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
+ path = os.path.join(SAVE_DIR, filename)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
+ with open(path, "wb") as f:
27
+ f.write(data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
+ return {"saved_as": filename}