warp-beacon 2.1.1__tar.gz → 2.1.3__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 (52) hide show
  1. {warp_beacon-2.1.1/warp_beacon.egg-info → warp_beacon-2.1.3}/PKG-INFO +1 -1
  2. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/etc/warp_beacon.conf +2 -2
  3. warp_beacon-2.1.3/warp_beacon/__version__.py +2 -0
  4. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/jobs/abstract.py +2 -0
  5. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/scraper/__init__.py +47 -9
  6. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/scraper/account_selector.py +3 -2
  7. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/scraper/exceptions.py +6 -0
  8. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/scraper/instagram/instagram.py +9 -16
  9. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/scraper/youtube/abstract.py +9 -8
  10. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/telegram/bot.py +19 -15
  11. {warp_beacon-2.1.1 → warp_beacon-2.1.3/warp_beacon.egg-info}/PKG-INFO +1 -1
  12. warp_beacon-2.1.1/warp_beacon/__version__.py +0 -2
  13. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/LICENSE +0 -0
  14. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/MANIFEST.in +0 -0
  15. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/README.md +0 -0
  16. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/assets/placeholder.gif +0 -0
  17. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/etc/.gitignore +0 -0
  18. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/etc/accounts.json +0 -0
  19. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/etc/warp_beacon.service +0 -0
  20. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/pyproject.toml +0 -0
  21. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/setup.cfg +0 -0
  22. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/setup.py +0 -0
  23. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/__init__.py +0 -0
  24. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/compress/__init__.py +0 -0
  25. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/compress/video.py +0 -0
  26. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/jobs/__init__.py +0 -0
  27. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/jobs/download_job.py +0 -0
  28. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/jobs/types.py +0 -0
  29. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/jobs/upload_job.py +0 -0
  30. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/mediainfo/__init__.py +0 -0
  31. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/mediainfo/abstract.py +0 -0
  32. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/mediainfo/audio.py +0 -0
  33. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/mediainfo/silencer.py +0 -0
  34. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/mediainfo/video.py +0 -0
  35. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/scraper/abstract.py +0 -0
  36. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/scraper/instagram/__init__.py +0 -0
  37. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/scraper/youtube/__init__.py +0 -0
  38. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/scraper/youtube/music.py +0 -0
  39. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/scraper/youtube/shorts.py +0 -0
  40. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/scraper/youtube/youtube.py +0 -0
  41. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/storage/__init__.py +0 -0
  42. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/telegram/__init__.py +0 -0
  43. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/telegram/handlers.py +0 -0
  44. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/telegram/placeholder_message.py +0 -0
  45. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/telegram/utils.py +0 -0
  46. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/uploader/__init__.py +0 -0
  47. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon/warp_beacon.py +0 -0
  48. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon.egg-info/SOURCES.txt +0 -0
  49. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon.egg-info/dependency_links.txt +0 -0
  50. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon.egg-info/entry_points.txt +0 -0
  51. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon.egg-info/requires.txt +0 -0
  52. {warp_beacon-2.1.1 → warp_beacon-2.1.3}/warp_beacon.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: warp_beacon
3
- Version: 2.1.1
3
+ Version: 2.1.3
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
@@ -1,11 +1,11 @@
1
1
  TG_TOKEN=""
2
2
  TG_BOT_NAME=""
3
- TG_BOT_ADMIN_USERNAME=""
3
+ TG_BOT_ADMINS_USERNAMES=""
4
4
  TG_API_ID=""
5
5
  TG_API_HASH=""
6
6
  TG_BOT_NAME=""
7
- TG_BOT_ADMIN_USERNAME=""
8
7
  IG_MAX_RETRIES=10
8
+ IG_REQUEST_TIMEOUT=30
9
9
  MONGODB_HOST="mongodb"
10
10
  MONGODB_PORT="27017"
11
11
  MONGODB_USER="root"
@@ -0,0 +1,2 @@
1
+ __version__ = "2.1.3"
2
+
@@ -34,6 +34,7 @@ class JobSettings(TypedDict):
34
34
  source_username: str
