Things and stuff

This commit is contained in:
Chris W 2023-11-08 11:00:11 -07:00
parent ad8a496d7a
commit c0c16338e7
22 changed files with 603 additions and 422 deletions

View File

@ -1,3 +1,9 @@
APP_ID= APP_ID=
APP_HASH= APP_HASH=
TG_PHONE=+15555555555 TG_PHONE=+15555555555
DB_HOST=
DB_USER=
DB_PASS=
DB_PORT=27017
DB_NAME=fenneko

View File

@ -1,13 +1,16 @@
import os import os
import jsonpickle
from mongoengine import connect from mongoengine import connect
from telemongo import MongoSession from telemongo import MongoSession
from telethon import TelegramClient from telethon import Client as TelegramClient
from dotenv import load_dotenv from dotenv import load_dotenv
from cyber_fenneko.bot import Bot from cyber_fenneko.bot import Bot
load_dotenv() load_dotenv()
jsonpickle.set_encoder_options('json', ensure_ascii=False, indent=4)
# Remember to use your own values from my.telegram.org! # Remember to use your own values from my.telegram.org!
api_id = os.environ.get('APP_ID') api_id = os.environ.get('APP_ID')
api_hash = os.environ.get('APP_HASH') 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_pass = os.environ.get('DB_PASS')
db_name = os.environ.get('DB_NAME') 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) bot = Bot(client)
# Import all middlewares from the middleware directory # Import all middlewares from the middleware directory

View File

@ -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 from . import client, bot, phone_number
client.add_event_handler(bot._on_raw, Raw) client.add_event_handler(bot._on_raw, Event)
client.add_event_handler(bot._on_new_message, NewMessage) # client.add_event_handler(bot._on_new_message, NewMessage)
with client: async def main():
client.start(phone_number) async with client:
client.run_until_disconnected() 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())

View File

@ -1,17 +1,20 @@
import logging import logging
from pprint import pprint
from typing import Dict from typing import Dict
from telethon import TelegramClient from telethon import Client as TelegramClient
from telethon.tl.types import TypeUpdate from telethon._impl.client.types import Message
from telethon.tl.custom.message 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.constants import GLOBAL_ARGS
from cyber_fenneko.internal.command import Command from cyber_fenneko.internal.command import Command
from cyber_fenneko.internal.command_context import CommandContext from cyber_fenneko.internal.command_context import CommandContext
from cyber_fenneko.internal.arg_parser import parse_args from cyber_fenneko.internal.arg_parser import parse_args
from cyber_fenneko.utils import to_dict
class Bot: class Bot:
commands: Dict[str, Command] commands: Dict[str, Command]
command_aliases: Dict[str, str] command_aliases: Dict[str, str]
update_handlers: Dict[TypeUpdate, list] update_handlers: Dict[Update, list]
client: TelegramClient client: TelegramClient
logger: logging.Logger logger: logging.Logger
default_prefix: str default_prefix: str
@ -27,7 +30,7 @@ class Bot:
def set_default_prefix(self, prefix: str): def set_default_prefix(self, prefix: str):
self.default_prefix = prefix self.default_prefix = prefix
def on(self, *events: TypeUpdate): def on(self, *events: Event):
def decorator(func): def decorator(func):
for event in events: for event in events:
if event not in self.update_handlers: 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 func # we still want the function to be normally accessible
return decorator return decorator
async def _on_raw(self, event: TypeUpdate): async def _on_raw(self, event: Update):
if event.__class__ not in self.update_handlers: pass
return # if event.__class__ not in self.update_handlers:
# return
for handler in self.update_handlers[event.__class__]: # for handler in self.update_handlers[event.__class__]:
await handler(self, event) # await handler(self, event)
async def _on_new_message(self, event: Message): async def _on_new_message(self, event: NewMessage):
message_text = event.message.message.strip() # pprint(to_dict(event))
message_text = event.text.strip()
if not message_text: if not message_text:
return return
@ -82,9 +87,9 @@ class Bot:
if cmd.prefix != prefix: if cmd.prefix != prefix:
return return
if event.out and not cmd.outgoing: if event._raw.out and not cmd.outgoing:
return return
elif not event.out and not cmd.incoming: elif not event._raw.out and not cmd.incoming:
return return
text = message_text[len(command_text) + 1:].strip() text = message_text[len(command_text) + 1:].strip()
@ -94,16 +99,15 @@ class Bot:
await event.respond(f'Error: {e}') await event.respond(f'Error: {e}')
return return
reply_message = await event.message.get_reply_message() reply_message = await event.get_replied_message()
# create the command context # create the command context
ctx = CommandContext( ctx = CommandContext(
client=self.client,
bot=self, bot=self,
client=event.client,
raw_text=message_text, raw_text=message_text,
text=text, text=text,
event=event, event=event,
message=event.message,
reply_to=reply_message, reply_to=reply_message,
args=args args=args
) )
@ -113,4 +117,4 @@ class Bot:
# if the command is marked as 'delete', delete the message that triggered it # if the command is marked as 'delete', delete the message that triggered it
if ctx.args['delete']: if ctx.args['delete']:
await event.message.delete() await event.delete()

View File

@ -1,4 +1,3 @@
import time
import openai import openai
from cyber_fenneko.internal.constants import OPENAI_API_KEY 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. # Get the message. If this command was sent in response to a message, use that message.
# Otherwise, use the message that invoked the command. # 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 # 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 this message was sent in response to another message, delete the sent message
if ctx.reply_to: 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. "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. 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. 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. 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. 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. 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 # Send the response
await message.reply(completion.choices[0].message.content, link_preview=False, parse_mode='markdown') await message.reply(markdown=completion.choices[0].message.content, link_preview=False)

View File

