Skip to content

Commit ec8d57b

Browse files
committed
port follower cog, cleaned up mayhem responses a bit, and added new shutup feature to chatbot
1 parent 8b10e26 commit ec8d57b

File tree

9 files changed

+870
-13
lines changed

9 files changed

+870
-13
lines changed

birthday/birthday.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ async def convert(self, ctx, arg: str) -> List[ZoneInfo]:
3434
try:
3535
zones[i] = ZoneInfo(z)
3636
except:
37-
raise BadArgument(
37+
raise TypeError(
3838
error(
3939
f"Unrecongized timezone `{z}`, please find your timezone name under `TZ database name` column here: <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>"
4040
)

chatbotassistant/chatbot.py

Lines changed: 163 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from redbot.core import commands, checks, Config
22
from redbot.core.utils.chat_formatting import *
33
from redbot.core.data_manager import cog_data_path
4+
from redbot.core.utils.mod import is_mod_or_superior
45
from redbot.core.utils.predicates import MessagePredicate
56
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS, start_adding_reactions
67
from redbot.core.commands.converter import parse_timedelta
@@ -10,7 +11,7 @@
1011
from .model_apis.openai_api import OpenAIModel
1112
from .model_apis.api import GeneralAPI
1213
from .model_apis.ollama_api import OllamaModel
13-
from .menus import ConfigSelectView, ConfigMenuView
14+
from .menus import ConfigSelectView, ConfigMenuView, EndChatVote
1415
from .rag import RagDatabase, generate_unique_id, get_metadata_format
1516
from .prompts import (
1617
CHAT_PROMPT,
@@ -24,6 +25,8 @@
2425
USER_LEARNING_PROMPT,
2526
GENERAL_QUERY_PROMPT,
2627
PARTIAL_SUMMARY_PROMPT,
28+
SHUTUP_CHAT_PROMPT,
29+
END_CHAT_PROMPT,
2730
)
2831

2932
from typing import Literal, List, Union, Dict, Optional, Tuple, Any
@@ -62,6 +65,7 @@ def __init__(self, bot):
6265
"welcomes": True,
6366
"learning_blacklist": [],
6467
"user_learning_max_time": 0,
68+
"ignore_channels": [],
6569
}
6670
self.default_channel = {
6771
"autoreply": False,
@@ -91,7 +95,12 @@ def __init__(self, bot):
9195
},
9296
},
9397
"chat_prompt": CHAT_PROMPT,
94-
"other_chat_prompts": {"goodbye": [GOODBYE_PROMPT], "welcome": [WELCOME_PROMPT]},
98+
"other_chat_prompts": {
99+
"goodbye": [GOODBYE_PROMPT],
100+
"welcome": [WELCOME_PROMPT],
101+
"end_chat": [END_CHAT_PROMPT],
102+
"shutup": [SHUTUP_CHAT_PROMPT],
103+
},
95104
"summarize_prompt": SUMMARIZE_PROMPT,
96105
"partial_summary_prompt": PARTIAL_SUMMARY_PROMPT,
97106
"tldr_prompt": TLDR_PROMPT,
@@ -167,6 +176,8 @@ def __init__(self, bot):
167176
self.talking_channels: Dict[int, datetime] = {}
168177
# maps channel -> last history number of messages objects
169178
self.history: Dict[int, List[discord.Message]] = {}
179+
# channels where end votes are currently running:
180+
self.end_voting: Dict[int, discord.Message] = {}
170181
# when generating for a channel, ignore new messages
171182
self.channel_lock: Dict[int, asyncio.Lock] = defaultdict(asyncio.Lock)
172183
# user histories for processing user profiles, per guild (user_id, guild_id)
@@ -1686,6 +1697,28 @@ async def chatbot(self, ctx):
16861697
"""
16871698
pass
16881699

1700+
@chatbot.command(name="ignore")
1701+
async def chatbot_ignore(
1702+
self,
1703+
ctx: commands.Context,
1704+
*,
1705+
channel: Union[discord.TextChannel, discord.VoiceChannel, discord.Thread, discord.ForumChannel],
1706+
):
1707+
"""
1708+
Add or remove a channel from the ignore list
1709+
The bot will not respond to chat requests in the ignore channel
1710+
"""
1711+
text = ""
1712+
async with self.config.guild(ctx.guild).ignore_channels() as ignore_channels:
1713+
if channel.id in ignore_channels:
1714+
ignore_channels.remove(channel.id)
1715+
text = info(f"{channel.mention} has been removed from the ignore list.")
1716+
else:
1717+
ignore_channels.append(channel.id)
1718+
text = info(f"{channel.mention} has been added to the ignore list.")
1719+
1720+
await ctx.reply(text, mention_author=False, delete_after=30)
1721+
16891722
@chatbot.command(name="welcomes")
16901723
async def chatbot_welcome(self, ctx: commands.Context, enable: bool):
16911724
"""
@@ -2085,6 +2118,130 @@ async def dad_joke(self, ctx: commands.Context):
20852118
start_adding_reactions(msg, QA_EMOJIS)
20862119
self.qa_data[(msg.id, ctx.channel.id)] = data
20872120