35
35
  unvailable_error_count: int
36
36
  geoblock_error_count: int
37
+ account_switches: int
37
38
 
38
39
  class AbstractJob(ABC):
39
40
  job_id: uuid.UUID = None
@@ -62,6 +63,7 @@ class AbstractJob(ABC):
62
63
  source_usename: str = ""
63
64
  unvailable_error_count: int = 0
64
65
  geoblock_error_count: int = 0
66
+ account_switches: int = 0
65
67
 
66
68
  def __init__(self, **kwargs: Unpack[JobSettings]) -> None:
67
69
  if kwargs:
@@ -4,7 +4,7 @@ from typing import Optional
4
4
  import multiprocessing
5
5
  from queue import Empty
6
6
 
7
- from warp_beacon.scraper.exceptions import NotFound, UnknownError, TimeOut, Unavailable, FileTooBig, YotubeLiveError, YotubeAgeRestrictedError, IGRateLimitAccured
7
+ from warp_beacon.scraper.exceptions import NotFound, UnknownError, TimeOut, Unavailable, FileTooBig, YotubeLiveError, YotubeAgeRestrictedError, IGRateLimitAccured, CaptchaIssue, AllAccountsFailed
8
8
  from warp_beacon.mediainfo.video import VideoInfo
9
9
  from warp_beacon.mediainfo.audio import AudioInfo
10
10
  from warp_beacon.mediainfo.silencer import Silencer
@@ -65,6 +65,17 @@ class AsyncDownloader(object):
65
65
 
66
66
  return media_info
67
67
 
68
+ def try_next_account(self, job: DownloadJob, report_error: str = None) -> None:
69
+ logging.warning("Switching account!")
70
+ if job.account_switches > self.acc_selector.count_service_accounts(job.job_origin):
71
+ raise AllAccountsFailed()
72
+ if report_error:
73
+ self.acc_selector.bump_acc_fail("rate_limits")
74
+ self.acc_selector.next()
75
+ cur_acc = self.acc_selector.get_current()
76
+ logging.info("Current account: '%s'", str(cur_acc))
77
+ job.account_switches += 1
78
+
68
79
  def do_work(self) -> None:
69
80
  logging.info("download worker started")
70
81
  while self.allow_loop.value == 1:
@@ -108,11 +119,14 @@ class AsyncDownloader(object):
108
119
  job_failed=True,
109
120
  job_failed_msg="Unable to access to media under this URL. Seems like the media is private.")
110
121
  )
122
+ self.send_message_to_admin(
123
+ f"Task {job.job_id} failed. URL: '{job.url}'. Reason: 'NotFound'."
124
+ )
111
125
  break
112
126
  except Unavailable as e:
113
127
  logging.warning("Not found or unavailable error occurred!")
114
128
  logging.exception(e)
115
- if job.unvailable_error_count > self.acc_selector.count_service_accounts(job.job_origin.value):
129
+ if job.unvailable_error_count > self.acc_selector.count_service_accounts(job.job_origin):
116
130
  self.uploader.queue_task(job.to_upload_job(
117
131
  job_failed=True,
118
132
  job_failed_msg="Video is unvailable for all your service accounts.")
@@ -128,7 +142,10 @@ class AsyncDownloader(object):
128
142
  logging.exception(e)
129
143
  self.uploader.queue_task(job.to_upload_job(
130
144
  job_failed=True,
131
- job_failed_msg="Failed to download content. Please check you Internet connection or retry amount bot configuration settings.")
145
+ job_failed_msg="Failed to download content due timeout error. Please check you Internet connection, retry amount or request timeout bot configuration settings.")
146
+ )
147
+ self.send_message_to_admin(
148
+ f"Task {job.job_id} failed. URL: '{job.url}'. Reason: 'TimeOut'."
132
149
  )
133
150
  break
134
151
  except FileTooBig as e:
@@ -138,15 +155,22 @@ class AsyncDownloader(object):
138
155
  job_failed=True,
