# HG changeset patch # User Matthew Wild # Date 1261423628 0 # Node ID 7d84f4403d673895460eb2ae7e3ece72c3b440d8 Initial commit, hello world! diff -r 000000000000 -r 7d84f4403d67 init.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/init.lua Mon Dec 21 19:27:08 2009 +0000 @@ -0,0 +1,144 @@ +local verse = require "verse"; +local st = require "util.stanza"; + +module("riddim", package.seeall); +plugins = {}; + +local riddim_mt = {}; +riddim_mt.__index = riddim_mt; + +function new(stream, config) + if not stream then + error("riddim.new(): Verse stream required as first parameter", 2); + end + return setmetatable({ stream = stream, config = config or {} }, riddim_mt); +end + +-- self.conn is ready for stanzas +function riddim_mt:start() + self:add_plugin("groupchat"); + self:add_plugin("commands"); + self:add_plugin("ping"); + self:event("started"); + self.stream:hook("stanza", function (stanza) + local body = stanza:get_child("body"); + local event = { + sender = { jid = stanza.attr.from }; + body = (body and body:get_text()) or nil; + stanza = stanza; + }; + if stanza.name == "message" then + local replied; + local bot = self; + function event:reply(reply) + if replied then return false; end + replied = true; + return bot:send_message(stanza.attr.from, reply); + end + end + local ret; + if stanza.name == "iq" and (stanza.attr.type == "get" or stanza.attr.type == "set") then + local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; + if xmlns then + event.xmlns = xmlns; + print(event.stanza) + ret = self:event("iq/"..xmlns, event); + if not ret then + ret = self:event(stanza.name, event); + end + end + else + ret = self:event(stanza.name, event); + end + + if ret and type(ret) == "table" and ret.name then + self:send(ret); + end + return ret; + end, 1); +end + +function riddim_mt:send(s) + return self.stream:send(tostring(s)); +end + +function riddim_mt:event(name, ...) + return self.stream:event("bot/"..name, ...); +end + +function riddim_mt:hook(name, ...) + return self.stream:hook("bot/"..name, ...); +end + +function riddim_mt:send_message(to, text, formatted_text) + self:send(st.message({ to = to, type = "chat" }):tag("body"):text(text)); +end + +function riddim_mt:add_plugin(name) + require("riddim.plugins."..name); + return riddim.plugins[name](self); +end + +-- Built-in bot starter +if not (... and package.loaded[...] ~= nil) then + require "verse.client"; + + -- Config loading + local chunk, err = loadfile("config.lua"); + if not chunk then + print("File or syntax error:", err); + return 1; + end + + local config = {}; + setfenv(chunk, setmetatable(config, {__index = _G})); + local ok, err = pcall(chunk); + if not ok then + print("Error while processing config:", err); + return 1; + end + setmetatable(config, nil); + + if not config.jid then + io.write("Enter the bot's JID: "); + config.jid = io.read("*l"); + end + + if not config.password then + io.write("Enter the password for "..config.jid..": "); + config.password = io.read("*l"); + end + + -- Create the stream object and bot object + local c = verse.new(); + local b = riddim.new(c, config); + + if config.debug then + c:hook("incoming-raw", print); + end + + for _, plugin in ipairs(config.plugins or {"ping"}) do + c:add_plugin(plugin); + end + + b:hook("started", function () + b:send(verse.presence()); + for k, v in pairs(autojoin or {}) do + if type(k) == "number" then + b:join_room(v); + elseif type(k) == "string" then + if type(v) == "string" then + b:join_room(k, v); + end + end + end + end); + + c:hook("binding-success", function () b:start(); end) + + c:connect_client(config.jid, config.password); + + verse.loop(); +end + +return _M; diff -r 000000000000 -r 7d84f4403d67 plugins/commands.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/commands.lua Mon Dec 21 19:27:08 2009 +0000 @@ -0,0 +1,46 @@ +require "util.xstanza" + +function riddim.plugins.commands(bot) + local function handle_message(message) + -- Parse message body + end + + local command_pattern = "^%"..(bot.config.command_prefix or "@").."([%a%-%_%d]+)(%s?)(.*)$"; + + local function process_command(event) + local body, sender = event.body, event.sender; + if not body then return; end + if event.delay then return; end -- Don't process old messages + + local command, hasparam, param = body:match(command_pattern); + + if not command then + command, hasparam, param = body:match("%[([%a%-%_%d]+)(%s?)(.*)%]"); + end + + if hasparam ~= " " then param = nil; end + + if command then + local command_event = { + command = command, + param = param, + sender = sender, + stanza = event.stanza, + reply = event.reply, + room = event.room, + }; + local ret = bot:event("commands/"..command, command_event); + if type(ret) == "string" then + event:reply(ret); + end + return ret; + end + end + + -- Hook messages to bot and from rooms, fire a command event on the bot + bot:hook("message", process_command); + + bot:hook("groupchat/joining", function (room) + room:hook("message", process_command); + end); +end diff -r 000000000000 -r 7d84f4403d67 plugins/groupchat.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/groupchat.lua Mon Dec 21 19:27:08 2009 +0000 @@ -0,0 +1,115 @@ +local events = require "events"; +local st = require "util.stanza"; + +local room_mt = {}; +room_mt.__index = room_mt; + +local xmlns_delay = "urn:xmpp:delay"; + +function riddim.plugins.groupchat(bot) + bot.rooms = {}; + + bot.stream:hook("stanza", function (stanza) + local room_jid = jid.bare(stanza.attr.from); + local room = bot.rooms[room_jid] + if room then + local nick = select(3, jid.split(stanza.attr.from)); + local body = stanza:get_child("body"); + local delay = stanza:get_child("delay", xmlns_delay); + local event = { + room_jid = room_jid; + room = room; + sender = room.occupants[nick]; + nick = nick; + body = (body and body:get_text()) or nil; + stanza = stanza; + delay = (delay and delay.attr.stamp); + }; + if stanza.name == "message" then + local replied; + local r = st.reply(stanza); + if stanza.attr.type == "groupchat" then + r.attr.type = stanza.attr.type; + r.attr.to = jid.bare(stanza.attr.to); + end + function event:reply(reply) + if not reply then reply = "Nothing to say to you"; end + if replied then return false; end + replied = true; + if r.attr.type == "groupchat" then + reply = event.sender.nick..": "..reply; + end + room:send(r:tag("body"):text(reply)); + end + end + local ret; + if stanza.name ~= "message" or nick ~= room.nick then + ret = room:event(stanza.name, event); + end + return ret or (stanza.name == "message") or nil; + end + end, 500); + + function bot:join_room(jid, nick) + nick = nick or bot.config.nick or ("bot"..math.random(10000,99999)); + local room = setmetatable({ + bot = bot, jid = jid, nick = nick, + occupants = {}, + events = events.new() }, room_mt); + self.rooms[jid] = room; + local occupants = room.occupants; + room:hook("presence", function (presence) + local nick = presence.nick; + if not occupants[nick] then + if presence.type ~= "unavailable" then + occupants[nick] = { + nick = nick; + jid = presence.stanza.attr.from; + presence = presence.stanza; + }; + if nick == room.nick then + room.bot:event("groupchat/joined", room); + else + room:event("occupant-joined", occupants[nick]); + end + else + occupants[nick].presence = presence.stanza; + room:event("occupant-left", occupants[nick]); + occupants[nick] = nil; + end + end + end); + self:send(st.presence({to = jid.."/"..nick})); + self:event("groupchat/joining", room); + return room; + end +end + +function room_mt:send(stanza) + if stanza.name == "message" and not stanza.attr.type then + stanza.attr.type = "groupchat"; + end + if stanza.attr.type == "groupchat" then + stanza.attr.to = self.jid; + end + self.bot:send(stanza); +end + +function room_mt:send_message(text) + self:send(st.message():tag("body"):text(text)); +end + +function room_mt:leave(message) + self.bot:event("groupchat/leaving", room); + self:send(st.presence({type="unavailable"})); + self.bot:event("groupchat/left", room); +end + +function room_mt:event(name, arg) + self.bot.stream:debug("Firing room event: %s", name); + return self.events.fire_event(name, arg); +end + +function room_mt:hook(name, callback, priority) + return self.events.add_handler(name, callback, priority); +end diff -r 000000000000 -r 7d84f4403d67 plugins/ping.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/ping.lua Mon Dec 21 19:27:08 2009 +0000 @@ -0,0 +1,14 @@ + +function riddim.plugins.ping(bot) + bot.stream:add_plugin("ping"); + bot:hook("commands/ping", function (command) + local jid = command.param; + if jid then + bot.stream:ping(jid, function (time) + command:reply(string.format("Pong from %s in %0.3f seconds", jid, time)); + end); + return true; + end + return "pong"; + end); +end diff -r 000000000000 -r 7d84f4403d67 plugins/version.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/version.lua Mon Dec 21 19:27:08 2009 +0000 @@ -0,0 +1,50 @@ +local st = require "util.stanza"; +require "util.xstanza"; + +local xmlns_version = "jabber:iq:version"; + +function riddim.plugins.version(bot) + bot.stream:add_plugin("version"); + bot.stream.version:set{ + name = "Riddim"; + version = "alpha"; + }; + + bot:hook("commands/version", function (command) + local who, param = bot.stream.jid, command.param or (command.room and command.room.nick); + if param then + if command.room and command.room.occupants[param] then + who = command.room.occupants[param].jid; + elseif command.room and command.room.occupants[param:gsub("%s$", "")] then + who = command.room.occupants[param:gsub("%s$", "")].jid; + else + who = param; + end + end + + bot.stream:query_version(who, + function (reply) + if not reply.error then + local saywho = (who == command.sender.jid and "You are") or (param.." is"); + command:reply(saywho.." running "..(reply.name or "something") + .." version "..(reply.version or "unknown") + .." on "..(reply.platform or "an unknown platform")); + else + local type, condition, text = reply.type, reply.condition, reply.text; + local r = "There was an error requesting "..param.."'s version"; + if condition == "service-unavailable" then + r = param.." doesn't reply to version requests"; + elseif condition == "remote-server-not-found" then + r = param.." can't be reached via XMPP" + elseif condition and not text then + r = r..": "..condition; + end + if text then + r = r .. " ("..text..")"; + end + command:reply(r); + end + end); + return true; + end); +end diff -r 000000000000 -r 7d84f4403d67 squishy --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/squishy Mon Dec 21 19:27:08 2009 +0000 @@ -0,0 +1,8 @@ +Module "riddim.plugins.groupchat" "plugins/groupchat.lua" +Module "riddim.plugins.commands" "plugins/commands.lua" +Module "riddim.plugins.version" "plugins/version.lua" +Module "riddim.plugins.ping" "plugins/ping.lua" + +Main "init.lua" + +Output "riddim.lua"