warp-beacon 2.0.10__py3-none-any.whl → 2.1.0__py3-none-any.whl
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.
- etc/warp_beacon/warp_beacon.conf +6 -5
- var/warp_beacon/accounts.json +61 -0
- warp_beacon/__version__.py +1 -1
- warp_beacon/jobs/abstract.py +6 -0
- warp_beacon/scraper/__init__.py +46 -14
- warp_beacon/scraper/abstract.py +31 -3
- warp_beacon/scraper/account_selector.py +81 -0
- warp_beacon/scraper/exceptions.py +3 -0
- warp_beacon/scraper/instagram/__init__.py +0 -0
- warp_beacon/scraper/{instagram.py → instagram/instagram.py} +146 -26
- warp_beacon/scraper/youtube/abstract.py +11 -13
- warp_beacon/scraper/youtube/music.py +0 -2
- warp_beacon/scraper/youtube/shorts.py +0 -2
- warp_beacon/scraper/youtube/youtube.py +0 -1
- warp_beacon/telegram/bot.py +2 -2
- warp_beacon/telegram/utils.py +12 -5
- {warp_beacon-2.0.10.dist-info → warp_beacon-2.1.0.dist-info}/METADATA +2 -2
- warp_beacon-2.1.0.dist-info/RECORD +43 -0
- {warp_beacon-2.0.10.dist-info → warp_beacon-2.1.0.dist-info}/WHEEL +1 -1
- {warp_beacon-2.0.10.dist-info → warp_beacon-2.1.0.dist-info}/top_level.txt +2 -0
- warp_beacon-2.0.10.dist-info/RECORD +0 -40
- {warp_beacon-2.0.10.dist-info → warp_beacon-2.1.0.dist-info}/LICENSE +0 -0
- {warp_beacon-2.0.10.dist-info → warp_beacon-2.1.0.dist-info}/entry_points.txt +0 -0
etc/warp_beacon/warp_beacon.conf
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
TG_TOKEN=""
|
2
|
+
TG_BOT_NAME=""
|
3
|
+
TG_BOT_ADMIN_USERNAME=""
|
2
4
|
TG_API_ID=""
|
3
5
|
TG_API_HASH=""
|
4
6
|
TG_BOT_NAME=""
|
5
7
|
TG_BOT_ADMIN_USERNAME=""
|
6
|
-
|
7
|
-
INSTAGRAM_PASSWORD=""
|
8
|
-
INSTAGRAM_VERIFICATION_CODE=""
|
8
|
+
IG_MAX_RETRIES=10
|
9
9
|
MONGODB_HOST="mongodb"
|
10
10
|
MONGODB_PORT="27017"
|
11
11
|
MONGODB_USER="root"
|
12
12
|
MONGODB_PASSWORD="changeme"
|
13
|
-
|
14
|
-
|
13
|
+
ENABLE_DONATES=true
|
14
|
+
SERVICE_ACCOUNTS_FILE=/var/warp_beacon/accounts.json
|
15
|
+
FORCE_IPV6=true
|
@@ -0,0 +1,61 @@
|
|
1
|
+
{
|
2
|
+
"instagram":
|
3
|
+
[
|
4
|
+
{
|
5
|
+
"login": "ig_account0",
|
6
|
+
"password": "ig_password",
|
7
|
+
"imap_server": "imap.gmail.com",
|
8
|
+
"imap_login": "user@gmail.com",
|
9
|
+
"imap_password": "",
|
10
|
+
"auth_details":
|
11
|
+
{
|
12
|
+
"delay_range": [1, 3],
|
13
|
+
"country_code": 7,
|
14
|
+
"locale": "en_US",
|
15
|
+
"timezone_offset": 10800,
|
16
|
+
"user_agent": "Barcelona 291.0.0.31.111 Android (33/13; 600dpi; 1440x3044; samsung; SM-G998B; p3s; exynos2100; en_US; 493450264)",
|
17
|
+
"device":
|
18
|
+
{
|
19
|
+
"app_version": "291.0.0.31.111",
|
20
|
+
"android_version": 33,
|
21
|
+
"android_release": "13.0.0",
|
22
|
+
"dpi": "600dpi",
|
23
|
+
"resolution": "1440x3044",
|
24
|
+
"manufacturer": "Samsung",
|
25
|
+
"device": "p3s",
|
26
|
+
"model": "SM-G998B",
|
27
|
+
"cpu": "exynos2100",
|
28
|
+
"version_code": "493450264"
|
29
|
+
}
|
30
|
+
}
|
31
|
+
},
|
32
|
+
{
|
33
|
+
"login": "ig_account1",
|
34
|
+
"password": "passwd",
|
35
|
+
"imap_server": "imap.gmail.com",
|
36
|
+
"imap_login": "mail_login1",
|
37
|
+
"imap_password": "imap_password1",
|
38
|
+
"auth_details":
|
39
|
+
{
|
40
|
+
"delay_range": [1, 3],
|
41
|
+
"country_code": 7,
|
42
|
+
"locale": "en_US",
|
43
|
+
"timezone_offset": 10800,
|
44
|
+
"user_agent": "Barcelona 291.0.0.31.111 Android (33/13; 600dpi; 1440x3044; samsung; SM-G998B; p3s; exynos2100; en_US; 493450264)",
|
45
|
+
"device":
|
46
|
+
{
|
47
|
+
"app_version": "291.0.0.31.111",
|
48
|
+
"android_version": 33,
|
49
|
+
"android_release": "13.0.0",
|
50
|
+
"dpi": "600dpi",
|
51
|
+
"resolution": "1440x3044",
|
52
|
+
"manufacturer": "Samsung",
|
53
|
+
"device": "p3s",
|
54
|
+
"model": "SM-G998B",
|
55
|
+
"cpu": "exynos2100",
|
56
|
+
"version_code": "493450264"
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
]
|
61
|
+
}
|
warp_beacon/__version__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
__version__ = "2.0
|
1
|
+
__version__ = "2.1.0"
|
2
2
|
|
warp_beacon/jobs/abstract.py
CHANGED
@@ -31,6 +31,9 @@ class JobSettings(TypedDict):
|
|
31
31
|
canonical_name: str
|
32
32
|
is_message_to_admin: bool
|
33
33
|
message_text: str
|
34
|
+
source_username: str
|
35
|
+
unvailable_error_count: int
|
36
|
+
geoblock_error_count: int
|
34
37
|
|
35
38
|
class AbstractJob(ABC):
|
36
39
|
job_id: uuid.UUID = None
|
@@ -56,6 +59,9 @@ class AbstractJob(ABC):
|
|
56
59
|
canonical_name: str = ""
|
57
60
|
is_message_to_admin: bool = False
|
58
61
|
message_text: str = ""
|
62
|
+
source_usename: str = ""
|
63
|
+
unvailable_error_count: int = 0
|
64
|
+
geoblock_error_count: int = 0
|
59
65
|
|
60
66
|
def __init__(self, **kwargs: Unpack[JobSettings]) -> None:
|
61
67
|
if kwargs:
|
warp_beacon/scraper/__init__.py
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
import os
|
2
|
-
import time
|
3
|
-
import asyncio
|
4
2
|
|
5
3
|
from typing import Optional
|
6
4
|
import multiprocessing
|
7
5
|
from queue import Empty
|
8
6
|
|
9
|
-
from warp_beacon.scraper.exceptions import NotFound, UnknownError, TimeOut, Unavailable, FileTooBig, YotubeLiveError, YotubeAgeRestrictedError
|
7
|
+
from warp_beacon.scraper.exceptions import NotFound, UnknownError, TimeOut, Unavailable, FileTooBig, YotubeLiveError, YotubeAgeRestrictedError, IGRateLimitAccured
|
10
8
|
from warp_beacon.mediainfo.video import VideoInfo
|
11
9
|
from warp_beacon.mediainfo.audio import AudioInfo
|
12
10
|
from warp_beacon.mediainfo.silencer import Silencer
|
@@ -16,9 +14,12 @@ from warp_beacon.jobs import Origin
|
|
16
14
|
from warp_beacon.jobs.download_job import DownloadJob
|
17
15
|
from warp_beacon.jobs.upload_job import UploadJob
|
18
16
|
from warp_beacon.jobs.types import JobType
|
17
|
+
from warp_beacon.scraper.account_selector import AccountSelector
|
19
18
|
|
20
19
|
import logging
|
21
20
|
|
21
|
+
ACC_FILE = os.environ.get("SERVICE_ACCOUNTS_FILE", default="/var/warp_beacon/instagram_accounts.json")
|
22
|
+
|
22
23
|
class AsyncDownloader(object):
|
23
24
|
__JOE_BIDEN_WAKEUP = None
|
24
25
|
workers = []
|
@@ -27,11 +28,13 @@ class AsyncDownloader(object):
|
|
27
28
|
uploader = None
|
28
29
|
workers_count = 0
|
29
30
|
auth_event = multiprocessing.Event()
|
31
|
+
acc_selector = None
|
30
32
|
|
31
33
|
def __init__(self, uploader: AsyncUploader, workers_count: int) -> None:
|
32
34
|
self.allow_loop = multiprocessing.Value('i', 1)
|
33
35
|
self.uploader = uploader
|
34
36
|
self.workers_count = workers_count
|
37
|
+
self.acc_selector = AccountSelector(ACC_FILE)
|
35
38
|
|
36
39
|
def __del__(self) -> None:
|
37
40
|
self.stop_all()
|
@@ -77,18 +80,19 @@ class AsyncDownloader(object):
|
|
77
80
|
if job.job_origin is not Origin.UNKNOWN:
|
78
81
|
if not job.in_process:
|
79
82
|
actor = None
|
83
|
+
self.acc_selector.set_module(job.job_origin)
|
80
84
|
if job.job_origin is Origin.INSTAGRAM:
|
81
|
-
from warp_beacon.scraper.instagram import InstagramScraper
|
82
|
-
actor = InstagramScraper()
|
85
|
+
from warp_beacon.scraper.instagram.instagram import InstagramScraper
|
86
|
+
actor = InstagramScraper(self.acc_selector.get_current())
|
83
87
|
elif job.job_origin is Origin.YT_SHORTS:
|
84
88
|
from warp_beacon.scraper.youtube.shorts import YoutubeShortsScraper
|
85
|
-
actor = YoutubeShortsScraper()
|
89
|
+
actor = YoutubeShortsScraper(self.acc_selector.get_current())
|
86
90
|
elif job.job_origin is Origin.YT_MUSIC:
|
87
91
|
from warp_beacon.scraper.youtube.music import YoutubeMusicScraper
|
88
|
-
actor = YoutubeMusicScraper()
|
92
|
+
actor = YoutubeMusicScraper(self.acc_selector.get_current())
|
89
93
|
elif job.job_origin is Origin.YOUTUBE:
|
90
94
|
from warp_beacon.scraper.youtube.youtube import YoutubeScraper
|
91
|
-
actor = YoutubeScraper()
|
95
|
+
actor = YoutubeScraper(self.acc_selector.get_current())
|
92
96
|
#self.auth_event = multiprocessing.Event()
|
93
97
|
actor.send_message_to_admin_func = self.send_message_to_admin
|
94
98
|
actor.auth_event = self.auth_event
|
@@ -97,14 +101,28 @@ class AsyncDownloader(object):
|
|
97
101
|
logging.info("Downloading URL '%s'", job.url)
|
98
102
|
items = actor.download(job.url)
|
99
103
|
break
|
100
|
-
except
|
101
|
-
logging.warning("Not found
|
104
|
+
except NotFound as e:
|
105
|
+
logging.warning("Not found error occurred!")
|
102
106
|
logging.exception(e)
|
103
107
|
self.uploader.queue_task(job.to_upload_job(
|
104
108
|
job_failed=True,
|
105
109
|
job_failed_msg="Unable to access to media under this URL. Seems like the media is private.")
|
106
110
|
)
|
107
111
|
break
|
112
|
+
except Unavailable as e:
|
113
|
+
logging.warning("Not found or unavailable error occurred!")
|
114
|
+
logging.exception(e)
|
115
|
+
if job.unvailable_error_count > self.acc_selector.count_service_accounts(job.job_origin.value):
|
116
|
+
self.uploader.queue_task(job.to_upload_job(
|
117
|
+
job_failed=True,
|
118
|
+
job_failed_msg="Video is unvailable for all your service accounts.")
|
119
|
+
)
|
120
|
+
break
|
121
|
+
job.unvailable_error_count += 1
|
122
|
+
logging.info("Trying to switch account")
|
123
|
+
self.acc_selector.next()
|
124
|
+
self.job_queue.put(job)
|
125
|
+
break
|
108
126
|
except TimeOut as e:
|
109
127
|
logging.warning("Timeout error occurred!")
|
110
128
|
logging.exception(e)
|
@@ -121,6 +139,14 @@ class AsyncDownloader(object):
|
|
121
139
|
job_failed_msg="Unfortunately this file has exceeded the Telegram limits. A file cannot be larger than 2 gigabytes.")
|
122
140
|
)
|
123
141
|
break
|
142
|
+
except IGRateLimitAccured as e:
|
143
|
+
logging.warning("IG ratelimit accured :(")
|
144
|
+
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"])
|
124
150
|
except YotubeLiveError as e:
|
125
151
|
logging.warning("Youtube Live videos are not supported. Skipping.")
|
126
152
|
logging.exception(e)
|
@@ -146,10 +172,16 @@ class AsyncDownloader(object):
|
|
146
172
|
else:
|
147
173
|
exception_msg = str(e)
|
148
174
|
if "geoblock_required" in exception_msg:
|
149
|
-
self.
|
150
|
-
|
151
|
-
|
152
|
-
|
175
|
+
if job.geoblock_error_count > self.acc_selector.count_service_accounts(job.job_origin.value):
|
176
|
+
self.uploader.queue_task(job.to_upload_job(
|
177
|
+
job_failed=True,
|
178
|
+
job_failed_msg="This content does not accessible for all yout bot accounts. Seems like author blocked certain regions.")
|
179
|
+
)
|
180
|
+
break
|
181
|
+
job.geoblock_error_count += 1
|
182
|
+
logging.info("Trying to switch account")
|
183
|
+
self.acc_selector.next()
|
184
|
+
self.job_queue.put(job)
|
153
185
|
break
|
154
186
|
self.uploader.queue_task(job.to_upload_job(
|
155
187
|
job_failed=True,
|
warp_beacon/scraper/abstract.py
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
import os
|
2
2
|
import pathlib
|
3
3
|
|
4
|
+
import socket
|
5
|
+
import requests.packages.urllib3.util.connection as urllib3_cn
|
6
|
+
|
4
7
|
from abc import ABC, abstractmethod
|
5
8
|
from typing import Callable, Union
|
6
9
|
|
@@ -10,14 +13,21 @@ from pillow_heif import register_heif_opener
|
|
10
13
|
import logging
|
11
14
|
|
12
15
|
class ScraperAbstract(ABC):
|
16
|
+
original_gai_family = None
|
13
17
|
send_message_to_admin_func = None
|
14
18
|
auth_event = None
|
19
|
+
account = None
|
20
|
+
account_index = 0
|
15
21
|
|
16
|
-
def __init__(self) -> None:
|
17
|
-
|
22
|
+
def __init__(self, account: tuple) -> None:
|
23
|
+
self.account_index = account[0]
|
24
|
+
self.account = account[1]
|
25
|
+
if os.environ.get("FORCE_IPV6", default="false") == "true":
|
26
|
+
self.force_ipv6()
|
18
27
|
|
19
28
|
def __del__(self) -> None:
|
20
|
-
|
29
|
+
if os.environ.get("FORCE_IPV6", default="false") == "true":
|
30
|
+
self.restore_gai()
|
21
31
|
|
22
32
|
@abstractmethod
|
23
33
|
def download(self, url: str) -> bool:
|
@@ -63,3 +73,21 @@ class ScraperAbstract(ABC):
|
|
63
73
|
logging.exception(e)
|
64
74
|
|
65
75
|
return ''
|
76
|
+
|
77
|
+
def force_ipv6(self) -> None:
|
78
|
+
def allowed_gai_family():
|
79
|
+
"""
|
80
|
+
https://github.com/shazow/urllib3/blob/master/urllib3/util/connection.py
|
81
|
+
"""
|
82
|
+
family = socket.AF_INET
|
83
|
+
if urllib3_cn.HAS_IPV6:
|
84
|
+
family = socket.AF_INET6 # force ipv6 only if it is available
|
85
|
+
return family
|
86
|
+
if self.original_gai_family is None:
|
87
|
+
self.original_gai_family = urllib3_cn.allowed_gai_family
|
88
|
+
logging.info("Forcing IPv6 ...")
|
89
|
+
urllib3_cn.allowed_gai_family = allowed_gai_family
|
90
|
+
|
91
|
+
def restore_gai(self) -> None:
|
92
|
+
logging.info("Restoring normal IP stack ...")
|
93
|
+
urllib3_cn.allowed_gai_family = self.original_gai_family
|
@@ -0,0 +1,81 @@
|
|
1
|
+
import os
|
2
|
+
import json
|
3
|
+
import re
|
4
|
+
|
5
|
+
from itertools import cycle
|
6
|
+
|
7
|
+
from warp_beacon.jobs import Origin
|
8
|
+
|
9
|
+
import logging
|
10
|
+
|
11
|
+
class AccountSelector(object):
|
12
|
+
accounts = []
|
13
|
+
acc_pool = None
|
14
|
+
current = None
|
15
|
+
current_module_name = None
|
16
|
+
index = 0
|
17
|
+
accounts_meta_data = {}
|
18
|
+
session_dir = "/var/warp_beacon"
|
19
|
+
|
20
|
+
def __init__(self, acc_file_path: str) -> None:
|
21
|
+
if os.path.exists(acc_file_path):
|
22
|
+
with open(acc_file_path, 'r', encoding="utf-8") as f:
|
23
|
+
self.accounts = json.loads(f.read())
|
24
|
+
if self.accounts:
|
25
|
+
self.__init_meta_data()
|
26
|
+
self.load_yt_sessions()
|
27
|
+
else:
|
28
|
+
raise ValueError("Accounts file not found")
|
29
|
+
|
30
|
+
def __del__(self) -> None:
|
31
|
+
pass
|
32
|
+
|
33
|
+
#def enrich_service_data(self) -> None:
|
34
|
+
# for k, v in self.accounts.items():
|
35
|
+
|
36
|
+
def load_yt_sessions(self) -> None:
|
37
|
+
self.accounts["youtube"] = []
|
38
|
+
for f in os.listdir(self.session_dir):
|
39
|
+
if "yt_session" in f and ".json" in f:
|
40
|
+
match = re.search('\d+', f)
|
41
|
+
index = 0
|
42
|
+
if match:
|
43
|
+
index = int(match.group(0))
|
44
|
+
self.accounts["youtube"].insert(index, {"session_file": "%s/%s" % (self.session_dir, f), "index": index})
|
45
|
+
if not self.accounts["youtube"]:
|
46
|
+
self.accounts["youtube"].append({"session_file": "%s/yt_session_0.json" % self.session_dir, "index": 0})
|
47
|
+
|
48
|
+
def __init_meta_data(self) -> None:
|
49
|
+
for module_name, lst in self.accounts.items():
|
50
|
+
if module_name not in self.accounts_meta_data:
|
51
|
+
self.accounts_meta_data[module_name] = []
|
52
|
+
for index, _ in enumerate(lst):
|
53
|
+
self.accounts_meta_data[module_name].insert(index, {"auth_fails": 0, "rate_limits": 0})
|
54
|
+
|
55
|
+
def set_module(self, module_origin: Origin) -> None:
|
56
|
+
module_name = 'youtube' if next((s for s in ("yt", "youtube", "youtu_be") if s in module_origin.value), None) else 'instagram'
|
57
|
+
self.current_module_name = module_name
|
58
|
+
self.acc_pool = cycle(self.accounts[module_name])
|
59
|
+
self.current = next(self.acc_pool)
|
60
|
+
self.index = self.accounts[module_name].index(self.current)
|
61
|
+
|
62
|
+
def next(self) -> dict:
|
63
|
+
self.current = next(self.acc_pool)
|
64
|
+
self.index = self.accounts[self.current_module_name].index(self.current)
|
65
|
+
return self.current
|
66
|
+
|
67
|
+
def bump_acc_fail(self, key: str, amount: int = 1) -> int:
|
68
|
+
self.accounts_meta_data[self.index][key] += amount
|
69
|
+
return self.accounts_meta_data[self.index][key]
|
70
|
+
|
71
|
+
def how_much(self, key: str) -> int:
|
72
|
+
return self.accounts_meta_data[self.current_module_name][self.index][key]
|
73
|
+
|
74
|
+
def get_current(self) -> tuple:
|
75
|
+
return (self.index, self.current)
|
76
|
+
|
77
|
+
def get_meta_data(self) -> dict:
|
78
|
+
return self.accounts_meta_data[self.current_module_name][self.index]
|
79
|
+
|
80
|
+
def count_service_accounts(self, mod_name: str) -> int:
|
81
|
+
return len(self.accounts_meta_data[mod_name])
|
File without changes
|
@@ -1,64 +1,103 @@
|
|
1
1
|
import os
|
2
2
|
import time
|
3
|
-
|
4
3
|
import socket
|
5
4
|
import ssl
|
6
|
-
|
5
|
+
import re
|
7
6
|
from typing import Callable, Optional, Union
|
8
7
|
|
9
|
-
|
8
|
+
import random
|
9
|
+
import email
|
10
|
+
import imaplib
|
10
11
|
import json
|
11
|
-
|
12
12
|
import requests
|
13
13
|
import urllib3
|
14
14
|
from urllib.parse import urljoin, urlparse
|
15
15
|
|
16
16
|
from instagrapi.mixins.story import Story
|
17
|
-
from instagrapi.types import Media
|
17
|
+
#from instagrapi.types import Media
|
18
18
|
from instagrapi import Client
|
19
|
+
from instagrapi.mixins.challenge import ChallengeChoice
|
19
20
|
from instagrapi.exceptions import LoginRequired, PleaseWaitFewMinutes, MediaNotFound, ClientNotFoundError, UserNotFound, UnknownError as IGUnknownError
|
20
21
|
|
21
|
-
from warp_beacon.scraper.exceptions import NotFound, UnknownError, TimeOut, extract_exception_message
|
22
|
+
from warp_beacon.scraper.exceptions import NotFound, UnknownError, TimeOut, IGRateLimitAccured, extract_exception_message
|
22
23
|
from warp_beacon.scraper.abstract import ScraperAbstract
|
23
24
|
from warp_beacon.jobs.types import JobType
|
24
25
|
from warp_beacon.telegram.utils import Utils
|
25
26
|
|
26
27
|
import logging
|
27
28
|
|
28
|
-
|
29
|
+
INST_SESSION_FILE_TPL = "/var/warp_beacon/inst_session_account_%d.json"
|
29
30
|
|
30
31
|
class InstagramScraper(ScraperAbstract):
|
31
32
|
cl = None
|
33
|
+
inst_session_file = ""
|
32
34
|
|
33
|
-
def __init__(self) -> None:
|
34
|
-
super().__init__()
|
35
|
+
def __init__(self, account: tuple) -> None:
|
36
|
+
super().__init__(account)
|
37
|
+
#
|
38
|
+
self.inst_session_file = INST_SESSION_FILE_TPL % self.account_index
|
35
39
|
self.cl = Client()
|
40
|
+
self.setup_device()
|
41
|
+
self.cl.challenge_code_handler = self.challenge_code_handler
|
42
|
+
self.cl.change_password_handler = self.change_password_handler
|
43
|
+
|
44
|
+
def setup_device(self) -> None:
|
45
|
+
details = self.account.get("auth_details", {})
|
46
|
+
self.cl.delay_range = details.get("delay_range", [1, 3])
|
47
|
+
self.cl.set_country_code(details.get("country_code", 1))
|
48
|
+
self.cl.set_locale(details.get("locale", "en_US"))
|
49
|
+
self.cl.set_timezone_offset(details.get("timezone_offset", 10800))
|
50
|
+
self.cl.set_user_agent(details.get("user_agent", "Barcelona 291.0.0.31.111 Android (33/13; 600dpi; 1440x3044; samsung; SM-G998B; p3s; exynos2100; en_US; 493450264)"))
|
51
|
+
device = details.get("device", {})
|
52
|
+
self.cl.set_device({
|
53
|
+
"app_version": device.get("app_version", "291.0.0.31.111"),
|
54
|
+
"android_version": device.get("android_version", 33),
|
55
|
+
"android_release": device.get("android_release", "13.0.0"),
|
56
|
+
"dpi": device.get("dpi", "600dpi"),
|
57
|
+
"resolution": device.get("resolution", "1440x3044"),
|
58
|
+
"manufacturer": device.get("manufacturer", "Samsung"),
|
59
|
+
"device": device.get("device", "p3s"),
|
60
|
+
"model": device.get("model", "SM-G998B"),
|
61
|
+
"cpu": device.get("cpu", "exynos2100"),
|
62
|
+
"version_code": device.get("version_code", "493450264")
|
63
|
+
})
|
36
64
|
|
37
65
|
def safe_write_session(self) -> None:
|
38
|
-
tmp_fname = "%s~" %
|
39
|
-
with open(tmp_fname, 'w+') as f:
|
66
|
+
tmp_fname = "%s~" % self.inst_session_file
|
67
|
+
with open(tmp_fname, 'w+', encoding="utf-8") as f:
|
40
68
|
f.write(json.dumps(self.cl.get_settings()))
|
41
|
-
if os.path.
|
42
|
-
os.unlink(
|
43
|
-
os.rename(tmp_fname,
|
69
|
+
if os.path.exists(self.inst_session_file):
|
70
|
+
os.unlink(self.inst_session_file)
|
71
|
+
os.rename(tmp_fname, self.inst_session_file)
|
44
72
|
|
45
73
|
def load_session(self) -> None:
|
46
|
-
if os.path.exists(
|
47
|
-
self.cl.load_settings(
|
74
|
+
if os.path.exists(self.inst_session_file):
|
75
|
+
self.cl.load_settings(self.inst_session_file)
|
48
76
|
else:
|
49
77
|
self.login()
|
50
78
|
|
51
79
|
def login(self) -> None:
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
if username is not None and password is not None:
|
57
|
-
self.cl.login(username=username, password=password, verification_code=verification_code)
|
80
|
+
username = self.account["login"]
|
81
|
+
password = self.account["password"]
|
82
|
+
if username and password:
|
83
|
+
self.cl.login(username=username, password=password, verification_code="")
|
58
84
|
self.safe_write_session()
|
59
85
|
|
60
86
|
def scrap(self, url: str) -> tuple[str]:
|
61
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)
|
62
101
|
def _scrap() -> tuple[str]:
|
63
102
|
if "stories" in url:
|
64
103
|
# remove URL options
|
@@ -102,6 +141,17 @@ class InstagramScraper(ScraperAbstract):
|
|
102
141
|
try:
|
103
142
|
ret_val = func(*args, **kwargs)
|
104
143
|
break
|
144
|
+
except LoginRequired as e:
|
145
|
+
logging.error("LoginRequired occurred in download handler!")
|
146
|
+
logging.exception(e)
|
147
|
+
old_session = self.cl.get_settings()
|
148
|
+
self.cl.set_settings({})
|
149
|
+
self.setup_device()
|
150
|
+
self.cl.set_uuids(old_session["uuids"])
|
151
|
+
if os.path.exists(self.inst_session_file):
|
152
|
+
os.unlink(self.inst_session_file)
|
153
|
+
time.sleep(5)
|
154
|
+
self.load_session()
|
105
155
|
except (socket.timeout,
|
106
156
|
ssl.SSLError,
|
107
157
|
requests.exceptions.ConnectionError,
|
@@ -173,7 +223,7 @@ class InstagramScraper(ScraperAbstract):
|
|
173
223
|
for media_chunk in Utils.chunker(media_info.resources, 10):
|
174
224
|
chunk = []
|
175
225
|
for media in media_chunk:
|
176
|
-
_media_info = self.cl.media_info
|
226
|
+
_media_info = self._download_hndlr(self.cl.media_info, media.pk)
|
177
227
|
if media.media_type == 1: # photo
|
178
228
|
chunk.append(self.download_photo(url=_media_info.thumbnail_url))
|
179
229
|
elif media.media_type == 2: # video
|
@@ -184,6 +234,10 @@ class InstagramScraper(ScraperAbstract):
|
|
184
234
|
|
185
235
|
def download(self, url: str) -> Optional[list[dict]]:
|
186
236
|
res = []
|
237
|
+
wait_timeout = int(os.environ.get("IG_WAIT_TIMEOUT", default=60))
|
238
|
+
timeout_increment = int(os.environ.get("IG_TIMEOUT_INCREMENT", default=30))
|
239
|
+
ratelimit_threshold = int(os.environ.get("IG_RATELIMIT_TRESHOLD", default=5))
|
240
|
+
please_wait_few_minutes_count = 1
|
187
241
|
while True:
|
188
242
|
try:
|
189
243
|
scrap_type, media_id = self.scrap(url)
|
@@ -205,10 +259,14 @@ class InstagramScraper(ScraperAbstract):
|
|
205
259
|
res.append(self.download_stories(self.scrap_stories(media_id)))
|
206
260
|
break
|
207
261
|
except PleaseWaitFewMinutes as e:
|
262
|
+
please_wait_few_minutes_count += 1
|
208
263
|
logging.warning("Please wait a few minutes error. Trying to relogin ...")
|
209
264
|
logging.exception(e)
|
210
|
-
|
211
|
-
|
265
|
+
if please_wait_few_minutes_count >= ratelimit_threshold:
|
266
|
+
logging.warning("IG ratelimit accured")
|
267
|
+
raise IGRateLimitAccured()
|
268
|
+
wait_timeout += timeout_increment
|
269
|
+
logging.info("Waiting %d seconds according configuration option `IG_WAIT_TIMEOUT` with `IG_TIMEOUT_INCREMENT`", wait_timeout)
|
212
270
|
if res:
|
213
271
|
for i in res:
|
214
272
|
if i["media_type"] == JobType.COLLECTION:
|
@@ -218,10 +276,72 @@ class InstagramScraper(ScraperAbstract):
|
|
218
276
|
else:
|
219
277
|
if os.path.exists(i["local_media_path"]):
|
220
278
|
os.unlink(i["local_media_path"])
|
221
|
-
os.
|
279
|
+
if os.path.exists(self.inst_session_file):
|
280
|
+
os.unlink(self.inst_session_file)
|
281
|
+
self.login()
|
222
282
|
time.sleep(wait_timeout)
|
223
283
|
except (MediaNotFound, ClientNotFoundError, UserNotFound) as e:
|
224
284
|
raise NotFound(extract_exception_message(e))
|
225
285
|
except IGUnknownError as e:
|
226
286
|
raise UnknownError(extract_exception_message(e))
|
227
287
|
return res
|
288
|
+
|
289
|
+
def email_challenge_resolver(self, username: str) -> Optional[str]:
|
290
|
+
logging.info("Started email challenge resolver")
|
291
|
+
mail = imaplib.IMAP4_SSL(os.environ.get("MAIL_SERVER", default="imap.bagrintsev.me"))
|
292
|
+
mail.login(os.environ.get("MAIL_LOGIN", default=""), os.environ.get("MAIL_PASSWORD", default="")) # email server creds
|
293
|
+
mail.select("inbox")
|
294
|
+
_, data = mail.search(None, "(UNSEEN)")
|
295
|
+
ids = data.pop().split()
|
296
|
+
for num in reversed(ids):
|
297
|
+
mail.store(num, "+FLAGS", "\\Seen") # mark as read
|
298
|
+
_, data = mail.fetch(num, "(RFC822)")
|
299
|
+
msg = email.message_from_string(data[0][1].decode())
|
300
|
+
payloads = msg.get_payload()
|
301
|
+
if not isinstance(payloads, list):
|
302
|
+
payloads = [msg]
|
303
|
+
code = None
|
304
|
+
for payload in payloads:
|
305
|
+
body = ''
|
306
|
+
try:
|
307
|
+
body = payload.get_payload(decode=True).decode()
|
308
|
+
except:
|
309
|
+
continue
|
310
|
+
if "<div" not in body:
|
311
|
+
continue
|
312
|
+
match = re.search(">([^>]*?({u})[^<]*?)<".format(u=username), body)
|
313
|
+
if not match:
|
314
|
+
continue
|
315
|
+
logging.info("Match from email: '%s'", match.group(1))
|
316
|
+
match = re.search(r">(\d{6})<", body)
|
317
|
+
if not match:
|
318
|
+
logging.info('Skip this email, "code" not found')
|
319
|
+
continue
|
320
|
+
code = match.group(1)
|
321
|
+
if code:
|
322
|
+
logging.info("Found IG code at mail server: '%s'", code)
|
323
|
+
return code
|
324
|
+
return None
|
325
|
+
|
326
|
+
def get_code_from_sms(self, username: str) -> Optional[str]:
|
327
|
+
while True:
|
328
|
+
code = input(f"Enter code (6 digits) for {username}: ").strip()
|
329
|
+
if code and code.isdigit():
|
330
|
+
return code
|
331
|
+
return None
|
332
|
+
|
333
|
+
def challenge_code_handler(self, username: str, choice: ChallengeChoice) -> bool:
|
334
|
+
if choice == ChallengeChoice.SMS:
|
335
|
+
return False
|
336
|
+
#return self.get_code_from_sms(username)
|
337
|
+
elif choice == ChallengeChoice.EMAIL:
|
338
|
+
return self.email_challenge_resolver(username)
|
339
|
+
|
340
|
+
return False
|
341
|
+
|
342
|
+
def change_password_handler(self, username: str) -> str:
|
343
|
+
# Simple way to generate a random string
|
344
|
+
chars = list("abcdefghijklmnopqrstuvwxyz1234567890!&£@#")
|
345
|
+
password = "".join(random.sample(chars, 10))
|
346
|
+
logging.info("Generated new IG password: '%s'", password)
|
347
|
+
return password
|
@@ -4,7 +4,7 @@ import time
|
|
4
4
|
import socket
|
5
5
|
import ssl
|
6
6
|
|
7
|
-
from abc import abstractmethod
|
7
|
+
#from abc import abstractmethod
|
8
8
|
from typing import Callable, Union
|
9
9
|
|
10
10
|
import json
|
@@ -78,10 +78,10 @@ def patched_fetch_bearer_token(self) -> None:
|
|
78
78
|
|
79
79
|
class YoutubeAbstract(ScraperAbstract):
|
80
80
|
DOWNLOAD_DIR = "/tmp"
|
81
|
-
YT_SESSION_FILE = '/var/warp_beacon/
|
81
|
+
YT_SESSION_FILE = '/var/warp_beacon/yt_session_%d.json'
|
82
82
|
|
83
|
-
def __init__(self) -> None:
|
84
|
-
|
83
|
+
def __init__(self, account: tuple) -> None:
|
84
|
+
super().__init__(account)
|
85
85
|
|
86
86
|
def __del__(self) -> None:
|
87
87
|
pass
|
@@ -91,7 +91,7 @@ class YoutubeAbstract(ScraperAbstract):
|
|
91
91
|
raise NameError("No file provided")
|
92
92
|
path_info = pathlib.Path(filename)
|
93
93
|
ext = path_info.suffix
|
94
|
-
old_filename = path_info.stem
|
94
|
+
#old_filename = path_info.stem
|
95
95
|
time_name = str(time.time()).replace('.', '_')
|
96
96
|
new_filename = "%s%s" % (time_name, ext)
|
97
97
|
new_filepath = "%s/%s" % (os.path.dirname(filename), new_filename)
|
@@ -170,11 +170,9 @@ class YoutubeAbstract(ScraperAbstract):
|
|
170
170
|
InnerTube.fetch_bearer_token = patched_fetch_bearer_token
|
171
171
|
_default_clients["ANDROID"]["innertube_context"]["context"]["client"]["clientVersion"] = "19.08.35"
|
172
172
|
_default_clients["ANDROID_MUSIC"] = _default_clients["ANDROID"]
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
on_progress_callback = self.yt_on_progress
|
180
|
-
)
|
173
|
+
yt_opts = {"url": url, "on_progress_callback": self.yt_on_progress}
|
174
|
+
#yt_opts["client"] = "WEB"
|
175
|
+
yt_opts["use_oauth"] = True
|
176
|
+
yt_opts["allow_oauth_cache"] = True
|
177
|
+
yt_opts["token_file"] = self.YT_SESSION_FILE % self.account_index
|
178
|
+
return YouTube(**yt_opts)
|
@@ -2,7 +2,6 @@ from warp_beacon.jobs.types import JobType
|
|
2
2
|
from warp_beacon.scraper.youtube.abstract import YoutubeAbstract
|
3
3
|
from warp_beacon.scraper.exceptions import YotubeLiveError, NotFound, YotubeAgeRestrictedError
|
4
4
|
|
5
|
-
from pytubefix import YouTube
|
6
5
|
from pytubefix.exceptions import AgeRestrictedError
|
7
6
|
|
8
7
|
import logging
|
warp_beacon/telegram/bot.py
CHANGED
@@ -50,7 +50,7 @@ class Bot(object):
|
|
50
50
|
bot_token=tg_token,
|
51
51
|
api_id=tg_api_id,
|
52
52
|
api_hash=tg_api_hash,
|
53
|
-
workdir='/',
|
53
|
+
workdir='/var/warp_beacon',
|
54
54
|
workers=int(os.environ.get("TG_WORKERS_POOL_SIZE", default=workers_amount))
|
55
55
|
)
|
56
56
|
|
@@ -373,4 +373,4 @@ class Bot(object):
|
|
373
373
|
finally:
|
374
374
|
job.remove_files()
|
375
375
|
|
376
|
-
return tg_file_ids
|
376
|
+
return tg_file_ids
|
warp_beacon/telegram/utils.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
import
|
1
|
+
from typing import Union
|
2
2
|
|
3
|
+
import re
|
3
4
|
import requests
|
4
5
|
|
5
|
-
from
|
6
|
-
from typing import Union
|
6
|
+
from pyrogram.types import Message
|
7
7
|
|
8
8
|
from warp_beacon.jobs import Origin
|
9
9
|
from warp_beacon.jobs.types import JobType
|
@@ -14,7 +14,7 @@ class Utils(object):
|
|
14
14
|
expected_patronum_compiled_re = re.compile(r'Expected ([A-Z]+), got ([A-Z]+) file id instead')
|
15
15
|
|
16
16
|
@staticmethod
|
17
|
-
def extract_file_id(message:
|
17
|
+
def extract_file_id(message: Message) -> Union[None, str]:
|
18
18
|
possible_attrs = ("video", "photo", "audio", "animation", "document")
|
19
19
|
for attr in possible_attrs:
|
20
20
|
if hasattr(message, attr):
|
@@ -73,10 +73,17 @@ class Utils(object):
|
|
73
73
|
return (seq[pos:pos + size] for pos in range(0, len(seq), size))
|
74
74
|
|
75
75
|
@staticmethod
|
76
|
-
def extract_message_text(message:
|
76
|
+
def extract_message_text(message: Message) -> str:
|
77
77
|
if hasattr(message, "text") and message.text:
|
78
78
|
return message.text
|
79
79
|
if hasattr(message, "caption") and message.caption:
|
80
80
|
return message.caption
|
81
81
|
|
82
82
|
return ''
|
83
|
+
|
84
|
+
@staticmethod
|
85
|
+
def extract_message_author(message: Message) -> str:
|
86
|
+
if message.from_user:
|
87
|
+
return message.from_user
|
88
|
+
return ''
|
89
|
+
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: warp_beacon
|
3
|
-
Version: 2.0
|
3
|
+
Version: 2.1.0
|
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
|
@@ -230,7 +230,7 @@ Requires-Dist: tgcrypto
|
|
230
230
|
Requires-Dist: pyrogram
|
231
231
|
Requires-Dist: pillow-heif
|
232
232
|
Requires-Dist: pytubefix
|
233
|
-
Requires-Dist: av
|
233
|
+
Requires-Dist: av ==12.3.0
|
234
234
|
Requires-Dist: urlextract
|
235
235
|
Requires-Dist: pillow
|
236
236
|
Requires-Dist: pymongo
|
@@ -0,0 +1,43 @@
|
|
1
|
+
etc/warp_beacon/warp_beacon.conf,sha256=pEhCjw6VBCN4c61rTJ4RwOKxAggpy0VvTcgObXFZpaI,318
|
2
|
+
lib/systemd/system/warp_beacon.service,sha256=lPmHqLqcI2eIV7nwHS0qcALQrznixqJuwwPfa2mDLUA,372
|
3
|
+
var/warp_beacon/accounts.json,sha256=rKFQM_b9eoDS4mJ1B_SZNolPLXx1SQdQMdY2F_ZcBt8,1523
|
4
|
+
var/warp_beacon/placeholder.gif,sha256=cE5CGJVaop4Sx21zx6j4AyoHU0ncmvQuS2o6hJfEH88,6064
|
5
|
+
warp_beacon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
+
warp_beacon/__version__.py,sha256=VU4ruV9BcMZdraS-VA1wk8B9lrPoExMsmOPqnbRqNdw,23
|
7
|
+
warp_beacon/warp_beacon.py,sha256=7KEtZDj-pdhtl6m-zFLsSojs1ZR4o7L0xbqtdmYPvfE,342
|
8
|
+
warp_beacon/compress/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
+
warp_beacon/compress/video.py,sha256=_PDMVYCyzLYxHv1uZmmzGcG_8rjaZr7BTXsXTTy_oS4,2846
|
10
|
+
warp_beacon/jobs/__init__.py,sha256=ED8_tPle4iL4kqNW0apAVkgNQtRRTnYfAJwBjO1g0JY,180
|
11
|
+
warp_beacon/jobs/abstract.py,sha256=fXdYgJcCJhsuxf_4nXzsA9cZt_dGbTr-tjvbzs_m_vQ,2631
|
12
|
+
warp_beacon/jobs/download_job.py,sha256=5HiPcnJppFMhO14___3eSkoMygM3y-vhpGkMAuNhK7s,854
|
13
|
+
warp_beacon/jobs/types.py,sha256=Ae8zINgbs7cOcYkYoOCOACA7duyhnIGMQAJ_SJB1QRQ,176
|
14
|
+
warp_beacon/jobs/upload_job.py,sha256=_ul4psPej1jLEs-BMcMR80GbXDSmm38jE9yoZtecclY,741
|
15
|
+
warp_beacon/mediainfo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
|
+
warp_beacon/mediainfo/abstract.py,sha256=ZR2JMuRpoh7nDNov9a8YkAfr6BI2HXnXzQtVrLgDxjs,1185
|
17
|
+
warp_beacon/mediainfo/audio.py,sha256=ous88kwQj4bDIChN5wnGil5LqTs0IQHH0d-nyrL0-ZM,651
|
18
|
+
warp_beacon/mediainfo/silencer.py,sha256=MgUc9Ibbhjhg9GbJMNfJqrdDkMsQShZkQ1sCwvW_-qI,1647
|
19
|
+
warp_beacon/mediainfo/video.py,sha256=AIRy_op_BvehsjarM1rvT5Qo0QWwf-Q6xVVd_aCnbJ4,2505
|
20
|
+
warp_beacon/scraper/__init__.py,sha256=QZOyrPh5PTgFh70Qqh629RPWi4Ed-kNipiwsSlS7Mnk,12791
|
21
|
+
warp_beacon/scraper/abstract.py,sha256=aNZ9ypF9B8BjflcIwi-7wEzIqF-XPeF0xvfX9CP_iIw,2708
|
22
|
+
warp_beacon/scraper/account_selector.py,sha256=SbDO7W5WUCmMDokhuSx0yfEQiu5TW9UumwScGHsaGk8,2633
|
23
|
+
warp_beacon/scraper/exceptions.py,sha256=5A1viJm8sT_ZcI1IccAxQ9O_eYpJNHt3TmYLM0P22Q8,1238
|
24
|
+
warp_beacon/scraper/instagram/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
25
|
+
warp_beacon/scraper/instagram/instagram.py,sha256=DfPbm8Eef72FMKR1s7cb5w9nvmHroNZI2Y950eQ6vTY,13321
|
26
|
+
warp_beacon/scraper/youtube/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
27
|
+
warp_beacon/scraper/youtube/abstract.py,sha256=Fp_NL8Y5j5Tk9_d5QDfkCQZva7I0JC_1B26RhorMjtw,6065
|
28
|
+
warp_beacon/scraper/youtube/music.py,sha256=8TguqXc1BC9Slp09BzCU8dtQ6BnyKwzZSLLFHpqDHtg,1498
|
29
|
+
warp_beacon/scraper/youtube/shorts.py,sha256=pHfvEBau8Zp7Ar3LBuPmjqYq8fmjJUQvzeZXujQMpG4,1203
|
30
|
+
warp_beacon/scraper/youtube/youtube.py,sha256=K98n2TSJaDZt-xT7mADZL1UEf2exIYm0Wnenn2GAYfI,2250
|
31
|
+
warp_beacon/storage/__init__.py,sha256=8XsJXq9X7GDlTaWREF4W1PDX9PH5utwhjf5c5M8Bb7o,3378
|
32
|
+
warp_beacon/telegram/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
33
|
+
warp_beacon/telegram/bot.py,sha256=Y3QT5iIBdM6ILgN832asCbzr2K0v0hYK7IxwV7aC48M,13100
|
34
|
+
warp_beacon/telegram/handlers.py,sha256=MTcHZmWe8RAcZdicnqQewy_SkwujhnaoqJgWHpebfVs,6350
|
35
|
+
warp_beacon/telegram/placeholder_message.py,sha256=u5kVfTjGmVYkwA5opniRltHXGpsdSxI41WEde8J5os0,6418
|
36
|
+
warp_beacon/telegram/utils.py,sha256=LdCU4ChJHyzpCvyG5v3XcUtUgK3v5by_v8D56VsPeI0,2171
|
37
|
+
warp_beacon/uploader/__init__.py,sha256=qODBuIvWtypQQyKl3Y0QiBXC5SipVFL4b64D91EmTyw,4764
|
38
|
+
warp_beacon-2.1.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
39
|
+
warp_beacon-2.1.0.dist-info/METADATA,sha256=4Qi56y5hptlTQyAyqngag-ViMEFAOmeNOupii3hAqLI,21250
|
40
|
+
warp_beacon-2.1.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
41
|
+
warp_beacon-2.1.0.dist-info/entry_points.txt,sha256=eSB61Rb89d56WY0O-vEIQwkn18J-4CMrJcLA_R_8h3g,119
|
42
|
+
warp_beacon-2.1.0.dist-info/top_level.txt,sha256=SIMha34qjGXaijMgtzESgQ-Cu-KbLHbmQacwue_ggTY,917
|
43
|
+
warp_beacon-2.1.0.dist-info/RECORD,,
|
@@ -13,8 +13,10 @@ warp_beacon/mediainfo/silencer
|
|
13
13
|
warp_beacon/mediainfo/video
|
14
14
|
warp_beacon/scraper
|
15
15
|
warp_beacon/scraper/abstract
|
16
|
+
warp_beacon/scraper/account_selector
|
16
17
|
warp_beacon/scraper/exceptions
|
17
18
|
warp_beacon/scraper/instagram
|
19
|
+
warp_beacon/scraper/instagram/instagram
|
18
20
|
warp_beacon/scraper/types
|
19
21
|
warp_beacon/scraper/youtube
|
20
22
|
warp_beacon/scraper/youtube/abstract
|
@@ -1,40 +0,0 @@
|
|
1
|
-
etc/warp_beacon/warp_beacon.conf,sha256=-QyHfsyo-1Ukxlsz3KSE2vKvLCRm5NtA36_jmfqSAsI,307
|
2
|
-
lib/systemd/system/warp_beacon.service,sha256=lPmHqLqcI2eIV7nwHS0qcALQrznixqJuwwPfa2mDLUA,372
|
3
|
-
var/warp_beacon/placeholder.gif,sha256=cE5CGJVaop4Sx21zx6j4AyoHU0ncmvQuS2o6hJfEH88,6064
|
4
|
-
warp_beacon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
warp_beacon/__version__.py,sha256=vUAwymcHllnRpWKcmq_zCPb9Ro_Z8h2NwtbWgfkmsVs,24
|
6
|
-
warp_beacon/warp_beacon.py,sha256=7KEtZDj-pdhtl6m-zFLsSojs1ZR4o7L0xbqtdmYPvfE,342
|
7
|
-
warp_beacon/compress/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
-
warp_beacon/compress/video.py,sha256=_PDMVYCyzLYxHv1uZmmzGcG_8rjaZr7BTXsXTTy_oS4,2846
|
9
|
-
warp_beacon/jobs/__init__.py,sha256=ED8_tPle4iL4kqNW0apAVkgNQtRRTnYfAJwBjO1g0JY,180
|
10
|
-
warp_beacon/jobs/abstract.py,sha256=NCwsogk3-m8F6a-SeiY4CEdTgqJn2V_MU4H7_iYuF7k,2463
|
11
|
-
warp_beacon/jobs/download_job.py,sha256=5HiPcnJppFMhO14___3eSkoMygM3y-vhpGkMAuNhK7s,854
|
12
|
-
warp_beacon/jobs/types.py,sha256=Ae8zINgbs7cOcYkYoOCOACA7duyhnIGMQAJ_SJB1QRQ,176
|
13
|
-
warp_beacon/jobs/upload_job.py,sha256=_ul4psPej1jLEs-BMcMR80GbXDSmm38jE9yoZtecclY,741
|
14
|
-
warp_beacon/mediainfo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
-
warp_beacon/mediainfo/abstract.py,sha256=ZR2JMuRpoh7nDNov9a8YkAfr6BI2HXnXzQtVrLgDxjs,1185
|
16
|
-
warp_beacon/mediainfo/audio.py,sha256=ous88kwQj4bDIChN5wnGil5LqTs0IQHH0d-nyrL0-ZM,651
|
17
|
-
warp_beacon/mediainfo/silencer.py,sha256=MgUc9Ibbhjhg9GbJMNfJqrdDkMsQShZkQ1sCwvW_-qI,1647
|
18
|
-
warp_beacon/mediainfo/video.py,sha256=AIRy_op_BvehsjarM1rvT5Qo0QWwf-Q6xVVd_aCnbJ4,2505
|
19
|
-
warp_beacon/scraper/__init__.py,sha256=3pwcnYp4fdGG_zdnKGryfbtKJWHOZXuUOTSiCB3mvdQ,11049
|
20
|
-
warp_beacon/scraper/abstract.py,sha256=-yMCEW2JUQWUVs4TbiUi6PEw8RyRuTvrJRIUeEpASP0,1723
|
21
|
-
warp_beacon/scraper/exceptions.py,sha256=j3sUi3LT3l-mqW8X6sNSpmx5JIioFODWlOZW8I9RJHA,1191
|
22
|
-
warp_beacon/scraper/instagram.py,sha256=SHyyNvy5dIPhKllIBEvUhM6yq2zeOw4V6r1cvP69NwU,8580
|
23
|
-
warp_beacon/scraper/youtube/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
24
|
-
warp_beacon/scraper/youtube/abstract.py,sha256=ZbCaYAsGowGsrbJDY39Yk_JAhETncw9aE9GYA9sPXTA,5940
|
25
|
-
warp_beacon/scraper/youtube/music.py,sha256=Cf7QNgers2zo-cQRxi__NPz7V8ZoWCC-g9b8yZNfJ4E,1529
|
26
|
-
warp_beacon/scraper/youtube/shorts.py,sha256=vMEshUBtFwchwp_W2uUoPKXwgll2wFgKdIfvSFKXxLE,1234
|
27
|
-
warp_beacon/scraper/youtube/youtube.py,sha256=TmKMdGbhORTBcBYB6V4ItvWKnxWtUfMbXhomonsmAso,2280
|
28
|
-
warp_beacon/storage/__init__.py,sha256=8XsJXq9X7GDlTaWREF4W1PDX9PH5utwhjf5c5M8Bb7o,3378
|
29
|
-
warp_beacon/telegram/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
|
-
warp_beacon/telegram/bot.py,sha256=HGsc1H_CxiE0GBxELqATpSWIjahwjU8FuH0geQOruAI,13086
|
31
|
-
warp_beacon/telegram/handlers.py,sha256=MTcHZmWe8RAcZdicnqQewy_SkwujhnaoqJgWHpebfVs,6350
|
32
|
-
warp_beacon/telegram/placeholder_message.py,sha256=u5kVfTjGmVYkwA5opniRltHXGpsdSxI41WEde8J5os0,6418
|
33
|
-
warp_beacon/telegram/utils.py,sha256=1tm_DH1F2snDxSqwZnKD4ijvTrobv_kscgt3w-bWa6g,2027
|
34
|
-
warp_beacon/uploader/__init__.py,sha256=qODBuIvWtypQQyKl3Y0QiBXC5SipVFL4b64D91EmTyw,4764
|
35
|
-
warp_beacon-2.0.10.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
36
|
-
warp_beacon-2.0.10.dist-info/METADATA,sha256=nsFNDX6H3CtUbCBnDu5E2Wlec9v4FePl6mSQMqp_544,21242
|
37
|
-
warp_beacon-2.0.10.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
|
38
|
-
warp_beacon-2.0.10.dist-info/entry_points.txt,sha256=eSB61Rb89d56WY0O-vEIQwkn18J-4CMrJcLA_R_8h3g,119
|
39
|
-
warp_beacon-2.0.10.dist-info/top_level.txt,sha256=pu6xG8OO_nCGllnOfAZ6QpVfivtmHVxPlYK8SZzUDqA,840
|
40
|
-
warp_beacon-2.0.10.dist-info/RECORD,,
|
File without changes
|
File without changes
|