139
156
  job_failed_msg="Unfortunately this file has exceeded the Telegram limits. A file cannot be larger than 2 gigabytes.")
140
157
  )
158
+ self.send_message_to_admin(
159
+ f"Task {job.job_id} failed. URL: '{job.url}'. Reason: 'FileTooBig'."
160
+ )
141
161
  break
142
162
  except IGRateLimitAccured as e:
143
163
  logging.warning("IG ratelimit accured :(")
144
164
  logging.exception(e)
145
- logging.warning("Switching Instagram account!")
146
- self.acc_selector.bump_acc_fail("rate_limits")
147
- self.acc_selector.next()
148
- cur_acc = self.acc_selector.get_current()
149
- logging.info("Current Instagram account: index: '%d', login: '%s'", cur_acc[0], cur_acc[1]["login"])
165
+ self.try_next_account(job, report_error="rate_limits")
166
+ self.job_queue.put(job)
167
+ break
168
+ except CaptchaIssue as e:
169
+ logging.warning("Challange accured!")
170
+ logging.exception(e)
171
+ self.try_next_account(job)
172
+ self.job_queue.put(job)
173
+ break
150
174
  except YotubeLiveError as e:
151
175
  logging.warning("Youtube Live videos are not supported. Skipping.")
152
176
  logging.exception(e)
@@ -162,6 +186,20 @@ class AsyncDownloader(object):
162
186
  job_failed=True,
163
187
  job_failed_msg="Youtube Age Restricted error. Check your bot Youtube account settings.")
164
188
  )
189
+ self.send_message_to_admin(
190
+ f"Task {job.job_id} failed. URL: '{job.url}'. Reason: 'YotubeAgeRestrictedError'."
191
+ )
192
+ break
193
+ except AllAccountsFailed as e:
194
+ logging.error("All accounts failed!")
195
+ logging.exception(e)
196
+ self.uploader.queue_task(job.to_upload_job(
197
+ job_failed=True,
198
+ job_failed_msg="All bot accounts failed to download content. Bot administrator noticed about the issue.")
199
+ )
200
+ self.send_message_to_admin(
201
+ f"Task {job.job_id} failed. URL: '{job.url}'. Reason: 'AllAccountsFailed'."
202
+ )
165
203
  break
166
204
  except (UnknownError, Exception) as e:
167
205
  logging.warning("UnknownError occurred!")
@@ -172,7 +210,7 @@ class AsyncDownloader(object):
172
210
  else:
173
211
  exception_msg = str(e)
174
212
  if "geoblock_required" in exception_msg:
