From e7b553662d8c70a3d8a089f050970a430e2424f6 Mon Sep 17 00:00:00 2001 From: "Nyeki.py" <84998222+Nekidev@users.noreply.github.com> Date: Fri, 18 Oct 2024 19:44:12 -0300 Subject: [PATCH] fix(db): :zap: Add a messages flushing queue. --- payphone/bot.py | 25 +++--- payphone/cogs/calls/cog.py | 171 ++++++++++++++++++++++--------------- payphone/cogs/mods/cog.py | 13 +-- payphone/core/checks.py | 3 +- payphone/core/constants.py | 3 + payphone/core/redis.py | 6 ++ pyproject.toml | 1 + uv.lock | 26 ++++++ 8 files changed, 158 insertions(+), 90 deletions(-) create mode 100644 payphone/core/redis.py diff --git a/payphone/bot.py b/payphone/bot.py index f5da2fd..1f3a65b 100644 --- a/payphone/bot.py +++ b/payphone/bot.py @@ -9,16 +9,13 @@ from discord.ext.commands import errors from tortoise import expressions, transactions, exceptions as tortoise_exc -import redis.asyncio as aredis - from payphone.db import setup, models -from payphone.core import config, constants, exceptions, formatting, help_command +from payphone.core import constants, exceptions, formatting, help_command +from payphone.core.redis import redis # TODO: Make it an AutoShardedBot on production. class Payphone(commands.AutoShardedBot): - redis = aredis.Redis(host="localhost" if config.IS_DEBUG else "redis") - def __init__(self) -> None: intents = discord.Intents.default() intents.message_content = True @@ -43,9 +40,9 @@ class Payphone(commands.AutoShardedBot): await setup.init() - await self.redis.ping() + await redis.ping() - await self.redis.delete( + await redis.delete( constants.CHANNELS_ONCALL_SET, constants.CHANNELS_SEARCH_QUEUE ) @@ -125,7 +122,7 @@ class Payphone(commands.AutoShardedBot): user_id = user if isinstance(user, int) else user.id key = constants.BAN_TEMPLATE % user_id - is_banned = await self.redis.get(key) + is_banned = await redis.get(key) if is_banned is not None: return bool(int(is_banned)) @@ -139,7 +136,7 @@ class Payphone(commands.AutoShardedBot): ), ).exists() - await self.redis.set(key, int(is_banned), ex=300) + await redis.set(key, int(is_banned), ex=300) return is_banned @@ -204,7 +201,7 @@ class Payphone(commands.AutoShardedBot): banned_by_id=mod_id, ) - await self.redis.set(key, "1", ex=300) + await redis.set(key, "1", ex=300) return row @@ -231,7 +228,7 @@ class Payphone(commands.AutoShardedBot): user_id=user_id, ).update(expires_at=datetime.now(timezone.utc)) - await self.redis.set(key, "0", ex=300) + await redis.set(key, "0", ex=300) async def get_prefix( self, message: discord.Message | None = None, server_id: int | None = None @@ -274,7 +271,7 @@ class Payphone(commands.AutoShardedBot): key = constants.PREFIX_TEMPLATE % server_id - prefix: bytes | None = await self.redis.get(key) + prefix: bytes | None = await redis.get(key) if prefix is not None: prefix = prefix.decode() @@ -282,7 +279,7 @@ class Payphone(commands.AutoShardedBot): else: server, _ = await models.Server.get_or_create(id=server_id) - await self.redis.set(key, server.prefix, ex=60 * 60) + await redis.set(key, server.prefix, ex=60 * 60) prefix = server.prefix @@ -314,7 +311,7 @@ class Payphone(commands.AutoShardedBot): await models.Server.filter(id=server_id).update(prefix=prefix) - await self.redis.set(constants.PREFIX_TEMPLATE % server_id, prefix, ex=60 * 60) + await redis.set(constants.PREFIX_TEMPLATE % server_id, prefix, ex=60 * 60) async def on_command_error( self, context: commands.Context, exception: commands.CommandError diff --git a/payphone/cogs/calls/cog.py b/payphone/cogs/calls/cog.py index 283381f..8c7bb82 100644 --- a/payphone/cogs/calls/cog.py +++ b/payphone/cogs/calls/cog.py @@ -9,15 +9,16 @@ from discord.utils import find from tortoise import transactions, exceptions -import asyncpg.exceptions - import aiohttp +import orjson + from payphone import bot from payphone.db import models, utils from payphone.cogs import base from payphone.cogs.calls import embeds from payphone.core import constants, checks, censorship, formatting +from payphone.core.redis import redis class Calls(base.Cog): @@ -27,11 +28,44 @@ class Calls(base.Cog): self.hangup_calls.add_exception_type(exceptions.ConfigurationError) self.hangup_calls.start() + self.flush_messages.add_exception_type(TypeError) + self.flush_messages.start() + def cog_unload(self) -> None: self.hangup_calls.cancel() + self.flush_messages.cancel() return super().cog_unload() + @tasks.loop(seconds=5) + async def flush_messages(self) -> None: + """Flush all the in-memory messages and message contents to the DB.""" + + pipe = redis.pipeline() + + pipe.smembers(constants.MESSAGES_QUEUE) + pipe.smembers(constants.MESSAGE_CONTENTS_QUEUE) + + pipe.delete( + constants.MESSAGES_QUEUE, + constants.MESSAGE_CONTENTS_QUEUE, + constants.MESSAGES_TO_FLUSH_IDS, + ) + + messages, message_contents, _ = await pipe.execute() + + messages = [models.Message(**orjson.loads(msg)) for msg in messages] + message_contents = [ + models.MessageContent(**orjson.loads(msg)) for msg in message_contents + ] + + await models.Message.bulk_create(messages, batch_size=500) + await models.MessageContent.bulk_create(message_contents, batch_size=500) + + print( + f"DONE! Flushed {len(messages):,} messages and {len(message_contents):,} message contents." + ) + @tasks.loop(seconds=15) async def hangup_calls(self) -> None: """Hangs up inactive calls. @@ -74,9 +108,7 @@ class Calls(base.Cog): if len(hung_up_channel_ids) > 0: await models.Channel.filter(id__in=hung_up_channel_ids).update(call=None) - await self.bot.redis.srem( - constants.CHANNELS_ONCALL_SET, *hung_up_channel_ids - ) + await redis.srem(constants.CHANNELS_ONCALL_SET, *hung_up_channel_ids) @commands.command(aliases=["c"]) @commands.guild_only() @@ -94,7 +126,7 @@ class Calls(base.Cog): user_id = ctx.author.id channel_id = ctx.channel.id - is_on_call = await self.bot.redis.sismember( + is_on_call = await redis.sismember( constants.CHANNELS_ONCALL_SET, str(channel_id) ) @@ -104,7 +136,7 @@ class Calls(base.Cog): ) return - is_searching = await self.bot.redis.sismember( + is_searching = await redis.sismember( constants.CHANNELS_SEARCH_QUEUE, str(channel_id) ) @@ -153,9 +185,7 @@ class Calls(base.Cog): if last_calls_rss_channels_map[call_rs.call_id] != call_rs.channel_id: last_calls_other_channels_ids.add(call_rs.channel_id) - other_channel_id: int = await self.bot.redis.spop( - constants.CHANNELS_SEARCH_QUEUE - ) + other_channel_id: int = await redis.spop(constants.CHANNELS_SEARCH_QUEUE) i = 0 while True: @@ -173,13 +203,9 @@ class Calls(base.Cog): i += 1 if other_channel_id in last_calls_other_channels_ids: - await self.bot.redis.sadd( - constants.CHANNELS_SEARCH_QUEUE, str(other_channel_id) - ) + await redis.sadd(constants.CHANNELS_SEARCH_QUEUE, str(other_channel_id)) - other_channel_id = await self.bot.redis.spop( - constants.CHANNELS_SEARCH_QUEUE - ) + other_channel_id = await redis.spop(constants.CHANNELS_SEARCH_QUEUE) if other_channel_id is None: break @@ -191,7 +217,7 @@ class Calls(base.Cog): channel = await utils.get_channel(ctx.channel) if other_channel_id is not None: - await self.bot.redis.sadd( + await redis.sadd( constants.CHANNELS_ONCALL_SET, str(channel_id), str(other_channel_id) ) @@ -220,14 +246,12 @@ class Calls(base.Cog): ).update(call=call) except errors.DiscordException: - await self.bot.redis.srem( + await redis.srem( constants.CHANNELS_ONCALL_SET, str(channel_id), str(other_channel_id), ) - await self.bot.redis.sadd( - constants.CHANNELS_SEARCH_QUEUE, str(channel_id) - ) + await redis.sadd(constants.CHANNELS_SEARCH_QUEUE, str(channel_id)) msg_phone_answered = "" await ctx.send( @@ -239,7 +263,7 @@ class Calls(base.Cog): ) else: - await self.bot.redis.sadd(constants.CHANNELS_SEARCH_QUEUE, str(channel_id)) + await redis.sadd(constants.CHANNELS_SEARCH_QUEUE, str(channel_id)) await ctx.send( ( @@ -264,7 +288,7 @@ class Calls(base.Cog): user_id = ctx.author.id channel_id = ctx.channel.id - is_on_call = await self.bot.redis.sismember( + is_on_call = await redis.sismember( constants.CHANNELS_ONCALL_SET, str(channel_id) ) @@ -292,7 +316,7 @@ class Calls(base.Cog): except errors.DiscordException: pass - await self.bot.redis.srem( + await redis.srem( constants.CHANNELS_ONCALL_SET, str(channel_id), str(other_channel.id) ) @@ -335,9 +359,7 @@ class Calls(base.Cog): if last_calls_rss_channels_map[call_rs.call_id] != call_rs.channel_id: last_calls_other_channels_ids.add(call_rs.channel_id) - other_channel_id: int = await self.bot.redis.spop( - constants.CHANNELS_SEARCH_QUEUE - ) + other_channel_id: int = await redis.spop(constants.CHANNELS_SEARCH_QUEUE) i = 0 while True: @@ -355,13 +377,9 @@ class Calls(base.Cog): i += 1 if other_channel_id in last_calls_other_channels_ids: - await self.bot.redis.sadd( - constants.CHANNELS_SEARCH_QUEUE, str(other_channel_id) - ) + await redis.sadd(constants.CHANNELS_SEARCH_QUEUE, str(other_channel_id)) - other_channel_id = await self.bot.redis.spop( - constants.CHANNELS_SEARCH_QUEUE - ) + other_channel_id = await redis.spop(constants.CHANNELS_SEARCH_QUEUE) if other_channel_id is None: break @@ -371,7 +389,7 @@ class Calls(base.Cog): break if other_channel_id is not None: - await self.bot.redis.sadd( + await redis.sadd( constants.CHANNELS_ONCALL_SET, str(channel_id), str(other_channel_id) ) @@ -400,14 +418,12 @@ class Calls(base.Cog): ).update(call=call) except errors.DiscordException: - await self.bot.redis.srem( + await redis.srem( constants.CHANNELS_ONCALL_SET, str(channel_id), str(other_channel_id), ) - await self.bot.redis.sadd( - constants.CHANNELS_SEARCH_QUEUE, str(channel_id) - ) + await redis.sadd(constants.CHANNELS_SEARCH_QUEUE, str(channel_id)) msg_phone_answered = "" await ctx.send( @@ -423,7 +439,7 @@ class Calls(base.Cog): ) else: - await self.bot.redis.sadd(constants.CHANNELS_SEARCH_QUEUE, str(channel_id)) + await redis.sadd(constants.CHANNELS_SEARCH_QUEUE, str(channel_id)) channel.call = None await channel.save() @@ -447,7 +463,7 @@ class Calls(base.Cog): channel_id = ctx.channel.id - is_on_call = await self.bot.redis.sismember( + is_on_call = await redis.sismember( constants.CHANNELS_ONCALL_SET, str(channel_id) ) @@ -462,7 +478,7 @@ class Calls(base.Cog): lambda c: c.id != channel_id, call.channels ) - await self.bot.redis.srem( + await redis.srem( constants.CHANNELS_ONCALL_SET, *( [str(channel_id)] @@ -493,12 +509,12 @@ class Calls(base.Cog): call.ended_at = datetime.now(timezone.utc) await call.save() - is_searching = await self.bot.redis.sismember( + is_searching = await redis.sismember( constants.CHANNELS_SEARCH_QUEUE, str(channel_id) ) if is_searching: - await self.bot.redis.srem(constants.CHANNELS_SEARCH_QUEUE, str(channel_id)) + await redis.srem(constants.CHANNELS_SEARCH_QUEUE, str(channel_id)) await ctx.send(f"☎️ {secrets.choice(constants.MSGS_PHONE_NO_ANSWER)}") @@ -571,7 +587,7 @@ class Calls(base.Cog): await models.Channel.bulk_update(updated_channels, ["call_id"]) - await self.bot.redis.srem( + await redis.srem( constants.CHANNELS_ONCALL_SET, str(updated_channels[0]), str(updated_channels[1]), @@ -581,7 +597,7 @@ class Calls(base.Cog): other_channel = find(lambda c: c.id != channel_id, updated_channels) - await self.bot.redis.srem( + await redis.srem( constants.CHANNELS_ONCALL_SET, str(other_channel.id), str(channel_id) ) @@ -651,7 +667,7 @@ class Calls(base.Cog): user_id = message.author.id channel_id = message.channel.id - is_on_call = await self.bot.redis.sismember( + is_on_call = await redis.sismember( constants.CHANNELS_ONCALL_SET, str(channel_id) ) @@ -671,7 +687,7 @@ class Calls(base.Cog): ) except exceptions.DoesNotExist: - await self.bot.redis.srem(constants.CHANNELS_ONCALL_SET, str(channel_id)) + await redis.srem(constants.CHANNELS_ONCALL_SET, str(channel_id)) return other_channel: models.Channel = find( @@ -747,28 +763,45 @@ class Calls(base.Cog): wait=True, ) - while True: - try: - message_row = await models.Message.create( - id=message.id, - call=call, - channel=channel, - user=user, - profile=profile, - mask=call_rs.mask if call_rs.is_masked else None, - propagation_id=webhook_message.id if webhook_message else None, - propagation_channel=other_channel if webhook_message else None, - ) - break + pipe = redis.pipeline() - except asyncpg.exceptions.DeadlockDetectedError: - continue - - await models.MessageContent.create( - message=message_row, - content=cleaned_content, - was_propagated=not bool(blocked_word), + pipe.sadd( + constants.MESSAGES_QUEUE, + orjson.dumps( + { + "id": message.id, + "call_id": call.id, + "channel_id": channel.id, + "user_id": user.id, + "profile_id": profile.id, + "mask": call_rs.mask if call_rs.is_masked else None, + "propagation_id": webhook_message.id if webhook_message else None, + "propagation_channel_id": ( + other_channel.id if webhook_message else None + ), + "replying_to_id": ( + message.reference.message_id + if message.reference is not None + else None + ), + "created_at": datetime.now(timezone.utc), + } + ), ) + pipe.sadd( + constants.MESSAGE_CONTENTS_QUEUE, + orjson.dumps( + { + "message_id": message.id, + "content": cleaned_content, + "was_propagated": not bool(blocked_word), + "created_at": datetime.now(timezone.utc), + } + ), + ) + pipe.sadd(constants.MESSAGES_TO_FLUSH_IDS, message.id) + + await pipe.execute() @commands.Cog.listener() @commands.guild_only() @@ -785,7 +818,7 @@ class Calls(base.Cog): message_id = payload.message_id channel_id = payload.channel_id - is_on_call = await self.bot.redis.sismember( + is_on_call = await redis.sismember( constants.CHANNELS_ONCALL_SET, str(channel_id) ) @@ -852,7 +885,7 @@ class Calls(base.Cog): if censorship.moderate(content) is not None: return - is_on_call = await self.bot.redis.sismember( + is_on_call = await redis.sismember( constants.CHANNELS_ONCALL_SET, str(channel_id) ) diff --git a/payphone/cogs/mods/cog.py b/payphone/cogs/mods/cog.py index 4f2e998..a799654 100644 --- a/payphone/cogs/mods/cog.py +++ b/payphone/cogs/mods/cog.py @@ -16,6 +16,7 @@ from payphone.db import models, utils from payphone.cogs import base from payphone.cogs.mods import embeds, views from payphone.core import checks, converters, constants, formatting as fmt +from payphone.core.redis import redis class Mods(base.Cog): @@ -243,10 +244,10 @@ class Mods(base.Cog): """ oncall_channel_ids = list( - await self.bot.redis.smembers(constants.CHANNELS_ONCALL_SET) + await redis.smembers(constants.CHANNELS_ONCALL_SET) ) searching_channel_ids = list( - await self.bot.redis.smembers(constants.CHANNELS_SEARCH_QUEUE) + await redis.smembers(constants.CHANNELS_SEARCH_QUEUE) ) channel_ids = [int(c) for c in oncall_channel_ids] + [ @@ -272,10 +273,10 @@ class Mods(base.Cog): await asyncio.sleep(seconds) oncall_channel_ids = list( - await self.bot.redis.smembers(constants.CHANNELS_ONCALL_SET) + await redis.smembers(constants.CHANNELS_ONCALL_SET) ) searching_channel_ids = list( - await self.bot.redis.smembers(constants.CHANNELS_SEARCH_QUEUE) + await redis.smembers(constants.CHANNELS_SEARCH_QUEUE) ) channel_ids = [int(c) for c in oncall_channel_ids] + [ @@ -311,11 +312,11 @@ class Mods(base.Cog): stats = [ ( "On-call Channels", - lambda: self.bot.redis.scard(constants.CHANNELS_ONCALL_SET), + lambda: redis.scard(constants.CHANNELS_ONCALL_SET), ), ( "Searching Channels", - lambda: self.bot.redis.scard(constants.CHANNELS_SEARCH_QUEUE), + lambda: redis.scard(constants.CHANNELS_SEARCH_QUEUE), ), ( "Messages", diff --git a/payphone/core/checks.py b/payphone/core/checks.py index 674b6be..6a9356e 100644 --- a/payphone/core/checks.py +++ b/payphone/core/checks.py @@ -2,6 +2,7 @@ from discord.ext import commands from payphone.db import utils from payphone.core import exceptions, constants +from payphone.core.redis import redis def is_moderator(): @@ -39,7 +40,7 @@ def is_on_call(): async def predicate(ctx: commands.Context) -> bool: channel_id = ctx.channel.id - is_on_call = await ctx.bot.redis.sismember( + is_on_call = await redis.sismember( constants.CHANNELS_ONCALL_SET, str(channel_id) ) diff --git a/payphone/core/constants.py b/payphone/core/constants.py index 18b88e2..91cb3cd 100644 --- a/payphone/core/constants.py +++ b/payphone/core/constants.py @@ -170,6 +170,9 @@ MSGS_PHONE_NOT_IN_CALL = [ CHANNELS_SEARCH_QUEUE = "channels:searching" CHANNELS_ONCALL_SET = "channels:on-call" +MESSAGES_QUEUE = "messages" +MESSAGE_CONTENTS_QUEUE = "message-contents" +MESSAGES_TO_FLUSH_IDS = "message-ids" BAN_TEMPLATE = "b:%s" PREFIX_TEMPLATE = "p:%s" diff --git a/payphone/core/redis.py b/payphone/core/redis.py new file mode 100644 index 0000000..29de44d --- /dev/null +++ b/payphone/core/redis.py @@ -0,0 +1,6 @@ +from redis import asyncio as aredis + +from payphone.core import config + + +redis = aredis.Redis(host="localhost" if config.IS_DEBUG else "redis") diff --git a/pyproject.toml b/pyproject.toml index 5d17295..dbc47b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ requires-python = ">=3.12" dependencies = [ "aerich==0.7.1", "aiohttp>=3.10.10", + "orjson>=3.10.7", "py-cord>=2.6.1", "python-dotenv>=1.0.1", "redis[hiredis]>=5.1.1", diff --git a/uv.lock b/uv.lock index d20f63f..f8d7a10 100644 --- a/uv.lock +++ b/uv.lock @@ -268,6 +268,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/b7/b9e70fde2c0f0c9af4cc5277782a89b66d35948ea3369ec9f598358c3ac5/multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", size = 10051 }, ] +[[package]] +name = "orjson" +version = "3.10.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/03/821c8197d0515e46ea19439f5c5d5fd9a9889f76800613cfac947b5d7845/orjson-3.10.7.tar.gz", hash = "sha256:75ef0640403f945f3a1f9f6400686560dbfb0fb5b16589ad62cd477043c4eee3", size = 5056450 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/7c/b4ecc2069210489696a36e42862ccccef7e49e1454a3422030ef52881b01/orjson-3.10.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44a96f2d4c3af51bfac6bc4ef7b182aa33f2f054fd7f34cc0ee9a320d051d41f", size = 251409 }, + { url = "https://files.pythonhosted.org/packages/60/84/e495edb919ef0c98d054a9b6d05f2700fdeba3886edd58f1c4dfb25d514a/orjson-3.10.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ac14cd57df0572453543f8f2575e2d01ae9e790c21f57627803f5e79b0d3c3", size = 147913 }, + { url = "https://files.pythonhosted.org/packages/c5/27/e40bc7d79c4afb7e9264f22320c285d06d2c9574c9c682ba0f1be3012833/orjson-3.10.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bdbb61dcc365dd9be94e8f7df91975edc9364d6a78c8f7adb69c1cdff318ec93", size = 147390 }, + { url = "https://files.pythonhosted.org/packages/30/be/fd646fb1a461de4958a6eacf4ecf064b8d5479c023e0e71cc89b28fa91ac/orjson-3.10.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b48b3db6bb6e0a08fa8c83b47bc169623f801e5cc4f24442ab2b6617da3b5313", size = 152973 }, + { url = "https://files.pythonhosted.org/packages/b1/00/414f8d4bc5ec3447e27b5c26b4e996e4ef08594d599e79b3648f64da060c/orjson-3.10.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23820a1563a1d386414fef15c249040042b8e5d07b40ab3fe3efbfbbcbcb8864", size = 164039 }, + { url = "https://files.pythonhosted.org/packages/a0/6b/34e6904ac99df811a06e42d8461d47b6e0c9b86e2fe7ee84934df6e35f0d/orjson-3.10.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0c6a008e91d10a2564edbb6ee5069a9e66df3fbe11c9a005cb411f441fd2c09", size = 142035 }, + { url = "https://files.pythonhosted.org/packages/17/7e/254189d9b6df89660f65aec878d5eeaa5b1ae371bd2c458f85940445d36f/orjson-3.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d352ee8ac1926d6193f602cbe36b1643bbd1bbcb25e3c1a657a4390f3000c9a5", size = 169941 }, + { url = "https://files.pythonhosted.org/packages/02/1a/d11805670c29d3a1b29fc4bd048dc90b094784779690592efe8c9f71249a/orjson-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d2d9f990623f15c0ae7ac608103c33dfe1486d2ed974ac3f40b693bad1a22a7b", size = 167994 }, + { url = "https://files.pythonhosted.org/packages/20/5f/03d89b007f9d6733dc11bc35d64812101c85d6c4e9c53af9fa7e7689cb11/orjson-3.10.7-cp312-none-win32.whl", hash = "sha256:7c4c17f8157bd520cdb7195f75ddbd31671997cbe10aee559c2d613592e7d7eb", size = 143130 }, + { url = "https://files.pythonhosted.org/packages/c6/9d/9b9fb6c60b8a0e04031ba85414915e19ecea484ebb625402d968ea45b8d5/orjson-3.10.7-cp312-none-win_amd64.whl", hash = "sha256:1d9c0e733e02ada3ed6098a10a8ee0052dd55774de3d9110d29868d24b17faa1", size = 137326 }, + { url = "https://files.pythonhosted.org/packages/15/05/121af8a87513c56745d01ad7cf215c30d08356da9ad882ebe2ba890824cd/orjson-3.10.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:77d325ed866876c0fa6492598ec01fe30e803272a6e8b10e992288b009cbe149", size = 251331 }, + { url = "https://files.pythonhosted.org/packages/73/7f/8d6ccd64a6f8bdbfe6c9be7c58aeb8094aa52a01fbbb2cda42ff7e312bd7/orjson-3.10.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ea2c232deedcb605e853ae1db2cc94f7390ac776743b699b50b071b02bea6fe", size = 142012 }, + { url = "https://files.pythonhosted.org/packages/04/65/f2a03fd1d4f0308f01d372e004c049f7eb9bc5676763a15f20f383fa9c01/orjson-3.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3dcfbede6737fdbef3ce9c37af3fb6142e8e1ebc10336daa05872bfb1d87839c", size = 169920 }, + { url = "https://files.pythonhosted.org/packages/e2/1c/3ef8d83d7c6a619ad3d69a4d5318591b4ce5862e6eda7c26bbe8208652ca/orjson-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11748c135f281203f4ee695b7f80bb1358a82a63905f9f0b794769483ea854ad", size = 167916 }, + { url = "https://files.pythonhosted.org/packages/f2/0d/820a640e5a7dfbe525e789c70871ebb82aff73b0c7bf80082653f86b9431/orjson-3.10.7-cp313-none-win32.whl", hash = "sha256:a7e19150d215c7a13f39eb787d84db274298d3f83d85463e61d277bbd7f401d2", size = 143089 }, + { url = "https://files.pythonhosted.org/packages/1a/72/a424db9116c7cad2950a8f9e4aeb655a7b57de988eb015acd0fcd1b4609b/orjson-3.10.7-cp313-none-win_amd64.whl", hash = "sha256:eef44224729e9525d5261cc8d28d6b11cafc90e6bd0be2157bde69a52ec83024", size = 137081 }, +] + [[package]] name = "payphone" version = "0.1.0" @@ -275,6 +299,7 @@ source = { virtual = "." } dependencies = [ { name = "aerich" }, { name = "aiohttp" }, + { name = "orjson" }, { name = "py-cord" }, { name = "python-dotenv" }, { name = "redis", extra = ["hiredis"] }, @@ -285,6 +310,7 @@ dependencies = [ requires-dist = [ { name = "aerich", specifier = "==0.7.1" }, { name = "aiohttp", specifier = ">=3.10.10" }, + { name = "orjson", specifier = ">=3.10.7" }, { name = "py-cord", specifier = ">=2.6.1" }, { name = "python-dotenv", specifier = ">=1.0.1" }, { name = "redis", extras = ["hiredis"], specifier = ">=5.1.1" },