summaryrefslogtreecommitdiffstats
path: root/web.py
diff options
context:
space:
mode:
authorMarius Halden <marius.h@lden.org>2016-01-11 18:01:30 +0100
committerMarius Halden <marius.h@lden.org>2016-01-11 18:01:30 +0100
commit82f748855794d15ef84e836e80077be9233d68d4 (patch)
treeb254d232bb604adda03e167ab3deecc51f4c88ce /web.py
downloadirc-sse-82f748855794d15ef84e836e80077be9233d68d4.tar.gz
irc-sse-82f748855794d15ef84e836e80077be9233d68d4.tar.bz2
irc-sse-82f748855794d15ef84e836e80077be9233d68d4.tar.xz
Initial commit
Diffstat (limited to 'web.py')
-rw-r--r--web.py349
1 files changed, 349 insertions, 0 deletions
diff --git a/web.py b/web.py
new file mode 100644
index 0000000..afff5a9
--- /dev/null
+++ b/web.py
@@ -0,0 +1,349 @@
+#!/usr/bin/env python
+
+import sys, time, yaml, os, json
+
+from twisted.internet import reactor, protocol, task
+#from twisted.internet import endpoints
+from twisted.web import server, resource, static
+from twisted.words.protocols import irc
+from twisted.python import log
+from crontab import CronTab
+
+config_file = "config.yaml"
+
+midnight = CronTab("0 0 * * *")
+log.startLogging(sys.stdout)
+
+class Config(object):
+ _cfg = None
+ _file = config_file
+
+ def __init__(self):
+ with open(self._file, 'r') as fp:
+ self._cfg = yaml.load(fp)
+ log.msg("Loaded config from file")
+
+ def save(self):
+ with open(self._file, 'w') as fp:
+ yaml.dump(self.cfg, fp)
+ log.msg("Dumped config to file")
+
+ @property
+ def cfg(self):
+ return self._cfg
+
+ @cfg.setter
+ def cfg(self, newCfg):
+ self._cfg = newCfg
+ self.save()
+
+config = Config()
+
+class Cache(object):
+ _cache = {}
+ _file = config.cfg.get("cache").get("file")
+ _max_log = config.cfg.get("cache").get("max_log")
+ _changed = False
+
+ def __init__(self):
+ if os.path.exists(self._file):
+ with open(self._file, 'r') as fp:
+ tmp = yaml.load(fp)
+ self._cache = tmp
+ log.msg("Loaded cache from file")
+
+ def setTopic(self, newTopic, channel):
+ if self._cache[channel]["topic"] != newTopic:
+ self._cache[channel]["topic"] = newTopic
+ self._changed = True
+
+ def addMsg(self, msg, channel):
+ if channel != None:
+ self._cache[channel]["msg"].append(msg)
+ self._changed = True
+ while len(self._cache[channel]["msg"]) > self._max_log:
+ self._cache[channel]["msg"].pop(0)
+ else:
+ for channel in self._cache:
+ self._cache[channel]["msg"].append(msg)
+ self._changed = True
+
+ while len(self._cache[channel]["msg"]) > self._max_log:
+ self._cache[channel]["msg"].pop(0)
+
+ def addChannel(self, channel):
+ if self._cache.get(channel, None) == None:
+ self._cache[channel] = {}
+ self._changed = True
+
+ if self._cache[channel].get("topic", None) == None:
+ self._cache[channel]["topic"] = ""
+ self._changed = True
+
+ if self._cache[channel].get("msg", None) == None:
+ self._cache[channel]["msg"] = []
+ self._changed = True
+
+ @property
+ def cache(self):
+ return self._cache
+
+ def save(self):
+ if self._changed:
+ with open(self._file, 'w') as fp:
+ yaml.dump(self._cache, fp)
+ log.msg("Dumped cache to file")
+ self._changed = False
+
+cache = Cache()
+
+class Root(resource.Resource):
+ def getChild(self, path, request):
+ if path == '':
+ return static.File("index.html")
+
+ return resource.Resource.getChild(self, path, request)
+ def render_GET(self, request):
+ log.msg(request)
+
+class Subscribe(resource.Resource):
+ isLeaf = True
+ def __init__(self):
+ self.subscribers = set()
+
+ def render_GET(self, request):
+ request.setHeader('Content-Type', 'text/event-stream; charset=utf-8')
+ request.setResponseCode(200)
+ self.subscribers.add(request)
+
+ channel = request.uri.split("?", 1)
+ if len(channel) != 2:
+ channel = None
+ else:
+ channel = "#%s" % channel[-1]
+
+ request.channel = channel
+
+ d = request.notifyFinish()
+ d.addBoth(self.removeSubscriber)
+
+ log.msg("Adding subscriber...")
+ request.write("")
+
+ return server.NOT_DONE_YET
+
+ def removeSubscriber(self, subscriber):
+ if subscriber in self.subscribers:
+ log.msg("Removing subscriber")
+ self.subscribers.remove(subscriber)
+
+ def publishToAll(self, data, channel=None):
+ for subscriber in self.subscribers:
+ if subscriber.channel == channel or channel == None:
+ for line in data.split('\n'):
+ subscriber.write("data: %s\n" % line)
+ subscriber.write("\n")
+
+subscribe = Subscribe()
+
+class Backlog(resource.Resource):
+ isLeaf = True
+ def render_GET(self, request):
+ channel = request.uri.split("?", 1)
+ if len(channel) != 2:
+ channel = None
+ else:
+ channel = channel[-1]
+
+ return json.dumps(cache.cache.get("#%s" % channel, {}))
+
+class IRCBotProtocol(irc.IRCClient):
+ admins = config.cfg.get("irc").get("admins")
+ nickname = config.cfg.get("irc").get("nick")
+
+ def connectionMade(self):
+ irc.IRCClient.connectionMade(self)
+ log.msg("Connected to IRC")
+
+ def signedOn(self):
+ for channel in config.cfg.get("irc").get("channels"):
+ log.msg("Joining %s" % channel)
+ self.join(channel)
+
+ def joined(self, channel):
+ log.msg("Joined %s" % channel)
+ cache.addChannel(channel)
+ #self.topic(channel, None)
+
+ def topicUpdated(self, user, channel, newTopic):
+ if channel == self.nickname:
+ return
+
+ msg = {
+ "type": "topic",
+ "user": user,
+ "channel": channel,
+ "time": time.strftime("%H:%M:%S", time.localtime(time.time())),
+ "date": time.strftime("%Y-%m-%d", time.localtime(time.time())),
+ "topic": newTopic,
+ }
+
+ #log.msg("%s %s Topic %s" % (user, channel, newTopic))
+ subscribe.publishToAll("%s" % json.dumps(msg), channel)
+ cache.setTopic(newTopic, channel)
+ cache.addMsg(msg, channel)
+
+ def privmsg(self, user, channel, message):
+ if channel == self.nickname:
+ if message == "!refresh" and user in self.admins:
+ subscribe.publishToAll("%s" % json.dumps({"type":"refresh"}), None)
+ return
+
+ msg = {
+ "type": "privmsg",
+ "user": user,
+ "channel": channel,
+ "time": time.strftime("%H:%M:%S", time.localtime(time.time())),
+ "date": time.strftime("%Y-%m-%d", time.localtime(time.time())),
+ "message": message,
+ }
+
+ log.msg("%s %s %s" % (user, channel, message))
+ subscribe.publishToAll("%s" % json.dumps(msg), channel)
+ cache.addMsg(msg, channel)
+
+ def action(self, user, channel, action):
+ if channel == self.nickname:
+ return
+
+ msg = {
+ "type": "action",
+ "user": user,
+ "channel": channel,
+ "time": time.strftime("%H:%M:%S", time.localtime(time.time())),
+ "date": time.strftime("%Y-%m-%d", time.localtime(time.time())),
+ "action": action,
+ }
+
+ #log.msg("%s %s %s" % (user, channel, action))
+ subscribe.publishToAll("%s" % json.dumps(msg), channel)
+ cache.addMsg(msg, channel)
+
+ def modeChanged(self, user, channel, set, modes, args):
+ if channel == self.nickname:
+ return
+
+ #log.msg("%s\n%s\n%s\n%s\n%s" % (user, channel, set, modes, list(args)))
+ msg = {
+ "type": "mode",
+ "user": user,
+ "channel": channel,
+ "time": time.strftime("%H:%M:%S", time.localtime(time.time())),
+ "date": time.strftime("%Y-%m-%d", time.localtime(time.time())),
+ "set": set,
+ "modes": modes,
+ "args": list(args),
+ }
+
+ subscribe.publishToAll("%s" % json.dumps(msg), channel)
+ cache.addMsg(msg, channel)
+
+ def userLeft(self, user, channel):
+ ### Keep track of users
+ if channel == self.nickname:
+ return
+
+ msg = {
+ "type": "leave",
+ "user": user,
+ "channel": channel,
+ "time": time.strftime("%H:%M:%S", time.localtime(time.time())),
+ "date": time.strftime("%Y-%m-%d", time.localtime(time.time())),
+ }
+
+ subscribe.publishToAll("%s" % json.dumps(msg), channel)
+ cache.addMsg(msg, channel)
+
+ def userJoined(self, user, channel):
+ ### Keep track of users
+ if channel == self.nickname:
+ return
+
+ msg = {
+ "type": "join",
+ "user": user,
+ "channel": channel,
+ "time": time.strftime("%H:%M:%S", time.localtime(time.time())),
+ "date": time.strftime("%Y-%m-%d", time.localtime(time.time())),
+ }
+
+ subscribe.publishToAll("%s" % json.dumps(msg), channel)
+ cache.addMsg(msg, channel)
+
+ def userQuit(self, user, quitMessage):
+ ### We need to keep track of names if we want to use this...
+ return
+
+ def userKicked(self, kickee, channel, kicker, message):
+ ### Keep track of users
+ msg = {
+ "type": "kick",
+ "kickee": kickee,
+ "channel": channel,
+ "kicker": kicker,
+ "message": message,
+ "time": time.strftime("%H:%M:%S", time.localtime(time.time())),
+ "date": time.strftime("%Y-%m-%d", time.localtime(time.time())),
+ }
+
+ subscribe.publishToAll("%s" % json.dumps(msg), channel)
+ cache.addMsg(msg, channel)
+
+ def userRenamed(self, oldname, newname):
+ ### We might need to keep track of users here to... :(
+ msg = {
+ "type": "rename",
+ "oldname": oldname,
+ "newname": newname,
+ "time": time.strftime("%H:%M:%S", time.localtime(time.time())),
+ "date": time.strftime("%Y-%m-%d", time.localtime(time.time())),
+ }
+
+ subscribe.publishToAll("%s" % json.dumps(msg), None)
+ cache.addMsg(msg, None)
+
+ def alterCollideNick(self, nick):
+ return nick + "^"
+
+class IRCBotFactory(protocol.ClientFactory):
+ protocol = IRCBotProtocol
+
+def day_change():
+ reactor.callLater(midnight.next(), day_change)
+
+ msg = {
+ "type": "daychange",
+ "time": time.strftime("%H:%M:%S", time.localtime(time.time())),
+ "date": time.strftime("%Y-%m-%d", time.localtime(time.time())),
+ }
+
+ log.msg("Day changed...")
+ cache.addMsg(msg, None)
+ subscribe.publishToAll("%s" % json.dumps(msg), None)
+
+root = Root()
+root.putChild("subscribe", subscribe)
+root.putChild("backlog", Backlog())
+root.putChild("style.css", static.File("style.css"))
+root.putChild("irc-sse.js", static.File("irc-sse.js"))
+site = server.Site(root)
+
+t = task.LoopingCall(cache.save)
+t.start(30)
+
+reactor.callLater(midnight.next(), day_change)
+reactor.addSystemEventTrigger("after", "shutdown", cache.save)
+reactor.listenTCP(config.cfg.get("http").get("port"), site)
+reactor.connectTCP(config.cfg.get("irc").get("server"), config.cfg.get("irc").get("port"), IRCBotFactory())
+reactor.run()
+