Merge pull request #3001 from InspectorCaracal/discord-integration

Add discord chat integration
This commit is contained in:
Griatch 2022-11-30 23:55:39 +01:00 committed by GitHub
commit a71534cc3b
7 changed files with 1181 additions and 27 deletions

View file

@ -72,6 +72,7 @@ class AccountCmdSet(CmdSet):
self.add(comms.CmdIRCStatus())
self.add(comms.CmdRSS2Chan())
self.add(comms.CmdGrapevine2Chan())
self.add(comms.CmdDiscord2Chan())
# self.add(comms.CmdChannels())
# self.add(comms.CmdAddCom())
# self.add(comms.CmdDelCom())

View file

@ -3,7 +3,7 @@ Communication commands:
- channel
- page
- irc/rss/grapevine linking
- irc/rss/grapevine/discord linking
"""
@ -14,7 +14,7 @@ from evennia.accounts.models import AccountDB
from evennia.comms.comms import DefaultChannel
from evennia.comms.models import Msg
from evennia.locks.lockhandler import LockException
from evennia.utils import create, logger, utils
from evennia.utils import create, logger, search, utils
from evennia.utils.evmenu import ask_yes_no
from evennia.utils.logger import tail_log_file
from evennia.utils.utils import class_from_module, strip_unsafe_input
@ -34,6 +34,7 @@ __all__ = (
"CmdIRCStatus",
"CmdRSS2Chan",
"CmdGrapevine2Chan",
"CmdDiscord2Chan",
)
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
@ -1908,3 +1909,173 @@ class CmdGrapevine2Chan(COMMAND_DEFAULT_CLASS):
bot.start(ev_channel=channel, grapevine_channel=grapevine_channel)
self.msg(f"Grapevine connection created {channel} <-> {grapevine_channel}.")
class CmdDiscord2Chan(COMMAND_DEFAULT_CLASS):
"""
Link an Evennia channel to an external Discord channel
Usage:
discord2chan[/switches]
discord2chan[/switches] <evennia_channel> [= <discord_channel_id>]
discord2chan/name <bot_name>
Switches:
/list - (or no switch) show existing Evennia <-> Discord links
/remove - remove an existing link by link ID
/delete - alias to remove
/guild - toggle the Discord server tag on/off
/channel - toggle the Evennia/Discord channel tags on/off
Example:
discord2chan mydiscord = 555555555555555
This creates a link between an in-game Evennia channel and an external
Discord channel. You must have a valid Discord bot application
(https://discord.com/developers/applications)) and your DISCORD_BOT_TOKEN
must be added to settings. (Please put it in secret_settings !)
"""
key = "discord2chan"
aliases = ("discord",)
switch_options = (
"channel",
"delete",
"guild",
"list",
"remove",
)
locks = "cmd:serversetting(DISCORD_ENABLED) and pperm(Developer)"
help_category = "Comms"
def func(self):
"""Manage the Evennia<->Discord channel links"""
if not settings.DISCORD_BOT_TOKEN:
self.msg(
"You must add your Discord bot application token to settings as DISCORD_BOT_TOKEN"
)
return
discord_bot = [
bot for bot in AccountDB.objects.filter(db_is_bot=True, username="DiscordBot")
]
if not discord_bot:
# create a new discord bot
bot_class = class_from_module(settings.DISCORD_BOT_CLASS, fallback=bots.DiscordBot)
discord_bot = create.create_account("DiscordBot", None, None, typeclass=bot_class)
discord_bot.start()
self.msg("Created and initialized a new Discord relay bot.")
else:
discord_bot = discord_bot[0]
if not discord_bot.is_typeclass(settings.DISCORD_BOT_CLASS, exact=True):
self.msg(
f"WARNING: The Discord bot's typeclass is '{discord_bot.typeclass_path}'. This does not match {settings.DISCORD_BOT_CLASS} in settings!"
)
if "guild" in self.switches:
discord_bot.db.tag_guild = not discord_bot.db.tag_guild
self.msg(
f"Messages to Evennia |wwill {'' if discord_bot.db.tag_guild else 'not '}|ninclude the Discord server."
)
return
if "channel" in self.switches:
discord_bot.db.tag_channel = not discord_bot.db.tag_channel
self.msg(
f"Relayed messages |wwill {'' if discord_bot.db.tag_channel else 'not '}|ninclude the originating channel."
)
return
if "list" in self.switches or not self.args:
# show all connections
if channel_list := discord_bot.db.channels:
table = self.styled_table(
"|wLink ID|n",
"|wEvennia|n",
"|wDiscord|n",
border="cells",
maxwidth=_DEFAULT_WIDTH,
)
# iterate through the channel links
# load in the pretty names for the discord channels from cache
dc_chan_names = discord_bot.attributes.get("discord_channels", {})
for i, (evchan, dcchan) in enumerate(channel_list):
dc_info = dc_chan_names.get(dcchan, {"name": dcchan, "guild": "unknown"})
table.add_row(
i, evchan, f"#{dc_info.get('name','?')}@{dc_info.get('guild','?')}"
)
self.msg(table)
else:
self.msg("No Discord connections found.")
return
if "disconnect" in self.switches or "remove" in self.switches or "delete" in self.switches:
if channel_list := discord_bot.db.channels:
try:
lid = int(self.args.strip())
except ValueError:
self.msg("Usage: discord2chan/remove <link id>")
return
if lid < len(channel_list):
ev_chan, dc_chan = discord_bot.db.channels.pop(lid)
dc_chan_names = discord_bot.attributes.get("discord_channels", {})
dc_info = dc_chan_names.get(dc_chan, {"name": "unknown", "guild": "unknown"})
self.msg(
f"Removed link between {ev_chan} and #{dc_info.get('name','?')}@{dc_info.get('guild','?')}"
)
return
else:
self.msg("There are no active connections to Discord.")
return
ev_channel = self.lhs
dc_channel = self.rhs
if ev_channel and not dc_channel:
# show all discord channels linked to self.lhs
if channel_list := discord_bot.db.channels:
table = self.styled_table(
"|wLink ID|n",
"|wEvennia|n",
"|wDiscord|n",
border="cells",
maxwidth=_DEFAULT_WIDTH,
)
# iterate through the channel links
# load in the pretty names for the discord channels from cache
dc_chan_names = discord_bot.attributes.get("discord_channels", {})
results = False
for i, (evchan, dcchan) in enumerate(channel_list):
if evchan.lower() == ev_channel.lower():
dc_info = dc_chan_names.get(dcchan, {"name": dcchan, "guild": "unknown"})
table.add_row(i, evchan, f"#{dc_info['name']}@{dc_info['guild']}")
results = True
if results:
self.msg(table)
else:
self.msg(f"There are no Discord channels connected to {ev_channel}.")
else:
self.msg("There are no active connections to Discord.")
return
# check if link already exists
if channel_list := discord_bot.db.channels:
if (ev_channel, dc_channel) in channel_list:
self.msg("Those channels are already linked.")
return
else:
discord_bot.db.channels = []
# create the new link
channel_obj = search.search_channel(ev_channel)
if not channel_obj:
self.msg(f"There is no channel '{ev_channel}'")
return
channel_obj = channel_obj[0]
discord_bot.db.channels.append((channel_obj.name, dc_channel))
channel_obj.connect(discord_bot)
if dc_chans := discord_bot.db.discord_channels:
dc_channel_name = dc_chans.get(dc_channel, {}).get("name", dc_channel)
else:
dc_channel_name = dc_channel
self.msg(f"Discord connection created: {channel_obj.name} <-> #{dc_channel_name}.")

View file

@ -2002,6 +2002,58 @@ class TestComms(BaseEvenniaCommandTest):
)
@override_settings(DISCORD_BOT_TOKEN="notarealtoken", DISCORD_ENABLED=True)
class TestDiscord(BaseEvenniaCommandTest):
def setUp(self):
super().setUp()
self.channel = create.create_channel(key="testchannel", desc="A test channel")
self.cmddiscord = cmd_comms.CmdDiscord2Chan
self.cmddiscord.account_caller = False
# create bot manually so it doesn't get started
self.discordbot = create.create_account(
"DiscordBot", None, None, typeclass="evennia.accounts.bots.DiscordBot"
)
def tearDown(self):
if self.channel.pk:
self.channel.delete()
@parameterized.expand(
[
("", "No Discord connections found."),
("/list", "No Discord connections found."),
("/guild", "Messages to Evennia will include the Discord server."),
("/channel", "Relayed messages will include the originating channel."),
]
)
def test_discord__switches(self, cmd_args, expected):
self.call(self.cmddiscord(), cmd_args, expected)
def test_discord__linking(self):
self.call(
self.cmddiscord(), "nosuchchannel = 5555555", "There is no channel 'nosuchchannel'"
)
self.call(
self.cmddiscord(),
"testchannel = 5555555",
"Discord connection created: testchannel <-> #5555555",
)
self.assertTrue(self.discordbot in self.channel.subscriptions.all())
self.assertTrue(("testchannel", "5555555") in self.discordbot.db.channels)
self.call(self.cmddiscord(), "testchannel = 5555555", "Those channels are already linked.")
def test_discord__list(self):
self.discordbot.db.channels = [("testchannel", "5555555")]
cmdobj = self.cmddiscord()
cmdobj.msg = lambda text, **kwargs: setattr(self, "out", str(text))
self.call(cmdobj, "", None)
self.assertIn("testchannel", self.out)
self.assertIn("5555555", self.out)
self.call(cmdobj, "testchannel", None)
self.assertIn("testchannel", self.out)
self.assertIn("5555555", self.out)
class TestBatchProcess(BaseEvenniaCommandTest):
"""
Test the batch processor.