warp-beacon 1.0.4__py3-none-any.whl → 1.0.6__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.
Binary file
@@ -1,2 +1,2 @@
1
- __version__ = "1.0.4"
1
+ __version__ = "1.0.6"
2
2
 
@@ -6,6 +6,7 @@ import uuid
6
6
  class JobSettings(TypedDict):
7
7
  job_id: uuid.UUID
8
8
  message_id: int
9
+ placeholder_message_id: int
9
10
  local_media_path: str
10
11
  media_info: dict
11
12
  url: str
@@ -22,6 +23,7 @@ class JobSettings(TypedDict):
22
23
  class AbstractJob(ABC):
23
24
  job_id: uuid.UUID = None
24
25
  message_id: int = 0
26
+ placeholder_message_id: int = 0
25
27
  local_media_path: str = ""
26
28
  media_info: dict = {}
27
29
  url: str = ""
@@ -152,7 +152,7 @@ class AsyncDownloader(object):
152
152
  if proc.is_alive():
153
153
  logging.info("stopping process #%d", proc.pid)
154
154
  proc.terminate()
155
- proc.join()
155
+ #proc.join()
156
156
  logging.info("process #%d stopped", proc.pid)
157
157
  self.workers.clear()
158
158
 
@@ -50,8 +50,12 @@ class AsyncUploader(object):
50
50
 
51
51
  def stop_all(self) -> None:
52
52
  self.allow_loop = False
53
- for i in self.threads:
54
- i.join()
53
+ if self.threads:
54
+ for i in self.threads:
55
+ t_id = i.native_id
56
+ logging.info("Stopping thread #'%s'", t_id)
57
+ i.join()
58
+ logging.info("Thread #'%s' stopped", t_id)
55
59
  self.threads.clear()
56
60
 
57
61
  def is_inprocess(self, uniq_id: str) -> bool:
@@ -80,7 +84,7 @@ class AsyncUploader(object):
80
84
  path = job.local_media_path
81
85
  in_process = job.in_process
82
86
  uniq_id = job.uniq_id
83
- message_id = job.message_id
87
+ message_id = job.placeholder_message_id
84
88
  if not in_process:
85
89
  logging.info("Accepted upload job, file(s): '%s'", path)
86
90
  try:
@@ -4,12 +4,16 @@
4
4
  import os
5
5
  import signal
6
6
  import asyncio
7
+ import time
8
+ from io import BytesIO
7
9
  import logging
8
10
 
9
11
  from urlextract import URLExtract
10
12
 
11
- from telegram import ForceReply, Update, Chat, error, InputMediaVideo, InputMediaPhoto
13
+ import telegram
14
+ from telegram import Bot, ForceReply, Update, Chat, error, InputMediaVideo, InputMediaPhoto, MessageEntity, InlineKeyboardMarkup, InlineKeyboardButton
12
15
  from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters
16
+ from telegram.constants import ParseMode
13
17
 
14
18
  import warp_beacon.scrapler
15
19
  from warp_beacon.storage import Storage
@@ -28,6 +32,7 @@ logger = logging.getLogger(__name__)
28
32
  storage = Storage()
29
33
  uploader = None
30
34
  downloader = None
35
+ placeholder = ("animation", None)
31
36
 
32
37
  # Define a few command handlers. These usually take the two arguments update and
33
38
  # context.
