optimuslib 0.0.45__py3-none-any.whl → 0.0.48__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.
optimuslib/optimuslib.py CHANGED
@@ -24,7 +24,8 @@
24
24
  #########################################################
25
25
  # Local testing on
26
26
  # C:\Users\Admin\AppData\Local\Programs\Python\Python311\Lib\site-packages\optimuslib
27
- # /home/pi/.local/lib/python3.9/site-packages/optimuslib/optimuslib.py
27
+ # /home/pi/.local/lib/python3.11/site-packages/optimuslib/optimuslib.py
28
+ # ~/.local/lib/python3.11/site-packages/optimuslib/optimuslib.py
28
29
 
29
30
  ### import code
30
31
  # import optimuslib
@@ -93,19 +94,6 @@
93
94
  # python -m pip install --no-cache-dir --upgrade -r requirements.txt
94
95
 
95
96
 
96
- ###### CHECK OS AND PERFORM COMMAND ACCORDINGLY
97
- # get OS version
98
- import platform
99
- def checkOS():
100
- osType = platform.system()
101
- osVersion = platform.platform()
102
- oshostname = platform.node()
103
- # log.info('osType: %s, osVersion: %s, oshostname: %s', osType, osVersion, oshostname)
104
- print(f'osType: {osType}, osVersion: {osVersion}, oshostname: {oshostname}')
105
- return osType, osVersion, oshostname
106
-
107
-
108
-
109
97
 
110
98
  ############## LOG INFO IN TERMINAL AND log.txt FILE ###################
111
99
  import logging
@@ -121,7 +109,7 @@ log.setLevel(logging.DEBUG)
121
109
  # # Handler - 1
122
110
  file = logging.FileHandler('optimuslibLogs.log', 'a', 'utf-8')
123
111
  # fileformat = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(filename)s - %(module)s:%(funcName)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
