<file_sep>import asyncio
import click
from utils.db import db as dbs


@click.group()
def db():
    pass


@db.command()
def seed():
    asyncio.run(dbs.seed())


cli = click.CommandCollection(sources=[db])


if __name__ == '__main__':
    cli()
<file_sep>import asyncio
import itertools
import logging

from lib import telegram as tglib
from conf import config
from utils.functions import commands as bot_commands
from utils.serialization import serialize_response as serialize
from utils import command_parser


tg_app = tglib.TelegramBot(token=config.TELEGRAM_BOT_TOKEN)
logging.basicConfig(level=logging.INFO)


async def send_response(command: str, message: str, function, chat_id: int):
    """
    Dynamic choose function for send response to user (result of functions)
    """
    response = {
        'chat_id': chat_id, 
        'text': 'Command not found'
    }
    
    if not function:
        return await tg_app.send_message(response)
    

    send_type = {
        'photo': tg_app.send_photo, 
        'text': tg_app.send_message
    }
    # do some work
    result = function(message)    
    
    result_type = result['result_type']
    response[result['result_type']] = result['result']
    if function.__name__.endswith('cmd'):
        response['parse_mode'] = 'MarkdownV2'
    # Send typed response
    await send_type[result_type](params=response)



async def main():
    offset = 0      # last update id
    for i in itertools.count(1):
        data: dict = await tg_app.get_updates(offset=offset)

        # Serialize data
        tg_resp = serialize(data.get('result', []))

        ready = bool(data['ok'])
        
        if tg_resp.results and ready:
            result = tg_resp.results[0]
            offset = result.update_id
            offset += 1
            result = result.dict()          
            last_chat_text = result['message']['text']
            last_chat_id = result['message']['chat']['id']
            last_chat_name = result['message']['chat']['username']

            command, message = command_parser.parse_user_message(last_chat_text)
            if not message:
                continue
            await send_response(command, message, bot_commands.get(command), last_chat_id)

            logging.info('[%s]: [%s]', last_chat_name, last_chat_text)

        await asyncio.sleep(1)


if __name__ == '__main__':
    asyncio.run(main())
<file_sep>import typing
from typing import AnyStr, Tuple, Union, List


def parse_user_message(user_message: AnyStr = None) -> Union[Tuple[AnyStr, AnyStr], Tuple[AnyStr, None]]:
    """Parsing string with message and split
    
    :user_message: string like '/echo text'
    @return: tuple with command/message
    """

    command, text = None, ' '

    if not user_message:
        return command, text
    
    user_message: List[AnyStr, AnyStr] = user_message.split(' ')
    
    try:
        # Cut first symbol '/' from command
        if len(user_message) == 1:
            command = user_message[0][1:] if user_message[0].startswith('/') else user_message[0]
            return command, text
        command, text = user_message.pop(0)[1:], ' '.join(user_message)

    except Exception as e:
        # TODO: add logging
        pass
    
    return command, text
<file_sep>import aiohttp
import typing


class TelegramBot:
    func_result: dict = {}

    def __init__(self, token):
        self.token: str = token
        self.API_URL: str = f'https://api.telegram.org/bot{self.token}/'

    async def do_request(self, query: str, params: dict = None) -> dict:
        if not params:
            params = {}

        async with aiohttp.ClientSession() as session:
            async with session.get(query, params=params) as resp:
                return await resp.json()


    async def run_function(self, func, *args, **kwargs):
        # TODO: standartize in future functions
        self.func_result = func(*args, **kwargs)
        return self.func_result

    async def get_updates(self, timeout: int = 30, offset: int = 0) -> dict:
        """Pull latest events from bot"""
        params = {'timeout': timeout}
        if offset:
            params['offset'] = offset
        query: str = self.API_URL + 'getUpdates'
        return await self.do_request(query, params)

    async def send_message(self, params: dict) -> dict:
        """Send answer for messages
        chat_id (from): int
        text: str
        """
        query = self.API_URL + 'sendMessage'
        return await self.do_request(query, params)

    async def send_photo(self, params: dict) -> dict:
        """Send photo for messages
        chat_id (from): int
        text: str
        """
        query = self.API_URL + 'sendPhoto'
        return await self.do_request(query, params)
<file_sep>aiodns==2.0.0
aiohttp==3.6.2
aiosqlite==0.15.0
async-timeout==3.0.1
attrs==20.2.0
brotlipy==0.7.0
cchardet==2.1.6
certifi==2020.6.20
cffi==1.14.3
chardet==3.0.4
click==7.1.2
databases==0.3.2
idna==2.10
Mako==1.1.3
markovify==0.8.3
MarkupSafe==1.1.1
multidict==4.7.6
pycares==3.1.1
pycparser==2.20
requests==2.24.0
typing-extensions==3.7.4.3
ujson==4.0.1
Unidecode==1.1.1
urllib3==1.25.10
yarl==1.6.0
<file_sep>from enum import Enum
from dataclasses import dataclass, asdict, field
from typing import List, Dict


ResultTypes = Enum('ResultTypes', ((s, s) for s in ('text', 'photo')))


