File

clix.lua @ 60:1537dabc5df8

clix.export: Don't add empty iq:private nodes.
author Kim Alvefur <zash@zash.se>
date Thu, 05 May 2011 01:30:06 +0200
parent 52:95ea1dca92ed
child 70:5e93cfc73444
line wrap: on
line source

#!/usr/bin/env lua
-- Clix -- Command-line XMPP
-- Copyright (C) 2008-2010 Matthew Wild
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
require "verse"
require "verse.client"

-- Global to allow commands to add to it
short_opts = { v = "verbose", q = "quiet", t = "to", f = "from", e = "type",
	a = "account", p = "password", r = "resource", o = "presence", c = "chatroom" }

if #arg < 1 then
	print("Command Line XMPP, available commands:");
	for module in pairs(package.preload) do
		if module:match("^clix%.") then
			local m = require(module);
			io.write("\t", module:gsub("^clix%.", ""), ": ");
			m({ short_help = true }, {});
		end
	end
	return 0;
end

local accounts = { default = {} };
local current_account;

local config_path = (os.getenv("XDG_CONFIG_HOME") or (os.getenv("HOME").."/.config"));
local config_file, err = io.open(config_path.."/.clixrc") or io.open(config_path.."/.clix");

if not config_file then
	print("Unable to open config file... looked for "..config_path.."/.clixrc");
	if err then print(err); end
	os.exit(1);
end

for line in config_file:lines() do
	line = line:match("^%s*(.-)%s*$");
	if line:match("^%[") then
		current_account = line:match("^%[(.-)%]");
		accounts[current_account] = {};
		if not current_account then -- This is the first defined account
			accounts.default = accounts[current_account];
		end
	elseif current_account then
		local k,v = line:match("^(%w+)%s*[:=]%s*(.+)$");
		if k then
			accounts[current_account or "default"][k] = v;
		end
	end
end

function clix_connect(opts, on_connect, plugins)
	local account = accounts[opts.account or "default"];
	if not (account and account.jid) then
		io.stderr:write("The specified account (", opts.account or "default", ") wasn't found in the config file\n");
		return nil;
	end
	verse.set_logger(opts.verbose and verse.logger() or verse.filter_log({"error"}, verse.logger()));
	local conn = verse.new(verse.logger());
	for _, plugin in ipairs(plugins or {}) do
		conn:add_plugin(plugin);
	end
	conn.log.debug = opts.verbose;
	conn:hook("authentication-failure", function (err)
		conn:error("Authentication failure ("..(err.condition or "unknown error")..")"..(err.text and (": "..err.text) or ""));
		conn:close();
	end);
	conn:hook("ready", function ()
		if not opts.quiet then
			io.stderr:write("clix: connected as ", conn.jid, "\n");
		end
		if opts.chatroom then
			conn:send(verse.presence{to=opts.to.."/"..(opts.nick or "clix")});
		end
		if opts.presence then
			conn:send(verse.presence());
		end
		return on_connect(conn);
	end);
	conn:hook("bind-failure", function (err)
		conn:error("Resource binding failure ("..(err.condition or "unknown error")..")"..(err.text and (": "..err.text) or ""));
		conn:close();
	end);
	conn:hook("disconnected", function (info)
		if info.reason then
			conn:warn("Disconnecting: %s", tostring(info.reason));
		end
		verse.quit();
	end);
	-- Optional config parameters
	conn.connect_host = account.address;
	conn.connect_port = account.port;
	
	-- Connect!
	conn:connect_client(account.jid, opts.password or account.password);
	
	-- COMPAT: Tigase discards stanzas sent at the same time as </stream:stream>
	local _real_close = conn.close;
	function conn:close()
		conn:debug("Delaying close...");
		local function close_conn()
			local function do_close()
				if _real_close then
					conn:debug("Closing now...");
					local close = _real_close;
					_real_close = nil;
					close(conn);
				end
			end
			local close_delay = tonumber(opts.close_delay) or 0;
			if close_delay > 0 then
				verse.add_task(close_delay, do_close);
			else
				do_close();
			end
		end
		if conn.conn:bufferlen() == 0 then
			close_conn();
		else
			conn:hook("drained", close_conn);
		end
	end
	-- /COMPAT
	
	
	if type(opts.resource) == "string" then
		conn.resource = opts.resource;
	end
	
	local ok, ret = pcall(verse.loop);
	if not ok and not ret:match("interrupted!$") then
		io.stderr:write("Fatal error: ", ret, "\n");
		return 1;
	end
	return err or 0;
end

local opts = {};

local command, args_handled_up_to;
for i, opt in ipairs(arg) do
        if opt:match("^%-") and opt ~= "--" then
                local name = opt:match("^%-%-?([^%s=]+)()")
                name = (short_opts[name] or name):gsub("%-+", "_");
                if name:match("^no_") then
                        name = name:sub(4, -1);
                        opts[name] = false;
                else
                        opts[name] = opt:match("=(.*)$") or true;
                end
        elseif not command then
        	command = arg[i];
       		args_handled_up_to = i-1;
        else
       		args_handled_up_to = i-1;
       		break;
       	end
end

-- Remove all the handled args from the arg array
for n=((args_handled_up_to > 0) and args_handled_up_to or #arg),1,-1 do
	table.remove(arg, n);
end

local ok, m = pcall(require, "clix."..command);
if not ok then
	local is_debug;
	for i=1,#arg do
		if arg[i] == "--debug" then
			is_debug = true; break;
		end
	end
	print("Error running command '"..command..(is_debug and "" or "' (run with --debug to see full error)"));
	if is_debug then
		print(m);
	end
	return 1;
end

if type(m) ~= "function" then
	print(command.." is not a valid command");
	return 1;
end

return m(opts, arg) or 0;