Software /
code /
prosody-modules
Changeset
5627:89b6d0e09b86
Merge upstream.
author | Trần H. Trung <xmpp:trần.h.trung@trung.fun> |
---|---|
date | Wed, 26 Jul 2023 18:45:41 +0700 |
parents | 5595:f7410850941f (diff) 5626:81042c2a235a (current diff) |
children | 5628:a74b07764d3f |
files | |
diffstat | 5 files changed, 896 insertions(+), 154 deletions(-) [+] |
line wrap: on
line diff
--- a/mod_invites_api/mod_invites_api.lua Tue Jul 25 11:01:58 2023 +0200 +++ b/mod_invites_api/mod_invites_api.lua Wed Jul 26 18:45:41 2023 +0700 @@ -1,12 +1,22 @@ local http_formdecode = require "net.http".formdecode; +local jid = require "util.jid"; +local usermanager = require "core.usermanager"; +local datetime = require "util.datetime"; -local api_key_store; -local invites; +-- Whether local users can invite other users to create an account on this server +local allow_user_invites = module:get_option_boolean("allow_user_invites", false); +-- Who can see and use the contact invite command. It is strongly recommened to +-- keep this available to all local users. To allow/disallow invite-registration +-- on the server, use the option above instead. +local allow_contact_invites = module:get_option_boolean("allow_contact_invites", true); + +local api_key_store, api_key_store_kv, invites, date, config, path; -- COMPAT: workaround to avoid executing inside prosodyctl if prosody.shutdown then module:depends("http"); api_key_store = module:open_store("invite_api_keys", "map"); invites = module:depends("invites"); + api_key_store_kv = module:open_store("invite_api_keys"); end local function get_api_user(request, params) @@ -58,7 +68,18 @@ return 405; end - local invite = invites.create_account(nil, { source = "api/token/"..api_user.id }); + local invite; + local username, domain = jid.prepped_split(api_user.jid); + if username and usermanager.user_exists(username, domain) then + local ttl = module:get_option_number("invite_expiry", 86400 * 7); + invite = invites.create_contact(username, + allow_user_invites, -- allow_registration + { source = "api/token/"..api_user.id }, -- additional_data + ttl + ); + else + invite = invites.create_account(nil, { source = "api/token/"..api_user.id }); + end if not invite then return 500; end @@ -79,49 +100,557 @@ }); end +function get_value(callback) + for key_id, key_info in pairs(api_key_store_kv:get(nil) or {}) do + date = datetime.datetime(key_info.created_at); + callback(key_id, key_info); + end +end + +local function get_url(id, token) + local config, path = module:get_option("http_paths"); + if config then + for k, v in pairs(config) do + if k == module.name then + path = v; + break; + else + path = "/"..module.name; + end + end + else path = "/"..module.name ; end + + local url_base = module:get_option_string("http_external_link", module.host); + return url_base..path.."?key="..id.."/"..token.."&redirect=true"; +end + function module.command(arg) if #arg < 2 then - print("Usage:"); + print("========================================================================"); + print(""); + print("Create:"); + print(""); + print("> prosodyctl mod_"..module.name.." create JID NAME"); + print(""); + print("Query:"); + print(""); + print("> prosodyctl mod_"..module.name.." get JID NAME"); + print("> prosodyctl mod_"..module.name.." date JID NAME"); + print("> prosodyctl mod_"..module.name.." id JID NAME"); + print("> prosodyctl mod_"..module.name.." key JID NAME"); + print("> prosodyctl mod_"..module.name.." url JID NAME"); print(""); - print(" prosodyctl mod_"..module.name.." create NAME"); - print(" prosodyctl mod_"..module.name.." delete KEY_ID"); - print(" prosodyctl mod_"..module.name.." list"); + print("Revoke:"); + print(""); + print("> prosodyctl mod_"..module.name.." delete JID NAME"); + print("> prosodyctl mod_"..module.name.." delete-id HOST ID"); + print(""); + print("Renew:"); print(""); + print("> prosodyctl mod_"..module.name.." renew JID NAME"); + print(""); + print("Help:"); + print(""); + print("> prosodyctl mod_"..module.name.." help COMMAND"); + print(""); + print("========================================================================"); + return; end local command = table.remove(arg, 1); + if command == "help" then + help_command = table.remove(arg, 1); + if help_command == "create" then + print("========================================================================"); + print(""); + print("Create a key:"); + print(""); + print("> prosodyctl mod_"..module.name.." create JID NAME"); + print(""); + print("------------------------------------------------------------------------"); + print(""); + print("Usage:"); + print(""); + print(" `JID` can either be a user's account or a host."); + print(" When `JID` is a host, you need to supply a `NAME`."); + print(""); + print(" Each user's account can only have 1 API key but hosts are unlimited."); + print(""); + print("========================================================================"); + elseif help_command == "renew" then + print("========================================================================"); + print(""); + print("Re-new a key:"); + print(""); + print("> prosodyctl mod_"..module.name.." create JID NAME"); + print(""); + print("------------------------------------------------------------------------"); + print(""); + print("Usage:"); + print(""); + print(" `JID` can either be a user's account or a host."); + print(" When `JID` is a host, you need to supply a `NAME`."); + print(""); + print(" The old `ID` will be kept and a new token will be generated for the API"); + print(" key you specified."); + print(""); + print("========================================================================"); + elseif help_command == "get" then + print("========================================================================"); + print(""); + print("Get info of a key:"); + print(""); + print("> prosodyctl mod_"..module.name.." get JID NAME"); + print(""); + print("------------------------------------------------------------------------"); + print(""); + print("Usage:"); + print(""); + print(" When `JID` is a domain, it will list all the keys under that host."); + print(" When `JID` is a user's account, it fetches the key for that user."); + print(" If you supply both a host and a `NAME`, it fetches the key with `NAME`"); + print(" under that host.") + print(""); + print(" Output for a host is: DATE, ID, JID, NAME."); + print(""); + print(" Output for a user's account is: DATE, ID, URL."); + print(""); + print(" Output for a host with a valid `NAME` is: DATE, ID, URL."); + print(""); + print("========================================================================"); + elseif help_command == "date" then + print("========================================================================"); + print(""); + print("Get the time stamp of a key:"); + print(""); + print("> prosodyctl mod_"..module.name.." date JID NAME"); + print(""); + print("------------------------------------------------------------------------"); + print(""); + print("Usage:"); + print(""); + print(" Same as the `get` command but print only the birthday of the key."); + print(""); + print("========================================================================"); + elseif help_command == "id" then + print("========================================================================"); + print(""); + print("Print the ID of a key:"); + print(""); + print("> prosodyctl mod_"..module.name.." id JID NAME"); + print(""); + print("------------------------------------------------------------------------"); + print(""); + print("Usage:"); + print(""); + print(" Same as the `get` command but print only the ID of the key."); + print(""); + print("========================================================================"); + elseif help_command == "key" then + print("========================================================================"); + print(""); + print("Print the API key:"); + print(""); + print("> prosodyctl mod_"..module.name.." key JID NAME"); + print(""); + print("------------------------------------------------------------------------"); + print(""); + print("Usage:"); + print(""); + print(" Same as the `get` command but print only the key."); + print(""); + print(" The key has the format: ID/TOKEN"); + print(""); + print(" The `renew` command will generate a new token and revoke the old one."); + print(""); + print("========================================================================"); + elseif help_command == "url" then + print("========================================================================"); + print(""); + print("Print the URL of a key:"); + print(""); + print("> prosodyctl mod_"..module.name.." url JID NAME"); + print(""); + print("------------------------------------------------------------------------"); + print(""); + print("Usage:"); + print(""); + print(" Same as the `get` command but print only the URL of the key."); + print(""); + print("========================================================================"); + elseif help_command == "delete" then + print("========================================================================"); + print(""); + print("Delete a key by JID and NAME:"); + print(""); + print("> prosodyctl mod_"..module.name.." delete JID NAME"); + print(""); + print("------------------------------------------------------------------------"); + print(""); + print("Usage:"); + print(""); + print(" Same as `create` command but delete the key specified."); + print(""); + print("========================================================================"); + elseif help_command == "delete-id" then + print("========================================================================"); + print(""); + print("Delete API key by ID:"); + print(""); + print("> prosodyctl mod_"..module.name.." delete HOST ID"); + print(""); + print("------------------------------------------------------------------------"); + print(""); + print("Usage:"); + print(""); + print(" Input must be a valid host - cannot be a user's account."); + print(""); + print(" To get the ID of a key, use the `id` command."); + print(""); + print("========================================================================"); + end + return; + end - local host = table.remove(arg, 1); - if not prosody.hosts[host] then - print("Error: please supply a valid host"); + local username, domain = jid.prepped_split(table.remove(arg, 1)); + if not prosody.hosts[domain] then + print("Error: please supply a valid host."); return 1; end - require "core.storagemanager".initialize_host(host); - module.host = host; --luacheck: ignore 122/module + require "core.storagemanager".initialize_host(domain); + module.host = domain; --luacheck: ignore 122/module + usermanager.initialize_host(module.host); api_key_store = module:open_store("invite_api_keys", "map"); + api_key_store_kv = module:open_store("invite_api_keys"); if command == "create" then - local id = require "util.id".short(); - local token = require "util.id".long(); - api_key_store:set(nil, id, { - id = id; - token = token; - name = arg[1]; - created_at = os.time(); - allowed_methods = { GET = true, POST = true }; - }); - print(id.."/"..token); + local util_id = require "util.id".short(); + local util_token = require "util.id".long(); + local found = false; + local os_time = os.time(); + if username then + if usermanager.user_exists(username, module.host) then + get_value(function (id, info) + if username.."@"..module.host == info.jid then + date = datetime.datetime(info.created_at); + print("Found:"); + print(date, id, info.jid); + util_id = id; + util_token = info.token; + found = true; + end + end); + if found == false then + api_key_store:set(nil, util_id, { + id = util_id; + token = util_token; + name = nil; + jid = username.."@"..domain; + created_at = os_time; + allowed_methods = { GET = true, POST = true }; + }); + date = datetime.datetime(os_time); + print("Created:"); + print(date, util_id.."/"..util_token, username.."@"..domain); + end + return; + else + print("Error: "..username.."@"..domain.." does not exists."); + return 1; + end + elseif domain then + local arg_name = table.remove(arg, 1); + if not arg_name or arg_name == "" then + print("Error: key for host needs a `NAME`."); + return; + end + found = false; + get_value(function (id, info) + if domain == info.jid and arg_name == info.name then + date = datetime.datetime(info.created_at); + print("Found:"); + print(date, id, info.jid, info.name); + util_id = id; + util_token = info.token; + found = true; + end + end); + if found == false then + api_key_store:set(nil, util_id, { + id = util_id; + token = util_token; + name = arg_name; + jid = domain; + created_at = os_time; + allowed_methods = { GET = true, POST = true }; + }); + date = datetime.datetime(os_time); + print("Created:"); + print(date, util_id.."/"..util_token, domain, arg_name); + end + return; + end + elseif command == "renew" then + local util_token = require "util.id".long(); + local os_time = os.time(); + if username then + local found; + if usermanager.user_exists(username, module.host) then + get_value(function (id, info) + if username.."@"..module.host == info.jid then + api_key_store:set(nil, id, { + id = id; + token = util_token; + name = arg[1]; + jid = username.."@"..domain; + created_at = os_time; + allowed_methods = { GET = true, POST = true }; + }); + found = true; + date = datetime.datetime(os_time); + print("Re-newed:"); + print(date, id.."/"..util_token, info.jid); + end + end); + if not found then + print("Error: Could not find the key for "..username.."@"..domain); + print(""); + print("To make this API key, run:"); + print("prosodyctl "..module.name.." create "..username.."@"..domain); + return; + end + return; + else + print("Error: "..username.."@"..domain.." does not exists."); + return 1; + end + elseif domain then + local arg_name = table.remove(arg, 1); + if not arg_name or arg_name == "" then + print("Error: key for host needs a `NAME`."); + return 1; + end + found = false; + get_value(function (id, info) + if domain == info.jid and arg_name == info.name then + api_key_store:set(nil, id, { + id = id; + token = util_token; + name = arg_name; + jid = domain; + created_at = os_time; + allowed_methods = { GET = true, POST = true }; + }); + date = datetime.datetime(os_time); + print("Re-newed:"); + print(date, id.."/"..util_token, info.jid, info.name); + found = true; + end + end); + if not found then + date = datetime.datetime(os_time); + print("Error: Could not find "..arg_name.." in "..domain); + print(""); + print("To make this API key, run:"); + print("prosodyctl "..module.name.." create "..domain.." "..arg_name); + return; + end + return; + end + elseif command == "get" then + local name = table.remove(arg, 1); + local found = false; + if name and not username then + get_value(function (id, info) + if info.name == name then + print(date, id, get_url(id, info.token)); + found = true; + end + end); + if found == false then + print("Error: could not find "..name.." in "..domain); + print(""); + print("You can create it with:"); + print(""); + print("> prosodyctl "..module.name.." create "..domain.." "..name); + end + return; + elseif username then + get_value(function (id, info) + local j = jid.prepped_split(info.jid); + if j == username then + print(date, id, get_url(id, info.token)); + found = true; + end + end); + if found == false then + print("Error: could not find the key for "..username.."@"..domain); + print(""); + print("You can create it with:"); + print(""); + print("> prosodyctl "..module.name.." create "..username.."@"..domain); + end + return; + else + get_value(function (id, info) + if info.jid == module.host then + print(date, id, info.jid, info.name or "<unknown>"); + else + print(date, id, info.jid, info.name or ""); + end + end); + return; + end + elseif command == "date" then + local name = table.remove(arg, 1); + if name and not username then + get_value(function (id, info) + if info.name == name then + print(date); + end + end); + return; + elseif username then + get_value(function (id, info) + local j = jid.prepped_split(info.jid); + if j == username then + print(date); + end + end); + return; + else + get_value(function (id, info) + if info.jid == module.host then + print(date, info.jid, info.name or "<unknown>"); + else + print(date, info.jid); + end + end); + return; + end + elseif command == "id" then + local name = table.remove(arg, 1); + if name and not username then + get_value(function (id, info) + if info.name == name then + print(id); + end + end); + return; + elseif username then + get_value(function (id, info) + local j = jid.prepped_split(info.jid); + if j == username then + print(id); + end + end); + return; + else + get_value(function (id, info) + if info.jid == module.host then + print(id, info.jid, info.name or "<unknown>"); + else + print(id, info.jid); + end + end); + return; + end + elseif command == "key" then + local name = table.remove(arg, 1); + if name and not username then + get_value(function (id, info) + if info.name == name then + print(id.."/"..info.token); + end + end); + return; + elseif username then + get_value(function (id, info) + local j = jid.prepped_split(info.jid); + if j == username then + print(id.."/"..info.token); + end + end); + return; + else + get_value(function (id, info) + if info.jid == module.host then + print(id.."/"..info.token, info.jid, info.name or "<unknown>"); + else + print(id.."/"..info.token, info.jid); + end + end); + return; + end + elseif command == "url" then + local name = table.remove(arg, 1); + if name and not username then + get_value(function (id, info) + if info.name == name then + print(get_url(id, info.token)); + end + end); + return; + elseif username then + get_value(function (id, info) + local j = jid.prepped_split(info.jid); + if j == username then + print(get_url(id, info.token)); + end + end); + return; + else + get_value(function (id, info) + if info.jid == module.host then + print(get_url(id, info.token), info.jid, info.name or "<unknown>"); + else + print(get_url(id, info.token), info.jid); + end + end); + return; + end elseif command == "delete" then - local id = table.remove(arg, 1); - if not api_key_store:get(nil, id) then + local name = table.remove(arg, 1); + if name and not username then + get_value(function (id, info) + if info.name == name then + api_key_store:set(nil, id, nil); + print("Deleted:"); + print(date, id.."/"..info.token, info.jid, info.name or ""); + end + end); + return; + elseif username then + get_value(function (id, info) + local j = jid.prepped_split(info.jid); + if j == username then + api_key_store:set(nil, id, nil); + print("Deleted:"); + print(date, id.."/"..info.token, info.jid, info.name or ""); + end + end); + return; + else + print("Error: Needs a valid `JID`. Or a host and a `NAME`."); + end + elseif command == "delete-id" then + if username then + print("Error: Input must be a valid host - cannot be a user's account."); + return 1; + end + local arg_id = table.remove(arg, 1); + if not api_key_store:get(nil, arg_id) then print("Error: key not found"); return 1; - end - api_key_store:set(nil, id, nil); - elseif command == "list" then - local api_key_store_kv = module:open_store("invite_api_keys"); - for key_id, key_info in pairs(api_key_store_kv:get(nil) or {}) do - print(key_id, key_info.name or "<unknown>"); + else + get_value(function (id, info) + if arg_id == id then + api_key_store:set(nil, id, nil); + print("Deleted:"); + print(date, id, info.jid, info.name or ""); + end + end); + return; end else print("Unknown command - "..command);
--- a/mod_invites_register_web/README.markdown Tue Jul 25 11:01:58 2023 +0200 +++ b/mod_invites_register_web/README.markdown Wed Jul 26 18:45:41 2023 +0700 @@ -34,13 +34,6 @@ validates invite tokens. It also supports guiding the user through client download and configuration via mod_register_apps. -There is no specific configuration for this module (though it uses the -optional `site_name` to override the displayed site name. - -You may also set `webchat_url` to the URL of a web chat that will be linked -to after successful registration. If not specified but mod_conversejs is loaded -on the current host, it will default to the URL of that module. - This module depends on mod_invites_page solely for the case where an invalid invite token is received - it will redirect to mod_invites_page so that an appropriate error can be served to the user. @@ -49,3 +42,41 @@ loaded). As a consequence of this module being loaded, the default password policies will be enforced for all registrations on the server if not explicitly loaded or configured. + +Configuration +============= + +It uses the optional `site_name` to override the displayed site name. + +You can set `webchat_url` to the URL of a web chat that will be linked +to after successful registration. If not specified but mod_conversejs is loaded +on the current host, it will default to the URL of that module. + +You can use your own html templates with `invites_html_template`. Names of the +files MUST match the default. More over, you can offer multiple (human) +languages by adding the `&l=` to the URL. Meaning this module will serve +`register.html` for your default URL: +``` + + https://prosody.example.net/?=aowiefjoaij + +``` + +And if you have a `register.en.html` in the directory you have specified in +your config file, it will be served at: +``` + + https://prosody.example.net/?=aowiefjoaij&l=en + +``` + +So in your `register.html`, you can point to the English version by using an +`<a>` tag like this: +``` + + <a href="/?={token}&l=en">English</a> + +``` + +You can further customize your URL with [mod_invites_page] too. +
--- a/mod_invites_register_web/mod_invites_register_web.lua Tue Jul 25 11:01:58 2023 +0200 +++ b/mod_invites_register_web/mod_invites_register_web.lua Wed Jul 26 18:45:41 2023 +0700 @@ -34,10 +34,17 @@ local invites = module:depends("invites"); local invites_page = module:depends("invites_page"); +local templatePath = module:get_option_string("invites_html_template", "html"); +function template_get(sTemplate, sLang) + local template = sLang and templatePath.."/"..sTemplate.."."..sLang..".html" + or templatePath.."/"..sTemplate..".html"; + return assert(module:load_resource(template):read("*a")); +end + +local lang = nil; function serve_register_page(event) - local register_page_template = assert(module:load_resource("html/register.html")):read("*a"); - local query_params = event.request.url.query and http_formdecode(event.request.url.query); + lang = query_params and query_params.l; local invite = query_params and invites.get(query_params.t); if not invite then @@ -49,6 +56,7 @@ }; end + local register_page_template = template_get("register", lang); event.response.headers["Content-Type"] = "text/html; charset=utf-8"; local invite_page = render_html_template(register_page_template, { @@ -71,8 +79,8 @@ local user, password, token = form_data["user"], form_data["password"], form_data["token"]; local app_id = form_data["app_id"]; - local register_page_template = assert(module:load_resource("html/register.html")):read("*a"); - local error_template = assert(module:load_resource("html/register_error.html")):read("*a"); + local register_page_template = template_get("register", lang); + local error_template = template_get("register_error", lang); local invite = invites.get(token); if not invite then @@ -163,6 +171,8 @@ module:fire_event("user-registering", registering); if not registering.allowed then + prepped_username = nil; + password = nil; return render_html_template(error_template, { site_name = site_name; msg_class = "alert-danger"; @@ -201,9 +211,9 @@ }; end -- If recognised app, we serve a page that includes setup instructions - success_template = assert(module:load_resource("html/register_success_setup.html")):read("*a"); + success_template = template_get("register_success_setup", lang); else - success_template = assert(module:load_resource("html/register_success.html")):read("*a"); + success_template = template_get("register_success", lang); end -- Due to the credentials being served here, ensure that
--- a/mod_webpresence/README.markdown Tue Jul 25 11:01:58 2023 +0200 +++ b/mod_webpresence/README.markdown Wed Jul 26 18:45:41 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 Tue Jul 25 11:01:58 2023 +0200 +++ b/mod_webpresence/mod_webpresence.lua Wed Jul 26 18:45:41 2023 +0700 @@ -1,13 +1,159 @@ +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 + +-- 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, "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 +164,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; + }; });