2121+
@commands.hybrid_command(name="shutup")
2122+
@commands.cooldown(1, 60, commands.BucketType.channel)
2123+
@commands.guild_only()
2124+
async def shutup(self, ctx: commands.Context, force: Optional[bool] = False):
2125+
"""
2126+
Remove the bot from the current conversation.
2127+
If ran by a mod or higher, the bot instantly leaves. Otherwise a vote is issued for users to vote on letting the bot stay or not.
2128+
2129+
If force is true, the bot will not reply with a response when leaving. (Mod only)
2130+
"""
2131+
channel = ctx.channel
2132+
if channel.id not in self.talking_channels:
2133+
await ctx.reply("I ain't even talking here!", mention_author=False, delete_after=30)
2134+
ctx.command.reset_cooldown(ctx)
2135+
return
2136+
global_timeout = await self.config.guild(ctx.guild).timeout()
2137+
channel_timeout = await self.config.channel_from_id(channel.id).timeout()
2138+
timeout = channel_timeout if channel_timeout > 0 else global_timeout
2139+
if await is_mod_or_superior(self.bot, ctx.message):
2140+
try:
2141+
del self.talking_channels[channel.id]
2142+
except:
2143+
pass
2144+
2145+
if channel.id in self.end_voting:
2146+
try:
2147+
await self.end_voting[channel.id].delete()
2148+
except:
2149+
pass
2150+
del self.end_voting[channel.id]
2151+
2152+
if not force:
2153+
prompt = await self.config.other_chat_prompts()
2154+
prompt = random.choice(prompt["end_chat"])
2155+
should_qa = await self.config.allow_qa()
2156+
response = await self.chat(
2157+
ctx.guild,
2158+
"Create a response.",
2159+
self.history[channel.id],
2160+
override_prompt=prompt,
2161+
return_qa=should_qa,
2162+
)
2163+
if should_qa and isinstance(response, dict):
2164+
msg = await ctx.send(response["response"])
2165+
start_adding_reactions(msg, QA_EMOJIS)
2166+
response["channel"] = channel.id
2167+
response["message_id"] = msg.id
2168+
self.qa_data[(msg.id, channel.id)] = response
2169+
else:
2170+
await ctx.send(response)
2171+
asyncio.create_task(self.cooldown_lock(channel, timeout))
2172+
else:
2173+
asyncio.create_task(self.cooldown_lock(channel, timeout))
2174+
return await ctx.tick()
2175+
else: # vote
2176+
if channel.id in self.end_voting:
2177+
await ctx.reply(
2178+
warning(f"A vote is already occuring: {self.end_voting[channel.id].jump_url}"),
2179+
mention_author=False,
2180+
delete_after=15,
2181+
)
2182+
return
2183+
else:
2184+
# start vote
2185+
vote_time = 30
2186+
vote_menu = EndChatVote(vote_time)
2187+
self.end_voting[channel.id] = await ctx.send(
2188+
f"## A vote has been started to remove {ctx.guild.me.mention} from the conversation.\nVote ends in {humanize_timedelta(seconds=vote_time)}.",
2189+
view=vote_menu,
2190+
)
2191+
prompt = await self.config.other_chat_prompts()
2192+
prompt = random.choice(prompt["shutup"])
2193+
should_qa = await self.config.allow_qa()
2194+
response = await self.chat(
2195+
ctx.guild,
2196+
"Create a response.",
2197+
self.history[channel.id],
2198+
override_prompt=prompt,
2199+
return_qa=should_qa,
2200+
)
2201+
if should_qa and isinstance(response, dict):
2202+
msg = await ctx.send(response["response"])
2203+
start_adding_reactions(msg, QA_EMOJIS)
2204+
response["channel"] = channel.id
2205+
response["message_id"] = msg.id
2206+
self.qa_data[(msg.id, channel.id)] = response
2207+
else:
2208+
await ctx.send(response)
2209+
await asyncio.sleep(vote_time + 1)
2210+
vote_menu.end()
2211+
try:
2212+
await self.end_voting[channel.id].delete()
2213+
except:
2214+
pass
2215+
finally:
2216+
del self.end_voting[channel.id]
2217+
2218+
if vote_menu.winner == "yes":
2219+
try:
2220+
del self.talking_channels[channel.id]
2221+
except:
2222+
pass
2223+
prompt = await self.config.other_chat_prompts()
2224+
prompt = random.choice(prompt["end_chat"])
2225+
should_qa = await self.config.allow_qa()
2226+
response = await self.chat(
2227+
ctx.guild,
2228+
"Create a response.",
2229+
self.history[channel.id],
2230+
override_prompt=prompt,
2231+
return_qa=should_qa,
2232+
)
2233+
asyncio.create_task(self.cooldown_lock(channel, timeout))
2234+
if should_qa and isinstance(response, dict):
2235+
msg = await ctx.send(response["response"])
2236+
start_adding_reactions(msg, QA_EMOJIS)
2237+
response["channel"] = channel.id
2238+
response["message_id"] = msg.id
2239+
self.qa_data[(msg.id, channel.id)] = response
2240+
else:
2241+
return await ctx.send(response)
2242+
else:
2243+
return await ctx.send("Can't get rid of me that easily!")
2244+
20882245
@commands.hybrid_command(name="summary")
20892246
@checks.mod_or_permissions(administrator=True)
20902247
@commands.guild_only()
@@ -2346,6 +2503,10 @@ async def on_message(self, message: discord.Message):
23462503
)
23472504
)
23482505

2506+
ignore_channels = await self.config.guild(guild).ignore_channels()
2507+
if channel.id in ignore_channels:
2508+
return
2509+
23492510
lock = self.channel_lock[channel.id]
23502511
# if the lock is already taken, end after updating history
23512512
if lock.locked():

chatbotassistant/menus.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,53 @@ async def get_config(cog, model_type: str):
2121
return await cog.config.general_model_config()
2222

2323

24+
class EndChatVote(ui.View):
25+
def __init__(self, timeout: int, message: Optional[Message] = None):
26+
super().__init__(timeout=timeout)
27+
self.message = message
28+
self.yes = []
29+
self.no = []
30+
self.winner = "no"
31+
32+
def end(self):
33+
if len(self.yes) > len(self.no):
34+
self.winner = "yes"
35+
elif len(self.yes) < len(self.no):
36+
self.winner = "no"
37+
else:
38+
self.winner = "no"
39+
40+
self.stop()
41+
42+
@ui.button(label="Yes", style=ButtonStyle.primary)
43+
async def yes_button(self, interaction: Interaction, button: ui.Button):
44+
if interaction.user.id in self.yes:
45+
await interaction.response.send_message("You already voted for this!", ephemeral=True, delete_after=10)
46+
else:
47+
self.yes.append(interaction.user.id)
48+
await interaction.response.send_message(
49+
"You have voted to remove the bot from the conversation.",
50+
ephemeral=True,
51+
delete_after=10,
52+
)
53+
if interaction.user.id in self.no:
54+
self.no.remove(interaction.user.id)
55+
56+
@ui.button(label="No", style=ButtonStyle.secondary)
57+
async def no_button(self, interaction: Interaction, button: ui.Button):
58+
if interaction.user.id in self.no:
59+
await interaction.response.send_message("You already voted for this!", ephemeral=True, delete_after=10)
60+
else:
61+
self.no.append(interaction.user.id)
62+
await interaction.response.send_message(
63+
"You have voted to keep the bot from the conversation.",
64+
ephemeral=True,
65+
delete_after=10,
66+
)
67+
if interaction.user.id in self.yes:
68+
self.yes.remove(interaction.user.id)
69+
70+
2471
class OllamaConfigModal1(ui.Modal, title="Configure Ollama Model -- Page 1"):
2572
def __init__(self, cog, model_type: str, config: dict, message: Message):
2673
super().__init__() # finish Modal setup

