warp-beacon 2.6.4__tar.gz → 2.6.6__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.
Files changed (62) hide show
  1. {warp_beacon-2.6.4/warp_beacon.egg-info → warp_beacon-2.6.6}/PKG-INFO +4 -1
  2. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/pyproject.toml +4 -1
  3. warp_beacon-2.6.6/warp_beacon/__version__.py +2 -0
  4. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/scheduler/scheduler.py +14 -7
  5. warp_beacon-2.6.6/warp_beacon/scraper/instagram/captcha.py +155 -0
  6. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/scraper/instagram/instagram.py +2 -2
  7. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/scraper/youtube/abstract.py +14 -2
  8. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/scraper/youtube/music.py +5 -13
  9. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/scraper/youtube/shorts.py +5 -9
  10. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/scraper/youtube/youtube.py +5 -15
  11. {warp_beacon-2.6.4 → warp_beacon-2.6.6/warp_beacon.egg-info}/PKG-INFO +4 -1
  12. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon.egg-info/SOURCES.txt +1 -0
  13. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon.egg-info/requires.txt +3 -0
  14. warp_beacon-2.6.4/warp_beacon/__version__.py +0 -2
  15. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/LICENSE +0 -0
  16. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/MANIFEST.in +0 -0
  17. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/README.md +0 -0
  18. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/assets/placeholder.gif +0 -0
  19. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/etc/.gitignore +0 -0
  20. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/etc/accounts.json +0 -0
  21. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/etc/proxies.json +0 -0
  22. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/etc/warp_beacon.conf +0 -0
  23. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/etc/warp_beacon.service +0 -0
  24. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/setup.cfg +0 -0
  25. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/setup.py +0 -0
  26. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/__init__.py +0 -0
  27. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/compress/__init__.py +0 -0
  28. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/compress/video.py +0 -0
  29. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/jobs/__init__.py +0 -0
  30. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/jobs/abstract.py +0 -0
  31. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/jobs/download_job.py +0 -0
  32. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/jobs/types.py +0 -0
  33. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/jobs/upload_job.py +0 -0
  34. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/mediainfo/__init__.py +0 -0
  35. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/mediainfo/abstract.py +0 -0
  36. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/mediainfo/audio.py +0 -0
  37. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/mediainfo/silencer.py +0 -0
  38. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/mediainfo/video.py +0 -0
  39. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/scheduler/__init__.py +0 -0
  40. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/scheduler/instagram_human.py +0 -0
  41. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/scraper/__init__.py +0 -0
  42. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/scraper/abstract.py +0 -0
  43. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/scraper/account_selector.py +0 -0
  44. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/scraper/exceptions.py +0 -0
  45. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/scraper/fail_handler.py +0 -0
  46. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/scraper/instagram/__init__.py +0 -0
  47. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/scraper/link_resolver.py +0 -0
  48. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/scraper/youtube/__init__.py +0 -0
  49. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/storage/__init__.py +0 -0
  50. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/storage/mongo.py +0 -0
  51. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/telegram/__init__.py +0 -0
  52. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/telegram/bot.py +0 -0
  53. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/telegram/caption_shortener.py +0 -0
  54. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/telegram/handlers.py +0 -0
  55. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/telegram/placeholder_message.py +0 -0
  56. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/telegram/utils.py +0 -0
  57. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/uploader/__init__.py +0 -0
  58. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/warp_beacon.py +0 -0
  59. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon/yt_auth.py +0 -0
  60. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon.egg-info/dependency_links.txt +0 -0
  61. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon.egg-info/entry_points.txt +0 -0
  62. {warp_beacon-2.6.4 → warp_beacon-2.6.6}/warp_beacon.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: warp_beacon
3
- Version: 2.6.4
3
+ Version: 2.6.6
4
4
  Summary: Telegram bot for expanding external media links
5
5
  Home-page: https://github.com/sb0y/warp_beacon
6
6
  Author: Andrey Bagrintsev
@@ -238,6 +238,9 @@ Requires-Dist: pymongo
238
238
  Requires-Dist: instagrapi==2.0.0
239
239
  Requires-Dist: bs4
240
240
  Requires-Dist: yt_dlp