@dataclass
class Base:
    def dict(self):
        return asdict(self)

    def __getattr__(self, name):
        if name.endswith('_'):
            return getattr(self, name[:-1])


@dataclass
class BaseResponse(Base):
    result_type: str = ResultTypes.text.value
    result: str = None


@dataclass
class TelegramMessageFrom(Base):
    id: int = 0
    is_bot: bool = False
    first_name: str = None
    last_name: str = None
    username: str = None
    language_code: str = None


@dataclass
class TelegramMessageChat(Base):
    id: int = 0
    first_name: str = None
    last_name: str = None
    username: str = None
    type: str = None


@dataclass
class TelegramMessage(Base):
    date: int = 0
    text: str = None
    message_id: int = 0
    entities: list = None   # [{"offset":0,"length":5,"type":"bot_command"}]
    from_: TelegramMessageFrom = field(default_factory=TelegramMessageFrom)
    chat: TelegramMessageChat = field(default_factory=TelegramMessageChat)


@dataclass
class TelegramEntity(Base):
    update_id: int = None
    message: TelegramMessage = field(default_factory=TelegramMessage)
    edited_message: TelegramMessage = field(default_factory=TelegramMessage)



@dataclass
class TelegramResponse(Base):
    results: List[TelegramEntity] = field(default_factory=TelegramEntity)
<file_sep># This file include function by name
import os
import subprocess
import requests
import itertools

from random import randint
from conf import config
from models.response import BaseResponse, ResultTypes


commands = {}

def command(command_name): 
    def wrap(f): 
        commands[command_name[1:]] = f
        return f
    return wrap


# @command('/cats')
# def command_random_cats(search_query: str = 'cats') -> dict:
#    """Get random cats"""
#    resp = BaseResponse()
#
#    headers = {'Authorization': config.PEXELS_IMAGE_API_TOKEN}
#    params = {'query': search_query or 'cats', 'page': randint(1,2852), 'per_page': 1}
#
#    r = requests.get(url=config.PEXELS_IMAGE_API_URL, params=params, headers=headers)
#    data: dict = r.json()
    
#    if r.status_code == 200 and data:
#        resp.result_type = ResultTypes.photo.value
#        resp.result = data['photos'][-1]['src']['large']
#    
#    return resp.dict()


@command('/echo')
def command_echo(text: str) -> dict:
    """Test echo function"""
    resp = BaseResponse()
    resp.result = text

    return resp.dict()

@command('/start')
def command_start(*args) -> dict:
    resp = BaseResponse()

    text = ''
    for name, f in commands.items():
        if name == 'start':
            continue

        text += f"""/{name}: {f.__doc__};
"""

    resp.result = text
    
    return resp.dict()


@command('/public_domain')
def command_domain(*args, **kwargs) -> dict:
    """ Show public domain for server"""
    resp = BaseResponse()
    try:
        r = requests.get('http://127.0.0.1:4040/api/tunnels')
        resp.result = str(r.json()['tunnels'][0]['public_url'])
    except Exception as e:
        resp.result = str(e)

    return resp.dict()


@command('/cmd')
def command_cmd(*args):
    """ Server console"""
    resp = BaseResponse()
    params = list(args)[0].split(' ')
    auth = params.pop(0)
    if auth == config.AUTH:
        try:
            r = subprocess.check_output(params)
            resp.result = f"""```{r.decode('utf-8')}```"""
        except Exception as e:
            resp.result = str(e)
        return resp.dict()

    resp.result = 'Wrong auth'
    return resp.dict()


<file_sep>from models.response import TelegramResponse, TelegramEntity, TelegramMessage, TelegramMessageChat


def serialize_response(results: []) -> TelegramResponse:
    """
    Serializing response from telegram API to Dataclass objects
    """
    telegram_response = TelegramResponse(results=[TelegramEntity(**row) for row in results])

    for i, row in enumerate(telegram_response.results):
        if row.edited_message:
            continue

        if row and row.message and row.message.get('from'):
            row.message['from_'] = row.message.pop('from')
        message = TelegramMessage(**row.message)
        message.chat = TelegramMessageChat(**message.chat)
        telegram_response.results[i].message = message

    return telegram_response
<file_sep>DSN = ''
TELEGRAM_BOT_TOKEN = ''
PEXELS_IMAGE_API_TOKEN = ''     # pexels.com
PEXELS_IMAGE_API_URL = 'https://api.pexels.com/v1/search'<file_sep>import asyncio
from databases import Database
from conf import config


class DatabaseTG(Database):
    async def seed(self):
        await self.connect()
        # SQL query for sqlite
        queries = ["""
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY, 
                tg_id INTEGER, 
                tg_username TEXT, 
                created_at TEXT, 
                about TEXT
            );""", 
            """
            CREATE TABLE IF NOT EXISTS message_log (
               id INTEGER PRIMARY KEY,
               user_id INTEGER,
               message_id INTEGER,
               message TEXT,
               FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
            );"""
        ]
        for q in queries:     
            await self.execute(q)


db = DatabaseTG(config.DSN)


if __name__ == '__main__':
    asyncio.run(db.seed())
