muc log, updated example config
authorStiletto <blasux@blasux.ru>
Tue, 23 Oct 2012 23:50:28 +0400
changeset 3 dd7a02c6d476
parent 2 4e050075fab9
child 4 8785de25b6bd
muc log, updated example config
Makefile
config.ini.example
iswydt.vala
log_head.html
logs/script.js
logs/style.css
muc.vala
muc_log.vala
--- a/Makefile	Tue Oct 23 18:33:49 2012 +0400
+++ b/Makefile	Tue Oct 23 23:50:28 2012 +0400
@@ -1,10 +1,10 @@
 all: iswydt-bot
 
 VALAC = valac
-LIBS      := gee-1.0 loudmouth-1.0
+LIBS      := gee-1.0 loudmouth-1.0 posix
 VALALIBS  := $(patsubst %, --pkg %, $(LIBS))
-VFLAGS = 
+VFLAGS = -g
 
-iswydt-bot: iswydt.vala config.vala muc.vala
+iswydt-bot: iswydt.vala config.vala muc.vala muc_log.vala
 	$(VALAC) $(VFLAGS) $(VALALIBS) -o $@ $^
 
--- a/config.ini.example	Tue Oct 23 18:33:49 2012 +0400
+++ b/config.ini.example	Tue Oct 23 23:50:28 2012 +0400
@@ -1,4 +1,4 @@
-[default]
+[server]
 jid=fuck@example.com
 password=shit
 server=talk.example.com
@@ -6,3 +6,12 @@
 port=5222
 ssl=yes
 
+[muc]
+mucs=example1@conference.blasux.ru example2@conference.blasux.ru
+nick=Govnupa
+log-head=log_head.html
+log-path=logs/%Y/%m/<muc>.html
+
+[muc example1@conference.blasux.ru]
+nick=Ζαλυπα
+log=yes
--- a/iswydt.vala	Tue Oct 23 18:33:49 2012 +0400
+++ b/iswydt.vala	Tue Oct 23 23:50:28 2012 +0400
@@ -21,7 +21,7 @@
         get { return _state; }
     }
 
-    public Module[] modules;
+    public Gee.ArrayList<Module> modules;
 
     public signal void state_changed(State old_state, State new_state, string description);
     public signal void check_time();
@@ -36,7 +36,7 @@
     }
 
     public Connection(Config cfg) {
-        this.modules = new Module[10];
+        this.modules = new Gee.ArrayList<Module>();
         this.jid = cfg["server","jid"];
         this.password = cfg["server","password"];
 
@@ -81,8 +81,6 @@
         cn.register_message_handler(muc_handler, Lm.MessageType.MESSAGE, Lm.HandlerPriority.NORMAL);
         cn.register_message_handler(muc_handler, Lm.MessageType.PRESENCE, Lm.HandlerPriority.NORMAL);
         cn.register_message_handler(muc_handler, Lm.MessageType.IQ, Lm.HandlerPriority.NORMAL);*/
-        var module = new ModuleMuc(cfg, this);
-        modules = {module};
         state_changed.connect( (olds, news, desc) => {
             if (news == Connection.State.CONNECTED) {
                 cn.send_raw("<message to='stiletto@stiletto.name'><body>ТХБ</body></message>");
@@ -96,6 +94,9 @@
                 open();
         });
     }
+    public void add_module(Module module) {
+        modules.add(module);
+    }
     public void open() {
 
         stderr.printf("Connecting to %s:%d as %s\n",server,port,jid);
@@ -126,10 +127,15 @@
             _change_state(State.DISCONNECTED, e.message);
         }
     }
-
+    public void close() {
+        cn.close();
+    }
 }
     
 
+static Connection account;
+static MainLoop loop;
+
 int main(string[] args) {
     if (args.length<2) {
         stderr.printf("Usage: %s <config file>\n",args[0]);
@@ -138,10 +144,13 @@
     Log.set_handler("Muc", (LogLevelFlags)65535, (domain, levels, message) => {
         stderr.printf("--- %s\n", message);
     });
+    Log.set_handler("muc_log", (LogLevelFlags)65535, (domain, levels, message) => {
+        stderr.printf("--- %s\n", message);
+    });
     log("LM", LogLevelFlags.LEVEL_DEBUG, "HATE HATE");
     var cfg = new Config.from_file(args[1]);
-    var loop = new MainLoop();
-    var account = new Connection(cfg);
+    loop = new MainLoop();
+    account = new Connection(cfg);
     //account.open();
     stdout.printf("Fuck yeah\n");
     var checktimer = new TimeoutSource(2000);
@@ -151,6 +160,24 @@
         return true;
     });
     checktimer.attach(loop.get_context());
