mirror of
https://github.com/evennia/evennia.git
synced 2026-03-18 22:06:30 +01:00
Added RSS feed support to Evennia. This uses the @rss2chan command to tie a feed to an in-game channel. Updates to the feed will henceforth be echoed to the channel. The reader requires the python-feedreader package to be installed.
This commit is contained in:
parent
b2d7f37e9c
commit
cdab5a240b
8 changed files with 286 additions and 25 deletions
|
|
@ -1,9 +1,11 @@
|
|||
"""
|
||||
This module ties together all the commands of the default command set.
|
||||
This module ties together all the commands of the default command
|
||||
set. Note that some commands, such as communication-commands are
|
||||
instead put in the OOC cmdset.
|
||||
"""
|
||||
from src.commands.cmdset import CmdSet
|
||||
from src.commands.default import general, help, admin, system
|
||||
from src.commands.default import comms, building
|
||||
from src.commands.default import building
|
||||
from src.commands.default import batchprocess
|
||||
|
||||
class DefaultCmdSet(CmdSet):
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
"""
|
||||
|
||||
This is the cmdset for OutOfCharacter (OOC) commands.
|
||||
These are stored on the Player object and should
|
||||
thus be able to handle getting a Player object
|
||||
as caller rather than a Character.
|
||||
This is the cmdset for OutOfCharacter (OOC) commands. These are
|
||||
stored on the Player object and should thus be able to handle getting
|
||||
a Player object as caller rather than a Character.
|
||||
|
||||
"""
|
||||
from src.commands.cmdset import CmdSet
|
||||
|
|
@ -53,3 +52,4 @@ class OOCCmdSet(CmdSet):
|
|||
self.add(comms.CmdIMC2Chan())
|
||||
self.add(comms.CmdIMCInfo())
|
||||
self.add(comms.CmdIMCTell())
|
||||
self.add(comms.CmdRSS2Chan())
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ for easy handling.
|
|||
"""
|
||||
from django.conf import settings
|
||||
from src.comms.models import Channel, Msg, PlayerChannelConnection, ExternalChannelConnection
|
||||
from src.comms import irc, imc2
|
||||
from src.comms import irc, imc2, rss
|
||||
from src.comms.channelhandler import CHANNELHANDLER
|
||||
from src.utils import create, utils
|
||||
from src.commands.default.muxcommand import MuxCommand
|
||||
|
|
@ -1078,3 +1078,91 @@ class CmdIMCTell(MuxCommand):
|
|||
IMC2_CLIENT.msg_imc2(message, from_obj=self.caller, packet_type="imctell", data=data)
|
||||
|
||||
self.caller.msg("You paged {c%s@%s{n (over IMC): '%s'." % (target, destination, message))
|
||||
|
||||
|
||||
# RSS connection
|
||||
class CmdRSS2Chan(MuxCommand):
|
||||
"""
|
||||
@rss2chan - link evennia channel to an RSS feed
|
||||
|
||||
Usage:
|
||||
@rss2chan[/switches] <evennia_channel> = <rss_url>
|
||||
|
||||
Switches:
|
||||
/disconnect - this will stop the feed and remove the connection to the channel.
|
||||
/remove - "
|
||||
/list - show all rss->evennia mappings
|
||||
|
||||
Example:
|
||||
@rss2chan rsschan = http://code.google.com/feeds/p/evennia/updates/basic
|
||||
|
||||
This creates an RSS reader that connects to a given RSS feed url. Updates will be
|
||||
echoed as a title and news link to the given channel. The rate of updating is set
|
||||
with the RSS_UPDATE_INTERVAL variable in settings (default is every 10 minutes).
|
||||
|
||||
When disconnecting you need to supply both the channel and url again so as to identify
|
||||
the connection uniquely.
|
||||
"""
|
||||
|
||||
key = "@rss2chan"
|
||||
locks = "cmd:serversetting(RSS_ENABLED) and pperm(Immortals)"
|
||||
help_category = "Comms"
|
||||
|
||||
def func(self):
|
||||
"Setup the rss-channel mapping"
|
||||
|
||||
if not settings.RSS_ENABLED:
|
||||
string = """RSS is not enabled. You need to activate it in game/settings.py."""
|
||||
self.caller.msg(string)
|
||||
return
|
||||
|
||||
if 'list' in self.switches:
|
||||
# show all connections
|
||||
connections = ExternalChannelConnection.objects.filter(db_external_key__startswith='rss_')
|
||||
if connections:
|
||||
cols = [["Evennia-channel"], ["RSS-url"]]
|
||||
for conn in connections:
|
||||
cols[0].append(conn.channel.key)
|
||||
cols[1].append(conn.external_config.split('|')[0])
|
||||
ftable = utils.format_table(cols)
|
||||
string = ""
|
||||
for ir, row in enumerate(ftable):
|
||||
if ir == 0:
|
||||
string += "{w%s{n" % "".join(row)
|
||||
else:
|
||||
string += "\n" + "".join(row)
|
||||
self.caller.msg(string)
|
||||
else:
|
||||
self.caller.msg("No connections found.")
|
||||
return
|
||||
|
||||
if not self.args or not self.rhs:
|
||||
string = "Usage: @rss2chan[/switches] <evennia_channel> = <rss url>"
|
||||
self.caller.msg(string)
|
||||
return
|
||||
channel = self.lhs
|
||||
url = self.rhs
|
||||
|
||||
if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches:
|
||||
chanmatch = find_channel(self.caller, channel, silent=True)
|
||||
if chanmatch:
|
||||
channel = chanmatch.key
|
||||
|
||||
ok = rss.delete_connection(channel, url)
|
||||
if not ok:
|
||||
self.caller.msg("RSS connection/reader could not be removed, does it exist?")
|
||||
else:
|
||||
self.caller.msg("RSS connection destroyed.")
|
||||
return
|
||||
|
||||
channel = find_channel(self.caller, channel)
|
||||
if not channel:
|
||||
return
|
||||
interval = settings.RSS_UPDATE_INTERVAL
|
||||
if not interval:
|
||||
interval = 10*60
|
||||
ok = rss.create_connection(channel, url, interval)
|
||||
if not ok:
|
||||
self.caller.msg("This RSS connection already exists.")
|
||||
return
|
||||
self.caller.msg("Connection created. Starting RSS reader.")
|
||||
|
|
|
|||
|
|
@ -174,8 +174,6 @@ def connect_to_irc(connection):
|
|||
def connect_all():
|
||||
"""
|
||||
Activate all irc bots.
|
||||
|
||||
Returns a list of (key, TCPClient) tuples for server to properly set services.
|
||||
"""
|
||||
for connection in ExternalChannelConnection.objects.filter(db_external_key__startswith='irc_'):
|
||||
connect_to_irc(connection)
|
||||
|
|
|
|||
156
src/comms/rss.py
Normal file
156
src/comms/rss.py
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
"""
|
||||
RSS parser for Evennia
|
||||
|
||||
This connects an RSS feed to an in-game Evennia channel, sending messages
|
||||
to the channel whenever the feed updates.
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
from twisted.internet import task
|
||||
from django.conf import settings
|
||||
from src.comms.models import ExternalChannelConnection, Channel
|
||||
from src.utils import logger, utils
|
||||
from src.scripts.models import ScriptDB
|
||||
|
||||
RSS_ENABLED = settings.RSS_ENABLED
|
||||
RSS_UPDATE_INTERVAL = settings.RSS_UPDATE_INTERVAL
|
||||
INFOCHANNEL = Channel.objects.channel_search(settings.CHANNEL_MUDINFO[0])
|
||||
RETAG = re.compile(r'<[^>]*?>')
|
||||
|
||||
# holds rss readers they can be shut down at will.
|
||||
RSS_READERS = {}
|
||||
|
||||
def msg_info(message):
|
||||
"""
|
||||
Send info to default info channel
|
||||
"""
|
||||
message = '[%s][RSS]: %s' % (INFOCHANNEL[0].key, message)
|
||||
try:
|
||||
INFOCHANNEL[0].msg(message)
|
||||
except AttributeError:
|
||||
logger.log_infomsg("MUDinfo (rss): %s" % message)
|
||||
|
||||
if RSS_ENABLED:
|
||||
try:
|
||||
import feedparser
|
||||
except ImportError:
|
||||
raise ImportError("RSS requirs python-feedparser to be installed. Install or set RSS_ENABLED=False.")
|
||||
|
||||
class RSSReader(object):
|
||||
"""
|
||||
Reader script used to connect to each individual RSS feed
|
||||
"""
|
||||
def __init__(self, key, url, interval):
|
||||
"""
|
||||
The reader needs an rss url and It also needs an interval
|
||||
for how often it is to check for new updates (defaults
|
||||
to 10 minutes)
|
||||
"""
|
||||
self.key = key
|
||||
self.url = url
|
||||
self.interval = interval
|
||||
self.entries = {} # stored feeds
|
||||
self.task = None
|
||||
# first we do is to load the feed so we don't resend
|
||||
# old entries whenever the reader starts.
|
||||
self.update_feed()
|
||||
# start runner
|
||||
self.start()
|
||||
|
||||
def update_feed(self):
|
||||
"Read the url for new updated data and determine what's new."
|
||||
feed = feedparser.parse(self.url)
|
||||
new = []
|
||||
for entry in (e for e in feed['entries'] if e['id'] not in self.entries):
|
||||
txt = "[RSS] %s: %s" % (RETAG.sub("", entry['title']), entry['link'].replace('\n','').encode('utf-8'))
|
||||
self.entries[entry['id']] = txt
|
||||
new.append(txt)
|
||||
return new
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
Called every self.interval seconds - tries to get new feed entries,
|
||||
and if so, uses the appropriate ExternalChannelConnection to send the
|
||||
data to subscribing channels.
|
||||
"""
|
||||
new = self.update_feed()
|
||||
if not new:
|
||||
return
|
||||
conns = ExternalChannelConnection.objects.filter(db_external_key=self.key)
|
||||
for conn in (conn for conn in conns if conn.channel):
|
||||
for txt in new:
|
||||
conn.to_channel("%s:%s" % (conn.channel.key, txt))
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Starting the update task and store a reference in the
|
||||
global variable so it can be found and shut down later.
|
||||
"""
|
||||
global RSS_READERS
|
||||
self.task = task.LoopingCall(self.update)
|
||||
self.task.start(self.interval, now=False)
|
||||
RSS_READERS[self.key] = self
|
||||
|
||||
def build_connection_key(channel, url):
|
||||
"This is used to id the connection"
|
||||
if hasattr(channel, 'key'):
|
||||
channel = channel.key
|
||||
return "rss_%s>%s" % (url, channel)
|
||||
|
||||
def create_connection(channel, url, interval):
|
||||
"""
|
||||
This will create a new RSS->channel connection
|
||||
"""
|
||||
if not type(channel) == Channel:
|
||||
new_channel = Channel.objects.filter(db_key=channel)
|
||||
if not new_channel:
|
||||
logger.log_errmsg("Cannot attach RSS->Evennia: Evennia Channel '%s' not found." % channel)
|
||||
return False
|
||||
channel = new_channel[0]
|
||||
key = build_connection_key(channel, url)
|
||||
old_conns = ExternalChannelConnection.objects.filter(db_external_key=key)
|
||||
if old_conns:
|
||||
return False
|
||||
config = "%s|%i" % (url, interval)
|
||||
# There is no sendback from evennia to the rss, so we need not define any sendback code.
|
||||
conn = ExternalChannelConnection(db_channel=channel, db_external_key=key, db_external_config=config)
|
||||
conn.save()
|
||||
|
||||
connect_to_rss(conn)
|
||||
return True
|
||||
|
||||
def delete_connection(channel, url):
|
||||
"""
|
||||
Delete rss connection between channel and url
|
||||
"""
|
||||
key = build_connection_key(channel, url)
|
||||
try:
|
||||
conn = ExternalChannelConnection.objects.get(db_external_key=key)
|
||||
except Exception:
|
||||
return False
|
||||
conn.delete()
|
||||
reader = RSS_READERS.get(key, None)
|
||||
if reader and reader.task:
|
||||
reader.task.stop()
|
||||
return True
|
||||
|
||||
def connect_to_rss(connection):
|
||||
"""
|
||||
Create the parser instance and connect to RSS feed and channel
|
||||
"""
|
||||
global RSS_READERS
|
||||
key = utils.to_str(connection.external_key)
|
||||
url, interval = [utils.to_str(conf) for conf in connection.external_config.split('|')]
|
||||
# Create reader (this starts the running task and stores a reference in RSS_TASKS)
|
||||
RSSReader(key, url, int(interval))
|
||||
|
||||
def connect_all():
|
||||
"""
|
||||
Activate all rss feed parsers
|
||||
"""
|
||||
if not RSS_ENABLED:
|
||||
return
|
||||
for connection in ExternalChannelConnection.objects.filter(db_external_key__startswith="rss_"):
|
||||
print "connecting RSS: %s" % connection
|
||||
connect_to_rss(connection)
|
||||
|
|
@ -54,8 +54,6 @@ SSL_ENABLED = settings.SSL_ENABLED and SSL_PORTS and SSL_INTERFACES
|
|||
SSH_ENABLED = settings.SSH_ENABLED and SSH_PORTS and SSH_INTERFACES
|
||||
WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS and WEBSERVER_INTERFACES
|
||||
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
|
||||
IMC2_ENABLED = settings.IMC2_ENABLED
|
||||
IRC_ENABLED = settings.IRC_ENABLED
|
||||
|
||||
AMP_HOST = settings.AMP_HOST
|
||||
AMP_PORT = settings.AMP_PORT
|
||||
|
|
@ -283,20 +281,6 @@ if WEBSERVER_ENABLED:
|
|||
webserver.setName('EvenniaWebServer%s' % pstring)
|
||||
PORTAL.services.addService(webserver)
|
||||
|
||||
if IRC_ENABLED:
|
||||
|
||||
# IRC channel connections
|
||||
|
||||
from src.comms import irc
|
||||
irc.connect_all()
|
||||
|
||||
if IMC2_ENABLED:
|
||||
|
||||
# IMC2 channel connections
|
||||
|
||||
from src.comms import imc2
|
||||
imc2.connect_all()
|
||||
|
||||
if os.name == 'nt':
|
||||
# Windows only: Set PID file manually
|
||||
f = open(os.path.join(settings.GAME_DIR, 'portal.pid'), 'w')
|
||||
|
|
|
|||
|
|
@ -51,6 +51,10 @@ AMP_ENABLED = True
|
|||
AMP_HOST = settings.AMP_HOST
|
||||
AMP_PORT = settings.AMP_PORT
|
||||
|
||||
# server-channel mappings
|
||||
IMC2_ENABLED = settings.IMC2_ENABLED
|
||||
IRC_ENABLED = settings.IRC_ENABLED
|
||||
RSS_ENABLED = settings.RSS_ENABLED
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Evennia Main Server object
|
||||
|
|
@ -258,6 +262,27 @@ if AMP_ENABLED:
|
|||
amp_service.setName("EvenniaPortal")
|
||||
EVENNIA.services.addService(amp_service)
|
||||
|
||||
|
||||
if IRC_ENABLED:
|
||||
|
||||
# IRC channel connections
|
||||
|
||||
from src.comms import irc
|
||||
irc.connect_all()
|
||||
|
||||
if IMC2_ENABLED:
|
||||
|
||||
# IMC2 channel connections
|
||||
|
||||
from src.comms import imc2
|
||||
imc2.connect_all()
|
||||
|
||||
if RSS_ENABLED:
|
||||
|
||||
# RSS feed channel connections
|
||||
from src.comms import rss
|
||||
rss.connect_all()
|
||||
|
||||
# clear server startup mode
|
||||
ServerConfig.objects.conf("server_starting_mode", delete=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -307,6 +307,14 @@ IMC2_NETWORK = "server01.mudbytes.net"
|
|||
IMC2_PORT = 5000
|
||||
IMC2_CLIENT_PWD = ""
|
||||
IMC2_SERVER_PWD = ""
|
||||
# RSS allows to connect RSS feeds (from forum updates, blogs etc) to
|
||||
# an in-game channel. The channel will be updated when the rss feed
|
||||
# updates. Use @rss2chan in game to connect if this setting is
|
||||
# active. OBS: RSS support requires the python-feedparser package to
|
||||
# be installed (through package manager or from the website
|
||||
# http://code.google.com/p/feedparser/)
|
||||
RSS_ENABLED=False
|
||||
RSS_UPDATE_INTERVAL = 60*10 # 10 minutes
|
||||
|
||||
###################################################
|
||||
# Config for Django web features
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue