Skip to content

Commit 7e58774

Browse files
committed
wip
1 parent 118c49e commit 7e58774

File tree

6 files changed

+104
-84
lines changed

6 files changed

+104
-84
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ dependencies = [
77
"discord-py>=2.6.3",
88
"dotenv>=0.9.9",
99
"fastapi[standard]>=0.117.1",
10+
"hikari>=2.4.1",
1011
"pre-commit>=4.3.0",
1112
"pytest>=8.4.2",
1213
"requests>=2.32.5",

src/bot.py

Lines changed: 43 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import asyncio
22
import os
3+
from hikari import GuildForumChannel, GuildPublicThread, ForumTag, RESTApp
34
from copy import deepcopy
4-
from threading import Lock
55

6-
import discord
76

87
from src.utils.data_types import (
98
ProjectItemEditedAssignees,
@@ -14,60 +13,47 @@
1413
SimpleProjectItemEvent,
1514
)
1615
from src.utils.error import ForumChannelNotFound
17-
from src.utils.utils import add_tag_to_thread, get_post_id, retrieve_discord_id
16+
from src.utils.utils import get_post_id, retrieve_discord_id
1817

18+
async def run(state: dict[str, bool | list[ProjectItemEvent]]):
19+
discord_rest = RESTApp()
20+
await discord_rest.start()
1921

20-
class DiscordClient(discord.Client):
21-
bg_task: asyncio.Task
22-
23-
def __init__(self, *, state, lock, **kwargs):
24-
super().__init__(**kwargs)
25-
self.state: dict[str, bool | list[ProjectItemEvent]] = state
26-
self.lock: Lock = lock
27-
28-
async def on_ready(self):
29-
print(f"Logged on as {self.user}!")
30-
self.bg_task = self.loop.create_task(self.process_updates())
31-
32-
async def process_updates(self):
22+
async with discord_rest.acquire(os.getenv("DISCORD_TOKEN")) as client:
3323
forum_channel_id = int(os.getenv("FORUM_CHANNEL_ID"))
34-
forum_channel: discord.ForumChannel = self.get_channel(forum_channel_id)
35-
if forum_channel is None:
24+
forum_channel = await client.fetch_channel(forum_channel_id)
25+
if forum_channel is None or not isinstance(forum_channel, GuildForumChannel):
3626
raise ForumChannelNotFound(f"Forum channel with ID {forum_channel_id} not found.")
37-
local_queue_copy: list[ProjectItemEvent] = []
3827

39-
while True:
40-
with self.lock:
41-
if self.state["update-received"]:
42-
local_queue_copy = deepcopy(self.state["update-queue"])
43-
self.state["update-queue"].clear()
44-
self.state["update-received"] = False
28+
if state["update-received"]:
29+
local_queue_copy: list[ProjectItemEvent] = deepcopy(state["update-queue"])
30+
state["update-queue"].clear()
31+
state["update-received"] = False
4532

4633
for event in local_queue_copy:
47-
post_id = await get_post_id(event.name, forum_channel)
34+
post_id = await get_post_id(event.name, forum_channel_id, client)
4835
author_discord_id = retrieve_discord_id(event.sender)
4936
if post_id is None:
5037
message = f"Nowy task stworzony {event.name} przez <@{author_discord_id}>"
51-
await forum_channel.create_thread(name=event.name, content=message, auto_archive_duration=10080)
52-
post_id = await get_post_id(event.name, forum_channel)
53-
thread: discord.Thread = forum_channel.get_thread(int(post_id)) or await self.fetch_channel(
54-
int(post_id)
55-
)
56-
if thread is None:
38+
post: GuildPublicThread = await client.create_forum_post(forum_channel, event.name, message, auto_archive_duration=10080)
39+
else:
40+
post = await client.fetch_channel(post_id)
41+
42+
if not isinstance(post, GuildPublicThread):
5743
continue
5844

5945
if isinstance(event, SimpleProjectItemEvent):
6046
match event.event_type.value:
6147
case "archived":
6248
message = f"Task zarchiwizowany przez <@{author_discord_id}>."
63-
await thread.send(message)
64-
await thread.edit(archived=True)
49+
await client.create_message(post.id, message)
50+
await client.edit_channel(post.id, archived=True)
6551
case "restored":
6652
message = f"Task przywrócony przez <@{author_discord_id}>."
67-
await thread.send(message)
68-
await thread.edit(archived=False)
53+
await client.create_message(post.id, message)
54+
await client.edit_channel(post.id, archived=False)
6955
case "deleted":
70-
await thread.delete()
56+
await client.delete_channel(post.id)
7157
elif isinstance(event, ProjectItemEditedAssignees):
7258
assignee_mentions: list[str] = []
7359
if event.new_assignees:
@@ -81,29 +67,34 @@ async def process_updates(self):
8167
message = (
8268
f"Osoby przypisane do taska edytowane, aktualni przypisani: {', '.join(assignee_mentions)}"
8369
)
84-
await thread.send(message)
70+
await client.create_message(post.id, message)
8571
elif isinstance(event, ProjectItemEditedBody):
8672
message = f"Opis taska zaktualizowany przez <@{author_discord_id}>. Nowy opis: {event.new_body}"
87-
await thread.send(message)
73+
await client.create_message(post.id, message)
8874
elif isinstance(event, ProjectItemEditedTitle):
89-
await thread.edit(name=event.new_title)
75+
await client.edit_channel(post.id, name=event.new_title)
9076
elif isinstance(event, ProjectItemEditedSingleSelect):
91-
thread_tags = list(thread.applied_tags)
92-
for tag in thread_tags:
93-
if tag.name.startswith(f"{event.value_type.value}: "):
94-
await thread.remove_tags(tag)
77+
current_tag_ids = list(post.applied_tag_ids)
78+
available_tags = list(forum_channel.available_tags)
9579

96-
await add_tag_to_thread(
97-
thread, forum_channel, f"{event.value_type.value}: {event.new_value}", event.value_type.value
80+
for tag in available_tags:
81+
if tag.id in current_tag_ids and tag.name.startswith(f"{event.value_type.value}: "):
82+
current_tag_ids.remove(tag.id)
83+
84+
new_tag_name = f"{event.value_type.value}: {event.new_value}"
85+
new_tag = next(
86+
(tag for tag in available_tags if tag.name == new_tag_name),
87+
None
9888
)
9989

100-
local_queue_copy.clear()
90+
if new_tag is None:
91+
new_tag = ForumTag(name=new_tag_name)
92+
await client.edit_channel(forum_channel.id, available_tags=available_tags.append(new_tag))
10193

102-
await asyncio.sleep(1)
94+
current_tag_ids.append(new_tag.id)
10395

96+
await client.edit_channel(post.id, applied_tag_ids=current_tag_ids)
10497

105-
def run(state, lock):
106-
intents = discord.Intents.default()
98+
local_queue_copy.clear()
10799

108-
client = DiscordClient(intents=intents, state=state, lock=lock)
109-
client.run(os.getenv("DISCORD_BOT_TOKEN"))
100+
await asyncio.sleep(1)

src/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
def main():
88
dotenv.load_dotenv()
99
host, port = os.getenv("IP_ADDRESS", "0.0.0.0:8000").split(":")
10-
uvicorn.run("src.server:src", host=host, port=int(port), reload=True)
10+
uvicorn.run("src.server:app", host=host, port=int(port), reload=True)
1111

1212

1313
if __name__ == "__main__":

src/server.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import asyncio
12
import os
2-
import threading
3+
from contextlib import asynccontextmanager
34
from typing import Any
45

56
from fastapi import FastAPI, Request
@@ -18,12 +19,23 @@
1819
)
1920
from src.utils.utils import fetch_assignees, fetch_item_name, fetch_single_select_value, get_item_name
2021

21-
app = FastAPI()
2222

2323
state: dict[str, bool | list[ProjectItemEvent]] = {"update-received": False, "update-queue": []}
24-
lock: threading.Lock = threading.Lock()
25-
threading.Thread(target=run, args=(state, lock), daemon=True).start()
2624

25+
@asynccontextmanager
26+
async def lifespan(app: FastAPI):
27+
# startup
28+
task = asyncio.create_task(run(state))
29+
yield
30+
# shutdown
31+
task.cancel()
32+
try:
33+
await task
34+
except asyncio.CancelledError:
35+
pass
36+
37+
38+
app = FastAPI(lifespan=lifespan)
2739

2840
@app.post("/webhook_endpoint")
2941
async def webhook_endpoint(request: Request):
@@ -49,10 +61,9 @@ async def webhook_endpoint(request: Request):
4961
body["action"], item_name, body.get("sender", {}).get("login", "Unknown")
5062
)
5163