@@ -50,34 +55,164 @@ async def random(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
50
55
  return
51
56
  await upload_job(update, context, UploadJob(tg_file_id=d["tg_file_id"], media_type=d["media_type"], message_id=update.message.message_id))
52
57
 
53
- async def send_text(update: Update, context: ContextTypes.DEFAULT_TYPE, reply_id: int, text: str) -> None:
58
+ async def remove_placeholder(update: Update, context: ContextTypes.DEFAULT_TYPE, placeholder_message_id: int) -> None:
54
59
  try:
55
- await update.message.reply_text(
60
+ timeout = int(os.environ.get("TG_WRITE_TIMEOUT", default=120))
61
+ deleted_message = await context.bot.delete_message(
62
+ chat_id=update.message.chat_id,
63
+ message_id=placeholder_message_id,
64
+ write_timeout=timeout,
65
+ read_timeout=timeout,
66
+ connect_timeout=timeout
67
+ )
68
+ except Exception as e:
69
+ logging.error("Failed to remove placeholder message!")
70
+ logging.exception(e)
71
+
72
+ async def send_text(update: Update, context: ContextTypes.DEFAULT_TYPE, reply_id: int, text: str) -> int:
73
+ try:
74
+ reply = await update.message.reply_text(
56
75
  text,
57
76
  reply_to_message_id=reply_id
58
77
  )
78
+
79
+ return reply.message_id
59
80
  except Exception as e:
60
81
  logging.error("Failed to send text message!")
61
82
  logging.exception(e)
62
83
 
63
- def build_tg_args(job: UploadJob) -> dict:
84
+ return 0
85
+
86
+ def create_default_placeholder_img(text: str, width: int = 800, height: int = 1280) -> BytesIO:
87
+ from PIL import Image, ImageDraw, ImageFont
88
+ bio = BytesIO()
89
+ bio.name = 'placeholder.png'
90
+ img = Image.new("RGB", (width, height), (255, 255, 255))
91
+ draw = ImageDraw.Draw(img)
92
+ font = ImageFont.load_default(size=48)
93
+ _, _, w, h = draw.textbbox((0, 0), text, font=font)
94
+ draw.text(((width-w)/2, (height-h)/2), text, font=font, fill="#000")
95
+ img.save(bio, 'PNG')
96
+ bio.seek(0)
97
+ return bio
98
+
99
+ def extract_file_id(message: "Message") -> str:
100
+ if message.animation:
101
+ return message.animation.file_id
102
+ if message.photo:
103
+ return message.photo[-1].file_id
104
+ if message.document:
105
+ return message.document.file_id
106
+ if message.video:
107
+ return message.video.file_id
108
+
109
+ return None
110
+
111
+ async def create_placeholder_message(update: Update, context: ContextTypes.DEFAULT_TYPE, reply_id: int) -> int:
112
+ global placeholder
113
+ retry_amount = 0
114
+ max_retries = int(os.environ.get("TG_MAX_RETRIES", default=5))
115
+ while not retry_amount >= max_retries:
116
+ try:
117
+ text = "*Loading, this may take a moment \.\.\.* ⏱️ "
118
+ reply = None
119
+ if placeholder[1] is None:
120
+ ph_found = False
121
+ for ph in ('/var/warp_beacon/placeholder.gif', "%s/placeholder.gif" % os.path.dirname(os.path.abspath(warp_beacon.__file__))):
122
+ if not os.path.exists(ph):
123
+ continue
124
+ reply = await update.message.reply_animation(
125
+ caption=text,
126
+ parse_mode="MarkdownV2",
127
+ show_caption_above_media=True,
128
+ animation=open(ph, 'rb'),
129
+ reply_to_message_id=reply_id
130
+ )
131
+ placeholder = ("animation", extract_file_id(reply))
132
+ ph_found = True
133
+ if not ph_found:
134
+ try:
135
+ reply = await update.message.reply_animation(
136
+ caption=text,
137
+ parse_mode="MarkdownV2",
138
+ show_caption_above_media=True,
139
+ animation="https://bagrintsev.me/warp_beacon/placeholder_that_we_deserve.mp4",
140
+ reply_to_message_id=reply_id
141
+ )
142
+ placeholder = ("animation", extract_file_id(reply))
143
+ except Exception as e:
144
+ logging.error("Failed to download secret placeholder!")
145
+ logging.exception(e)
146
+ img = create_default_placeholder_img("Loading, this may take a moment ...")
147
+ reply = await update.message.reply_photo(
148
+ caption=text,
149
+ show_caption_above_media=True,
150
+ parse_mode="MarkdownV2",
151
+ filename="placeholder.png",
152
+ photo=img,
153
+ reply_to_message_id=reply_id
154
+ )
155
+ placeholder = ("photo", extract_file_id(reply))
156
+ else:
157
+ if placeholder[0] == "animation":
158
+ reply = await update.message.reply_animation(
159
+ caption=text,
160
+ parse_mode="MarkdownV2",
161
+ show_caption_above_media=True,
162
+ animation=placeholder[1],
163
+ reply_to_message_id=reply_id
164
+ )
165
+ else:
166
+ reply = await update.message.reply_photo(
167
+ caption=text,
168
+ parse_mode="MarkdownV2",
169
+ show_caption_above_media=True,
170
+ photo=placeholder[1],
171
+ reply_to_message_id=reply_id
172
+ )
173
+ return reply.message_id
174
+ except telegram.error.RetryAfter as e:
175
+ logging.warning("RetryAfter exception!")
176
+ logging.exception(e)
177
+ await send_text(update, context, None, "Telegram error: %s" % e.message)
178
+ time.sleep(e.retry_after)
179
+ except Exception as e:
180
+ logging.error("Failed to create placeholder message!")
181
+ logging.exception(e)
182
+ retry_amount += 1
183
+ time.sleep(2)
184
+
185
+ return 0
186
+
187
+ def build_tg_args(update: Update, context: ContextTypes.DEFAULT_TYPE, job: UploadJob) -> dict:
64
188
  args = {}
65
189
  timeout = int(os.environ.get("TG_WRITE_TIMEOUT", default=120))
66
190
  if job.media_type == "video":
67
191
  if job.tg_file_id:
68
- args["video"] = job.tg_file_id.replace(":video", '')
192
+ if job.placeholder_message_id:
193
+ args["media"] = InputMediaVideo(media=job.tg_file_id.replace(":video", ''), supports_streaming=True)
194
+ else:
195
+ args["video"] = job.tg_file_id.replace(":video", '')
69
196
  else:
70
- args["video"] = open(job.local_media_path, 'rb')
71
- args["supports_streaming"] = True
72
- args["duration"] = int(job.media_info["duration"])
73
- args["width"] = job.media_info["width"]
74
- args["height"] = job.media_info["height"]
75
- args["thumbnail"] = job.media_info["thumb"]
197
+ args["media"] = InputMediaVideo(
198
+ media=open(job.local_media_path, 'rb'),
199
+ supports_streaming=True,
200
+ width=job.media_info["width"],
201
+ height=job.media_info["height"],
202
+ duration=int(job.media_info["duration"]),
203
+ thumbnail=job.media_info["thumb"]
204
+ )
76
205
  elif job.media_type == "image":
77
206
  if job.tg_file_id:
78
- args["photo"] = job.tg_file_id.replace(":image", '')
207
+ if job.placeholder_message_id:
208
+ args["media"] = InputMediaPhoto(media=job.tg_file_id.replace(":image", ''))
209
+ else:
210
+ args["photo"] = job.tg_file_id.replace(":image", '')
79
211
  else:
80
- args["photo"] = open(job.local_media_path, 'rb')
212
+ #args["photo"] = open(job.local_media_path, 'rb')
213
+ args["media"] = InputMediaPhoto(
214
+ media=open(job.local_media_path, 'rb')
215
+ )
81
216
  elif job.media_type == "collection":
82
217
  if job.tg_file_id:
83
218
  args["media"] = []
@@ -110,12 +245,17 @@ def build_tg_args(job: UploadJob) -> dict:
110
245
  args["media"] = mediafs
111
246
 
112
247
  # common args
113
- args["disable_notification"] = True
248
+ if job.placeholder_message_id and job.media_type != "collection":
249
+ args["message_id"] = job.placeholder_message_id
250
+ args["chat_id"] = update.message.chat_id
251
+ else:
252
+ args["disable_notification"] = True
253
+ args["reply_to_message_id"] = job.message_id
114
254
  args["write_timeout"] = timeout
115
255
  args["read_timeout"] = timeout
116
256
  args["connect_timeout"] = timeout
117
- args["reply_to_message_id"] = job.message_id
118
-
257
+ if os.environ.get("ENABLE_DONATES", None) == "true" and job.media_type != "collection":
258
+ args["reply_markup"] = InlineKeyboardMarkup([[InlineKeyboardButton("❤ Donate", url=os.environ.get("DONATE_LINK", "https://pay.cryptocloud.plus/pos/W5BMtNQt5bJFoW2E"))]])
119
259
  return args
120
260
 
121
261
  async def upload_job(update: Update, context: ContextTypes.DEFAULT_TYPE, job: UploadJob) -> list[str]:
@@ -126,17 +266,26 @@ async def upload_job(update: Update, context: ContextTypes.DEFAULT_TYPE, job: Up
126
266
  max_retries = int(os.environ.get("TG_MAX_RETRIES", default=5))
127
267
  while not retry_amount >= max_retries:
128
268
  try:
269
+ message = None
129
270
  if job.media_type == "video":
130
- message = await update.message.reply_video(**build_tg_args(job))
271
+ if job.placeholder_message_id:
272
+ message = await context.bot.edit_message_media(**build_tg_args(update, context, job))
273
+ else:
274
+ message = await update.message.reply_video(**build_tg_args(update, context, job))
131
275
  tg_file_ids.append(message.video.file_id)
132
276
  job.tg_file_id = message.video.file_id
133
277
  elif job.media_type == "image":
134
- message = await update.message.reply_photo(**build_tg_args(job))
278
+ if job.placeholder_message_id:
279
+ message = await context.bot.edit_message_media(**build_tg_args(update, context, job))
280
+ else:
281
+ message = await update.message.reply_photo(**build_tg_args(update, context, job))
135
282
  if message.photo:
136
283
  tg_file_ids.append(message.photo[-1].file_id)
137
284
  job.tg_file_id = message.photo[-1].file_id
138
285
  elif job.media_type == "collection":
139
- sent_messages = await update.message.reply_media_group(**build_tg_args(job))
286
+ sent_messages = await update.message.reply_media_group(**build_tg_args(update, context, job))
287
+ if job.placeholder_message_id:
288
+ await remove_placeholder(update, context, job.placeholder_message_id)
140
289
  for i, msg in enumerate(sent_messages):
141
290
  if msg.video:
142
291
  tg_file_ids.append(msg.video.file_id + ':video')
@@ -151,6 +300,7 @@ async def upload_job(update: Update, context: ContextTypes.DEFAULT_TYPE, job: Up
151
300
  except error.TimedOut as e:
152
301
  logging.error("TG timeout error!")
153
302
  logging.exception(e)
303
+ await remove_placeholder(update, context, job.placeholder_message_id)
154
304
  await send_text(
155
305
  update,
156
306
  context,
@@ -175,6 +325,7 @@ async def upload_job(update: Update, context: ContextTypes.DEFAULT_TYPE, job: Up
175
325
  msg = "Telegram error: %s" % str(e.message)
176
326
  else:
177
327
  msg = "Unfortunately, Telegram limits were exceeded. Your video size is %.2f MB." % job.media_info["filesize"]
328
+ await remove_placeholder(update, context, job.placeholder_message_id)
178
329
  await send_text(
179
330
  update,
180
331
  context,
@@ -249,6 +400,8 @@ async def handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
249
400
  async def upload_wrapper(job: UploadJob) -> None:
250
401
  try:
251
402
  if job.job_failed and job.job_failed_msg:
403
+ if job.placeholder_message_id:
404
+ await remove_placeholder(update, context, job.placeholder_message_id)
252
405
  return await send_text(update, context, reply_id=job.message_id, text=job.job_failed_msg)
253
406
  tg_file_ids = await upload_job(update, context, job)
254
407
  if tg_file_ids:
@@ -264,11 +417,32 @@ async def handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
264
417
  uploader.process_done(job.uniq_id)
265
418
  uploader.remove_callback(job.message_id)
266
419
 
267
- uploader.add_callback(effective_message_id, upload_wrapper, update, context)
268
-
269
420
  try:
421
+ # create placeholder message for long download
422
+ placeholder_message_id = await create_placeholder_message(
423
+ update,
424
+ context,
425
+ reply_id=effective_message_id
426
+ )
427
+
428
+ if not placeholder_message_id:
429
+ await send_text(
430
+ update=update,
431
+ context=context,
432
+ reply_id=effective_message_id,
433
+ text="Failed to create message placeholder. Please check your bot Internet connection.")
434
+ return
435
+
436
+ uploader.add_callback(
437
+ placeholder_message_id,
438
+ upload_wrapper,
439
+ update,
440
+ context
441
+ )
442
+
270
443
  downloader.queue_task(DownloadJob.build(
271
444
  url=url,
445
+ placeholder_message_id=placeholder_message_id,
272
446
  message_id=effective_message_id,
273
447
  in_process=uploader.is_inprocess(uniq_id),
274
448
  uniq_id=uniq_id
@@ -291,11 +465,7 @@ def main() -> None:
291
465
  global uploader, downloader
292
466
 
293
467
  loop = asyncio.get_event_loop()
294
- stop_signals = (signal.SIGINT, signal.SIGTERM, signal.SIGABRT)
295
- for sig in stop_signals or []:
296
- loop.add_signal_handler(sig, _raise_system_exit)
297
- loop.add_signal_handler(sig, _raise_system_exit)
298
-
468
+
299
469
  uploader = AsyncUploader(
300
470
  storage=storage,
301
471
  pool_size=int(os.environ.get("UPLOAD_POOL_SIZE", default=warp_beacon.scrapler.CONST_CPU_COUNT)),
@@ -308,6 +478,11 @@ def main() -> None:
308
478
  downloader.start()
309
479
  uploader.start()
310
480
 
481
+ stop_signals = (signal.SIGINT, signal.SIGTERM, signal.SIGABRT)
482
+ for sig in stop_signals or []:
483
+ loop.add_signal_handler(sig, _raise_system_exit)
484
+ loop.add_signal_handler(sig, _raise_system_exit)
485
+
311
486
  # Create the Application and pass it your bot's token.
312
487
  tg_token = os.environ.get("TG_TOKEN", default=None)
313
488
  application = Application.builder().token(tg_token).concurrent_updates(True).build()
@@ -321,38 +496,47 @@ def main() -> None:
321
496
  application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handler))
322
497
 
323
498
  allow_loop = True
324
- try:
325
- loop.run_until_complete(application.initialize())
326
- if application.post_init:
327
- loop.run_until_complete(application.post_init(application))
328
- loop.run_until_complete(application.updater.start_polling())
329
- loop.run_until_complete(application.start())
330
- while allow_loop:
331
- try:
332
- loop.run_forever()
333
- except (KeyboardInterrupt, SystemExit) as e:
334
- allow_loop = False
335
- raise e
336
- except Exception as e:
337
- logging.error("Main loop Telegram error!")
338
- logging.exception(e)
339
- except (KeyboardInterrupt, SystemExit):
340
- logging.debug("Application received stop signal. Shutting down.")
341
- finally:
499
+ while allow_loop:
342
500
  try:
343
- if application.updater.running: # type: ignore[union-attr]
344
- loop.run_until_complete(application.updater.stop()) # type: ignore[union-attr]
345
- if application.running:
346
- loop.run_until_complete(application.stop())
347
- if application.post_stop:
348
- loop.run_until_complete(application.post_stop(application))
349
- loop.run_until_complete(application.shutdown())
350
- if application.post_shutdown:
351
- loop.run_until_complete(application.post_shutdown(application))
501
+ loop.run_until_complete(application.initialize())
502
+ if application.post_init:
503
+ loop.run_until_complete(application.post_init(application))
504
+ loop.run_until_complete(application.updater.start_polling())
505
+ loop.run_until_complete(application.start())
506
+ while allow_loop:
507
+ try:
508
+ loop.run_forever()
509
+ except (KeyboardInterrupt, SystemExit) as e:
510
+ allow_loop = False
511
+ raise e
512
+ except Exception as e:
513
+ logging.error("Main loop Telegram error!")
514
+ logging.exception(e)
515
+ except (KeyboardInterrupt, SystemExit):
516
+ logging.debug("Application received stop signal. Shutting down.")
517
+ except telegram.error.TimedOut as e:
518
+ logging.error("Telegram connection timeout!")
519
+ logging.exception(e)
520
+ time.sleep(2)
521
+ continue
522
+ except Exception as e:
523
+ logging.error("Failed to start application!")
524
+ logging.exception(e)
352
525
  finally:
353
- loop.close()
354
- downloader.stop_all()
355
- uploader.stop_all()
526
+ try:
527
+ if application.updater.running: # type: ignore[union-attr]
528
+ loop.run_until_complete(application.updater.stop()) # type: ignore[union-attr]
529
+ if application.running:
530
+ loop.run_until_complete(application.stop())
531
+ if application.post_stop:
532
+ loop.run_until_complete(application.post_stop(application))
533
+ loop.run_until_complete(application.shutdown())
534
+ if application.post_shutdown:
535
+ loop.run_until_complete(application.post_shutdown(application))
536
+ finally:
537
+ downloader.stop_all()
538
+ uploader.stop_all()
539
+ loop.close()
356
540
  except Exception as e:
357
541
  logging.exception(e)
358
542
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: warp_beacon
3
- Version: 1.0.4
3
+ Version: 1.0.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
@@ -1,22 +1,23 @@
1
1
  etc/warp_beacon/warp_beacon.conf,sha256=De80YgoU2uZ5-v2s3QX3etAGZ4bZxaBqV0d2xk872RQ,240
2
2
  lib/systemd/system/warp_beacon.service,sha256=lPmHqLqcI2eIV7nwHS0qcALQrznixqJuwwPfa2mDLUA,372
3
+ var/warp_beacon/placeholder.gif,sha256=cE5CGJVaop4Sx21zx6j4AyoHU0ncmvQuS2o6hJfEH88,6064
3
4
  warp_beacon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- warp_beacon/__version__.py,sha256=6wBrqLaWadCfrxfgwwZYsCOPDrwUXxTkLxUFuR0WLPo,23
5
- warp_beacon/warp_beacon.py,sha256=7vjHQdrwezQOpw_5I_cDsl8WHOEraX72Ft3gTySRZrg,12191
5
+ warp_beacon/__version__.py,sha256=KunIFMMdy9Pr0jimIawsybWFdVLbq6gm7t27g1kPr0E,23
6
+ warp_beacon/warp_beacon.py,sha256=2wNFO1WFg9_1RXUar5STqSpGBysxBXUBfIGLlOafvvc,18668
6
7
  warp_beacon/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- warp_beacon/jobs/abstract.py,sha256=LxPRt6ojM6lHXKnZ79H1lMSL9vs9H0yUkN4ZWo4Whjk,1428
8
+ warp_beacon/jobs/abstract.py,sha256=8iKHelnddpnKtF-FzNkJ2zDcXrEmBO7q5ZoIV2Qby3o,1490
8
9
  warp_beacon/jobs/download_job.py,sha256=wfZrKUerfYIjWkRxPzfl5gwIlcotIMH7OpTUM9ae8NY,736
9
10
  warp_beacon/jobs/upload_job.py,sha256=Vaogc4vbpAfyaT4VkIHEPLFRELmM44TDqkmnPYh3Ymc,740
10
11
  warp_beacon/mediainfo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
12
  warp_beacon/mediainfo/video.py,sha256=wYPf1_55PW_x6ifSsgXTKVNbTHU_31IumVpUk1ua5dY,2172
12
- warp_beacon/scrapler/__init__.py,sha256=4CKmUOVlQO2_nI47Pa1eQNPGpoqrXSk4JH4EjH-etOk,6099
13
+ warp_beacon/scrapler/__init__.py,sha256=ilydR4N5SPNYTCGqAHEeFkcXvHY466YNk8K-mLivh0Q,6100
13
14
  warp_beacon/scrapler/abstract.py,sha256=MJxpEovCWDYq2SwbbMsRDfp77WTwvbXXKiQxKWoj0ZQ,304
14
15
  warp_beacon/scrapler/instagram.py,sha256=8CF_Zdxn1hStz_PgLxTc0FTt5heI84d-Ks0XzmD7-_o,7248
15
16
  warp_beacon/storage/__init__.py,sha256=NhD3V7UNRiZNf61yQEAjXOfi-tfA2LaJa7a7kvbkmtE,2402
16
- warp_beacon/uploader/__init__.py,sha256=39rXMYel1iqSgpS7QbFU6wk-WKF1f6EL6W3qceNxLhk,3685
17
- warp_beacon-1.0.4.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
18
- warp_beacon-1.0.4.dist-info/METADATA,sha256=6oR7v-3jpNm3zZ30DKvshv01MgaNKCZXBRh5J0tl_9o,18201
19
- warp_beacon-1.0.4.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
20
- warp_beacon-1.0.4.dist-info/entry_points.txt,sha256=eSB61Rb89d56WY0O-vEIQwkn18J-4CMrJcLA_R_8h3g,119
21
- warp_beacon-1.0.4.dist-info/top_level.txt,sha256=VZcz1AU0_EdW3t-74hnT1eazFqSyWgGdF5divvARwmM,334
22
- warp_beacon-1.0.4.dist-info/RECORD,,
17
+ warp_beacon/uploader/__init__.py,sha256=dz4If8kHTp_eWMm6v3Zp9lwZHR8eDNcNQRIooj9eGgc,3836
18
+ warp_beacon-1.0.6.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
19
+ warp_beacon-1.0.6.dist-info/METADATA,sha256=a8hgXjbD2ZzU9JrhlXy95XrX62URZYX2vfVSns1zNBc,18201
20
+ warp_beacon-1.0.6.dist-info/WHEEL,sha256=-oYQCr74JF3a37z2nRlQays_SX2MqOANoqVjBBAP2yE,91
21
+ warp_beacon-1.0.6.dist-info/entry_points.txt,sha256=eSB61Rb89d56WY0O-vEIQwkn18J-4CMrJcLA_R_8h3g,119
22
+ warp_beacon-1.0.6.dist-info/top_level.txt,sha256=VZcz1AU0_EdW3t-74hnT1eazFqSyWgGdF5divvARwmM,334
23
+ warp_beacon-1.0.6.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.3.0)
2
+ Generator: setuptools (71.0.3)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5