+
+    account.add_module(new ModuleMuc(cfg, account));
+    account.add_module(new ModuleMucLog(cfg, account));
+
+    Posix.sigaction_t action = Posix.sigaction_t();
+    action.sa_handler = ((i) => {
+        if (account.state != Connection.State.CONNECTED)
+            loop.quit();
+        else {
+            account.state_changed.connect( (olds, news, desc) => {
+                if (news == Connection.State.DISCONNECTED)
+                    loop.quit();
+            });
+            account.close();
+        }
+    });
+    Posix.sigaction(Posix.SIGINT, action, null);
+
     loop.run();
     return 0;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/log_head.html	Tue Oct 23 23:50:28 2012 +0400
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+    <meta name="description" content="Log for <muc>" />
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <link rel="stylesheet" type="text/css" href="../../style.css" title="Irclog Stylesheet" />
+    <script type="text/javascript" src="../../script.js"></script>
+    <link rel="shortcut icon" href="moose1.ico" type="image/x-icon" />
+    <title>Log for <muc>, <date></title>
+</head>
+<body>
+    <!-- <p><img style="float:right" src="moosecamel.png" width="400" height="194"
+    alt="A Camel and a Moose"/></p> -->
+
+    <h1>Log for <muc>, <date></h1>
+
+    <p>All times shown according to <abbr title="Coordinated Universal Time">UTC</abbr>.</p>
+    <table id="log" style="clear:both">
+        <tr class="head">
+            <th>Time</th>
+            <th>Nick</th>
+            <th>Message</th>
+        </tr>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/logs/script.js	Tue Oct 23 23:50:28 2012 +0400
@@ -0,0 +1,19 @@
+window.onload = function () {
+    var log = document.getElementById("log");
+    var rows = log.getElementsByTagName("tr");
+    var rownum = rows.length;
+    for (var i = 0; i < rownum; i++) {
+        var row = rows[i];
+        if (row.getAttribute("class") == "message") {
+            var nicktd = row.getElementsByClassName("nick")[0];
+            if (nicktd) {
+                var nick = nicktd.textContent;
+                var sum = 0;
+                for( var j = 0, iTop = nick.length; j < iTop; j++ ) {
+                    sum += 0x56 ^ nick.charCodeAt(j);
+                }
+                nicktd.setAttribute("class","nick nick"+(sum % 10));
+            }
+        }
+    }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/logs/style.css	Tue Oct 23 23:50:28 2012 +0400
@@ -0,0 +1,130 @@
+body {
+	background-color: white;
+	color: black;
+}
+
+h1, h2, h3,  #footer * { 
+    color: #70709f; 
+    clear: left;
+}
+
+
+#footer {
+	background-color: #ececef; 
+	border-color: #c0c0ff;
+	color: inherit;
+	border-top: 2px solid;
+	border-bottom: 2px solid;
+	margin-bottom: 0.2em;
+	padding-top: 0.75ex;
+	padding-bottom: 0.75ex;
+	min-height: 4.7ex;
+	clear: left;
+}
+
+#footer * {
+  background-color: inherit;
+  font-size: 92%;
+}
+
+tr, td {
+	vertical-align: top;
+}
+
+.dark {
+	background-color: #efefef;
+}
+
+.special {
+	font-size: 80%;
+}
+.special * {
+	color: gray;
+}
+
+#log {
+    border-collapse: collapse;
+    width: 100%;
+    border: 1px solid #efefef;
+}
+
+#log tr { border-top: 1px solid #efefef; }
+#log tr.head, #log tr.dark.new { border-top-style: none; }
+#log tr.dark { border-top: 1px solid #dfdfdf; }
+#log tr td.text { width: 90%; }
+
+#log td, th {
+	font-family: Consolas, "Lucida Console", "Courier New", monospace;
+    padding: 0.2em 0.4em;
+    width: 1px;
+}
+
+#log th { border-bottom: 1px solid #C0C0C0; }
+
+#log .nick {
+    text-align: right;
+    border-right: 1px solid #C0C0C0;
+}
+#log .time a { color: #333; text-decoration: none; }
+#log .time a:hover { color: #000; text-decoration: underline; }
+
+#log .time { border-left:  1px solid #efefef; }
+#log .msg  { border-right: 1px solid #efefef; line-height: 1.3em; }
+#log .act  { font-style: italic; }
+
+.log-index li {
+    display: inline;
+}
+
+label,input {
+	display: block;
+	width: 10em;
+	float: left;
+	margin-bottom: 2ex;
+}
+
+label {
+	text-align: right;
+	width: 8em;
+	padding-right: 2ex;
+}
+
+br {
+	clear: left;
+}
+
+/* index page */
+
+.calendar {
+    float: left;
+}
+
+/* most important channel members. This is subjective, if you have a different
+ * opinion, feel free to change */
+.nick_timtoady 	{ color: green; font-weight: bold; }
+.nick_audreyt 	{ color: red;  font-weight: bold;}
+
+
+/* all "active" bots, (svnbot6, lambdabot, specbot etc.) */
+.bots  {color: #FFA500; font-style: italic}
+
+/* The rest gets dynamically allocated colors */
+.nick1 { color: #00AA33; }
+.nick2 { color: #AA0000; }
+.nick3 { color: #005500; }
+.nick4 { color: #FF0077; }
+.nick5 { color: blue; }
+.nick6 { color: #8B008B; }
+.nick7 { color: #50507f; }
+.nick8 { color: #00008B; }
+.nick9 { color: #222222; }
+
+abbr { cursor: help; }
+
+.search_found {
+	font-weight: bold;
+}
+
+tr.logstart td.text {
+    color: #3f0;
+}
--- a/muc.vala	Tue Oct 23 18:33:49 2012 +0400
+++ b/muc.vala	Tue Oct 23 23:50:28 2012 +0400
@@ -29,18 +29,18 @@
 
     static Role role_from_string(string role) {
         switch (role) {
-            case "moderator": return Role.MODERATOR; break;
-            case "participant": return Role.PARTICIPANT; break;
-            case "visitor": return Role.VISITOR; break;
+            case "moderator": return Role.MODERATOR;
+            case "participant": return Role.PARTICIPANT;
+            case "visitor": return Role.VISITOR;
         }
         return Role.NONE;
     }
     static Affiliation affil_from_string(string affil) {
         switch (affil) {
-            case "owner": return Affiliation.OWNER; break;
-            case "admin": return Affiliation.ADMIN; break;
-            case "member": return Affiliation.MEMBER; break;
-            case "outcast": return Affiliation.OUTCAST; break;
+            case "owner": return Affiliation.OWNER;
+            case "admin": return Affiliation.ADMIN;
+            case "member": return Affiliation.MEMBER;
+            case "outcast": return Affiliation.OUTCAST;
         }
         return Affiliation.NONE;
     }
@@ -68,8 +68,8 @@
     public signal void on_role(Conference conf, Occupant occupant, Role prev);
     public signal void on_affil(Conference conf, Occupant occupant, Affiliation prev);
     public signal void on_nick(Conference conf, Occupant occupant, string prev);
-    public signal void on_message(Conference conf, Occupant occupant, Lm.MessageNode message, string body);
-    public signal void on_subject(Conference conf);
+    public signal void on_message(Conference conf, Occupant? occupant, Lm.MessageNode message, string? body);
+    public signal void on_subject(Conference conf, Occupant? occupant);
 
     public class Conference : Object {
         public string jid;
@@ -227,13 +227,13 @@
                 }
             } else if ((message.get_type()==Lm.MessageType.MESSAGE)&&(type=="groupchat")) {
                 var subj = node.get_attribute("subject");
+                var occupant = (from.length > 1) ? occupants[from[1]] : null;
                 if (subj != null) {
                     if (subj != this.subject) {
                         this.subject = subj;
-                        module.on_subject(this);
+                        module.on_subject(this, occupant);
                     }
                 } else {
-                    var occupant = (from.length > 1) ? occupants[from[1]] : null;
                     var body = node.find_child("body");
                     module.on_message(this, occupant, node, (body!=null) ? body.get_value() : null);
                 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/muc_log.vala	Tue Oct 23 23:50:28 2012 +0400
@@ -0,0 +1,124 @@
+using Gee;
+
+class ModuleMucLog : Module {
+    protected weak ModuleMuc muc;
+    protected HashMap<string,RoomLog> rooms;
+    class RoomLog : Object {
+        public string jid;
+        public string logpath;
+        public string filename;
+        protected weak ModuleMucLog muclog;
+        protected FileStream file;
+        public string getid(Time time) {
+            return time.format("%s");
+        }
+        protected int lastday;
+        protected int lastyear;
+        protected int lastmonth;
+
+        public RoomLog(ModuleMucLog muclog, string jid) {
+            this.jid = jid;
+            this.muclog = muclog;
+            logpath = muclog.getconf(jid, "log-path", null);
+            filename = null;
+            write(Time.local(new time_t()), "logstart", "", "Log started");
+        }
+        ~RoomLog() {
+            write(Time.local(new time_t()), "logstop", "", "Log stopped");
+            file.flush();
+        }
+        public void write(Time time, string _class, string nick, string str) {
+
+            if ((lastday != time.day)||(lastmonth != time.month)||(lastyear!=time.year)) {
+                var fname = time.format(logpath).replace("<muc>",jid);
+                log("muc_log", LogLevelFlags.LEVEL_INFO, "Switching log file '%s' to '%s'", filename, fname);
+                if (file!=null) file.flush();
+                //try {
+                    file = FileStream.open(fname,"a");
+                if (file==null) {
+                    int start = 0;
+                    int length = fname.length;
+                    while (true) {
+                        var pos = fname.index_of("/", start);
+                        if (pos==-1)
+                            break;
+                        stderr.printf("--------------------- %s\n", fname[0:pos]);
+                        Posix.mkdir(fname[0:pos],0777);
+                        start = pos+1;
+                    } 
+
+                    file = FileStream.open(fname,"a");
+                    if (file == null) {
+                        log("muc_log", LogLevelFlags.LEVEL_WARNING, "Failed to create log file '%s'", fname);
+                        return;
+                    }
+                }
+
+                if (file.tell()==0) {
+                    var loghead = muclog.getconf(jid, "log-head",null);
+                    if (loghead!="null") {
+                        try {
+                            var headfile = new IOChannel.file(loghead,"r");
+                            string head;
+                            size_t length;
+                            headfile.read_to_end(out head, out length);
+                            headfile.shutdown(false);
+                            file.printf("%s\n", head.replace("<muc>",jid).replace("<date>",time.format("%d.%m.%Y")));
+                        } catch (FileError fe) {
+                            log("muc_log", LogLevelFlags.LEVEL_WARNING, "Failed to read head");
+                        }
+                    }
+                }
+                filename = fname;
+                lastyear = time.year; lastmonth = time.month; lastday = time.day;
+            }
+            
+            var id = getid(time);
+            var times = time.format("%H:%M:%S");
+            file.printf("<tr id='l_%s' class='%s'><td class='time'><a id='i_%s' href='#i_%s'>%s</a></td>", id, _class, id, id, times);
+            file.printf("<td class='nick'>%s</td><td class='text'>%s</td></tr>\n", nick, str);
+        }
+            
+    }
+    public string getconf(string jid, string key, string? def) {
+        var res = cfg["muc "+jid, key];
+        if (res==null) res = cfg["muc", key];
+        if (res==null) res = def;
+        return res;
+    }
+    public ModuleMucLog(Config cfg, Connection conn) {
+        base(cfg, conn);
+        rooms = new HashMap<string,RoomLog>();
+        muc = null;
+        foreach (var module in conn.modules) {
+            if (module.name()=="muc")
+                muc = (ModuleMuc)module;
+        }
+        if (muc==null)
+            log("muc_log", LogLevelFlags.LEVEL_ERROR, "Module 'muc' is not loaded");
+        muc.state_changed.connect( (conf, olds, news, desc) => {
+            switch (news) {
+                case ModuleMuc.State.CONNECTED:
+                    if (getconf(conf.jid, "log", "no")=="yes") {
+                        rooms[conf.jid] = new RoomLog(this, conf.jid);
+                    }
+                    break;
+                case ModuleMuc.State.DISCONNECTED:
+                    rooms.unset(conf.jid);
+                    break;
+            }
+        });
+        muc.on_message.connect( (conf, user, message, body) => {
+            if ((user!=null)&&(body!=null)&&(message.get_child("delay")==null)) {
+                var room = rooms[conf.jid];
+                if (room!=null) {
+                    var nick = Markup.escape_text(user.nick).replace(" ","&nbsp;");
+                    room.write(Time.local(new time_t()), "message", nick, Markup.escape_text(body).replace("\n","<br/>"));
+                }
+            }
+        });
+    }
+    public override string name() {
+        return "muc_log";
+    }
+}