Skip to content

Commit f07d54d

Browse files
AmbratolmAmbratolm
authored andcommitted
Added ai incite command
1 parent 1577c17 commit f07d54d

File tree

5 files changed

+131
-35
lines changed

5 files changed

+131
-35
lines changed

api/main.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ async def lifespan(self: ActApi):
5353
@property
5454
def info_text(self):
5555
scheme, netloc = self.address.scheme, self.address.netloc
56-
full_url = lambda txt=None: f"{Fore.BLUE}{scheme}://{netloc}{txt}{Fore.RESET}"
56+
full_url = (
57+
lambda txt=None: f"{Fore.GREEN}{scheme}://{netloc}{txt or ""}{Fore.RESET}"
58+
)
5759
output = "Links:"
5860
output += f"\n• Root: {full_url()}"
5961
output += f"\n• Swagger: {full_url(self.docs_url)}"

api/routers/member_router.py

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,39 @@ def __init__(self, bot: ActBot, *args, **kwargs):
1616
super().__init__(prefix="/members", tags=["Members"], *args, **kwargs)
1717

1818
def member_dict(member: Member):
19-
return {
20-
"id": member.id,
21-
"name": member.name,
22-
"display_name": member.display_name,
23-
"diplay_avatar_url": member.display_avatar.url,
24-
"display_icon_url": member.display_icon,
25-
"banner_url": member.banner.url if member.banner else None,
26-
"created_at": member.created_at,
27-
"joined_at": member.joined_at,
28-
}
19+
return (
20+
{
21+
"id": member.id,
22+
"name": member.name,
23+
"display_name": member.display_name,
24+
"diplay_avatar_url": member.display_avatar.url,
25+
"display_icon_url": member.display_icon,
26+
"banner_url": member.banner.url if member.banner else None,
27+
"created_at": member.created_at,
28+
"joined_at": member.joined_at,
29+
}
30+
if member
31+
else {}
32+
)
2933

3034
# ----------------------------------------------------------------------------------------------------
3135

32-
@self.get("/{guild_id}/", response_model=list[dict])
33-
async def get_members(guild_id: int):
36+
@self.get("/{guild_id}/top", response_model=list[dict])
37+
async def get_top_members(guild_id: int, limit: int = 10):
3438
try:
3539
for guild in bot.guilds:
3640
if guild.id == guild_id:
37-
return [member_dict(member) for member in guild.members]
38-
41+
top_actors = cast(
42+
BoardCog, bot.get_cog(BoardCog.__cog_name__)
43+
).get_top_actors(guild, limit)
44+
45+
return [
46+
member_dict(
47+
guild.get_member(actor.id)
48+
or await guild.fetch_member(actor.id)
49+
)
50+
for actor in top_actors
51+
]
3952
raise HTTPException(
4053
status_code=404,
4154
detail=f"No guild with id '{guild_id}' found among the guilds the discord bot is member of.",

bot/cogs/ai_cog.py

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,26 @@
22
import tomllib
33
from datetime import UTC, datetime
44

5-
from discord import Guild, Interaction, Member, Message, User, app_commands
5+
from discord import (
6+
DMChannel,
7+
GroupChannel,
8+
Guild,
9+
Interaction,
10+
Member,
11+
Message,
12+
TextChannel,
13+
Thread,
14+
User,
15+
app_commands,
16+
)
17+
from discord.abc import Messageable
618
from discord.ext import tasks
719
from discord.ext.commands import Cog
820
from google.genai.errors import APIError
921
from odmantic import query
1022

1123
from bot.main import ActBot
24+
from bot.ui import EmbedX
1225
from db.actor import Actor
1326
from db.main import DbRef
1427
from utils.ai import ActAi
@@ -22,7 +35,7 @@
2235
# ----------------------------------------------------------------------------------------------------
2336
# * AI Cog
2437
# ----------------------------------------------------------------------------------------------------
25-
class AI(Cog, description="Integrated generative AI chat bot."):
38+
class AiCog(Cog, description="Integrated generative AI chat bot."):
2639
CONFIG_PATH = pathlib.Path(__file__).parent / "ai_cog.toml"
2740
MAX_FILE_SIZE = 524288 # 512 KB (0.5 MB)
2841
COOLDOWN_TIME = 60 # 1 min
@@ -47,10 +60,67 @@ def cog_unload(self):
4760
# ----------------------------------------------------------------------------------------------------
4861
# * Incite
4962
# ----------------------------------------------------------------------------------------------------
50-
# @app_commands.checks.has_permissions(administrator=True)
51-
# @app_commands.command(description="")
52-
# async def incite(self, interaction: Interaction, member: Member):
53-
# pass
63+
@app_commands.guild_only()
64+
@app_commands.checks.has_permissions(administrator=True)
65+
@app_commands.default_permissions(administrator=True)
66+
@app_commands.command(description="Incite AI chat bot to interact with a member")
67+
async def incite(
68+
self, interaction: Interaction, member: Member, prompt: str | None = None
69+
):
70+
# Deny bot-self & DM & non-messageable channel
71+
if (
72+
self.bot.user == member
73+
or not interaction.guild
74+
or not isinstance(interaction.channel, Messageable)
75+
):
76+
await interaction.response.send_message(
77+
embed=EmbedX.warning("This command cannot be used in this context."),
78+
ephemeral=True,
79+
)
80+
return
81+
82+
# Prepare prompt
83+
text_prompt = (
84+
f"Begin natural talk w/ {member.mention} that feels like ur own initiative."
85+
f"Absolutely avoid references to instructions, prompts, or being told to message them."
86+
)
87+
if prompt:
88+
text_prompt += f'follow this prompt:"{prompt.strip()}".'
89+
90+
# Defer response to prevent timeout
91+
await interaction.response.defer(ephemeral=True)
92+
93+
# Save current member who interacted to be remembered next time
94+
self.save_actor_interaction(interaction.guild, member)
95+
96+
# Load members who interacted before
97+
actors = self.load_actors(interaction.guild)
98+
if actors:
99+
text_prompt += f"\n{text_csv(actors, "|")}"
100+
101+
# Perform prompt
102+
log.info(f"[Prompt] {text_prompt}")
103+
try:
104+
self.ai.use_session(
105+
interaction.guild.id, history=self.load_history(interaction.guild)
106+
)
107+
reply = self.ai.prompt(text=text_prompt) or f"{member.mention} 👋"
108+
await interaction.followup.send(
109+
embed=EmbedX.success(
110+
title="Incentive",
111+
description=f"{self.bot.user} has been incited to talk with {member.mention}.\n\n**Prompt:**```{text_prompt}```",
112+
),
113+
ephemeral=True,
114+
)
115+
async with interaction.channel.typing():
116+
await interaction.channel.send(reply)
117+
except Exception as e:
118+
await interaction.followup.send(embed=EmbedX.error(str(e)), ephemeral=True)
119+
log.exception(e)
120+
return
121+
122+
# Save history
123+
self.save_history(interaction.guild)
54124

55125
# ----------------------------------------------------------------------------------------------------
56126
# * On Message
@@ -103,12 +173,15 @@ async def on_message(self, message: Message):
103173
f"{message.content.replace(self.bot.user.mention, "").strip()}"
104174
)
105175

