Compare commits

...

9 commits

5 changed files with 215 additions and 19 deletions

73
functions/convert.py Normal file
View file

@ -0,0 +1,73 @@
from utils.format_number import format_number
import yaml
import aiohttp
import json
import re
from datetime import datetime
from http import HTTPStatus
from decimal import Decimal, ROUND_DOWN
from utils.format_number import format_number
config = yaml.safe_load(open('config.yaml'))
class Converter:
def __init__(self):
self.amount: float = 1.0
self.conv_amount: float = 0.0
self.from_currency: str = ''
self.conv_currency: str = ''
async def convert(self) -> None:
if not await self.kekkai():
await self.ddg()
number = Decimal(str(self.conv_amount))
self.conv_amount = format_number(number.quantize(Decimal('1.0000'), rounding=ROUND_DOWN))
async def kekkai(self) -> bool:
date = datetime.today().strftime('%Y-%m-%d')
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=3)) as session:
async with session.get(f'{config['kekkai_instance']}/api/getRate/', params={
'from_currency': self.from_currency,
'conv_currency': self.conv_currency,
'date': date,
'conv_amount': self.amount
}) as res:
if not HTTPStatus(res.status).is_success:
return False
data = await res.json()
self.conv_amount = data.get('conv_amount', 0.0)
return True
async def ddg(self) -> None:
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=3)) as session:
async with session.get(
'https://duckduckgo.com/js/spice/currency/'
f'{self.amount}/{self.from_currency}/{self.conv_currency}'
) as res:
data_text = await res.text()
data = json.loads(re.findall(r'\(\s*(.*)\s*\);$', data_text)[0])
for key in ['terms', 'privacy', 'timestamp']:
data.pop(key, None)
if len(data.get('to')) == 0:
raise RuntimeError('Failed to get the exchange rate from DuckDuckGo')
conv = data.get('to')[0]
conv_amount = conv.get('mid')
if conv_amount is None:
raise RuntimeError('Error when converting currency via DuckDuckGo')
self.conv_amount = float(conv_amount)

19
functions/create_chart.py Normal file
View file

@ -0,0 +1,19 @@
import yaml
import aiohttp
from http import HTTPStatus
config = yaml.safe_load(open('config.yaml'))
async def create_chart(from_currency: str, conv_currency: str) -> (dict, None):
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=3)) as session:
async with session.get(f'{config["kekkai_instance"]}/api/getChart/week/', params={
'from_currency': from_currency,
'conv_currency': conv_currency
}) as res:
if not HTTPStatus(res.status).is_success:
return None
data = await res.json()
return data.get('message', None)

95
main.py
View file

@ -1,34 +1,94 @@
import logging
import sys
from functions.convert import Converter
from utils.format_number import format_number
from utils.inline_query import reply
from functions.create_chart import create_chart
import yaml
from aiohttp import web
from aiogram import Bot, Dispatcher, Router
from aiogram import Bot, Dispatcher, Router, types
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.filters import CommandStart
from aiogram.types import Message
from aiogram.utils.markdown import hbold
from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application
import hashlib
config = yaml.safe_load(open('config.yaml'))
bot = Bot(token=config['telegram_token'], default=DefaultBotProperties(parse_mode=ParseMode.HTML))
router = Router()
@router.message()
async def echo_handler(message: Message) -> None:
try:
# Send a copy of the received message
await message.send_copy(chat_id=message.chat.id)
except TypeError:
# But not all the types is supported to be copied so need to handle it
await message.answer("Nice try!")
@router.inline_query()
async def currency(query: types.InlineQuery) -> None:
text = query.query.lower()
args = text.split()
result_id = hashlib.md5(text.encode()).hexdigest()
get_bot = await bot.get_me()
if len(args) < 2:
return await reply(result_id,
[("2 or 3 arguments are required.",
f'@{get_bot.username} USD RUB \n'
f'@{get_bot.username} 12 USD RUB',
None, None)],
query)
conv = Converter()
from_currency, conv_currency = '', ''
if len(args) == 3:
try:
conv.amount = float(args[0].replace(',', '.'))
if conv.amount < 0:
return await reply(result_id, [("Negative amounts are not supported.", None, None)], query)
except ValueError:
return await reply(result_id, [("Please enter a valid number for the amount.",
f'@{get_bot.username} USD RUB \n'
f'@{get_bot.username} 12 USD RUB',
None, None)], query)
from_currency = args[1]
conv_currency = args[2]
elif len(args) == 2:
from_currency = args[0]
conv_currency = args[1]
else:
return await reply(result_id,
[(
'The source and target currency could not be determined.',
None, None
)],
query)
if not conv_currency or not from_currency:
return await reply(result_id, [('The currency exchange rate could not be found.', None, None)], query)
conv.from_currency = from_currency.upper()
conv.conv_currency = conv_currency.upper()
await conv.convert()
chart = await create_chart(from_currency, conv_currency)
message = f'{format_number(conv.amount)} {conv.from_currency} = {conv.conv_amount} {conv.conv_currency}'
results = [(message, None, None)]
if chart:
results.insert(0, (f'{message}\n[График]({chart})', None, chart))
await reply(result_id, results, query)
async def on_startup(bot: Bot) -> None:
# Убедитесь, что передаете HTTPS URL
await bot.set_webhook(f"{config['webhook']['base_url']}{config['webhook']['path']}", secret_token=config['webhook']['secret_token'])
await bot.set_webhook(
f"{config['webhook']['base_url']}{config['webhook']['path']}",
secret_token=config['webhook']['secret_token'],
allowed_updates=['inline_query']
)
def main() -> None:
@ -37,8 +97,6 @@ def main() -> None:
dp.include_router(router)
dp.startup.register(on_startup)
bot = Bot(token=config['telegram_token'], default=DefaultBotProperties(parse_mode=ParseMode.HTML))
app = web.Application()
webhook_requests_handler = SimpleRequestHandler(
dispatcher=dp,
@ -53,5 +111,4 @@ def main() -> None:
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO, stream=sys.stdout)
main()

12
utils/format_number.py Normal file
View file

@ -0,0 +1,12 @@
from decimal import Decimal
def format_number(number):
number = Decimal(str(number))
formatted_integer_part = '{:,.0f}'.format(number).replace(',', ' ')
if '.' in str(number):
fractional_part = str(number).split('.')[1]
return formatted_integer_part + '.' + fractional_part
else:
return formatted_integer_part

35
utils/inline_query.py Normal file
View file

@ -0,0 +1,35 @@
from aiogram import types
import re
async def reply(result_id: str, args: list, query: types.InlineQuery) -> None:
if not args:
return
articles = []
for idx, arg in enumerate(args):
title = arg[0]
description = arg[1] if arg[1] else None
img = arg[2] if arg[2] else None
article = types.InlineQueryResultArticle(
id=f"{result_id}_{idx}",
title=re.sub(r'\bГрафик\b|\[([^\]]+)\]\([^)]+\)', '', title, flags=re.IGNORECASE),
thumbnail_url=img,
description=description,
input_message_content=types.InputTextMessageContent(
message_text=title,
parse_mode='markdown'
)
)
articles.append(article)
await query.answer(
results=articles,
parse_mode='markdown',
cache_time=0,
is_personal=True
)