175
- if job.geoblock_error_count > self.acc_selector.count_service_accounts(job.job_origin.value):
213
+ if job.geoblock_error_count > self.acc_selector.count_service_accounts(job.job_origin):
176
214
  self.uploader.queue_task(job.to_upload_job(
177
215
  job_failed=True,
178
216
  job_failed_msg="This content does not accessible for all yout bot accounts. Seems like author blocked certain regions.")
@@ -77,5 +77,6 @@ class AccountSelector(object):
77
77
  def get_meta_data(self) -> dict:
78
78
  return self.accounts_meta_data[self.current_module_name][self.index]
79
79
 
80
- def count_service_accounts(self, mod_name: str) -> int:
81
- return len(self.accounts_meta_data[mod_name])
80
+ def count_service_accounts(self, mod_name: Origin) -> int:
81
+ module_name = 'youtube' if next((s for s in ("yt", "youtube", "youtu_be") if s in mod_name.value), None) else 'instagram'
82
+ return len(self.accounts_meta_data[module_name])
@@ -37,6 +37,12 @@ class YotubeAgeRestrictedError(ScraperError):
37
37
  class IGRateLimitAccured(ScraperError):
38
38
  pass
39
39
 
40
+ class CaptchaIssue(ScraperError):
41
+ pass
42
+
43
+ class AllAccountsFailed(ScraperError):
44
+ pass
45
+
40
46
  class UnknownError(ScraperError):
41
47
  pass
42
48
 
@@ -17,9 +17,9 @@ from instagrapi.mixins.story import Story
17
17
  #from instagrapi.types import Media
18
18
  from instagrapi import Client
19
19
  from instagrapi.mixins.challenge import ChallengeChoice
20
- from instagrapi.exceptions import LoginRequired, PleaseWaitFewMinutes, MediaNotFound, ClientNotFoundError, UserNotFound, UnknownError as IGUnknownError
20
+ from instagrapi.exceptions import LoginRequired, PleaseWaitFewMinutes, MediaNotFound, ClientNotFoundError, UserNotFound, ChallengeRequired, ChallengeSelfieCaptcha, UnknownError as IGUnknownError
21
21
 
22
- from warp_beacon.scraper.exceptions import NotFound, UnknownError, TimeOut, IGRateLimitAccured, extract_exception_message
22
+ from warp_beacon.scraper.exceptions import NotFound, UnknownError, TimeOut, IGRateLimitAccured, CaptchaIssue, extract_exception_message
23
23
  from warp_beacon.scraper.abstract import ScraperAbstract
24
24
  from warp_beacon.jobs.types import JobType
25
25
  from warp_beacon.telegram.utils import Utils
@@ -85,19 +85,7 @@ class InstagramScraper(ScraperAbstract):
85
85
 
86
86
  def scrap(self, url: str) -> tuple[str]:
87
87
  self.load_session()
88
- try:
89
- self.cl.get_timeline_feed()
90
- except LoginRequired as e:
91
- logging.error("Exception occurred while cheking IG session!")
92
- logging.exception(e)
93
- old_session = self.cl.get_settings()
94
- self.cl.set_settings({})
95
- self.setup_device()
96
- self.cl.set_uuids(old_session["uuids"])
97
- if os.path.exists(self.inst_session_file):
98
- os.unlink(self.inst_session_file)
99
- time.sleep(5)
100
- return self.scrap(url)
88
+ self._download_hndlr(self.cl.get_timeline_feed)
101
89
  def _scrap() -> tuple[str]:
102
90
  if "stories" in url:
103
91
  # remove URL options
@@ -141,6 +129,10 @@ class InstagramScraper(ScraperAbstract):
141
129
  try:
142
130
  ret_val = func(*args, **kwargs)
143
131
  break
132
+ except (ChallengeRequired, ChallengeSelfieCaptcha) as e:
133
+ logging.warning("Instagram wants Challange!")
134
+ logging.exception(e)
135
+ raise CaptchaIssue()
144
136
  except LoginRequired as e:
145
137
  logging.error("LoginRequired occurred in download handler!")
146
138
  logging.exception(e)
@@ -171,6 +163,7 @@ class InstagramScraper(ScraperAbstract):
171
163
  return ret_val
172
164
 
173
165
  def download_video(self, url: str, media_info: dict) -> dict:
166
+ self.cl.request_timeout = int(os.environ.get("IG_REQUEST_TIMEOUT", default=60))
174
167
  path = self._download_hndlr(self.cl.video_download_by_url, url, folder='/tmp')
175
168
  return {"local_media_path": str(path), "media_type": JobType.VIDEO, "media_info": {"duration": round(media_info.video_duration)}}
176
169
 
@@ -236,7 +229,7 @@ class InstagramScraper(ScraperAbstract):
236
229
  res = []
237
230
  wait_timeout = int(os.environ.get("IG_WAIT_TIMEOUT", default=60))
238
231
  timeout_increment = int(os.environ.get("IG_TIMEOUT_INCREMENT", default=30))
239
- ratelimit_threshold = int(os.environ.get("IG_RATELIMIT_TRESHOLD", default=5))
232
+ ratelimit_threshold = int(os.environ.get("IG_RATELIMIT_TRESHOLD", default=3))
240
233
  please_wait_few_minutes_count = 1
241
234
  while True:
242
235
  try:
@@ -17,7 +17,7 @@ from PIL import Image
17
17
 
18
18
  from warp_beacon.scraper.abstract import ScraperAbstract
19
19
  from warp_beacon.mediainfo.abstract import MediaInfoAbstract
20
- from warp_beacon.scraper.exceptions import NotFound, UnknownError, TimeOut, Unavailable, extract_exception_message
20
+ from warp_beacon.scraper.exceptions import TimeOut, Unavailable, extract_exception_message
21
21
 
22
22
  from pytubefix import YouTube
23
23
  from pytubefix.innertube import _default_clients
@@ -52,7 +52,8 @@ def patched_fetch_bearer_token(self) -> None:
52
52
  self.send_message_to_admin_func(
53
53
  f"Please open {verification_url} and input code `{user_code}`.\n\n"
54
54
  "Please select a Google account with verified age.\n"
55
- "This will allow you to avoid error the **AgeRestrictedError** when accessing some content.")
55
+ "This will allow you to avoid error the **AgeRestrictedError** when accessing some content.",
56
+ yt_auth=True)
56
57
  self.auth_event.wait()
