Skip to content

Commit 35bb984

Browse files
authored
Merge pull request #12 from ae-utbm/news
Automaticaly post news
2 parents 6850dcb + 9b6a8a1 commit 35bb984

13 files changed

Lines changed: 178 additions & 24 deletions

File tree

.pre-commit-config.yaml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v6.0.0
4+
hooks:
5+
- id: end-of-file-fixer
6+
- id: mixed-line-ending
7+
args: [ "--fix=lf" ]
28
- repo: https://github.com/astral-sh/ruff-pre-commit
39
# Ruff version.
4-
rev: v0.11.4
10+
rev: v0.14.0
511
hooks:
6-
- id: ruff # just check the code, and print the errors
7-
- id: ruff # actually fix the fixable errors, but print nothing
8-
args: ["--fix", "--silent"]
12+
- id: ruff-check # just check the code, and print the errors
13+
- id: ruff-check # actually fix the fixable errors, but print nothing
14+
args: [ "--fix", "--silent" ]
915
# Run the formatter.
1016
- id: ruff-format

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ WORKDIR /app
1414
RUN --mount=type=cache,target=/root/.cache/uv \
1515
uv sync --locked --compile-bytecode
1616

17-
ENTRYPOINT ["uv", "run", "-m", "src.main"]
17+
ENTRYPOINT ["uv", "run", "-m", "src.main"]

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,4 @@ Pour lancer le bot :
3636

