Compare commits

..

No commits in common. "74814012216ea2f76308052006c104d783adeedc" and "3a83e553430b9474fa86afad5c1555ba5a084e0c" have entirely different histories.

17 changed files with 430 additions and 277 deletions

12
bot.py
View file

@ -1,13 +1,13 @@
import yaml
from aiogram import Bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
import yaml
from database.server import Database
db = Database("/data/shirino.db")
config = yaml.safe_load(open("../config.yaml", "r", encoding="utf-8"))
db = Database('/data/shirino.db')
config = yaml.safe_load(open('../config.yaml', 'r', encoding='utf-8'))
bot = Bot(
token=config["telegram_token"],
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
)
token=config['telegram_token'],
default=DefaultBotProperties(parse_mode=ParseMode.HTML)
)

View file

@ -1,14 +1,14 @@
import hashlib
from aiogram import Router, types
from aiogram import types, Router
from aiogram.filters import Command
from bot import bot, db
from functions.convert import Converter
from functions.create_chart import create_chart
from i18n.localization import I18n
from utils.format_number import format_number
from utils.inline_query import reply
from i18n.localization import I18n
router = Router()
i18n = I18n()
@ -21,38 +21,46 @@ async def currency(query: types.InlineQuery) -> None:
result_id = hashlib.md5(text.encode()).hexdigest()
get_bot = await bot.get_me()
data = await db.fetch("SELECT * FROM users WHERE user_id = ?", query.from_user.id)
data = await db.fetch(
'SELECT lang, chart, chart_period '
'FROM users WHERE user_id = ?',
query.from_user.id
)
lang = data.get("lang")
lang = data.get('lang')
locale = i18n.get_locale(lang)
currency_example = locale["currency_example"].format(bot_username=get_bot.username)
currency_example = locale["currency_example"].format(
bot_username=get_bot.username
)
if len(args) < 2:
await reply(
result_id,
[(locale["error_not_enough_args"], currency_example, None, None)],
query,
query
)
return
conv = Converter()
from_currency, conv_currency = "", ""
from_currency, conv_currency = '', ''
if len(args) == 3:
try:
conv.amount = float(args[0].replace(",", "."))
conv.amount = float(args[0].replace(',', '.'))
if conv.amount < 0:
await reply(
result_id, [(locale["error_negative_amount"], None, None)], query
result_id,
[(locale["error_negative_amount"], None, None)],
query
)
return
except ValueError:
await reply(
result_id,
[(locale["error_invalid_number"], currency_example, None, None)],
query,
query
)
return
from_currency = args[1]
@ -74,17 +82,20 @@ async def currency(query: types.InlineQuery) -> None:
try:
await conv.convert()
except RuntimeError:
await reply(result_id, [(locale["error_currency_rate"], None, None)], query)
await reply(
result_id,
[(locale["error_currency_rate"], None, None)],
query
)
return
chart = None
if bool(data.get("chart", 1)):
if bool(data.get('chart', 1)):
chart = await create_chart(
from_currency,
conv_currency,
data.get("chart_period", "month"),
data.get("chart_backend", "matplotlib"),
data.get('chart_period', 'month')
)
message = (
@ -94,6 +105,13 @@ async def currency(query: types.InlineQuery) -> None:
results = [(message, None, None)]
if chart:
results.insert(0, (message, None, chart))
results.insert(
0,
(
message,
None,
chart
)
)
await reply(result_id, results, query)

View file

@ -1,10 +1,12 @@
import json
from typing import List, Optional, Tuple
from typing import Optional, Tuple, List
from aiogram import Router, types
from aiogram.filters import Command
from aiogram.types import (CallbackQuery, InlineKeyboardButton,
InlineKeyboardMarkup)
from aiogram.types import (
InlineKeyboardMarkup,
InlineKeyboardButton,
CallbackQuery,
)
import json
from bot import db
from i18n.localization import I18n
@ -29,11 +31,17 @@ PERIOD_OPTIONS: List[PeriodOption] = [
async def get_user_locale(user_id: int) -> dict:
data = await db.fetch("SELECT lang FROM users WHERE user_id = $1", user_id)
data = await db.fetch(
'SELECT lang FROM users WHERE user_id = $1', user_id
)
if not data:
await db.insert("INSERT INTO users (user_id) VALUES (?)", user_id)
data = await db.fetch("SELECT lang FROM users WHERE user_id = $1", user_id)
return i18n.get_locale(data.get("lang", "en"))
await db.insert(
'INSERT INTO users (user_id) VALUES (?)', user_id
)
data = await db.fetch(
'SELECT lang FROM users WHERE user_id = $1', user_id
)
return i18n.get_locale(data.get('lang', 'en'))
def build_options_keyboard(
@ -49,9 +57,13 @@ def build_options_keyboard(
for code, label_key in options:
label = locale.get(label_key, label_key)
text = f"[X] {label}" if code == current_value else label
text = (
f"[X] {label}" if code == current_value else label
)
row.append(
InlineKeyboardButton(text=text, callback_data=f"{callback_prefix}_{code}")
InlineKeyboardButton(
text=text, callback_data=f"{callback_prefix}_{code}"
)
)
if len(row) == buttons_per_row:
buttons.append(row)
@ -62,7 +74,7 @@ def build_options_keyboard(
buttons.append(
[
InlineKeyboardButton(
text=locale["back"],
text=locale.get("back"),
callback_data=back_callback,
)
]
@ -73,23 +85,25 @@ def build_options_keyboard(
def get_chart_toggle_keyboard(
chart_enabled: bool, locale: dict
) -> InlineKeyboardMarkup:
toggle_text = locale["chart_disable"] if chart_enabled else locale["chart_enable"]
toggle_text = (
locale.get("chart_disable")
if chart_enabled
else locale.get("chart_enable")
)
return InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(text=toggle_text, callback_data="chart_toggle"),
InlineKeyboardButton(
text=locale["chart_period"],
callback_data="chart_period",
text=toggle_text, callback_data="chart_toggle"
),
InlineKeyboardButton(
text=locale["setting_backend"],
callback_data="setting_backend",
text=locale.get("chart_period"),
callback_data="chart_period",
),
],
[
InlineKeyboardButton(
text=locale["back"],
text=locale.get("back"),
callback_data="back_to_settings",
),
],
@ -116,8 +130,9 @@ async def safe_edit_message_text(
new_text_clean = new_text.strip()
is_text_same = current_text == new_text_clean
is_markup_same = markup_to_json(message.reply_markup) == markup_to_json(
new_reply_markup
is_markup_same = (
markup_to_json(message.reply_markup)
== markup_to_json(new_reply_markup)
)
if is_text_same and is_markup_same:
@ -138,18 +153,20 @@ async def settings_handler(message: types.Message):
inline_keyboard=[
[
InlineKeyboardButton(
text=locale["setting_chart"],
text=locale.get("setting_chart"),
callback_data="setting_chart",
),
InlineKeyboardButton(
text=locale["setting_lang"],
text=locale.get("setting_lang"),
callback_data="setting_lang",
),
],
]
)
await message.answer(locale["settings_title"], reply_markup=settings_keyboard)
await message.answer(
locale.get("settings_title"), reply_markup=settings_keyboard
)
@router.callback_query(lambda c: c.data == "setting_lang")
@ -157,9 +174,10 @@ async def show_language_menu(callback: CallbackQuery):
locale = await get_user_locale(callback.from_user.id)
data = await db.fetch(
"SELECT lang FROM users WHERE user_id = $1", callback.from_user.id
'SELECT lang FROM users WHERE user_id = $1',
callback.from_user.id
)
current_lang = data.get("lang", "en")
current_lang = data.get('lang', 'en')
keyboard = build_options_keyboard(
options=LANG_OPTIONS,
@ -169,14 +187,15 @@ async def show_language_menu(callback: CallbackQuery):
back_callback="back_to_settings",
)
await safe_edit_message_text(callback, locale["choose_language"], keyboard)
await safe_edit_message_text(
callback, locale.get("choose_language"), keyboard
)
@router.callback_query(lambda c: c.data and c.data.startswith("lang_"))
async def language_selected(callback: CallbackQuery):
lang = callback.data.split("_")[1]
await db.update(
"UPDATE users SET lang = $1 WHERE user_id = $2",
'UPDATE users SET lang = $1 WHERE user_id = $2',
lang,
callback.from_user.id,
)
@ -190,56 +209,13 @@ async def language_selected(callback: CallbackQuery):
back_callback="back_to_settings",
)
await safe_edit_message_text(callback, locale["choose_language"], keyboard)
await callback.answer(locale["language_set"].format(lang=lang))
@router.callback_query(lambda c: c.data == "setting_backend")
async def show_backend_settings(callback: CallbackQuery):
locale = await get_user_locale(callback.from_user.id)
data = await db.fetch(
"SELECT chart_backend, lang FROM users WHERE user_id = $1",
callback.from_user.id,
)
current_backend = data["chart_backend"]
backend_label = locale.get(current_backend, current_backend)
keyboard = build_options_keyboard(
options=[("typst", "Typst"), ("matplotlib", "Matplotlib")],
current_value=current_backend,
callback_prefix="backend",
locale=locale,
back_callback="back_to_settings",
)
await safe_edit_message_text(
callback, f"{locale['choose_chart_backend']}", keyboard
callback, locale.get("choose_language"), keyboard
)
@router.callback_query(lambda c: c.data and c.data.startswith("backend_"))
async def set_backend(callback: CallbackQuery):
backend = callback.data.split("_")[1]
await db.update(
"UPDATE users SET chart_backend = $1 WHERE user_id = $2",
backend,
callback.from_user.id,
await callback.answer(
locale.get("language_set").format(lang=lang)
)
locale = await get_user_locale(callback.from_user.id)
keyboard = build_options_keyboard(
options=[("typst", "Typst"), ("matplotlib", "Matplotlib")],
current_value=backend,
callback_prefix="backend",
locale=locale,
back_callback="back_to_settings",
)
await safe_edit_message_text(callback, locale["choose_chart_backend"], keyboard)
@router.callback_query(lambda c: c.data == "back_to_settings")
async def back_to_settings(callback: CallbackQuery):
@ -249,30 +225,26 @@ async def back_to_settings(callback: CallbackQuery):
inline_keyboard=[
[
InlineKeyboardButton(
text=locale["setting_chart"],
text=locale.get("setting_chart"),
callback_data="setting_chart",
),
InlineKeyboardButton(
text=locale["setting_lang"],
text=locale.get("setting_lang"),
callback_data="setting_lang",
),
InlineKeyboardButton(
text=locale["setting_backend"],
callback_data="setting_backend",
),
],
]
)
await safe_edit_message_text(
callback, locale.get("settings_title", "Settings"), settings_keyboard
callback, locale.get("settings_title"), settings_keyboard
)
@router.callback_query(lambda c: c.data == "setting_chart")
async def show_chart_settings(callback: CallbackQuery):
data = await db.fetch(
"SELECT * FROM users WHERE user_id = $1",
'SELECT chart, chart_period, lang FROM users WHERE user_id = $1',
callback.from_user.id,
)
lang = data.get("lang", "en")
@ -281,18 +253,15 @@ async def show_chart_settings(callback: CallbackQuery):
chart_status = bool(data.get("chart", 1))
period = data.get("chart_period")
status_text = (
locale.get("enabled", "Enabled")
if chart_status
else locale.get("disabled", "Disabled")
)
status_text = locale.get("enabled") \
if chart_status \
else locale.get("disabled")
period_text = locale.get(period, period)
text = (
f"{locale['chart_settings']}\n"
f"{locale['status']}: {status_text}\n"
f"{locale['period']}: {period_text}\n"
f"{locale['selected_chart_backend']}: {data.get('chart_backend')}"
f"{locale.get('chart_settings')}\n"
f"{locale.get('status')}: {status_text}\n"
f"{locale.get('period')}: {period_text}"
)
keyboard = get_chart_toggle_keyboard(chart_status, locale)
@ -302,7 +271,8 @@ async def show_chart_settings(callback: CallbackQuery):
@router.callback_query(lambda c: c.data == "chart_toggle")
async def toggle_chart(callback: CallbackQuery):
data = await db.fetch(
"SELECT chart, lang FROM users WHERE user_id = $1", callback.from_user.id
'SELECT chart, lang FROM users WHERE user_id = $1',
callback.from_user.id
)
lang = data.get("lang", "en")
locale = i18n.get_locale(lang)
@ -311,9 +281,15 @@ async def toggle_chart(callback: CallbackQuery):
new_status = not current_status
await db.update(
"UPDATE users SET chart = $1 WHERE user_id = $2",
new_status,
callback.from_user.id,
'UPDATE users SET chart = $1 WHERE user_id = $2',
new_status, callback.from_user.id
)
await callback.answer(
locale.get(
f"chart_now_{'enabled' if new_status else 'disabled'}",
f"Chart now {'enabled' if new_status else 'disabled'}",
)
)
await show_chart_settings(callback)
@ -322,7 +298,8 @@ async def toggle_chart(callback: CallbackQuery):
@router.callback_query(lambda c: c.data == "chart_period")
async def change_chart_period(callback: CallbackQuery):
data = await db.fetch(
"SELECT chart_period, lang FROM users WHERE user_id = $1", callback.from_user.id
'SELECT chart_period, lang FROM users WHERE user_id = $1',
callback.from_user.id
)
lang = data.get("lang", "en")
locale = i18n.get_locale(lang)
@ -337,16 +314,19 @@ async def change_chart_period(callback: CallbackQuery):
back_callback="setting_chart",
)
await safe_edit_message_text(callback, locale["choose_period"], keyboard)
await safe_edit_message_text(
callback,
locale.get("choose_period"),
keyboard
)
@router.callback_query(lambda c: c.data and c.data.startswith("period_"))
async def set_chart_period(callback: CallbackQuery):
period = callback.data.split("_")[1]
await db.update(
"UPDATE users SET chart_period = $1 WHERE user_id = $2",
period,
callback.from_user.id,
'UPDATE users SET chart_period = $1 WHERE user_id = $2',
period, callback.from_user.id
)
locale = await get_user_locale(callback.from_user.id)
@ -359,4 +339,9 @@ async def set_chart_period(callback: CallbackQuery):
back_callback="setting_chart",
)
await safe_edit_message_text(callback, locale["choose_period"], keyboard)
await safe_edit_message_text(
callback,
locale.get("choose_period"),
keyboard
)
await callback.answer(locale.get("period_set").format(period=period))

View file

@ -1,7 +1,6 @@
import re
from aiogram import Router, types
from aiogram import types, Router
from aiogram.filters import CommandStart
import re
from bot import bot, db
from i18n.localization import I18n
@ -9,20 +8,19 @@ from i18n.localization import I18n
router = Router()
i18n = I18n()
def escape_md_v2(text: str) -> str:
return re.sub(r"([_*\[\]()~#+\-=|{}.!\\])", r"\\\1", text)
return re.sub(r'([_*\[\]()~#+\-=|{}.!\\])', r'\\\1', text)
@router.message(CommandStart())
async def start(message: types.Message) -> None:
get_bot = await bot.get_me()
data = await db.fetch(
"SELECT lang FROM users WHERE user_id = $1", message.from_user.id
'SELECT lang FROM users WHERE user_id = $1',
message.from_user.id
)
locale = i18n.get_locale(data.get("lang"))
locale = i18n.get_locale(data.get('lang'))
raw_template = locale.get("start_message")
raw_text = raw_template.format(bot_username=get_bot.username)
@ -30,14 +28,15 @@ async def start(message: types.Message) -> None:
button_text = locale.get("source_code_button")
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=button_text, url="https://github.com/redume/shirino"
)
]
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
[types.InlineKeyboardButton(
text=button_text,
url="https://github.com/redume/shirino")
]
)
])
await message.reply(text, parse_mode="MarkdownV2", reply_markup=keyboard)
await message.reply(
text,
parse_mode="MarkdownV2",
reply_markup=keyboard
)

