news: add /pulse command for repository statistics summary

parent 48947111
......@@ -54,6 +54,32 @@ class NewsInfo:
async def p10(self) -> models.PackagesModel | None:
return await self._get_packages("p10")
async def bugs_by_range(
self, date_from: date, date_to: date
) -> dict[str, int] | None:
urls = await urls_for_range(self.client, date_from, date_to, "bugs")
if not urls:
return None
totals = {
"quickly_resolved": 0, "new": 0, "old": 0,
"resolved": 0, "reopened": 0, "random": 0,
}
for _, url in urls:
html = await self.client.get(url, "koi8-r")
data = await bugs_parser(html, url)
if data and isinstance(data, models.BugsModel):
for key in totals:
items = getattr(data, key, None)
if items:
totals[key] += len(items)
if all(v == 0 for v in totals.values()):
return None
return totals
async def packages_by_range(
self, date_from: date, date_to: date
) -> models.PackagesModel | None:
......
......@@ -41,8 +41,11 @@ async def urls_parser(client):
return models.NewsURL(**result)
async def urls_for_range(client, date_from: date, date_to: date) -> list[tuple[date, str]]:
async def urls_for_range(
client, date_from: date, date_to: date, news_type: str = "packages"
) -> list[tuple[date, str]]:
results = []
pattern = re.compile(rf"Sisyphus-(\d{{8}}) {re.escape(news_type)}")
current = date_from.replace(day=1)
while current <= date_to:
......@@ -61,7 +64,7 @@ async def urls_for_range(client, date_from: date, date_to: date) -> list[tuple[d
if not a or not a.get("href"):
continue
text = a.get_text(strip=True)
match = re.search(r"Sisyphus-(\d{8}) packages", text)
match = pattern.search(text)
if match:
news_date = datetime.strptime(match.group(1), "%Y%m%d").date()
if date_from <= news_date <= date_to:
......
......@@ -76,5 +76,6 @@ async def help_handler(m: Message) -> None:
" /statistics [branch] — статистика репозитория\n"
" /news — меню новостей\n"
" /news_range дата_от дата_до — новости за период\n"
" /pulse [дней] — пульс Сизифа (по умолчанию 7)\n"
)
import asyncio
from telegrinder import Dispatch, Message, CallbackQuery
from telegrinder.rules import Command, Argument, CallbackDataMarkup, Text, IsPrivate
from datetime import datetime
from datetime import datetime, timedelta
from altrepo import altrepo
from services.news import format_packages, format_bugs
from services.utils import _bold, int_validator
from data.keyboards import news_keyboards
dp = Dispatch()
......@@ -164,3 +167,46 @@ async def news_range_handler(
for msg in updated:
await m.ctx_api.send_message(chat_id=chat_id, text=msg)
await m.ctx_api.send_message(chat_id=chat_id, text=info_message)
@dp.message(
Command("pulse", Argument("days", [int_validator], optional=True))
)
async def pulse_handler(m: Message, days: int | None = None) -> None:
days = days or 7
if days < 1 or days > 180:
await m.answer("Допустимый диапазон: 1–180 дней.")
return
d_to = datetime.now().date()
d_from = d_to - timedelta(days=days - 1)
await m.answer(f"Сбор статистики за {days} дн...")
packages_data, bugs_data = await asyncio.gather(
altrepo.parser.news.packages_by_range(d_from, d_to),
altrepo.parser.news.bugs_by_range(d_from, d_to),
)
added = len(packages_data.added) if packages_data and packages_data.added else 0
updated = len(packages_data.updated) if packages_data and packages_data.updated else 0
removed = len(packages_data.removed) if packages_data and packages_data.removed else 0
message = _bold("Пульс Сизифа\n\n")
message += "🛍 Пакеты:\n"
message += f" Добавлено новых: {added}\n"
message += f" Обновлено: {updated}\n"
message += f" Удалено: {removed}\n\n"
if bugs_data:
message += "🪲 Баги:\n"
message += f" Новые: {bugs_data['new']}\n"
message += f" Переоткрытые: {bugs_data['reopened']}\n"
message += f" Быстро закрытые: {bugs_data['quickly_resolved']}\n"
message += f" Закрытые: {bugs_data['resolved']}\n\n"
date_range = f"{d_from.strftime('%d.%m.%Y')} — {d_to.strftime('%d.%m.%Y')}"
message += f"* статистика за {date_range}"
await m.answer(message)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment