smooth-operator/logbot.py
2017-06-10 10:57:41 +02:00

375 lines
11 KiB
Python
Executable file

#!/usr/bin/env python
# coding: utf-8
"""
LogBot
A minimal IRC log bot
Written by Chris Oliver
Includes python-irclib from http://python-irclib.sourceforge.net/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""
__author__ = "Chris Oliver <excid3@gmail.com>"
__version__ = "0.4.2"
__date__ = "08/11/2009"
__copyright__ = "Copyright (c) Chris Oliver"
__license__ = "GPL2"
import cgi
import os
import ftplib
import sys
import itertools
from time import strftime
try:
from datetime import datetime
from pytz import timezone
except: pass
try:
from hashlib import md5
except:
import md5
from ircbot import SingleServerIRCBot
from irclib import nm_to_n
import time
from threading import Timer
import re
from pullrequest import PullRequest
from Db import *
from flask import *
import threading
pat1 = re.compile(r"(^|[\n ])(([\w]+?://[\w\#$%&~.\-;:=,?@\[\]+]*)(/[\w\#$%&~/.\-;:=,?@\[\]+]*)?)", re.IGNORECASE | re.DOTALL)
#urlfinder = re.compile("(?i)\b((?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'\".,<>?«»“”‘’]))")
flaskapp = Flask(__name__)
flaskapp.config["TEMPLATES_AUTO_RELOAD"] = True
@flaskapp.template_filter('md5')
def format_md5(value):
return md5(value).hexdigest()
def urlify2(value):
return pat1.sub(r'\1<a href="\2" target="_blank">\3</a>', value)
#return urlfinder.sub(r'<a href="\1">\1</a>', value)
### Configuration options
DEBUG = False
# IRC Server Configuration
SERVER = os.getenv("IRC_SERVER", "irc.freenode.net")
PORT = os.getenv("IRC_PORT", 6667)
SERVER_PASS = os.getenv("IRC_SERVER_PASS", None)
CHANNELS = os.getenv("IRC_CHANNELS", "#pelux").split(",")
NICK = os.getenv("IRC_NICK", "pelux")
NICK_PASS = os.getenv("IRC_NICK_PASS", "")
# The local folder to save logs
LOG_FOLDER = "/var/www/html/"
# The message returned when someone messages the bot
HELP_MESSAGE = "Check out http://pelux.io!"
# FTP Configuration
FTP_SERVER = ""
FTP_USER = ""
FTP_PASS = ""
# This folder and sub folders for any channels MUST be created on the server
FTP_FOLDER = ""
# The amount of messages to wait before uploading to the FTP server
FTP_WAIT = 25
DEFAULT_TIMEZONE = 'UTC'
### Web interface
@flaskapp.route("/search/nickname/<nickname>/")
@flaskapp.route("/search/channel/<channel>/")
@flaskapp.route("/search/")
def search(channel = None, nickname = None):
messages = []
query = request.args.get('query')
if channel:
try:
channel = Channel.get(Channel.name == channel)
messages = LogMessage.select() \
.where(LogMessage.channel == channel and \
LogMessage.message.contains(query))
except:
pass # No such channel
elif nickname:
messages = LogMessage.select() \
.where(LogMessage.nickname == nickname and \
LogMessage.message.contains(query))
else:
messages = LogMessage.select() \
.where(LogMessage.message.contains(query))
return render_template("messages.html",
messages=messages,
back_button=False,
date=True,
channel=channel)
@flaskapp.route("/channels/<channel>/")
@flaskapp.route("/channels/<channel>/<day>/")
def channel(channel, day = None, query = None):
channel = Channel.get(Channel.name == channel)
if day:
d = Day.get(Day.date == day)
messages = LogMessage.select() \
.where(LogMessage.day == d and \
LogMessage.channel == channel)
return render_template("messages.html",
messages=messages,
back_button=True,
date=False,
channel=channel)
else:
days = LogMessage.select(LogMessage.day).distinct()
days = [ day.day for day in days ]
return render_template("days.html", back_button=True, days=days, channel=channel)
@flaskapp.route("/")
@flaskapp.route("/channels/")
def channels():
return render_template("channels.html", back_button=True, channels = Channel.select())
### Helper functions
def append_line(filename, line):
data = open(filename, "rb").readlines()[:-2]
data += [line, "\n", "\n</body>", "\n</html>"]
write_lines(filename, data)
def write_lines(filename, lines):
f = open(filename, "wb")
f.writelines(lines)
f.close()
def write_string(filename, string):
f = open(filename, "wb")
f.write(string)
f.close()
color_pattern = re.compile(r'(\[\d{1,2}m)')
"Pattern that matches ANSI color codes and the text that follows"
def pairs(items):
"""
Return pairs from items
>>> list(pairs([1,2,3,4]))
[(1, 2), (3, 4)]
"""
items = iter(items)
while True:
yield next(items), next(items)
### Logbot class
class Logbot(SingleServerIRCBot):
def __init__(self, server, port, server_pass=None, channels=[],
nick="pelux", nick_pass=None):
SingleServerIRCBot.__init__(self,
[(server, port, server_pass)],
nick,
nick)
self.chans = [x.lower() for x in channels]
self.set_ftp()
self.nick_pass = nick_pass
print "Logbot %s" % __version__
print "Connecting to %s:%i..." % (server, port)
print "Press Ctrl-C to quit"
def quit(self):
self.connection.disconnect("Quitting...")
def color(self, user):
return "#%s" % md5(user).hexdigest()[:6]
def set_ftp(self, ftp=None):
self.ftp = ftp
def write_event(self, event_name, event, params={}):
target = event.target()
if event_name == "nick":
message = params["new"]
elif event_name == "kick":
message = "%s kicked %s from %s. Reason: %s" % (nm_to_n(params["kicker"]),
params["user"], params["channel"], params["reason"])
elif event_name == "mode":
message = "%s changed mode on %s: %s" % (params["giver"],
params["person"], params["modes"])
elif event_name == "quit":
target = params["chan"]
message = "%s has quit" % nm_to_n(event.source())
elif len(event.arguments()) > 0:
message = event.arguments()[0]
else:
message = ""
add_log_message(target,
nm_to_n(event.source()),
event_name,
message)
def check_for_prs(self, c):
p = PullRequest()
for line in p.check_all():
message = line["message"]
channel = line["channel"]
c.privmsg(channel, message)
time.sleep(1)
Timer(60*5, self.check_for_prs, [c]).start()
### These are the IRC events
def on_all_raw_messages(self, c, e):
"""Display all IRC connections in terminal"""
if DEBUG: print e.arguments()[0]
def on_welcome(self, c, e):
"""Join channels after successful connection"""
if self.nick_pass:
c.privmsg("nickserv", "identify %s" % self.nick_pass)
for chan in self.chans:
c.join(chan)
self.check_for_prs(c)
def on_nicknameinuse(self, c, e):
"""Nickname in use"""
c.nick(c.get_nickname() + "_")
def on_invite(self, c, e):
"""Arbitrarily join any channel invited to"""
c.join(e.arguments()[0])
#TODO: Save? Rewrite config file?
### Loggable events
def on_action(self, c, e):
"""Someone says /me"""
self.write_event("action", e)
def on_join(self, c, e):
self.write_event("join", e)
def on_kick(self, c, e):
self.write_event("kick", e,
{"kicker" : e.source(),
"channel" : e.target(),
"user" : e.arguments()[0],
"reason" : e.arguments()[1],
})
def on_mode(self, c, e):
self.write_event("mode", e,
{"modes" : e.arguments()[0],
"person" : e.arguments()[1] if len(e.arguments()) > 1 else e.target(),
"giver" : nm_to_n(e.source()),
})
def on_nick(self, c, e):
old_nick = nm_to_n(e.source())
# Only write the event on channels that actually had the user in the channel
for chan in self.channels:
if old_nick in [x.lstrip('~%&@+') for x in self.channels[chan].users()]:
self.write_event("nick", e,
{"old" : old_nick,
"new" : e.target(),
"chan": chan,
})
def on_part(self, c, e):
self.write_event("part", e)
def on_pubmsg(self, c, e):
# if e.arguments()[0].startswith(NICK):
# c.privmsg(e.target(), self.format["help"])
self.write_event("pubmsg", e)
def on_pubnotice(self, c, e):
self.write_event("pubnotice", e)
def on_privmsg(self, c, e):
# c.privmsg(nm_to_n(e.source()), self.format["help"])
pas
def on_quit(self, c, e):
nick = nm_to_n(e.source())
# Only write the event on channels that actually had the user in the channel
for chan in self.channels:
if nick in [x.lstrip('~%&@+') for x in self.channels[chan].users()]:
self.write_event("quit", e, {"chan" : chan})
def on_topic(self, c, e):
self.write_event("topic", e)
def connect_ftp():
print "Using FTP %s..." % (FTP_SERVER)
f = ftplib.FTP(FTP_SERVER, FTP_USER, FTP_PASS)
f.cwd(FTP_FOLDER)
return f
def main():
# Start up database
create_tables()
t = threading.Thread(target=flaskapp.run, kwargs={"host": "0.0.0.0"})
t.start()
# Create the logs directory
if not os.path.exists(LOG_FOLDER):
os.makedirs(LOG_FOLDER)
write_string("%s/index.html" % LOG_FOLDER, html_header.replace("%title%", "Chat Logs"))
# Start the bot
bot = Logbot(SERVER, PORT, SERVER_PASS, CHANNELS, NICK, NICK_PASS)
try:
# Connect to FTP
if FTP_SERVER:
bot.set_ftp(connect_ftp())
bot.start()
except KeyboardInterrupt:
if FTP_SERVER: bot.ftp.quit()
bot.quit()
t.join()
if __name__ == "__main__":
main()