Starting to implement grapewine support
This commit is contained in:
parent
addaacf5d0
commit
59bc475708
7 changed files with 212 additions and 12 deletions
|
|
@ -15,6 +15,8 @@ _IDLE_TIMEOUT = settings.IDLE_TIMEOUT
|
||||||
|
|
||||||
_IRC_ENABLED = settings.IRC_ENABLED
|
_IRC_ENABLED = settings.IRC_ENABLED
|
||||||
_RSS_ENABLED = settings.RSS_ENABLED
|
_RSS_ENABLED = settings.RSS_ENABLED
|
||||||
|
_GRAPEWINE_ENABLED = settings.GRAPEWINE_ENABLED
|
||||||
|
|
||||||
|
|
||||||
_SESSIONS = None
|
_SESSIONS = None
|
||||||
|
|
||||||
|
|
@ -424,3 +426,91 @@ class RSSBot(Bot):
|
||||||
self.ndb.ev_channel = self.db.ev_channel
|
self.ndb.ev_channel = self.db.ev_channel
|
||||||
if self.ndb.ev_channel:
|
if self.ndb.ev_channel:
|
||||||
self.ndb.ev_channel.msg(txt, senders=self.id)
|
self.ndb.ev_channel.msg(txt, senders=self.id)
|
||||||
|
|
||||||
|
|
||||||
|
# Grapewine bot
|
||||||
|
|
||||||
|
class GrapewineBot(Bot):
|
||||||
|
"""
|
||||||
|
A Grapewine (https://grapewine.haus) relayer. The channel to connect to is the first
|
||||||
|
name in the settings.GRAPEWINE_CHANNELS list.
|
||||||
|
|
||||||
|
"""
|
||||||
|
factory_path = "evennia.server.portal.grapewine.RestartingWebsocketServerFactory"
|
||||||
|
|
||||||
|
def start(self, ev_channel=None, grapewine_channel=None):
|
||||||
|
"""
|
||||||
|
Start by telling the portal to connect to the grapewine network.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not _GRAPEWINE_ENABLED:
|
||||||
|
self.delete()
|
||||||
|
return
|
||||||
|
|
||||||
|
global _SESSIONS
|
||||||
|
if not _SESSIONS:
|
||||||
|
from evennia.server.sessionhandler import SESSIONS as _SESSIONS
|
||||||
|
|
||||||
|
# connect to Evennia channel
|
||||||
|
if ev_channel:
|
||||||
|
# connect to Evennia channel
|
||||||
|
channel = search.channel_search(ev_channel)
|
||||||
|
if not channel:
|
||||||
|
raise RuntimeError("Evennia Channel '%s' not found." % ev_channel)
|
||||||
|
channel = channel[0]
|
||||||
|
channel.connect(self)
|
||||||
|
self.db.ev_channel = channel
|
||||||
|
|
||||||
|
if grapewine_channel:
|
||||||
|
self.db.grapewine_channel = grapewine_channel
|
||||||
|
|
||||||
|
# these will be made available as properties on the protocol factory
|
||||||
|
configdict = {"uid": self.dbid,
|
||||||
|
"grapewine_channel": self.db.grapewine_channel}
|
||||||
|
|
||||||
|
_SESSIONS.start_bot_session(self.factory_path, configdict)
|
||||||
|
|
||||||
|
def at_msg_send(self, **kwargs):
|
||||||
|
"Shortcut here or we can end up in infinite loop"
|
||||||
|
pass
|
||||||
|
|
||||||
|
def msg(self, text=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Takes text from connected channel (only).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text (str, optional): Incoming text from channel.
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
options (dict): Options dict with the following allowed keys:
|
||||||
|
- from_channel (str): dbid of a channel this text originated from.
|
||||||
|
- from_obj (list): list of objects sending this text.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from_obj = kwargs.get("from_obj", None)
|
||||||
|
options = kwargs.get("options", None) or {}
|
||||||
|
if not self.ndb.ev_channel and self.db.ev_channel:
|
||||||
|
# cache channel lookup
|
||||||
|
self.ndb.ev_channel = self.db.ev_channel
|
||||||
|
if ("from_channel" in options and text and
|
||||||
|
self.ndb.ev_channel.dbid == options["from_channel"]):
|
||||||
|
if not from_obj or from_obj != [self]:
|
||||||
|
# send outputfunc text(msg, chan, sender)
|
||||||
|
super().msg(text=(text, self.db.grapewine_channel, from_obj.key))
|
||||||
|
|
||||||
|
def execute_cmd(self, txt=None, session=None, event=None, grapewine_channel=None,
|
||||||
|
sender=None, game=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Take incoming data from protocol and send it to connected channel. This is
|
||||||
|
triggered by the bot_data_in Inputfunc.
|
||||||
|
"""
|
||||||
|
if event == "channels/broadcast":
|
||||||
|
# A private message to the bot - a command.
|
||||||
|
|
||||||
|
text = f"{sender}@{game}: {txt}"
|
||||||
|
|
||||||
|
if not self.ndb.ev_channel and self.db.ev_channel:
|
||||||
|
# simple cache of channel lookup
|
||||||
|
self.ndb.ev_channel = self.db.ev_channel
|
||||||
|
if self.ndb.ev_channel:
|
||||||
|
self.ndb.ev_channel.msg(text, senders=self)
|
||||||
|
|
|
||||||
|
|
@ -74,3 +74,4 @@ class AccountCmdSet(CmdSet):
|
||||||
self.add(comms.CmdIRC2Chan())
|
self.add(comms.CmdIRC2Chan())
|
||||||
self.add(comms.CmdIRCStatus())
|
self.add(comms.CmdIRCStatus())
|
||||||
self.add(comms.CmdRSS2Chan())
|
self.add(comms.CmdRSS2Chan())
|
||||||
|
self.add(comms.CmdGrapewine2Chan())
|
||||||
|
|
|
||||||
|
|
@ -816,7 +816,6 @@ def _list_bots(cmd):
|
||||||
"""
|
"""
|
||||||
ircbots = [bot for bot in AccountDB.objects.filter(db_is_bot=True, username__startswith="ircbot-")]
|
ircbots = [bot for bot in AccountDB.objects.filter(db_is_bot=True, username__startswith="ircbot-")]
|
||||||
if ircbots:
|
if ircbots:
|
||||||
from evennia.utils.evtable import EvTable
|
|
||||||
table = cmd.styled_table("|w#dbref|n", "|wbotname|n", "|wev-channel|n",
|
table = cmd.styled_table("|w#dbref|n", "|wbotname|n", "|wev-channel|n",
|
||||||
"|wirc-channel|n", "|wSSL|n", maxwidth=_DEFAULT_WIDTH)
|
"|wirc-channel|n", "|wSSL|n", maxwidth=_DEFAULT_WIDTH)
|
||||||
for ircbot in ircbots:
|
for ircbot in ircbots:
|
||||||
|
|
@ -829,7 +828,7 @@ def _list_bots(cmd):
|
||||||
|
|
||||||
class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
|
class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
link an evennia channel to an external IRC channel
|
Link an evennia channel to an external IRC channel
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
irc2chan[/switches] <evennia_channel> = <ircnetwork> <port> <#irchannel> <botname>[:typeclass]
|
irc2chan[/switches] <evennia_channel> = <ircnetwork> <port> <#irchannel> <botname>[:typeclass]
|
||||||
|
|
@ -924,9 +923,8 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
self.msg("Account '%s' already exists and is not a bot." % botname)
|
self.msg("Account '%s' already exists and is not a bot." % botname)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
password = hashlib.md5(bytes(str(time.time()), 'utf-8')).hexdigest()[:11]
|
|
||||||
try:
|
try:
|
||||||
bot = create.create_account(botname, None, password, typeclass=botclass)
|
bot = create.create_account(botname, None, None, typeclass=botclass)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
self.msg("|rError, could not create the bot:|n '%s'." % err)
|
self.msg("|rError, could not create the bot:|n '%s'." % err)
|
||||||
return
|
return
|
||||||
|
|
@ -1052,7 +1050,6 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
# show all connections
|
# show all connections
|
||||||
rssbots = [bot for bot in AccountDB.objects.filter(db_is_bot=True, username__startswith="rssbot-")]
|
rssbots = [bot for bot in AccountDB.objects.filter(db_is_bot=True, username__startswith="rssbot-")]
|
||||||
if rssbots:
|
if rssbots:
|
||||||
from evennia.utils.evtable import EvTable
|
|
||||||
table = self.styled_table("|wdbid|n", "|wupdate rate|n", "|wev-channel",
|
table = self.styled_table("|wdbid|n", "|wupdate rate|n", "|wev-channel",
|
||||||
"|wRSS feed URL|n", border="cells", maxwidth=_DEFAULT_WIDTH)
|
"|wRSS feed URL|n", border="cells", maxwidth=_DEFAULT_WIDTH)
|
||||||
for rssbot in rssbots:
|
for rssbot in rssbots:
|
||||||
|
|
@ -1083,7 +1080,6 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
url = self.rhs
|
url = self.rhs
|
||||||
|
|
||||||
botname = "rssbot-%s" % url
|
botname = "rssbot-%s" % url
|
||||||
# create a new bot
|
|
||||||
bot = AccountDB.objects.filter(username__iexact=botname)
|
bot = AccountDB.objects.filter(username__iexact=botname)
|
||||||
if bot:
|
if bot:
|
||||||
# re-use existing bot
|
# re-use existing bot
|
||||||
|
|
@ -1092,6 +1088,95 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
self.msg("Account '%s' already exists and is not a bot." % botname)
|
self.msg("Account '%s' already exists and is not a bot." % botname)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
# create a new bot
|
||||||
bot = create.create_account(botname, None, None, typeclass=bots.RSSBot)
|
bot = create.create_account(botname, None, None, typeclass=bots.RSSBot)
|
||||||
bot.start(ev_channel=channel, rss_url=url, rss_rate=10)
|
bot.start(ev_channel=channel, rss_url=url, rss_rate=10)
|
||||||
self.msg("RSS reporter created. Fetching RSS.")
|
self.msg("RSS reporter created. Fetching RSS.")
|
||||||
|
|
||||||
|
|
||||||
|
class CmdGrapewine2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
|
"""
|
||||||
|
Link an Evennia channel to an exteral Grapewine channel
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
grapewine2chan[/switches] <evennia_channel> = <grapewine_channel>
|
||||||
|
grapewine2chan/disconnect <connection #id>
|
||||||
|
|
||||||
|
Switches:
|
||||||
|
/list - (or no switch): show existing grapewine <-> Evennia
|
||||||
|
mappings and available grapewine chans
|
||||||
|
/remove - alias to disconnect
|
||||||
|
/delete - alias to disconnect
|
||||||
|
|
||||||
|
Example:
|
||||||
|
grapewine2chan mygrapewine = gossip
|
||||||
|
|
||||||
|
This creates a link between an in-game Evennia channel and an external
|
||||||
|
Grapewine channel. The game must be registered with the Grapewine network
|
||||||
|
(register at https://grapewine.haus) and the GRAPEWINE_* auth information
|
||||||
|
must be added to game settings.
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = "grapewine2chan"
|
||||||
|
switch_options = ("disconnect", "remove", "delete", "list")
|
||||||
|
locks = "cmd:serversetting(GRAPEWINE_ENABLED) and pperm(Developer)"
|
||||||
|
help_category = "Comms"
|
||||||
|
|
||||||
|
def func(self):
|
||||||
|
"""Setup the Grapewine channel mapping"""
|
||||||
|
|
||||||
|
if not settings.GRAPEWINE_ENABLED:
|
||||||
|
self.msg("Set GRAPEWINE_ENABLED=True in settings to enable.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if "list" in self.switches:
|
||||||
|
# show all connections
|
||||||
|
gwbots = [bot for bot in
|
||||||
|
AccountDB.objects.filter(db_is_bot=True,
|
||||||
|
username__startswith="grapewinebot-")]
|
||||||
|
if gwbots:
|
||||||
|
table = self.styled_table("|wdbid|n", "|wev-channel",
|
||||||
|
"|wgw-channel|n", border="cells", maxwidth=_DEFAULT_WIDTH)
|
||||||
|
for gwbot in gwbots:
|
||||||
|
table.add_row(gwbot.id, gwbot.db.ev_channel, gwbot.db.grapewine_channel)
|
||||||
|
self.msg(table)
|
||||||
|
else:
|
||||||
|
self.msg("No grapewine bots found.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches:
|
||||||
|
botname = "grapewinebot-%s" % self.lhs
|
||||||
|
matches = AccountDB.objects.filter(db_is_bot=True, db_key=botname)
|
||||||
|
|
||||||
|
if not matches:
|
||||||
|
# try dbref match
|
||||||
|
matches = AccountDB.objects.filter(db_is_bot=True, id=self.args.lstrip("#"))
|
||||||
|
if matches:
|
||||||
|
matches[0].delete()
|
||||||
|
self.msg("Grapewine connection destroyed.")
|
||||||
|
else:
|
||||||
|
self.msg("Grapewine connection/bot could not be removed, does it exist?")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.args or not self.rhs:
|
||||||
|
string = "Usage: grapewine2chan[/switches] <evennia_channel> = <grapewine_channel>"
|
||||||
|
self.msg(string)
|
||||||
|
return
|
||||||
|
|
||||||
|
channel = self.lhs
|
||||||
|
grapewine_channel = self.rhs
|
||||||
|
|
||||||
|
botname = "grapewinebot-%s-%s" % (channel, grapewine_channel)
|
||||||
|
bot = AccountDB.objects.filter(username__iexact=botname)
|
||||||
|
if bot:
|
||||||
|
# re-use existing bot
|
||||||
|
bot = bot[0]
|
||||||
|
if not bot.is_bot:
|
||||||
|
self.msg("Account '%s' already exists and is not a bot." % botname)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# create a new bot
|
||||||
|
bot = create.create_account(botname, None, None, typeclass=bots.GrapewineBot)
|
||||||
|
|
||||||
|
bot.start(ev_channel=channel, grapewine_channel=grapewine_channel)
|
||||||
|
self.msg(f"Grapewine connection created {channel} <-> {grapewine_channel}.")
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ class WebSocketClient(WebSocketServerProtocol, Session):
|
||||||
Implements the server-side of the Websocket connection.
|
Implements the server-side of the Websocket connection.
|
||||||
"""
|
"""
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(WebSocketClient, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.protocol_key = "webclient/websocket"
|
self.protocol_key = "webclient/websocket"
|
||||||
|
|
||||||
def get_client_session(self):
|
def get_client_session(self):
|
||||||
|
|
@ -167,7 +167,7 @@ class WebSocketClient(WebSocketServerProtocol, Session):
|
||||||
"""
|
"""
|
||||||
Data User > Evennia.
|
Data User > Evennia.
|
||||||
|
|
||||||
Args::
|
Args:
|
||||||
text (str): Incoming text.
|
text (str): Incoming text.
|
||||||
kwargs (any): Options from protocol.
|
kwargs (any): Options from protocol.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ GUEST_ENABLED = settings.GUEST_ENABLED
|
||||||
WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS and WEBSERVER_INTERFACES
|
WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS and WEBSERVER_INTERFACES
|
||||||
IRC_ENABLED = settings.IRC_ENABLED
|
IRC_ENABLED = settings.IRC_ENABLED
|
||||||
RSS_ENABLED = settings.RSS_ENABLED
|
RSS_ENABLED = settings.RSS_ENABLED
|
||||||
|
GRAPEWINE_ENABLED = settings.GRAPEWINE_ENABLED
|
||||||
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
|
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
|
||||||
|
|
||||||
INFO_DICT = {"servername": SERVERNAME, "version": VERSION,
|
INFO_DICT = {"servername": SERVERNAME, "version": VERSION,
|
||||||
|
|
@ -583,6 +584,10 @@ if RSS_ENABLED:
|
||||||
# RSS feed channel connections
|
# RSS feed channel connections
|
||||||
ENABLED.append('rss')
|
ENABLED.append('rss')
|
||||||
|
|
||||||
|
if GRAPEWINE_ENABLED:
|
||||||
|
# Grapewine channel connections
|
||||||
|
ENABLED.append('grapewine')
|
||||||
|
|
||||||
if ENABLED:
|
if ENABLED:
|
||||||
INFO_DICT["irc_rss"] = ", ".join(ENABLED) + " enabled."
|
INFO_DICT["irc_rss"] = ", ".join(ENABLED) + " enabled."
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -706,6 +706,22 @@ IRC_ENABLED = False
|
||||||
RSS_ENABLED = False
|
RSS_ENABLED = False
|
||||||
RSS_UPDATE_INTERVAL = 60 * 10 # 10 minutes
|
RSS_UPDATE_INTERVAL = 60 * 10 # 10 minutes
|
||||||
|
|
||||||
|
# Grapewine (grapewine.haus) is a network for listing MUDs as well as allow
|
||||||
|
# users of said MUDs to communicate with each other on shared channels. To use,
|
||||||
|
# your game must first be registered by logging in and creating a game entry at
|
||||||
|
# https://grapewine.haus. Evennia links grapewine channels to in-game channels
|
||||||
|
# with the @grapewine2chan command, available once this flag is set
|
||||||
|
# Grapewine requires installing the pyopenssl library (pip install pyopenssl)
|
||||||
|
GRAPEWINE_ENABLED = False
|
||||||
|
# Grapewine channels to allow connection to. See https://grapevine.haus/chat
|
||||||
|
# for the available channels. Only channels in this list can be linked to in-game
|
||||||
|
# channels later.
|
||||||
|
GRAPEWINE_CHANNELS = ["gossip", "testing"]
|
||||||
|
# Grapewine authentication. Register your game at https://grapewine.haus to get
|
||||||
|
# them. These are secret and should thus be overridden in secret_settings file
|
||||||
|
GRAPEWINE_CLIENT_ID = ""
|
||||||
|
GRAPEWINE_CLIENT_SECRET = ""
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Django web features
|
# Django web features
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
|
||||||
|
|
@ -469,11 +469,14 @@ def create_account(key, email, password,
|
||||||
new_account = typeclass(username=key, email=email,
|
new_account = typeclass(username=key, email=email,
|
||||||
is_staff=is_superuser, is_superuser=is_superuser,
|
is_staff=is_superuser, is_superuser=is_superuser,
|
||||||
last_login=now, date_joined=now)
|
last_login=now, date_joined=now)
|
||||||
valid, error = new_account.validate_password(password, new_account)
|
if password is not None:
|
||||||
if not valid:
|
# the password may be None for 'fake' accounts, like bots
|
||||||
raise error
|
valid, error = new_account.validate_password(password, new_account)
|
||||||
|
if not valid:
|
||||||
|
raise error
|
||||||
|
|
||||||
|
new_account.set_password(password)
|
||||||
|
|
||||||
new_account.set_password(password)
|
|
||||||
new_account._createdict = dict(locks=locks, permissions=permissions, report_to=report_to,
|
new_account._createdict = dict(locks=locks, permissions=permissions, report_to=report_to,
|
||||||
tags=tags, attributes=attributes)
|
tags=tags, attributes=attributes)
|
||||||
# saving will trigger the signal that calls the
|
# saving will trigger the signal that calls the
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue