Changeset

5588:f16720087ef2

mod_webpresence: Add opt-in mechanism.
author Trần H. Trung <work@trung.fun>
date Tue, 23 May 2023 23:45:00 +0700
parents 5484:bb083e9f78dd
children 5589:c362833be557
files mod_webpresence/README.markdown mod_webpresence/mod_webpresence.lua
diffstat 2 files changed, 280 insertions(+), 111 deletions(-) [+]
line wrap: on
line diff
--- a/mod_webpresence/README.markdown	Thu May 18 21:11:13 2023 +0200
+++ b/mod_webpresence/README.markdown	Tue May 23 23:45:00 2023 +0700
@@ -2,65 +2,80 @@
 labels:
 - 'Stage-Stable'
 summary: Display your online status in web pages
+rockspec:
+  build:
+    copy_directories:
+    - icons
 ...
 
 Introduction
 ============
 
 Quite often you may want to publish your Jabber status to your blog or
-website. mod\_webpresence allows you to do exactly this.
-
-Details
-=======
-
-This module uses Prosody's built-in HTTP server (it does not depend on
-mod\_httpserver). It supplies a status icon representative of a user's
-online state.
+website. mod\_webpresence allows you to do exactly this via adhoc control.
 
 Installation
 ============
 
-Simply copy mod\_webpresence.lua to your modules directory, the image
-files are embedded within it. Then add "webpresence" to your
-modules\_enabled list.
+Copy mod\_webpresence.lua to your modules directory then add it to your
+modules\_enabled list:
+
+```
 
-Usage
-=====
+    modules_enabled = {
+        "webpresence";
+    };
+
+```
+
+Configuration & Usage
+=====================
+
+There is a set of icons supplied with the module. But you can configure it to
+load your own in the config file:
 
-Once loaded you can embed the icon into a page using a simple `<img>`
-tag, as follows:
+```
+
+    webpresence_icons = "/path/to/your/icons";    
 
-    <img src="http://prosody.example.com:5280/status/john.smith" />
+```
+
+Beware that the icon files must have the same names as the default files.
+
+This module will always returns offline until you enable it via adhoc.
 
-Alternatively, it can be used to get status name as plaint text, status
-message as plain text or html-code for embedding on web-pages.
+You can embed the icon into a page using a simple `<img>` tag, as follows:
+
+    <img src="http://prosody.example.com:5280/status/john.smith@domain.net" />
 
-To get status name in plain text you can use something like that link:
-`http://prosody.example.com:5280/status/john.smith/text`
+Alternatively, it can be used to get status name as plain text, status message
+as plain text or html-code for embedding on web-pages.
+
+To get status name in plain text you can use something like this link:
+`http://prosody.example.com:5280/status/john.smith@domain.net/text`
 
 To get status message as plain text you can use something like following
-link: `http://prosody.example.com:5280/status/john.smith/message`
+link: `http://prosody.example.com:5280/status/john.smith@domain.net/message`
 
-To get html code, containig status name, status image and status message
-(if set): `http://prosody.example.com:5280/status/john.smith/html`
-
-All other
+To get html code, containing status name, status image and status message
+(if set): `http://prosody.example.com:5280/status/john.smith@domain.net/html`
 
 Compatibility
 =============
 
-  ----- -------
-  trunk   Works
-  0.10   Works
-  0.9   Works
-  0.8   Works
-  0.7   Works
-  0.6   Works
-  ----- -------
+  -----     -------
+  trunk     Works
+  0.12.3    Works
+  0.10      Works
+  0.9       Works
+  0.8       Works
+  0.7       Works
+  0.6       Works
+  -----     -------
 
 Todo
 ====
 
 -   Display PEP information (maybe a new plugin?)
--   More (free) iconsets
 -   Internal/external image generator (GD, ImageMagick)
+-   Display the correct boolean in the first form.
--- a/mod_webpresence/mod_webpresence.lua	Thu May 18 21:11:13 2023 +0200
+++ b/mod_webpresence/mod_webpresence.lua	Tue May 23 23:45:00 2023 +0700
@@ -1,13 +1,156 @@
+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_bare)
+	local config, path = module:get_option("http_paths");
+	if config then
+		for k, v in pairs(config) do
+			if k == moduleName then path = v;
+			else path = "/status" .. "/"; end
+		end
+	else path = "/status" .. "/"; end
+
+	local urlConfig = module:get_option_string("http_external_link");
+	local urlBase = urlConfig..path..jid_bare;
+	local style = { "/text", "/message", "/json", "/html" };
+	local urlResult = urlBase.."\n";
+	for _, v in ipairs(style) do
+		urlResult = urlResult..urlBase..v.."\n";
+	end
+	return urlResult;
+end
+
+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, "Could not set value: %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("presence_icons", "icons");
+    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");
@@ -18,101 +161,112 @@
 
 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 user and host then
-      local user_sessions = hosts[host] and hosts[host].sessions[user];
-      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
-  status = status or "offline";
+	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")
-    };
+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 "")
-        )
-    };
+	  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
-    };
+	  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 "")
-    };
+	  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")
-      })
-    };
+	  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)
-        )
-      };
+	  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"
+	  type = "image"
   end;
 
   return statuses[status][type]();
 end
 
 module:provides("http", {
-    default_path = "/status";
-    route = {
-        ["GET /*"] = handle_request;
-    };
+	default_path = "/status";
+	route = {
+		["GET /*"] = handle_request;
+	};
 });