124
- fileformat = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(filename)s - %(funcName)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
112
+ fileformat = logging.Formatter(fmt='%(asctime)s | %(levelname)s | %(filename)s | %(funcName)s | %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
125
113
  file.setLevel(logging.DEBUG)
126
114
  # file.setLevel(logging.INFO)
127
115
  file.setFormatter(fileformat)
@@ -144,6 +132,17 @@ def loglib(data):
144
132
  log.info('%d %s' % ('1', 'a'))
145
133
  log.info('%d %s' % '1' % 'a')
146
134
 
135
+ ###### CHECK OS AND PERFORM COMMAND ACCORDINGLY
136
+ # get OS version
137
+ import platform
138
+ def checkOS():
139
+ osType = platform.system()
140
+ osVersion = platform.platform()
141
+ oshostname = platform.node()
142
+ # log.info('osType: %s, osVersion: %s, oshostname: %s', osType, osVersion, oshostname)
143
+ log.info(f'osType: {osType}, osVersion: {osVersion}, oshostname: {oshostname}')
144
+ return osType, osVersion, oshostname
145
+
147
146
 
148
147
  osType, osVersion, oshostname = checkOS()
149
148
  if osType == 'Windows':
@@ -251,6 +250,7 @@ def traverse(path):
251
250
 
252
251
  # '''
253
252
  ############## REQUESTS LIBRARY ###################
253
+ '''
254
254
  import time, sys, requests
255
255
  # import sys
256
256
  import requests
@@ -335,11 +335,11 @@ def sendRequest(method, requestUrl, cookies=None, headers=None, data=None, json=
335
335
  s.proxies = { 'http': proxyhost, 'https': proxyhost}
336
336
  # pythonproxy.parseProxyHostAndRun(proxyhost)
337
337
 
338
- '''
339
- if not validResponseCodes:
340
- # validResponseCodes = [200,201,302,307,401,400,403,404,405,406]
341
- validResponseCodes = [200,201,302,307,401,400,404,405,406]
342
- '''
338
+ '''
339
+ # if not validResponseCodes:
340
+ # # validResponseCodes = [200,201,302,307,401,400,403,404,405,406]
341
+ # validResponseCodes = [200,201,302,307,401,400,404,405,406]
342
+ '''
343
343
  totalRequestsSent += 1
344
344
 
345
345
  # log.info('[%d/%d] Sending Request: %s',totalRequestsSent,len(requestUrl),requestUrl)
@@ -365,8 +365,8 @@ def sendRequest(method, requestUrl, cookies=None, headers=None, data=None, json=
365
365
  # except (MalformedRequest, InternalError, StatusUnknown, ConnectionError, ConnectionResetError, http.client.RemoteDisconnected, RemoteDisconnected, ProtocolError, HTTPException, socket.gaierror) as e:
366
366
  except requests.exceptions.ConnectionError as e:
367
367
  log.error(f'ConnectionError occured - {e}')
368
- print('Sleeping for 60 secods before next request')
369
- time.sleep(60)
368
+ log.error('Sleeping for 15 secods before next request')
369
+ time.sleep(15)
370
370
  sendRequest(method, requestUrl, cookies, headers, data, json, params, files, timeout, proxyhost, validResponseCodes, allow_redirects, verify)
371
371
  # sys.exit()
372
372
  except requests.exceptions.ConnectionResetError as e:
@@ -374,7 +374,7 @@ def sendRequest(method, requestUrl, cookies=None, headers=None, data=None, json=
374
374
  log.error('Is your internet connection Stable?')
375
375
  sys.exit()
376
376
  except requests.exceptions.RequestException as e:
377
- print(f"Request failed: {e}")
377
+ log.error(f"Request failed: {e}")
378
378
  except ConnectionRefusedError as e:
379
379
  log.error(f'Exception occured - {e}')
380
380
  log.error('Is proxy set and server not running?')
@@ -419,6 +419,122 @@ def sendBulkRequests(method, requestUrlList, cookies=None, headers=None, data=No
419
419
 
420
420
  ################# REQUESTS MODULE DEBUGGING
421
421
  '''
422
+ ########## CORRECTED BY CHATGPT
423
+ import time, sys, requests, logging
424
+ from requests.adapters import HTTPAdapter, Retry
425
+ from requests_futures.sessions import FuturesSession
426
+ requests.packages.urllib3.disable_warnings()
427
+
428
+ # Setup logging
429
+ logging.basicConfig(level=logging.DEBUG)
430
+ log = logging.getLogger(__name__)
431
+
432
+ # Response codes to force retry on
433
+ RETRY_STATUS_CODES = [429, 500, 502, 503, 504]
434
+
435
+ # Max retry attempts
436
+ MAX_RETRIES = 5
437
+
438
+ # Futures Session for parallel async requests
439
+ futuresworkercount = 100
440
+ s = FuturesSession(max_workers=futuresworkercount)
441
+
442
+ # Configure retry strategy
443
+ retry_strategy = Retry(
444
+ total=MAX_RETRIES,
445
+ backoff_factor=1, # Exponential backoff: 1,2,4,8...
446
+ status_forcelist=RETRY_STATUS_CODES,
447
+ allowed_methods=["HEAD", "GET", "OPTIONS", "POST", "PUT", "PATCH", "DELETE"],
448
+ raise_on_status=False
449
+ )
450
+
451
+ adapter = HTTPAdapter(max_retries=retry_strategy)
452
+ s.mount("https://", adapter)
453
+ s.mount("http://", adapter)
454
+
455
+ totalRequestsSent = 0
456
+
457
+
458
+ def _handle_429(response, attempt):
459
+ """Handles 429 Too Many Requests with delay/retry."""
460
+ if response.status_code != 429:
461
+ return False
462
+
463
+ retry_after = response.headers.get("Retry-After")
464
+ if retry_after:
465
+ delay = int(retry_after)
466
+ else:
467
+ delay = min(2 ** attempt, 60) # exponential backoff, max 60s
468
+
469
+ log.warning(f"429 Too Many Requests - sleeping {delay}s before retry...")
470
+ time.sleep(delay)
471
+ return True
472
+
473
+
474
+ def sendRequest(method, requestUrl, cookies=None, headers=None, data=None, json=None, params=None, files=None, timeout=None, proxyhost=None, allow_redirects=True, verify=False):
475
+ """Send HTTP request with retry + 429 handling."""
476
+ global totalRequestsSent
477
+ totalRequestsSent += 1
478
+
479
+ if proxyhost:
480
+ log.debug('Using proxy: %s', proxyhost)
481
+ s.proxies = {'http': proxyhost, 'https': proxyhost}
482
+
483
+ log.debug(f'[{totalRequestsSent}] Sending Request: {requestUrl}')
484
+
485
+ method = method.lower()
486
+ attempts = 0
487
+
488
+ while attempts < MAX_RETRIES:
489
+ try:
490
+ if method == "get":
491
+ response = s.get(requestUrl, cookies=cookies, headers=headers, params=params, timeout=timeout, allow_redirects=allow_redirects, verify=verify)
492
+ elif method == "post":
493
+ response = s.post(requestUrl, cookies=cookies, headers=headers, data=data, json=json, files=files, params=params, timeout=timeout, allow_redirects=allow_redirects, verify=verify)
494
+ elif method == "patch":
495
+ response = s.patch(requestUrl, cookies=cookies, headers=headers, data=data, json=json, files=files, params=params, timeout=timeout, allow_redirects=allow_redirects, verify=verify)
496
+ elif method == "delete":
497
+ response = s.delete(requestUrl, cookies=cookies, headers=headers, data=data, json=json, files=files, params=params, timeout=timeout, allow_redirects=allow_redirects, verify=verify)
498
+ elif method == "put":
499
+ response = s.put(requestUrl, cookies=cookies, headers=headers, data=data, json=json, files=files, params=params, timeout=timeout, allow_redirects=allow_redirects, verify=verify)
500
+ else:
501
+ raise ValueError(f"Unsupported HTTP method: {method}")
502
+
503
+ result = response.result()
504
+
505
+ # Handle 429 manually
506
+ if result.status_code == 429:
507
+ if _handle_429(result, attempts):
508
+ attempts += 1
509
+ continue
510
+
511
+ log.debug(f"Response [{result.status_code}] - {result.text}...")
512
+ # log.debug(f"Response [{result.status_code}] - {result.text[:200]}...")
513
+ return response
514
+
515
+ except requests.exceptions.ConnectionError as e:
516
+ log.error(f'ConnectionError: {e} - retrying in 5s...')
517
+ time.sleep(5)
518
+ except requests.exceptions.RequestException as e:
519
+ log.error(f"Request failed: {e}")
520
+ break
521
+
522
+ attempts += 1
523
+
524
+ log.error(f"Request failed after {MAX_RETRIES} attempts: {requestUrl}")
525
+ return None
526
+
527
+
528
+ def sendBulkRequests(method, requestUrlList, **kwargs):
529
+ """Send multiple requests in bulk."""
530
+ futureResponseList = []
531
+ for idx, requestUrl in enumerate(requestUrlList, 1):
532
+ log.debug(f'Sending Bulk Request...[{idx}/{len(requestUrlList)}]')
533
+ futureResponse = sendRequest(method, requestUrl, **kwargs)
534
+ futureResponseList.append(futureResponse)
535
+ return futureResponseList
536
+ ########### CORRECTED BY CHATGPT
537
+ '''
422
538
  # GET ALL COOKIES IN SESSION
423
539
  print(s.cookies.get_dict())
424
540
 
@@ -922,7 +1038,7 @@ def select_file_from_computer(image_path):
922
1038
  autoit.win_active("Open")
923
1039
  autoit.control_set_text("Open", "Edit1", image_path)
924
1040
  autoit.control_send("Open", "Edit1", "{ENTER}")
925
- log.info("Sleeping for %s seconds...", wait_time)
1041
+ log.info(f"Sleeping for {wait_time} seconds...", )
926
1042
  sleep(wait_time)
927
1043
 
928
1044
  # click_function('Sign In',"//a[@data-nav-ref='nav_ya_signin']",)
@@ -932,11 +1048,13 @@ def select_file_from_computer(image_path):
932
1048
  ############################
933
1049
  import os
934
1050
  def createFolder(foldername):
935
- try:
936
- if os.mkdir(foldername):
937
- log.info('Folder Created: %s', foldername)
938
- except FileExistsError:
939
- log.info('%s folder already exists', foldername)
1051
+ # try:
1052
+ # if os.mkdir(foldername):
1053
+ # log.info(f"Folder Created: {foldername}")
1054
+ # except FileExistsError:
1055
+ # log.info(f"{foldername} folder already exists")
1056
+ if not os.path.exists(foldername):
1057
+ os.makedirs(foldername)
940
1058
 
941
1059
  ############################
942
1060
  import re
@@ -1140,13 +1258,13 @@ def textfile_to_image(text_file_path, image_output_path):
1140
1258
  for font_filename in COMMON_MONO_FONT_FILENAMES:
1141
1259
  try:
1142
1260
  font = ImageFont.truetype(font_filename, size=large_font)
1143
- print(f'Using font "{font_filename}".')
1261
+ log.error(f'Using font "{font_filename}".')
1144
1262
  break
1145
1263
  except IOError:
1146
- print(f'Could not load font "{font_filename}".')
1264
+ log.error(f'Could not load font "{font_filename}".')
1147
1265
  if font is None:
1148
1266
  font = ImageFont.load_default()
1149
- print('Using default font.')
1267
+ log.error('Using default font.')
1150
1268
 
1151
1269
  # make a sufficiently sized background image based on the combination of font and lines
1152
1270
  font_points_to_pixels = lambda pt: round(pt * 96.0 / 72)
@@ -1206,28 +1324,34 @@ def logAndSend(botToken,chatId,output):
1206
1324
  sendDiscordBotNotification(botToken,chatId,output)
1207
1325
 
1208
1326
  ########## format a number as per indian currency
1209
- def formatCurrrencyIndian(number):
1210
- # Convert the number to string and split it into the integer and decimal parts
1327
+ def formatCurrencyIndian(number):
1328
+ """
1329
+ Format a number as per Indian currency format with commas.
1330
+ Example: 12345678.90 -> '1,23,45,678.90'
1331
+ """
1211
1332
  number_str = "{:.2f}".format(number)
1212
1333
  integer_part, decimal_part = number_str.split(".")
1213
-
1214
- # Reverse the integer part for easier grouping
1215
- integer_part_reversed = integer_part[::-1]
1216
-
1217
- # Group the digits in the Indian numbering system (3,2,2,...)
1218
- groups = []
1219
- groups.append(integer_part_reversed[:3])
1220
- groups.append(integer_part_reversed[3:5])
1221
- for i in range(5, len(integer_part_reversed), 2):
1222
- groups.append(integer_part_reversed[i:i+2])
1223
-
1224
- # Join the groups with commas and reverse back
1225
- indian_number_format = ",".join(groups)[::-1]
1226
-
1227
- # Combine integer part with decimal part
1228
- formatted_number = indian_number_format + "." + decimal_part
1229
-
1230
- return formatted_number
1334
+ # Handle negative numbers
1335
+ sign = ""
1336
+ if integer_part.startswith('-'):
1337
+ sign = "-"
1338
+ integer_part = integer_part[1:]
1339
+
1340
+ # First group (last 3 digits)
1341
+ if len(integer_part) > 3:
1342
+ last3 = integer_part[-3:]
1343
+ rest = integer_part[:-3]
1344
+ # Group rest in pairs
1345
+ pairs = []
1346
+ while len(rest) > 2:
1347
+ pairs.insert(0, rest[-2:])
1348
+ rest = rest[:-2]
1349
+ if rest:
1350
+ pairs.insert(0, rest)
1351
+ formatted = sign + ",".join(pairs + [last3]) + "." + decimal_part
1352
+ else:
1353
+ formatted = sign + integer_part + "." + decimal_part
1354
+ return formatted
1231
1355
 
1232
1356
  ########### DISCORD BOT NOTIFICATION - BOT DETAILS ##############
1233
1357
  # def getDiscordBotInfo(botToken):
@@ -1325,6 +1449,9 @@ def fetchOTPDiscord(botToken, otpFetchWaitTime, otpChannelId):
1325
1449
  id, timestamp, username, content = getDiscordBotUpdates(botToken, otpChannelId, messageCount=1)
1326
1450
  log.info('update_id: %s, text: %s', id, content)
1327
1451
  twofa_value = content[-6:]
1452
+ twofa_value = twofa_value.replace("-","")
1453
+ twofa_value = twofa_value.replace("S","")
1454
+ twofa_value = twofa_value.replace("T","")
1328
1455
  getVarInfo('twofa_value',twofa_value)
1329
1456
  break
1330
1457
  except ValueError as e:
@@ -1410,6 +1537,176 @@ def buildUnicodeTable(headers, rows):
1410
1537
  # fetchOTPDiscord(botToken, otpFetchWaitTime, otpChannelId)
1411
1538
  # sendDiscordBotNotification(botToken,channelId,message)
1412
1539
  # sendDiscordBotFile(botToken,channelId,filePath)
1540
+
1541
+
1542
+ ########################## FETCH OTP FROM GMAIL VIA APP PASSWORD ##########################
1543
+ import imaplib
1544
+ import email
1545
+ import time
1546
+ import re
1547
+ import quopri
1548
+ import threading
1549
+ from bs4 import BeautifulSoup
1550
+ from email.utils import parsedate_to_datetime
1551
+ from datetime import datetime, timezone
1552
+
1553
+
1554
+ def extract_clean_text(msg):
1555
+ text_parts = []
1556
+
1557
+ for part in msg.walk():
1558
+ content_type = part.get_content_type()
1559
+ charset = part.get_content_charset()
1560
+
1561
+ log.debug(f"Found MIME part: {content_type}, charset={charset}")
1562
+
1563
+ if content_type in ("text/plain", "text/html"):
1564
+ raw = part.get_payload(decode=True)
1565
+
1566
+ if not raw:
1567
+ log.debug("Empty payload")
1568
+ continue
1569
+
1570
+ try:
1571
+ decoded = raw.decode(charset or "utf-8", errors="ignore")
1572
+ except Exception:
1573
+ decoded = quopri.decodestring(raw).decode(errors="ignore")
1574
+
1575
+ if content_type == "text/html":
1576
+ soup = BeautifulSoup(decoded, "html.parser")
1577
+ decoded = soup.get_text(" ")
1578
+
1579
+ text_parts.append(decoded)
1580
+
1581
+ full_text = " ".join(text_parts)
1582
+ log.debug(f"Extracted email text (first 300 chars): {full_text[:300]!r}")
1583
+
1584
+ return full_text
1585
+
1586
+
1587
+
1588
+ def is_recent(msg, max_age_seconds):
1589
+ date_hdr = msg.get("Date")
1590
+ if not date_hdr:
1591
+ log.debug("Email has no Date header")
1592
+ return False
1593
+
1594
+ try:
1595
+ msg_time = parsedate_to_datetime(date_hdr)
1596
+ if msg_time.tzinfo is None:
1597
+ msg_time = msg_time.replace(tzinfo=timezone.utc)
1598
+
1599
+ now = datetime.now(timezone.utc)
1600
+ age = (now - msg_time).total_seconds()
1601
+
1602
+ log.debug(f"Email age (seconds): {age}")
1603
+ return 0 <= age <= max_age_seconds
1604
+
1605
+ except Exception as e:
1606
+ log.exception("Failed to parse email date")
1607
+ return False
1608
+
1609
+ def input_with_timeout(prompt, timeout):
1610
+ """Get user input with timeout"""
1611
+ result = {"value": None}
1612
+
1613
+ def ask():
1614
+ try:
1615
+ result["value"] = input(prompt)
1616
+ except EOFError:
1617
+ pass
1618
+
1619
+ t = threading.Thread(target=ask, daemon=True)
1620
+ t.start()
1621
+ t.join(timeout)
1622
+
1623
+ return result["value"]
1624
+
1625
+
1626
+ # ============== IMAP OTP ==================
1627
+
1628
+ def poll_for_otp(EMAIL, APP_PASSWORD, SENDER, SUBJECT, POLL_INTERVAL, POLL_TIMEOUT, MAX_AGE_SECONDS):
1629
+ """Poll Gmail IMAP for OTP email"""
1630
+ IMAP_SERVER = "imap.gmail.com"
1631
+ mail = imaplib.IMAP4_SSL(IMAP_SERVER)
1632
+ mail.login(EMAIL, APP_PASSWORD)
1633
+
1634
+ start = time.time()
1635
+
1636
+ while time.time() - start < POLL_TIMEOUT:
1637
+ # 🔑 Gmail IMAP requires re-select to refresh UNSEEN
1638
+ mail.select("inbox", readonly=False)
1639
+
1640
+ filter = f'(UNSEEN FROM "{SENDER}" SUBJECT "{SUBJECT}")'
1641
+ log.debug(f"IMAP search filter: {filter}")
1642
+ status, messages = mail.search(None,filter)
1643
+
1644
+ ids = messages[0].split()
1645
+
1646
+ log.debug(f"IMAP search status: {status}")
1647
+ log.debug(f"Message IDs returned: {ids}")
1648
+
1649
+ for eid in ids:
1650
+ _, data = mail.fetch(eid, "(RFC822)")
1651
+ msg = email.message_from_bytes(data[0][1])
1652
+
1653
+ log.debug(f"From: {msg.get('From')}")
1654
+ log.debug(f"Subject: {msg.get('Subject')}")
1655
+ log.debug(f"Date: {msg.get('Date')}")
1656
+
1657
+ if not is_recent(msg, MAX_AGE_SECONDS):
1658
+ continue
1659
+
1660
+ body = extract_clean_text(msg)
1661
+
1662
+ if not body.strip():
1663
+ log.warning("Email body extracted but empty")
1664
+ continue
1665
+
1666
+ log.debug("Running OTP regex scan")
1667
+
1668
+ match = re.search(r"\b(\d{4,8})\b", body)
1669
+
1670
+ if not match:
1671
+ log.warning("No OTP match found in email body")
1672
+ continue
1673
+
1674
+ otp = match.group(1)
1675
+ log.info(f"OTP extracted successfully: {otp}")
1676
+
1677
+ mail.store(eid, "+FLAGS", "\\Seen")
1678
+ mail.logout()
1679
+ return otp
1680
+
1681
+ log.info("Waiting for OTP...")
1682
+ time.sleep(POLL_INTERVAL)
1683
+
1684
+ mail.logout()
1685
+ raise TimeoutError("Auto OTP fetch timed out")
1686
+
1687
+
1688
+ def get_gmail_otp(EMAIL, APP_PASSWORD, SENDER, SUBJECT,POLL_INTERVAL,POLL_TIMEOUT,MAX_AGE_SECONDS,MANUAL_INPUT_TIMEOUT):
1689
+
1690
+ """Get OTP automatically or fall back to manual input"""
1691
+ try:
1692
+ otp = poll_for_otp(EMAIL, APP_PASSWORD, SENDER, SUBJECT, POLL_INTERVAL, POLL_TIMEOUT, MAX_AGE_SECONDS)
1693
+ log.info(f"OTP received automatically: {otp}")
1694
+ return otp
1695
+
1696
+ except TimeoutError:
1697
+ log.info("⚠️ Auto OTP fetch timed out.")
1698
+ log.info(f"Please enter OTP manually (waiting {MANUAL_INPUT_TIMEOUT} seconds)...")
1699
+
1700
+ manual_otp = input_with_timeout("Enter OTP: ", MANUAL_INPUT_TIMEOUT)
1701
+
1702
+ if manual_otp and manual_otp.strip().isdigit():
1703
+ return manual_otp.strip()
1704
+
1705
+ raise TimeoutError("No OTP entered within manual input window")
1706
+
1707
+ # ================== RUN ===================
1708
+
1709
+
1413
1710
  ################################################
1414
1711
  log.info('[+] Optimuslib Import Sucessfull.')
1415
1712
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: optimuslib
3
- Version: 0.0.45
3
+ Version: 0.0.48
4
4
  Summary: Function Library for mostly used codes
5
5
  Author: Shomi Nanwani
6
6
  Requires-Python: >=3.9,<4.0
@@ -9,10 +9,12 @@ Classifier: Programming Language :: Python :: 3.9
9
9
  Classifier: Programming Language :: Python :: 3.10
10
10
  Classifier: Programming Language :: Python :: 3.11
11
11
  Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
12
14
  Requires-Dist: pillow (==9.5.0)
13
15
  Requires-Dist: pyperclip (>=1.8.2,<2.0.0)
14
16
  Requires-Dist: requests (>=2.31.0,<3.0.0)
15
17
  Requires-Dist: requests-futures (>=1.0.0,<2.0.0)
16
18
  Description-Content-Type: text/markdown
17
19
 
18
-
20
+ Optimuslib
@@ -0,0 +1,5 @@
1
+ optimuslib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ optimuslib/optimuslib.py,sha256=xsoQQgTyA5UhIfLS3mEyLKR4jzbhLriT-E-3yk7FcXg,66286
3
+ optimuslib-0.0.48.dist-info/METADATA,sha256=1VrHrlb6_vwzIFBuNiEXSIu1_isvo6Or6tzhtrtt_5A,723
4
+ optimuslib-0.0.48.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
5
+ optimuslib-0.0.48.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 2.3.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,5 +0,0 @@
1
- optimuslib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- optimuslib/optimuslib.py,sha256=w5cw5tk3ZTTE7fBA_qc5zV-KCsFS4k3Aba8CFqOfNj4,56245
3
- optimuslib-0.0.45.dist-info/METADATA,sha256=ydRkxEl8_AyW4xIhZqV-WHp3nezeDTY3q6-e3BcZRms,611
4
- optimuslib-0.0.45.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
5
- optimuslib-0.0.45.dist-info/RECORD,,