mediadown 3.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: mediadown
3
+ Version: 3.2.0
4
+ Summary: Social Media Downloader untuk YouTube, Facebook, Instagram, dan TikTok
5
+ Author: KEYBOARDWARRIOR
6
+ Requires-Dist: inquirer
7
+ Requires-Dist: yt-dlp
8
+ Requires-Dist: requests
9
+ Requires-Dist: urllib3
10
+ Requires-Dist: beautifulsoup4
11
+ Requires-Dist: rich
12
+ Dynamic: author
13
+ Dynamic: requires-dist
14
+ Dynamic: summary
File without changes
@@ -0,0 +1,295 @@
1
+ import os
2
+ import sys
3
+ import subprocess
4
+ import time
5
+ import shutil
6
+ import re
7
+ import inquirer
8
+ import yt_dlp
9
+ import requests
10
+ import urllib3
11
+ from pathlib import Path
12
+ from bs4 import BeautifulSoup
13
+ from rich.console import Console
14
+ from rich.panel import Panel
15
+ from rich.progress import Progress, TextColumn, BarColumn, DownloadColumn, TransferSpeedColumn, TimeRemainingColumn
16
+
17
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
18
+ console = Console()
19
+ SAVE_DIR = Path("/sdcard/Download")
20
+
21
+ def clear():
22
+ os.system("clear" if os.name != "nt" else "cls")
23
+
24
+ def draw_banner():
25
+ banner = (
26
+ "[italic magenta]╦ ╦╔╦╗ ╔╦╗╦ ╔═╗[/]\n"
27
+ "[magenta]╚╦╝ ║───║║║ ╠═╝[/]\n"
28
+ "[magenta] ╩ ╩ ═╩╝╩═╝╩ [/] [white]Version : 3.2 [/]\n"
29
+ " [bold reverse magenta] KEYBOARDWARRIOR | OFFICIAL [/]\n\n"
30
+ "[bold cyan]Social Media Downloader[/]\n"
31
+ "[dim white]YouTube, Facebook, Instagram, TikTok[/]"
32
+ )
33
+ console.print(Panel(banner, border_style="magenta", expand=False))
34
+
35
+
36
+ def tentukan_lokasi(subfolder):
37
+ print(f"\n[ℹ] Info: Folder default -> {SAVE_DIR / subfolder}")
38
+ user_input = input("➔ Masukkan lokasi penyimpanan (Kosongin = default): ").strip()
39
+ jalur_final = Path(user_input) if user_input else SAVE_DIR / subfolder
40
+ try:
41
+ jalur_final.mkdir(parents=True, exist_ok=True)
42
+ except PermissionError:
43
+ console.print("[bold red]❌ Error:[/bold red] Izin penyimpanan ditolak! Ketik 'termux-setup-storage' dulu.")
44
+ sys.exit(1)
45
+ return jalur_final
46
+
47
+ def ambil_input_url():
48
+ console.print("\n[bold yellow]➔ Tempel/Masukkan URL Target[/bold yellow]")
49
+ console.print("[dim]Pisahkan dengan spasi, koma, atau Enter. Tekan Enter 2x jika sudah selesai:[/dim]")
50
+ baris = []
51
+ while True:
52
+ line = input()
53
+ if not line: break
54
+ baris.append(line)
55
+ teks_mentah = " ".join(baris).replace(",", " ")
56
+ return [u.strip() for u in teks_mentah.split() if u.strip()]
57
+
58
+ def jalankan_unduhan(urls, subfolder, butuh_login=False):
59
+ folder_tujuan = tentukan_lokasi(subfolder)
60
+ for index, url in enumerate(urls, 1):
61
+ clear()
62
+ draw_banner()
63
+ console.print(Panel(f"[bold cyan]Mengunduh ke-{index} dari {len(urls)}[/bold cyan]\n[dim]Target : {url}[/dim]", border_style="cyan"))
64
+ ydl_opts = {
65
+ 'format': 'bestvideo+bestaudio/best',
66
+ 'merge_output_format': 'mp4',
67
+ 'outtmpl': str(folder_tujuan / "%(title)s_%(id)s.%(ext)s"),
68
+ 'quiet': True,
69
+ 'no_warnings': True,
70
+ 'ignoreerrors': True
71
+ }
72
+ if butuh_login:
73
+ ydl_opts['cookiesfrombrowser'] = ('chrome',)
74
+ progress_bar = Progress(
75
+ TextColumn("[bold blue]{task.description}"),
76
+ BarColumn(bar_width=25, complete_style="green"),
77
+ DownloadColumn(),
78
+ TransferSpeedColumn(),
79
+ TimeRemainingColumn(),
80
+ console=console
81
+ )
82
+ with progress_bar:
83
+ task_id = progress_bar.add_task("📥 Downloading ", total=None)
84
+ def progress_hook(d):
85
+ if d['status'] == 'downloading':
86
+ total = d.get('total_bytes') or d.get('total_bytes_estimate') or 0
87
+ progress_bar.update(task_id, total=total, completed=d.get('downloaded_bytes', 0))
88
+ elif d['status'] == 'finished':
89
+ progress_bar.update(task_id, description="[bold green]🔄 Muxing Audio+Video...")
90
+
91
+ ydl_opts['progress_hooks'] = [progress_hook]
92
+ try:
93
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
94
+ ydl.download([url])
95
+ console.print(f"[bold green]✔ Sukses mengunduh antrean ke-{index}![/bold green]\n")
96
+ except Exception as e:
97
+ console.print(f"[bold red]❌ Gagal pada antrean ke-{index}:[/bold red] {e}\n")
98
+ time.sleep(1)
99
+ console.print(f"[bold green]✔ PROSES SELESAI![/bold green] File disimpan di: [underline cyan]{folder_tujuan}[/underline cyan]")
100
+ input("\nTekan Enter untuk kembali ke menu...")
101
+
102
+ def menu_youtube():
103
+ while True:
104
+ clear()
105
+ draw_banner()
106
+ questions = [
107
+ inquirer.List('opsi', message="YouTube", choices=[
108
+ ('1. Download Video', '1'),
109
+ ('2. Download Shorts', '2'),
110
+ ('3. Mass Download Channel/Playlist Video', '3'),
111
+ ('4. Mass Download Channel/Playlist Shorts', '4'),
112
+ ('➔ Kembali ke Menu Utama', 'kembali')
113
+ ], carousel=True)
114
+ ]
115
+ ans = inquirer.prompt(questions)
116
+ if not ans or ans['opsi'] == 'kembali': break
117
+ urls = ambil_input_url()
118
+ if not urls: continue
119
+ mapping = {
120
+ '1': 'YT_Videos',
121
+ '2': 'YT_Shorts',
122
+ '3': 'YT_Bulk_Videos',
123
+ '4': 'YT_Bulk_Shorts'
124
+ }
125
+ jalankan_unduhan(urls, mapping[ans['opsi']])
126
+
127
+ def menu_facebook():
128
+ while True:
129
+ clear()
130
+ draw_banner()
131
+ questions = [
132
+ inquirer.List('opsi', message="Facebook", choices=[
133
+ ('1. Download FB Reels', '1'),
134
+ ('2. Download FB Story (Harus Publik / Login)', '2'),
135
+ ('3. Download FB Video', '3'),
136
+ ('➔ Kembali ke Menu Utama', 'kembali')
137
+ ], carousel=True)
138
+ ]
139
+ ans = inquirer.prompt(questions)
140
+ if not ans or ans['opsi'] == 'kembali': break
141
+ urls = ambil_input_url()
142
+ if not urls: continue
143
+ mapping = {'1': 'FB_Reels', '2': 'FB_Stories', '3': 'FB_Videos'}
144
+ butuh_login = True if ans['opsi'] == '2' else False
145
+ jalankan_unduhan(urls, mapping[ans['opsi']], butuh_login=butuh_login)
146
+
147
+ def menu_instagram():
148
+ while True:
149
+ clear()
150
+ draw_banner()
151
+ questions = [
152
+ inquirer.List('opsi', message="Instagram", choices=[
153
+ ('1. Download IG Reels', '1'),
154
+ ('2. Download IG Story (Memerlukan Akses/Cookies)', '2'),
155
+ ('➔ Kembali ke Menu Utama', 'kembali')
156
+ ], carousel=True)
157
+ ]
158
+ ans = inquirer.prompt(questions)
159
+ if not ans or ans['opsi'] == 'kembali': break
160
+ urls = ambil_input_url()
161
+ if not urls: continue
162
+ mapping = {'1': 'IG_Reels', '2': 'IG_Stories'}
163
+ butuh_login = True if ans['opsi'] == '2' else False
164
+ jalankan_unduhan(urls, mapping[ans['opsi']], butuh_login=butuh_login)
165
+
166
+ def menu_tiktok():
167
+ while True:
168
+ clear()
169
+ draw_banner()
170
+ questions = [
171
+ inquirer.List('opsi', message="TikTok", choices=[
172
+ ('1. Download TikTok Video (No Watermark)', '1'),
173
+ ('➔ Kembali ke Menu Utama', 'kembali')
174
+ ], carousel=True)
175
+ ]
176
+ ans = inquirer.prompt(questions)
177
+ if not ans or ans['opsi'] == 'kembali': break
178
+ urls = ambil_input_url()
179
+ if not urls: continue
180
+ folder_tujuan = tentukan_lokasi('TikTok_Videos')
181
+ session = requests.Session()
182
+ for index, url in enumerate(urls, 1):
183
+ clear()
184
+ draw_banner()
185
+ console.print(Panel(f"[bold cyan]Mengunduh ke-{index} dari {len(urls)}[/bold cyan]\n[dim]Target : {url}[/dim]", border_style="cyan"))
186
+ with console.status("[yellow]⏳ Memproses data dari server ssstik...[/]"):
187
+ try:
188
+ user_ip = "140.213.39.86"
189
+ user_loc = "ID"
190
+ try:
191
+ trace_response = session.get("https://ssstik.io/cdn-cgi/trace", verify=False, timeout=10).text
192
+ for line in trace_response.split("\n"):
193
+ if "=" in line:
194
+ parts = line.split("=", 1)
195
+ if parts[0] == 'ip': user_ip = parts[1]
196
+ if parts[0] == 'loc': user_loc = parts[1]
197
+ except:
198
+ pass
199
+
200
+ html_homepage = session.get("https://ssstik.io/id", verify=False, timeout=10).text
201
+ token_tt = "Y0FLY3E_"
202
+ match = re.search(r'name="tt"\s+value="([^"]+)"', html_homepage)
203
+ if match:
204
+ token_tt = match.group(1)
205
+ else:
206
+ match2 = re.search(r'tt\s*=\s*["\']([^"\']+)["\']', html_homepage)
207
+ if match2:
208
+ token_tt = match2.group(1)
209
+
210
+ dl_headers = {
211
+ "Host": "ssstik.io",
212
+ "Connection": "keep-alive",
213
+ "sec-ch-ua-platform": '"Android"',
214
+ "User-Agent": "Mozilla/5.0 (Linux; Android 14; V2201 Build/UP1A.231005.007)",
215
+ "Accept": "*/*",
216
+ "X-Requested-With": "mark.via.gp",
217
+ "Sec-Fetch-Site": "same-origin",
218
+ "Sec-Fetch-Mode": "cors",
219
+ "Referer": "https://ssstik.io/id",
220
+ "HX-Trigger": "_gcaptcha_pt",
221
+ "HX-Target": "target",
222
+ "HX-Current-URL": "https://ssstik.io/id",
223
+ "HX-Request": "true",
224
+ "Content-Type": "application/x-www-form-urlencoded"
225
+ }
226
+ debug_param = f"ab=1&loc={user_loc}&ip={user_ip}"
227
+ post_data = {
228
+ "id": url,
229
+ "locale": "id",
230
+ "tt": token_tt,
231
+ "debug": debug_param
232
+ }
233
+ response = session.post("https://ssstik.io/abc?url=dl", data=post_data, headers=dl_headers, verify=False, timeout=10).text
234
+ soup = BeautifulSoup(response, "html.parser")
235
+ user_node = soup.select_one('#avatarAndTextUsual h2')
236
+ username = user_node.text.strip() if user_node else "Unknown"
237
+ caption_node = soup.select_one('p.maintext')
238
+ caption = caption_node.text.strip() if caption_node else "-"
239
+ if len(caption) > 150:
240
+ caption = caption[:147] + "..."
241
+ video_node = soup.find("a", class_=lambda x: x and "without_watermark" in x)
242
+ video_url = video_node.get("href") if video_node else None
243
+ if video_url:
244
+ clean_username = re.sub(r'[^\w\-_\.]', '', username)
245
+ filename = f"{clean_username}_{int(time.time())}.mp4"
246
+ filepath = folder_tujuan / filename
247
+ res_file = session.get(video_url, stream=True, verify=False, timeout=30)
248
+ with open(filepath, 'wb') as f:
249
+ for chunk in res_file.iter_content(chunk_size=1024*1024):
250
+ if chunk:
251
+ f.write(chunk)
252
+ hasil = (
253
+ f"[bold green]✔ Berhasil Mengunduh Antrean ke-{index}![/bold green]\n\n"
254
+ f"[white]👤 Creator : @{username}\n"
255
+ f"📝 Caption : {caption}\n"
256
+ f"📂 Simpan : {filepath}[/white]"
257
+ )
258
+ console.print(Panel(hasil, border_style="green"))
259
+ else:
260
+ console.print(f"[bold red]❌ Gagal mendapatkan link video dari target ke-{index}[/bold red]\n")
261
+ except Exception as e:
262
+ console.print(f"[bold red]❌ Gagal pada antrean ke-{index}:[/bold red] {e}\n")
263
+ time.sleep(1.5)
264
+ console.print(f"[bold green]✔ PROSES SELESAI![/bold green] File disimpan di: [underline cyan]{folder_tujuan}[/underline cyan]")
265
+ input("\nTekan Enter untuk kembali ke menu...")
266
+
267
+ def main():
268
+ while True:
269
+ clear()
270
+ draw_banner()
271
+ questions = [
272
+ inquirer.List('platform', message="Pilih Platform Sosial Media", choices=[
273
+ ('YouTube Downloader Menu', 'yt'),
274
+ ('Facebook Downloader Menu', 'fb'),
275
+ ('Instagram Downloader Menu', 'ig'),
276
+ ('TikTok Downloader Menu', 'tt'),
277
+ ('Keluar Aplikasi', 'keluar')
278
+ ], carousel=True)
279
+ ]
280
+ answers = inquirer.prompt(questions)
281
+ if not answers or answers['platform'] == 'keluar':
282
+ console.print("\n[bold yellow]Keluar dari aplikasi[/bold yellow]")
283
+ break
284
+ platform = answers['platform']
285
+ if platform == 'yt': menu_youtube()
286
+ elif platform == 'fb': menu_facebook()
287
+ elif platform == 'ig': menu_instagram()
288
+ elif platform == 'tt': menu_tiktok()
289
+
290
+ if __name__ == "__main__":
291
+ try:
292
+ main()
293
+ except KeyboardInterrupt:
294
+ print("\n\n[!] Program distop paksa (Ctrl+C). Keluar!")
295
+ sys.exit(0)
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: mediadown
3
+ Version: 3.2.0
4
+ Summary: Social Media Downloader untuk YouTube, Facebook, Instagram, dan TikTok
5
+ Author: KEYBOARDWARRIOR
6
+ Requires-Dist: inquirer
7
+ Requires-Dist: yt-dlp
8
+ Requires-Dist: requests
9
+ Requires-Dist: urllib3
10
+ Requires-Dist: beautifulsoup4
11
+ Requires-Dist: rich
12
+ Dynamic: author
13
+ Dynamic: requires-dist
14
+ Dynamic: summary
@@ -0,0 +1,9 @@
1
+ setup.py
2
+ mediadown/__init__.py
3
+ mediadown/cli.py
4
+ mediadown.egg-info/PKG-INFO
5
+ mediadown.egg-info/SOURCES.txt
6
+ mediadown.egg-info/dependency_links.txt
7
+ mediadown.egg-info/entry_points.txt
8
+ mediadown.egg-info/requires.txt
9
+ mediadown.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mediadown = mediadown.cli:main
@@ -0,0 +1,6 @@
1
+ inquirer
2
+ yt-dlp
3
+ requests
4
+ urllib3
5
+ beautifulsoup4
6
+ rich
@@ -0,0 +1 @@
1
+ mediadown
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,22 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name='mediadown',
5
+ version='3.2.0',
6
+ packages=find_packages(),
7
+ install_requires=[
8
+ 'inquirer',
9
+ 'yt-dlp',
10
+ 'requests',
11
+ 'urllib3',
12
+ 'beautifulsoup4',
13
+ 'rich'
14
+ ],
15
+ entry_points={
16
+ 'console_scripts': [
17
+ 'mediadown=mediadown.cli:main',
18
+ ],
19
+ },
20
+ author='KEYBOARDWARRIOR',
21
+ description='Social Media Downloader untuk YouTube, Facebook, Instagram, dan TikTok',
22
+ )