support more dice notation
squashed commits: - fix unpacking regex matches for dice notation - fix missing cast to integer - fix raw result output - fix formatting of chat message for raw result - faster dockerfile builds? - fancier output format - fix variable name collision - remove unnecessary newline - more robust handling of dF - fix typo - fix formatting - use blank character for dF
This commit is contained in:
parent
8aaf150f14
commit
6a7ec8eef7
|
@ -1,7 +1,8 @@
|
||||||
FROM archlinux:latest
|
FROM archlinux:latest
|
||||||
WORKDIR /umi
|
WORKDIR /umi
|
||||||
COPY . .
|
COPY "requirements.txt" .
|
||||||
RUN pacman -Syu --noconfirm
|
RUN pacman -Syu --noconfirm
|
||||||
RUN pacman -S --noconfirm python-pip libffi libsodium ffmpeg
|
RUN pacman -S --noconfirm python-pip libffi libsodium ffmpeg
|
||||||
RUN pip install -r requirements.txt
|
RUN pip install -r requirements.txt
|
||||||
|
COPY . .
|
||||||
CMD ["python", "-u", "app.py"]
|
CMD ["python", "-u", "app.py"]
|
202
cogs/random.py
202
cogs/random.py
|
@ -5,6 +5,7 @@ import httpx
|
||||||
from decouple import config
|
from decouple import config
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
import re
|
import re
|
||||||
|
from typing import Iterator
|
||||||
|
|
||||||
GUILD = config("DISCORD_GUILD_ID", cast=int)
|
GUILD = config("DISCORD_GUILD_ID", cast=int)
|
||||||
|
|
||||||
|
@ -20,31 +21,193 @@ class Random(Cog):
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def roll(faces: int = 6, number: int = 1) -> str:
|
def roll(
|
||||||
"""Roll die(s) using random.org integer generation"""
|
number: int = 1,
|
||||||
|
faces: int = 6,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Roll standard dice using random.org integer generation
|
||||||
|
|
||||||
|
Params:
|
||||||
|
int number How many dice to roll
|
||||||
|
int faces How many faces on a standard die
|
||||||
|
"""
|
||||||
columns = number if number < 11 else 10
|
columns = number if number < 11 else 10
|
||||||
return httpx.get(f'https://www.random.org/integers/?num={number}&min=1&max={faces}&col={columns}&base=10&format=plain&rnd=new').text
|
return httpx.get(f'https://www.random.org/integers/?num={number}&min=1&max={faces}&col={columns}&base=10&format=plain&rnd=new').text
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def roll_results(faces: int = 6, number: int = 1) -> str:
|
def parse_dice_notation(input: str) -> Iterator[re.Match[str]]:
|
||||||
|
"""
|
||||||
|
Parse dice notation to get numbers, faces, modifiers, and tokens.
|
||||||
|
https://en.wikipedia.org/wiki/Dice_notation
|
||||||
|
|
||||||
|
Dice notation can take the form of AdX+B (chainable):
|
||||||
|
|
||||||
|
A how many dice to roll (if not provided, roll once)
|
||||||
|
dX die with X faces (1-X)
|
||||||
|
+ add the result to the previous result
|
||||||
|
- subtract the result from the previous result
|
||||||
|
B constant modifier
|
||||||
|
|
||||||
|
Extensions implemented:
|
||||||
|
|
||||||
|
d(a,b,...) custom die with faces defined by a sequence
|
||||||
|
d% special die with 00-99
|
||||||
|
dF fudge dice (two +, two -, two blanks)
|
||||||
|
dF.1 fudge dice variant (one +, one -, four blanks)
|
||||||
|
dF.2 alias for dF
|
||||||
|
|
||||||
|
Unimplemented:
|
||||||
|
|
||||||
|
-L subtract the lowest
|
||||||
|
-H subtract the highest
|
||||||
|
klL keep lowest L values
|
||||||
|
khH keep highest H values
|
||||||
|
xC multiply the result by C
|
||||||
|
Cx(AdX+B) multiply the dice group by C (including modifier)
|
||||||
|
/C divide by C?
|
||||||
|
|
||||||
|
Params:
|
||||||
|
str input some dice notation to parse
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple(plus_minus_dice, number, faces, plus_minus_mod, modifier)
|
||||||
|
|
||||||
|
str plus_minus_dice a token for adding or removing from total
|
||||||
|
str number the number of dice to roll
|
||||||
|
str faces the faces on the dice (sequence or number)
|
||||||
|
---
|
||||||
|
str plus_minus_mod a token for adding or subtracting a modifier
|
||||||
|
str modifier some number to add/subtract from total
|
||||||
|
"""
|
||||||
|
pattern = re.compile(
|
||||||
|
r'''
|
||||||
|
# dice group
|
||||||
|
(\A|\+|\-) # start-of-string or plus or minus dice result (group 1)
|
||||||
|
(\d+)? # number of dice (group 2)
|
||||||
|
d # the literal 'd'
|
||||||
|
( # faces (group 3)
|
||||||
|
\d+ # either plain number
|
||||||
|
|\(.+?\) # or sequence of strings
|
||||||
|
|\[\d+:\d+(?:\d+)?\] # or range of numbers as start:end:step
|
||||||
|
|% # or literal '%' (for 00-99)
|
||||||
|
|F(?:\.\d+?)? # or literal 'F' (for fudge dice ++--__)
|
||||||
|
)
|
||||||
|
# OR
|
||||||
|
|
|
||||||
|
# number modifier
|
||||||
|
(\+|\-) # plus or minus (group 4)
|
||||||
|
(\d+)? # some number value (group 5)
|
||||||
|
''',
|
||||||
|
re.IGNORECASE | re.VERBOSE
|
||||||
|
)
|
||||||
|
# returns (plus_minus_dice, number, faces, plus_minus_mod, modifier)
|
||||||
|
return pattern.finditer(input)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def roll_result(roll: str) -> str:
|
||||||
|
"""Calculates the result of a roll and output a summary"""
|
||||||
|
matches = Random.parse_dice_notation(roll)
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
roll_results = []
|
||||||
|
for match in matches:
|
||||||
|
plus_minus_dice, number, faces, plus_minus_mod, modifier = match.groups()
|
||||||
|
|
||||||
|
if faces and faces[0] == '(': # non-standard dice sequence (a,b,c,...).
|
||||||
|
number = int(number) if number else 1 # number may be omitted for single die.
|
||||||
|
outcomes = faces[1:-1].split(',') # remove parens and get list of faces,
|
||||||
|
result = Random.roll(number, len(outcomes)) # then roll for an index in that list
|
||||||
|
result = [outcomes[int(r)-1] for r in result.split()] # that we can then lookup.
|
||||||
|
try: # we don't actually know if the faces are numeric...
|
||||||
|
subtotal = sum(map(int, result))
|
||||||
|
if plus_minus_dice == '-':
|
||||||
|
total -= subtotal
|
||||||
|
else:
|
||||||
|
total += subtotal
|
||||||
|
except (ValueError, TypeError): # in case faces were not all numeric (for this group or a previous group),
|
||||||
|
total = None # there is no total.
|
||||||
|
result = ' '.join(result) # finally, repack the result
|
||||||
|
roll_results.append( (f'{number}d{faces}', result) ) # and append it to output
|
||||||
|
elif faces and faces == '%': # special 00-99
|
||||||
|
number = int(number) if number else 1 # number may be omitted for single die.
|
||||||
|
result = Random.roll(number, 100) # we roll a d100,
|
||||||
|
result = re.sub('100', '00', result) # but we sub in 00
|
||||||
|
result = re.sub(r'\b(\d){1}\b', r'0\1', result) # and zero pad single digits.
|
||||||
|
try: # this may be part of a larger group of rolls...
|
||||||
|
subtotal = sum(map(int, result.split()))
|
||||||
|
if plus_minus_dice == '-':
|
||||||
|
total -= int(subtotal) # this may fail because total is None
|
||||||
|
else:
|
||||||
|
total += int(subtotal) # this may fail because total is None
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
total = None
|
||||||
|
roll_results.append( (f'{number}d{faces}', result) ) # and append it to output
|
||||||
|
elif faces and faces[0] == 'F': # fudge dice
|
||||||
|
number = int(number) if number else 1 # number may be omitted for single die.
|
||||||
|
if faces == 'F' or faces == 'F.2':
|
||||||
|
outcomes = '(+,+,-,-,␢,␢)'
|
||||||
|
elif faces == 'F.1':
|
||||||
|
outcomes = '(+,+,-,-,␢,␢)'
|
||||||
|
outcomes = outcomes[1:-1].split(',') # remove parens and get list of faces,
|
||||||
|
result = Random.roll(number, len(outcomes)) # then roll for an index in that list
|
||||||
|
result = [outcomes[int(r)-1] for r in result.split()] # that we can then lookup.
|
||||||
|
try: # we don't actually know if the faces are numeric...
|
||||||
|
subtotal = sum(map(int, result))
|
||||||
|
if plus_minus_dice == '-':
|
||||||
|
total -= subtotal
|
||||||
|
else:
|
||||||
|
total += subtotal
|
||||||
|
except (ValueError, TypeError): # in case faces were not all numeric (for this group or a previous group),
|
||||||
|
total = None # there is no total.
|
||||||
|
result = ' '.join(result) # finally, repack the result
|
||||||
|
roll_results.append( (f'{number}d{faces}', f'{result}\n') ) # and append it to output
|
||||||
|
elif faces: # standard dice
|
||||||
|
number = int(number) if number else 1 # number may be omitted for single die.
|
||||||
|
result = Random.roll(number, faces)
|
||||||
|
try: # this may be part of a larger group of rolls...
|
||||||
|
subtotal = sum(map(int, result.split()))
|
||||||
|
if plus_minus_dice == '-':
|
||||||
|
total -= subtotal # this may fail because total is None
|
||||||
|
else:
|
||||||
|
total += subtotal # this may fail because total is None
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
total = None
|
||||||
|
roll_results.append( (f'{number}d{faces}', result) ) # and append it to output
|
||||||
|
elif modifier: # no dice
|
||||||
|
try: # this may be part of a larger group of rolls...
|
||||||
|
if plus_minus_mod == '-':
|
||||||
|
total -= int(modifier) # this may fail because total is None
|
||||||
|
elif plus_minus_mod == '+':
|
||||||
|
total += int(modifier) # this may fail because total is None
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
total = None
|
||||||
|
|
||||||
|
raw_result_list = []
|
||||||
|
for raw_roll in roll_results:
|
||||||
|
raw_result_list.append(f'=== {raw_roll[0]} ===\n**{raw_roll[1]}**')
|
||||||
|
raw = ''.join(raw_result_list)
|
||||||
|
|
||||||
|
output = f"*You rolled {roll} and got **{total}**.*\n\nRaw result:\n{raw}"
|
||||||
|
if total is None:
|
||||||
|
output = f"*You rolled {roll} and got:*\n\n{raw}"
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def rolls_results(rolls: str) -> str:
|
||||||
"""Returns a formatted result of dice outcomes"""
|
"""Returns a formatted result of dice outcomes"""
|
||||||
result = Random.roll(faces=int(faces), number=int(number))
|
results = [Random.roll_result(roll.strip()) for roll in rolls.split(';')]
|
||||||
if faces == 100: # 00 - 99
|
return '\n'.join(results)
|
||||||
result = re.sub('100', '00', result)
|
|
||||||
result = re.sub(r'\b(\d){1}\b', r'0\1', result) # zero pad single digit
|
|
||||||
return f"*You rolled {number}d{faces} and got:*\n**{result}**"
|
|
||||||
|
|
||||||
@command(
|
@command(
|
||||||
name='roll',
|
name='roll',
|
||||||
aliases=['dice']
|
aliases=['dice']
|
||||||
)
|
)
|
||||||
async def roll_prefix(self, ctx: Context, query: str = '1d6'):
|
async def roll_prefix(self, ctx: Context, rolls: str = '1d6'):
|
||||||
"""Roll some dice (default 1d6)"""
|
"""Roll some dice (default 1d6)"""
|
||||||
number, faces = query.split('d')
|
return await ctx.send(Random.rolls_results(rolls))
|
||||||
if not number: # handle raw dN rolls, e.g. 'd6' or 'd20'
|
|
||||||
number = 1
|
|
||||||
return await ctx.send(Random.roll_results(faces=int(faces), number=int(number)))
|
|
||||||
|
|
||||||
@slash_command(
|
@slash_command(
|
||||||
name='roll',
|
name='roll',
|
||||||
guild_ids=[GUILD]
|
guild_ids=[GUILD]
|
||||||
|
@ -52,10 +215,15 @@ class Random(Cog):
|
||||||
async def roll_slash(self,
|
async def roll_slash(self,
|
||||||
ctx: discord.ApplicationContext,
|
ctx: discord.ApplicationContext,
|
||||||
faces: Option(int, "How many faces are on each die?", min_value=1, max_value=1_000_000_000, default=6),
|
faces: Option(int, "How many faces are on each die?", min_value=1, max_value=1_000_000_000, default=6),
|
||||||
number: Option(int, "How many dies should be rolled?", min_value=1, max_value=10_000, default=1)
|
number: Option(int, "How many dies should be rolled?", min_value=1, max_value=10_000, default=1),
|
||||||
|
modifier: Option(int, "Add this number to the final result", default=0),
|
||||||
):
|
):
|
||||||
"""Roll some dice (default 1d6)"""
|
"""Roll some standard dice with an optional modifier (default 1d6 with +0)"""
|
||||||
return await ctx.respond(Random.roll_results(faces=faces, number=number))
|
if modifier and modifier > 0:
|
||||||
|
modifier = f'+{modifier}'
|
||||||
|
return await ctx.respond(
|
||||||
|
Random.rolls_results(f'{number}d{faces}{modifier}')
|
||||||
|
)
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
||||||
|
|
7
pdm.lock
7
pdm.lock
|
@ -284,8 +284,8 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock_version = "4.0"
|
lock_version = "4.1"
|
||||||
content_hash = "sha256:889439b64bada10d9afdf954f0b552fb3a224b91dfac0b20c3531ea0fdf2c69d"
|
content_hash = "sha256:3a6be9c0370a67e849f5f92299e3bc8d4bb2f81a009c80ba91f75a2978b75677"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
"aiodns 3.0.0" = [
|
"aiodns 3.0.0" = [
|
||||||
|
@ -958,7 +958,9 @@ content_hash = "sha256:889439b64bada10d9afdf954f0b552fb3a224b91dfac0b20c3531ea0f
|
||||||
{url = "https://files.pythonhosted.org/packages/85/c9/d465977afb008f0721e63fdf9a0e8e37bf6f188dc3c787b6ec9568621a48/pycryptodomex-3.15.0-cp35-abi3-win32.whl", hash = "sha256:46b3f05f2f7ac7841053da4e0f69616929ca3c42f238c405f6c3df7759ad2780"},
|
{url = "https://files.pythonhosted.org/packages/85/c9/d465977afb008f0721e63fdf9a0e8e37bf6f188dc3c787b6ec9568621a48/pycryptodomex-3.15.0-cp35-abi3-win32.whl", hash = "sha256:46b3f05f2f7ac7841053da4e0f69616929ca3c42f238c405f6c3df7759ad2780"},
|
||||||
{url = "https://files.pythonhosted.org/packages/89/71/bd68f1c0654ee9c82a841feb41b8e05328ff4c154eeae98508e51d163256/pycryptodomex-3.15.0-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:65204412d0c6a8e3c41e21e93a5e6054a74fea501afa03046a388cf042e3377a"},
|
{url = "https://files.pythonhosted.org/packages/89/71/bd68f1c0654ee9c82a841feb41b8e05328ff4c154eeae98508e51d163256/pycryptodomex-3.15.0-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:65204412d0c6a8e3c41e21e93a5e6054a74fea501afa03046a388cf042e3377a"},
|
||||||
{url = "https://files.pythonhosted.org/packages/89/d2/1666f32e1ab5c2716447821800ecb39764d00453fddb23d45fee4d422bde/pycryptodomex-3.15.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:996e1ba717077ce1e6d4849af7a1426f38b07b3d173b879e27d5e26d2e958beb"},
|
{url = "https://files.pythonhosted.org/packages/89/d2/1666f32e1ab5c2716447821800ecb39764d00453fddb23d45fee4d422bde/pycryptodomex-3.15.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:996e1ba717077ce1e6d4849af7a1426f38b07b3d173b879e27d5e26d2e958beb"},
|
||||||
|
{url = "https://files.pythonhosted.org/packages/8a/c8/2fa65115759682e9505752e188a038d5cb4ea2f88ff4dba85ca0bfcc21f3/pycryptodomex-3.15.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:04a5d6a17560e987272fc1763e9772a87689a08427b8cbdebe3ca7cba95d6156"},
|
||||||
{url = "https://files.pythonhosted.org/packages/92/97/fafba850c72cffb4e794b827d196a01b1b7ba332686354cc5e01527e1bf1/pycryptodomex-3.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:3709f13ca3852b0b07fc04a2c03b379189232b24007c466be0f605dd4723e9d4"},
|
{url = "https://files.pythonhosted.org/packages/92/97/fafba850c72cffb4e794b827d196a01b1b7ba332686354cc5e01527e1bf1/pycryptodomex-3.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:3709f13ca3852b0b07fc04a2c03b379189232b24007c466be0f605dd4723e9d4"},
|
||||||
|
{url = "https://files.pythonhosted.org/packages/95/6f/48b103bea4ccc8351cf4a94af86d9c5799aac8de8882d00f8cbb59fcf703/pycryptodomex-3.15.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:7db44039cc8b449bd08ab338a074e87093bd170f1a1b76d2fcef8a3e2ee11199"},
|
||||||
{url = "https://files.pythonhosted.org/packages/99/72/1f22fc479774170abc6e10690e412155af071dea9bc9ce37f0a55ff241e1/pycryptodomex-3.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:f8be976cec59b11f011f790b88aca67b4ea2bd286578d0bd3e31bcd19afcd3e4"},
|
{url = "https://files.pythonhosted.org/packages/99/72/1f22fc479774170abc6e10690e412155af071dea9bc9ce37f0a55ff241e1/pycryptodomex-3.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:f8be976cec59b11f011f790b88aca67b4ea2bd286578d0bd3e31bcd19afcd3e4"},
|
||||||
{url = "https://files.pythonhosted.org/packages/99/b0/e44654a967809bb8c3225e40f4d58686784b6a3ea3aecae7710296a965ef/pycryptodomex-3.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:fc9bc7a9b79fe5c750fc81a307052f8daabb709bdaabb0fb18fb136b66b653b5"},
|
{url = "https://files.pythonhosted.org/packages/99/b0/e44654a967809bb8c3225e40f4d58686784b6a3ea3aecae7710296a965ef/pycryptodomex-3.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:fc9bc7a9b79fe5c750fc81a307052f8daabb709bdaabb0fb18fb136b66b653b5"},
|
||||||
{url = "https://files.pythonhosted.org/packages/a7/b0/e60dd0b215420dff6423614de65852bcba5e17539ae50c45acb4a29eac97/pycryptodomex-3.15.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:78d9621cf0ea35abf2d38fa2ca6d0634eab6c991a78373498ab149953787e5e5"},
|
{url = "https://files.pythonhosted.org/packages/a7/b0/e60dd0b215420dff6423614de65852bcba5e17539ae50c45acb4a29eac97/pycryptodomex-3.15.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:78d9621cf0ea35abf2d38fa2ca6d0634eab6c991a78373498ab149953787e5e5"},
|
||||||
|
@ -969,6 +971,7 @@ content_hash = "sha256:889439b64bada10d9afdf954f0b552fb3a224b91dfac0b20c3531ea0f
|
||||||
{url = "https://files.pythonhosted.org/packages/ce/1c/0f93364b744f75770ac4d928413f89a9978ab96cfe6416d84c808e754c40/pycryptodomex-3.15.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:b9279adc16e4b0f590ceff581f53a80179b02cba9056010d733eb4196134a870"},
|
{url = "https://files.pythonhosted.org/packages/ce/1c/0f93364b744f75770ac4d928413f89a9978ab96cfe6416d84c808e754c40/pycryptodomex-3.15.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:b9279adc16e4b0f590ceff581f53a80179b02cba9056010d733eb4196134a870"},
|
||||||
{url = "https://files.pythonhosted.org/packages/e5/3f/4e92e56ee0eefaedbb1bc4e85848b3fbbfed34f929a88e39088b2052ce1f/pycryptodomex-3.15.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e2b12968522a0358b8917fc7b28865acac002f02f4c4c6020fcb264d76bfd06d"},
|
{url = "https://files.pythonhosted.org/packages/e5/3f/4e92e56ee0eefaedbb1bc4e85848b3fbbfed34f929a88e39088b2052ce1f/pycryptodomex-3.15.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e2b12968522a0358b8917fc7b28865acac002f02f4c4c6020fcb264d76bfd06d"},
|
||||||
{url = "https://files.pythonhosted.org/packages/e7/c2/24b88c306d9ee909061e1535b355be43693be2e5557321417ca2448a1125/pycryptodomex-3.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:191e73bc84a8064ad1874dba0ebadedd7cce4dedee998549518f2c74a003b2e1"},
|
{url = "https://files.pythonhosted.org/packages/e7/c2/24b88c306d9ee909061e1535b355be43693be2e5557321417ca2448a1125/pycryptodomex-3.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:191e73bc84a8064ad1874dba0ebadedd7cce4dedee998549518f2c74a003b2e1"},
|
||||||
|
{url = "https://files.pythonhosted.org/packages/f7/4f/84cbd54a5a28cdc59df165c6f4cde9ea9847ae0b9055ebcc5a6b35913ba5/pycryptodomex-3.15.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:781efd04ea6762bb2ef7d4fa632c9c89895433744b6c345bd0c239d5ab058dfc"},
|
||||||
{url = "https://files.pythonhosted.org/packages/f8/e5/7e21896a03affd83cb9bc5cbf8576f63c5f6f26cccfb4196c874d466b5c7/pycryptodomex-3.15.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:5676a132169a1c1a3712edf25250722ebc8c9102aa9abd814df063ca8362454f"},
|
{url = "https://files.pythonhosted.org/packages/f8/e5/7e21896a03affd83cb9bc5cbf8576f63c5f6f26cccfb4196c874d466b5c7/pycryptodomex-3.15.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:5676a132169a1c1a3712edf25250722ebc8c9102aa9abd814df063ca8362454f"},
|
||||||
]
|
]
|
||||||
"pynacl 1.5.0" = [
|
"pynacl 1.5.0" = [
|
||||||
|
|
Loading…
Reference in a new issue