Fred808 commited on
Commit
01870a8
·
verified ·
1 Parent(s): c603bd2

Update api_server.py

Browse files
Files changed (1) hide show
  1. api_server.py +192 -180
api_server.py CHANGED
@@ -1,181 +1,193 @@
1
- from fastapi import FastAPI, HTTPException, BackgroundTasks
2
- from fastapi.responses import JSONResponse
3
- import asyncio
4
- import os
5
- from typing import Optional, Dict, Any
6
- from pydantic import BaseModel
7
- import download_channel # Import the existing downloader
8
-
9
- app = FastAPI(title="Telegram Channel Downloader API")
10
-
11
- # Track active downloads and their status
12
- active_downloads: Dict[str, Dict[str, Any]] = {}
13
-
14
- class DownloadRequest(BaseModel):
15
- channel: Optional[str] = None # If None, uses default from download_channel.py
16
- message_limit: Optional[int] = None
17
-
18
- class DownloadStatus(BaseModel):
19
- channel: str
20
- status: str # "running", "completed", "failed"
21
- message_count: int = 0
22
- downloaded: int = 0
23
- skipped: int = 0
24
- not_rar: int = 0
25
- error: Optional[str] = None
26
-
27
- async def run_download(channel: Optional[str], message_limit: Optional[int], task_id: str):
28
- """Background task to run the download"""
29
- try:
30
- # Override channel and message limit if provided
31
- if channel:
32
- download_channel.CHANNEL = channel
33
- if message_limit is not None:
34
- download_channel.MESSAGE_LIMIT = message_limit
35
-
36
- # Create a status tracker
37
- status = {
38
- "channel": download_channel.CHANNEL,
39
- "status": "running",
40
- "message_count": 0,
41
- "downloaded": 0,
42
- "skipped": 0,
43
- "not_rar": 0,
44
- "error": None
45
- }
46
- active_downloads[task_id] = status
47
-
48
- # Patch the download function to update our status
49
- original_download = download_channel.download_channel
50
-
51
- async def wrapped_download():
52
- nonlocal status
53
- try:
54
- # Initialize client and get entity
55
- client = download_channel.TelegramClient(
56
- download_channel.SESSION_FILE,
57
- download_channel.API_ID,
58
- download_channel.API_HASH
59
- )
60
-
61
- async with client:
62
- try:
63
- entity = await client.get_entity(download_channel.CHANNEL)
64
- except Exception as e:
65
- status["error"] = f"Failed to resolve channel: {str(e)}"
66
- status["status"] = "failed"
67
- return 1
68
-
69
- try:
70
- async for message in client.iter_messages(entity, limit=download_channel.MESSAGE_LIMIT or None):
71
- status["message_count"] += 1
72
-
73
- if not message.media:
74
- continue
75
-
76
- # Check if it's a RAR file
77
- is_rar = False
78
- if message.file:
79
- filename = getattr(message.file, 'name', '') or ''
80
- if filename:
81
- is_rar = filename.lower().endswith('.rar')
82
- else:
83
- mime_type = getattr(message.file, 'mime_type', '') or ''
84
- is_rar = 'rar' in mime_type.lower() if mime_type else False
85
-
86
- if not is_rar:
87
- status["not_rar"] += 1
88
- continue
89
-
90
- # Use the same filename logic
91
- if filename:
92
- suggested = f"{message.id}_{filename}"
93
- else:
94
- suggested = f"{message.id}.rar"
95
-
96
- out_path = os.path.join(download_channel.OUTPUT_DIR, suggested)
97
-
98
- if os.path.exists(out_path):
99
- status["skipped"] += 1
100
- continue
101
-
102
- try:
103
- await client.download_media(message, file=out_path)
104
- status["downloaded"] += 1
105
-
106
- # Upload to HF if token available
107
- if download_channel.HF_TOKEN:
108
- path_in_repo = f"files/{os.path.basename(out_path)}"
109
- ok = download_channel.upload_file_to_hf(
110
- out_path, path_in_repo, download_channel.HF_TOKEN
111
- )
112
- if not ok:
113
- print(f"Warning: failed to upload {out_path}")
114
-
115
- await asyncio.sleep(0.2) # Be polite
116
-
117
- except download_channel.errors.FloodWaitError as fw:
118
- wait = int(fw.seconds) if fw.seconds else 60
119
- print(f"Hit FloodWait: sleeping {wait}s")
120
- await asyncio.sleep(wait + 1)
121
- except Exception as e:
122
- print(f"Error downloading {message.id}: {e}")
123
-
124
- except Exception as e:
125
- status["error"] = str(e)
126
- status["status"] = "failed"
127
- return 1
128
-
129
- status["status"] = "completed"
130
- return 0
131
-
132
- except Exception as e:
133
- status["error"] = str(e)
134
- status["status"] = "failed"
135
- return 1
136
-
137
- await wrapped_download()
138
-
139
- except Exception as e:
140
- active_downloads[task_id] = {
141
- "channel": download_channel.CHANNEL,
142
- "status": "failed",
143
- "message_count": 0,
144
- "downloaded": 0,
145
- "skipped": 0,
146
- "not_rar": 0,
147
- "error": str(e)
148
- }
149
-
150
- @app.post("/download", response_model=Dict[str, str])
151
- async def start_download(request: DownloadRequest, background_tasks: BackgroundTasks):
152
- """Start a new download task"""
153
- task_id = f"download_{len(active_downloads) + 1}"
154
-
155
- # Schedule the download
156
- background_tasks.add_task(
157
- run_download,
158
- channel=request.channel,
159
- message_limit=request.message_limit,
160
- task_id=task_id
161
- )
162
-
163
- return {"task_id": task_id}
164
-
165
- @app.get("/status/{task_id}", response_model=DownloadStatus)
166
- async def get_status(task_id: str):
167
- """Get the status of a download task"""
168
- if task_id not in active_downloads:
169
- raise HTTPException(status_code=404, detail="Task not found")
170
- return active_downloads[task_id]
171
-
172
- @app.get("/active", response_model=Dict[str, DownloadStatus])
173
- async def list_active():
174
- """List all active or completed downloads"""
175
- return active_downloads
176
-
177
- if __name__ == "__main__":
178
- import uvicorn
179
- # Note: When running directly, this runs on 8000
180
- # For production, use: uvicorn api_server:app --host 0.0.0.0 --port 8000
 
 
 
 
 
 
 
 
 
 
 
 
181
  uvicorn.run(app, host="127.0.0.1", port=8000)
 
