warp-beacon 1.0.4__py3-none-any.whl → 1.0.5__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.5"
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,158 @@ 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
192
  args["video"] = job.tg_file_id.replace(":video", '')
69
193
  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"]
194
+ args["media"] = InputMediaVideo(
195
+ media=open(job.local_media_path, 'rb'),
196
+ supports_streaming=True,
197
+ width=job.media_info["width"],
198
+ height=job.media_info["height"],
199
+ duration=int(job.media_info["duration"]),
200
+ thumbnail=job.media_info["thumb"]
201
+ )
76
202
  elif job.media_type == "image":
77
203
  if job.tg_file_id:
78
204
  args["photo"] = job.tg_file_id.replace(":image", '')
79
205
  else:
80
- args["photo"] = open(job.local_media_path, 'rb')
206
+ #args["photo"] = open(job.local_media_path, 'rb')
207
+ args["media"] = InputMediaPhoto(
208
+ media=open(job.local_media_path, 'rb')
209
+ )
81
210
  elif job.media_type == "collection":
82
211
  if job.tg_file_id:
83
212
  args["media"] = []
@@ -110,12 +239,17 @@ def build_tg_args(job: UploadJob) -> dict:
110
239
  args["media"] = mediafs
111
240
 
112
241
  # common args
113
- args["disable_notification"] = True
242
+ if job.placeholder_message_id and job.media_type != "collection":
243
+ args["message_id"] = job.placeholder_message_id
244
+ args["chat_id"] = update.message.chat_id
245
+ else:
246
+ args["disable_notification"] = True
247
+ args["reply_to_message_id"] = job.message_id
114
248
  args["write_timeout"] = timeout
115
249
  args["read_timeout"] = timeout
116
250
  args["connect_timeout"] = timeout
117
- args["reply_to_message_id"] = job.message_id
118
-
251
+ if os.environ.get("ENABLE_DONATES", None) == "true" and job.media_type != "collection":
252
+ args["reply_markup"] = InlineKeyboardMarkup([[InlineKeyboardButton("❤ Donate", url=os.environ.get("DONATE_LINK", "https://pay.cryptocloud.plus/pos/W5BMtNQt5bJFoW2E"))]])
119
253
  return args
120
254
 
121
255
  async def upload_job(update: Update, context: ContextTypes.DEFAULT_TYPE, job: UploadJob) -> list[str]:
