koi-net-ask-topic-groups-node 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- koi_net_ask_topic_groups_node/__init__.py +0 -0
- koi_net_ask_topic_groups_node/__main__.py +3 -0
- koi_net_ask_topic_groups_node/config.py +37 -0
- koi_net_ask_topic_groups_node/core.py +35 -0
- koi_net_ask_topic_groups_node/knowledge_handlers/ask_thread_handler.py +65 -0
- koi_net_ask_topic_groups_node/knowledge_handlers/slack_usergroup_handler.py +62 -0
- koi_net_ask_topic_groups_node/models.py +18 -0
- koi_net_ask_topic_groups_node/rid_types.py +14 -0
- koi_net_ask_topic_groups_node/slack_command_handler.py +88 -0
- koi_net_ask_topic_groups_node/slack_config_manager.py +27 -0
- koi_net_ask_topic_groups_node/slack_event_handler.py +84 -0
- koi_net_ask_topic_groups_node/socket_mode.py +22 -0
- koi_net_ask_topic_groups_node/user_group_sensor.py +49 -0
- koi_net_ask_topic_groups_node-0.1.0.dist-info/METADATA +15 -0
- koi_net_ask_topic_groups_node-0.1.0.dist-info/RECORD +17 -0
- koi_net_ask_topic_groups_node-0.1.0.dist-info/WHEEL +5 -0
- koi_net_ask_topic_groups_node-0.1.0.dist-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
from rid_lib.types import KoiNetNode
|
|
3
|
+
from koi_net.config import (
|
|
4
|
+
FullNodeConfig,
|
|
5
|
+
KoiNetConfig,
|
|
6
|
+
ServerConfig,
|
|
7
|
+
FullNodeProfile,
|
|
8
|
+
NodeProvides,
|
|
9
|
+
EnvConfig
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from .rid_types import AskCoreThread, AskTopicGroup, SlackUserGroup
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SlackEnvConfig(EnvConfig):
|
|
16
|
+
ask_tg_slack_bot_token: str
|
|
17
|
+
ask_tg_slack_signing_secret: str
|
|
18
|
+
ask_tg_slack_app_token: str
|
|
19
|
+
ask_tg_slack_user_token: str
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SlackConfig(BaseModel):
|
|
23
|
+
team_id: str | None = None
|
|
24
|
+
|
|
25
|
+
class AskTopicGroupsConfig(FullNodeConfig):
|
|
26
|
+
env: SlackEnvConfig = Field(default_factory=SlackEnvConfig)
|
|
27
|
+
koi_net: KoiNetConfig = KoiNetConfig(
|
|
28
|
+
node_name="ask-topic-groups", # human readable name for your node
|
|
29
|
+
node_profile=FullNodeProfile(
|
|
30
|
+
provides=NodeProvides(
|
|
31
|
+
event=[AskTopicGroup, SlackUserGroup],
|
|
32
|
+
state=[AskTopicGroup, SlackUserGroup]
|
|
33
|
+
)
|
|
34
|
+
),
|
|
35
|
+
rid_types_of_interest=[KoiNetNode, AskCoreThread] # RID types this node should subscribe to
|
|
36
|
+
)
|
|
37
|
+
slack: SlackConfig = SlackConfig()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from koi_net.core import FullNode
|
|
2
|
+
from slack_bolt import App as SlackApp
|
|
3
|
+
from slack_sdk import WebClient
|
|
4
|
+
|
|
5
|
+
from .slack_event_handler import SlackEventHandler
|
|
6
|
+
|
|
7
|
+
from .knowledge_handlers.slack_usergroup_handler import SlackUserGroupHandler
|
|
8
|
+
from .knowledge_handlers.ask_thread_handler import AskThreadHandler
|
|
9
|
+
from .user_group_sensor import UserGroupSensor
|
|
10
|
+
from .slack_config_manager import SlackConfigManager
|
|
11
|
+
from .slack_command_handler import SlackCommandHandler
|
|
12
|
+
from .socket_mode import SlackSocketMode
|
|
13
|
+
from .config import AskTopicGroupsConfig
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AskTopicGroupsNode(FullNode):
|
|
17
|
+
config_schema = AskTopicGroupsConfig
|
|
18
|
+
|
|
19
|
+
slack_app: SlackApp = lambda config: SlackApp(
|
|
20
|
+
token=config.env.ask_tg_slack_bot_token,
|
|
21
|
+
signing_secret=config.env.ask_tg_slack_signing_secret)
|
|
22
|
+
|
|
23
|
+
slack_admin_client: WebClient = lambda config: WebClient(
|
|
24
|
+
token=config.env.ask_tg_slack_user_token)
|
|
25
|
+
|
|
26
|
+
socket_mode = SlackSocketMode
|
|
27
|
+
slack_command_handler = SlackCommandHandler
|
|
28
|
+
slack_event_handler = SlackEventHandler
|
|
29
|
+
slack_config_manager = SlackConfigManager
|
|
30
|
+
|
|
31
|
+
user_group_sensor = UserGroupSensor
|
|
32
|
+
|
|
33
|
+
# knowledge handlers
|
|
34
|
+
ask_thread_handler = AskThreadHandler
|
|
35
|
+
slack_usergroup_handler = SlackUserGroupHandler
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from koi_net.components import KobjQueue
|
|
4
|
+
from koi_net.components.interfaces import KnowledgeHandler, HandlerType
|
|
5
|
+
from koi_net.protocol.knowledge_object import KnowledgeObject
|
|
6
|
+
from rid_lib.ext import Bundle
|
|
7
|
+
from rid_lib.types import SlackMessage
|
|
8
|
+
from slack_bolt import App
|
|
9
|
+
|
|
10
|
+
from ..models import ThreadLinkModel
|
|
11
|
+
from ..rid_types import AskCoreThread, ThreadLink
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class AskThreadHandler(KnowledgeHandler):
|
|
16
|
+
slack_app: App
|
|
17
|
+
kobj_queue: KobjQueue
|
|
18
|
+
|
|
19
|
+
handler_type = HandlerType.Network
|
|
20
|
+
rid_types = (AskCoreThread,)
|
|
21
|
+
|
|
22
|
+
def ensure_bot_in_channel(self, channel_id: str):
|
|
23
|
+
try:
|
|
24
|
+
result = self.slack_app.client.conversations_info(channel=channel_id)
|
|
25
|
+
channel = result["channel"]
|
|
26
|
+
|
|
27
|
+
if not channel.get("is_member", False):
|
|
28
|
+
self.slack_app.client.conversations_join(channel=channel_id)
|
|
29
|
+
self.log.info(f"Joined channel #{channel_id}")
|
|
30
|
+
|
|
31
|
+
except Exception as e:
|
|
32
|
+
self.log.error(f"Unhandled exception joining channel: {e}")
|
|
33
|
+
|
|
34
|
+
def handle(self, kobj: KnowledgeObject):
|
|
35
|
+
thread: AskCoreThread = kobj.rid
|
|
36
|
+
|
|
37
|
+
self.ensure_bot_in_channel(channel_id=thread.channel_id)
|
|
38
|
+
|
|
39
|
+
result = self.slack_app.client.chat_postMessage(
|
|
40
|
+
channel=thread.channel_id,
|
|
41
|
+
thread_ts=thread.ts,
|
|
42
|
+
blocks=[
|
|
43
|
+
{
|
|
44
|
+
"type": "section",
|
|
45
|
+
"text": {
|
|
46
|
+
"type": "mrkdwn",
|
|
47
|
+
"text": "React to this message to tag a topic group!"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
)
|
|
52
|
+
slack_msg = SlackMessage(
|
|
53
|
+
team_id=thread.team_id,
|
|
54
|
+
channel_id=thread.channel_id,
|
|
55
|
+
ts=result["ts"]
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
thread_link = ThreadLinkModel(thread=thread, message=slack_msg)
|
|
59
|
+
self.kobj_queue.push(bundle=Bundle.generate(
|
|
60
|
+
rid=ThreadLink(slack_msg.team_id, slack_msg.channel_id, slack_msg.ts),
|
|
61
|
+
contents=thread_link.model_dump()
|
|
62
|
+
))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from koi_net.components import Cache, KobjQueue
|
|
4
|
+
from koi_net.components.interfaces import KnowledgeHandler, HandlerType
|
|
5
|
+
from koi_net.protocol.knowledge_object import KnowledgeObject
|
|
6
|
+
from rid_lib.ext import Bundle
|
|
7
|
+
from rid_lib.types import SlackUser, SlackUserGroup
|
|
8
|
+
from slack_bolt import App
|
|
9
|
+
|
|
10
|
+
from ..models import TopicGroupModel
|
|
11
|
+
from ..rid_types import AskTopicGroup
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class SlackUserGroupHandler(KnowledgeHandler):
|
|
16
|
+
slack_app: App
|
|
17
|
+
cache: Cache
|
|
18
|
+
kobj_queue: KobjQueue
|
|
19
|
+
|
|
20
|
+
handler_type = HandlerType.Network
|
|
21
|
+
rid_types = (SlackUserGroup,)
|
|
22
|
+
|
|
23
|
+
def handle(self, kobj: KnowledgeObject):
|
|
24
|
+
ug_rid: SlackUserGroup = kobj.rid
|
|
25
|
+
ug_handle: str = kobj.contents["handle"]
|
|
26
|
+
ug_name: str = kobj.contents["name"]
|
|
27
|
+
ug_users: list[str] = kobj.contents["users"]
|
|
28
|
+
ug_description: str = kobj.contents["description"]
|
|
29
|
+
|
|
30
|
+
if not ug_handle.startswith("tg-"):
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
tg_rid = AskTopicGroup(ug_rid.team_id, ug_rid.subteam_id)
|
|
34
|
+
|
|
35
|
+
emoji_pattern = "emoji:"
|
|
36
|
+
emoji_index = ug_description.find(emoji_pattern)
|
|
37
|
+
|
|
38
|
+
emoji_str = None
|
|
39
|
+
if emoji_index >= 0:
|
|
40
|
+
emoji_str = ug_description[emoji_index + len(emoji_pattern):].split()[0]
|
|
41
|
+
|
|
42
|
+
user_rids = [SlackUser(ug_rid.team_id, user_id) for user_id in ug_users]
|
|
43
|
+
|
|
44
|
+
bundle = self.cache.read(tg_rid)
|
|
45
|
+
if bundle:
|
|
46
|
+
topic_group = bundle.validate_contents(TopicGroupModel)
|
|
47
|
+
else:
|
|
48
|
+
topic_group = TopicGroupModel.model_construct()
|
|
49
|
+
|
|
50
|
+
topic_group.usergroup = ug_rid
|
|
51
|
+
topic_group.handle = ug_handle
|
|
52
|
+
topic_group.name = ug_name
|
|
53
|
+
topic_group.emoji = emoji_str
|
|
54
|
+
topic_group.users = user_rids
|
|
55
|
+
|
|
56
|
+
tg_bundle = Bundle.generate(
|
|
57
|
+
rid=tg_rid,
|
|
58
|
+
contents=topic_group.model_dump()
|
|
59
|
+
)
|
|
60
|
+
self.kobj_queue.push(bundle=tg_bundle)
|
|
61
|
+
|
|
62
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from rid_lib.types import SlackMessage, SlackUser
|
|
3
|
+
|
|
4
|
+
from .rid_types import AskCoreThread, SlackUserGroup
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TopicGroupModel(BaseModel):
|
|
8
|
+
usergroup: SlackUserGroup
|
|
9
|
+
handle: str
|
|
10
|
+
name: str
|
|
11
|
+
emoji: str | None = None
|
|
12
|
+
users: list[SlackUser] = []
|
|
13
|
+
threads: list[AskCoreThread] = []
|
|
14
|
+
|
|
15
|
+
class ThreadLinkModel(BaseModel):
|
|
16
|
+
thread: AskCoreThread
|
|
17
|
+
message: SlackMessage
|
|
18
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from rid_lib.types import SlackMessage, SlackUserGroup
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AskCoreThread(SlackMessage):
|
|
5
|
+
namespace = "ask-core.thread"
|
|
6
|
+
|
|
7
|
+
class AskCoreResponse(SlackMessage):
|
|
8
|
+
namespace = "ask-core.response"
|
|
9
|
+
|
|
10
|
+
class ThreadLink(SlackMessage):
|
|
11
|
+
namespace = "ask-tg.thread-link"
|
|
12
|
+
|
|
13
|
+
class AskTopicGroup(SlackUserGroup):
|
|
14
|
+
namespace = "ask-tg.topic-group"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from logging import Logger
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from slack_bolt import App, Respond
|
|
6
|
+
from slack_sdk import WebClient
|
|
7
|
+
|
|
8
|
+
from .config import AskTopicGroupsConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class SlackCommandHandler:
|
|
13
|
+
log: Logger
|
|
14
|
+
slack_app: App
|
|
15
|
+
slack_admin_client: WebClient
|
|
16
|
+
config: AskTopicGroupsConfig
|
|
17
|
+
|
|
18
|
+
def __post_init__(self):
|
|
19
|
+
self.register_handlers()
|
|
20
|
+
|
|
21
|
+
def register_handlers(self):
|
|
22
|
+
self.slack_app.command("/join-topic")(self.topic_group_join)
|
|
23
|
+
self.slack_app.command("/leave-topic")(self.topic_group_leave)
|
|
24
|
+
|
|
25
|
+
def get_user_group(self, ug_id: str):
|
|
26
|
+
resp = self.slack_app.client.usergroups_list()
|
|
27
|
+
usergroups = resp["usergroups"]
|
|
28
|
+
|
|
29
|
+
for usergroup in usergroups:
|
|
30
|
+
if usergroup["id"] == ug_id:
|
|
31
|
+
return usergroup
|
|
32
|
+
|
|
33
|
+
def topic_group_join(self, ack, respond: Respond, command):
|
|
34
|
+
ack()
|
|
35
|
+
|
|
36
|
+
user_id = command["user_id"]
|
|
37
|
+
text = command['text']
|
|
38
|
+
|
|
39
|
+
capture_pattern = re.compile(r'<!subteam\^(S[A-Z0-9]+)\|@[^>]+>')
|
|
40
|
+
ug_id = capture_pattern.findall(text)[0]
|
|
41
|
+
|
|
42
|
+
usergroup = self.get_user_group(ug_id)
|
|
43
|
+
ug_name: str = usergroup["name"]
|
|
44
|
+
ug_handle: str = usergroup["handle"]
|
|
45
|
+
|
|
46
|
+
if ug_handle.startswith("tg-"):
|
|
47
|
+
ug_users: list[str] = self.slack_app.client.usergroups_users_list(usergroup=ug_id)['users']
|
|
48
|
+
|
|
49
|
+
if user_id not in ug_users:
|
|
50
|
+
ug_users.append(user_id)
|
|
51
|
+
self.slack_admin_client.usergroups_users_update(usergroup=ug_id, users=ug_users)
|
|
52
|
+
respond(f"Joined {ug_name}!")
|
|
53
|
+
|
|
54
|
+
else:
|
|
55
|
+
respond(f"You are already a member of {ug_name}")
|
|
56
|
+
else:
|
|
57
|
+
respond(f"{ug_handle} is not a topic group")
|
|
58
|
+
|
|
59
|
+
def topic_group_leave(self, ack, respond: Respond, command):
|
|
60
|
+
ack()
|
|
61
|
+
|
|
62
|
+
user_id = command["user_id"]
|
|
63
|
+
text = command['text']
|
|
64
|
+
|
|
65
|
+
capture_pattern = re.compile(r'<!subteam\^(S[A-Z0-9]+)\|@[^>]+>')
|
|
66
|
+
ug_id = capture_pattern.findall(text)[0]
|
|
67
|
+
|
|
68
|
+
usergroup = self.get_user_group(ug_id)
|
|
69
|
+
ug_name: str = usergroup["name"]
|
|
70
|
+
ug_handle: str = usergroup["handle"]
|
|
71
|
+
|
|
72
|
+
if ug_handle.startswith("tg-"):
|
|
73
|
+
ug_users: list[str] = self.slack_app.client.usergroups_users_list(usergroup=ug_id)['users']
|
|
74
|
+
|
|
75
|
+
if user_id in ug_users:
|
|
76
|
+
ug_users.remove(user_id)
|
|
77
|
+
|
|
78
|
+
if len(ug_users) > 0:
|
|
79
|
+
self.slack_admin_client.usergroups_users_update(usergroup=ug_id, users=ug_users or "")
|
|
80
|
+
respond(f"Left {ug_name}")
|
|
81
|
+
else:
|
|
82
|
+
respond("Cannot leave group, you are the only user!")
|
|
83
|
+
else:
|
|
84
|
+
respond(f"You are not a member of {ug_name}")
|
|
85
|
+
else:
|
|
86
|
+
respond(f"{ug_handle} is not a valid topic group")
|
|
87
|
+
|
|
88
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from logging import Logger
|
|
3
|
+
|
|
4
|
+
from slack_bolt import App
|
|
5
|
+
|
|
6
|
+
from .config import AskTopicGroupsConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class SlackConfigManager:
|
|
11
|
+
slack_app: App
|
|
12
|
+
config: AskTopicGroupsConfig
|
|
13
|
+
log: Logger
|
|
14
|
+
|
|
15
|
+
def start(self):
|
|
16
|
+
resp = self.slack_app.client.auth_test()
|
|
17
|
+
team_id = resp.get("team_id")
|
|
18
|
+
user_id = resp.get("user_id")
|
|
19
|
+
|
|
20
|
+
if not team_id or not user_id:
|
|
21
|
+
raise RuntimeError("Slack auth test failed")
|
|
22
|
+
|
|
23
|
+
self.config.slack.team_id = team_id
|
|
24
|
+
self.config.save_to_yaml()
|
|
25
|
+
|
|
26
|
+
self.log.info(f"Set team id to {team_id}")
|
|
27
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from logging import Logger
|
|
3
|
+
|
|
4
|
+
from rid_lib.ext import Bundle
|
|
5
|
+
from slack_bolt import App
|
|
6
|
+
from slack_sdk import WebClient
|
|
7
|
+
from koi_net.components import Cache, KobjQueue
|
|
8
|
+
|
|
9
|
+
from .config import AskTopicGroupsConfig
|
|
10
|
+
from .models import ThreadLinkModel, TopicGroupModel
|
|
11
|
+
from .rid_types import AskTopicGroup, ThreadLink
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class SlackEventHandler:
|
|
16
|
+
log: Logger
|
|
17
|
+
slack_app: App
|
|
18
|
+
slack_admin_client: WebClient
|
|
19
|
+
kobj_queue: KobjQueue
|
|
20
|
+
config: AskTopicGroupsConfig
|
|
21
|
+
cache: Cache
|
|
22
|
+
|
|
23
|
+
def __post_init__(self):
|
|
24
|
+
self.register_handlers()
|
|
25
|
+
|
|
26
|
+
def register_handlers(self):
|
|
27
|
+
self.slack_app.event("reaction_added")(self.handle_reaction_added)
|
|
28
|
+
self.slack_app.event("reaction_removed")(self.handle_reaction_removed)
|
|
29
|
+
|
|
30
|
+
def handle_reaction_added(self, event: dict):
|
|
31
|
+
self.log.info(f"reactions added: {event}")
|
|
32
|
+
|
|
33
|
+
item = event["item"]
|
|
34
|
+
if item["type"] != "message":
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
# reaction emoji -> user group -> thread link -> thread -> topic group model
|
|
38
|
+
|
|
39
|
+
found_match = False
|
|
40
|
+
for topic_group_rid in self.cache.list_rids(rid_types=[AskTopicGroup]):
|
|
41
|
+
bundle = self.cache.read(topic_group_rid)
|
|
42
|
+
if not bundle:
|
|
43
|
+
continue
|
|
44
|
+
|
|
45
|
+
topic_group = bundle.validate_contents(TopicGroupModel)
|
|
46
|
+
|
|
47
|
+
if topic_group.emoji == event["reaction"]:
|
|
48
|
+
found_match = True
|
|
49
|
+
break
|
|
50
|
+
|
|
51
|
+
if not found_match:
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
self.log.info(f"Matched reaction to {topic_group_rid}")
|
|
55
|
+
|
|
56
|
+
thread_link_rid = ThreadLink(
|
|
57
|
+
team_id=self.config.slack.team_id,
|
|
58
|
+
channel_id=item["channel"],
|
|
59
|
+
ts=item["ts"]
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
bundle = self.cache.read(thread_link_rid)
|
|
63
|
+
if not bundle:
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
thread_link = bundle.validate_contents(ThreadLinkModel)
|
|
67
|
+
|
|
68
|
+
topic_group.threads.append(thread_link.thread)
|
|
69
|
+
|
|
70
|
+
self.log.info(f"Appending thread {thread_link.thread}")
|
|
71
|
+
|
|
72
|
+
self.kobj_queue.push(bundle=Bundle.generate(
|
|
73
|
+
rid=topic_group_rid,
|
|
74
|
+
contents=topic_group.model_dump()
|
|
75
|
+
))
|
|
76
|
+
|
|
77
|
+
self.slack_app.client.chat_postMessage(
|
|
78
|
+
channel=thread_link.thread.channel_id,
|
|
79
|
+
thread_ts=thread_link.thread.ts,
|
|
80
|
+
text=f"Inviting {topic_group.usergroup.mention} to participate!"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def handle_reaction_removed(self, event: dict):
|
|
84
|
+
self.log.info(f"reactions removed: {event}")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from slack_bolt import App
|
|
3
|
+
from slack_bolt.adapter.socket_mode import SocketModeHandler
|
|
4
|
+
|
|
5
|
+
from .config import AskTopicGroupsConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class SlackSocketMode:
|
|
10
|
+
slack_app: App
|
|
11
|
+
config: AskTopicGroupsConfig
|
|
12
|
+
handler: SocketModeHandler | None = field(init=False, default=None)
|
|
13
|
+
|
|
14
|
+
def start(self):
|
|
15
|
+
self.handler = SocketModeHandler(
|
|
16
|
+
app=self.slack_app,
|
|
17
|
+
app_token=self.config.env.ask_tg_slack_app_token)
|
|
18
|
+
self.handler.connect()
|
|
19
|
+
|
|
20
|
+
def stop(self):
|
|
21
|
+
if self.handler:
|
|
22
|
+
self.handler.close()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from logging import Logger
|
|
3
|
+
from rid_lib.ext import Bundle
|
|
4
|
+
from slack_bolt import App
|
|
5
|
+
from koi_net.components import KobjQueue
|
|
6
|
+
|
|
7
|
+
from .rid_types import SlackUserGroup
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class UserGroupSensor:
|
|
12
|
+
log: Logger
|
|
13
|
+
slack_app: App
|
|
14
|
+
kobj_queue: KobjQueue
|
|
15
|
+
|
|
16
|
+
def __post_init__(self):
|
|
17
|
+
self.register_handlers()
|
|
18
|
+
|
|
19
|
+
def register_handlers(self):
|
|
20
|
+
self.slack_app.event("subteam_updated")(self.handle_usergroup_update)
|
|
21
|
+
|
|
22
|
+
def handle_usergroup_update(self, event: dict):
|
|
23
|
+
usergroup = event["subteam"]
|
|
24
|
+
ug_rid = SlackUserGroup(usergroup["team_id"], usergroup["id"])
|
|
25
|
+
self.log.info(f"Detected update for {ug_rid}")
|
|
26
|
+
|
|
27
|
+
self.kobj_queue.push(bundle=Bundle.generate(
|
|
28
|
+
rid=ug_rid,
|
|
29
|
+
contents=usergroup
|
|
30
|
+
))
|
|
31
|
+
|
|
32
|
+
def backfill_usergroups(self):
|
|
33
|
+
resp = self.slack_app.client.usergroups_list()
|
|
34
|
+
usergroups = resp.data["usergroups"]
|
|
35
|
+
|
|
36
|
+
for usergroup in usergroups:
|
|
37
|
+
ug_rid = SlackUserGroup(usergroup["team_id"], usergroup["id"])
|
|
38
|
+
self.log.info(f"Processing {ug_rid}")
|
|
39
|
+
|
|
40
|
+
resp = self.slack_app.client.usergroups_users_list(usergroup=usergroup["id"])
|
|
41
|
+
usergroup["users"] = resp.data["users"]
|
|
42
|
+
|
|
43
|
+
self.kobj_queue.push(bundle=Bundle.generate(
|
|
44
|
+
rid=ug_rid,
|
|
45
|
+
contents=usergroup
|
|
46
|
+
))
|
|
47
|
+
|
|
48
|
+
def start(self):
|
|
49
|
+
self.backfill_usergroups()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: koi-net-ask-topic-groups-node
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Topic groups KOI-net node for the ask network application.
|
|
5
|
+
Author-email: Luke Miller <luke@metagov.org>
|
|
6
|
+
Project-URL: Homepage, https://github.com/metagov/koi-net-ask-topic-groups-node
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: koi-net~=2.0
|
|
10
|
+
Requires-Dist: rid-lib>=3.2.16
|
|
11
|
+
Requires-Dist: slack-bolt>=1.27.0
|
|
12
|
+
|
|
13
|
+
# Ask Topic Groups Node
|
|
14
|
+
|
|
15
|
+
This is the topic groups node in the Ask KOI network application. It's responsible for the topic groups feature, allowing users to leave and join groups of their choice. It prompts thread participants to tag related topic groups to link them, and invite members to participate.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
koi_net_ask_topic_groups_node/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
koi_net_ask_topic_groups_node/__main__.py,sha256=BVN9ou4zhiMwSEpt9pJgkorAGm1bQHI5U2vsNPfdPIA,64
|
|
3
|
+
koi_net_ask_topic_groups_node/config.py,sha256=9RjeheFACEFSMCOfukaciWOOcB_fytRaXfsB9ij1xtk,1099
|
|
4
|
+
koi_net_ask_topic_groups_node/core.py,sha256=Es7r7uZLhNeGNdJ6QjlRTkdhXM0XqwrITYe3Pv50O14,1250
|
|
5
|
+
koi_net_ask_topic_groups_node/models.py,sha256=RtMO7HFDofTDo--fDirnNL35sCfmmrQz5F5TRJ3pkZ8,422
|
|
6
|
+
koi_net_ask_topic_groups_node/rid_types.py,sha256=EZuTDHmr5UpepY54odqjJrLed-dPrUEW-bJjV4-s7MI,348
|
|
7
|
+
koi_net_ask_topic_groups_node/slack_command_handler.py,sha256=01CM1xwp9plthNDgD9CwdX_g5Mp7sAyE-nvlxPvZk6A,2968
|
|
8
|
+
koi_net_ask_topic_groups_node/slack_config_manager.py,sha256=z5yqmILfVvJow7K-rNrQGqYD8C46uQXxMoUJYLzOc3M,650
|
|
9
|
+
koi_net_ask_topic_groups_node/slack_event_handler.py,sha256=JXpIyMoudUIGOUNeGyNOhI_8QdeqBE_EsSt8iC6bai0,2630
|
|
10
|
+
koi_net_ask_topic_groups_node/socket_mode.py,sha256=hnfzN0oaq057IUn3rszlTFig7Ede0TadSeVcp9eHgBA,611
|
|
11
|
+
koi_net_ask_topic_groups_node/user_group_sensor.py,sha256=1LEdrFNgr47irUEOSPnPaXP5F0By5bU9Mho_mEECIaQ,1501
|
|
12
|
+
koi_net_ask_topic_groups_node/knowledge_handlers/ask_thread_handler.py,sha256=Ubo9TBl7F87Pd-k95RUgQXL_coVQ1lkMeNAUpGi-2-o,2120
|
|
13
|
+
koi_net_ask_topic_groups_node/knowledge_handlers/slack_usergroup_handler.py,sha256=yZcFDHFIzFgeUonOjfPpMXIiOFj5nesUUtL4ckKyET8,1976
|
|
14
|
+
koi_net_ask_topic_groups_node-0.1.0.dist-info/METADATA,sha256=TnjwP6ZLewigE90B6XULGrqaMpofIDAUBcaqrVfzHcE,727
|
|
15
|
+
koi_net_ask_topic_groups_node-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
16
|
+
koi_net_ask_topic_groups_node-0.1.0.dist-info/top_level.txt,sha256=tA93Vvix2p2StfY2LEaHNE5MJHsn7q6csUaiauvHVfU,30
|
|
17
|
+
koi_net_ask_topic_groups_node-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
koi_net_ask_topic_groups_node
|