View file

@ -2,6 +2,5 @@ CREATE TABLE IF NOT EXISTS users (
user_id INTEGER PRIMARY KEY,
chart BOOLEAN DEFAULT 1,
chart_period TEXT DEFAULT 'month',
lang TEXT DEFAULT 'en',
chart_backend TEXT DEFAULT 'matplotlib'
lang TEXT DEFAULT 'en'
);

View file

@ -1,13 +1,12 @@
import json
from datetime import date, datetime
from pathlib import Path
from typing import Any, Dict, List, Optional
from typing import Optional, List, Dict, Any
import aiosqlite
import yaml
config = yaml.safe_load(open("../config.yaml", "r", encoding="utf-8"))
config = yaml.safe_load(open('../config.yaml', 'r', encoding='utf-8'))
def custom_encoder(obj):
"""
@ -20,7 +19,6 @@ def custom_encoder(obj):
return obj.isoformat()
raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable")
class Database:
"""
Asynchronous SQLite database handler using aiosqlite.
@ -67,6 +65,7 @@ class Database:
self.db_path = db_path
self.conn: Optional[aiosqlite.Connection] = None
async def _create_table(self) -> None:
"""
Create table from SQL file using aiosqlite.
@ -75,7 +74,7 @@ class Database:
and executes them as a script.
"""
sql_file = Path(__file__).parent / "schemas" / "data.sql"
sql = sql_file.read_text(encoding="utf-8")
sql = sql_file.read_text(encoding='utf-8')
async with self.conn.execute("BEGIN"):
await self.conn.executescript(sql)
@ -140,13 +139,12 @@ class Database:
async with self.conn.execute(query, args) as cursor:
rows = await cursor.fetchall()
return (
json.loads(
json.dumps([dict(row) for row in rows], default=custom_encoder)
return json.loads(
json.dumps(
[dict(row) for row in rows],
default=custom_encoder
)
if rows
else []
)
) if rows else []
async def insert(self, query: str, *args) -> Dict[str, Any]:
"""

View file

@ -9,15 +9,14 @@ import yaml
from utils.format_number import format_number
config = yaml.safe_load(open("../config.yaml", "r", encoding="utf-8"))
config = yaml.safe_load(open('../config.yaml', 'r', encoding='utf-8'))
class Converter:
def __init__(self):
self.amount: float = 1.0
self.conv_amount: float = 0.0
self.from_currency: str = ""
self.conv_currency: str = ""
self.from_currency: str = ''
self.conv_currency: str = ''
async def convert(self) -> None:
if not await self.kekkai():
@ -28,66 +27,68 @@ class Converter:
async def get_lastdate(self) -> str:
async with aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=3)
) as session:
async with session.get(f"{config['kekkai_instance']}/api/metadata") as res:
) as session:
async with session.get(
f"{config['kekkai_instance']}/api/metadata"
) as res:
if not HTTPStatus(res.status).is_success:
return (datetime.now() - timedelta(1)).strftime("%Y-%m-%d")
return (
datetime.now() - timedelta(1)
).strftime('%Y-%m-%d')
data = await res.json()
return data.get(
"last_date", (datetime.now() - timedelta(1)).strftime("%Y-%m-%d")
)
'last_date',
(datetime.now() - timedelta(1)).strftime('%Y-%m-%d')
)
async def kekkai(self) -> bool:
date = await self.get_lastdate()
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:
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)
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:
) as session:
async with session.get(
"https://duckduckgo.com/js/spice/currency/"
f"{self.amount}/{self.from_currency}/{self.conv_currency}"
) as res:
'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])
data = json.loads(re.findall(r'\(\s*(.*)\s*\);$', data_text)[0])
for key in ["terms", "privacy", "timestamp"]:
for key in ['terms', 'privacy', 'timestamp']:
data.pop(key, None)
if not data.get("to"):
if not data.get('to'):
raise RuntimeError(
"Failed to get the exchange rate from DuckDuckGo"
)
'Failed to get the exchange rate from DuckDuckGo'
)
conv = data.get("to")[0]
conv_amount = conv.get("mid")
conv = data.get('to')[0]
conv_amount = conv.get('mid')
if conv_amount is None:
raise RuntimeError("Error when converting currency via DuckDuckGo")
raise RuntimeError(
'Error when converting currency via DuckDuckGo'
)
self.conv_amount = float(conv_amount)

View file

@ -1,34 +1,26 @@
import time
from http import HTTPStatus
from urllib.parse import urlencode
import aiohttp
import yaml
import aiohttp
config = yaml.safe_load(open("../config.yaml", "r", encoding="utf-8"))
config = yaml.safe_load(open('../config.yaml', 'r', encoding='utf-8'))
async def create_chart(
from_currency: str, conv_currency: str, period: str, backend: str
) -> (str, None):
params = {
"from_currency": from_currency,
"conv_currency": conv_currency,
"period": period,
"backend": backend,
"time_unique": time.time(),
}
# Without time_unqiue Telegram does not want to load the image
# Probably because of some kind of caching, but it's disabled.
base_url = f'{config["kekkai_instance"]}/api/getChart/'
query_string = urlencode(params)
full_url = f"{base_url}?{query_string}"
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=3)) as session:
async with session.get(full_url) as res:
from_currency: str,
conv_currency: str,
period: str) -> (dict, None):
async with aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=3)
) as session:
async with session.get(
f'{config["kekkai_instance"]}/api/getChart/{period}/',
params={
'from_currency': from_currency,
'conv_currency': conv_currency
}) as res:
if not HTTPStatus(res.status).is_success:
return None
return full_url
data = await res.json()
return data.get('detail', None)

View file

@ -40,6 +40,3 @@ week: "Week"
month: "Month"
quarter: "Quarter"
year: "Year"
setting_backend: "Chart backend"
choose_chart_backend: "Choose chart backend for create charts"
selected_chart_backend: "Backend for graphs"

View file

@ -40,6 +40,3 @@ week: "Неделя"
month: "Месяц"
quarter: "Квартал"
year: "Год"
setting_backend: "Бэкенд графиков"
choose_chart_backend: "Выберите бэкэнд для создания графиков"
selected_chart_backend: "Бэкенд для графиков"

View file

@ -1,6 +1,5 @@
from pathlib import Path
import yaml
from pathlib import Path
class I18n:
@ -22,14 +21,4 @@ class I18n:
def get_locale(self, lang: str | None = None) -> dict:
"""Return the whole dictionary for one language (fallback → default_lang)."""
lang = (lang or self.default_lang).lower()[:2]
lang_dict = self.translations.get(
lang, self.translations.get(self.default_lang, {})
)
fallback_dict = self.translations.get(self.default_lang, {})
merged_dict = {
key: lang_dict.get(key, fallback_dict.get(key, key))
for key in set(lang_dict) | set(fallback_dict)
}
return merged_dict
return self.translations.get(lang, self.translations[self.default_lang])

28
main.py
View file

@ -1,23 +1,23 @@
import yaml
from aiogram import Dispatcher
from aiogram.webhook.aiohttp_server import (SimpleRequestHandler,
setup_application)
from aiohttp import web
from aiogram import Dispatcher
from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application
from commands import currency, start, settings
from bot import bot, db
from commands import currency, settings, start
config = yaml.safe_load(open("../config.yaml", "r", encoding="utf-8"))
config = yaml.safe_load(open('../config.yaml', 'r', encoding='utf-8'))
async def on_startup(bot: bot) -> None:
await db.connect()
await db._create_table()
await bot.set_webhook(
f"{config['webhook']['base_url']}{config['webhook']['path']}",
secret_token=config["webhook"]["secret_token"],
allowed_updates=["inline_query", "message", "callback_query"],
)
secret_token=config['webhook']['secret_token'],
allowed_updates=['inline_query', 'message', 'callback_query']
)
async def on_shutdown():
@ -36,14 +36,16 @@ def main() -> None:
app = web.Application()
webhook_requests_handler = SimpleRequestHandler(
dispatcher=dp, bot=bot, secret_token=config["webhook"]["secret_token"]
dispatcher=dp,
bot=bot,
secret_token=config['webhook']['secret_token']
)
webhook_requests_handler.register(app, path=config["webhook"]["path"])
webhook_requests_handler.register(app, path=config['webhook']['path'])
setup_application(app, dp, bot=bot)
web.run_app(app, host="0.0.0.0", port=443)
web.run_app(app, host='0.0.0.0', port=443)
if __name__ == "__main__":
if __name__ == '__main__':
main()

5
mypy.ini Normal file
View file

@ -0,0 +1,5 @@
[mypy]
check_untyped_defs = True
warn_return_any = True
warn_unreachable = True
show_error_codes = True

193
pylintrc Normal file
View file

@ -0,0 +1,193 @@
[MAIN]
analyse-fallback-blocks=no
clear-cache-post-run=no
extension-pkg-allow-list=
extension-pkg-whitelist=
fail-on=
fail-under=10
ignore=CVS
ignore-paths=
ignore-patterns=^\.#
ignored-modules=
jobs=4
limit-inference-results=100
load-plugins=
persistent=yes
py-version=3.10
recursive=no
suggestion-mode=yes
unsafe-load-any-extension=no
[BASIC]
argument-naming-style=snake_case
attr-naming-style=snake_case
bad-names=foo,
bar,
baz,
toto,
tutu,
tata
bad-names-rgxs=
class-attribute-naming-style=any
class-const-naming-style=UPPER_CASE
class-naming-style=PascalCase
const-naming-style=UPPER_CASE
docstring-min-length=-1
function-naming-style=snake_case
good-names=i,
j,
k,
ex,
Run,
_
good-names-rgxs=
include-naming-hint=no
inlinevar-naming-style=any
method-naming-style=snake_case
module-naming-style=snake_case
name-group=
no-docstring-rgx=^_
property-classes=abc.abstractproperty
variable-naming-style=snake_case
[CLASSES]
check-protected-access-in-special-methods=no
defining-attr-methods=__init__,
__new__,
setUp,
__post_init__
exclude-protected=_asdict,
_fields,
_replace,
_source,
_make
valid-classmethod-first-arg=cls
valid-metaclass-classmethod-first-arg=mcs
[DESIGN]
exclude-too-few-public-methods=
ignored-parents=
max-args=5
max-attributes=7
max-bool-expr=5
max-branches=12
max-locals=15
max-parents=7
max-public-methods=20
max-returns=6
max-statements=50
min-public-methods=0
[EXCEPTIONS]
overgeneral-exceptions=builtins.BaseException,builtins.Exception
[FORMAT]
expected-line-ending-format=
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
indent-after-paren=4
indent-string=' '
max-line-length=100
max-module-lines=1000
single-line-class-stmt=no
single-line-if-stmt=no
[IMPORTS]
allow-any-import-level=
allow-reexport-from-package=no
allow-wildcard-with-all=no
deprecated-modules=
ext-import-graph=
import-graph=
int-import-graph=
known-standard-library=
known-third-party=enchant
preferred-modules=
[LOGGING]
logging-format-style=old
logging-modules=logging
[MESSAGES CONTROL]
confidence=HIGH,
CONTROL_FLOW,
INFERENCE,
INFERENCE_FAILURE,
UNDEFINED
disable=raw-checker-failed,
bad-inline-option,
locally-disabled,
file-ignored,
suppressed-message,
useless-suppression,
deprecated-pragma,
use-symbolic-message-instead,
missing-module-docstring,
missing-class-docstring,
missing-function-docstring,
broad-exception-caught
enable=c-extension-no-member
[METHOD_ARGS]
timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request
[MISCELLANEOUS]
notes=FIXME,
XXX,
TODO
notes-rgx=
[REFACTORING]
max-nested-blocks=5
never-returning-functions=sys.exit,argparse.parse_error
[REPORTS]
evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
msg-template=
reports=no
score=yes
[SIMILARITIES]
ignore-comments=yes
ignore-docstrings=yes
ignore-imports=yes
ignore-signatures=yes
min-similarity-lines=4
[SPELLING]
max-spelling-suggestions=4
spelling-dict=
spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
spelling-ignore-words=
spelling-private-dict-file=
spelling-store-unknown-words=no
[STRING]
check-quote-consistency=no
check-str-concat-over-line-jumps=no
[TYPECHECK]
contextmanager-decorators=contextlib.contextmanager
generated-members=
ignore-none=yes
ignore-on-opaque-inference=yes
ignored-checks-for-mixins=no-member,
not-async-context-manager,
not-context-manager,
attribute-defined-outside-init
ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace
missing-member-hint=yes
missing-member-hint-distance=1
missing-member-max-choices=1
mixin-class-rgx=.*[Mm]ixin
signature-mutators=
[VARIABLES]
additional-builtins=
allow-global-unused-variables=yes
allowed-redefined-builtins=
callbacks=cb_,
_cb
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
ignored-argument-names=_.*|^ignored_|^unused_
init-import=no
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io

View file

@ -1,20 +0,0 @@
[tool.isort]
profile = "black"
line_length = 79
known_first_party = ["kekkai", "chart"]
[tool.black]
line-length = 79
target-version = ['py312']
include = '\.pyi?$'
exclude = '''
/(
\.git
| \.mypy_cache
| \.venv
| build
| dist
| Dockerfile
| .*\.md$
)/
'''

View file

@ -1,22 +1,22 @@
from decimal import Decimal
def format_number(number):
number = Decimal(str(number))
integer_part = number // 1
fractional_part = number - integer_part
formatted_integer = "{:,.0f}".format(integer_part).replace(",", " ")
formatted_integer = '{:,.0f}'.format(integer_part).replace(',', ' ')
if fractional_part == 0:
return formatted_integer
fractional_str = f"{fractional_part:.30f}".split(".")[1]
fractional_str = f"{fractional_part:.30f}".split('.')[1]
first_non_zero = next(
(i for i, char in enumerate(fractional_str) if char != "0"), len(fractional_str)
)
result_fractional = fractional_str[: first_non_zero + 3]
result_fractional = result_fractional.rstrip("0")
(i for i, char in enumerate(fractional_str) if char != '0'),
len(fractional_str)
)
result_fractional = fractional_str[:first_non_zero + 3]
result_fractional = result_fractional.rstrip('0')
if not result_fractional:
return formatted_integer

View file

@ -2,10 +2,8 @@ import re
from aiogram import types
def esc_md(text: str) -> str:
return re.sub(r"([_*\[\]()~`>#+\-=|{}.!\\])", r"\\\1", text)
return re.sub(r'([_*\[\]()~`>#+\-=|{}.!\\])', r'\\\1', text)
async def reply(result_id: str, args: list, query: types.InlineQuery) -> None:
if not args:
@ -26,7 +24,7 @@ async def reply(result_id: str, args: list, query: types.InlineQuery) -> None:
title=title,
description=description,
caption=esc_md(title),
parse_mode="MarkdownV2",
parse_mode="MarkdownV2"
)
else:
article = types.InlineQueryResultArticle(