diff --git a/Db.py b/Db.py
new file mode 100644
index 0000000..7e30ca0
--- /dev/null
+++ b/Db.py
@@ -0,0 +1,60 @@
+from peewee import *
+import datetime
+
+DATABASE = "irc.db"
+
+database = SqliteDatabase(DATABASE)
+
+class BaseModel(Model):
+ class Meta:
+ database = database
+
+class Day(BaseModel):
+ date = CharField()
+
+class Channel(BaseModel):
+ name = CharField(unique = True)
+
+class LogMessage(BaseModel):
+ day = ForeignKeyField(Day)
+ channel = ForeignKeyField(Channel)
+ nickname = CharField()
+ datetime = DateTimeField()
+ message_type = CharField()
+ message = CharField()
+
+
+def create_tables():
+ database.connect()
+ try:
+ database.create_tables([LogMessage, Day, Channel])
+ except OperationalError:
+ pass # Database already exists
+
+def get_current_day():
+ today = datetime.date.today()
+ try:
+ return Day.get(Day.date == today)
+ except:
+ Day.create(date = today)
+ return Day.get(Day.date == today)
+
+def get_channel(c):
+ try:
+ return Channel.get(Channel.name == c)
+ except:
+ Channel.create(name = c)
+ return Channel.get(Channel.name == c)
+
+def add_log_message(channel, nickname, message_type, message = None):
+ msg = LogMessage.create(
+ day = get_current_day(),
+ channel = get_channel(channel),
+ nickname = nickname,
+ datetime = datetime.datetime.now().strftime("%H:%m:%S"),
+ message_type = message_type,
+ message = message)
+
+def show_all_messages():
+ for message in LogMessage.select():
+ print "<%s> %s" % (message.nickname, message.message)
diff --git a/logbot.py b/logbot.py
index 2c51112..6abb562 100755
--- a/logbot.py
+++ b/logbot.py
@@ -56,11 +56,17 @@ 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
+
def urlify2(value):
return pat1.sub(r'\1\3', value)
#return urlfinder.sub(r'\1', value)
@@ -91,80 +97,63 @@ FTP_FOLDER = ""
# The amount of messages to wait before uploading to the FTP server
FTP_WAIT = 25
-CHANNEL_LOCATIONS_FILE = os.path.expanduser("~/.logbot-channel_locations.conf")
DEFAULT_TIMEZONE = 'UTC'
-default_format = {
- "help" : HELP_MESSAGE,
- "action" : '
%time% * %user% %message%
',
- "join" : '%time% -!- %user% [%host%] has joined %channel%
',
- "kick" : '%time% -!- %user% was kicked from %channel% by %kicker% [%reason%]
',
- "mode" : '%time% -!- mode/%channel% [%modes% %person%] by %giver%
',
- "nick" : '%time% %old% is now known as %new%
',
- "part" : '%time% -!- %user% [%host%] has parted %channel%
',
- "pubmsg" : '%time% <%user%> %message%
',
- "pubnotice" : '%time% -%user%:%channel%- %message%
',
- "quit" : '%time% -!- %user% has quit [%message%]
',
- "topic" : '%time% %user% changed topic of %channel% to: %message%
',
-}
+### Web interface
-html_header = """
-
-
-
- %title%
-
-
-
-
- %title%
- Back
-
-
-
-
-"""
+ return render_template("messages.html",
+ messages=messages,
+ back_button=False,
+ date=True,
+ channel=channel)
+
+@flaskapp.route("/channels//")
+@flaskapp.route("/channels///")
+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
@@ -198,58 +187,20 @@ def pairs(items):
while True:
yield next(items), next(items)
-def html_color(input):
- """
- >>> html_color("This is plain but [30m this is in color")
- 'This is plain but this is in color'
- >>> html_color("[32mtwo[37mcolors")
- 'twocolors'
- """
- first = []
- parts = color_pattern.split(input)
- if len(parts) % 2:
- # an odd number of parts occurred - first part is uncolored
- first = [parts.pop(0)]
- rest = itertools.starmap(replace_color, pairs(parts))
- return ''.join(itertools.chain(first, rest))
-
-def replace_color(code, text):
- code = code.lstrip('[').rstrip('m')
- colors = {
- '30': '000316',
- '31': 'aa0000',
- '32': '00aa00',
- '33': 'aa5500',
- '34': '0000aa',
- '35': 'E850A8',
- '36': '00aaaa',
- '37': 'F5F1DE',
- }
- if code not in colors:
- return text
- return '%(text)s' % dict(
- color = colors[code],
- text = text,
- )
-
-
### Logbot class
class Logbot(SingleServerIRCBot):
def __init__(self, server, port, server_pass=None, channels=[],
- nick="timber", nick_pass=None, format=default_format):
+ nick="pelux", nick_pass=None):
SingleServerIRCBot.__init__(self,
[(server, port, server_pass)],
nick,
nick)
self.chans = [x.lower() for x in channels]
- self.format = format
self.set_ftp()
- self.count = 0
self.nick_pass = nick_pass
- self.load_channel_locations()
print "Logbot %s" % __version__
print "Connecting to %s:%i..." % (server, port)
print "Press Ctrl-C to quit"
@@ -263,115 +214,24 @@ class Logbot(SingleServerIRCBot):
def set_ftp(self, ftp=None):
self.ftp = ftp
- def format_event(self, name, event, params):
- msg = self.format[name]
- for key, val in params.iteritems():
- msg = msg.replace(key, val)
-
- # Always replace %user% with e.source()
- # and %channel% with e.target()
- msg = msg.replace("%user%", nm_to_n(event.source()))
- msg = msg.replace("%host%", event.source())
- try: msg = msg.replace("%channel%", event.target())
- except: pass
- msg = msg.replace("%color%", self.color(nm_to_n(event.source())))
- try:
- user_message = cgi.escape(event.arguments()[0])
- msg = msg.replace("%message%", html_color(user_message))
- except: pass
-
- return msg
-
- def write_event(self, name, event, params={}):
- # Format the event properly
- if name == 'nick' or name == 'quit':
- chans = params["%chan%"]
+ def write_event(self, event_name, event, params={}):
+ 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 len(event.arguments()) > 0:
+ message = event.arguments()[0]
else:
- chans = event.target()
- msg = self.format_event(name, event, params)
- msg = urlify2(msg)
+ message = ""
- # In case there are still events that don't supply a channel name (like /quit and /nick did)
- if not chans or not chans.startswith("#"):
- chans = self.chans
- else:
- chans = [chans]
-
- for chan in chans:
- self.append_log_msg(chan, msg)
-
- self.count += 1
-
- if self.ftp and self.count > FTP_WAIT:
- self.count = 0
- print "Uploading to FTP..."
- for root, dirs, files in os.walk("logs"):
- #TODO: Create folders
-
- for fname in files:
- full_fname = os.path.join(root, fname)
-
- if sys.platform == 'win32':
- remote_fname = "/".join(full_fname.split("\\")[1:])
- else:
- remote_fname = "/".join(full_fname.split("/")[1:])
- if DEBUG: print repr(remote_fname)
-
- # Upload!
- try: self.ftp.storbinary("STOR %s" % remote_fname, open(full_fname, "rb"))
- # Folder doesn't exist, try creating it and storing again
- except ftplib.error_perm, e: #code, error = str(e).split(" ", 1)
- if str(e).split(" ", 1)[0] == "553":
- self.ftp.mkd(os.path.dirname(remote_fname))
- self.ftp.storbinary("STOR %s" % remote_fname, open(full_fname, "rb"))
- else: raise e
- # Reconnect on timeout
- except ftplib.error_temp, e: self.set_ftp(connect_ftp())
- # Unsure of error, try reconnecting
- except: self.set_ftp(connect_ftp())
-
- print "Finished uploading"
-
- def append_log_msg(self, channel, msg):
- print "%s >>> %s" % (channel, msg)
- #Make sure the channel is always lowercase to prevent logs with other capitalisations to be created
- channel_title = channel
- channel = channel.lower()
-
- # Create the channel path if necessary
- chan_path = "%s/%s" % (LOG_FOLDER, channel)
- if not os.path.exists(chan_path):
- os.makedirs(chan_path)
-
- # Create channel index
- write_string("%s/index.html" % chan_path, html_header.replace("%title%", "%s | Logs" % channel_title))
-
- # Append channel to log index
- append_line("%s/index.html" % LOG_FOLDER, '%s' % (channel.replace("#", "%23"), channel_title))
-
- # Current log
- try:
- localtime = datetime.now(timezone(self.channel_locations.get(channel,DEFAULT_TIMEZONE)))
- time = localtime.strftime("%H:%M:%S")
- date = localtime.strftime("%Y-%m-%d")
- except:
- time = strftime("%H:%M:%S")
- date = strftime("%Y-%m-%d")
-
- log_path = "%s/%s/%s.html" % (LOG_FOLDER, channel, date)
-
- # Create the log date index if it doesnt exist
- if not os.path.exists(log_path):
- write_string(log_path, html_header.replace("%title%", "%s | Logs for %s" % (channel_title, date)))
-
- # Append date log
- append_line("%s/index.html" % chan_path, '%s' % (date, date))
-
- # Append current message
- time = "[%s]" % \
- (time, time, time)
- msg = msg.replace("%time%", time)
- append_line(log_path, msg)
+ add_log_message(event.target(),
+ nm_to_n(event.source()),
+ event_name,
+ message)
def check_for_prs(self, c):
p = PullRequest()
@@ -420,17 +280,17 @@ class Logbot(SingleServerIRCBot):
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],
+ {"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()),
+ {"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):
@@ -439,25 +299,25 @@ class Logbot(SingleServerIRCBot):
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,
+ {"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"])
+# 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):
- print nm_to_n(e.source()), e.arguments()
- c.privmsg(nm_to_n(e.source()), self.format["help"])
+# c.privmsg(nm_to_n(e.source()), self.format["help"])
+ pas
def on_quit(self, c, e):
nick = nm_to_n(e.source())
@@ -469,14 +329,6 @@ class Logbot(SingleServerIRCBot):
def on_topic(self, c, e):
self.write_event("topic", e)
- # Loads the channel - timezone-location pairs from the CHANNEL_LOCATIONS_FILE
- # See the README for details and example
- def load_channel_locations(self):
- self.channel_locations = {}
- if os.path.exists(CHANNEL_LOCATIONS_FILE):
- f = open(CHANNEL_LOCATIONS_FILE, 'r')
- self.channel_locations = dict((k.lower(), v) for k, v in dict([line.strip().split(None,1) for line in f.readlines()]).iteritems())
-
def connect_ftp():
print "Using FTP %s..." % (FTP_SERVER)
f = ftplib.FTP(FTP_SERVER, FTP_USER, FTP_PASS)
@@ -484,6 +336,12 @@ def connect_ftp():
return f
def main():
+ # Start up database
+ create_tables()
+
+ t = threading.Thread(target=flaskapp.run, args=())
+ t.start()
+
# Create the logs directory
if not os.path.exists(LOG_FOLDER):
os.makedirs(LOG_FOLDER)
@@ -500,6 +358,7 @@ def main():
except KeyboardInterrupt:
if FTP_SERVER: bot.ftp.quit()
bot.quit()
+ t.join()
if __name__ == "__main__":
diff --git a/static/style.css b/static/style.css
new file mode 100644
index 0000000..9a44f50
--- /dev/null
+++ b/static/style.css
@@ -0,0 +1,49 @@
+body {
+ background-color: #F8F8FF;
+ font-family: Fixed, monospace;
+ font-size: 13px;
+}
+
+#searchform {
+ display: inline;
+}
+
+#navbar {
+ margin-bottom: 20px;
+}
+
+p {
+ margin: 0px;
+ padding: 0px;
+}
+
+p:target {
+ background-color:#ffcccc
+}
+
+p:target a {
+ color:red;
+}
+
+.join .marker {
+ color: blue;
+}
+
+.action .nick {
+ color: gray;
+ font-style: italic;
+}
+
+.action .message {
+ color: gray;
+ font-style: italic;
+}
+a:link {
+ color: black;
+ text-decoration: none;
+}
+
+a:focus {
+ color: red;
+}
+
diff --git a/templates/base.html b/templates/base.html
new file mode 100644
index 0000000..f476b04
--- /dev/null
+++ b/templates/base.html
@@ -0,0 +1,6 @@
+
+List of channels
+
+{% for channel in channels %}
+ {{ channel.name }}
+{% endfor %}
diff --git a/templates/channels.html b/templates/channels.html
new file mode 100644
index 0000000..903fff8
--- /dev/null
+++ b/templates/channels.html
@@ -0,0 +1,20 @@
+
+
+ List of channels
+
+
+
+
+ {% if back_button %}
+
Back
+ {% endif %}
+
+
+
+
+{% for channel in channels %}
+ {{ channel.name }}
+{% endfor %}
diff --git a/templates/days.html b/templates/days.html
new file mode 100644
index 0000000..2fdda00
--- /dev/null
+++ b/templates/days.html
@@ -0,0 +1,24 @@
+
+
+ Days with logs for {{ channel.name }}
+
+
+
+
+ {% if back_button %}
+
Back
+ {% endif %}
+
+
+
+
+{% for day in days %}
+
+
+ {{ day.date }}
+
+
+{% endfor %}
diff --git a/templates/messages.html b/templates/messages.html
new file mode 100644
index 0000000..610458d
--- /dev/null
+++ b/templates/messages.html
@@ -0,0 +1,186 @@
+
+
+ List of messages
+
+
+
+
+ {% if back_button %}
+
Back
+ {% endif %}
+
+
+
+
+{% for message in messages %}
+ {% if message.message_type == "pubmsg" %}
+
+
+
+ {% if date %}
+ {{ message.day.date }}
+ {% endif %}
+ {{message.datetime}}
+
+
+
+ <
+
+
+ {{ message.nickname }}
+
+
+ >
+
+
+ {{ message.message }}
+
+
+ {% elif message.message_type == "join" %}
+
+
+
+ {% if date %}
+ {{ message.day.date }}
+ {% endif %}
+ {{message.datetime}}
+
+
+
+ -!-
+
+
+ {{ message.nickname }} joined {{ message.channel.name }}
+
+
+ {% elif message.message_type == "nick" %}
+
+
+
+ {% if date %}
+ {{ message.day.date }}
+ {% endif %}
+ {{message.datetime}}
+
+
+
+ -!-
+
+
+ {{ message.nickname }}
+
+
+ is now known as
+
+
+ {{ message.message }}
+
+
+ {% elif message.message_type == "action" %}
+
+
+
+ {% if date %}
+ {{ message.day.date }}
+ {% endif %}
+ {{message.datetime}}
+
+
+
+ {{ message.nickname }}
+
+
+ {{ message.message }}
+
+
+ {% elif message.message_type == "kick" %}
+
+
+
+ {% if date %}
+ {{ message.day.date }}
+ {% endif %}
+ {{message.datetime}}
+
+
+
+ -!-
+
+
+ {{ message.message }}
+
+
+ {% elif message.message_type == "mode" %}
+
+
+
+ {% if date %}
+ {{ message.day.date }}
+ {% endif %}
+ {{message.datetime}}
+
+
+
+ -!-
+
+
+ {{ message.message }}
+
+
+ {% elif message.message_type == "part" %}
+
+
+
+ {% if date %}
+ {{ message.day.date }}
+ {% endif %}
+ {{message.datetime}}
+
+
+
+ {{ message.nickname }}
+
+
+ left channel
+
+
+ {{ message.channel.name }}
+
+
+ with reason:
+
+
+ {{ message.message }}
+
+
+ {% elif message.message_type == "topic" %}
+
+
+
+ {% if date %}
+ {{ message.day.date }}
+ {% endif %}
+ {{message.datetime}}
+
+
+
+ {{ message.nickname }}
+
+
+ changed topic of
+
+
+ {{ message.channel.name }}
+
+
+ to:
+
+
+ {{ message.message }}
+
+
+ {% endif %}
+{% endfor %}