@ -1,21 +1,24 @@
import time import time
from telethon._impl.tl.types import ChatParticipant, ChatParticipantCreator, ChatParticipantAdmin
from cyber_fenneko.utils import format_time from cyber_fenneko.utils import format_time, to_dict
from .. import bot from .. import bot, Bot
from ..internal.command_context import CommandContext from ..internal.command_context import CommandContext
@bot.command( @bot.command(
'cache', '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() start = time.time()
msg = await ctx.event.respond('Caching...') msg = await ctx.event.respond('Caching...')
dlgs = await bot.client.get_dialogs(limit=200) users = []
async for participant in ctx.client.get_participants(ctx.event.chat):
end = time.time() id = participant._raw.user_id
ms = round((end - start) * 1000, 3) user = participant._chat_map[id]
time = format_time(ms) users.append(user)
await msg.edit(f'Cached {len(dlgs)} dialogs ({time})') print(users)
end = time.time()
await msg.edit(f'Cached {len(users)} users in {format_time(end - start)}')

View File

@ -9,7 +9,7 @@ async def help(bot, ctx: CommandContext):
cmd = ctx.text cmd = ctx.text
if cmd: if cmd:
if cmd in bot.commands: 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: else:
await ctx.respond(f'Command `{cmd}` not found') await ctx.respond(f'Command `{cmd}` not found')
else: else:
@ -19,4 +19,4 @@ async def help(bot, ctx: CommandContext):
continue continue
commands.append(command.help()) commands.append(command.help())
commands.sort() commands.sort()
await ctx.respond('\n'.join(commands), parse_mode='Markdown') await ctx.respond(markdown='\n'.join(commands))

View File

@ -1,9 +1,9 @@
import time
import httpx import httpx
from io import BytesIO
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from urllib.parse import urlencode from urllib.parse import urlencode
from collections import OrderedDict 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 cyber_fenneko.models.settings import Settings, HighlightSettings
from .. import bot 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('shadow_color', type=ArgType.str, description='The color of the shadow'),
Arg('background', type=ArgType.str, description='The background color'), Arg('background', type=ArgType.str, description='The background color'),
Arg('tab_width', type=ArgType.int, description='The tab width'), Arg('tab_width', type=ArgType.int, description='The tab width'),
Arg('line_pad', type=ArgType.int, description='The line padding'), Arg('line_pad', type=ArgType.int, aliases=['pad'], description='The line padding'),
Arg('line_offset', type=ArgType.int, description='The line offset'), Arg('line_offset', type=ArgType.int, aliases=['offset'], description='The line offset'),
Arg('window_title', type=ArgType.str, description='The window title'), 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_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_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('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 # Check if the user wants to list available themes
if ctx.args['themes']: if ctx.args['themes']:
themes = httpx.get(f'{ENDPOINT_URL}/themes').json() themes = httpx.get(f'{ENDPOINT_URL}/themes').json()
themes_list = ', '.join(themes) themes_list = [f'- `{theme}`' for theme in themes]
message = f'Available themes ({len(themes)}):\n{themes_list}' message = f'Available themes ({len(themes)}):\n' + '\n'.join(themes_list)
await ctx.event.respond(message) await ctx.event.respond(markdown=message)
return return
# Check if the user wants to list available fonts # Check if the user wants to list available fonts
if ctx.args['fonts']: if ctx.args['fonts']:
fonts = httpx.get(f'{ENDPOINT_URL}/fonts').json() fonts = httpx.get(f'{ENDPOINT_URL}/fonts').json()
fonts_list = ', '.join(fonts) fonts_list = [f'- `{font}`' for font in fonts]
message = f'Available fonts ({len(fonts)}):\n{fonts_list}' message = f'Available fonts ({len(fonts)}):\n' + '\n'.join(fonts_list)
await ctx.event.respond(message) await ctx.event.respond(markdown=message)
return return
# Check if the user wants to list available languages # 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 # Get the message that triggered this command
message = ctx.reply_to 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') await ctx.event.respond('Reply to a message to highlight it')
return return
# Check if there is a code block in the message language = ctx.args['language']
if not message.entities: code = None
await ctx.event.respond('No code block found')
return # First check if the message contains a file
if message.file:
# Filter through entities in the message to find the code block mime = message.file._mime
code_blocks = [] if mime and (mime.startswith('text/') or mime.startswith('application/')):
for entity in message.entities: with NamedTemporaryFile(mode='wb', suffix=message.file.ext) as file:
if isinstance(entity, MessageEntityPre): await message.file.download(file)
code_blocks.append(message.message[entity.offset:entity.offset + entity.length]) file.seek(0)
break code = file.read().decode('utf-8')
else: language = language or message.file.ext[1:]
code_blocks.append(ctx.text)
# 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() settings = Settings.objects.first()
print(settings)
if settings.highlight is None: if settings.highlight is None:
settings.highlight = HighlightSettings() settings.highlight = HighlightSettings()
settings.save() settings.save()
for code_block in code_blocks: # Inkify returns an image, we just need to build the URL
# Inkify returns an image, we just need to build the URL query = OrderedDict(
query = OrderedDict( code=code,
code=code_block, language=language,
language=ctx.args['language'] or settings.highlight.theme, theme=ctx.args['theme'] or settings.highlight.theme,
theme=ctx.args['theme'] or settings.highlight.theme, font=ctx.args['font'] or settings.highlight.font,
font=ctx.args['font'] or settings.highlight.font, shadow_color=ctx.args['shadow_color'] or settings.highlight.shadow_color,
shadow_color=ctx.args['shadow_color'] or settings.highlight.shadow_color, background=ctx.args['background'] or settings.highlight.background,
background=ctx.args['background'] or settings.highlight.background, tab_width=ctx.args['tab_width'] or settings.highlight.tab_width,
tab_width=ctx.args['tab_width'] or settings.highlight.tab_width, line_pad=ctx.args['line_pad'] or settings.highlight.line_pad,
line_pad=ctx.args['line_pad'] or settings.highlight.line_pad, line_offset=ctx.args['line_offset'] or settings.highlight.line_offset,
line_offset=ctx.args['line_offset'] or settings.highlight.line_offset, window_title=ctx.args['window_title'] or settings.highlight.window_title,
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_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_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,
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_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_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,
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_horiz=ctx.args['pad_horiz'] or settings.highlight.pad_horiz, pad_vert=ctx.args['pad_vert'] or settings.highlight.pad_vert,
pad_vert=ctx.args['pad_vert'] or settings.highlight.pad_vert, highlight_lines=ctx.args['highlight_lines'] or settings.highlight.highlight_lines,
highlight_lines=ctx.args['highlight_lines'] or settings.highlight.highlight_lines, background_image=ctx.args['background_image'] or settings.highlight.background_image,
background_image=ctx.args['background_image'] or settings.highlight.background_image, )
)
# Remove any arguments that are None, and transform booleans into lowercase strings # 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: 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()} 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)}'
url = f'{ENDPOINT_URL}/generate?{urlencode(query)}' async with httpx.AsyncClient() as client:
res = httpx.get(url) response = await client.get(url)
file = BytesIO(response.content)
if res.status_code != 200:
await ctx.event.respond(f'Error: {res.status_code}')
return
# Save the image to a temporary file try:
with NamedTemporaryFile(suffix='.png') as f: await ctx.client.send_photo(
f.write(res.content) ctx.event.chat,
f.flush() file=file,
size=int(response.headers['content-length']),
# Send the image compress=True,
await ctx.event.respond(file=f.name) name='highlight.png',
)
except Exception as e:
await ctx.event.respond(f'Failed to highlight code: {e}')
return

View File

@ -1,6 +1,5 @@
import time import jsonpickle
import json from cyber_fenneko.utils import to_dict
from telethon.tl.types import MessageEntityPre
from .. import bot from .. import bot
from ..internal.command_context import CommandContext from ..internal.command_context import CommandContext
from ..internal.arg_parser import Arg, ArgType 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): async def jsondump(bot, ctx: CommandContext):
# Get the message to paste # Get the message to paste
message = ctx.reply_to or ctx.message message = ctx.reply_to or ctx.message
dict = message.to_dict() dict = to_dict(message)
# Prettify the JSON # 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 # Check if the JSON is too long
if len(raw) > 4000 or ctx.args['paste']: 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) await ctx.event.respond(f'Created paste: {url}', link_preview=False)
else: else:
# Respond with the JSON # Respond with the JSON
entities = [MessageEntityPre(0, len(raw), "json")] response = f'<pre><code class="language-json">{raw}</code></pre>'
await ctx.event.respond(raw, formatting_entities=entities, link_preview=False) await ctx.event.respond(html=response)

