File

mod_webpresence/mod_webpresence.lua @ 5661:e76ec7ad941e

mod_invites_register_web: update README for `invites_template_html`.
author Trần H. Trung <xmpp:trần.h.trung@trung.fun>
date Tue, 29 Aug 2023 22:06:19 +0700
parent 5633:758866b43aa4
line wrap: on
line source

module:depends("adhoc");
module:depends("http");

local moduleHost = module.host;
local moduleName = module:get_name();
local jid = require "util.jid";
local jid_split = require "util.jid".prepped_split;
local serialization = require "util.serialization";

-- ADHOC
local storage = module:open_store(moduleName, "keyval");
local utilDataforms = require "util.dataforms";
local utilAdhoc = require "util.adhoc";
local adhoc_new = module:require("adhoc").new;

local function webpresence_set(user, value)
	local ok, err = storage:set(user, value); -- value is table.

	if not ok or err then 
		module:log(error, "Could not write data %s", tostring(user));
		return ok, err;
	else
		return ok;
	end
end
local function webpresence_get(user) 
	local result = storage:get(user);
	if not result then
		result = { ["webpresence"] = false };
		webpresence_set(user, result);
	end
	return result[moduleName]; -- bool
end

local form = utilDataforms.new {
	title = "Web Presence Policy";
	instructions = "Your webpresence shows offline by default";
	{
		type = "boolean";
		name = moduleName;
		label = "Show";
		--value = webpresence_get();
	};
};
local formResult = utilDataforms.new {
	title = "Web Presence Policy";
	{
		type = "boolean";
		name = moduleName;
		label = "Show";
		--value;
	};
	{
		type = "text-multi";
		name = "url";
		label = "Check your presence at";
		value = "text-multi\n";
	};
};

local function webpresence_url(jid)
	local path = "/status";
	local config = module:get_option("http_paths");
	if config then
		for k, v in pairs(config) do
			if k == moduleName then 
				path = v;
				break;
			end
		end
	end

	local urlBase = module:context(module.host):http_url(module.name, path);
	local style = { "text", "message", "json", "html" };
	local urlResult = urlBase.."/"..jid.."\n";
	for _, v in ipairs(style) do
		urlResult = urlResult..urlBase.."/"..jid.."/"..v.."\n";
	end
	return urlResult;
end

-- TODO:
-- Fix the handler (somehow) to make `form` shows the correct value.
--
local adhoc_handler = utilAdhoc.new_simple_form(form, function(fields, state, data)
	local jid_bare = jid.bare(data.from);
	local user, host = jid_split(jid_bare);

	local oldData, _ = storage:get(user);
	oldValue = webpresence_get(user);
	form.webpresence = oldValue;

	local urlResult = webpresence_url(jid_bare);
	local newValue = {
		[moduleName] = fields.webpresence;
	};

	if state then
		return {
			status = "completed";
			info = "No change for: "..tostring(data.from).." …\n"
				.."Old data: "..serialization.serialize(oldData).."\n"
				.."New data: "..serialization.serialize(newValue).."\n";
			result = {
				layout = formResult;
				values = {
					webpresence = oldValue;
					url = urlResult;
				};
			};
		};
	else
		local resultSet, resultErr = webpresence_set(user, newValue)
		if not resultSet or resultErr then
			module:log("error", moduleName..": ".."Could not set value for "..user.."@"..host..": %s", errOut);
			return {
				status = "completed";
				info = "Could not set value: "..tostring(data.from).." …\n"
					.."Old data: "..serialization.serialize(oldData).."\n"
					.."New data: "..serialization.serialize(newValue).."\n"
					.."Error: "..errOut.."\n";
				result = {
					layout = formResult;
					values = {
						webpresence = newValue[moduleName];
						url = urlResult;
					};
				};
			};
		else
			return {
				status = "completed";
				info = "Changing value for: "..tostring(data.from).." …\n"
					.."Old data: "..serialization.serialize(oldData).."\n"
					.."New data: "..serialization.serialize(newValue).."\n";
				result = {
					layout = formResult;
					values = {
						webpresence = newValue[moduleName];
						url = urlResult;
					};
				};
			};
		end
	end
end);

module:provides("adhoc", adhoc_new("Web Presence Policy", moduleName, adhoc_handler, "any"));

-- HTTP
local b64 = require "util.encodings".base64.encode;
local sha1 = require "util.hashes".sha1;
local stanza = require "util.stanza".stanza;
local json = require "util.json".encode_ordered;
local usermanager = require "core.usermanager";

local function require_resource(name)
    local icon_path = module:get_option_string("webpresence_icons", "icons");
    local f, err  = module:load_resource(icon_path.."/"..name);
    if f then
        return f:read("*a");
    end
    module:log("warn", "Failed to open image file %s", icon_path..name);
    return "";
end

local statuses = { online = {}, away = {}, xa = {}, dnd = {}, chat = {}, offline = {} };

local function user_list(host) return user:list(host); end

local function handle_request(event, path)
	local status, message;
	local jid, type = path:match("([^/]+)/?(.*)$");
	if jid then
		local user, host = jid_split(jid);
		if host and not user then
			user, host = host, event.request.headers.host;
			if host then host = host:gsub(":%d+$", ""); end
		end
		if host ~= moduleHost then 
			status = "offline";
		else
			if user and host and usermanager.user_exists(user, host) then
				local user_sessions = hosts[host] and hosts[host].sessions[user];
				local show = webpresence_get(user)
				if show == false then 
					status = "offline";
				else
					if user_sessions and user_sessions.top_resources then
						status = user_sessions.top_resources[1];
						if status and status.presence then
							message = status.presence:child_with_name("status");
							status = status.presence:child_with_name("show");
							if not status then
								status = "online";
							else
								status = status:get_text();
							end
							if message then
								message = message:get_text();
							end
						end
					end
				end
			end
		end
		status = status or "offline";
	end

statuses[status].image = function()
	return { status_code = 200, headers = { content_type = "image/png" },
	body =  require_resource("status_"..status..".png")
};
  end;
  statuses[status].html = function()
	  local jid_hash = sha1(jid, true);
	  return { status_code = 200, headers = { content_type = "text/html" },
	  body =  [[<!DOCTYPE html>]]..
	  tostring(
	  stanza("html")
	  :tag("head")
	  :tag("title"):text("XMPP Status Page for "..jid):up():up()
	  :tag("body")
	  :tag("div", { id = jid_hash.."_status", class = "xmpp_status" })
	  :tag("img", { id = jid_hash.."_img", class = "xmpp_status_image xmpp_status_"..status,
	  src = "data:image/png;base64,"..b64(require_resource("status_"..status..".png")) }):up()
	  :tag("span", { id = jid_hash.."_status_name", class = "xmpp_status_name" })
	  :text("\194\160"..status):up()
	  :tag("span", { id = jid_hash.."_status_message", class = "xmpp_status_message" })
	  :text(message and "\194\160"..message.."" or "")
	  )
  };
  end;
  statuses[status].text = function()
	  return { status_code = 200, headers = { content_type = "text/plain" },
	  body = status
  };
  end;
  statuses[status].message = function()
	  return { status_code = 200, headers = { content_type = "text/plain" },
	  body = (message and message or "")
  };
  end;
  statuses[status].json = function()
	  return { status_code = 200, headers = { content_type = "application/json" },
	  body = json({
		  jid    = jid,
		  show   = status,
		  status = (message and message or "null")
	  })
  };
  end;
  statuses[status].xml = function()
	  return { status_code = 200, headers = { content_type = "application/xml" },
	  body = [[<?xml version="1.0" encoding="utf-8"?>]]..
	  tostring(
	  stanza("result")
	  :tag("jid"):text(jid):up()
	  :tag("show"):text(status):up()
	  :tag("status"):text(message)
	  )
  };
  end

  if ((type == "") or (not statuses[status][type])) then
	  type = "image"
  end;

  return statuses[status][type]();
end

module:provides("http", {
	default_path = "/status";
	route = {
		["GET /*"] = handle_request;
	};
});