pyrobale 0.2.8__py3-none-any.whl → 0.2.8.1__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.
- {pyrobale-0.2.8.dist-info → pyrobale-0.2.8.1.dist-info}/METADATA +1 -1
- pyrobale-0.2.8.1.dist-info/RECORD +5 -0
- pyrobale.py +242 -72
- pyrobale-0.2.8.dist-info/RECORD +0 -5
- {pyrobale-0.2.8.dist-info → pyrobale-0.2.8.1.dist-info}/WHEEL +0 -0
- {pyrobale-0.2.8.dist-info → pyrobale-0.2.8.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,5 @@
|
|
1
|
+
pyrobale.py,sha256=3X2xUh7sdHBCZkp-JcXJ6xL0ggW4BdIcDpmhSR09v7A,89846
|
2
|
+
pyrobale-0.2.8.1.dist-info/METADATA,sha256=np0kt27IkkZVub_7t8_n44e9bKOygIuMNaSV0l0v1co,43670
|
3
|
+
pyrobale-0.2.8.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
4
|
+
pyrobale-0.2.8.1.dist-info/licenses/LICENSE,sha256=XQsvifC5HE85232_XgRHpeQ35MfeL2YcpU4yPiAgmW4,35269
|
5
|
+
pyrobale-0.2.8.1.dist-info/RECORD,,
|
pyrobale.py
CHANGED
@@ -20,8 +20,15 @@ import re
|
|
20
20
|
import sys
|
21
21
|
import os
|
22
22
|
from urllib.parse import urlparse, unquote
|
23
|
+
from io import BytesIO
|
24
|
+
import mimetypes
|
25
|
+
from os import PathLike
|
26
|
+
from pathlib import Path
|
27
|
+
import uuid
|
28
|
+
from typing import Iterator
|
29
|
+
|
30
|
+
__version__ = '0.2.8.1'
|
23
31
|
|
24
|
-
__version__ = '0.2.8'
|
25
32
|
|
26
33
|
class ChatActions:
|
27
34
|
"""Represents different chat action states that can be sent to Bale"""
|
@@ -29,8 +36,10 @@ class ChatActions:
|
|
29
36
|
PHOTO: str = 'upload_photo'
|
30
37
|
VIDEO: str = 'record_video'
|
31
38
|
CHOOSE_STICKER: str = 'choose_sticker'
|
39
|
+
|
40
|
+
|
32
41
|
class conditions:
|
33
|
-
|
42
|
+
|
34
43
|
"""
|
35
44
|
A class for defining conditions for message handling.
|
36
45
|
"""
|
@@ -352,18 +361,24 @@ class LabeledPrice:
|
|
352
361
|
|
353
362
|
class Document:
|
354
363
|
def __init__(self, data: dict):
|
364
|
+
print(data)
|
355
365
|
if data:
|
356
366
|
self.file_id = data.get('file_id')
|
357
367
|
self.file_unique_id = data.get('file_unique_id')
|
358
368
|
self.file_name = data.get('file_name')
|
359
369
|
self.mime_type = data.get('mime_type')
|
360
370
|
self.file_size = data.get('file_size')
|
371
|
+
self.input_file = InputFile(self.file_id)
|
361
372
|
else:
|
362
373
|
self.file_id = None
|
363
374
|
self.file_unique_id = None
|
364
375
|
self.file_name = None
|
365
376
|
self.mime_type = None
|
366
377
|
self.file_size = None
|
378
|
+
self.input_file = None
|
379
|
+
|
380
|
+
def __bool__(self):
|
381
|
+
return bool(self.file_id)
|
367
382
|
|
368
383
|
|
369
384
|
class Invoice:
|
@@ -545,39 +560,130 @@ class InlineKeyboardMarkup:
|
|
545
560
|
return {"inline_keyboard": self.inline_keyboard}
|
546
561
|
|
547
562
|
|
563
|
+
class InputMedia:
|
564
|
+
"""Base class for input media types"""
|
565
|
+
|
566
|
+
def __init__(self, media: str, caption: str = None):
|
567
|
+
self.media = media
|
568
|
+
self.caption = caption
|
569
|
+
|
570
|
+
@property
|
571
|
+
def media_dict(self) -> dict:
|
572
|
+
media_dict = {
|
573
|
+
'media': self.media,
|
574
|
+
'type': self.type
|
575
|
+
}
|
576
|
+
if self.caption:
|
577
|
+
media_dict['caption'] = self.caption
|
578
|
+
return media_dict
|
579
|
+
|
580
|
+
|
581
|
+
class InputMediaPhoto(InputMedia):
|
582
|
+
"""Represents a photo to be sent"""
|
583
|
+
type = 'photo'
|
584
|
+
|
585
|
+
|
586
|
+
class InputMediaVideo(InputMedia):
|
587
|
+
"""Represents a video to be sent"""
|
588
|
+
type = 'video'
|
589
|
+
|
590
|
+
def __init__(self, media: str, caption: str = None, width: int = None,
|
591
|
+
height: int = None, duration: int = None):
|
592
|
+
super().__init__(media, caption)
|
593
|
+
self.width = width
|
594
|
+
self.height = height
|
595
|
+
self.duration = duration
|
596
|
+
|
597
|
+
@property
|
598
|
+
def media_dict(self) -> dict:
|
599
|
+
media_dict = super().media_dict
|
600
|
+
if self.width:
|
601
|
+
media_dict['width'] = self.width
|
602
|
+
if self.height:
|
603
|
+
media_dict['height'] = self.height
|
604
|
+
if self.duration:
|
605
|
+
media_dict['duration'] = self.duration
|
606
|
+
return media_dict
|
607
|
+
|
608
|
+
|
609
|
+
class InputMediaAnimation(InputMedia):
|
610
|
+
"""Represents an animation to be sent"""
|
611
|
+
type = 'animation'
|
612
|
+
|
613
|
+
def __init__(self, media: str, caption: str = None, width: int = None,
|
614
|
+
height: int = None, duration: int = None):
|
615
|
+
super().__init__(media, caption)
|
616
|
+
self.width = width
|
617
|
+
self.height = height
|
618
|
+
self.duration = duration
|
619
|
+
|
620
|
+
@property
|
621
|
+
def media_dict(self) -> dict:
|
622
|
+
media_dict = super().media_dict
|
623
|
+
if self.width:
|
624
|
+
media_dict['width'] = self.width
|
625
|
+
if self.height:
|
626
|
+
media_dict['height'] = self.height
|
627
|
+
if self.duration:
|
628
|
+
media_dict['duration'] = self.duration
|
629
|
+
return media_dict
|
630
|
+
|
631
|
+
|
548
632
|
class InputFile:
|
549
|
-
"""Represents a file to be
|
633
|
+
"""Represents a file to be sent"""
|
550
634
|
|
551
|
-
def __init__(self,
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
self.filename = filename
|
558
|
-
if isinstance(file, str):
|
559
|
-
if file.startswith(('http://', 'https://')):
|
560
|
-
r = requests.get(file)
|
561
|
-
r.raise_for_status()
|
562
|
-
self.file = r.content
|
563
|
-
else:
|
564
|
-
try:
|
565
|
-
self.file = open(file, 'rb')
|
566
|
-
except IOError:
|
567
|
-
raise BaleException(
|
568
|
-
f"Failed to open file: {traceback.format_exc()}")
|
569
|
-
else:
|
570
|
-
self.file = file
|
635
|
+
def __init__(self, file: Union[str, bytes] = None, file_id: str = None):
|
636
|
+
if file and file_id:
|
637
|
+
raise ValueError(
|
638
|
+
"Either file or file_id should be provided, not both")
|
639
|
+
elif not file and not file_id:
|
640
|
+
raise ValueError("Either file or file_id must be provided")
|
571
641
|
|
572
|
-
|
573
|
-
|
574
|
-
|
642
|
+
self.file = file
|
643
|
+
self.file_id = file_id
|
644
|
+
|
645
|
+
@property
|
646
|
+
def file_type(self) -> str:
|
647
|
+
if self.file_id:
|
648
|
+
return "id"
|
649
|
+
if isinstance(self.file, bytes):
|
650
|
+
return "bytes"
|
651
|
+
if self.file.startswith(('http://', 'https://')):
|
652
|
+
return "url"
|
653
|
+
return "path"
|
654
|
+
|
655
|
+
def __str__(self) -> str:
|
656
|
+
if self.file_id:
|
657
|
+
return self.file_id
|
658
|
+
return str(self.file)
|
659
|
+
|
660
|
+
|
661
|
+
class InputMediaAudio(InputMedia):
|
662
|
+
"""Represents an audio file to be sent"""
|
663
|
+
type = 'audio'
|
664
|
+
|
665
|
+
def __init__(self, media: str, caption: str = None, duration: int = None,
|
666
|
+
performer: str = None, title: str = None):
|
667
|
+
super().__init__(media, caption)
|
668
|
+
self.duration = duration
|
669
|
+
self.performer = performer
|
670
|
+
self.title = title
|
575
671
|
|
576
672
|
@property
|
577
|
-
def
|
578
|
-
|
579
|
-
|
580
|
-
|
673
|
+
def media_dict(self) -> dict:
|
674
|
+
media_dict = super().media_dict
|
675
|
+
if self.duration:
|
676
|
+
media_dict['duration'] = self.duration
|
677
|
+
if self.performer:
|
678
|
+
media_dict['performer'] = self.performer
|
679
|
+
if self.title:
|
680
|
+
media_dict['title'] = self.title
|
681
|
+
return media_dict
|
682
|
+
|
683
|
+
|
684
|
+
class InputMediaDocument(InputMedia):
|
685
|
+
"""Represents a document to be sent"""
|
686
|
+
type = 'document'
|
581
687
|
|
582
688
|
|
583
689
|
class CallbackQuery:
|
@@ -857,8 +963,8 @@ class Chat:
|
|
857
963
|
photo_url,
|
858
964
|
reply_to_message,
|
859
965
|
reply_markup)
|
860
|
-
|
861
|
-
def send_action(self, action: str, how_many_times
|
966
|
+
|
967
|
+
def send_action(self, action: str, how_many_times=1) -> bool:
|
862
968
|
"""Send a chat action"""
|
863
969
|
return self.client.send_chat_action(self.id, action, how_many_times)
|
864
970
|
|
@@ -1259,6 +1365,7 @@ class User:
|
|
1259
1365
|
photo_url,
|
1260
1366
|
reply_to_message,
|
1261
1367
|
reply_markup)
|
1368
|
+
|
1262
1369
|
def send_action(self, action: str, how_many_times: int = 1):
|
1263
1370
|
"""Send a chat action to this user"""
|
1264
1371
|
return self.client.send_chat_action(self.id, action, how_many_times)
|
@@ -1286,33 +1393,65 @@ class Message:
|
|
1286
1393
|
'chat', {})})
|
1287
1394
|
self.text = result.get('text')
|
1288
1395
|
self.caption = result.get('caption')
|
1289
|
-
|
1290
|
-
|
1291
|
-
self.
|
1292
|
-
|
1293
|
-
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
1396
|
+
|
1397
|
+
# Media handling
|
1398
|
+
self.document = Document(
|
1399
|
+
result.get(
|
1400
|
+
'document',
|
1401
|
+
{})) if result.get('document') else None
|
1402
|
+
# Handle photo array properly
|
1403
|
+
photos = result.get('photo', [])
|
1404
|
+
self.photo = [Document(photo) for photo in photos] if photos else None
|
1405
|
+
self.largest_photo = Document(photos[-1]) if photos else None
|
1406
|
+
|
1407
|
+
self.video = Document(
|
1408
|
+
result.get('video')) if result.get('video') else None
|
1409
|
+
self.audio = Document(
|
1410
|
+
result.get('audio')) if result.get('audio') else None
|
1411
|
+
self.voice = Voice(
|
1412
|
+
result.get('voice')) if result.get('voice') else None
|
1413
|
+
self.animation = Document(
|
1414
|
+
result.get('animation')) if result.get('animation') else None
|
1415
|
+
self.sticker = Document(
|
1416
|
+
result.get('sticker')) if result.get('sticker') else None
|
1417
|
+
self.video_note = Document(
|
1418
|
+
result.get('video_note')) if result.get('video_note') else None
|
1419
|
+
|
1420
|
+
self.media_group_id = result.get('media_group_id')
|
1421
|
+
self.has_media = any([self.document,
|
1422
|
+
self.photo,
|
1423
|
+
self.video,
|
1424
|
+
self.audio,
|
1425
|
+
self.voice,
|
1426
|
+
self.animation,
|
1427
|
+
self.sticker,
|
1428
|
+
self.video_note])
|
1429
|
+
|
1430
|
+
self.contact = Contact(
|
1431
|
+
result.get('contact')) if result.get('contact') else None
|
1432
|
+
self.location = Location(
|
1433
|
+
result.get('location')) if result.get('location') else None
|
1297
1434
|
self.forward_from = User(client, {'ok': True, 'result': result.get(
|
1298
1435
|
'forward_from', {})}) if result.get('forward_from') else None
|
1299
1436
|
self.forward_from_message_id = result.get('forward_from_message_id')
|
1300
|
-
self.invoice = Invoice(
|
1437
|
+
self.invoice = Invoice(
|
1438
|
+
result.get('invoice')) if result.get('invoice') else None
|
1301
1439
|
self.reply_to_message = Message(client, {'ok': True, 'result': result.get(
|
1302
1440
|
'reply_to_message', {})}) if result.get('reply_to_message') else None
|
1303
1441
|
self.reply = self.reply_message
|
1304
1442
|
self.send = lambda text, parse_mode=None, reply_markup=None: self.client.send_message(
|
1305
1443
|
self.chat.id, text, parse_mode, reply_markup, reply_to_message=self)
|
1306
|
-
|
1444
|
+
|
1307
1445
|
self.command = None
|
1308
1446
|
self.args = None
|
1309
|
-
txt = self.text.split(' ')
|
1310
|
-
|
1311
|
-
self.command = txt[0]
|
1312
|
-
self.has_slash_command = self.command.startswith(
|
1313
|
-
|
1314
|
-
|
1315
|
-
|
1447
|
+
txt = self.text.split(' ') if self.text else []
|
1448
|
+
|
1449
|
+
self.command = txt[0] if txt else None
|
1450
|
+
self.has_slash_command = self.command.startswith(
|
1451
|
+
'/') if self.text else None
|
1452
|
+
self.args = txt[1:] if self.text else None
|
1453
|
+
|
1454
|
+
self.start = self.command == '/start' if self.text else None
|
1316
1455
|
|
1317
1456
|
def edit(self,
|
1318
1457
|
text: str,
|
@@ -1523,6 +1662,7 @@ class Client:
|
|
1523
1662
|
self.database_name = database_name
|
1524
1663
|
self.auto_log_start_message = auto_log_start_message
|
1525
1664
|
self._base_url = f"{session}/bot{token}"
|
1665
|
+
self._file_url = f"{session}/file/bot{token}"
|
1526
1666
|
self._session = requests.Session()
|
1527
1667
|
self._message_handler = None
|
1528
1668
|
self._message_edit_handler = None
|
@@ -1601,6 +1741,17 @@ class Client:
|
|
1601
1741
|
data = self._make_request('GET', 'getMe')
|
1602
1742
|
return User(self, data)
|
1603
1743
|
|
1744
|
+
def get_file(self, file_id: str) -> bytes:
|
1745
|
+
"""Get file information from Bale API"""
|
1746
|
+
data = {
|
1747
|
+
'file_id': file_id
|
1748
|
+
}
|
1749
|
+
response = self._make_request('POST', 'getFile', json=data)
|
1750
|
+
file_path = response['result']['file_path']
|
1751
|
+
url = f"{self._file_url}/{file_path}"
|
1752
|
+
file_response = self._session.get(url)
|
1753
|
+
return file_response.content
|
1754
|
+
|
1604
1755
|
def set_webhook(self, url: str, certificate: Optional[str] = None,
|
1605
1756
|
max_connections: Optional[int] = None) -> bool:
|
1606
1757
|
"""Set webhook for getting updates"""
|
@@ -1984,16 +2135,21 @@ class Client:
|
|
1984
2135
|
'reply_markup': reply_markup.keyboard if reply_markup else None}
|
1985
2136
|
response = self._make_request('POST', 'sendInvoice', json=data)
|
1986
2137
|
return Message(self, response)
|
1987
|
-
|
1988
|
-
def send_chat_action(self,
|
2138
|
+
|
2139
|
+
def send_chat_action(self,
|
2140
|
+
chat: Union[int,
|
2141
|
+
str,
|
2142
|
+
'Chat'],
|
2143
|
+
action: str,
|
2144
|
+
how_many_times: int = 1) -> bool:
|
1989
2145
|
"""Send a chat action"""
|
1990
2146
|
if not chat:
|
1991
2147
|
raise ValueError("Chat ID cannot be empty")
|
1992
|
-
|
2148
|
+
|
1993
2149
|
data = {
|
1994
|
-
'chat_id': str(chat) if isinstance(
|
1995
|
-
|
1996
|
-
|
2150
|
+
'chat_id': str(chat) if isinstance(
|
2151
|
+
chat, (int, str)) else str(
|
2152
|
+
chat.id), 'action': action}
|
1997
2153
|
res = []
|
1998
2154
|
for _ in range(how_many_times):
|
1999
2155
|
response = self._make_request('POST', 'sendChatAction', json=data)
|
@@ -2127,7 +2283,8 @@ class Client:
|
|
2127
2283
|
"""Handle different types of messages"""
|
2128
2284
|
msg_data = update.get('message', {})
|
2129
2285
|
|
2130
|
-
if 'new_chat_members' in msg_data and hasattr(
|
2286
|
+
if 'new_chat_members' in msg_data and hasattr(
|
2287
|
+
self, '_member_join_handler'):
|
2131
2288
|
chat, user = msg_data['chat'], msg_data['new_chat_members'][0]
|
2132
2289
|
self._create_thread(
|
2133
2290
|
self._member_join_handler,
|
@@ -2137,7 +2294,8 @@ class Client:
|
|
2137
2294
|
)
|
2138
2295
|
return
|
2139
2296
|
|
2140
|
-
if 'left_chat_member' in msg_data and hasattr(
|
2297
|
+
if 'left_chat_member' in msg_data and hasattr(
|
2298
|
+
self, '_member_leave_handler'):
|
2141
2299
|
chat, user = msg_data['chat'], msg_data['left_chat_member']
|
2142
2300
|
self._create_thread(
|
2143
2301
|
self._member_leave_handler,
|
@@ -2151,17 +2309,23 @@ class Client:
|
|
2151
2309
|
text = msg_data['text']
|
2152
2310
|
for command, handler in self._text_handlers.items():
|
2153
2311
|
if text.startswith(command):
|
2154
|
-
conds = conditions(
|
2312
|
+
conds = conditions(
|
2313
|
+
self, message.author, message, None, message.chat)
|
2155
2314
|
params = inspect.signature(handler).parameters
|
2156
2315
|
args = (message, conds) if len(params) > 1 else (message,)
|
2157
2316
|
self._create_thread(handler, *args)
|
2158
2317
|
return
|
2159
2318
|
|
2160
2319
|
if self._message_handler:
|
2161
|
-
conds = conditions(
|
2320
|
+
conds = conditions(
|
2321
|
+
self,
|
2322
|
+
message.author,
|
2323
|
+
message,
|
2324
|
+
None,
|
2325
|
+
message.chat)
|
2162
2326
|
params = inspect.signature(self._message_handler).parameters
|
2163
|
-
args = ((message, update, conds) if len(params) > 2 else
|
2164
|
-
|
2327
|
+
args = ((message, update, conds) if len(params) > 2 else
|
2328
|
+
(message, update) if len(params) > 1 else (message,))
|
2165
2329
|
self._create_thread(self._message_handler, *args)
|
2166
2330
|
|
2167
2331
|
def _handle_update(self, update):
|
@@ -2176,17 +2340,21 @@ class Client:
|
|
2176
2340
|
|
2177
2341
|
for update_type, (cls, handler) in message_types.items():
|
2178
2342
|
if update_type in update:
|
2179
|
-
message = cls(
|
2343
|
+
message = cls(
|
2344
|
+
self, {
|
2345
|
+
'ok': True, 'result': update[update_type]})
|
2180
2346
|
handler(message, update)
|
2181
2347
|
return
|
2182
2348
|
|
2183
2349
|
if 'callback_query' in update and self._callback_handler:
|
2184
|
-
obj = CallbackQuery(
|
2350
|
+
obj = CallbackQuery(
|
2351
|
+
self, {
|
2352
|
+
'ok': True, 'result': update['callback_query']})
|
2185
2353
|
params = inspect.signature(self._callback_handler).parameters
|
2186
2354
|
conds = conditions(self, obj.author, None, obj, obj.chat)
|
2187
2355
|
args = (obj, update, conds) if len(params) > 2 else (obj, update)
|
2188
2356
|
self._create_thread(self._callback_handler, *args)
|
2189
|
-
|
2357
|
+
|
2190
2358
|
def _handle_tick_events(self, current_time):
|
2191
2359
|
"""Handle periodic tick events"""
|
2192
2360
|
if hasattr(self, '_tick_handlers'):
|
@@ -2198,7 +2366,7 @@ class Client:
|
|
2198
2366
|
def run(self, debug=False):
|
2199
2367
|
"""Start p-olling for new messages"""
|
2200
2368
|
try:
|
2201
|
-
self.get_me()
|
2369
|
+
self.user = User(self, {"ok": True, "result": self.get_me()})
|
2202
2370
|
except BaseException:
|
2203
2371
|
raise BaleTokenNotFoundError("token not found")
|
2204
2372
|
|
@@ -2222,7 +2390,7 @@ class Client:
|
|
2222
2390
|
print("Source file changed, restarting...")
|
2223
2391
|
python = sys.executable
|
2224
2392
|
os.execl(python, python, *sys.argv)
|
2225
|
-
|
2393
|
+
|
2226
2394
|
updates = self.get_updates(offset=offset, timeout=30)
|
2227
2395
|
for update in updates:
|
2228
2396
|
update_id = update['update_id']
|
@@ -2231,7 +2399,8 @@ class Client:
|
|
2231
2399
|
offset = update_id + 1
|
2232
2400
|
past_updates.add(update_id)
|
2233
2401
|
if len(past_updates) > 100:
|
2234
|
-
past_updates = set(
|
2402
|
+
past_updates = set(
|
2403
|
+
sorted(list(past_updates))[-50:])
|
2235
2404
|
|
2236
2405
|
current_time = time.time()
|
2237
2406
|
self._handle_tick_events(current_time)
|
@@ -2250,9 +2419,10 @@ class Client:
|
|
2250
2419
|
return False
|
2251
2420
|
|
2252
2421
|
def get_updates(self, offset: Optional[int] = None,
|
2253
|
-
|
2254
|
-
|
2255
|
-
params = {k: v for k, v in locals().items() if k !=
|
2422
|
+
limit: Optional[int] = None,
|
2423
|
+
timeout: Optional[int] = None) -> List[Dict[str, Any]]:
|
2424
|
+
params = {k: v for k, v in locals().items() if k !=
|
2425
|
+
'self' and v is not None}
|
2256
2426
|
response = self._make_request('GET', 'getUpdates', params=params)
|
2257
2427
|
return response.get('result', [])
|
2258
2428
|
|
@@ -2264,6 +2434,6 @@ class Client:
|
|
2264
2434
|
self._threads.clear()
|
2265
2435
|
if hasattr(self, '_close_handler'):
|
2266
2436
|
self._close_handler()
|
2267
|
-
|
2437
|
+
|
2268
2438
|
def create_ref_link(self, data: str):
|
2269
|
-
return f"https://ble.ir/{self.get_me().username}?start={data}"
|
2439
|
+
return f"https://ble.ir/{self.get_me().username}?start={data}"
|
pyrobale-0.2.8.dist-info/RECORD
DELETED
@@ -1,5 +0,0 @@
|
|
1
|
-
pyrobale.py,sha256=Nb4HcO5aydJbc6Ofwz60PtZpLAh0Obx_IT4YydUeFgw,84787
|
2
|
-
pyrobale-0.2.8.dist-info/METADATA,sha256=DjHaCavEk_o0UZI_eLqTRzDzHSTV7V85LI3ifOWOrT0,43668
|
3
|
-
pyrobale-0.2.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
4
|
-
pyrobale-0.2.8.dist-info/licenses/LICENSE,sha256=XQsvifC5HE85232_XgRHpeQ35MfeL2YcpU4yPiAgmW4,35269
|
5
|
-
pyrobale-0.2.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|