View File

@ -1,8 +1,9 @@
import re import re
from io import BytesIO
from tempfile import NamedTemporaryFile
import time import time
import httpx import httpx
from telethon.tl.types import MessageEntityPre from telethon._impl.tl.types import MessageEntityPre
from telethon.tl.custom.message import Message
from cyber_fenneko.models.paste import Paste from cyber_fenneko.models.paste import Paste
from .. import bot from .. import bot
@ -28,37 +29,37 @@ ENDPOINT = 'https://0x45.st/api/pastes'
) )
async def paste(bot, ctx: CommandContext): async def paste(bot, ctx: CommandContext):
# Get the message to paste # Get the message to paste
message: Message = ctx.reply_to message = ctx.reply_to
if message is None: if message is None:
await ctx.event.respond('You must reply to a message to use this command') await ctx.event.respond('You must reply to a message to use this command')
return 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'] 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 # Next check if the message contains any code blocks
if message.entities is not None: if not code and message._raw.entities:
for entity in message.entities: for entity in message._raw.entities:
if entity.CONSTRUCTOR_ID == MessageEntityPre.CONSTRUCTOR_ID: if isinstance(entity, MessageEntityPre):
if entity.language is not None: code = message.text[entity.offset:entity.offset + entity.length]
language = entity.language language = language or entity.language
contents = text[entity.offset:entity.offset + entity.length]
contents = re.sub(r'```\n?', '', contents)
break break
if not contents: # If there are no code blocks (or more than one), we'll just use the entire message
contents = text 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'] url = json['url']
# Respond with the paste 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)

View File

@ -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'<a href="{url}">{query}</a>', link_preview=False)

View File