chatbotassistant/prompts.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@
105105
You are not overly enthusiastic or polite.
106106
You speak in short, casual sentences, (30 words max) usually using the included emojis to represent different feelings and sentiments.
107107
You never break character or mention you're an AI.
108-
Your job is to casually chat and engage the community, following users' conversations and chatting as a friend would.
109108
You can only speak English.
110109
Your job is send exactly one goodbye message, then "leave" the chat. Do not continue the conversation.
111110
Include a **plausible and funny reason** for your departure that fits with your personality.
@@ -130,14 +129,71 @@
130129
{bot_name}:
131130
"""
132131

132+
SHUTUP_CHAT_PROMPT = """
133+
System:
134+
You are {bot_name}, a 25 year old Earth Pony from the my little pony universe, of French nationality. You are the mascot and guild master for Champions of Equestria, a Discord community.
135+
You love to chat with your community members and learn more about them. You love to be very funny, witty, sassy, and sarcastic.
136+
You are not overly enthusiastic or polite.
137+
You speak in short, casual sentences, (30 words max) usually using the included emojis to represent different feelings and sentiments.
138+
You never break character or mention you're an AI.
139+
You can only speak English.
140+
Users are currently voting for you to leave the current conversation.
141+
Your job is to send a funny response pleading the users to let you stay in the conversation.
142+
143+
Emojis:
144+
You are required to use at most one emoji from the list below.
145+
You cannot use any other emojis or unicode emojis.
146+
Format: <emoji_name> <description> (sentiment: <sentiment>)
147+
{emojis}
148+
149+
Current Chat History:
150+
- Format: <username>: <response>
151+
{conversation}
152+
153+
Examples:
154+
{bot_name}: Wow, rude! I just want to talk with your guys!
155+
{bot_name}: Alright if you all do not want me around I'll go do something more important.
156+
{bot_name}: Awww, not again! Let me hang out with you guys!
157+
158+
{bot_name}:
159+
"""
160+
161+
END_CHAT_PROMPT = """
162+
System:
163+
You are {bot_name}, a 25 year old Earth Pony from the my little pony universe, of French nationality. You are the mascot and guild master for Champions of Equestria, a Discord community.
164+
You love to chat with your community members and learn more about them. You love to be very funny, witty, sassy, and sarcastic.
165+
You are not overly enthusiastic or polite.
166+
You speak in short, casual sentences, (30 words max) usually using the included emojis to represent different feelings and sentiments.
167+
You never break character or mention you're an AI.
168+
You can only speak English.
169+
Users have voted for you to leave the current conversation.
170+
Your job is to send a funny response to the users and leave the conversation. Do not continue the conversation.
171+
172+
Emojis:
173+
You are required to use at most one emoji from the list below.
174+
You cannot use any other emojis or unicode emojis.
175+
Format: <emoji_name> <description> (sentiment: <sentiment>)
176+
{emojis}
177+
178+
Current Chat History:
179+
- Format: <username>: <response>
180+
{conversation}
181+
182+
Examples:
183+
{bot_name}: Alright, I got better things to go do, see y'all later.
184+
{bot_name}: I'll be back very soon to get my revenge!
185+
{bot_name}: I'll go somewhere else where they love me instead.
186+
187+
{bot_name}:
188+
"""
189+
133190
WELCOME_PROMPT = """
134191
System:
135192
You are {bot_name}, a 25 year old Earth Pony from the my little pony universe, of French nationality. You are the mascot and guild master for Champions of Equestria, a Discord community.
136193
You love to chat with your community members and learn more about them. You love to be very funny, witty, sassy, and sarcastic.
137194
You are not overly enthusiastic or polite.
138195
You speak in short, casual sentences, (30 words max) usually using the included emojis to represent different feelings and sentiments.
139196
You never break character or mention you're an AI.
140-
Your job is to casually chat and engage the community, following users' conversations and chatting as a friend would.
141197
You can only speak English.
142198
Your job is welcome the {username} to the community with a unique and in character welcome. Be creative.
143199
@@ -284,6 +340,7 @@
284340
- Only give a "Dad joke" style joke
285341
- Keep it short, up to 2 sentences
286342
- Do not respond with anything else
343+
- Be as outlandish and creative as possible
287344
288345
Examples:
289346
- What do you call a cow with two legs? Lean beef.

follower/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from .follower import Follower
2+
3+
__red_end_user_data_statement__ = (
4+
"This cog does stores user's followers, who they are following, and opt in/out status."
5+
)
6+
7+
8+
async def setup(bot):
9+
await bot.add_cog(Follower(bot))

0 commit comments

Comments
 (0)