3737
```shell
3838
uv run -m src.main
39-
```
39+
```

bot.toml.example

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
[bot]
2-
token = "<your discord bot token here>"
3-
log_level = "INFO"
4-
5-
[guild]
6-
id = <the id of the server here>
7-
8-
[sith_api]
9-
api_key = "<your sith api token here>"
10-
url = "https://ae.utbm.fr/"
1+
[bot]
2+
token = "<your discord bot token here>"
3+
log_level = "INFO"
4+
5+
[guild]
6+
id = <the id of the server here>
7+
news_channel_id = <the id of the news channel here>
8+
news_role_id = <the id of the news channel here>
9+
10+
[sith_api]
11+
api_key = "<your sith api token here>"
12+
url = "https://ae.utbm.fr/"

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ dependencies = [
99
"discord-py[speed]>=2.6.4",
1010
"pydantic-settings>=2.11.0",
1111
"pydantic>=2.12.0",
12+
"pytz>=2025.2",
1213
]
1314

1415
[dependency-groups]

src/client.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import asyncio
22
import logging
33
import types
4-
from datetime import date
4+
from datetime import date, datetime
55

66
from aiohttp import (
77
ClientSession,
@@ -44,11 +44,37 @@ class SimpleClubSchema(BaseModel):
4444
name: str
4545

4646

47+
class ClubProfileSchema(SimpleClubSchema):
48+
logo: str | None = None
49+
url: str
50+
51+
4752
class ClubSearchResultSchema(BaseModel):
4853
count: int
4954
results: list[SimpleClubSchema]
5055

5156

57+
class NewsSchema(BaseModel):
58+
id: int
59+
title: str
60+
summary: str
61+
is_published: bool
62+
club: ClubProfileSchema
63+
url: str
64+
65+
66+
class NewsDateSchema(BaseModel):
67+
id: int
68+
start_date: datetime
69+
end_date: datetime
70+
news: NewsSchema
71+
72+
73+
class NewsDateResultSchema(BaseModel):
74+
count: int
75+
results: list[NewsDateSchema]
76+
77+
5278
class SithClient(ClientSession):
5379
def __init__(self):
5480
self.logger = logging.getLogger("sith")
@@ -83,6 +109,21 @@ async def search_clubs(self, search: str) -> list[SimpleClubSchema] | None:
83109
except ValidationError as e:
84110
self.logger.error(str(e))
85111

112+
async def search_news(
113+
self, after: datetime | None = None, before: datetime | None = None
114+
) -> list[NewsDateSchema] | None:
115+
params = {"is_published": "true"}
116+
if after:
117+
params["after"] = after.isoformat()
118+
if before:
119+
params["before"] = before.isoformat()
120+
async with self.get("/api/news/date", params=params) as res:
121+
content = await res.read()
122+
try:
123+
return NewsDateResultSchema.model_validate_json(content).results
124+
except ValidationError as e:
125+
self.logger.error(str(e))
126+
86127

87128
async def request_logging_start(
88129
_session: SithClient,

src/commands/club.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from src.settings import Settings
1313

1414
if TYPE_CHECKING:
15-
from src.client import SithClient
1615
from src.main import AeBot
1716

1817

@@ -27,8 +26,8 @@ async def transform(
2726

2827

2928
class ClubCog(commands.GroupCog, group_name="club"):
30-
def __init__(self, client: SithClient, bot: AeBot):
31-
self.club_service = ClubService(client, bot)
29+
def __init__(self, bot: AeBot):
30+
self.club_service = ClubService(bot)
3231
self.settings = Settings()
3332

3433
async def autocomplete_club(

src/commands/news.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from __future__ import annotations
2+
3+
import datetime
4+
from typing import TYPE_CHECKING
5+
6+
import pytz
7+
from discord.ext import commands, tasks
8+
9+
from src.services.news import NewsService
10+
11+
if TYPE_CHECKING:
12+
from discord import Role, TextChannel
13+
14+
from src.main import AeBot
15+
16+
17+
class NewsCog(commands.Cog):
18+
news_channel: TextChannel
19+
news_role: Role
20+
21+
def __init__(self, bot: AeBot):
22+
self.bot = bot
23+
self.news_service = NewsService(bot)
24+
25+
@commands.Cog.listener(name="on_ready")
26+
async def on_ready(self):
27+
news_channel_id = self.bot.settings.guild.news_channel_id
28+
news_role_id = self.bot.settings.guild.news_role_id
29+
if not news_channel_id:
30+
# If no news channel id is given in the config,
31+
# the feature of automatic news post is disabled.
32+
return
33+
self.news_channel = self.bot.get_channel(news_channel_id)
34+
self.news_role = self.bot.watched_guild.get_role(news_role_id)
35+
await self.bot.wait_until_ready()
36+
self.post_news.start()
37+
38+
@tasks.loop(
39+
time=datetime.time(hour=9, minute=30, tzinfo=pytz.timezone("Europe/Paris"))
40+
)
41+
async def post_news(self):
42+
news_dates = await self.news_service.get_upcoming_news()
43+
if not news_dates:
44+
return
45+
embeds = [self.news_service.embed(n.news) for n in news_dates]
46+
content = "## Événements dans les prochains jours"
47+
if self.news_role:
48+
content += f"\n{self.news_role.mention}"
49+
await self.news_channel.send(content, embeds=embeds)

src/main.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from src.commands.admin import AdminCog
1515
from src.commands.club import ClubCog
1616
from src.commands.misc import MiscCog
17+
from src.commands.news import NewsCog
1718
from src.settings import BASE_DIR, Settings
1819

1920
if TYPE_CHECKING:
@@ -33,7 +34,8 @@ def __init__(self, client: SithClient):
3334
)
3435

3536
async def setup_hook(self):
36-
await self.add_cog(ClubCog(self.client, self))
37+
await self.add_cog(ClubCog(self))
38+
await self.add_cog(NewsCog(self))
3739
await self.add_cog(AdminCog(self))
3840
await self.add_cog(MiscCog())
3941

src/services/club.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
if TYPE_CHECKING:
1313
from discord import Guild, Member
1414

15-
from src.client import ClubSchema, SimpleClubSchema, SithClient
15+
from src.client import ClubSchema, SimpleClubSchema
1616
from src.main import AeBot
1717

1818

@@ -51,9 +51,9 @@ def save(self):
5151
class ClubService:
5252
"""Manage features directly related to clubs."""
5353

54-
def __init__(self, client: SithClient, bot: AeBot):
54+
def __init__(self, bot: AeBot):
5555
self._config = Settings()
56-
self._client = client
56+
self._client = bot.client
5757
self._club_cache = {}
5858
self._bot = bot
5959

0 commit comments

Comments
 (0)