241
+ Requires-Dist: pydub
242
+ Requires-Dist: SpeechRecognition
243
+ Requires-Dist: playwright
241
244
  Dynamic: author
242
245
  Dynamic: home-page
243
246
  Dynamic: license-file
@@ -26,7 +26,10 @@ dependencies = [
26
26
  "pymongo",
27
27
  "instagrapi == 2.0.0",
28
28
  "bs4",
29
- "yt_dlp"
29
+ "yt_dlp",
30
+ "pydub",
31
+ "SpeechRecognition",
32
+ "playwright"
30
33
  ]
31
34
  keywords = ["bot", "telegram", "instagram", "export"]
32
35
  classifiers = [
@@ -0,0 +1,2 @@
1
+ __version__ = "2.6.6"
2
+
@@ -22,6 +22,7 @@ class IGScheduler(object):
22
22
  def __init__(self, downloader: warp_beacon.scraper.AsyncDownloader) -> None:
23
23
  self.downloader = downloader
24
24
  self.event = threading.Event()
25
+ self.handle_time_planning()
25
26
 
26
27
  def __del__(self) -> None:
27
28
  self.stop()
@@ -63,8 +64,8 @@ class IGScheduler(object):
63
64
  if os.path.exists(self.state_file):
64
65
  with open(self.state_file, 'r', encoding="utf-8") as f:
65
66
  self.state = json.loads(f.read())
66
- if "remaining" in self.state:
67
- logging.info("Next scheduler activity in '%d' seconds", int(self.state["remaining"]))
67
+ self.handle_time_planning()
68
+ logging.info("Next scheduler activity in '%d' seconds", int(self.state["remaining"]))
68
69
  self.load_yt_sessions()
69
70
  except Exception as e:
70
71
  logging.error("Failed to load Scheduler state!")
@@ -124,6 +125,10 @@ class IGScheduler(object):
124
125
  def yt_nearest_expire(self) -> int:
125
126
  return int(min(self.state["yt_sess_exp"], key=lambda x: x.get("expires", 0)).get("expires", 0))
126
127
 
128
+ def handle_time_planning(self) -> None:
129
+ if int(self.state.get("remaining", 0)) <= 0:
130
+ self.state["remaining"] = randrange(9292, 26200)
131
+
127
132
  def do_work(self) -> None:
128
133
  logging.info("Scheduler thread started ...")
129
134
  self.load_state()
@@ -135,22 +140,24 @@ class IGScheduler(object):
135
140
  #max_val = max(yt_expires, ig_sched)
136
141
  now = datetime.datetime.now()
137
142
  if 3 <= now.hour < 7 and min_val != yt_expires:
138
- logging.info("Scheduler is paused due to night hours (4:00 - 7:00)")
139
- self.state["remaining"] = 10800
143
+ logging.info("Scheduler is paused due to night hours (3:00 - 7:00)")
144
+ self.state["remaining"] = 14400
140
145
  self.save_state()
141
146
 
142
147
  if ig_sched <= 0:
143
- self.state["remaining"] = randrange(9292, 26200)
148
+ self.handle_time_planning()
144
149
 
145
150
  start_time = time.time()
146
- logging.info("Next scheduler activity in '%s' seconds", min_val)
151
+ logging.info("Next scheduler activity in '%s' seconds", int(min_val))
147
152
  logging.info("IG timeout '%d' secs", int(self.state["remaining"]))
148
153
  self.event.wait(timeout=min_val)
154
+ self.event.clear()
149
155
  elapsed = time.time() - start_time
150
156
  self.state["remaining"] -= elapsed
151
157
 
152
158
  if self.running:
153
- self.validate_ig_session()
159
+ if self.state["remaining"] <= 0:
160
+ self.validate_ig_session()
154
161
  if yt_expires <= time.time() + 60:
155
162
  self.validate_yt_session()
156
163
  self.save_state()
@@ -0,0 +1,155 @@
1
+ import os
2
+ import time
3
+ import random
4
+ import logging
5
+ import asyncio
6
+ from types import CoroutineType
7
+ from typing import Any
8
+ from urllib.parse import urlparse
9
+ import requests
10
+ from warp_beacon.scraper.instagram.instagram import InstagramScraper
11
+ from pydub import AudioSegment
12
+ import speech_recognition as sr
13
+ from playwright.async_api import async_playwright, Page
14
+
15
+ class CaptchaSolver(object):
16
+ TIMEOUT_STANDARD = 7
17
+ TIMEOUT_SHORT = 1
18
+ TIMEOUT_DETECTION = 0.05
19
+ TEMP_DIR = "/tmp"
20
+
21
+ scraper = None
22
+ proxy_config = None
23
+
24
+ def __init__(self, scraper: InstagramScraper) -> None:
25
+ self.scraper = scraper
26
+ if self.scraper.proxy:
27
+ dsn = self.scraper.proxy.get("dsn", "")
28
+ self.proxy_config = self.parse_proxy_from_dsn(dsn)
29
+
30
+ def parse_proxy_from_dsn(self, dsn: str) -> dict:
31
+ parsed = urlparse(dsn)
32
+
33
+ proxy_config = {
34
+ "server": f"{parsed.scheme}://{parsed.hostname}:{parsed.port}",
35
+ }
36
+
37
+ if parsed.username and parsed.password:
38
+ proxy_config["username"] = parsed.username
39
+ proxy_config["password"] = parsed.password
40
+
41
+ return proxy_config
42
+
43
+ async def _patch_page(self, page: CoroutineType[Any, Any, Page]):
44
+ await page.add_init_script("""() => {
45
+ Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
46
+ window.chrome = { runtime: {} };
47
+ Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
48
+ Object.defineProperty(navigator, 'plugins', { get: () => [1,2,3,4,5] });
49
+ }""")
50
+
51
+ async def solve_audio_captcha(self, page: CoroutineType[Any, Any, Page]) -> None:
52
+ logging.info("Processing audio captcha ..")
53
+ mp3_path = os.path.join(self.TEMP_DIR, f"{random.randrange(1,1000)}.mp3")
54
+ wav_path = os.path.join(self.TEMP_DIR, f"{random.randrange(1,1000)}.wav")
55
+ try:
56
+ await page.click('button[aria-label=\"Get an audio challenge\"]')
57
+ time.sleep(0.3)
58
+ await page.wait_for_selector('audio', timeout=10000)
59
+
60
+ audio_src = await page.get_attribute('audio > source', 'src')
61
+ audio_content = requests.get(audio_src, timeout=60).content
62
+ with open(mp3_path, 'wb') as f:
63
+ f.write(audio_content)
64
+
65
+ if not os.path.exists(mp3_path):
66
+ logging.error("MP3 file not downloaded!")
67
+ return
68
+
69
+ sound = AudioSegment.from_mp3(mp3_path)
70
+ sound.export(wav_path, format='wav')
71
+
72
+ recognizer = sr.Recognizer()
73
+ with sr.AudioFile(wav_path) as source:
74
+ audio = recognizer.record(source)
75
+
76
+ try:
77
+ text = recognizer.recognize_google(audio)
78
+ logging.info("Detected text '%s'", text)
79
+ except sr.UnknownValueError:
80
+ logging.error("Failed to detect text!")
81
+ text = ''
82
+
83
+ if text:
84
+ await page.fill('input[type=\"text\"]', text)
85
+ await page.press('input[type=\"text\"]', 'Enter')
86
+ logging.info("Audio captcha solved!")
87
+ except Exception as e:
88
+ logging.error("Exception in captcha audio solve!")
89
+ logging.exception(e)
90
+ finally:
91
+ if os.path.exists(mp3_path):
92
+ os.unlink(mp3_path)
93
+ if os.path.exists(wav_path):
94
+ os.unlink(wav_path)
95
+
96
+ async def solve_challenge(self, challenge_url: str) -> None:
97
+ async with async_playwright() as p:
98
+ browser = None
99
+ try:
100
+ browser = await p.chromium.launch(
101
+ headless=True,
102
+ args=[
103
+ "--no-sandbox",
104
+ "--disable-blink-features=AutomationControlled",
105
+ "--disable-infobars",
106
+ "--disable-dev-shm-usage"
107
+ ],
108
+ proxy=self.proxy_config
109
+ )
110
+ context = await browser.new_context(
111
+ viewport={"width": 1280, "height": 800},
112
+ 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",
113
+ java_script_enabled=True,
114
+ locale="en-US"
115
+ )
116
+
117
+ page = await context.new_page()
118
+ await self._patch_page(page)
119
+
120
+ await page.goto(challenge_url)
121
+
122
+ # finding iframe with captcha
123
+ frame_element = await page.wait_for_selector('iframe[src*=\"recaptcha\"]')
124
+ frame = await frame_element.content_frame()
125
+ time.sleep(0.1)
126
+
127
+ # checkbox click
128
+ checkbox = await frame.wait_for_selector('#recaptcha-anchor', timeout=10000)
129
+ await checkbox.click()
130
+
131
+ # waiting for frame with task
132
+ await asyncio.sleep(3)
133
+
134
+ # checking if iframe with task exists
135
+ frames = page.frames
136
+ challenge_frame = None
137
+ for f in frames:
138
+ if '/recaptcha/' in f.url and 'bframe' in f.url:
139
+ challenge_frame = f
140
+ break
141
+
142
+ if not challenge_frame:
143
+ logging.info("Captcha solved!")
144
+ else:
145
+ await self.solve_audio_captcha(challenge_frame)
146
+ except Exception as e:
147
+ logging.error("Exception in solver!")
148
+ logging.exception(e)
149
+
150
+ if browser:
151
+ await asyncio.sleep(10)
152
+ await browser.close()
153
+
154
+ def run(self, challenge_url: str) -> None:
155
+ asyncio.run(self.solve_challenge(challenge_url))
@@ -5,6 +5,8 @@ import ssl
5
5
  import re
6
6
  from typing import Callable, Optional, Union
7
7
 
8
+ import logging
9
+
8
10
  import random
9
11
  import email
10
12
  import imaplib
@@ -28,8 +30,6 @@ from warp_beacon.jobs.types import JobType
28
30
  from warp_beacon.jobs.download_job import DownloadJob
29
31
  from warp_beacon.telegram.utils import Utils
30
32
 
31
- import logging
32
-
33
33
  INST_SESSION_FILE_TPL = "/var/warp_beacon/inst_session_account_%d.json"
34
34
 
35
35
  class InstagramScraper(ScraperAbstract):
@@ -291,15 +291,27 @@ class YoutubeAbstract(ScraperAbstract):
291
291
 
292
292
  def download(self, job: DownloadJob) -> list:
293
293
  ret = []
294
+ thumbnail = None
294
295
  try:
295
- ret = self.download_hndlr(self._download, job.url, session=True)
296
+ video_id = self.get_video_id(job.url)
297
+ # shorts custom thumb
298
+ ##vinfo = VideoInfo(local_file)
299
+ #thumbnail = self.download_hndlr(self.download_thumbnail, video_id=yt.video_id, crop_center=vinfo.get_demensions())
300
+ if video_id:
301
+ thumbnail = self.download_hndlr(self.download_thumbnail, video_id)
302
+ except Exception as e:
303
+ logging.error("Failed to download thumb!")
304
+ logging.exception(e)
305
+
306
+ try:
307
+ ret = self.download_hndlr(self._download, job.url, session=True, thumbnail=thumbnail)
296
308
  return ret
297
309
  except (Unavailable, TimeOut, KeyError) as e:
298
310
  logging.warning("Download failed, trying to download with yt_dlp")
299
311
  logging.exception(e)
300
312
 
301
313
  try:
302
- ret = self.download_hndlr(self._download_yt_dlp, job.url)
314
+ ret = self.download_hndlr(self._download_yt_dlp, job.url, thumbnail=thumbnail)
303
315
  except NotImplementedError:
304
316
  logging.info("yt_dlp is not supported for this submodule yet")
305
317
  raise Unavailable("Сontent unvailable")
@@ -1,3 +1,6 @@
1
+ import io
2
+ from typing import Optional
3
+
1
4
  import logging
2
5
 
3
6
  import time
@@ -9,22 +12,15 @@ from warp_beacon.jobs.types import JobType
9
12
  from warp_beacon.scraper.youtube.abstract import YoutubeAbstract
10
13
  from warp_beacon.scraper.exceptions import NotFound, FileTooBig, Unavailable
11
14
 
12
-
13
15
  class YoutubeMusicScraper(YoutubeAbstract):
14
16
  YT_MAX_RETRIES_DEFAULT = 3
15
17
  YT_PAUSE_BEFORE_RETRY_DEFAULT = 3
16
18
  YT_TIMEOUT_DEFAULT = 2
17
19
  YT_TIMEOUT_INCREMENT_DEFAULT = 60
18
20
 
19
- def _download(self, url: str, session: bool = True, timeout: int = 0) -> list:
21
+ def _download(self, url: str, session: bool = True, thumbnail: Optional[io.BytesIO] = None, timeout: int = 0) -> list:
20
22
  res = []
21
23
  try:
22
- thumbnail = None
23
- audio_id = self.get_video_id(url)
24
-
25
- if audio_id:
26
- thumbnail = self.download_hndlr(self.download_thumbnail, audio_id)
27
-
28
24
  yt = self.build_yt(url, session=session)
29
25
 
30
26
  stream = yt.streams.get_audio_only()
@@ -79,12 +75,8 @@ class YoutubeMusicScraper(YoutubeAbstract):
79
75
 
80
76
  return yt_dlp.YoutubeDL(ydl_opts)
81
77
 
82
- def _download_yt_dlp(self, url: str, timeout: int = 60) -> list:
78
+ def _download_yt_dlp(self, url: str, timeout: int = 60, thumbnail: Optional[io.BytesIO] = None) -> list:
83
79
  res = []
84
- thumbnail = None
85
- video_id = self.get_video_id(url)
86
- if video_id:
87
- thumbnail = self.download_hndlr(self.download_thumbnail, video_id)
88
80
  with self.build_yt_dlp(timeout) as ydl:
89
81
  info = ydl.extract_info(url, download=True)
90
82
  local_file = ydl.prepare_filename(info)
@@ -1,3 +1,6 @@
1
+ import io
2
+ from typing import Optional
3
+
1
4
  import logging
2
5
 
3
6
  from warp_beacon.jobs.types import JobType
@@ -12,9 +15,8 @@ class YoutubeShortsScraper(YoutubeAbstract):
12
15
  YT_TIMEOUT_DEFAULT = 2
13
16
  YT_TIMEOUT_INCREMENT_DEFAULT = 60
14
17
 
15
- def _download(self, url: str, session: bool = True, timeout: int = 0) -> list:
18
+ def _download(self, url: str, session: bool = True, thumbnail: Optional[io.BytesIO] = None, timeout: int = 0) -> list:
16
19
  res = []
17
- thumbnail = None
18
20
  yt = self.build_yt(url, session=session)
19
21
  stream = yt.streams.get_highest_resolution()
20
22
 
@@ -30,8 +32,6 @@ class YoutubeShortsScraper(YoutubeAbstract):
30
32
  )
31
33
 
32
34
  local_file = self.rename_local_file(local_file)
33
- vinfo = VideoInfo(local_file)
34
- thumbnail = self.download_hndlr(self.download_thumbnail, video_id=yt.video_id, crop_center=vinfo.get_demensions())
35
35
 
36
36
  logging.debug("Temp filename: '%s'", local_file)
37
37
  res.append({
@@ -44,12 +44,8 @@ class YoutubeShortsScraper(YoutubeAbstract):
44
44
 
45
45
  return res
46
46
 
47
- def _download_yt_dlp(self, url: str, timeout: int = 60) -> list:
47
+ def _download_yt_dlp(self, url: str, thumbnail: Optional[io.BytesIO] = None, timeout: int = 60) -> list:
48
48
  res = []
49
- thumbnail = None
50
- video_id = self.get_video_id(url)
51
- if video_id:
52
- thumbnail = self.download_hndlr(self.download_thumbnail, video_id)
53
49
  with self.build_yt_dlp(timeout) as ydl:
54
50
  info = ydl.extract_info(url, download=True)
55
51
  local_file = ydl.prepare_filename(info)
@@ -1,5 +1,7 @@
1
1
  import time
2
2
  import os
3
+ import io
4
+ from typing import Optional
3
5
  import logging
4
6
 
5
7
  import av
@@ -65,14 +67,10 @@ class YoutubeScraper(YoutubeAbstract):
65
67
 
66
68
  return output_path
67
69
 
68
- def _download_pytubefix_max_res(self, url: str, session: bool = True, timeout: int = 60) -> list:
70
+ def _download_pytubefix_max_res(self, url: str, session: bool = True, thumbnail: Optional[io.BytesIO] = None, timeout: int = 60) -> list:
69
71
  res = []
70
72
  local_video_file, local_audio_file = '', ''
71
73
  try:
72
- thumbnail = None
73
- video_id = self.get_video_id(url)
74
- if video_id:
75
- thumbnail = self.download_hndlr(self.download_thumbnail, video_id)
76
74
  yt = self.build_yt(url, session=session)
77
75
 
78
76
  if self.is_live(yt.initial_data):
@@ -125,13 +123,9 @@ class YoutubeScraper(YoutubeAbstract):
125
123
 
126
124
  return res
127
125
 
128
- def _download_pytube_dash(self, url: str, session: bool = True, timeout: int = 60) -> list:
126
+ def _download_pytube_dash(self, url: str, session: bool = True, thumbnail: Optional[io.BytesIO] = None, timeout: int = 60) -> list:
129
127
  res = []
130
128
  try:
131
- thumbnail = None
132
- video_id = self.get_video_id(url)
133
- if video_id:
134
- thumbnail = self.download_hndlr(self.download_thumbnail, video_id)
135
129
  yt = self.build_yt(url, session=session)
136
130
 
137
131
  if self.is_live(yt.initial_data):
@@ -164,12 +158,8 @@ class YoutubeScraper(YoutubeAbstract):
164
158
 
165
159
  return res
166
160
 
167
- def _download_yt_dlp(self, url: str, timeout: int = 60) -> list:
161
+ def _download_yt_dlp(self, url: str, thumbnail: Optional[io.BytesIO] = None, timeout: int = 60) -> list:
168
162
  res = []
169
- thumbnail = None
170
- video_id = self.get_video_id(url)
171
- if video_id:
172
- thumbnail = self.download_hndlr(self.download_thumbnail, video_id)
173
163
  with self.build_yt_dlp(timeout) as ydl:
174
164
  info = ydl.extract_info(url, download=True)
175
165
  local_file = ydl.prepare_filename(info)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: warp_beacon
3
- Version: 2.6.4
3
+ Version: 2.6.6
4
4
  Summary: Telegram bot for expanding external media links
5
5
  Home-page: https://github.com/sb0y/warp_beacon
6
6
  Author: Andrey Bagrintsev
@@ -238,6 +238,9 @@ Requires-Dist: pymongo
238
238
  Requires-Dist: instagrapi==2.0.0
239
239
  Requires-Dist: bs4
240
240
  Requires-Dist: yt_dlp
241
+ Requires-Dist: pydub
242
+ Requires-Dist: SpeechRecognition
243
+ Requires-Dist: playwright
241
244
  Dynamic: author
242
245
  Dynamic: home-page
243
246
  Dynamic: license-file
@@ -42,6 +42,7 @@ warp_beacon/scraper/exceptions.py
42
42
  warp_beacon/scraper/fail_handler.py
43
43
  warp_beacon/scraper/link_resolver.py
44
44
  warp_beacon/scraper/instagram/__init__.py
45
+ warp_beacon/scraper/instagram/captcha.py
45
46
  warp_beacon/scraper/instagram/instagram.py
46
47
  warp_beacon/scraper/youtube/__init__.py
47
48
  warp_beacon/scraper/youtube/abstract.py
@@ -12,3 +12,6 @@ pymongo
12
12
  instagrapi==2.0.0
13
13
  bs4
14
14
  yt_dlp
15
+ pydub
16
+ SpeechRecognition
17
+ playwright
@@ -1,2 +0,0 @@
1
- __version__ = "2.6.4"
2
-
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes