diff options
Diffstat (limited to 'web.py')
-rw-r--r-- | web.py | 349 |
1 files changed, 349 insertions, 0 deletions
@@ -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() + |