diff --git a/.env.example b/.env.example index 4dcfd8e..53e5ea5 100755 --- a/.env.example +++ b/.env.example @@ -1,3 +1,9 @@ APP_ID= APP_HASH= -TG_PHONE=+15555555555 \ No newline at end of file +TG_PHONE=+15555555555 + +DB_HOST= +DB_USER= +DB_PASS= +DB_PORT=27017 +DB_NAME=fenneko \ No newline at end of file diff --git a/cyber_fenneko/__init__.py b/cyber_fenneko/__init__.py index 2dc545a..152008b 100755 --- a/cyber_fenneko/__init__.py +++ b/cyber_fenneko/__init__.py @@ -1,13 +1,16 @@ import os +import jsonpickle from mongoengine import connect from telemongo import MongoSession -from telethon import TelegramClient +from telethon import Client as TelegramClient from dotenv import load_dotenv from cyber_fenneko.bot import Bot load_dotenv() +jsonpickle.set_encoder_options('json', ensure_ascii=False, indent=4) + # Remember to use your own values from my.telegram.org! api_id = os.environ.get('APP_ID') api_hash = os.environ.get('APP_HASH') @@ -19,9 +22,10 @@ db_user = os.environ.get('DB_USER') db_pass = os.environ.get('DB_PASS') db_name = os.environ.get('DB_NAME') -session = MongoSession(db=db_name, host=db_host, port=int(db_port), username=db_user, password=db_pass) +connect(db=db_name, host=db_host, port=int(db_port), username=db_user, password=db_pass) +session = MongoSession() -client = TelegramClient(session, api_id, api_hash) +client = TelegramClient(session, int(api_id), api_hash) bot = Bot(client) # Import all middlewares from the middleware directory diff --git a/cyber_fenneko/__main__.py b/cyber_fenneko/__main__.py index a2480a0..0c9eb47 100755 --- a/cyber_fenneko/__main__.py +++ b/cyber_fenneko/__main__.py @@ -1,9 +1,30 @@ -from telethon.events import NewMessage, Raw +import asyncio +from datetime import datetime +from telethon.events import NewMessage, Event + +from cyber_fenneko.models.chat import Chat, ChatType from . import client, bot, phone_number -client.add_event_handler(bot._on_raw, Raw) -client.add_event_handler(bot._on_new_message, NewMessage) +client.add_event_handler(bot._on_raw, Event) +# client.add_event_handler(bot._on_new_message, NewMessage) -with client: - client.start(phone_number) - client.run_until_disconnected() \ No newline at end of file +async def main(): + async with client: + me = await client.interactive_login(phone_number) + Chat.objects(id=me.id).update_one( + set__id=me.id, + set__name=me.first_name, + set__username=me.username, + set__chat_type=ChatType.USER, + set__phone=me.phone, + set__packed=me.pack().__bytes__(), + set__updated_at=datetime.utcnow(), + upsert=True + ) + + client.add_event_handler(bot._on_raw, Event) + client.add_event_handler(bot._on_new_message, NewMessage) + + await client.run_until_disconnected() + +asyncio.run(main()) \ No newline at end of file diff --git a/cyber_fenneko/bot.py b/cyber_fenneko/bot.py index e026059..9f166b3 100755 --- a/cyber_fenneko/bot.py +++ b/cyber_fenneko/bot.py @@ -1,17 +1,20 @@ import logging +from pprint import pprint from typing import Dict -from telethon import TelegramClient -from telethon.tl.types import TypeUpdate -from telethon.tl.custom.message import Message +from telethon import Client as TelegramClient +from telethon._impl.client.types import Message +from telethon._impl.tl.abcs import Update +from telethon._impl.client.events import NewMessage, Event from cyber_fenneko.internal.constants import GLOBAL_ARGS from cyber_fenneko.internal.command import Command from cyber_fenneko.internal.command_context import CommandContext from cyber_fenneko.internal.arg_parser import parse_args +from cyber_fenneko.utils import to_dict class Bot: commands: Dict[str, Command] command_aliases: Dict[str, str] - update_handlers: Dict[TypeUpdate, list] + update_handlers: Dict[Update, list] client: TelegramClient logger: logging.Logger default_prefix: str @@ -27,7 +30,7 @@ class Bot: def set_default_prefix(self, prefix: str): self.default_prefix = prefix - def on(self, *events: TypeUpdate): + def on(self, *events: Event): def decorator(func): for event in events: if event not in self.update_handlers: @@ -61,15 +64,17 @@ class Bot: return func # we still want the function to be normally accessible return decorator - async def _on_raw(self, event: TypeUpdate): - if event.__class__ not in self.update_handlers: - return + async def _on_raw(self, event: Update): + pass + # if event.__class__ not in self.update_handlers: + # return - for handler in self.update_handlers[event.__class__]: - await handler(self, event) + # for handler in self.update_handlers[event.__class__]: + # await handler(self, event) - async def _on_new_message(self, event: Message): - message_text = event.message.message.strip() + async def _on_new_message(self, event: NewMessage): + # pprint(to_dict(event)) + message_text = event.text.strip() if not message_text: return @@ -82,9 +87,9 @@ class Bot: if cmd.prefix != prefix: return - if event.out and not cmd.outgoing: + if event._raw.out and not cmd.outgoing: return - elif not event.out and not cmd.incoming: + elif not event._raw.out and not cmd.incoming: return text = message_text[len(command_text) + 1:].strip() @@ -94,16 +99,15 @@ class Bot: await event.respond(f'Error: {e}') return - reply_message = await event.message.get_reply_message() + reply_message = await event.get_replied_message() # create the command context ctx = CommandContext( - client=self.client, bot=self, + client=event.client, raw_text=message_text, text=text, event=event, - message=event.message, reply_to=reply_message, args=args ) @@ -113,4 +117,4 @@ class Bot: # if the command is marked as 'delete', delete the message that triggered it if ctx.args['delete']: - await event.message.delete() + await event.delete() diff --git a/cyber_fenneko/commands/ask.py b/cyber_fenneko/commands/ask.py index 8afc04e..717311c 100755 --- a/cyber_fenneko/commands/ask.py +++ b/cyber_fenneko/commands/ask.py @@ -1,4 +1,3 @@ -import time import openai from cyber_fenneko.internal.constants import OPENAI_API_KEY @@ -29,10 +28,10 @@ async def ask(bot, ctx: CommandContext): # Get the message. If this command was sent in response to a message, use that message. # Otherwise, use the message that invoked the command. - message = ctx.reply_to or ctx.message + message = ctx.reply_to or ctx.event # Get the prompt - prompt = message.message.strip() + prompt = ctx.reply_to.text.strip() if ctx.reply_to else ctx.text.strip() # If this message was sent in response to another message, delete the sent message if ctx.reply_to: @@ -52,7 +51,7 @@ async def ask(bot, ctx: CommandContext): "content": """You are a chatbot within Telegram masquerading as a real person. Your name is watzon, and you should act and respond like watzon would. Watzon is a web developer of 13 years, father of 2, and husband of 1. -You are having a conversation with other humans in a Telegram group chat. +You are having a conversation with other humans in a Telegram group chat. Assume you have been a part of the chat for a while. No need for greetings or introductions. The person you're responding to is either asking a question, or making a statement. You should respond to the human in a way that makes sense. The human is not trying to trick you. You are responding as a user, so act natural. Use slang if necessary, acronyms, etc. @@ -78,4 +77,4 @@ Markdown code blocks should not include the language, and should be delimited by ) # Send the response - await message.reply(completion.choices[0].message.content, link_preview=False, parse_mode='markdown') \ No newline at end of file + await message.reply(markdown=completion.choices[0].message.content, link_preview=False) \ No newline at end of file diff --git a/cyber_fenneko/commands/cache.py b/cyber_fenneko/commands/cache.py index 2fc8474..df24e56 100755 --- a/cyber_fenneko/commands/cache.py +++ b/cyber_fenneko/commands/cache.py @@ -1,21 +1,24 @@ import time - -from cyber_fenneko.utils import format_time -from .. import bot +from telethon._impl.tl.types import ChatParticipant, ChatParticipantCreator, ChatParticipantAdmin +from cyber_fenneko.utils import format_time, to_dict +from .. import bot, Bot from ..internal.command_context import CommandContext @bot.command( 'cache', - description='Cache upto the last 200 dialogs', + description='Cache as many users in the current chat as possible', ) -async def cache(bot, ctx: CommandContext): +async def cache(bot: Bot, ctx: CommandContext): start = time.time() msg = await ctx.event.respond('Caching...') - dlgs = await bot.client.get_dialogs(limit=200) - - end = time.time() - ms = round((end - start) * 1000, 3) - time = format_time(ms) + users = [] + async for participant in ctx.client.get_participants(ctx.event.chat): + id = participant._raw.user_id + user = participant._chat_map[id] + users.append(user) - await msg.edit(f'Cached {len(dlgs)} dialogs ({time})') \ No newline at end of file + print(users) + + end = time.time() + await msg.edit(f'Cached {len(users)} users in {format_time(end - start)}') \ No newline at end of file diff --git a/cyber_fenneko/commands/help.py b/cyber_fenneko/commands/help.py index c5cd087..fde23e9 100755 --- a/cyber_fenneko/commands/help.py +++ b/cyber_fenneko/commands/help.py @@ -9,7 +9,7 @@ async def help(bot, ctx: CommandContext): cmd = ctx.text if cmd: if cmd in bot.commands: - await ctx.respond(bot.commands[cmd].long_help(), parse_mode='Markdown') + await ctx.respond(markdown=bot.commands[cmd].long_help()) else: await ctx.respond(f'Command `{cmd}` not found') else: @@ -19,4 +19,4 @@ async def help(bot, ctx: CommandContext): continue commands.append(command.help()) commands.sort() - await ctx.respond('\n'.join(commands), parse_mode='Markdown') \ No newline at end of file + await ctx.respond(markdown='\n'.join(commands)) \ No newline at end of file diff --git a/cyber_fenneko/commands/highlight.py b/cyber_fenneko/commands/highlight.py index 405db73..65f1648 100755 --- a/cyber_fenneko/commands/highlight.py +++ b/cyber_fenneko/commands/highlight.py @@ -1,9 +1,9 @@ -import time import httpx +from io import BytesIO from tempfile import NamedTemporaryFile from urllib.parse import urlencode from collections import OrderedDict -from telethon.tl.types import TypeMessageEntity, MessageEntityPre, InputMediaPhoto +from telethon._impl.tl.types import MessageEntityPre from cyber_fenneko.models.settings import Settings, HighlightSettings from .. import bot @@ -22,9 +22,9 @@ ENDPOINT_URL = 'https://inkify.0x45.st' Arg('shadow_color', type=ArgType.str, description='The color of the shadow'), Arg('background', type=ArgType.str, description='The background color'), Arg('tab_width', type=ArgType.int, description='The tab width'), - Arg('line_pad', type=ArgType.int, description='The line padding'), - Arg('line_offset', type=ArgType.int, description='The line offset'), - Arg('window_title', type=ArgType.str, description='The window title'), + Arg('line_pad', type=ArgType.int, aliases=['pad'], description='The line padding'), + Arg('line_offset', type=ArgType.int, aliases=['offset'], description='The line offset'), + Arg('window_title', type=ArgType.str, aliases=['title'], description='The window title'), Arg('no_line_number', type=ArgType.bool, description='Whether to hide the line numbers'), Arg('no_round_corner', type=ArgType.bool, description='Whether to round the corners'), Arg('no_window_controls', type=ArgType.bool, description='Whether to hide the window controls'), @@ -46,17 +46,17 @@ async def highlight(bot, ctx: CommandContext): # Check if the user wants to list available themes if ctx.args['themes']: themes = httpx.get(f'{ENDPOINT_URL}/themes').json() - themes_list = ', '.join(themes) - message = f'Available themes ({len(themes)}):\n{themes_list}' - await ctx.event.respond(message) + themes_list = [f'- `{theme}`' for theme in themes] + message = f'Available themes ({len(themes)}):\n' + '\n'.join(themes_list) + await ctx.event.respond(markdown=message) return # Check if the user wants to list available fonts if ctx.args['fonts']: fonts = httpx.get(f'{ENDPOINT_URL}/fonts').json() - fonts_list = ', '.join(fonts) - message = f'Available fonts ({len(fonts)}):\n{fonts_list}' - await ctx.event.respond(message) + fonts_list = [f'- `{font}`' for font in fonts] + message = f'Available fonts ({len(fonts)}):\n' + '\n'.join(fonts_list) + await ctx.event.respond(markdown=message) return # Check if the user wants to list available languages @@ -69,71 +69,82 @@ async def highlight(bot, ctx: CommandContext): # Get the message that triggered this command message = ctx.reply_to - if not message or not message.message: + if not message: await ctx.event.respond('Reply to a message to highlight it') return - # Check if there is a code block in the message - if not message.entities: - await ctx.event.respond('No code block found') - return - - # Filter through entities in the message to find the code block - code_blocks = [] - for entity in message.entities: - if isinstance(entity, MessageEntityPre): - code_blocks.append(message.message[entity.offset:entity.offset + entity.length]) - break - else: - code_blocks.append(ctx.text) + language = ctx.args['language'] + code = None + + # First check if the message contains a file + if message.file: + mime = message.file._mime + if mime and (mime.startswith('text/') or mime.startswith('application/')): + with NamedTemporaryFile(mode='wb', suffix=message.file.ext) as file: + await message.file.download(file) + file.seek(0) + code = file.read().decode('utf-8') + language = language or message.file.ext[1:] + + # Next check if the message contains any code blocks + if not code and message._raw.entities: + for entity in message._raw.entities: + if isinstance(entity, MessageEntityPre): + code = message.text[entity.offset:entity.offset + entity.length] + language = language or entity.language + break + + # If there are no code blocks (or more than one), we'll just use the entire message + if not code: + code = message.text + settings = Settings.objects.first() - print(settings) if settings.highlight is None: settings.highlight = HighlightSettings() settings.save() - for code_block in code_blocks: - # Inkify returns an image, we just need to build the URL - query = OrderedDict( - code=code_block, - language=ctx.args['language'] or settings.highlight.theme, - theme=ctx.args['theme'] or settings.highlight.theme, - font=ctx.args['font'] or settings.highlight.font, - shadow_color=ctx.args['shadow_color'] or settings.highlight.shadow_color, - background=ctx.args['background'] or settings.highlight.background, - tab_width=ctx.args['tab_width'] or settings.highlight.tab_width, - line_pad=ctx.args['line_pad'] or settings.highlight.line_pad, - line_offset=ctx.args['line_offset'] or settings.highlight.line_offset, - window_title=ctx.args['window_title'] or settings.highlight.window_title, - no_line_number=ctx.args['no_line_number'] or settings.highlight.no_line_number, - no_round_corner=ctx.args['no_round_corner'] or settings.highlight.no_round_corner, - no_window_controls=ctx.args['no_window_controls'] or settings.highlight.no_window_controls, - shadow_blur_radius=ctx.args['shadow_blur_radius'] or settings.highlight.shadow_blur_radius, - shadow_offset_x=ctx.args['shadow_offset_x'] or settings.highlight.shadow_offset_x, - shadow_offset_y=ctx.args['shadow_offset_y'] or settings.highlight.shadow_offset_y, - pad_horiz=ctx.args['pad_horiz'] or settings.highlight.pad_horiz, - pad_vert=ctx.args['pad_vert'] or settings.highlight.pad_vert, - highlight_lines=ctx.args['highlight_lines'] or settings.highlight.highlight_lines, - background_image=ctx.args['background_image'] or settings.highlight.background_image, - ) + # Inkify returns an image, we just need to build the URL + query = OrderedDict( + code=code, + language=language, + theme=ctx.args['theme'] or settings.highlight.theme, + font=ctx.args['font'] or settings.highlight.font, + shadow_color=ctx.args['shadow_color'] or settings.highlight.shadow_color, + background=ctx.args['background'] or settings.highlight.background, + tab_width=ctx.args['tab_width'] or settings.highlight.tab_width, + line_pad=ctx.args['line_pad'] or settings.highlight.line_pad, + line_offset=ctx.args['line_offset'] or settings.highlight.line_offset, + window_title=ctx.args['window_title'] or settings.highlight.window_title, + no_line_number=ctx.args['no_line_number'] or settings.highlight.no_line_number, + no_round_corner=ctx.args['no_round_corner'] or settings.highlight.no_round_corner, + no_window_controls=ctx.args['no_window_controls'] or settings.highlight.no_window_controls, + shadow_blur_radius=ctx.args['shadow_blur_radius'] or settings.highlight.shadow_blur_radius, + shadow_offset_x=ctx.args['shadow_offset_x'] or settings.highlight.shadow_offset_x, + shadow_offset_y=ctx.args['shadow_offset_y'] or settings.highlight.shadow_offset_y, + pad_horiz=ctx.args['pad_horiz'] or settings.highlight.pad_horiz, + pad_vert=ctx.args['pad_vert'] or settings.highlight.pad_vert, + highlight_lines=ctx.args['highlight_lines'] or settings.highlight.highlight_lines, + background_image=ctx.args['background_image'] or settings.highlight.background_image, + ) - # Remove any arguments that are None, and transform booleans into lowercase strings - query = {k: v for k, v in query.items() if v is not None} - query = {k: str(v).lower() if isinstance(v, bool) else v for k, v in query.items()} + # Remove any arguments that are None, and transform booleans into lowercase strings + query = {k: v for k, v in query.items() if v is not None} + query = {k: str(v).lower() if isinstance(v, bool) else v for k, v in query.items()} - # Get the image from the URL - url = f'{ENDPOINT_URL}/generate?{urlencode(query)}' - res = httpx.get(url) - - if res.status_code != 200: - await ctx.event.respond(f'Error: {res.status_code}') - return + url = f'{ENDPOINT_URL}/generate?{urlencode(query)}' + async with httpx.AsyncClient() as client: + response = await client.get(url) + file = BytesIO(response.content) - # Save the image to a temporary file - with NamedTemporaryFile(suffix='.png') as f: - f.write(res.content) - f.flush() - - # Send the image - await ctx.event.respond(file=f.name) \ No newline at end of file + try: + await ctx.client.send_photo( + ctx.event.chat, + file=file, + size=int(response.headers['content-length']), + compress=True, + name='highlight.png', + ) + except Exception as e: + await ctx.event.respond(f'Failed to highlight code: {e}') + return \ No newline at end of file diff --git a/cyber_fenneko/commands/jsondump.py b/cyber_fenneko/commands/jsondump.py index ca76173..f69ce09 100755 --- a/cyber_fenneko/commands/jsondump.py +++ b/cyber_fenneko/commands/jsondump.py @@ -1,6 +1,5 @@ -import time -import json -from telethon.tl.types import MessageEntityPre +import jsonpickle +from cyber_fenneko.utils import to_dict from .. import bot from ..internal.command_context import CommandContext from ..internal.arg_parser import Arg, ArgType @@ -17,10 +16,10 @@ from ..utils.paste import paste as paste_util async def jsondump(bot, ctx: CommandContext): # Get the message to paste message = ctx.reply_to or ctx.message - dict = message.to_dict() + dict = to_dict(message) # Prettify the JSON - raw = json.dumps(dict, indent=4, sort_keys=True, default=str) + raw = jsonpickle.encode(dict, unpicklable=False, indent=4) # Check if the JSON is too long if len(raw) > 4000 or ctx.args['paste']: @@ -29,5 +28,5 @@ async def jsondump(bot, ctx: CommandContext): await ctx.event.respond(f'Created paste: {url}', link_preview=False) else: # Respond with the JSON - entities = [MessageEntityPre(0, len(raw), "json")] - await ctx.event.respond(raw, formatting_entities=entities, link_preview=False) + response = f'
{raw}
' + await ctx.event.respond(html=response) diff --git a/cyber_fenneko/commands/paste.py b/cyber_fenneko/commands/paste.py index edaf928..56204b0 100755 --- a/cyber_fenneko/commands/paste.py +++ b/cyber_fenneko/commands/paste.py @@ -1,8 +1,9 @@ import re +from io import BytesIO +from tempfile import NamedTemporaryFile import time import httpx -from telethon.tl.types import MessageEntityPre -from telethon.tl.custom.message import Message +from telethon._impl.tl.types import MessageEntityPre from cyber_fenneko.models.paste import Paste from .. import bot @@ -28,37 +29,37 @@ ENDPOINT = 'https://0x45.st/api/pastes' ) async def paste(bot, ctx: CommandContext): # Get the message to paste - message: Message = ctx.reply_to + message = ctx.reply_to if message is None: await ctx.event.respond('You must reply to a message to use this command') return - # Get the message text - text = message.text if ctx.args['raw'] else message.message - if text is None: - await ctx.event.respond('You must reply to a text message to use this command') - return - - # Get the language language = ctx.args['language'] + code = None - contents = None + # First check if the message contains a file + if message.file: + mime = message.file._mime + if mime and (mime.startswith('text/') or mime.startswith('application/')): + buff = BytesIO() + await message.file.download(buff) + code = buff.getvalue().decode('utf-8') + language = language or message.file.ext[1:] - # Check if the message has entities; any pre entities will be used as the contents - if message.entities is not None: - for entity in message.entities: - if entity.CONSTRUCTOR_ID == MessageEntityPre.CONSTRUCTOR_ID: - if entity.language is not None: - language = entity.language - contents = text[entity.offset:entity.offset + entity.length] - contents = re.sub(r'```\n?', '', contents) + # Next check if the message contains any code blocks + if not code and message._raw.entities: + for entity in message._raw.entities: + if isinstance(entity, MessageEntityPre): + code = message.text[entity.offset:entity.offset + entity.length] + language = language or entity.language break - if not contents: - contents = text + # If there are no code blocks (or more than one), we'll just use the entire message + if not code: + code = message.text_markdown if ctx.args['raw'] else message.text - json = paste_util(contents, language) + json = paste_util(code, language) url = json['url'] # Respond with the paste URL - await ctx.event.respond(f'Created paste: {url}', link_preview=False) + await ctx.event.respond(f'Created new paste at:\n{url}', link_preview=False) diff --git a/cyber_fenneko/commands/search.py b/cyber_fenneko/commands/search.py new file mode 100644 index 0000000..d9a9fc3 --- /dev/null +++ b/cyber_fenneko/commands/search.py @@ -0,0 +1,50 @@ +from urllib import parse +from cyber_fenneko.internal.arg_parser import Arg, ArgType +from .. import bot +from ..internal.command_context import CommandContext +from cyber_fenneko.models.settings import Settings + +engine_map = { + 'google': 'https://www.google.com/search?q=%s', + 'bing': 'https://www.bing.com/search?q=%s', + 'duckduckgo': 'https://duckduckgo.com/?q=%s', + 'qwant': 'https://www.qwant.com/?q=%s', + 'ecosia': 'https://www.ecosia.org/search?q=%s', + 'kagi': 'https://kagi.com/search?q=%s', +} + +@bot.command( + 'search', + aliases=['google', 'lmgtfy'], + args=[ + # Arg('delete', type=ArgType.bool, default=True), + Arg('google', type=ArgType.bool, default=False, description='Use Google as the search engine'), + Arg('bing', type=ArgType.bool, default=False, description='Use Bing as the search engine'), + Arg('duckduckgo', type=ArgType.bool, aliases=['ddg'], default=False, description='Use DuckDuckGo as the search engine'), + Arg('qwant', type=ArgType.bool, default=False, description='Use Qwant as the search engine'), + Arg('ecosia', type=ArgType.bool, default=False, description='Use Ecosia as the search engine'), + Arg('kagi', type=ArgType.bool, default=False, description='Use Kagi as the search engine'), + ], + description='"Help people out by posting a link to a search query for their question"', +) +async def search(bot, ctx: CommandContext): + print(ctx) + # Get the search query from the reply message or the sent message + query = ctx.reply_to.text if ctx.reply_to else ctx.text + if not query: + await ctx.event.respond('You must specify a search query') + return + + settings = Settings.objects.first() + + # Get the search engine + search_engine = None + for engine in engine_map: + if ctx.args[engine]: + search_engine = engine + + search_engine = search_engine or settings.search_engine or 'google' + url = engine_map[search_engine] % parse.quote_plus(query) + + # Send the search query + await ctx.event.respond(html=f'{query}', link_preview=False) \ No newline at end of file diff --git a/cyber_fenneko/commands/user.py b/cyber_fenneko/commands/user.py index fa39e50..d9c1676 100755 --- a/cyber_fenneko/commands/user.py +++ b/cyber_fenneko/commands/user.py @@ -1,112 +1,116 @@ -import time +# import time -from telethon.tl.types import User -from telethon.functions import messages +# from telethon._impl.tl.types import User +# from telethon._impl.tl.functions import messages -from cyber_fenneko.internal.arg_parser import Arg, ArgType -from cyber_fenneko.utils import format_bool, format_user_status -from cyber_fenneko.bot import Bot +# from cyber_fenneko.internal.arg_parser import Arg, ArgType +# from cyber_fenneko.models.chat import Chat +# from cyber_fenneko.utils import format_bool, format_user_status +# from cyber_fenneko.bot import Bot -from .. import bot -from ..internal.command_context import CommandContext +# from .. import bot +# from ..internal.command_context import CommandContext -@bot.command( - 'user', - aliases=['u'], - args = [ - Arg('user', type=ArgType.peer, aliases=['u'], description='The user to get information about'), - Arg('id', type=ArgType.bool, aliases=['i'], default=True, description='Return only the user ID'), - Arg('mention', type=ArgType.bool, aliases=['m'], default=False, description='Whether to mention the user in the response'), - Arg('check_forward', type=ArgType.bool, aliases=['fwd'], default=True, description='Whether to check the forward from the reply message'), +# @bot.command( +# 'user', +# aliases=['u'], +# args = [ +# Arg('user', type=ArgType.peer, aliases=['u'], description='The user to get information about'), +# Arg('id', type=ArgType.bool, aliases=['i'], default=True, description='Return only the user ID'), +# Arg('mention', type=ArgType.bool, aliases=['m'], default=False, description='Whether to mention the user in the response'), +# Arg('check_forward', type=ArgType.bool, aliases=['fwd'], default=True, description='Whether to check the forward from the reply message'), - Arg('full', type=ArgType.bool, aliases=['f'], default=False, description='Whether to get full information about the user'), - Arg('general', type=ArgType.bool, aliases=['g'], default=False, description='Whether to show general information about the user'), - Arg('common_chats', type=ArgType.bool, aliases=['chats'], default=False, description='Whether to get the user\'s common chats'), - Arg('meta', type=ArgType.bool, default=False, description='Extra infomation about the user that didn\'t fit in the other categories'), - ], - description='Gather and return information about the given user', -) -async def user(bot: Bot, ctx: CommandContext): - user: User = ctx.args['user'] - - if not user: - # Attempt to get the user from the reply message - message = ctx.reply_to - if message: - try: - if ctx.args['check_forward'] and message.forward: - user = await message.forward.get_sender() - else: - user = await message.get_sender() - except: - await ctx.event.respond('Failed to get user from reply message') - return +# Arg('full', type=ArgType.bool, aliases=['f'], default=False, description='Whether to get full information about the user'), +# Arg('general', type=ArgType.bool, aliases=['g'], default=False, description='Whether to show general information about the user'), +# Arg('common_chats', type=ArgType.bool, aliases=['chats'], default=False, description='Whether to get the user\'s common chats'), +# Arg('meta', type=ArgType.bool, default=False, description='Extra infomation about the user that didn\'t fit in the other categories'), +# ], +# description='Gather and return information about the given user', +# ) +# async def user(bot: Bot, ctx: CommandContext): +# peer: Chat = ctx.args['user'] +# user: User = peer._to_input_peer() - if not user: - await ctx.event.respond('You must specify a user to get information about') - return +# if not user: +# # Attempt to get the user from the reply message +# message = await ctx.event.get_replied_message() +# if message: +# try: +# if ctx.args['check_forward'] and message._raw.fwd_from: +# # user = message.forward_info +# # TODO: Forwarded messages +# raise NotImplementedError('Forwarded messages are not supported yet') +# else: +# user = message.sender +# except: +# await ctx.event.respond('Failed to get user from reply message') +# return - mention = ctx.args['mention'] - show_full = ctx.args['full'] - id = ctx.args['id'] - show_general = show_full or ctx.args['general'] - show_meta = show_full or ctx.args['meta'] - show_common_chats = show_full or ctx.args['common_chats'] +# if not user: +# await ctx.event.respond('You must specify a user to get information about') +# return - if show_general or show_meta or show_common_chats: - id = False +# mention = ctx.args['mention'] +# show_full = ctx.args['full'] +# id = ctx.args['id'] +# show_general = show_full or ctx.args['general'] +# show_meta = show_full or ctx.args['meta'] +# show_common_chats = show_full or ctx.args['common_chats'] - # Screen name (first name + last name if available) - screen_name = user.first_name - if user.last_name: - screen_name += f' {user.last_name}' +# if show_general or show_meta or show_common_chats: +# id = False - # Start building a response - response = '' - if id: - if mention: - response += f'[{screen_name}](tg://user?id={user.id}) (`{user.id}`)' - else: - response += f'**{screen_name}**: {user.id}' - else: - if mention: - response += f'[{screen_name}](tg://user?id={user.id}) (`{user.id}`):\n' - else: - response += f'**{screen_name}** ({user.id})\n' +# # Screen name (first name + last name if available) +# screen_name = user.first_name +# if user.last_name: +# screen_name += f' {user.last_name}' - # General - if show_general: - response += f'**general**\n' - response += f' username: `{user.username}`\n' - response += f' phone: `{user.phone}`\n' - response += f' bot: {format_bool(user.bot)}\n' - response += f' verified: {format_bool(user.verified)}\n' - response += f' restricted: {format_bool(user.restricted)}\n' - response += f' support: {format_bool(user.support)}\n' - response += f' scam: {format_bool(user.scam)}\n' - response += f' fake: {format_bool(user.fake)}\n' +# # Start building a response +# response = '' +# if id: +# if mention: +# response += f'[{screen_name}](tg://user?id={user.id}) (`{user.id}`)' +# else: +# response += f'**{screen_name}**: {user.id}' +# else: +# if mention: +# response += f'[{screen_name}](tg://user?id={user.id}) (`{user.id}`):\n' +# else: +# response += f'**{screen_name}** ({user.id})\n' - # Common chats - if show_common_chats: - response += f'**common chats**\n' - common_chats = await bot.client(messages.GetCommonChatsRequest(user.id, 0, 200)) - for chat in common_chats.chats: - response += f' {chat.title} ({chat.id})\n' +# # General +# if show_general: +# response += f'**general**\n' +# response += f' username: `{user.username}`\n' +# response += f' phone: `{user.phone}`\n' +# response += f' bot: {format_bool(user.bot)}\n' +# response += f' verified: {format_bool(user.verified)}\n' +# response += f' restricted: {format_bool(user.restricted)}\n' +# response += f' support: {format_bool(user.support)}\n' +# response += f' scam: {format_bool(user.scam)}\n' +# response += f' fake: {format_bool(user.fake)}\n' - # Meta - if show_meta: - response += f'**meta**\n' - response += f' access_hash: `{user.access_hash}`\n' - response += f' bot_chat_history: {format_bool(user.bot_chat_history)}\n' - response += f' bot_nochats: {format_bool(user.bot_nochats)}\n' - response += f' bot_inline_geo: {format_bool(user.bot_inline_geo)}\n' - response += f' min: {format_bool(user.min)}\n' - response += f' bot_inline_placeholder: {format_bool(user.bot_inline_placeholder)}\n' - response += f' lang_code: `{user.lang_code}`\n' - response += f' status: `{format_user_status(user.status)}`\n' - response += f' bot_info_version: `{user.bot_info_version}`\n' - response += f' restriction_reason: `{user.restriction_reason}`\n' - response += f' bot_inline_geo: {format_bool(user.bot_inline_geo)}\n' +# # Common chats +# if show_common_chats: +# response += f'**common chats**\n' +# common_chats = await bot.client(messages.get_common_chats(user.id, 0, 200)) +# for chat in common_chats.chats: +# response += f' {chat.title} ({chat.id})\n' - # Send the response - await ctx.reply(response, link_preview=False, parse_mode='markdown') \ No newline at end of file +# # Meta +# if show_meta: +# response += f'**meta**\n' +# response += f' access_hash: `{user.access_hash}`\n' +# response += f' bot_chat_history: {format_bool(user.bot_chat_history)}\n' +# response += f' bot_nochats: {format_bool(user.bot_nochats)}\n' +# response += f' bot_inline_geo: {format_bool(user.bot_inline_geo)}\n' +# response += f' min: {format_bool(user.min)}\n' +# response += f' bot_inline_placeholder: {format_bool(user.bot_inline_placeholder)}\n' +# response += f' lang_code: `{user.lang_code}`\n' +# response += f' status: `{format_user_status(user.status)}`\n' +# response += f' bot_info_version: `{user.bot_info_version}`\n' +# response += f' restriction_reason: `{user.restriction_reason}`\n' +# response += f' bot_inline_geo: {format_bool(user.bot_inline_geo)}\n' + +# # Send the response +# await ctx.reply(markdown=response, link_preview=False) \ No newline at end of file diff --git a/cyber_fenneko/internal/arg_parser.py b/cyber_fenneko/internal/arg_parser.py index db5bde7..db043e6 100755 --- a/cyber_fenneko/internal/arg_parser.py +++ b/cyber_fenneko/internal/arg_parser.py @@ -2,7 +2,9 @@ import logging from enum import Enum from typing import Dict, List, Union -from telethon import TelegramClient +from telethon import Client as TelegramClient + +from cyber_fenneko.utils import get_chat_by_id, get_chat_by_username ArgType = Enum('ArgType', ['str', 'int', 'bool', 'enum', 'peer']) @@ -128,20 +130,21 @@ def parse_args(client: TelegramClient, text: str, cmd_args: List[Arg]) -> (Dict[ # peer is a special type that can be a username, user id, or chat id. # we need to fetch the user or chat that the id belongs to. - # Check if the arg value is an int, if so it's a user or chat id - int_value = None + chat = None try: int_value = int(arg_value) + chat = get_chat_by_id(int_value) except ValueError: - pass + chat = get_chat_by_username(arg_value) - id_or_username = int_value if int_value is not None else arg_value + if chat is None: + raise ValueError(f'Invalid peer value {arg_value} for arg "{arg.name}"') - try: - entity = client.get_entity(id_or_username) - parsed_args[arg.name] = entity - except ValueError: - raise ValueError(f'{arg_value} is not a valid user or chat id or username') + peer = chat.unpack() + if peer is None: + raise ValueError(f'Invalid peer value {arg_value} for arg "{arg.name}"') + + parsed_args[arg.name] = peer elif arg.type == ArgType.bool: # if the arg is a boolean, we need to check if the value is true or false if arg_value.lower() == 'true': diff --git a/cyber_fenneko/internal/command_context.py b/cyber_fenneko/internal/command_context.py index 1444e31..d598b93 100755 --- a/cyber_fenneko/internal/command_context.py +++ b/cyber_fenneko/internal/command_context.py @@ -1,7 +1,7 @@ from typing import Dict, Union -from telethon import TelegramClient +from telethon import Client as TelegramClient from telethon.events import NewMessage -from telethon.tl.custom.message import Message +from telethon._impl.client.types import Message from .. import bot from .arg_parser import Arg @@ -26,9 +26,6 @@ class CommandContext: # The event that triggered this command event: NewMessage, - # The message from the event - message: Message, - # The message that was replied to reply_to: Union[Message, None], @@ -40,7 +37,6 @@ class CommandContext: self.raw_text = raw_text self.text = text self.event = event - self.message = message self.reply_to = reply_to self.args = args @@ -49,11 +45,11 @@ class CommandContext: async def respond(self, *args, **kwargs): """Respond to the message that triggered this command.""" - await self.message.respond(*args, **kwargs) + await self.event.respond(*args, **kwargs) async def reply(self, *args, **kwargs): """Reply to the message that triggered this command.""" - await self.message.reply(*args, **kwargs) + await self.event.reply(*args, **kwargs) def __repr__(self) -> str: return f'CommandContext(raw_text={self.raw_text}, text={self.text}, args={self.args})' \ No newline at end of file diff --git a/cyber_fenneko/middleware/caching.py b/cyber_fenneko/middleware/caching.py index fb6106f..36d94ae 100755 --- a/cyber_fenneko/middleware/caching.py +++ b/cyber_fenneko/middleware/caching.py @@ -1,26 +1,53 @@ from datetime import datetime -from telethon.tl.types import User, TypeMessage, MessageEmpty, UpdateNewMessage, UpdateNewChannelMessage, PeerUser, PeerChat, PeerChannel +from telethon import events +from telethon._impl.tl.types import User, Chat, Channel from .. import bot -from cyber_fenneko.models.entity import Entity +from cyber_fenneko.models.chat import Chat as Entity, ChatType -@bot.on(UpdateNewMessage, UpdateNewChannelMessage) -async def caching(bot, update): - message: TypeMessage = update.message - if isinstance(message, MessageEmpty): - return +def is_incoming(event): + return not event._raw.out + +@bot.client.on(events.NewMessage, filter=is_incoming) +async def caching(event: events.NewMessage): + chat = event.sender + if chat is not None: + ent = Entity.objects(id=chat.id).first() or Entity() + ent.id = chat.id + ent.name = chat.name + ent.username = chat.username + + if isinstance(chat, User): + ent.phone = chat.phone + ent.chat_type = ChatType.USER + elif isinstance(chat, Chat): + ent.chat_type = ChatType.GROUP + elif isinstance(chat, Channel): + ent.chat_type = ChatType.CHANNEL + + if not ent.packed: + ent.packed = chat.pack().__bytes__() + + ent.updated_at = datetime.utcnow() + ent.save() - from_id = message.from_id - if isinstance(from_id, PeerUser): - # Grab the user from the database - peer_user = Entity.objects(pk=from_id.user_id).first() - if peer_user is not None: - # Only update if it doesn't have a username or hasn't been updated in the last 24 hours - if (peer_user.username is None) or (peer_user.updated_at is not None and (datetime.utcnow() - peer_user.updated_at).total_seconds() > 86400): - try: - # Leave the creation to Telethon, we're just going to update the fields - user: User = await bot.client.get_entity(from_id.user_id) - peer_user.updated_at = datetime.utcnow() - peer_user.save() - except: - pass \ No newline at end of file + channel = event.chat + if channel is not None: + ent = Entity.objects(id=channel.id).first() or Entity() + ent.id = channel.id + ent.name = channel.name + ent.username = channel.username + + if isinstance(chat, User): + ent.phone = chat.phone + ent.chat_type = ChatType.USER + elif isinstance(chat, Chat): + ent.chat_type = ChatType.GROUP + elif isinstance(chat, Channel): + ent.chat_type = ChatType.CHANNEL + + if not ent.packed: + ent.packed = channel.pack().__bytes__() + + ent.updated_at = datetime.utcnow() + ent.save() \ No newline at end of file diff --git a/cyber_fenneko/models/chat.py b/cyber_fenneko/models/chat.py new file mode 100755 index 0000000..ff59ab9 --- /dev/null +++ b/cyber_fenneko/models/chat.py @@ -0,0 +1,30 @@ +from enum import Enum +from mongoengine import Document, IntField, StringField, DateTimeField, BinaryField, EnumField +from telethon._impl.session import PackedChat + +class ChatType(Enum): + USER = 1 + GROUP = 2 + CHANNEL = 3 + +class Chat(Document): + id = IntField(primary_key=True) + name = StringField() + username = StringField() + phone = StringField() + name = StringField() + packed = BinaryField() + chat_type = EnumField(ChatType) + created_at = DateTimeField() + updated_at = DateTimeField(required=True) + meta = { + 'collection': 'entities', + 'indexes': [ + 'username', + 'phone', + ] + } + + def unpack(self): + if self.packed: + return PackedChat.from_bytes(self.packed) \ No newline at end of file diff --git a/cyber_fenneko/models/entity.py b/cyber_fenneko/models/entity.py deleted file mode 100755 index e37d091..0000000 --- a/cyber_fenneko/models/entity.py +++ /dev/null @@ -1,18 +0,0 @@ -from mongoengine import Document, IntField, StringField, DateTimeField - -class Entity(Document): - id = IntField(primary_key=True) - hash = IntField(required=True) - username = StringField() - phone = StringField() - name = StringField() - created_at = DateTimeField() - updated_at = DateTimeField(required=True) - meta = { - 'collection': 'telethon_entities', - 'indexes': [ - 'username', - 'phone', - 'name' - ] - } \ No newline at end of file diff --git a/cyber_fenneko/models/paste.py b/cyber_fenneko/models/paste.py index 057a4a5..df64805 100755 --- a/cyber_fenneko/models/paste.py +++ b/cyber_fenneko/models/paste.py @@ -1,4 +1,4 @@ -from .entity import Entity +from .chat import Chat from mongoengine import Document, StringField, DateTimeField, IntField, BooleanField from datetime import datetime @@ -6,7 +6,7 @@ class Paste(Document): """Contains a record of a paste creation on Paste69""" url = StringField(required=True) language = StringField(required=True) - chat = Entity() - author = Entity() + chat = Chat() + author = Chat() message_id = IntField() created_at = DateTimeField(default=datetime.utcnow) \ No newline at end of file diff --git a/cyber_fenneko/models/settings.py b/cyber_fenneko/models/settings.py index 85c1522..9061466 100644 --- a/cyber_fenneko/models/settings.py +++ b/cyber_fenneko/models/settings.py @@ -1,25 +1,4 @@ -from .entity import Entity -from mongoengine import Document, StringField, DateTimeField, IntField, BooleanField, EmbeddedDocument, EmbeddedDocumentField -from datetime import datetime - -# Arg('theme', type=ArgType.str, description='The theme to use for syntax highlighting'), -# Arg('font', type=ArgType.str, description='The font to use'), -# Arg('shadow_color', type=ArgType.str, description='The color of the shadow'), -# Arg('background', type=ArgType.str, description='The background color'), -# Arg('tab_width', type=ArgType.int, description='The tab width'), -# Arg('line_pad', type=ArgType.int, description='The line padding'), -# Arg('line_offset', type=ArgType.int, description='The line offset'), -# Arg('window_title', type=ArgType.str, description='The window title'), -# Arg('no_line_number', type=ArgType.bool, description='Whether to hide the line numbers'), -# Arg('no_round_corner', type=ArgType.bool, description='Whether to round the corners'), -# Arg('no_window_controls', type=ArgType.bool, description='Whether to hide the window controls'), -# Arg('shadow_blur_radius', type=ArgType.int, description='The shadow blur radius'), -# Arg('shadow_offset_x', type=ArgType.int, description='The shadow offset x'), -# Arg('shadow_offset_y', type=ArgType.int, description='The shadow offset y'), -# Arg('pad_horiz', type=ArgType.int, default=10, description='The horizontal padding'), -# Arg('pad_vert', type=ArgType.int, default=10, description='The vertical padding'), -# Arg('highlight_lines', type=ArgType.str, description='The lines to highlight'), -# Arg('background_image', type=ArgType.str, description='The background image for the padding area as a URL'), +from mongoengine import Document, StringField, IntField, BooleanField, EmbeddedDocument, EmbeddedDocumentField class HighlightSettings(EmbeddedDocument): """Stores settings for the highlight command""" @@ -46,6 +25,7 @@ class HighlightSettings(EmbeddedDocument): class Settings(Document): """Stores configuration settings for the bot""" highlight = EmbeddedDocumentField(HighlightSettings) + search_engine = StringField(default='google') meta = { 'collection': 'settings' diff --git a/cyber_fenneko/utils/__init__.py b/cyber_fenneko/utils/__init__.py index 036d580..4b334e7 100755 --- a/cyber_fenneko/utils/__init__.py +++ b/cyber_fenneko/utils/__init__.py @@ -1,4 +1,10 @@ -from telethon.tl.types import TypeUserStatus, UserStatusRecently, UserStatusOnline, UserStatusOffline, UserStatusLastWeek, UserStatusLastMonth, UserStatusEmpty +import jsonpickle +from typing import Optional +from telethon._impl.tl.abcs import UserStatus +from telethon._impl.tl.core import Serializable +from telethon._impl.tl.types import UserStatusRecently, UserStatusOnline, UserStatusOffline, UserStatusLastWeek, UserStatusLastMonth, UserStatusEmpty + +from cyber_fenneko.models.chat import Chat # Format time (in ms) to a human readable format. def format_time(ms: float) -> str: @@ -23,7 +29,7 @@ def format_bool(value: bool) -> str: return 'Yes' if value else 'No' # Convert a TypeUserStatus to a string. -def format_user_status(status: TypeUserStatus) -> str: +def format_user_status(status: UserStatus) -> str: if isinstance(status, UserStatusRecently): return 'Recently' elif isinstance(status, UserStatusOnline): @@ -37,4 +43,23 @@ def format_user_status(status: TypeUserStatus) -> str: elif isinstance(status, UserStatusEmpty): return 'Empty' else: - return 'Unknown' \ No newline at end of file + return 'Unknown' + +def to_dict(obj: object) -> dict: + if obj is None or isinstance(obj, (bool, int, bytes, str)): return obj + if isinstance(obj, (list, tuple)): return [to_dict(v) for v in obj] + if isinstance(obj, dict): return {k: to_dict(v) for k, v in obj.items()} + if hasattr(obj, '_from_raw') or isinstance(obj, Serializable): + props = dir(obj) + return {k: to_dict(getattr(obj, k)) for k in props if not k.startswith('_') and not callable(getattr(obj, k))} + return obj.__repr__() + +def get_chat_by_id(id: int) -> Optional[Chat]: + return Chat.objects(id=id).first() + +def get_chat_by_username(username: str) -> Optional[Chat]: + username = username.lstrip('@') + return Chat.objects(username=username).first() + +def get_chat_by_phone(phone: str) -> Optional[Chat]: + return Chat.objects(phone=phone).first() \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index f30da0d..a670e42 100644 --- a/poetry.lock +++ b/poetry.lock @@ -96,39 +96,40 @@ files = [ [[package]] name = "httpcore" -version = "0.18.0" +version = "1.0.1" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-0.18.0-py3-none-any.whl", hash = "sha256:adc5398ee0a476567bf87467063ee63584a8bce86078bf748e48754f60202ced"}, - {file = "httpcore-0.18.0.tar.gz", hash = "sha256:13b5e5cd1dca1a6636a6aaea212b19f4f85cd88c366a2b82304181b769aab3c9"}, + {file = "httpcore-1.0.1-py3-none-any.whl", hash = "sha256:c5e97ef177dca2023d0b9aad98e49507ef5423e9f1d94ffe2cfe250aa28e63b0"}, + {file = "httpcore-1.0.1.tar.gz", hash = "sha256:fce1ddf9b606cfb98132ab58865c3728c52c8e4c3c46e2aabb3674464a186e92"}, ] [package.dependencies] -anyio = ">=3.0,<5.0" certifi = "*" h11 = ">=0.13,<0.15" -sniffio = "==1.*" [package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.23.0)"] [[package]] name = "httpx" -version = "0.25.0" +version = "0.25.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.25.0-py3-none-any.whl", hash = "sha256:181ea7f8ba3a82578be86ef4171554dd45fec26a02556a744db029a0a27b7100"}, - {file = "httpx-0.25.0.tar.gz", hash = "sha256:47ecda285389cb32bb2691cc6e069e3ab0205956f681c5b2ad2325719751d875"}, + {file = "httpx-0.25.1-py3-none-any.whl", hash = "sha256:fec7d6cc5c27c578a391f7e87b9aa7d3d8fbcd034f6399f9f79b45bcc12a866a"}, + {file = "httpx-0.25.1.tar.gz", hash = "sha256:ffd96d5cf901e63863d9f1b4b6807861dbea4d301613415d9e6e57ead15fc5d0"}, ] [package.dependencies] +anyio = "*" certifi = "*" -httpcore = ">=0.18.0,<0.19.0" +httpcore = "*" idna = "*" sniffio = "*" @@ -149,6 +150,22 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] +[[package]] +name = "jsonpickle" +version = "3.0.2" +description = "Python library for serializing any arbitrary object graph into JSON" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jsonpickle-3.0.2-py3-none-any.whl", hash = "sha256:4a8442d97ca3f77978afa58068768dba7bff2dbabe79a9647bc3cdafd4ef019f"}, + {file = "jsonpickle-3.0.2.tar.gz", hash = "sha256:e37abba4bfb3ca4a4647d28bb9f4706436f7b46c8a8333b4a718abafa8e46b37"}, +] + +[package.extras] +docs = ["jaraco.packaging (>=3.2)", "rst.linker (>=1.9)", "sphinx"] +testing = ["ecdsa", "feedparser", "gmpy2", "numpy", "pandas", "pymongo", "pytest (>=3.5,!=3.7.3)", "pytest-black-multipy", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-flake8 (>=1.1.1)", "scikit-learn", "sqlalchemy"] +testing-libs = ["simplejson", "ujson"] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -200,13 +217,13 @@ pymongo = ">=3.4,<5.0" [[package]] name = "openai" -version = "1.0.0rc1" +version = "1.1.1" description = "Client library for the openai API" optional = false python-versions = ">=3.7.1" files = [ - {file = "openai-1.0.0rc1-py3-none-any.whl", hash = "sha256:a2075ef79acdae200798999b33782e99a48a64fd6ca61f7691d5b25a179fdc1c"}, - {file = "openai-1.0.0rc1.tar.gz", hash = "sha256:f082b0158529f5d826155b065493994bcdfd66d46045805668a4ad57fdfc0b24"}, + {file = "openai-1.1.1-py3-none-any.whl", hash = "sha256:1496418b132c88352bcfffa8c24e83a69f0e01b1484cbb7bb48f722aad8fd6e1"}, + {file = "openai-1.1.1.tar.gz", hash = "sha256:80e49cb21d8445f6d51339b8af7376fc83302c78ab78578b78133ef89634869d"}, ] [package.dependencies] @@ -380,92 +397,92 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pymongo" -version = "4.5.0" +version = "4.6.0" description = "Python driver for MongoDB " optional = false python-versions = ">=3.7" files = [ - {file = "pymongo-4.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2d4fa1b01fa7e5b7bb8d312e3542e211b320eb7a4e3d8dc884327039d93cb9e0"}, - {file = "pymongo-4.5.0-cp310-cp310-manylinux1_i686.whl", hash = "sha256:dfcd2b9f510411de615ccedd47462dae80e82fdc09fe9ab0f0f32f11cf57eeb5"}, - {file = "pymongo-4.5.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:3e33064f1984db412b34d51496f4ea785a9cff621c67de58e09fb28da6468a52"}, - {file = "pymongo-4.5.0-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:33faa786cc907de63f745f587e9879429b46033d7d97a7b84b37f4f8f47b9b32"}, - {file = "pymongo-4.5.0-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:76a262c41c1a7cbb84a3b11976578a7eb8e788c4b7bfbd15c005fb6ca88e6e50"}, - {file = "pymongo-4.5.0-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:0f4b125b46fe377984fbaecf2af40ed48b05a4b7676a2ff98999f2016d66b3ec"}, - {file = "pymongo-4.5.0-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:40d5f6e853ece9bfc01e9129b228df446f49316a4252bb1fbfae5c3c9dedebad"}, - {file = "pymongo-4.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:152259f0f1a60f560323aacf463a3642a65a25557683f49cfa08c8f1ecb2395a"}, - {file = "pymongo-4.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d64878d1659d2a5bdfd0f0a4d79bafe68653c573681495e424ab40d7b6d6d41"}, - {file = "pymongo-4.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1bb3a62395ffe835dbef3a1cbff48fbcce709c78bd1f52e896aee990928432b"}, - {file = "pymongo-4.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe48f50fb6348511a3268a893bfd4ab5f263f5ac220782449d03cd05964d1ae7"}, - {file = "pymongo-4.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7591a3beea6a9a4fa3080d27d193b41f631130e3ffa76b88c9ccea123f26dc59"}, - {file = "pymongo-4.5.0-cp310-cp310-win32.whl", hash = "sha256:3a7166d57dc74d679caa7743b8ecf7dc3a1235a9fd178654dddb2b2a627ae229"}, - {file = "pymongo-4.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:21b953da14549ff62ea4ae20889c71564328958cbdf880c64a92a48dda4c9c53"}, - {file = "pymongo-4.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ead4f19d0257a756b21ac2e0e85a37a7245ddec36d3b6008d5bfe416525967dc"}, - {file = "pymongo-4.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aff6279e405dc953eeb540ab061e72c03cf38119613fce183a8e94f31be608f"}, - {file = "pymongo-4.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd4c8d6aa91d3e35016847cbe8d73106e3d1c9a4e6578d38e2c346bfe8edb3ca"}, - {file = "pymongo-4.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08819da7864f9b8d4a95729b2bea5fffed08b63d3b9c15b4fea47de655766cf5"}, - {file = "pymongo-4.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a253b765b7cbc4209f1d8ee16c7287c4268d3243070bf72d7eec5aa9dfe2a2c2"}, - {file = "pymongo-4.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8027c9063579083746147cf401a7072a9fb6829678076cd3deff28bb0e0f50c8"}, - {file = "pymongo-4.5.0-cp311-cp311-win32.whl", hash = "sha256:9d2346b00af524757576cc2406414562cced1d4349c92166a0ee377a2a483a80"}, - {file = "pymongo-4.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:c3c3525ea8658ee1192cdddf5faf99b07ebe1eeaa61bf32821126df6d1b8072b"}, - {file = "pymongo-4.5.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e5a27f348909235a106a3903fc8e70f573d89b41d723a500869c6569a391cff7"}, - {file = "pymongo-4.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9a9a39b7cac81dca79fca8c2a6479ef4c7b1aab95fad7544cc0e8fd943595a2"}, - {file = "pymongo-4.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:496c9cbcb4951183d4503a9d7d2c1e3694aab1304262f831d5e1917e60386036"}, - {file = "pymongo-4.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23cc6d7eb009c688d70da186b8f362d61d5dd1a2c14a45b890bd1e91e9c451f2"}, - {file = "pymongo-4.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fff7d17d30b2cd45afd654b3fc117755c5d84506ed25fda386494e4e0a3416e1"}, - {file = "pymongo-4.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6422b6763b016f2ef2beedded0e546d6aa6ba87910f9244d86e0ac7690f75c96"}, - {file = "pymongo-4.5.0-cp312-cp312-win32.whl", hash = "sha256:77cfff95c1fafd09e940b3fdcb7b65f11442662fad611d0e69b4dd5d17a81c60"}, - {file = "pymongo-4.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:e57d859b972c75ee44ea2ef4758f12821243e99de814030f69a3decb2aa86807"}, - {file = "pymongo-4.5.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2b0176f9233a5927084c79ff80b51bd70bfd57e4f3d564f50f80238e797f0c8a"}, - {file = "pymongo-4.5.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:89b3f2da57a27913d15d2a07d58482f33d0a5b28abd20b8e643ab4d625e36257"}, - {file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:5caee7bd08c3d36ec54617832b44985bd70c4cbd77c5b313de6f7fce0bb34f93"}, - {file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:1d40ad09d9f5e719bc6f729cc6b17f31c0b055029719406bd31dde2f72fca7e7"}, - {file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:076afa0a4a96ca9f77fec0e4a0d241200b3b3a1766f8d7be9a905ecf59a7416b"}, - {file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:3fa3648e4f1e63ddfe53563ee111079ea3ab35c3b09cd25bc22dadc8269a495f"}, - {file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:44ee985194c426ddf781fa784f31ffa29cb59657b2dba09250a4245431847d73"}, - {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b33c17d9e694b66d7e96977e9e56df19d662031483efe121a24772a44ccbbc7e"}, - {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d79ae3bb1ff041c0db56f138c88ce1dfb0209f3546d8d6e7c3f74944ecd2439"}, - {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d67225f05f6ea27c8dc57f3fa6397c96d09c42af69d46629f71e82e66d33fa4f"}, - {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41771b22dd2822540f79a877c391283d4e6368125999a5ec8beee1ce566f3f82"}, - {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a1f26bc1f5ce774d99725773901820dfdfd24e875028da4a0252a5b48dcab5c"}, - {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3236cf89d69679eaeb9119c840f5c7eb388a2110b57af6bb6baf01a1da387c18"}, - {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e1f61355c821e870fb4c17cdb318669cfbcf245a291ce5053b41140870c3e5cc"}, - {file = "pymongo-4.5.0-cp37-cp37m-win32.whl", hash = "sha256:49dce6957598975d8b8d506329d2a3a6c4aee911fa4bbcf5e52ffc6897122950"}, - {file = "pymongo-4.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f2227a08b091bd41df5aadee0a5037673f691e2aa000e1968b1ea2342afc6880"}, - {file = "pymongo-4.5.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:435228d3c16a375274ac8ab9c4f9aef40c5e57ddb8296e20ecec9e2461da1017"}, - {file = "pymongo-4.5.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8e559116e4128630ad3b7e788e2e5da81cbc2344dee246af44471fa650486a70"}, - {file = "pymongo-4.5.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:840eaf30ccac122df260b6005f9dfae4ac287c498ee91e3e90c56781614ca238"}, - {file = "pymongo-4.5.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:b4fe46b58010115514b842c669a0ed9b6a342017b15905653a5b1724ab80917f"}, - {file = "pymongo-4.5.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:a8127437ebc196a6f5e8fddd746bd0903a400dc6b5ae35df672dd1ccc7170a2a"}, - {file = "pymongo-4.5.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:2988ef5e6b360b3ff1c6d55c53515499de5f48df31afd9f785d788cdacfbe2d3"}, - {file = "pymongo-4.5.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:e249190b018d63c901678053b4a43e797ca78b93fb6d17633e3567d4b3ec6107"}, - {file = "pymongo-4.5.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:1240edc1a448d4ada4bf1a0e55550b6292420915292408e59159fd8bbdaf8f63"}, - {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6d2a56fc2354bb6378f3634402eec788a8f3facf0b3e7d468db5f2b5a78d763"}, - {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a0aade2b11dc0c326ccd429ee4134d2d47459ff68d449c6d7e01e74651bd255"}, - {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74c0da07c04d0781490b2915e7514b1adb265ef22af039a947988c331ee7455b"}, - {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3754acbd7efc7f1b529039fcffc092a15e1cf045e31f22f6c9c5950c613ec4d"}, - {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:631492573a1bef2f74f9ac0f9d84e0ce422c251644cd81207530af4aa2ee1980"}, - {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e2654d1278384cff75952682d17c718ecc1ad1d6227bb0068fd826ba47d426a5"}, - {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:168172ef7856e20ec024fe2a746bfa895c88b32720138e6438fd765ebd2b62dd"}, - {file = "pymongo-4.5.0-cp38-cp38-win32.whl", hash = "sha256:b25f7bea162b3dbec6d33c522097ef81df7c19a9300722fa6853f5b495aecb77"}, - {file = "pymongo-4.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:b520aafc6cb148bac09ccf532f52cbd31d83acf4d3e5070d84efe3c019a1adbf"}, - {file = "pymongo-4.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8543253adfaa0b802bfa88386db1009c6ebb7d5684d093ee4edc725007553d21"}, - {file = "pymongo-4.5.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:bc5d8c3647b8ae28e4312f1492b8f29deebd31479cd3abaa989090fb1d66db83"}, - {file = "pymongo-4.5.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:505f8519c4c782a61d94a17b0da50be639ec462128fbd10ab0a34889218fdee3"}, - {file = "pymongo-4.5.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:53f2dda54d76a98b43a410498bd12f6034b2a14b6844ca08513733b2b20b7ad8"}, - {file = "pymongo-4.5.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:9c04b9560872fa9a91251030c488e0a73bce9321a70f991f830c72b3f8115d0d"}, - {file = "pymongo-4.5.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:58a63a26a1e3dc481dd3a18d6d9f8bd1d576cd1ffe0d479ba7dd38b0aeb20066"}, - {file = "pymongo-4.5.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:f076b779aa3dc179aa3ed861be063a313ed4e48ae9f6a8370a9b1295d4502111"}, - {file = "pymongo-4.5.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:1b1d7d9aabd8629a31d63cd106d56cca0e6420f38e50563278b520f385c0d86e"}, - {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37df8f6006286a5896d1cbc3efb8471ced42e3568d38e6cb00857277047b0d63"}, - {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56320c401f544d762fc35766936178fbceb1d9261cd7b24fbfbc8fb6f67aa8a5"}, - {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbd705d5f3c3d1ff2d169e418bb789ff07ab3c70d567cc6ba6b72b04b9143481"}, - {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80a167081c75cf66b32f30e2f1eaee9365af935a86dbd76788169911bed9b5d5"}, - {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c42748ccc451dfcd9cef6c5447a7ab727351fd9747ad431db5ebb18a9b78a4d"}, - {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf62da7a4cdec9a4b2981fcbd5e08053edffccf20e845c0b6ec1e77eb7fab61d"}, - {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b5bbb87fa0511bd313d9a2c90294c88db837667c2bda2ea3fa7a35b59fd93b1f"}, - {file = "pymongo-4.5.0-cp39-cp39-win32.whl", hash = "sha256:465fd5b040206f8bce7016b01d7e7f79d2fcd7c2b8e41791be9632a9df1b4999"}, - {file = "pymongo-4.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:63d8019eee119df308a075b8a7bdb06d4720bf791e2b73d5ab0e7473c115d79c"}, - {file = "pymongo-4.5.0.tar.gz", hash = "sha256:681f252e43b3ef054ca9161635f81b730f4d8cadd28b3f2b2004f5a72f853982"}, + {file = "pymongo-4.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c011bd5ad03cc096f99ffcfdd18a1817354132c1331bed7a837a25226659845f"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux1_i686.whl", hash = "sha256:5e63146dbdb1eac207464f6e0cfcdb640c9c5ff0f57b754fa96fe252314a1dc6"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:2972dd1f1285866aba027eff2f4a2bbf8aa98563c2ced14cb34ee5602b36afdf"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:a0be99b599da95b7a90a918dd927b20c434bea5e1c9b3efc6a3c6cd67c23f813"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:9b0f98481ad5dc4cb430a60bbb8869f05505283b9ae1c62bdb65eb5e020ee8e3"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:256c503a75bd71cf7fb9ebf889e7e222d49c6036a48aad5a619f98a0adf0e0d7"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:b4ad70d7cac4ca0c7b31444a0148bd3af01a2662fa12b1ad6f57cd4a04e21766"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5717a308a703dda2886a5796a07489c698b442f5e409cf7dc2ac93de8d61d764"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f7f9feecae53fa18d6a3ea7c75f9e9a1d4d20e5c3f9ce3fba83f07bcc4eee2"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128b1485753106c54af481789cdfea12b90a228afca0b11fb3828309a907e10e"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3077a31633beef77d057c6523f5de7271ddef7bde5e019285b00c0cc9cac1e3"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebf02c32afa6b67e5861a27183dd98ed88419a94a2ab843cc145fb0bafcc5b28"}, + {file = "pymongo-4.6.0-cp310-cp310-win32.whl", hash = "sha256:b14dd73f595199f4275bed4fb509277470d9b9059310537e3b3daba12b30c157"}, + {file = "pymongo-4.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:8adf014f2779992eba3b513e060d06f075f0ab2fb3ad956f413a102312f65cdf"}, + {file = "pymongo-4.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ba51129fcc510824b6ca6e2ce1c27e3e4d048b6e35d3ae6f7e517bed1b8b25ce"}, + {file = "pymongo-4.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2973f113e079fb98515722cd728e1820282721ec9fd52830e4b73cabdbf1eb28"}, + {file = "pymongo-4.6.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af425f323fce1b07755edd783581e7283557296946212f5b1a934441718e7528"}, + {file = "pymongo-4.6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ec71ac633b126c0775ed4604ca8f56c3540f5c21a1220639f299e7a544b55f9"}, + {file = "pymongo-4.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ec6c20385c5a58e16b1ea60c5e4993ea060540671d7d12664f385f2fb32fe79"}, + {file = "pymongo-4.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:85f2cdc400ee87f5952ebf2a117488f2525a3fb2e23863a8efe3e4ee9e54e4d1"}, + {file = "pymongo-4.6.0-cp311-cp311-win32.whl", hash = "sha256:7fc2bb8a74dcfcdd32f89528e38dcbf70a3a6594963d60dc9595e3b35b66e414"}, + {file = "pymongo-4.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6695d7136a435c1305b261a9ddb9b3ecec9863e05aab3935b96038145fd3a977"}, + {file = "pymongo-4.6.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d603edea1ff7408638b2504905c032193b7dcee7af269802dbb35bc8c3310ed5"}, + {file = "pymongo-4.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79f41576b3022c2fe9780ae3e44202b2438128a25284a8ddfa038f0785d87019"}, + {file = "pymongo-4.6.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49f2af6cf82509b15093ce3569229e0d53c90ad8ae2eef940652d4cf1f81e045"}, + {file = "pymongo-4.6.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecd9e1fa97aa11bf67472220285775fa15e896da108f425e55d23d7540a712ce"}, + {file = "pymongo-4.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d2be5c9c3488fa8a70f83ed925940f488eac2837a996708d98a0e54a861f212"}, + {file = "pymongo-4.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab6bcc8e424e07c1d4ba6df96f7fb963bcb48f590b9456de9ebd03b88084fe8"}, + {file = "pymongo-4.6.0-cp312-cp312-win32.whl", hash = "sha256:47aa128be2e66abd9d1a9b0437c62499d812d291f17b55185cb4aa33a5f710a4"}, + {file = "pymongo-4.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:014e7049dd019a6663747ca7dae328943e14f7261f7c1381045dfc26a04fa330"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:288c21ab9531b037f7efa4e467b33176bc73a0c27223c141b822ab4a0e66ff2a"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:747c84f4e690fbe6999c90ac97246c95d31460d890510e4a3fa61b7d2b87aa34"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:055f5c266e2767a88bb585d01137d9c7f778b0195d3dbf4a487ef0638be9b651"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:82e620842e12e8cb4050d2643a81c8149361cd82c0a920fa5a15dc4ca8a4000f"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:6b18276f14b4b6d92e707ab6db19b938e112bd2f1dc3f9f1a628df58e4fd3f0d"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:680fa0fc719e1a3dcb81130858368f51d83667d431924d0bcf249644bce8f303"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:3919708594b86d0f5cdc713eb6fccd3f9b9532af09ea7a5d843c933825ef56c4"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db082f728160369d9a6ed2e722438291558fc15ce06d0a7d696a8dad735c236b"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e4ed21029d80c4f62605ab16398fe1ce093fff4b5f22d114055e7d9fbc4adb0"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bea9138b0fc6e2218147e9c6ce1ff76ff8e29dc00bb1b64842bd1ca107aee9f"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a0269811661ba93c472c8a60ea82640e838c2eb148d252720a09b5123f2c2fe"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d6a1b1361f118e7fefa17ae3114e77f10ee1b228b20d50c47c9f351346180c8"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e3b0127b260d4abae7b62203c4c7ef0874c901b55155692353db19de4b18bc4"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a49aca4d961823b2846b739380c847e8964ff7ae0f0a683992b9d926054f0d6d"}, + {file = "pymongo-4.6.0-cp37-cp37m-win32.whl", hash = "sha256:09c7de516b08c57647176b9fc21d929d628e35bcebc7422220c89ae40b62126a"}, + {file = "pymongo-4.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:81dd1308bd5630d2bb5980f00aa163b986b133f1e9ed66c66ce2a5bc3572e891"}, + {file = "pymongo-4.6.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:2f8c04277d879146eacda920476e93d520eff8bec6c022ac108cfa6280d84348"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5802acc012bbb4bce4dff92973dff76482f30ef35dd4cb8ab5b0e06aa8f08c80"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ccd785fafa1c931deff6a7116e9a0d402d59fabe51644b0d0c268295ff847b25"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fe03bf25fae4b95d8afe40004a321df644400fdcba4c8e5e1a19c1085b740888"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:2ca0ba501898b2ec31e6c3acf90c31910944f01d454ad8e489213a156ccf1bda"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:10a379fb60f1b2406ae57b8899bacfe20567918c8e9d2d545e1b93628fcf2050"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:a4dc1319d0c162919ee7f4ee6face076becae2abbd351cc14f1fe70af5fb20d9"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ddef295aaf80cefb0c1606f1995899efcb17edc6b327eb6589e234e614b87756"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:518c90bdd6e842c446d01a766b9136fec5ec6cc94f3b8c3f8b4a332786ee6b64"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b80a4ee19b3442c57c38afa978adca546521a8822d663310b63ae2a7d7b13f3a"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb438a8bf6b695bf50d57e6a059ff09652a07968b2041178b3744ea785fcef9b"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3db7d833a7c38c317dc95b54e27f1d27012e031b45a7c24e360b53197d5f6e7"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3729b8db02063da50eeb3db88a27670d85953afb9a7f14c213ac9e3dca93034b"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:39a1cd5d383b37285641d5a7a86be85274466ae336a61b51117155936529f9b3"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7b0e6361754ac596cd16bfc6ed49f69ffcd9b60b7bc4bcd3ea65c6a83475e4ff"}, + {file = "pymongo-4.6.0-cp38-cp38-win32.whl", hash = "sha256:806e094e9e85d8badc978af8c95b69c556077f11844655cb8cd2d1758769e521"}, + {file = "pymongo-4.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1394c4737b325166a65ae7c145af1ebdb9fb153ebedd37cf91d676313e4a67b8"}, + {file = "pymongo-4.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a8273e1abbcff1d7d29cbbb1ea7e57d38be72f1af3c597c854168508b91516c2"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:e16ade71c93f6814d095d25cd6d28a90d63511ea396bd96e9ffcb886b278baaa"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:325701ae7b56daa5b0692305b7cb505ca50f80a1288abb32ff420a8a209b01ca"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:cc94f9fea17a5af8cf1a343597711a26b0117c0b812550d99934acb89d526ed2"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:21812453354b151200034750cd30b0140e82ec2a01fd4357390f67714a1bfbde"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:0634994b026336195778e5693583c060418d4ab453eff21530422690a97e1ee8"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:ad4f66fbb893b55f96f03020e67dcab49ffde0177c6565ccf9dec4fdf974eb61"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:2703a9f8f5767986b4f51c259ff452cc837c5a83c8ed5f5361f6e49933743b2f"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bafea6061d63059d8bc2ffc545e2f049221c8a4457d236c5cd6a66678673eab"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f28ae33dc5a0b9cee06e95fd420e42155d83271ab75964baf747ce959cac5f52"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16a534da0e39785687b7295e2fcf9a339f4a20689024983d11afaa4657f8507"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef67fedd863ffffd4adfd46d9d992b0f929c7f61a8307366d664d93517f2c78e"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05c30fd35cc97f14f354916b45feea535d59060ef867446b5c3c7f9b609dd5dc"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1c63e3a2e8fb815c4b1f738c284a4579897e37c3cfd95fdb199229a1ccfb638a"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e5e193f89f4f8c1fe273f9a6e6df915092c9f2af6db2d1afb8bd53855025c11f"}, + {file = "pymongo-4.6.0-cp39-cp39-win32.whl", hash = "sha256:a09bfb51953930e7e838972ddf646c5d5f984992a66d79da6ba7f6a8d8a890cd"}, + {file = "pymongo-4.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:107a234dc55affc5802acb3b6d83cbb8c87355b38a9457fcd8806bdeb8bce161"}, + {file = "pymongo-4.6.0.tar.gz", hash = "sha256:fb1c56d891f9e34303c451998ef62ba52659648bb0d75b03c5e4ac223a3342c2"}, ] [package.dependencies] @@ -477,6 +494,7 @@ encryption = ["certifi", "pymongo[aws]", "pymongocrypt (>=1.6.0,<2.0.0)"] gssapi = ["pykerberos", "winkerberos (>=0.5.0)"] ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] snappy = ["python-snappy"] +test = ["pytest (>=7)"] zstd = ["zstandard"] [[package]] @@ -518,6 +536,23 @@ files = [ {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] +[[package]] +name = "telemongo" +version = "0.2.2" +description = "MongoDB backend for Telethon session storage" +optional = false +python-versions = "^3.9" +files = [] +develop = true + +[package.dependencies] +mongoengine = "^0.27.0" +telethon = {git = "https://github.com/LonamiWebs/Telethon", rev = "v2", subdirectory = "client"} + +[package.source] +type = "directory" +url = "../telethon-session-mongo" + [[package]] name = "Telethon" version = "2.0.0a0" @@ -541,7 +576,7 @@ doc = ["sphinx_rtd_theme (>=1.2,<2.0)", "types-docutils (>=0.20,<1.0)"] type = "git" url = "https://github.com/LonamiWebs/Telethon" reference = "v2" -resolved_reference = "6e88264b284a33bc0ad3dfe543e3ec2ec8191ccc" +resolved_reference = "ae44426a78d553cd1a3ea9f536c0f2c90751d657" subdirectory = "client" [[package]] @@ -578,4 +613,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "9018cdd99fd16a737946674c19bc23ffd394c3a63f7e90c6d7db606d314ff8cc" +content-hash = "4dcc24786204463583f6e954cb90106d79ec504ffa7f3f9ad321444e55cc5e9a" diff --git a/pyproject.toml b/pyproject.toml index c39f828..f66d70a 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ openai = {version = "^1.0.0b3", allow-prereleases = true} telemongo = {path = "../telethon-session-mongo", develop=true} mongoengine = "^0.27.0" telethon = {git = "https://github.com/LonamiWebs/Telethon", rev = "v2", subdirectory = "client"} +jsonpickle = "^3.0.2" [build-system] requires = ["poetry-core"]