diff --git a/bots/__main__.py b/bots/__main__.py index 7f389eb..792323c 100644 --- a/bots/__main__.py +++ b/bots/__main__.py @@ -10,174 +10,16 @@ STABLE_TOKEN_ADDRESS, PROTOCOL_NAME, ) -from .data import Token, LiquidityPool -from .helpers import LOGGING_HANDLER, LOGGING_LEVEL, format_currency, format_percentage +from .data import Token +from .helpers import ( + LOGGING_HANDLER, + LOGGING_LEVEL, +) from .price import PriceBot from .tvl import TVLBot from .fees import FeesBot from .rewards import RewardsBot - - -import discord -from discord.ext import commands - -intents = discord.Intents.default() -intents.message_content = True - - -# Defines a custom Select containing colour options -# that the user can choose. The callback function -# of this class is called when the user changes their choice -class Dropdown(discord.ui.Select): - def __init__(self): - # Set the options that will be presented inside the dropdown - options = [ - discord.SelectOption( - label="Red", description="Your favourite colour is red", emoji="🟥" - ), - discord.SelectOption( - label="Green", description="Your favourite colour is green", emoji="🟩" - ), - discord.SelectOption( - label="Blue", description="Your favourite colour is blue", emoji="🟦" - ), - ] - - # The placeholder is what will be shown when no option is chosen - # The min and max values indicate we can only pick one of the three options - # The options parameter defines the dropdown options. We defined this above - super().__init__( - placeholder="Choose your favourite colour...", - min_values=1, - max_values=1, - options=options, - ) - - async def callback(self, interaction: discord.Interaction): - # Use the interaction object to send a response message containing - # the user's favourite colour or choice. The self object refers to the - # Select object, and the values attribute gets a list of the user's - # selected options. We only want the first one. - await interaction.response.send_message( - f"Your favourite colour is {self.values[0]}" - ) - - -class DropdownView(discord.ui.View): - def __init__(self): - super().__init__() - - # Adds the dropdown to our view object. - self.add_item(Dropdown()) - - -bot = commands.Bot(command_prefix=commands.when_mentioned_or("/"), intents=intents) - - -@bot.command() -async def pool(ctx, *args): - print(">>>>>>>>>>>>>>>>>> test command") - - # Create the view containing our dropdown - view = DropdownView() - - # Sending a message containing our view - await ctx.send("Choose your pool:", view=view) - - if len(args) == 0: - await ctx.send("Missing pool address: should be /pool POOL_ADDRESS_HERE") - else: - pool = await LiquidityPool.by_address(args[0]) - if pool is None: - await ctx.send("Nothing found") - else: - print(pool) - tvl = await LiquidityPool.tvl([pool]) - embedVar = discord.Embed( - title=f"{pool.symbol}", - description=" | ".join( - [ - f"{'Stable Pool' if pool.is_stable else 'Volatile Pool'}", - f"Trading fee: {format_percentage(pool.pool_fee_percentage)}", - f"TVL: ~{format_currency(tvl)}", - f"APR: {format_percentage(pool.apr(tvl))}", - ] - ), - color=0xFFFFFF, - ) - - embedVar.add_field(name="", value="", inline=False) - - # Volume - - embedVar.add_field(name="Volume", value="", inline=False) - embedVar.add_field( - name=" ", - value=format_currency(pool.volume), - inline=True, - ) - embedVar.add_field( - name=" ", - value=format_currency( - pool.token0_volume, symbol=pool.token0.symbol, prefix=False - ), - inline=True, - ) - embedVar.add_field( - name=" ", - value=format_currency( - pool.token1_volume, symbol=pool.token1.symbol, prefix=False - ), - inline=True, - ) - embedVar.add_field(name="", value="", inline=False) - - # Fees - - embedVar.add_field(name="Fees", value="", inline=False) - embedVar.add_field( - name=" ", - value=format_currency( - pool.token0_fees.amount_in_stable - + pool.token1_fees.amount_in_stable - ), - inline=True, - ) - embedVar.add_field( - name=" ", - value=format_currency( - pool.token0_fees.amount, symbol=pool.token0.symbol, prefix=False - ), - inline=True, - ) - embedVar.add_field( - name=" ", - value=format_currency( - pool.token1_fees.amount, symbol=pool.token1.symbol, prefix=False - ), - inline=True, - ) - embedVar.add_field(name="", value="", inline=False) - - # Pool balance - - embedVar.add_field(name="Pool Balance", value="", inline=False) - embedVar.add_field( - name=" ", - value=format_currency( - pool.reserve0.amount, symbol=pool.token0.symbol, prefix=False - ), - inline=True, - ) - embedVar.add_field( - name=" ", - value=format_currency( - pool.reserve1.amount, symbol=pool.token1.symbol, prefix=False - ), - inline=True, - ) - - await ctx.send(embed=embedVar) +from .commander import CommanderBot async def main(): @@ -191,19 +33,18 @@ async def main(): token = await Token.get_by_token_address(TOKEN_ADDRESS) stable = await Token.get_by_token_address(STABLE_TOKEN_ADDRESS) - # price_bot = PriceBot(source_token=token, target_token=stable) + price_bot = PriceBot(source_token=token, target_token=stable) tvl_bot = TVLBot(protocol_name=PROTOCOL_NAME) - # fees_bot = FeesBot(protocol_name=PROTOCOL_NAME) - # rewards_bot = RewardsBot(protocol_name=PROTOCOL_NAME) - - # await bot.start(DISCORD_TOKEN_REWARDS) + fees_bot = FeesBot(protocol_name=PROTOCOL_NAME) + rewards_bot = RewardsBot(protocol_name=PROTOCOL_NAME) + commander_bot = CommanderBot() await asyncio.gather( - bot.start(DISCORD_TOKEN_TVL), - # price_bot.start(DISCORD_TOKEN_PRICING), - # fees_bot.start(DISCORD_TOKEN_FEES), - # tvl_bot.start(DISCORD_TOKEN_TVL), - # rewards_bot.start(DISCORD_TOKEN_REWARDS), + price_bot.start(DISCORD_TOKEN_PRICING), + fees_bot.start(DISCORD_TOKEN_FEES), + tvl_bot.start(DISCORD_TOKEN_TVL), + rewards_bot.start(DISCORD_TOKEN_REWARDS), + commander_bot.start(DISCORD_TOKEN_TVL), ) diff --git a/bots/commander.py b/bots/commander.py new file mode 100644 index 0000000..a8829e8 --- /dev/null +++ b/bots/commander.py @@ -0,0 +1,63 @@ +from .data import LiquidityPool +from .helpers import is_address +from .ui import PoolsDropdown, PoolStats + +import discord +from discord.ext import commands + + +class _CommanderBot(commands.Bot): + def __init__(self): + intents = discord.Intents.default() + intents.message_content = True + super().__init__(command_prefix="/", intents=intents) + + async def on_ready(self): + print(f"Logged in as {self.user} (ID: {self.user.id})") + print("------") + await bot.tree.sync() + + +bot = _CommanderBot() + + +def CommanderBot() -> commands.Bot: + return bot + + +async def on_select_pool( + response: discord.InteractionResponse, + address_or_pool: str | LiquidityPool, +): + pool = ( + await LiquidityPool.by_address(address_or_pool) + if isinstance(address_or_pool, str) + else address_or_pool + ) + tvl = await LiquidityPool.tvl([pool]) + embed = await PoolStats().render(pool, tvl) + + await response.send_message(embed=embed) + + +@bot.tree.command(name="pool", description="Get data for specific pool") +@discord.app_commands.describe( + address_or_query="Pool address or search query", +) +async def pool(interaction: discord.Interaction, address_or_query: str): + if is_address(address_or_query): + pool = await LiquidityPool.by_address(address_or_query) + + if pool is not None: + await on_select_pool(interaction.response, pool) + else: + await interaction.response.send_message( + f"No pool found with this address: {address_or_query}" + ) + return + + pools = await LiquidityPool.search(address_or_query) + + await interaction.response.send_message( + "Choose a pool:", view=PoolsDropdown(pools=pools, callback=on_select_pool) + ) diff --git a/bots/data.py b/bots/data.py index df50f69..c00b47f 100644 --- a/bots/data.py +++ b/bots/data.py @@ -1,5 +1,6 @@ import functools import asyncio +from thefuzz import fuzz from web3 import AsyncWeb3, AsyncHTTPProvider from web3.constants import ADDRESS_ZERO from dataclasses import dataclass @@ -74,7 +75,7 @@ async def get_by_token_address(cls, token_address: str) -> "Token": normalized_address = normalize_address(token_address) tokens = await cls.get_all_listed_tokens() return next(t for t in tokens if t.token_address == normalized_address) - except: + except Exception: return None @@ -261,9 +262,20 @@ async def by_address(cls, address: str) -> "LiquidityPool": try: a = normalize_address(address) return next(pool for pool in pools if pool.lp == a) - except: + except Exception: return None + @classmethod + async def search(cls, query: str, limit: int = 10) -> List["LiquidityPool"]: + def match_score(query: str, symbol: str): + return fuzz.token_sort_ratio(query, symbol) + + pools = await cls.get_pools() + pools_with_ratio = list(map(lambda p: (p, match_score(query, p.symbol)), pools)) + pools_with_ratio.sort(key=lambda p: p[1], reverse=True) + + return list(map(lambda pwr: pwr[0], pools_with_ratio))[:limit] + @classmethod async def tvl(cls, pools) -> float: result = 0 diff --git a/bots/helpers.py b/bots/helpers.py index 294a6c1..85b4ef5 100644 --- a/bots/helpers.py +++ b/bots/helpers.py @@ -7,6 +7,10 @@ from async_lru import alru_cache +def is_address(value: str) -> bool: + return Web3.is_address(value) + + def cache_in_seconds(seconds: int): return alru_cache(ttl=seconds) diff --git a/bots/ui/__init__.py b/bots/ui/__init__.py new file mode 100644 index 0000000..5099191 --- /dev/null +++ b/bots/ui/__init__.py @@ -0,0 +1,2 @@ +from .pools import PoolsDropdown # noqa +from .pool_stats import PoolStats # noqa diff --git a/bots/ui/pool_stats.py b/bots/ui/pool_stats.py new file mode 100644 index 0000000..ec94a89 --- /dev/null +++ b/bots/ui/pool_stats.py @@ -0,0 +1,91 @@ +import discord +from ..data import LiquidityPool +from ..helpers import format_percentage, format_currency + + +class PoolStats: + async def render(self, pool: LiquidityPool, tvl: float): + embed = discord.Embed( + title=f"{pool.symbol}", + description=" | ".join( + [ + f"{'Stable Pool' if pool.is_stable else 'Volatile Pool'}", + f"Trading fee: {format_percentage(pool.pool_fee_percentage)}", + f"TVL: ~{format_currency(tvl)}", + f"APR: {format_percentage(pool.apr(tvl))}", + ] + ), + color=0xFFFFFF, + ) + + embed.add_field(name="", value="", inline=False) + + # Volume + + embed.add_field(name="Volume", value="", inline=False) + embed.add_field( + name=" ", + value=format_currency(pool.volume), + inline=True, + ) + embed.add_field( + name=" ", + value=format_currency( + pool.token0_volume, symbol=pool.token0.symbol, prefix=False + ), + inline=True, + ) + embed.add_field( + name=" ", + value=format_currency( + pool.token1_volume, symbol=pool.token1.symbol, prefix=False + ), + inline=True, + ) + embed.add_field(name="", value="", inline=False) + + # Fees + + embed.add_field(name="Fees", value="", inline=False) + embed.add_field( + name=" ", + value=format_currency( + pool.token0_fees.amount_in_stable + pool.token1_fees.amount_in_stable + ), + inline=True, + ) + embed.add_field( + name=" ", + value=format_currency( + pool.token0_fees.amount, symbol=pool.token0.symbol, prefix=False + ), + inline=True, + ) + embed.add_field( + name=" ", + value=format_currency( + pool.token1_fees.amount, symbol=pool.token1.symbol, prefix=False + ), + inline=True, + ) + embed.add_field(name="", value="", inline=False) + + # Pool balance + + embed.add_field(name="Pool Balance", value="", inline=False) + embed.add_field( + name=" ", + value=format_currency( + pool.reserve0.amount, symbol=pool.token0.symbol, prefix=False + ), + inline=True, + ) + embed.add_field( + name=" ", + value=format_currency( + pool.reserve1.amount, symbol=pool.token1.symbol, prefix=False + ), + inline=True, + ) + + return embed diff --git a/bots/ui/pools.py b/bots/ui/pools.py new file mode 100644 index 0000000..f64db6a --- /dev/null +++ b/bots/ui/pools.py @@ -0,0 +1,35 @@ +import discord +from ..data import LiquidityPool +from typing import List, Callable, Awaitable + +intents = discord.Intents.default() +intents.message_content = True + + +def build_select_option(pool: LiquidityPool) -> discord.SelectOption: + return discord.SelectOption(label=pool.symbol, value=pool.lp, emoji="🏊‍♀️") + + +class _PoolsDropdown(discord.ui.Select): + def __init__( + self, + pools: List[LiquidityPool], + callback: Callable[[discord.InteractionResponse, str], Awaitable[None]], + ): + options = list(map(build_select_option, pools)) + super().__init__( + placeholder="Which pool are you intersted in...", + min_values=1, + max_values=1, + options=options, + ) + self._callback = callback + + async def callback(self, interaction: discord.Interaction): + await self._callback(interaction.response, self.values[0]) + + +class PoolsDropdown(discord.ui.View): + def __init__(self, pools: List[LiquidityPool], callback): + super().__init__() + self.add_item(_PoolsDropdown(pools=pools, callback=callback)) diff --git a/poetry.lock b/poetry.lock index 9072d68..f4e1b93 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1344,6 +1344,108 @@ files = [ {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, ] +[[package]] +name = "rapidfuzz" +version = "3.5.2" +description = "rapid fuzzy string matching" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rapidfuzz-3.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1a047d6e58833919d742bbc0dfa66d1de4f79e8562ee195007d3eae96635df39"}, + {file = "rapidfuzz-3.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22877c027c492b7dc7e3387a576a33ed5aad891104aa90da2e0844c83c5493ef"}, + {file = "rapidfuzz-3.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e0f448b0eacbcc416feb634e1232a48d1cbde5e60f269c84e4fb0912f7bbb001"}, + {file = "rapidfuzz-3.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05146497672f869baf41147d5ec1222788c70e5b8b0cfcd6e95597c75b5b96b"}, + {file = "rapidfuzz-3.5.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f2df3968738a38d2a0058b5e721753f5d3d602346a1027b0dde31b0476418f3"}, + {file = "rapidfuzz-3.5.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5afc1fcf1830f9bb87d3b490ba03691081b9948a794ea851befd2643069a30c1"}, + {file = "rapidfuzz-3.5.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84be69ea65f64fa01e5c4976be9826a5aa949f037508887add42da07420d65d6"}, + {file = "rapidfuzz-3.5.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8658c1045766e87e0038323aa38b4a9f49b7f366563271f973c8890a98aa24b5"}, + {file = "rapidfuzz-3.5.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:852b3f93c15fce58b8dc668bd54123713bfdbbb0796ba905ea5df99cfd083132"}, + {file = "rapidfuzz-3.5.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:12424a06ad9bd0cbf5f7cea1015e78d924a0034a0e75a5a7b39c0703dcd94095"}, + {file = "rapidfuzz-3.5.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b4e9ded8e80530bd7205a7a2b01802f934a4695ca9e9fbe1ce9644f5e0697864"}, + {file = "rapidfuzz-3.5.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:affb8fe36157c2dc8a7bc45b6a1875eb03e2c49167a1d52789144bdcb7ab3b8c"}, + {file = "rapidfuzz-3.5.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1d33a622572d384f4c90b5f7a139328246ab5600141e90032b521c2127bd605"}, + {file = "rapidfuzz-3.5.2-cp310-cp310-win32.whl", hash = "sha256:2cf9f2ed4a97b388cffd48d534452a564c2491f68f4fd5bc140306f774ceb63a"}, + {file = "rapidfuzz-3.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:6541ffb70097885f7302cd73e2efd77be99841103023c2f9408551f27f45f7a5"}, + {file = "rapidfuzz-3.5.2-cp310-cp310-win_arm64.whl", hash = "sha256:1dd2542e5103fb8ca46500a979ae14d1609dcba11d2f9fe01e99eec03420e193"}, + {file = "rapidfuzz-3.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bff7d3127ebc5cd908f3a72f6517f31f5247b84666137556a8fcc5177c560939"}, + {file = "rapidfuzz-3.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fdfdb3685b631d8efbb6d6d3d86eb631be2b408d9adafcadc11e63e3f9c96dec"}, + {file = "rapidfuzz-3.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:97b043fe8185ec53bb3ff0e59deb89425c0fc6ece6e118939963aab473505801"}, + {file = "rapidfuzz-3.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a4a7832737f87583f3863dc62e6f56dd4a9fefc5f04a7bdcb4c433a0f36bb1b"}, + {file = "rapidfuzz-3.5.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d876dba9a11fcf60dcf1562c5a84ef559db14c2ceb41e1ad2d93cd1dc085889"}, + {file = "rapidfuzz-3.5.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa4c0612893716bbb6595066ca9ecb517c982355abe39ba9d1f4ab834ace91ad"}, + {file = "rapidfuzz-3.5.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:120316824333e376b88b284724cfd394c6ccfcb9818519eab5d58a502e5533f0"}, + {file = "rapidfuzz-3.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cdbe8e80cc186d55f748a34393533a052d855357d5398a1ccb71a5021b58e8d"}, + {file = "rapidfuzz-3.5.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1062425c8358a547ae5ebad148f2e0f02417716a571b803b0c68e4d552e99d32"}, + {file = "rapidfuzz-3.5.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:66be181965aff13301dd5f9b94b646ce39d99c7fe2fd5de1656f4ca7fafcb38c"}, + {file = "rapidfuzz-3.5.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:53df7aea3cf301633cfa2b4b2c2d2441a87dfc878ef810e5b4eddcd3e68723ad"}, + {file = "rapidfuzz-3.5.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:76639dca5eb0afc6424ac5f42d43d3bd342ac710e06f38a8c877d5b96de09589"}, + {file = "rapidfuzz-3.5.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:27689361c747b5f7b8a26056bc60979875323f1c3dcaaa9e2fec88f03b20a365"}, + {file = "rapidfuzz-3.5.2-cp311-cp311-win32.whl", hash = "sha256:99c9fc5265566fb94731dc6826f43c5109e797078264e6389a36d47814473692"}, + {file = "rapidfuzz-3.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:666928ee735562a909d81bd2f63207b3214afd4ca41f790ab3025d066975c814"}, + {file = "rapidfuzz-3.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:d55de67c48f06b7772541e8d4c062a2679205799ce904236e2836cb04c106442"}, + {file = "rapidfuzz-3.5.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:04e1e02b182283c43c866e215317735e91d22f5d34e65400121c04d5ed7ed859"}, + {file = "rapidfuzz-3.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:365e544aba3ac13acf1a62cb2e5909ad2ba078d0bfc7d69b1f801dfd673b9782"}, + {file = "rapidfuzz-3.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b61f77d834f94b0099fa9ed35c189b7829759d4e9c2743697a130dd7ba62259f"}, + {file = "rapidfuzz-3.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43fb368998b9703fa8c63db292a8ab9e988bf6da0c8a635754be8e69da1e7c1d"}, + {file = "rapidfuzz-3.5.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25510b5d142c47786dbd27cfd9da7cae5bdea28d458379377a3644d8460a3404"}, + {file = "rapidfuzz-3.5.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf3093443751e5a419834162af358d1e31dec75f84747a91dbbc47b2c04fc085"}, + {file = "rapidfuzz-3.5.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fbaf546f15a924613f89d609ff66b85b4f4c2307ac14d93b80fe1025b713138"}, + {file = "rapidfuzz-3.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32d580df0e130ed85400ff77e1c32d965e9bc7be29ac4072ab637f57e26d29fb"}, + {file = "rapidfuzz-3.5.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:358a0fbc49343de20fee8ebdb33c7fa8f55a9ff93ff42d1ffe097d2caa248f1b"}, + {file = "rapidfuzz-3.5.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fb379ac0ddfc86c5542a225d194f76ed468b071b6f79ff57c4b72e635605ad7d"}, + {file = "rapidfuzz-3.5.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7fb21e182dc6d83617e88dea002963d5cf99cf5eabbdbf04094f503d8fe8d723"}, + {file = "rapidfuzz-3.5.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c04f9f1310ce414ab00bdcbf26d0906755094bfc59402cb66a7722c6f06d70b2"}, + {file = "rapidfuzz-3.5.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6da61cc38c1a95efc5edcedf258759e6dbab73191651a28c5719587f32a56ad"}, + {file = "rapidfuzz-3.5.2-cp312-cp312-win32.whl", hash = "sha256:f823fd1977071486739f484e27092765d693da6beedaceece54edce1dfeec9b2"}, + {file = "rapidfuzz-3.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:a8162d81486de85ab1606e48e076431b66d44cf431b2b678e9cae458832e7147"}, + {file = "rapidfuzz-3.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:dfc63fabb7d8da8483ca836bae7e55766fe39c63253571e103c034ba8ea80950"}, + {file = "rapidfuzz-3.5.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:df8fae2515a1e4936affccac3e7d506dd904de5ff82bc0b1433b4574a51b9bfb"}, + {file = "rapidfuzz-3.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dd6384780c2a16097d47588844cd677316a90e0f41ef96ff485b62d58de79dcf"}, + {file = "rapidfuzz-3.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:467a4d730ae3bade87dba6bd769e837ab97e176968ce20591fe8f7bf819115b1"}, + {file = "rapidfuzz-3.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54576669c1502b751b534bd76a4aeaaf838ed88b30af5d5c1b7d0a3ca5d4f7b5"}, + {file = "rapidfuzz-3.5.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abafeb82f85a651a9d6d642a33dc021606bc459c33e250925b25d6b9e7105a2e"}, + {file = "rapidfuzz-3.5.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73e14617a520c0f1bc15eb78c215383477e5ca70922ecaff1d29c63c060e04ca"}, + {file = "rapidfuzz-3.5.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7cdf92116e9dfe40da17f921cdbfa0039dde9eb158914fa5f01b1e67a20b19cb"}, + {file = "rapidfuzz-3.5.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1962d5ccf8602589dbf8e85246a0ee2b4050d82fade1568fb76f8a4419257704"}, + {file = "rapidfuzz-3.5.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:db45028eae2fda7a24759c69ebeb2a7fbcc1a326606556448ed43ee480237a3c"}, + {file = "rapidfuzz-3.5.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b685abb8b6d97989f6c69556d7934e0e533aa8822f50b9517ff2da06a1d29f23"}, + {file = "rapidfuzz-3.5.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:40139552961018216b8cd88f6df4ecbbe984f907a62a5c823ccd907132c29a14"}, + {file = "rapidfuzz-3.5.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0fef4705459842ef8f79746d6f6a0b5d2b6a61a145d7d8bbe10b2e756ea337c8"}, + {file = "rapidfuzz-3.5.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6b2ad5516f7068c7d9cbcda8ac5906c589e99bc427df2e1050282ee2d8bc2d58"}, + {file = "rapidfuzz-3.5.2-cp38-cp38-win32.whl", hash = "sha256:2da3a24c2f7dfca7f26ba04966b848e3bbeb93e54d899908ff88dfe3e1def9dc"}, + {file = "rapidfuzz-3.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:e3f2be79d4114d01f383096dbee51b57df141cb8b209c19d0cf65f23a24e75ba"}, + {file = "rapidfuzz-3.5.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:089a7e96e5032821af5964d8457fcb38877cc321cdd06ad7c5d6e3d852264cb9"}, + {file = "rapidfuzz-3.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75d8a52bf8d1aa2ac968ae4b21b83b94fc7e5ea3dfbab34811fc60f32df505b2"}, + {file = "rapidfuzz-3.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2bacce6bbc0362f0789253424269cc742b1f45e982430387db3abe1d0496e371"}, + {file = "rapidfuzz-3.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5fd627e604ddc02db2ddb9ddc4a91dd92b7a6d6378fcf30bb37b49229072b89"}, + {file = "rapidfuzz-3.5.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2e8b369f23f00678f6e673572209a5d3b0832f4991888e3df97af7b8b9decf3"}, + {file = "rapidfuzz-3.5.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c29958265e4c2b937269e804b8a160c027ee1c2627d6152655008a8b8083630e"}, + {file = "rapidfuzz-3.5.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00be97f9219355945c46f37ac9fa447046e6f7930f7c901e5d881120d1695458"}, + {file = "rapidfuzz-3.5.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada0d8d57e0f556ef38c24fee71bfe8d0db29c678bff2acd1819fc1b74f331c2"}, + {file = "rapidfuzz-3.5.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de89585268ed8ee44e80126814cae63ff6b00d08416481f31b784570ef07ec59"}, + {file = "rapidfuzz-3.5.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:908ff2de9c442b379143d1da3c886c63119d4eba22986806e2533cee603fe64b"}, + {file = "rapidfuzz-3.5.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:54f0061028723c026020f5bb20649c22bc8a0d9f5363c283bdc5901d4d3bff01"}, + {file = "rapidfuzz-3.5.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b581107ec0c610cdea48b25f52030770be390db4a9a73ca58b8d70fa8a5ec32e"}, + {file = "rapidfuzz-3.5.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1d5a686ea258931aaa38019204bdc670bbe14b389a230b1363d84d6cf4b9dc38"}, + {file = "rapidfuzz-3.5.2-cp39-cp39-win32.whl", hash = "sha256:97f811ca7709c6ee8c0b55830f63b3d87086f4abbcbb189b4067e1cd7014db7b"}, + {file = "rapidfuzz-3.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:58ee34350f8c292dd24a050186c0e18301d80da904ef572cf5fda7be6a954929"}, + {file = "rapidfuzz-3.5.2-cp39-cp39-win_arm64.whl", hash = "sha256:c5075ce7b9286624cafcf36720ef1cfb2946d75430b87cb4d1f006e82cd71244"}, + {file = "rapidfuzz-3.5.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:af5221e4f7800db3e84c46b79dba4112e3b3cc2678f808bdff4fcd2487073846"}, + {file = "rapidfuzz-3.5.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8501d7875b176930e6ed9dbc1bc35adb37ef312f6106bd6bb5c204adb90160ac"}, + {file = "rapidfuzz-3.5.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e414e1ca40386deda4291aa2d45062fea0fbaa14f95015738f8bb75c4d27f862"}, + {file = "rapidfuzz-3.5.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2059cd73b7ea779a9307d7a78ed743f0e3d33b88ccdcd84569abd2953cd859f"}, + {file = "rapidfuzz-3.5.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:58e3e21f6f13a7cca265cce492bc797425bd4cb2025fdd161a9e86a824ad65ce"}, + {file = "rapidfuzz-3.5.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b847a49377e64e92e11ef3d0a793de75451526c83af015bdafdd5d04de8a058a"}, + {file = "rapidfuzz-3.5.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a42c7a8c62b29c4810e39da22b42524295fcb793f41c395c2cb07c126b729e83"}, + {file = "rapidfuzz-3.5.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51b5166be86e09e011e92d9862b1fe64c4c7b9385f443fb535024e646d890460"}, + {file = "rapidfuzz-3.5.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f808dcb0088a7a496cc9895e66a7b8de55ffea0eb9b547c75dfb216dd5f76ed"}, + {file = "rapidfuzz-3.5.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d4b05a8f4ab7e7344459394094587b033fe259eea3a8720035e8ba30e79ab39b"}, + {file = "rapidfuzz-3.5.2.tar.gz", hash = "sha256:9e9b395743e12c36a3167a3a9fd1b4e11d92fb0aa21ec98017ee6df639ed385e"}, +] + +[package.extras] +full = ["numpy"] + [[package]] name = "referencing" version = "0.30.2" @@ -1606,6 +1708,20 @@ files = [ {file = "rpds_py-0.10.6.tar.gz", hash = "sha256:4ce5a708d65a8dbf3748d2474b580d606b1b9f91b5c6ab2a316e0b0cf7a4ba50"}, ] +[[package]] +name = "thefuzz" +version = "0.20.0" +description = "Fuzzy string matching in python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "thefuzz-0.20.0-py3-none-any.whl", hash = "sha256:bd2b657a12bd8518917d2d71c53125368706233b822fac688fca956730154388"}, + {file = "thefuzz-0.20.0.tar.gz", hash = "sha256:a25e49786b1c4603c7fc6e2d69e6bc660982a2919698b536ff8354e0631cc40d"}, +] + +[package.dependencies] +rapidfuzz = ">=3.0.0,<4.0.0" + [[package]] name = "tomli" version = "2.0.1" @@ -1862,4 +1978,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "a498c2243598ca7d9a2950ef64fa3655cc1841db8bacd1cc3f73f79fe0b476ee" +content-hash = "5515a802834ef5aff0cdb470274917b1241d112d6d1ba9b162b791e809ead996" diff --git a/pyproject.toml b/pyproject.toml index 0446d0d..b97488a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ protobuf = "4.21.6" async-lru = "2.0.4" discord-py = "2.3.2" python-dotenv = "1.0.0" +thefuzz = "^0.20.0" [tool.poetry.group.dev.dependencies] pytest = "^7.4.3"