Implemented last article command

This commit is contained in:
2025-09-15 18:48:17 +03:00
parent c14683a35c
commit 529da899b5
10 changed files with 64 additions and 207 deletions
+10 -13
View File
@@ -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
-2
View File
@@ -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())
+3
View File
@@ -0,0 +1,3 @@
from .utils import getArticles
__all__ = ["getArticles"]
+22
View File
@@ -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
-8
View File
@@ -1,8 +0,0 @@
import asyncio
from loguru import logger
async def internal():
logger.info("Internal")
await asyncio.sleep(4)
-11
View File
@@ -1,11 +0,0 @@
from .connect import load, start
from .utils import add, check, removeById, removeByParam
__all__ = [
"load",
"start",
"add",
"check",
"removeById",
"removeByParam",
]
-47
View File
@@ -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
-69
View File
@@ -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
+4 -10
View File
@@ -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:"
+25 -47
View File
@@ -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"<b>{str(i[0])}</b>: </b>{str(i[1])}</b>\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()