52-
with lock:
53-
if project_item_event is not None:
54-
state["update-queue"].append(project_item_event)
55-
state["update-received"] = True
64+
if project_item_event is not None:
65+
state["update-queue"].append(project_item_event)
66+
state["update-received"] = True
5667

5768
return
5869

src/utils/utils.py

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import shelve
44

55
import requests
6-
from discord import ForumChannel, ForumTag, Thread
6+
from hikari.impl import RESTClientImpl
77

88

99
async def get_item_name(item_node_id: str) -> str | None:
@@ -139,18 +139,18 @@ def fetch_single_select_value(item_node_id: str | None, field_name: str | None)
139139
return name
140140

141141

142-
async def get_post_id(name: str, forum_channel: ForumChannel) -> int | None:
142+
async def get_post_id(name: str, forum_channel_id: int, rest_client: RESTClientImpl) -> int | None:
143143
with shelve.open("post_id.db") as db:
144144
try:
145145
post_id: str = db[name]
146146
return int(post_id)
147147
except KeyError:
148148
pass
149-
for thread in forum_channel.threads:
149+
for thread in await rest_client.fetch_active_threads(forum_channel_id):
150150
if thread.name == name:
151151
db[name] = thread.id
152152
return thread.id
153-
async for thread in forum_channel.archived_threads():
153+
for thread in await rest_client.fetch_public_archived_threads(forum_channel_id):
154154
if thread.name == name:
155155
db[name] = thread.id
156156
return thread.id
@@ -165,22 +165,3 @@ def retrieve_discord_id(username: str) -> str | None:
165165
mapping: dict[str, str] = json.loads("".join(file.readlines()))
166166

167167
return mapping.get(username, None)
168-
169-
170-
def get_tags_for_single_select_type(section: str, tags: list[ForumTag]) -> list[ForumTag]:
171-
tags_for_single_select_type: list[ForumTag] = []
172-
for tag in tags:
173-
if tag.name.startswith(section + ": "):
174-
tags_for_single_select_type.append(tag)
175-
176-
return tags_for_single_select_type
177-
178-
179-
async def add_tag_to_thread(thread: Thread, forum_channel: ForumChannel, tag_name: str, single_select_type: str):
180-
tags = get_tags_for_single_select_type(single_select_type, list(forum_channel.available_tags))
181-
for tag in tags:
182-
if tag.name == tag_name:
183-
await thread.add_tags(tag)
184-
return
185-
await forum_channel.create_tag(name=tag_name)
186-
await add_tag_to_thread(thread, forum_channel, tag_name, single_select_type)

uv.lock

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)