106-
# Add members who interacted before
176+
# Save current member who interacted to be remembered next time
177+
self.save_actor_interaction(message.guild, message.author)
178+
179+
# Load members who interacted before
107180
actors = self.load_actors(message.guild)
108181
if actors:
109182
text_prompt += f"\n{text_csv(actors, "|")}"
110183

111-
# Prompt AI
184+
# Perform prompt
112185
log.info(f"[Prompt] {text_prompt} <{file_prompt}>")
113186
try:
114187
self.ai.use_session(
@@ -131,8 +204,7 @@ async def on_message(self, message: Message):
131204
fallback_reply = "What? 😕"
132205
await message.reply(reply or fallback_reply)
133206

134-
# Save current member who interacted to be remembered next time
135-
self.save_actor_interaction(message.guild, message.author)
207+
# Save current chat session
136208
self.save_history(message.guild)
137209

138210
# ----------------------------------------------------------------------------------------------------

bot/cogs/help_cog.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from discord import Color, Embed, Interaction, app_commands
1+
from discord import Color, Embed, Interaction, Member, app_commands
22
from discord.ext.commands import Cog, GroupCog
33
from tabulate import tabulate
44

@@ -28,15 +28,23 @@ async def commands(self, interaction: Interaction):
2828
color=Color.blue(),
2929
)
3030
for cmd in all_cmds:
31-
embed.add_field(
32-
name=f"/{cmd.name}",
33-
value=(
34-
cmd.description
35-
if not isinstance(cmd, app_commands.commands.ContextMenu)
36-
else ""
37-
),
38-
inline=False,
39-
)
31+
# Check if command has administrator requirement in default_permissions
32+
admin_required = False
33+
if hasattr(cmd, "default_permissions") and cmd.default_permissions:
34+
admin_required = cmd.default_permissions.administrator
35+
if not admin_required or (
36+
isinstance(interaction.user, Member)
37+
and interaction.user.guild_permissions.administrator
38+
):
39+
embed.add_field(
40+
name=f"/{cmd.name}",
41+
value=(
42+
cmd.description
43+
if not isinstance(cmd, app_commands.commands.ContextMenu)
44+
else ""
45+
),
46+
inline=False,
47+
)
4048
if self.bot.user:
4149
embed.set_thumbnail(url=self.bot.user.display_avatar)
4250
await interaction.response.send_message(embed=embed)

bot/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ async def on_error(
8585
await interaction.response.send_message(embed=embed, ephemeral=True)
8686
else:
8787
await interaction.followup.send(embed=embed, ephemeral=True)
88+
log.exception(error)
8889

8990
# ----------------------------------------------------------------------------------------------------
9091

0 commit comments

Comments
 (0)