57
58
 
58
59
  data = {
@@ -137,12 +138,12 @@ class YoutubeAbstract(ScraperAbstract):
137
138
  # do noting, not interested
138
139
  pass
139
140
  #except http.client.IncompleteRead as e:
140
- except (socket.timeout,
141
- ssl.SSLError,
142
- http.client.IncompleteRead,
143
- http.client.HTTPException,
144
- requests.RequestException,
145
- urllib.error.URLError,
141
+ except (socket.timeout,
142
+ ssl.SSLError,
143
+ http.client.IncompleteRead,
144
+ http.client.HTTPException,
145
+ requests.RequestException,
146
+ urllib.error.URLError,
146
147
  urllib.error.HTTPError) as e:
147
148
  if hasattr(e, "code") and int(e.code) == 403:
148
149
  raise Unavailable(extract_exception_message(e))
@@ -116,29 +116,33 @@ class Bot(object):
116
116
 
117
117
  return 0
118
118
 
119
- async def send_text_to_admin(self, text: str) -> int:
119
+ async def send_text_to_admin(self, text: str, yt_auth: bool = False) -> list[int]:
120
120
  try:
121
- admin = os.environ.get("TG_BOT_ADMIN_USERNAME", None)
122
- if not admin:
121
+ admins = os.environ.get("TG_BOT_ADMINS_USERNAMES", None)
122
+ if not admins:
123
123
  raise ValueError("Configuration value `TG_BOT_ADMIN_USERNAME` is empty!")
124
- message_reply = await self.client.send_message(
125
- chat_id=admin,
126
- text=text,
127
- parse_mode=ParseMode.MARKDOWN,
128
- reply_markup=InlineKeyboardMarkup(
129
- [
124
+
125
+ msg_ids = []
126
+ admins_array = admins.split(',')
127
+ for adm in admins_array:
128
+ adm = adm.strip()
129
+ msg_opts = {"chat_id": adm, "text": text, "parse_mode": ParseMode.MARKDOWN}
130
+ if yt_auth:
131
+ msg_opts["reply_markup"] = InlineKeyboardMarkup(
130
132
  [
131
- InlineKeyboardButton("✅ Done", callback_data="auth_process_done")
133
+ [
134
+ InlineKeyboardButton("✅ Done", callback_data="auth_process_done")
135
+ ]
132
136
  ]
133
- ]
134
- )
135
- )
136
- return message_reply.id
137
+ )
138
+ message_reply = await self.client.send_message(**msg_opts)
139
+ msg_ids.append(message_reply.id)
140
+ return msg_ids
137
141
  except Exception as e:
138
142
  logging.error("Failed to send text message to admin!")
139
143
  logging.exception(e)
140
144
 
141
- return 0
145
+ return []
142
146
 
143
147
  def build_tg_args(self, job: UploadJob) -> dict:
144
148
  args = {}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: warp_beacon
3
- Version: 2.1.1
3
+ Version: 2.1.3
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
@@ -1,2 +0,0 @@
1
- __version__ = "2.1.1"
2
-
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes