clix.lua

Sat, 24 Jun 2023 09:59:07 +0200

author
Kim Alvefur <zash@zash.se>
date
Sat, 24 Jun 2023 09:59:07 +0200
changeset 170
0d561f921c13
parent 169
4e67d34c1298
permissions
-rwxr-xr-x

clix.adhoc: Move stanza to dataform converter here

Removes the need for verse to have a custom util.dataforms fork only for
this

#!/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.
--
local verse = require "verse".init "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", i = "interactive",
    f = "file" }


local commands = "adhoc archive avatar bounce export mirror moderate ping\
presence publish_atom raw receive roster send sendfile sendfilecontent vcard\
version watch_pep"
if #arg < 1 then
	print("Command Line XMPP, available commands:");
	for command in commands:gmatch("%S+") do
		local m = require("clix."..command);
		io.write("\t", command, ": ");
		m({ short_help = true }, {});
	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

local function read_password()
	io.write("Password: ")
	local stty_ret = os.execute("stty -echo 2>/dev/null");
	if stty_ret ~= 0 then
		io.write("\027[08m"); -- ANSI 'hidden' text attribute
	end
	local ok, pass = pcall(io.read, "*l");
	if stty_ret == 0 then
		os.execute("stty sane");
	else
		io.write("\027[00m");
	end
	io.write("\n");
	if ok then return pass; 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_log_handler(io.stderr, opts.quiet and {} or not opts.verbose and {"info", "warn", "error"});
	local conn = verse.new(verse.new_logger("clix"));
	if opts.raw then
		conn:hook("incoming-raw", print, 1000);
		conn:hook("outgoing-raw", print, 1000);
	end
	for _, plugin in ipairs(plugins or {}) do
		conn:add_plugin(plugin);
	end
	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 ()
		conn:info("Connected as %s", conn.jid);
		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 and info.reason and info.reason ~= "stream closed"  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;

	if not account.password and not (account.clientkey and account.serverkey) and opts.interactive then
		account.password = read_password()
	end

	local clientkey, serverkey = account.clientkey, account.serverkey;
	if clientkey and serverkey then
		local hex = require "prosody.util.hex";
		clientkey = hex.from(clientkey);
		serverkey = hex.from(serverkey);
	elseif clientkey or serverkey then
		conn:warn("Only one of 'clientkey' and 'serverkey' available, both reqired for SCRAM")
		clientkey, serverkey = nil, nil;
	end
	
	-- Connect!
	conn:connect_client(account.jid, account.password, clientkey, serverkey);
	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;

mercurial