--- 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(" "," ");
+ room.write(Time.local(new time_t()), "message", nick, Markup.escape_text(body).replace("\n","<br/>"));
+ }
+ }
+ });
+ }
+ public override string name() {
+ return "muc_log";
+ }
+}