@@ -126,17 +260,26 @@ async def upload_job(update: Update, context: ContextTypes.DEFAULT_TYPE, job: Up
126
260
  max_retries = int(os.environ.get("TG_MAX_RETRIES", default=5))
127
261
  while not retry_amount >= max_retries:
128
262
  try:
263
+ message = None
129
264
  if job.media_type == "video":
130
- message = await update.message.reply_video(**build_tg_args(job))
265
+ if job.placeholder_message_id:
266
+ message = await context.bot.edit_message_media(**build_tg_args(update, context, job))
267
+ else:
268
+ message = await update.message.reply_video(**build_tg_args(update, context, job))
131
269
  tg_file_ids.append(message.video.file_id)
132
270
  job.tg_file_id = message.video.file_id
133
271
  elif job.media_type == "image":
134
- message = await update.message.reply_photo(**build_tg_args(job))
272
+ if job.placeholder_message_id:
273
+ message = await context.bot.edit_message_media(**build_tg_args(update, context, job))
274
+ else:
275
+ message = await update.message.reply_photo(**build_tg_args(update, context, job))
135
276
  if message.photo:
136
277
  tg_file_ids.append(message.photo[-1].file_id)
137
278
  job.tg_file_id = message.photo[-1].file_id
138
279
  elif job.media_type == "collection":
139
- sent_messages = await update.message.reply_media_group(**build_tg_args(job))
280
+ sent_messages = await update.message.reply_media_group(**build_tg_args(update, context, job))
281
+ if job.placeholder_message_id:
282
+ await remove_placeholder(update, context, job.placeholder_message_id)
140
283
  for i, msg in enumerate(sent_messages):
141
284
  if msg.video:
142
285
  tg_file_ids.append(msg.video.file_id + ':video')
@@ -151,6 +294,7 @@ async def upload_job(update: Update, context: ContextTypes.DEFAULT_TYPE, job: Up
151
294
  except error.TimedOut as e:
152
295
  logging.error("TG timeout error!")
153
296
  logging.exception(e)
297
+ await remove_placeholder(update, context, job.placeholder_message_id)
154
298
  await send_text(
155
299
  update,
156
300
  context,
@@ -175,6 +319,7 @@ async def upload_job(update: Update, context: ContextTypes.DEFAULT_TYPE, job: Up
175
319
  msg = "Telegram error: %s" % str(e.message)
176
320
  else:
177
321
  msg = "Unfortunately, Telegram limits were exceeded. Your video size is %.2f MB." % job.media_info["filesize"]
322
+ await remove_placeholder(update, context, job.placeholder_message_id)
178
323
  await send_text(
179
324
  update,
180
325
  context,
@@ -249,6 +394,8 @@ async def handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
249
394
  async def upload_wrapper(job: UploadJob) -> None:
250
395
  try:
251
396
  if job.job_failed and job.job_failed_msg:
397
+ if job.placeholder_message_id:
398
+ await remove_placeholder(update, context, job.placeholder_message_id)
252
399
  return await send_text(update, context, reply_id=job.message_id, text=job.job_failed_msg)
253
400
  tg_file_ids = await upload_job(update, context, job)
254
401
  if tg_file_ids:
@@ -264,11 +411,32 @@ async def handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
264
411
  uploader.process_done(job.uniq_id)
265
412
  uploader.remove_callback(job.message_id)
266
413
 
267
- uploader.add_callback(effective_message_id, upload_wrapper, update, context)
268
-
269
414
  try:
415
+ # create placeholder message for long download
416
+ placeholder_message_id = await create_placeholder_message(
417
+ update,
418
+ context,
419
+ reply_id=effective_message_id
420
+ )
421
+
422
+ if not placeholder_message_id:
423
+ await send_text(
424
+ update=update,
425
+ context=context,
426
+ reply_id=effective_message_id,
427
+ text="Failed to create message placeholder. Please check your bot Internet connection.")
428
+ return
429
+
430
+ uploader.add_callback(
431
+ placeholder_message_id,
432
+ upload_wrapper,
433
+ update,
434
+ context
435
+ )
436
+
270
437
  downloader.queue_task(DownloadJob.build(
271
438
  url=url,
439
+ placeholder_message_id=placeholder_message_id,
272
440
  message_id=effective_message_id,
273
441
  in_process=uploader.is_inprocess(uniq_id),
274
442
  uniq_id=uniq_id
@@ -291,11 +459,7 @@ def main() -> None:
291
459
  global uploader, downloader
292
460
 
293
461
  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
-
462
+
299
463
  uploader = AsyncUploader(
300
464
  storage=storage,
301
465
  pool_size=int(os.environ.get("UPLOAD_POOL_SIZE", default=warp_beacon.scrapler.CONST_CPU_COUNT)),
@@ -308,6 +472,11 @@ def main() -> None:
308
472
  downloader.start()
309
473
  uploader.start()
310
474
 
475
+ stop_signals = (signal.SIGINT, signal.SIGTERM, signal.SIGABRT)
476
+ for sig in stop_signals or []:
477
+ loop.add_signal_handler(sig, _raise_system_exit)
478
+ loop.add_signal_handler(sig, _raise_system_exit)
479
+
311
480
  # Create the Application and pass it your bot's token.
312
481
  tg_token = os.environ.get("TG_TOKEN", default=None)
313
482
  application = Application.builder().token(tg_token).concurrent_updates(True).build()
@@ -321,38 +490,47 @@ def main() -> None:
321
490
  application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handler))
322
491
 
323
492
  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:
493
+ while allow_loop:
342
494
  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))
495
+ loop.run_until_complete(application.initialize())
496
+ if application.post_init:
497
+ loop.run_until_complete(application.post_init(application))
498
+ loop.run_until_complete(application.updater.start_polling())
499
+ loop.run_until_complete(application.start())
500
+ while allow_loop:
501
+ try:
502
+ loop.run_forever()
503
+ except (KeyboardInterrupt, SystemExit) as e:
504
+ allow_loop = False
505
+ raise e
506
+ except Exception as e:
507
+ logging.error("Main loop Telegram error!")
508
+ logging.exception(e)
509
+ except (KeyboardInterrupt, SystemExit):
510
+ logging.debug("Application received stop signal. Shutting down.")
511
+ except telegram.error.TimedOut as e:
512
+ logging.error("Telegram connection timeout!")
513
+ logging.exception(e)
514
+ time.sleep(2)
515
+ continue
516
+ except Exception as e:
517
+ logging.error("Failed to start application!")
518
+ logging.exception(e)
352
519
  finally:
353
- loop.close()
354
- downloader.stop_all()
355
- uploader.stop_all()
520
+ try:
521
+ if application.updater.running: # type: ignore[union-attr]
522
+ loop.run_until_complete(application.updater.stop()) # type: ignore[union-attr]
523
+ if application.running:
524
+ loop.run_until_complete(application.stop())
525
+ if application.post_stop:
526
+ loop.run_until_complete(application.post_stop(application))
527
+ loop.run_until_complete(application.shutdown())
528
+ if application.post_shutdown:
529
+ loop.run_until_complete(application.post_shutdown(application))
530
+ finally:
531
+ downloader.stop_all()
532
+ uploader.stop_all()
533
+ loop.close()
356
534
  except Exception as e:
357
535
  logging.exception(e)
358
536
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: warp_beacon
3
- Version: 1.0.4
3
+ Version: 1.0.5
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=y6ueQGn6G9pdtgVve2F9iTIrwB6KvULRXFQlYadkN3E,23
6
+ warp_beacon/warp_beacon.py,sha256=bCdK_3X4ChVg6_v40LknUucwHnhOeVUG0ryv9004fjM,18395
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.5.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
19
+ warp_beacon-1.0.5.dist-info/METADATA,sha256=2u_7W0WcB9rjloAaVGGq95KyazK0zXn_eeeMskyI5w4,18201
20
+ warp_beacon-1.0.5.dist-info/WHEEL,sha256=-oYQCr74JF3a37z2nRlQays_SX2MqOANoqVjBBAP2yE,91
21
+ warp_beacon-1.0.5.dist-info/entry_points.txt,sha256=eSB61Rb89d56WY0O-vEIQwkn18J-4CMrJcLA_R_8h3g,119
22
+ warp_beacon-1.0.5.dist-info/top_level.txt,sha256=VZcz1AU0_EdW3t-74hnT1eazFqSyWgGdF5divvARwmM,334
23
+ warp_beacon-1.0.5.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