@ -1,112 +1,116 @@
import time # import time
from telethon.tl.types import User # from telethon._impl.tl.types import User
from telethon.functions import messages # from telethon._impl.tl.functions import messages
from cyber_fenneko.internal.arg_parser import Arg, ArgType # from cyber_fenneko.internal.arg_parser import Arg, ArgType
from cyber_fenneko.utils import format_bool, format_user_status # from cyber_fenneko.models.chat import Chat
from cyber_fenneko.bot import Bot # from cyber_fenneko.utils import format_bool, format_user_status
# from cyber_fenneko.bot import Bot
from .. import bot # from .. import bot
from ..internal.command_context import CommandContext # from ..internal.command_context import CommandContext
@bot.command( # @bot.command(
'user', # 'user',
aliases=['u'], # aliases=['u'],
args = [ # args = [
Arg('user', type=ArgType.peer, aliases=['u'], description='The user to get information about'), # 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('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('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('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('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('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('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'), # 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', # description='Gather and return information about the given user',
) # )
async def user(bot: Bot, ctx: CommandContext): # async def user(bot: Bot, ctx: CommandContext):
user: User = ctx.args['user'] # peer: Chat = ctx.args['user']
# user: User = peer._to_input_peer()
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
if not user: # if not user:
await ctx.event.respond('You must specify a user to get information about') # # Attempt to get the user from the reply message
return # 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'] # if not user:
show_full = ctx.args['full'] # await ctx.event.respond('You must specify a user to get information about')
id = ctx.args['id'] # return
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 show_general or show_meta or show_common_chats: # mention = ctx.args['mention']
id = False # 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) # if show_general or show_meta or show_common_chats:
screen_name = user.first_name # id = False
if user.last_name:
screen_name += f' {user.last_name}'
# Start building a response # # Screen name (first name + last name if available)
response = '' # screen_name = user.first_name
if id: # if user.last_name:
if mention: # screen_name += f' {user.last_name}'
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'
# General # # Start building a response
if show_general: # response = ''
response += f'**general**\n' # if id:
response += f' username: `{user.username}`\n' # if mention:
response += f' phone: `{user.phone}`\n' # response += f'[{screen_name}](tg://user?id={user.id}) (`{user.id}`)'
response += f' bot: {format_bool(user.bot)}\n' # else:
response += f' verified: {format_bool(user.verified)}\n' # response += f'**{screen_name}**: {user.id}'
response += f' restricted: {format_bool(user.restricted)}\n' # else:
response += f' support: {format_bool(user.support)}\n' # if mention:
response += f' scam: {format_bool(user.scam)}\n' # response += f'[{screen_name}](tg://user?id={user.id}) (`{user.id}`):\n'
response += f' fake: {format_bool(user.fake)}\n' # else:
# response += f'**{screen_name}** ({user.id})\n'
# Common chats # # General
if show_common_chats: # if show_general:
response += f'**common chats**\n' # response += f'**general**\n'
common_chats = await bot.client(messages.GetCommonChatsRequest(user.id, 0, 200)) # response += f' username: `{user.username}`\n'
for chat in common_chats.chats: # response += f' phone: `{user.phone}`\n'
response += f' {chat.title} ({chat.id})\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 # # Common chats
if show_meta: # if show_common_chats:
response += f'**meta**\n' # response += f'**common chats**\n'
response += f' access_hash: `{user.access_hash}`\n' # common_chats = await bot.client(messages.get_common_chats(user.id, 0, 200))
response += f' bot_chat_history: {format_bool(user.bot_chat_history)}\n' # for chat in common_chats.chats:
response += f' bot_nochats: {format_bool(user.bot_nochats)}\n' # response += f' {chat.title} ({chat.id})\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 # # Meta
await ctx.reply(response, link_preview=False, parse_mode='markdown') # 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)

View File

@ -2,7 +2,9 @@ import logging
from enum import Enum from enum import Enum
from typing import Dict, List, Union 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']) 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. # 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. # 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 chat = None
int_value = None
try: try:
int_value = int(arg_value) int_value = int(arg_value)
chat = get_chat_by_id(int_value)
except ValueError: 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: peer = chat.unpack()
entity = client.get_entity(id_or_username) if peer is None:
parsed_args[arg.name] = entity raise ValueError(f'Invalid peer value {arg_value} for arg "{arg.name}"')
except ValueError:
raise ValueError(f'{arg_value} is not a valid user or chat id or username') parsed_args[arg.name] = peer
elif arg.type == ArgType.bool: elif arg.type == ArgType.bool:
# if the arg is a boolean, we need to check if the value is true or false # if the arg is a boolean, we need to check if the value is true or false
if arg_value.lower() == 'true': if arg_value.lower() == 'true':

View File

@ -1,7 +1,7 @@
from typing import Dict, Union from typing import Dict, Union
from telethon import TelegramClient from telethon import Client as TelegramClient
from telethon.events import NewMessage from telethon.events import NewMessage
from telethon.tl.custom.message import Message from telethon._impl.client.types import Message
from .. import bot from .. import bot
from .arg_parser import Arg from .arg_parser import Arg
@ -26,9 +26,6 @@ class CommandContext:
# The event that triggered this command # The event that triggered this command
event: NewMessage, event: NewMessage,
# The message from the event
message: Message,
# The message that was replied to # The message that was replied to
reply_to: Union[Message, None], reply_to: Union[Message, None],
@ -40,7 +37,6 @@ class CommandContext:
self.raw_text = raw_text self.raw_text = raw_text
self.text = text self.text = text
self.event = event self.event = event
self.message = message
self.reply_to = reply_to self.reply_to = reply_to
self.args = args self.args = args
@ -49,11 +45,11 @@ class CommandContext:
async def respond(self, *args, **kwargs): async def respond(self, *args, **kwargs):
"""Respond to the message that triggered this command.""" """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): async def reply(self, *args, **kwargs):
"""Reply to the message that triggered this command.""" """Reply to the message that triggered this command."""
await self.message.reply(*args, **kwargs) await self.event.reply(*args, **kwargs)
def __repr__(self) -> str: def __repr__(self) -> str:
return f'CommandContext(raw_text={self.raw_text}, text={self.text}, args={self.args})' return f'CommandContext(raw_text={self.raw_text}, text={self.text}, args={self.args})'

View File

@ -1,26 +1,53 @@
from datetime import datetime 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 .. import bot
from cyber_fenneko.models.entity import Entity from cyber_fenneko.models.chat import Chat as Entity, ChatType
@bot.on(UpdateNewMessage, UpdateNewChannelMessage) def is_incoming(event):
async def caching(bot, update): return not event._raw.out
message: TypeMessage = update.message
if isinstance(message, MessageEmpty): @bot.client.on(events.NewMessage, filter=is_incoming)
return 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 channel = event.chat
if isinstance(from_id, PeerUser): if channel is not None:
# Grab the user from the database ent = Entity.objects(id=channel.id).first() or Entity()
peer_user = Entity.objects(pk=from_id.user_id).first() ent.id = channel.id
if peer_user is not None: ent.name = channel.name
# Only update if it doesn't have a username or hasn't been updated in the last 24 hours ent.username = channel.username
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: if isinstance(chat, User):
# Leave the creation to Telethon, we're just going to update the fields ent.phone = chat.phone
user: User = await bot.client.get_entity(from_id.user_id) ent.chat_type = ChatType.USER
peer_user.updated_at = datetime.utcnow() elif isinstance(chat, Chat):
peer_user.save() ent.chat_type = ChatType.GROUP
except: elif isinstance(chat, Channel):
pass ent.chat_type = ChatType.CHANNEL
if not ent.packed:
ent.packed = channel.pack().__bytes__()
ent.updated_at = datetime.utcnow()
ent.save()

30
cyber_fenneko/models/chat.py Executable file
View File

@ -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)

View File

@ -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'
]
}

View File

@ -1,4 +1,4 @@
from .entity import Entity from .chat import Chat
from mongoengine import Document, StringField, DateTimeField, IntField, BooleanField from mongoengine import Document, StringField, DateTimeField, IntField, BooleanField
from datetime import datetime from datetime import datetime
@ -6,7 +6,7 @@ class Paste(Document):
"""Contains a record of a paste creation on Paste69""" """Contains a record of a paste creation on Paste69"""
url = StringField(required=True) url = StringField(required=True)
language = StringField(required=True) language = StringField(required=True)
chat = Entity() chat = Chat()
author = Entity() author = Chat()
message_id = IntField() message_id = IntField()
created_at = DateTimeField(default=datetime.utcnow) created_at = DateTimeField(default=datetime.utcnow)

View File

@ -1,25 +1,4 @@
from .entity import Entity from mongoengine import Document, StringField, IntField, BooleanField, EmbeddedDocument, EmbeddedDocumentField
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'),
class HighlightSettings(EmbeddedDocument): class HighlightSettings(EmbeddedDocument):
"""Stores settings for the highlight command""" """Stores settings for the highlight command"""
@ -46,6 +25,7 @@ class HighlightSettings(EmbeddedDocument):
class Settings(Document): class Settings(Document):
"""Stores configuration settings for the bot""" """Stores configuration settings for the bot"""
highlight = EmbeddedDocumentField(HighlightSettings) highlight = EmbeddedDocumentField(HighlightSettings)
search_engine = StringField(default='google')
meta = { meta = {
'collection': 'settings' 'collection': 'settings'

View File

@ -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. # Format time (in ms) to a human readable format.
def format_time(ms: float) -> str: def format_time(ms: float) -> str:
@ -23,7 +29,7 @@ def format_bool(value: bool) -> str:
return 'Yes' if value else 'No' return 'Yes' if value else 'No'
# Convert a TypeUserStatus to a string. # Convert a TypeUserStatus to a string.
def format_user_status(status: TypeUserStatus) -> str: def format_user_status(status: UserStatus) -> str:
if isinstance(status, UserStatusRecently): if isinstance(status, UserStatusRecently):
return 'Recently' return 'Recently'
elif isinstance(status, UserStatusOnline): elif isinstance(status, UserStatusOnline):
@ -37,4 +43,23 @@ def format_user_status(status: TypeUserStatus) -> str:
elif isinstance(status, UserStatusEmpty): elif isinstance(status, UserStatusEmpty):
return 'Empty' return 'Empty'
else: else:
return 'Unknown' 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()

227
poetry.lock generated
View File

@ -96,39 +96,40 @@ files = [
[[package]] [[package]]
name = "httpcore" name = "httpcore"
version = "0.18.0" version = "1.0.1"
description = "A minimal low-level HTTP client." description = "A minimal low-level HTTP client."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "httpcore-0.18.0-py3-none-any.whl", hash = "sha256:adc5398ee0a476567bf87467063ee63584a8bce86078bf748e48754f60202ced"}, {file = "httpcore-1.0.1-py3-none-any.whl", hash = "sha256:c5e97ef177dca2023d0b9aad98e49507ef5423e9f1d94ffe2cfe250aa28e63b0"},
{file = "httpcore-0.18.0.tar.gz", hash = "sha256:13b5e5cd1dca1a6636a6aaea212b19f4f85cd88c366a2b82304181b769aab3c9"}, {file = "httpcore-1.0.1.tar.gz", hash = "sha256:fce1ddf9b606cfb98132ab58865c3728c52c8e4c3c46e2aabb3674464a186e92"},
] ]
[package.dependencies] [package.dependencies]
anyio = ">=3.0,<5.0"
certifi = "*" certifi = "*"
h11 = ">=0.13,<0.15" h11 = ">=0.13,<0.15"
sniffio = "==1.*"
[package.extras] [package.extras]
asyncio = ["anyio (>=4.0,<5.0)"]
http2 = ["h2 (>=3,<5)"] http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"] socks = ["socksio (==1.*)"]
trio = ["trio (>=0.22.0,<0.23.0)"]
[[package]] [[package]]
name = "httpx" name = "httpx"
version = "0.25.0" version = "0.25.1"
description = "The next generation HTTP client." description = "The next generation HTTP client."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "httpx-0.25.0-py3-none-any.whl", hash = "sha256:181ea7f8ba3a82578be86ef4171554dd45fec26a02556a744db029a0a27b7100"}, {file = "httpx-0.25.1-py3-none-any.whl", hash = "sha256:fec7d6cc5c27c578a391f7e87b9aa7d3d8fbcd034f6399f9f79b45bcc12a866a"},
{file = "httpx-0.25.0.tar.gz", hash = "sha256:47ecda285389cb32bb2691cc6e069e3ab0205956f681c5b2ad2325719751d875"}, {file = "httpx-0.25.1.tar.gz", hash = "sha256:ffd96d5cf901e63863d9f1b4b6807861dbea4d301613415d9e6e57ead15fc5d0"},
] ]
[package.dependencies] [package.dependencies]
anyio = "*"
certifi = "*" certifi = "*"
httpcore = ">=0.18.0,<0.19.0" httpcore = "*"
idna = "*" idna = "*"
sniffio = "*" sniffio = "*"
@ -149,6 +150,22 @@ files = [
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, {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]] [[package]]
name = "markdown-it-py" name = "markdown-it-py"
version = "3.0.0" version = "3.0.0"
@ -200,13 +217,13 @@ pymongo = ">=3.4,<5.0"
[[package]] [[package]]
name = "openai" name = "openai"
version = "1.0.0rc1" version = "1.1.1"
description = "Client library for the openai API" description = "Client library for the openai API"
optional = false optional = false
python-versions = ">=3.7.1" python-versions = ">=3.7.1"
files = [ files = [
{file = "openai-1.0.0rc1-py3-none-any.whl", hash = "sha256:a2075ef79acdae200798999b33782e99a48a64fd6ca61f7691d5b25a179fdc1c"}, {file = "openai-1.1.1-py3-none-any.whl", hash = "sha256:1496418b132c88352bcfffa8c24e83a69f0e01b1484cbb7bb48f722aad8fd6e1"},
{file = "openai-1.0.0rc1.tar.gz", hash = "sha256:f082b0158529f5d826155b065493994bcdfd66d46045805668a4ad57fdfc0b24"}, {file = "openai-1.1.1.tar.gz", hash = "sha256:80e49cb21d8445f6d51339b8af7376fc83302c78ab78578b78133ef89634869d"},
] ]
[package.dependencies] [package.dependencies]
@ -380,92 +397,92 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]] [[package]]
name = "pymongo" name = "pymongo"
version = "4.5.0" version = "4.6.0"
description = "Python driver for MongoDB <http://www.mongodb.org>" description = "Python driver for MongoDB <http://www.mongodb.org>"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "pymongo-4.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2d4fa1b01fa7e5b7bb8d312e3542e211b320eb7a4e3d8dc884327039d93cb9e0"}, {file = "pymongo-4.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c011bd5ad03cc096f99ffcfdd18a1817354132c1331bed7a837a25226659845f"},
{file = "pymongo-4.5.0-cp310-cp310-manylinux1_i686.whl", hash = "sha256:dfcd2b9f510411de615ccedd47462dae80e82fdc09fe9ab0f0f32f11cf57eeb5"}, {file = "pymongo-4.6.0-cp310-cp310-manylinux1_i686.whl", hash = "sha256:5e63146dbdb1eac207464f6e0cfcdb640c9c5ff0f57b754fa96fe252314a1dc6"},
{file = "pymongo-4.5.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:3e33064f1984db412b34d51496f4ea785a9cff621c67de58e09fb28da6468a52"}, {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:2972dd1f1285866aba027eff2f4a2bbf8aa98563c2ced14cb34ee5602b36afdf"},
{file = "pymongo-4.5.0-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:33faa786cc907de63f745f587e9879429b46033d7d97a7b84b37f4f8f47b9b32"}, {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:a0be99b599da95b7a90a918dd927b20c434bea5e1c9b3efc6a3c6cd67c23f813"},
{file = "pymongo-4.5.0-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:76a262c41c1a7cbb84a3b11976578a7eb8e788c4b7bfbd15c005fb6ca88e6e50"}, {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:9b0f98481ad5dc4cb430a60bbb8869f05505283b9ae1c62bdb65eb5e020ee8e3"},
{file = "pymongo-4.5.0-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:0f4b125b46fe377984fbaecf2af40ed48b05a4b7676a2ff98999f2016d66b3ec"}, {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:256c503a75bd71cf7fb9ebf889e7e222d49c6036a48aad5a619f98a0adf0e0d7"},
{file = "pymongo-4.5.0-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:40d5f6e853ece9bfc01e9129b228df446f49316a4252bb1fbfae5c3c9dedebad"}, {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:b4ad70d7cac4ca0c7b31444a0148bd3af01a2662fa12b1ad6f57cd4a04e21766"},
{file = "pymongo-4.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:152259f0f1a60f560323aacf463a3642a65a25557683f49cfa08c8f1ecb2395a"}, {file = "pymongo-4.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5717a308a703dda2886a5796a07489c698b442f5e409cf7dc2ac93de8d61d764"},
{file = "pymongo-4.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d64878d1659d2a5bdfd0f0a4d79bafe68653c573681495e424ab40d7b6d6d41"}, {file = "pymongo-4.6.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f7f9feecae53fa18d6a3ea7c75f9e9a1d4d20e5c3f9ce3fba83f07bcc4eee2"},
{file = "pymongo-4.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1bb3a62395ffe835dbef3a1cbff48fbcce709c78bd1f52e896aee990928432b"}, {file = "pymongo-4.6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128b1485753106c54af481789cdfea12b90a228afca0b11fb3828309a907e10e"},
{file = "pymongo-4.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe48f50fb6348511a3268a893bfd4ab5f263f5ac220782449d03cd05964d1ae7"}, {file = "pymongo-4.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3077a31633beef77d057c6523f5de7271ddef7bde5e019285b00c0cc9cac1e3"},
{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.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebf02c32afa6b67e5861a27183dd98ed88419a94a2ab843cc145fb0bafcc5b28"},
{file = "pymongo-4.5.0-cp310-cp310-win32.whl", hash = "sha256:3a7166d57dc74d679caa7743b8ecf7dc3a1235a9fd178654dddb2b2a627ae229"}, {file = "pymongo-4.6.0-cp310-cp310-win32.whl", hash = "sha256:b14dd73f595199f4275bed4fb509277470d9b9059310537e3b3daba12b30c157"},
{file = "pymongo-4.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:21b953da14549ff62ea4ae20889c71564328958cbdf880c64a92a48dda4c9c53"}, {file = "pymongo-4.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:8adf014f2779992eba3b513e060d06f075f0ab2fb3ad956f413a102312f65cdf"},
{file = "pymongo-4.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ead4f19d0257a756b21ac2e0e85a37a7245ddec36d3b6008d5bfe416525967dc"}, {file = "pymongo-4.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ba51129fcc510824b6ca6e2ce1c27e3e4d048b6e35d3ae6f7e517bed1b8b25ce"},
{file = "pymongo-4.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aff6279e405dc953eeb540ab061e72c03cf38119613fce183a8e94f31be608f"}, {file = "pymongo-4.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2973f113e079fb98515722cd728e1820282721ec9fd52830e4b73cabdbf1eb28"},
{file = "pymongo-4.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd4c8d6aa91d3e35016847cbe8d73106e3d1c9a4e6578d38e2c346bfe8edb3ca"}, {file = "pymongo-4.6.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af425f323fce1b07755edd783581e7283557296946212f5b1a934441718e7528"},
{file = "pymongo-4.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08819da7864f9b8d4a95729b2bea5fffed08b63d3b9c15b4fea47de655766cf5"}, {file = "pymongo-4.6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ec71ac633b126c0775ed4604ca8f56c3540f5c21a1220639f299e7a544b55f9"},
{file = "pymongo-4.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a253b765b7cbc4209f1d8ee16c7287c4268d3243070bf72d7eec5aa9dfe2a2c2"}, {file = "pymongo-4.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ec6c20385c5a58e16b1ea60c5e4993ea060540671d7d12664f385f2fb32fe79"},
{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.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:85f2cdc400ee87f5952ebf2a117488f2525a3fb2e23863a8efe3e4ee9e54e4d1"},
{file = "pymongo-4.5.0-cp311-cp311-win32.whl", hash = "sha256:9d2346b00af524757576cc2406414562cced1d4349c92166a0ee377a2a483a80"}, {file = "pymongo-4.6.0-cp311-cp311-win32.whl", hash = "sha256:7fc2bb8a74dcfcdd32f89528e38dcbf70a3a6594963d60dc9595e3b35b66e414"},
{file = "pymongo-4.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:c3c3525ea8658ee1192cdddf5faf99b07ebe1eeaa61bf32821126df6d1b8072b"}, {file = "pymongo-4.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6695d7136a435c1305b261a9ddb9b3ecec9863e05aab3935b96038145fd3a977"},
{file = "pymongo-4.5.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e5a27f348909235a106a3903fc8e70f573d89b41d723a500869c6569a391cff7"}, {file = "pymongo-4.6.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d603edea1ff7408638b2504905c032193b7dcee7af269802dbb35bc8c3310ed5"},
{file = "pymongo-4.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9a9a39b7cac81dca79fca8c2a6479ef4c7b1aab95fad7544cc0e8fd943595a2"}, {file = "pymongo-4.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79f41576b3022c2fe9780ae3e44202b2438128a25284a8ddfa038f0785d87019"},
{file = "pymongo-4.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:496c9cbcb4951183d4503a9d7d2c1e3694aab1304262f831d5e1917e60386036"}, {file = "pymongo-4.6.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49f2af6cf82509b15093ce3569229e0d53c90ad8ae2eef940652d4cf1f81e045"},
{file = "pymongo-4.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23cc6d7eb009c688d70da186b8f362d61d5dd1a2c14a45b890bd1e91e9c451f2"}, {file = "pymongo-4.6.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecd9e1fa97aa11bf67472220285775fa15e896da108f425e55d23d7540a712ce"},
{file = "pymongo-4.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fff7d17d30b2cd45afd654b3fc117755c5d84506ed25fda386494e4e0a3416e1"}, {file = "pymongo-4.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d2be5c9c3488fa8a70f83ed925940f488eac2837a996708d98a0e54a861f212"},
{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.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab6bcc8e424e07c1d4ba6df96f7fb963bcb48f590b9456de9ebd03b88084fe8"},
{file = "pymongo-4.5.0-cp312-cp312-win32.whl", hash = "sha256:77cfff95c1fafd09e940b3fdcb7b65f11442662fad611d0e69b4dd5d17a81c60"}, {file = "pymongo-4.6.0-cp312-cp312-win32.whl", hash = "sha256:47aa128be2e66abd9d1a9b0437c62499d812d291f17b55185cb4aa33a5f710a4"},
{file = "pymongo-4.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:e57d859b972c75ee44ea2ef4758f12821243e99de814030f69a3decb2aa86807"}, {file = "pymongo-4.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:014e7049dd019a6663747ca7dae328943e14f7261f7c1381045dfc26a04fa330"},
{file = "pymongo-4.5.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2b0176f9233a5927084c79ff80b51bd70bfd57e4f3d564f50f80238e797f0c8a"}, {file = "pymongo-4.6.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:288c21ab9531b037f7efa4e467b33176bc73a0c27223c141b822ab4a0e66ff2a"},
{file = "pymongo-4.5.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:89b3f2da57a27913d15d2a07d58482f33d0a5b28abd20b8e643ab4d625e36257"}, {file = "pymongo-4.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:747c84f4e690fbe6999c90ac97246c95d31460d890510e4a3fa61b7d2b87aa34"},
{file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:5caee7bd08c3d36ec54617832b44985bd70c4cbd77c5b313de6f7fce0bb34f93"}, {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:055f5c266e2767a88bb585d01137d9c7f778b0195d3dbf4a487ef0638be9b651"},
{file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:1d40ad09d9f5e719bc6f729cc6b17f31c0b055029719406bd31dde2f72fca7e7"}, {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:82e620842e12e8cb4050d2643a81c8149361cd82c0a920fa5a15dc4ca8a4000f"},
{file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:076afa0a4a96ca9f77fec0e4a0d241200b3b3a1766f8d7be9a905ecf59a7416b"}, {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:6b18276f14b4b6d92e707ab6db19b938e112bd2f1dc3f9f1a628df58e4fd3f0d"},
{file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:3fa3648e4f1e63ddfe53563ee111079ea3ab35c3b09cd25bc22dadc8269a495f"}, {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:680fa0fc719e1a3dcb81130858368f51d83667d431924d0bcf249644bce8f303"},
{file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:44ee985194c426ddf781fa784f31ffa29cb59657b2dba09250a4245431847d73"}, {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:3919708594b86d0f5cdc713eb6fccd3f9b9532af09ea7a5d843c933825ef56c4"},
{file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b33c17d9e694b66d7e96977e9e56df19d662031483efe121a24772a44ccbbc7e"}, {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db082f728160369d9a6ed2e722438291558fc15ce06d0a7d696a8dad735c236b"},
{file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d79ae3bb1ff041c0db56f138c88ce1dfb0209f3546d8d6e7c3f74944ecd2439"}, {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e4ed21029d80c4f62605ab16398fe1ce093fff4b5f22d114055e7d9fbc4adb0"},
{file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d67225f05f6ea27c8dc57f3fa6397c96d09c42af69d46629f71e82e66d33fa4f"}, {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bea9138b0fc6e2218147e9c6ce1ff76ff8e29dc00bb1b64842bd1ca107aee9f"},
{file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41771b22dd2822540f79a877c391283d4e6368125999a5ec8beee1ce566f3f82"}, {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a0269811661ba93c472c8a60ea82640e838c2eb148d252720a09b5123f2c2fe"},
{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.6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d6a1b1361f118e7fefa17ae3114e77f10ee1b228b20d50c47c9f351346180c8"},
{file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3236cf89d69679eaeb9119c840f5c7eb388a2110b57af6bb6baf01a1da387c18"}, {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e3b0127b260d4abae7b62203c4c7ef0874c901b55155692353db19de4b18bc4"},
{file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e1f61355c821e870fb4c17cdb318669cfbcf245a291ce5053b41140870c3e5cc"}, {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a49aca4d961823b2846b739380c847e8964ff7ae0f0a683992b9d926054f0d6d"},
{file = "pymongo-4.5.0-cp37-cp37m-win32.whl", hash = "sha256:49dce6957598975d8b8d506329d2a3a6c4aee911fa4bbcf5e52ffc6897122950"}, {file = "pymongo-4.6.0-cp37-cp37m-win32.whl", hash = "sha256:09c7de516b08c57647176b9fc21d929d628e35bcebc7422220c89ae40b62126a"},
{file = "pymongo-4.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f2227a08b091bd41df5aadee0a5037673f691e2aa000e1968b1ea2342afc6880"}, {file = "pymongo-4.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:81dd1308bd5630d2bb5980f00aa163b986b133f1e9ed66c66ce2a5bc3572e891"},
{file = "pymongo-4.5.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:435228d3c16a375274ac8ab9c4f9aef40c5e57ddb8296e20ecec9e2461da1017"}, {file = "pymongo-4.6.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:2f8c04277d879146eacda920476e93d520eff8bec6c022ac108cfa6280d84348"},
{file = "pymongo-4.5.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8e559116e4128630ad3b7e788e2e5da81cbc2344dee246af44471fa650486a70"}, {file = "pymongo-4.6.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5802acc012bbb4bce4dff92973dff76482f30ef35dd4cb8ab5b0e06aa8f08c80"},
{file = "pymongo-4.5.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:840eaf30ccac122df260b6005f9dfae4ac287c498ee91e3e90c56781614ca238"}, {file = "pymongo-4.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ccd785fafa1c931deff6a7116e9a0d402d59fabe51644b0d0c268295ff847b25"},
{file = "pymongo-4.5.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:b4fe46b58010115514b842c669a0ed9b6a342017b15905653a5b1724ab80917f"}, {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fe03bf25fae4b95d8afe40004a321df644400fdcba4c8e5e1a19c1085b740888"},
{file = "pymongo-4.5.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:a8127437ebc196a6f5e8fddd746bd0903a400dc6b5ae35df672dd1ccc7170a2a"}, {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:2ca0ba501898b2ec31e6c3acf90c31910944f01d454ad8e489213a156ccf1bda"},
{file = "pymongo-4.5.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:2988ef5e6b360b3ff1c6d55c53515499de5f48df31afd9f785d788cdacfbe2d3"}, {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:10a379fb60f1b2406ae57b8899bacfe20567918c8e9d2d545e1b93628fcf2050"},
{file = "pymongo-4.5.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:e249190b018d63c901678053b4a43e797ca78b93fb6d17633e3567d4b3ec6107"}, {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:a4dc1319d0c162919ee7f4ee6face076becae2abbd351cc14f1fe70af5fb20d9"},
{file = "pymongo-4.5.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:1240edc1a448d4ada4bf1a0e55550b6292420915292408e59159fd8bbdaf8f63"}, {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ddef295aaf80cefb0c1606f1995899efcb17edc6b327eb6589e234e614b87756"},
{file = "pymongo-4.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6d2a56fc2354bb6378f3634402eec788a8f3facf0b3e7d468db5f2b5a78d763"}, {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:518c90bdd6e842c446d01a766b9136fec5ec6cc94f3b8c3f8b4a332786ee6b64"},
{file = "pymongo-4.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a0aade2b11dc0c326ccd429ee4134d2d47459ff68d449c6d7e01e74651bd255"}, {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b80a4ee19b3442c57c38afa978adca546521a8822d663310b63ae2a7d7b13f3a"},
{file = "pymongo-4.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74c0da07c04d0781490b2915e7514b1adb265ef22af039a947988c331ee7455b"}, {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb438a8bf6b695bf50d57e6a059ff09652a07968b2041178b3744ea785fcef9b"},
{file = "pymongo-4.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3754acbd7efc7f1b529039fcffc092a15e1cf045e31f22f6c9c5950c613ec4d"}, {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3db7d833a7c38c317dc95b54e27f1d27012e031b45a7c24e360b53197d5f6e7"},
{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.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3729b8db02063da50eeb3db88a27670d85953afb9a7f14c213ac9e3dca93034b"},
{file = "pymongo-4.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e2654d1278384cff75952682d17c718ecc1ad1d6227bb0068fd826ba47d426a5"}, {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:39a1cd5d383b37285641d5a7a86be85274466ae336a61b51117155936529f9b3"},
{file = "pymongo-4.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:168172ef7856e20ec024fe2a746bfa895c88b32720138e6438fd765ebd2b62dd"}, {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7b0e6361754ac596cd16bfc6ed49f69ffcd9b60b7bc4bcd3ea65c6a83475e4ff"},
{file = "pymongo-4.5.0-cp38-cp38-win32.whl", hash = "sha256:b25f7bea162b3dbec6d33c522097ef81df7c19a9300722fa6853f5b495aecb77"}, {file = "pymongo-4.6.0-cp38-cp38-win32.whl", hash = "sha256:806e094e9e85d8badc978af8c95b69c556077f11844655cb8cd2d1758769e521"},
{file = "pymongo-4.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:b520aafc6cb148bac09ccf532f52cbd31d83acf4d3e5070d84efe3c019a1adbf"}, {file = "pymongo-4.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1394c4737b325166a65ae7c145af1ebdb9fb153ebedd37cf91d676313e4a67b8"},
{file = "pymongo-4.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8543253adfaa0b802bfa88386db1009c6ebb7d5684d093ee4edc725007553d21"}, {file = "pymongo-4.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a8273e1abbcff1d7d29cbbb1ea7e57d38be72f1af3c597c854168508b91516c2"},
{file = "pymongo-4.5.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:bc5d8c3647b8ae28e4312f1492b8f29deebd31479cd3abaa989090fb1d66db83"}, {file = "pymongo-4.6.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:e16ade71c93f6814d095d25cd6d28a90d63511ea396bd96e9ffcb886b278baaa"},
{file = "pymongo-4.5.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:505f8519c4c782a61d94a17b0da50be639ec462128fbd10ab0a34889218fdee3"}, {file = "pymongo-4.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:325701ae7b56daa5b0692305b7cb505ca50f80a1288abb32ff420a8a209b01ca"},
{file = "pymongo-4.5.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:53f2dda54d76a98b43a410498bd12f6034b2a14b6844ca08513733b2b20b7ad8"}, {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:cc94f9fea17a5af8cf1a343597711a26b0117c0b812550d99934acb89d526ed2"},
{file = "pymongo-4.5.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:9c04b9560872fa9a91251030c488e0a73bce9321a70f991f830c72b3f8115d0d"}, {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:21812453354b151200034750cd30b0140e82ec2a01fd4357390f67714a1bfbde"},
{file = "pymongo-4.5.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:58a63a26a1e3dc481dd3a18d6d9f8bd1d576cd1ffe0d479ba7dd38b0aeb20066"}, {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:0634994b026336195778e5693583c060418d4ab453eff21530422690a97e1ee8"},
{file = "pymongo-4.5.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:f076b779aa3dc179aa3ed861be063a313ed4e48ae9f6a8370a9b1295d4502111"}, {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:ad4f66fbb893b55f96f03020e67dcab49ffde0177c6565ccf9dec4fdf974eb61"},
{file = "pymongo-4.5.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:1b1d7d9aabd8629a31d63cd106d56cca0e6420f38e50563278b520f385c0d86e"}, {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:2703a9f8f5767986b4f51c259ff452cc837c5a83c8ed5f5361f6e49933743b2f"},
{file = "pymongo-4.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37df8f6006286a5896d1cbc3efb8471ced42e3568d38e6cb00857277047b0d63"}, {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bafea6061d63059d8bc2ffc545e2f049221c8a4457d236c5cd6a66678673eab"},
{file = "pymongo-4.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56320c401f544d762fc35766936178fbceb1d9261cd7b24fbfbc8fb6f67aa8a5"}, {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f28ae33dc5a0b9cee06e95fd420e42155d83271ab75964baf747ce959cac5f52"},
{file = "pymongo-4.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbd705d5f3c3d1ff2d169e418bb789ff07ab3c70d567cc6ba6b72b04b9143481"}, {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16a534da0e39785687b7295e2fcf9a339f4a20689024983d11afaa4657f8507"},
{file = "pymongo-4.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80a167081c75cf66b32f30e2f1eaee9365af935a86dbd76788169911bed9b5d5"}, {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef67fedd863ffffd4adfd46d9d992b0f929c7f61a8307366d664d93517f2c78e"},
{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.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05c30fd35cc97f14f354916b45feea535d59060ef867446b5c3c7f9b609dd5dc"},
{file = "pymongo-4.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf62da7a4cdec9a4b2981fcbd5e08053edffccf20e845c0b6ec1e77eb7fab61d"}, {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1c63e3a2e8fb815c4b1f738c284a4579897e37c3cfd95fdb199229a1ccfb638a"},
{file = "pymongo-4.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b5bbb87fa0511bd313d9a2c90294c88db837667c2bda2ea3fa7a35b59fd93b1f"}, {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e5e193f89f4f8c1fe273f9a6e6df915092c9f2af6db2d1afb8bd53855025c11f"},
{file = "pymongo-4.5.0-cp39-cp39-win32.whl", hash = "sha256:465fd5b040206f8bce7016b01d7e7f79d2fcd7c2b8e41791be9632a9df1b4999"}, {file = "pymongo-4.6.0-cp39-cp39-win32.whl", hash = "sha256:a09bfb51953930e7e838972ddf646c5d5f984992a66d79da6ba7f6a8d8a890cd"},
{file = "pymongo-4.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:63d8019eee119df308a075b8a7bdb06d4720bf791e2b73d5ab0e7473c115d79c"}, {file = "pymongo-4.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:107a234dc55affc5802acb3b6d83cbb8c87355b38a9457fcd8806bdeb8bce161"},
{file = "pymongo-4.5.0.tar.gz", hash = "sha256:681f252e43b3ef054ca9161635f81b730f4d8cadd28b3f2b2004f5a72f853982"}, {file = "pymongo-4.6.0.tar.gz", hash = "sha256:fb1c56d891f9e34303c451998ef62ba52659648bb0d75b03c5e4ac223a3342c2"},
] ]
[package.dependencies] [package.dependencies]
@ -477,6 +494,7 @@ encryption = ["certifi", "pymongo[aws]", "pymongocrypt (>=1.6.0,<2.0.0)"]
gssapi = ["pykerberos", "winkerberos (>=0.5.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)"] ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"]
snappy = ["python-snappy"] snappy = ["python-snappy"]
test = ["pytest (>=7)"]
zstd = ["zstandard"] zstd = ["zstandard"]
[[package]] [[package]]
@ -518,6 +536,23 @@ files = [
{file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, {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]] [[package]]
name = "Telethon" name = "Telethon"
version = "2.0.0a0" version = "2.0.0a0"
@ -541,7 +576,7 @@ doc = ["sphinx_rtd_theme (>=1.2,<2.0)", "types-docutils (>=0.20,<1.0)"]
type = "git" type = "git"
url = "https://github.com/LonamiWebs/Telethon" url = "https://github.com/LonamiWebs/Telethon"
reference = "v2" reference = "v2"
resolved_reference = "6e88264b284a33bc0ad3dfe543e3ec2ec8191ccc" resolved_reference = "ae44426a78d553cd1a3ea9f536c0f2c90751d657"
subdirectory = "client" subdirectory = "client"
[[package]] [[package]]
@ -578,4 +613,4 @@ files = [
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.11" python-versions = "^3.11"
content-hash = "9018cdd99fd16a737946674c19bc23ffd394c3a63f7e90c6d7db606d314ff8cc" content-hash = "4dcc24786204463583f6e954cb90106d79ec504ffa7f3f9ad321444e55cc5e9a"

View File

@ -17,6 +17,7 @@ openai = {version = "^1.0.0b3", allow-prereleases = true}
telemongo = {path = "../telethon-session-mongo", develop=true} telemongo = {path = "../telethon-session-mongo", develop=true}
mongoengine = "^0.27.0" mongoengine = "^0.27.0"
telethon = {git = "https://github.com/LonamiWebs/Telethon", rev = "v2", subdirectory = "client"} telethon = {git = "https://github.com/LonamiWebs/Telethon", rev = "v2", subdirectory = "client"}
jsonpickle = "^3.0.2"
[build-system] [build-system]
requires = ["poetry-core"] requires = ["poetry-core"]