diff --git a/README.md b/README.md index c2d02e5..536670c 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,12 @@ -Project template for simple telegram bots +### Telegram bot that uses [habr article api](https://git.frik.su/n0one/habr-article-API) for sending articles to user -Features: -- Base commands with FSM (no redis) -- JSON data storage -- Async worker function -- Cute project structure +Commands: +- /start +- /help +- /articles +- url -.env example: -```.env -botToken="token:from-botfather" -whitelist=["userid"] -dataPath="data/" -jsonFilename="data.json" -``` +To do: +- /rates +- /rate +- /remove-rate diff --git a/src/app/createApp.py b/src/app/createApp.py index 77ffca4..49f43b1 100644 --- a/src/app/createApp.py +++ b/src/app/createApp.py @@ -1,9 +1,7 @@ import asyncio -from app.jsonData import start as start_json from app.telegram import start_bot def createApp(): - start_json() asyncio.run(start_bot()) diff --git a/src/app/httpxHandler/__init__.py b/src/app/httpxHandler/__init__.py new file mode 100644 index 0000000..ebc1d01 --- /dev/null +++ b/src/app/httpxHandler/__init__.py @@ -0,0 +1,3 @@ +from .utils import getArticles + +__all__ = ["getArticles"] diff --git a/src/app/httpxHandler/utils.py b/src/app/httpxHandler/utils.py new file mode 100644 index 0000000..7c0e570 --- /dev/null +++ b/src/app/httpxHandler/utils.py @@ -0,0 +1,22 @@ +import base64 +import json + +import httpx + +habrApiUrl="https://habr.frik.su/api/".removesuffix("/") + + +async def getArticles(amount: int) -> list[tuple[str, str]]: + result = [] + async with httpx.AsyncClient() as client: + payload = { + "amount": amount + } + response = await client.post(habrApiUrl + "/articles/get/md", json=payload) + + responseJson = json.loads(response.content) + + for key in responseJson: + decoded = base64.b64decode(responseJson[key].encode('ascii')).decode('utf-8') + result.append((key, decoded)) + return result diff --git a/src/app/internal/internal.py b/src/app/internal/internal.py deleted file mode 100644 index fc2839c..0000000 --- a/src/app/internal/internal.py +++ /dev/null @@ -1,8 +0,0 @@ -import asyncio - -from loguru import logger - - -async def internal(): - logger.info("Internal") - await asyncio.sleep(4) diff --git a/src/app/jsonData/__init__.py b/src/app/jsonData/__init__.py deleted file mode 100644 index cda2c47..0000000 --- a/src/app/jsonData/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -from .connect import load, start -from .utils import add, check, removeById, removeByParam - -__all__ = [ - "load", - "start", - "add", - "check", - "removeById", - "removeByParam", -] diff --git a/src/app/jsonData/connect.py b/src/app/jsonData/connect.py deleted file mode 100644 index 0e4b8f5..0000000 --- a/src/app/jsonData/connect.py +++ /dev/null @@ -1,47 +0,0 @@ -import json -import os -import shutil -import sys -from datetime import datetime - -from loguru import logger - -from app.settings import settings - -dataPath = settings.dataPath.removesuffix("/") -jsonRawFilename = settings.jsonFilename.removesuffix(".json") -backupPath = dataPath + "/backup" -filePath = dataPath + "/" + jsonRawFilename + ".json" - - -def start(): - # Inits JSON file - try: - if not(os.path.exists(dataPath)): - os.mkdir(dataPath) - if not(os.path.exists(backupPath)): - os.mkdir(backupPath) - - if os.path.exists(filePath): - timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") - backupFilePath = backupPath + "/" + jsonRawFilename + f"_backup_{timestamp}.json" - shutil.copy(filePath, backupFilePath) - logger.info(f"JSON backup was created: {backupFilePath}") - - with open(filePath, "w") as f: - json.dump({}, f, ensure_ascii = False, indent=4) - logger.success(f"Successfully initialized JSON with path: {filePath}") - except Exception as e: - logger.error(f"Failed to initialize JSON: {e}") - sys.exit(1) - - -def load(): - # Returnes the contents of the JSON file as a dictionary - data = {} - try: - with open(filePath, "r") as f: - data = json.load(f) - except json.decoder.JSONDecodeError: - logger.warning("JSON file is empty!") - return data diff --git a/src/app/jsonData/utils.py b/src/app/jsonData/utils.py deleted file mode 100644 index 754115d..0000000 --- a/src/app/jsonData/utils.py +++ /dev/null @@ -1,69 +0,0 @@ -import json - -from loguru import logger - -from app.jsonData.connect import load -from app.settings import settings - - -def getParamId(param): - # Returnes -1 if param not found and id if found - currentData = load() - - id = -1 - for (i, j) in [(i[0], i[1]) for i in currentData]: - if j == param: - id = i - break - return id - - -def check(param): - # Returnes 1 if param exists and 0 if not - currentData = load() - - if param in [i[1] for i in currentData]: - return 1 - else: - return 0 - - -def removeById(id: int): - # Returnes 0 if deleted successfully and -1 if not - currentData = load() - - if id in currentData: - del currentData[id] - with open(settings.jsonFilename, 'w', encoding = 'utf-8') as f: - json.dump(currentData, f, ensure_ascii = False, indent = 4) - logger.info(f'Id {id} was deleted successfully!') - return 0 - else: - logger.info(f'Id {id} was not found in the data file when trying to delete it.') - return -1 - -def removeByParam(param): - # Returnes 0 if deleted successfully and -1 if not - id = getParamId(param) - - if id != -1: - return removeById(id) - else: - logger.info(f'Param {param} was not found in the data file when trying to delete it.') - return -1 - - -def add(param): - # Saves or updates data in JSON - currentData = load() - if currentData == {}: - id = 0 - else: - id = currentData[-1][0] + 1 - newData = {id: param} - - with open(settings.jsonFilename, 'w', encoding = 'utf-8') as f: - currentData.update(newData) - json.dump(currentData, f, ensure_ascii = False, indent = 4) - logger.info(f"Param {param} was added with id {id}") - return id diff --git a/src/app/telegram/strings.py b/src/app/telegram/strings.py index a98f8e8..9a2b65b 100644 --- a/src/app/telegram/strings.py +++ b/src/app/telegram/strings.py @@ -9,24 +9,18 @@ unexpectedError = "Unexpected error" # Commands -startCommand = "Hi" +startCommand = "Hi. I'm open source bot for reading habr articles from telegram. Here is your chatid btw: " -helpCommand = "This is help" +helpCommand = "Commands are obvious no help needed, hehe" # Route status -askParam = "Enter param:" - -successAdd = "Added successfully" - -successRemove = "Removed successfully" - -alreadyExists = "Key already exists" +askParam = "Enter url:" # Data status noData = "No data" -foundData = "Founde data:" +foundData = "Found data:" diff --git a/src/app/telegram/telegram.py b/src/app/telegram/telegram.py index 0df287b..fd19182 100644 --- a/src/app/telegram/telegram.py +++ b/src/app/telegram/telegram.py @@ -1,5 +1,3 @@ -import asyncio - from aiogram import Bot, Dispatcher, F, Router from aiogram.client.default import DefaultBotProperties from aiogram.enums import ParseMode @@ -10,8 +8,7 @@ from aiogram.fsm.storage.memory import MemoryStorage from aiogram.types import BotCommand, BotCommandScopeDefault, Message from loguru import logger -from app import jsonData -from app.internal.internal import internal +from app import httpxHandler from app.settings import settings from app.telegram import strings @@ -21,14 +18,14 @@ async def setCommands(): BotCommand(command='start', description='Start'), BotCommand(command='help', description='Help'), BotCommand(command='info', description='Info'), - BotCommand(command='add', description='Add'), + BotCommand(command='last', description='last'), BotCommand(command='remove', description='Remove') ] await bot.set_my_commands(commands, BotCommandScopeDefault()) -class addForm(StatesGroup): - param = State() +class ArticleForm(StatesGroup): + url = State() class removeForm(StatesGroup): param = State() @@ -46,7 +43,7 @@ removeRouter = Router() @dp.message(Command("start")) async def commandStart(message: Message) -> None: - await message.answer(strings.startCommand) + await message.answer(strings.startCommand + str(message.chat.id)) @dp.message(Command("help"), F.chat.id.in_(settings.whitelist)) async def commandHelp(message: Message) -> None: @@ -54,52 +51,33 @@ async def commandHelp(message: Message) -> None: @dp.message(Command("info"), F.chat.id.in_(settings.whitelist)) async def commandInfo(message: Message) -> None: - data = jsonData.load() - msgText = '' - if data == {}: - msgText = strings.noData - else: - msgText = strings.foundData - for i in data: - msgText += (f"{str(i[0])}: {str(i[1])}\n") + msgText = "" await message.answer(msgText) -@strategyRouter.message(Command("add"), F.chat.id.in_(settings.whitelist)) -async def commandAdd(message: Message, state: FSMContext): - await message.answer(strings.askParam) - await state.set_state(addForm.param) +@dp.message(Command("last"), F.chat.id.in_(settings.whitelist)) +async def commandLast(message: Message): + articles = await httpxHandler.getArticles(1) + if articles == []: + await message.answer(strings.unexpectedError) + else: + for (url, content) in articles: + print(url, content) + await message.answer("Url: " + url + "\n" + content) -@strategyRouter.message(F.text, addForm.param) + +@strategyRouter.message(Command("get"), F.chat.id.in_(settings.whitelist)) +async def commandGet(message: Message, state: FSMContext): + await message.answer(strings.askParam) + await state.set_state(ArticleForm.url) + +@strategyRouter.message(F.text, ArticleForm.url) async def captureStartPair(message: Message, state: FSMContext): await state.update_data(pair=message.text) data = await state.get_data() - param = data.get("param") - - t = jsonData.add(param) - msgText = strings.successAdd + str(t) - await asyncio.to_thread(internal) - - await message.answer(msgText) - await state.clear() - - -@removeRouter.message(Command("remove"), F.chat.id.in_(settings.whitelist)) -async def commandRemove(message: Message, state: FSMContext): - await message.answer(strings.askParam) - await state.set_state(removeForm.param) - -@removeRouter.message(F.text, removeForm.param) -async def captureRemoveParam(message: Message, state: FSMContext): - await state.update_data(pair=message.text) - data = await state.get_data() - param = data.get("param") - - t = jsonData.removeByParam(param) - if t == 0: - msgText = strings.successRemove - else: - msgText = strings.noData + url = data.get("url") + msgText = "" + await httpxHandler.getArticles(1) await message.answer(msgText) await state.clear()