1
+ from fastapi import FastAPI, HTTPException, BackgroundTasks
2
+ from fastapi.responses import JSONResponse
3
+ import asyncio
4
+ import os
5
+ from typing import Optional, Dict, Any
6
+ from pydantic import BaseModel
7
+ import download_channel # Import the existing downloader
8
+
9
+ app = FastAPI(title="Telegram Channel Downloader API")
10
+
11
+ # Track active downloads and their status
12
+ active_downloads: Dict[str, Dict[str, Any]] = {}
13
+
14
+ @app.on_event("startup")
15
+ async def start_initial_download():
16
+ """Start the download process automatically when the server starts"""
17
+ task_id = "initial_download"
18
+ # Start the download process with default settings
19
+ asyncio.create_task(run_download(
20
+ channel=None, # Use default from download_channel.py
21
+ message_limit=None, # Use default
22
+ task_id=task_id
23
+ ))
24
+ print(f"Started initial download task with ID: {task_id}")
25
+
26
+ class DownloadRequest(BaseModel):
27
+ channel: Optional[str] = None # If None, uses default from download_channel.py
28
+ message_limit: Optional[int] = None
29
+
30
+ class DownloadStatus(BaseModel):
31
+ channel: str
32
+ status: str # "running", "completed", "failed"
33
+ message_count: int = 0
34
+ downloaded: int = 0
35
+ skipped: int = 0
36
+ not_rar: int = 0
37
+ error: Optional[str] = None
38
+
39
+ async def run_download(channel: Optional[str], message_limit: Optional[int], task_id: str):
40
+ """Background task to run the download"""
41
+ try:
42
+ # Override channel and message limit if provided
43
+ if channel:
44
+ download_channel.CHANNEL = channel
45
+ if message_limit is not None:
46
+ download_channel.MESSAGE_LIMIT = message_limit
47
+
48
+ # Create a status tracker
49
+ status = {
50
+ "channel": download_channel.CHANNEL,
51
+ "status": "running",
52
+ "message_count": 0,
53
+ "downloaded": 0,
54
+ "skipped": 0,
55
+ "not_rar": 0,
56
+ "error": None
57
+ }
58
+ active_downloads[task_id] = status
59
+
60
+ # Patch the download function to update our status
61
+ original_download = download_channel.download_channel
62
+
63
+ async def wrapped_download():
64
+ nonlocal status
65
+ try:
66
+ # Initialize client and get entity
67
+ client = download_channel.TelegramClient(
68
+ download_channel.SESSION_FILE,
69
+ download_channel.API_ID,
70
+ download_channel.API_HASH
71
+ )
72
+
73
+ async with client:
74
+ try:
75
+ entity = await client.get_entity(download_channel.CHANNEL)
76
+ except Exception as e:
77
+ status["error"] = f"Failed to resolve channel: {str(e)}"
78
+ status["status"] = "failed"
79
+ return 1
80
+
81
+ try:
82
+ async for message in client.iter_messages(entity, limit=download_channel.MESSAGE_LIMIT or None):
83
+ status["message_count"] += 1
84
+
85
+ if not message.media:
86
+ continue
87
+
88
+ # Check if it's a RAR file
89
+ is_rar = False
90
+ if message.file:
91
+ filename = getattr(message.file, 'name', '') or ''
92
+ if filename:
93
+ is_rar = filename.lower().endswith('.rar')
94
+ else:
95
+ mime_type = getattr(message.file, 'mime_type', '') or ''
96
+ is_rar = 'rar' in mime_type.lower() if mime_type else False
97
+
98
+ if not is_rar:
99
+ status["not_rar"] += 1
100
+ continue
101
+
102
+ # Use the same filename logic
103
+ if filename:
104
+ suggested = f"{message.id}_{filename}"
105
+ else:
106
+ suggested = f"{message.id}.rar"
107
+
108
+ out_path = os.path.join(download_channel.OUTPUT_DIR, suggested)
109
+
110
+ if os.path.exists(out_path):
111
+ status["skipped"] += 1
112
+ continue
113
+
114
+ try:
115
+ await client.download_media(message, file=out_path)
116
+ status["downloaded"] += 1
117
+
118
+ # Upload to HF if token available
119
+ if download_channel.HF_TOKEN:
120
+ path_in_repo = f"files/{os.path.basename(out_path)}"
121
+ ok = download_channel.upload_file_to_hf(
122
+ out_path, path_in_repo, download_channel.HF_TOKEN
123
+ )
124
+ if not ok:
125
+ print(f"Warning: failed to upload {out_path}")
126
+
127
+ await asyncio.sleep(0.2) # Be polite
128
+
129
+ except download_channel.errors.FloodWaitError as fw:
130
+ wait = int(fw.seconds) if fw.seconds else 60
131
+ print(f"Hit FloodWait: sleeping {wait}s")
132
+ await asyncio.sleep(wait + 1)
133
+ except Exception as e:
134
+ print(f"Error downloading {message.id}: {e}")
135
+
136
+ except Exception as e:
137
+ status["error"] = str(e)
138
+ status["status"] = "failed"
139
+ return 1
140
+
141
+ status["status"] = "completed"
142
+ return 0
143
+
144
+ except Exception as e:
145
+ status["error"] = str(e)
146
+ status["status"] = "failed"
147
+ return 1
148
+
149
+ await wrapped_download()
150
+
151
+ except Exception as e:
152
+ active_downloads[task_id] = {
153
+ "channel": download_channel.CHANNEL,
154
+ "status": "failed",
155
+ "message_count": 0,
156
+ "downloaded": 0,
157
+ "skipped": 0,
158
+ "not_rar": 0,
159
+ "error": str(e)
160
+ }
161
+
162
+ @app.post("/download", response_model=Dict[str, str])
163
+ async def start_download(request: DownloadRequest, background_tasks: BackgroundTasks):
164
+ """Start a new download task"""
165
+ task_id = f"download_{len(active_downloads) + 1}"
166
+
167
+ # Schedule the download
168
+ background_tasks.add_task(
169
+ run_download,
170
+ channel=request.channel,
171
+ message_limit=request.message_limit,
172
+ task_id=task_id
173
+ )
174
+
175
+ return {"task_id": task_id}
176
+
177
+ @app.get("/status/{task_id}", response_model=DownloadStatus)
178
+ async def get_status(task_id: str):
179
+ """Get the status of a download task"""
180
+ if task_id not in active_downloads:
181
+ raise HTTPException(status_code=404, detail="Task not found")
182
+ return active_downloads[task_id]
183
+
184
+ @app.get("/active", response_model=Dict[str, DownloadStatus])
185
+ async def list_active():
186
+ """List all active or completed downloads"""
187
+ return active_downloads
188
+
189
+ if __name__ == "__main__":
190
+ import uvicorn
191
+ # Note: When running directly, this runs on 8000
192
+ # For production, use: uvicorn api_server:app --host 0.0.0.0 --port 8000
193
  uvicorn.run(app, host="127.0.0.1", port=8000)