Software /
code /
prosody-modules
Changeset
6211:750d64c47ec6 draft default tip
Merge
line wrap: on
line diff
--- a/mod_invites_api/README.markdown Tue Mar 18 00:19:25 2025 +0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,110 +0,0 @@ ---- -labels: -- 'Stage-Beta' -summary: 'Authenticated HTTP API to create invites' -... - -Introduction -============ - -This module is part of the suite of modules that implement invite-based -account registration for Prosody. The other modules are: - -- [mod_invites] -- [mod_invites_adhoc] -- [mod_invites_page] -- [mod_invites_register] -- [mod_invites_register_web] -- [mod_register_apps] - -For details and a full overview, start with the [mod_invites] documentation. - -Details -======= - -mod_invites_api provides an authenticated HTTP API to create invites -using mod_invites. - -You can use the command-line to create and manage API keys. - -Configuration -============= - -There are no specific configuration options for this module. - -All the usual [HTTP configuration options](https://prosody.im/doc/http) -can be used to configure this module. - -API usage -========= - -Step 1: Create an API key, with an optional name to help you remember what -it is for - -``` -$ prosodyctl mod_invites_api create example.com "My test key" -``` - -**Tip:** Remember to put quotes around your key name if it contains spaces. - -The command will print out a key: - -``` -HTwALnKL/73UUylA-2ZJbu9x1XMATuIbjWpip8ow1 -``` - -Step 2: Make a HTTP request to Prosody, containing the key - -``` -$ curl -v https://example.com:5281/invites_api?key=HTwALnKL/73UUylA-2ZJbu9x1XMATuIbjWpip8ow1 -``` - -Prosody will respond with a HTTP status code "201 Created" to indicate -creation of the invite, and per HTTP's usual rules, the URL of the created -invite page will be in the `Location` header: - -``` -< HTTP/1.1 201 Created -< Access-Control-Max-Age: 7200 -< Connection: Keep-Alive -< Access-Control-Allow-Origin: * -< Date: Sun, 13 Sep 2020 09:50:19 GMT -< Access-Control-Allow-Headers: Content-Type -< Access-Control-Allow-Methods: OPTIONS, GET -< Content-Length: 0 -< Location: https://example.com/invite?c-vhJjyB5Pb4HpAf -``` - -Sometimes for convenience, you may want to just visit the URL in the -browser. Append `&redirect=true` to the URL, and instead Prosody will -return a `303 See Other` response code, which will tell the browser to -redirect straight to the newly-created invite. This is super handy in a -bookmark :) - -If using the API programmatically, it is recommended to put the key in -the `Authorization` header if possible. This is quite simple: - -``` -Authorization: Bearer HTwALnKL/73UUylA-2ZJbu9x1XMATuIbjWpip8ow1 -``` - -You can also create an api key for a specific user: - -``` -prosodyctl mod_invites_api create user@example.com -``` - -Key management -============== - -To list all the available commands: - -``` -prosodyctl mod_invites_api help -``` - -To get help on a specific command for example `create`: - -``` -prosodyctl mod_invites_api help create -```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_invites_api/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,110 @@ +--- +labels: +- 'Stage-Beta' +summary: 'Authenticated HTTP API to create invites' +... + +Introduction +============ + +This module is part of the suite of modules that implement invite-based +account registration for Prosody. The other modules are: + +- [mod_invites] +- [mod_invites_adhoc] +- [mod_invites_page] +- [mod_invites_register] +- [mod_invites_register_web] +- [mod_register_apps] + +For details and a full overview, start with the [mod_invites] documentation. + +Details +======= + +mod_invites_api provides an authenticated HTTP API to create invites +using mod_invites. + +You can use the command-line to create and manage API keys. + +Configuration +============= + +There are no specific configuration options for this module. + +All the usual [HTTP configuration options](https://prosody.im/doc/http) +can be used to configure this module. + +API usage +========= + +Step 1: Create an API key, with an optional name to help you remember what +it is for + +``` +$ prosodyctl mod_invites_api create example.com "My test key" +``` + +**Tip:** Remember to put quotes around your key name if it contains spaces. + +The command will print out a key: + +``` +HTwALnKL/73UUylA-2ZJbu9x1XMATuIbjWpip8ow1 +``` + +Step 2: Make a HTTP request to Prosody, containing the key + +``` +$ curl -v https://example.com:5281/invites_api?key=HTwALnKL/73UUylA-2ZJbu9x1XMATuIbjWpip8ow1 +``` + +Prosody will respond with a HTTP status code "201 Created" to indicate +creation of the invite, and per HTTP's usual rules, the URL of the created +invite page will be in the `Location` header: + +``` +< HTTP/1.1 201 Created +< Access-Control-Max-Age: 7200 +< Connection: Keep-Alive +< Access-Control-Allow-Origin: * +< Date: Sun, 13 Sep 2020 09:50:19 GMT +< Access-Control-Allow-Headers: Content-Type +< Access-Control-Allow-Methods: OPTIONS, GET +< Content-Length: 0 +< Location: https://example.com/invite?c-vhJjyB5Pb4HpAf +``` + +Sometimes for convenience, you may want to just visit the URL in the +browser. Append `&redirect=true` to the URL, and instead Prosody will +return a `303 See Other` response code, which will tell the browser to +redirect straight to the newly-created invite. This is super handy in a +bookmark :) + +If using the API programmatically, it is recommended to put the key in +the `Authorization` header if possible. This is quite simple: + +``` +Authorization: Bearer HTwALnKL/73UUylA-2ZJbu9x1XMATuIbjWpip8ow1 +``` + +You can also create an api key for a specific user: + +``` +prosodyctl mod_invites_api create user@example.com +``` + +Key management +============== + +To list all the available commands: + +``` +prosodyctl mod_invites_api help +``` + +To get help on a specific command for example `create`: + +``` +prosodyctl mod_invites_api help create +```
--- a/mod_invites_page/README.markdown Tue Mar 18 00:19:25 2025 +0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ ---- -labels: -- 'Stage-Beta' -summary: 'Generate friendly web page for invitations' -rockspec: - dependencies: - - mod_register_apps - build: - copy_directories: - - html - - static -... - -Introduction -============ - -This module is part of the suite of modules that implement invite-based -account registration for Prosody. The other modules are: - -- [mod_invites] -- [mod_invites_adhoc] -- [mod_invites_register] -- [mod_invites_register_web] -- [mod_invites_api] -- [mod_register_apps] - -For details and a full overview, start with the [mod_invites] documentation. - -Details -======= - -mod_invites_page provides a unique web page for each generated invitation. -Without this module, Prosody will only be able to generate invite links as -`xmpp:` URIs (they look something like `xmpp:example.com?register;preauth=29Xbxr91`). -URIs will only work if the invited user already has an XMPP client installed. -This is usually not the case. - -This module transforms the URI into a friendly web page that can be shared -via any method (email, SMS, etc.), and opened in any browser. The page explains -the invitation and guides the user to set up their account using one of a -configurable list of XMPP clients (to configure the list, see mod_register_apps -documentation). - -Configuration -============= - -| Name | Description | Default | -|---------------------------|--------------------------------------------------------------------------------|-----------------------------------------------------| -| invites_page | The format of an invite page URL (must begin with `https://`) | `"https://{host}:5281/invites_page?{invite.token}"` | -| invites_registration_page | The format of an invite registration page URL (may be relative to invites_page)| `"register?t={invite.token}&c={app.id}"` | -| site_name | The friendly name of the server | `"example.com"` | -| invites_page_external | Set this to true if your invitation pages will be rendered by something else | `false` | - -The `invites_page` and `invites_registration_page` options are templates -that support a number of variables. The most useful being `{host}` and -`{invite.token}`. - -All the usual [HTTP configuration options](https://prosody.im/doc/http) -can be used to configure this module. In particular, if you run Prosody -behind a reverse proxy such as nginx or Apache, you will probably want -to set `http_external_url` so that Prosody knows what URLs should look -like for users. - -If you want to disable this module's built-in pages and use an external -invitation page generator (such as [ge0rg/easy-xmpp-invitation](https://github.com/ge0rg/easy-xmpp-invitation) -then set `invites_page_external = true` and set `invites_page` to the -appropriate URL for your installation.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_invites_page/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,78 @@ +--- +labels: +- 'Stage-Beta' +summary: 'Generate friendly web page for invitations' +rockspec: + dependencies: + - mod_register_apps + build: + copy_directories: + - html + - static +... + +Introduction +============ + +This module is part of the suite of modules that implement invite-based +account registration for Prosody. The other modules are: + +- [mod_invites] +- [mod_invites_adhoc] +- [mod_invites_register] +- [mod_invites_register_web] +- [mod_invites_api] +- [mod_register_apps] + +For details and a full overview, start with the [mod_invites] documentation. + +Details +======= + +mod_invites_page provides a unique web page for each generated invitation. +Without this module, Prosody will only be able to generate invite links as +`xmpp:` URIs (they look something like `xmpp:example.com?register;preauth=29Xbxr91`). +URIs will only work if the invited user already has an XMPP client installed. +This is usually not the case. + +This module transforms the URI into a friendly web page that can be shared +via any method (email, SMS, etc.), and opened in any browser. The page explains +the invitation and guides the user to set up their account using one of a +configurable list of XMPP clients (to configure the list, see mod_register_apps +documentation). + +For a complete experience one should also load +[mod_invites_register], [mod_invites_register_web], [mod_register_apps] and [mod_http_libjs] see [mod_invites] + +Configuration +============= + +| Name | Description | Default | +|---------------------------|--------------------------------------------------------------------------------|-----------------------------------------------------| +| invites_page | The format of an invite page URL (must begin with `https://`) | `"https://{host}:5281/invites_page?{invite.token}"` | +| invites_registration_page | The format of an invite registration page URL (may be relative to invites_page)| `"register?t={invite.token}&c={app.id}"` | +| site_name | The friendly name of the server | `"example.com"` | +| invites_page_external | Set this to true if your invitation pages will be rendered by something else | `false` | + +The `invites_page` and `invites_registration_page` options are templates +that support a number of variables. The most useful being `{host}` and +`{invite.token}`. + +All the usual [HTTP configuration options](https://prosody.im/doc/http) +can be used to configure this module. In particular, if you run Prosody +behind a reverse proxy such as nginx or Apache, you will probably want +to set `http_external_url` so that Prosody knows what URLs should look +like for users. + +If you want to disable this module's built-in pages and use an external +invitation page generator (such as [ge0rg/easy-xmpp-invitation](https://github.com/ge0rg/easy-xmpp-invitation) +then set `invites_page_external = true` and set `invites_page` to the +appropriate URL for your installation. + +Compatibility +============= + + Prosody-Version Status + --------------- --------------------- + trunk Works as of 24-12-08 + 0.12 Works
--- a/mod_invites_register_api/mod_invites_register_api.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_invites_register_api/mod_invites_register_api.lua Tue Mar 18 00:31:36 2025 +0700 @@ -75,39 +75,45 @@ if reset_for ~= prepped_username then return 403; -- Attempt to use reset invite for incorrect user end + local ok, err = usermanager.set_password(prepped_username, password, module.host); + if not ok then + module:log("error", "Unable to reset password for %s@%s: %s", prepped_username, module.host, err); + return 500; + end + module:fire_event("user-password-reset", user); elseif usermanager.user_exists(prepped_username, module.host) then return 409; -- Conflict - end + else + local registering = { + validated_invite = invite; + username = prepped_username; + host = module.host; + ip = request.ip; + allowed = true; + }; - local registering = { - validated_invite = invite; - username = prepped_username; - host = module.host; - ip = request.ip; - allowed = true; - }; + module:fire_event("user-registering", registering); - module:fire_event("user-registering", registering); - - if not registering.allowed then - return 403; - end + if not registering.allowed then + return 403; + end - local ok, err = usermanager.create_user(prepped_username, password, module.host); + local ok, err = usermanager.create_user(prepped_username, password, module.host); - if not ok then - local err_id = id.short(); - module:log("warn", "Registration failed (%s): %s", err_id, tostring(err)); - return 500; - end + if not ok then + local err_id = id.short(); + module:log("warn", "Registration failed (%s): %s", err_id, tostring(err)); + return 500; + end - module:fire_event("user-registered", { - username = prepped_username; - host = module.host; - source = "mod_"..module.name; - validated_invite = invite; - ip = request.ip; - }); + module:fire_event("user-registered", { + username = prepped_username; + host = module.host; + source = "mod_"..module.name; + validated_invite = invite; + ip = request.ip; + }); + end return json.encode({ jid = prepped_username .. "@" .. module.host;
--- a/mod_invites_register_web/README.markdown Tue Mar 18 00:19:25 2025 +0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ ---- -labels: -- 'Stage-Beta' -summary: 'Register accounts via the web using invite tokens' -rockspec: - dependencies: - - mod_invites_page - - mod_password_policy - - mod_register_apps - build: - copy_directories: - - html -... - -Introduction -============ - -This module is part of the suite of modules that implement invite-based -account registration for Prosody. The other modules are: - -- [mod_invites] -- [mod_invites_adhoc] -- [mod_invites_page] -- [mod_invites_register] -- [mod_invites_api] -- [mod_register_apps] - -For details and a full overview, start with the [mod_invites] documentation. - -Details -======= - -mod_invites_register_web implements a web-based registration form that -validates invite tokens. It also supports guiding the user through client -download and configuration via mod_register_apps. - -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. - -The module also depends on [mod_password_policy] (which will be automatically -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_template_html`. 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. -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_invites_register_web/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,90 @@ +--- +labels: +- 'Stage-Beta' +summary: 'Register accounts via the web using invite tokens' +rockspec: + dependencies: + - mod_invites_page + - mod_password_policy + - mod_register_apps + build: + copy_directories: + - html +... + +Introduction +============ + +This module is part of the suite of modules that implement invite-based +account registration for Prosody. The other modules are: + +- [mod_invites] +- [mod_invites_adhoc] +- [mod_invites_page] +- [mod_invites_register] +- [mod_invites_api] +- [mod_register_apps] + +For details and a full overview, start with the [mod_invites] documentation. + +Details +======= + +mod_invites_register_web implements a web-based registration form that +validates invite tokens. It also supports guiding the user through client +download and configuration via mod_register_apps. + +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. + +The module also depends on [mod_password_policy] (which will be automatically +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_template_html`. 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. + + +Compatibility +============= + +Prosody-Version Status +--------------- --------------------- +trunk Works as of 24-12-08 +0.12 Works
--- a/mod_invites_tracking/README.md Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_invites_tracking/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -20,11 +20,21 @@ Details ======= -Add to `modules_enabled`. +Add it to `modules_enabled`. + +Assuming file based storage the information will be stored at your storage location under `./invites_tracking/` Caveats ======= - The information is not deleted even when the associated user accounts are deleted. -- Currently, there is no way to make any use of that information. +- Currently, there is no integrated way to make use of that information. + +Compatibility +============= + +Prosody-Version Status +--------------- --------------------- +trunk Works as of 24-12-08 +0.12 unknown
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_ipcheck/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,21 @@ +--- +labels: +- 'Stage-Stable' +summary: 'XEP-0279: Server IP Check' +... + +Introduction +============ + +Sometimes for various reasons a client might want to know its IP address +as it appears to the server. This [simple +XEP](http://xmpp.org/extensions/xep-0279.html) allows the client to ask +the server for the IP address it is connected from. + +Compatibility +============= + + ----- ------- + 0.7 Works + 0.6 Works + ----- -------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_isolate_host/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,61 @@ +--- +labels: +- 'Stage-Beta' +summary: Prevent communication between hosts +... + +Introduction +============ + +In some environments it is desirable to isolate one or more hosts, and +prevent communication with external, or even other internal domains. + +Loading mod\_isolate\_host on a host will prevent all communication with +JIDs outside of the current domain, though it is possible to configure +exceptions. + +**Note:** if you just want to prevent communication with external +domains, this is possible without a plugin. See [Prosody: Disabling +s2s](http://prosody.im/doc/s2s#disabling) for more information. + +This module was sponsored by [Exa Networks](http://exa-networks.co.uk/). + +Configuration +============= + +To isolate all hosts by default, add the module to your global +modules\_enabled: + +``` {.lua} +modules_enabled = { + ... + "isolate_host"; + ... +} +``` + +Alternatively you can isolate a single host by putting a +modules\_enabled line under the VirtualHost directive: + +``` {.lua} +VirtualHost "example.com" +modules_enabled = { "isolate_host" } +``` + +After enabling the module, you can add further options to add exceptions +for the isolation: + + Option Description + -------------------------- ----------------------------------------------------------------------------------------- + isolate\_except\_domains A list of domains to allow communication with. + isolate\_except\_users A list of user JIDs allowed to bypass the isolation and communicate with other domains. + +**Note:** Admins of hosts are always allowed to communicate with other +domains + +Compatibility +============= + + ----- ------- + 0.9 Works + ----- -------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_jid_prep/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,29 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'Implement XEP-0328: JID Prep for clients' +... + +Introduction +============ + +This is a plugin that implements the JID prep protocol defined in +<https://xmpp.org/extensions/xep-0328.html> + +Details +======= + +JID prep requests can happen over XMPP using the protocol defined in the +document linked above, or alternatively over HTTP. Simply request: + + http://server:5280/jid_prep/USER@HOST + +The result will be the stringprepped JID, or a 400 Bad Request if the +given JID is invalid. + +Compatibility +============= + + ----- ------- + 0.9 Works + ----- -------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_limit_auth/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,35 @@ +--- +summary: Throttle authentication attempts with optional tarpit +... + +Introduction +============ + +This module lets you put a per-IP limit on the number of failed +authentication attempts. + +It features an optioanal +[tarpit](https://en.wikipedia.org/wiki/Tarpit_%28networking%29), i.e. +waiting some time before returning an "authentication failed" response. + +Configuration +============= + +``` {.lua} +modules_enabled = { + -- your other modules + "limit_auth"; +} + +limit_auth_period = 30 -- over 30 seconds + +limit_auth_max = 5 -- tolerate no more than 5 failed attempts + + -- Will only work with Prosody trunk: +limit_auth_tarpit_delay = 10 -- delay answer this long +``` + +Compatibility +============= + +Requires 0.9 or later. The tarpit feature requires Prosody trunk.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_limits_exception/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,26 @@ +--- +summary: Allow specified JIDs to bypass rate limits +... + +This module allows you to configure a list of JIDs that should be allowed to +bypass rate limit restrictions. + +It is designed for Prosody 0.11.x. Prosody 0.12.x supports this feature +natively. + +## Configuration + +First, enable this module by adding `"limits_exception"` to your +`modules_enabled` list. + +Next, configure a list of JIDs to exclude from rate limiting: + +``` +unlimited_jids = { "user1@example.com", "user2@example.net" } +``` + +## Compatibility + +Made for Prosody 0.11.x only. + +Using this module with Prosody trunk/0.12 may cause unexpected behaviour.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_list_active/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,34 @@ +Description +=========== + +This module lists those users, who **have** used their account in a +defined time-frame, basically the inverse of [mod_list_inactive]. + +Dependencies +============ + +[mod_lastlog] must be enabled to collect the data used by this module. + +Usage +===== + + prosodyctl mod_list_active example.com time [format] + +Time is a number followed by 'day', 'week', 'month' or 'year' + +Formats are: + + --------- ------------------------------------------------ + default `user@example.com` + event `user@example.com last action` + --------- ------------------------------------------------ + +Example +======= + + prosodyctl mod_list_active example.com 1year + +Help +==== + + prosodyctl mod_list_active
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_list_inactive/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,35 @@ +Description +=========== + +This module lists those users, who haven't used their account in a +defined time-frame. + +Dependencies +============ + +[mod_lastlog] must be enabled to collect the data used by this module. + +Usage +===== + + prosodyctl mod_list_inactive example.com time [format] + +Time is a number followed by 'day', 'week', 'month' or 'year' + +Formats are: + + --------- ------------------------------------------------ + delete `user:delete"user@example.com" -- last action` + default `user@example.com` + event `user@example.com last action` + --------- ------------------------------------------------ + +Example +======= + + prosodyctl mod_list_inactive example.com 1year + +Help +==== + + prosodyctl mod_list_inactive
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_listusers/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,15 @@ +--- +labels: +- 'Stage-Obsolete' +--- + +::: {.alert .alert-warning} +As of Prosody 0.12.x, `prosodyctl shell user list HOST` can be used +instead of this module. +::: + +This module adds a command to `prosodyctl` for listing users. + +``` sh +prosodyctl mod_listusers +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_log_auth/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,49 @@ +--- +labels: +- 'Stage-Stable' +summary: Log failed authentication attempts with their IP address +... + +Introduction +============ + +Prosody doesn't write IP addresses to its log file by default for +privacy reasons (unless debug logging is enabled). + +This module enables logging of the IP address in a failed authentication +attempt so that those trying to break into accounts for example can be +blocked. + +fail2ban configuration +====================== + +fail2ban is a utility for monitoring log files and automatically +blocking "bad" IP addresses at the firewall level. + +With this module enabled in Prosody you can use the following example +configuration for fail2ban: + + # /etc/fail2ban/filter.d/prosody-auth.conf + # Fail2Ban configuration file for prosody authentication + [Definition] + failregex = Failed authentication attempt \(not-authorized\) for user .* from IP: <HOST> + ignoreregex = + +And at the appropriate place (usually the bottom) of +/etc/fail2ban/jail.conf add these lines: + + [prosody] + enabled = true + port = 5222 + filter = prosody-auth + logpath = /var/log/prosody/prosody*.log + maxretry = 6 + +Compatibility +------------- + + ------- -------------- + trunk Works + 0.9 Works + 0.8 Doesn't work + ------- --------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_log_mark/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,7 @@ +--- +summary: Log a message once per minute +... + +This module sends `-- MARK --` to the log once per minute. This may be +useful to give a sense of how busy the server is or see that logging and +timers are still working.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_log_rate/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,33 @@ +--- +summary: Collect statistics on rate of log messages +... + +Introduction +============ + +If you ever wanted to collect statistics on the number of log messages, +this is the module for you! + +Setup +===== + +After [installing the module](https://prosody.im/doc/installing_modules) +and adding it to modules\_enabled as most other modules, you also need +to add it to your logging config: + +``` {.lua} +log = { + -- your other log sinks + info = "/var/log/prosody/prosody.log" + -- add this: + { to = "measure" }; +} +``` + +Then log messages will be counted by +[statsmanager](https://prosody.im/doc/developers/core/statsmanager). + +Compatibility +============= + +Reqires Prosody 0.10 or above.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_log_ringbuffer/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,123 @@ +--- +labels: +- 'Stage-Beta' +summary: 'Log to in-memory ringbuffer' +... + +Introduction +============ + +Sometimes debug logs are too verbose for continuous logging to disk. However +occasionally you may be interested in the debug logs when a certain event occurs. + +This module allows you to store all logs in a fixed-size buffer in Prosody's memory, +and dump them to a file whenever you want. + +# Configuration + +First of all, you need to load the module by adding it to your global `modules_enabled`: + +``` {.lua} +modules_enabled = { + ... + "log_ringbuffer"; + ... +} +``` + +By default the module will do nothing - you need to configure a log sink, using Prosody's +usual [logging configuration](https://prosody.im/doc/advanced_logging). + +``` {.lua} +log = { + -- Log errors to a file + error = "/var/log/prosody/prosody.err"; + + -- Log debug and higher to a 2MB buffer + { to = "ringbuffer", size = 1024*1024*2, filename_template = "debug-logs-{pid}-{count}.log", signal = "SIGUSR2" }; +} +``` + +The possible fields of the logging config entry are: + +`to` +: Set this to `"ringbuffer"`. + +`levels` +: The log levels to capture, e.g. `{ min = "debug" }`. By default, all levels are captured. + +`size` +: The size, in bytes, of the buffer. When the buffer fills, + old data will be overwritten by new data. + +`lines` +: If specified, preserves the latest N complete lines in the + buffer. The `size` option is ignored when this option is set. + +`filename` +: The name of the file to dump logs to when triggered. + +`filename_template` +: This parameter may optionally be specified instead of `filename. It + may contain a number of variables, described below. Defaults to + `"{paths.data}/ringbuffer-logs-{pid}-{count}.log"`. + +Only one of the following triggers may be specified: + +`signal` +: A signal that will cause the buffer to be dumped, e.g. `"SIGUSR2"`. + Do not use any signal that is used by any other Prosody module, to + avoid conflicts. + +`event` +: Alternatively, the name of a Prosody global event that will trigger + the logs to be dumped, e.g. `"config-reloaded"`. + +## Filename variables + +If `filename_template` is specified instead of `filename`, it may contain +any of the following variables in curly braces, e.g. `{pid}`. + +`pid` +: The PID of the current process + +`count` +: A counter that begins at 0 and increments for each dump made by + the current process. + +`time` +: The unix timestamp at which the dump is made. It can be formatted + to human-readable local time using `{time|yyyymmdd}` and `{time|hhmmss}`. + +`paths` +: Allows access to Prosody's known filesystem paths, use e.g. `{paths.data}` + for the path to Prosody's data directory. + +The filename does not have to be unique for every dump - if a file with the same +name already exists, it will be appended to. + +## Integration with mod_debug_traceback + +This module can be used in combination with [mod_debug_traceback] so that debug +logs are dumped at the same time as the traceback. Use the following configuration: + +``` {.lua} +log = { + --- + -- other optional logging config here -- + --- + + { + to = "ringbuffer"; + filename_template = "{paths.data}/traceback-{pid}-{count}.log"; + event = "debug_traceback/triggered"; + }; +} +``` + +If the filename template matches the traceback path, both logs and traceback will +be combined into the same file. Of course separate files can be specified if preferred. + +# Compatibility + +0.12 and later.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_log_slow_events/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,49 @@ +--- +labels: +- 'Stage-Stable' +summary: Log warning when event handlers take too long +... + +Introduction +============ + +Most activities in Prosody take place within our built-in events framework, for +example stanza processing and HTTP request handling, authentication, etc. + +Modules are able to execute code when an event occurs, and they should return +as quickly as possible. Poor performance (e.g. slow or laggy server) can be caused +by event handlers that are slow to respond. + +This module is able to monitor how long each event takes to be processed, and +logs a warning if an event takes above a certain amount of time, including +providing any details about the event such as the user or stanza that triggered it. + +The aim is to help debug why a server may not be as responsive as it should be, +and ultimately which module is to blame for that. + +Configuration +====================== + +There is a single configuration option: + +``` + -- Set the number of seconds an event may take before + -- logging a warning (fractional values are ok) + log_slow_events_threshold = 0.5 +``` + +Metrics +======= + +In addition to the log messages, a new 'slow_events' metric will be exported to +your configured stats backend (if any). + +Compatibility +------------- + + ------- -------------- + trunk Works + 0.10 Works + 0.9 Doesn't work + 0.8 Doesn't work + ------- --------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_mam/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,8 @@ +--- +labels: +- 'Stage-Obsolete' +summary: 'XEP-0313: Message Archive Management' +superseded_by: mod_mam +... + +Since Prosody 0.10, this module is [included in Prosody](https://prosody.im/doc/modules/mod_mam), you will be redirected there shortly.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_mam_adhoc/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,34 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'Ad-hoc interface to Message Archive Management Settings' +... + +Introduction +============ + +This module complements mod\_mam by allowing clients to change archiving +preferences through an Ad-hoc command. + +Details +======= + +When enabled, an "Archive Settings" command should appear in the list of +Ad-hoc commands available. This allows the user to change default policy +(always, never, roster) and which JIDs to always store or never store. + +Usage +===== + +First copy the module to the prosody plugins directory. + +Then add "mam\_adhoc" to your modules\_enabled list: + +``` {.lua} +modules_enabled = { + -- ... + "mam", + "mam_adhoc", + -- ... +} +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_mam_muc/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,8 @@ +--- +labels: +- 'Stage-Obsolete' +summary: 'XEP-0313: Message Archive Management for MUC' +superseded_by: mod_muc_mam +... + +Since Prosody 0.11, this module is [included in Prosody](https://prosody.im/doc/modules/mod_muc_mam), you will be redirected there shortly.
--- a/mod_measure_client_features/mod_measure_client_features.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_measure_client_features/mod_measure_client_features.lua Tue Mar 18 00:31:36 2025 +0700 @@ -1,13 +1,11 @@ module:set_global(); -local measure = require"core.statsmanager".measure; +local statsmanager = require "prosody.core.statsmanager"; + +local measure_features = module:metric("gauge", "features", "", "Features advertized by clients", {"feature"}); local disco_ns = "http://jabber.org/protocol/disco#info"; -local counters = { - total = measure("amount", "client_features.total"); -}; - module:hook("stats-update", function () local total = 0; local buckets = {}; @@ -26,11 +24,11 @@ total = total + 1; end end + statsmanager.cork(); + measure_features:clear(); for bucket, count in pairs(buckets) do - if counters[bucket] == nil then - counters[bucket] = measure("amount", "client_features."..bucket); - end - counters[bucket](count); + measure_features:with_labels(bucket):add(count); end - counters.total(total); + measure_features:with_labels("total"):add(total); + statsmanager.uncork(); end)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_measure_client_presence/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,11 @@ +--- +labels: +- Statistics +summary: Collect statistics on user presences +--- + +Description +=========== + +This module measures the number of resources of a certain show (available, +away, etc.) currently connected, and reports using Prosody 0.10 APIs
--- a/mod_measure_client_presence/mod_measure_client_presence.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_measure_client_presence/mod_measure_client_presence.lua Tue Mar 18 00:31:36 2025 +0700 @@ -1,6 +1,8 @@ module:set_global(); -local measure = require"core.statsmanager".measure; +local statsmanager = require "prosody.core.statsmanager"; + +local measure_presences = module:metric("gauge", "presence", "client", "Presence show used by clients", {"show"}); local valid_shows = { available = true, @@ -11,16 +13,6 @@ unavailable = true, } -local counters = { - available = measure("amount", "client_presence.available"), - chat = measure("amount", "client_presence.chat"), - away = measure("amount", "client_presence.away"), - dnd = measure("amount", "client_presence.dnd"), - xa = measure("amount", "client_presence.xa"), - unavailable = measure("amount", "client_presence.unavailable"), - invalid = measure("amount", "client_presence.invalid"); -}; - module:hook("stats-update", function () local buckets = { available = 0, @@ -31,7 +23,7 @@ unavailable = 0, invalid = 0, }; - for _, session in pairs(full_sessions) do + for _, session in pairs(prosody.full_sessions) do local status = "unavailable"; if session.presence then status = session.presence:get_child_text("show") or "available"; @@ -42,7 +34,10 @@ buckets.invalid = buckets.invalid + 1; end end + statsmanager.cork(); + measure_presences:clear(); for bucket, count in pairs(buckets) do - counters[bucket](count) + measure_presences:with_labels(bucket):add(count); end + statsmanager.uncork(); end)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_measure_conn_buffers/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,12 @@ +--- +labels: +- Statistics +- Stage-Alpha +summary: Measure connection buffer usage +... + +Description +=========== + +This module measures data that is buffered by Prosody, waiting to be written +to sockets.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_measure_conn_buffers/mod_measure_conn_buffers.lua Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,31 @@ +module:set_global(); + +local measure_total_pending_tx = module:measure("total_pending_tx", "amount"); + +local server = require "net.server"; + +if server.get_backend() ~= "epoll" or not server.loop.fds then + module:log_status("error", "This module is not compatible with your network_backend, only epoll is supported"); + return; +end + +local fds = server.loop.fds; + +module:hook("stats-update", function () + local pending_tx = 0; + for _, conn in pairs(fds) do + local buffer = conn.writebuffer; + if buffer then + if type(buffer) == "string" then + pending_tx = pending_tx + #buffer; + elseif buffer._length then -- dbuffer + pending_tx = pending_tx + buffer._length; + else -- simple table + for i = 1, #buffer do + pending_tx = pending_tx + #buffer[i]; + end + end + end + end + measure_total_pending_tx(pending_tx); +end);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_measure_cpu/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,10 @@ +--- +labels: +- Statistics +summary: Measure CPU usage +... + +Description +=========== + +This module measures CPU usage and reports using Prosody 0.10 APIs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_measure_memory/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,10 @@ +--- +labels: +- Statistics +summary: Measure memory usage +--- + +Description +=========== + +This module measures memory usage and reports using Prosody 0.10 APIs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_measure_message_length/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,1 @@ +Simple module that collects statistics on message length in bytes, word count and line count.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_measure_muc/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,38 @@ +--- +labels: +- Statistics +summary: Collect statistics on Grout Chat +... + +Description +=========== + +This module collects statistics from group chat component. + +It collects current count of hidden, persistent, archive-enabled, password +protected rooms. The current count of room is also exposed (hidden+public). + + +Configuration +============= + +mod\_measure\_muc must be load on MUC components (not globally): + +```lua +Component "conference.example.com" "muc" + modules_enabled = { + "measure_muc"; + } +``` + +See also the documentation of Prosody’s [MUC module](https://prosody.im/doc/modules/mod_muc). + +Compatibility +============= + + ------- ------------- + trunk Works + 0.11 Works + 0.10 Unknown + 0.9 Does not work + ------- -------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_measure_process/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,27 @@ +--- +labels: +- Statistics +summary: Measure process resource use metrics (cpu, memory, file descriptors) +--- + +Description +=========== + +This module exposes process resource use metrics similar to those exposed by +default when using a Prometheus client library. Specifically, the following +metrics are exposed: + +- CPU use +- Resident set and virtual memory size +- Number of open file descriptors and their limit + +This module uses the new OpenMetrics API and thus requires a recent version +of Prosody trunk (0.12+). + +Compatibility +============= + + ------- ------------- + trunk Works + 0.11 Does not work + ------- -------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_measure_registration/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,11 @@ +--- +labels: +- Statistics +summary: Collect statistics on user registration +--- + +Description +=========== + +This module measures the rate at which users register on the server (through +providers like `mod_register_ibr` or `mod_register_web`).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_measure_stanza_counts/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,11 @@ +--- +labels: +- Statistics +summary: Collect statistics on number of stanzas processed +--- + +Description +=========== + +This module measures the number of stanzas handled and reports using +Prosody 0.12+ APIs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_measure_storage/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,25 @@ +--- +labels: +- 'Stage-Alpha' +- Statistics +summary: Measure storage API operations +--- + +Introduction +============ + +This module collects statistics from storage operations. + +Configuration +============= + +Enable tagged metrics, whatever that is: + +``` {.lua} +measure_storage_tagged_metric = true +``` + +Compatibility +============= + +Requires 0.10
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_message_logging/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,34 @@ +--- +labels: +- 'Stage-Beta' +summary: 'Log/archive all user messages' +... + +Introduction +============ + +Often service administrators need to log their users' messages for +reasons such as auditing and compliance. This module simply logs user +messages to simple text files, which can be easily searched, archived or +removed on a regular basis. + +Usage +===== + +Simply load the module and it will start logging. Reloading Prosody +(e.g. with a SIGHUP) will cause it to close any open logs (and re-open +them if necessary). + +Configuration +============= + + Option name Description + ----------------------- ----------------------------------------------------------------------------------------------------------------------- + message\_logging\_dir The directory to save message logs in. Default is to create a 'message\_logs' subdirectory inside the data directory. + +Compatibility +============= + + ----- ------- + 0.9 Works + ----- -------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_migrate/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,51 @@ +--- +summary: prosodyctl cross storage driver migration tool +... + +Introduction +============ + +This module adds a command to `prosodyctl` for copying data between +storage drivers. + +Usage +===== + + prosodyctl mod_migrate example.com <source-store>[-<store-type>] <target-driver> [users]* + +`<source-store>` would be e.g. `accounts` or `private`. To migrate +archives, the optional suffix `<store-type>` would be set to `archive`, +so e.g. `archive2-archive` or `muc_log-archive`. Multiple stores can be +given if separated by commas. + +`<target-driver>` is the storage driver to copy data to, sans the +`mod_storage_` prefix. + +`mod_migrate` tries to request a list of users from `usermanager`, but +this does not always work. If so, you can supply usernames as arguments +after the target driver. + +The process is something like this: + +1. Decide on the future configuration and add for example SQL + connection details to your prosody config, but don't change the + `store` option yet. +2. With Prosody shut down, run + `prosodyctl mod_migrate example.com accounts sql` +3. Repeat for each store, substituting 'accounts'. E.g. vcards, + private... +4. Change the [`storage` configuration](https://prosody.im/doc/storage) + to use the new driver. +5. Start prosody again. + +Examples +======== + +``` sh +prosodyctl mod_migrate example.com accounts,roster,private,vcard sql +``` + +Compatibility +============= + +Should work with 0.8 and later.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_minimix/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,51 @@ +# Account based MUC joining + +Normally when joining a MUC groupchat, it is each individual client that +joins. This means their presence in the group is tied to the session, +which can be short-lived or unstable, especially in the case of mobile +clients. + +This has a few problems. For one, for every message to the groupchat, a +copy is sent to each joined client. This means that at the account +level, each message would pass by once for each client that is joined, +making it difficult to archive these messages in the users personal +archive. + +A potentially better approach would be that the user account itself is +the entity that joins the groupchat. Since the account is an entity that +lives in the server itself, and the server tends to be online on a good +connection most of the time, this may improve the experience and +simplify some problems. + +This is one of the essential changes in the MIX architecture, which is +being designed to replace MUC. + +`mod_minimix` is an experiment meant to determine if things can be +improved without replacing the entire MUC standard. It works by +pretending to each client that nothing is different and that they are +joining MUCs directly, but behind the scenes, it arranges it such that +only the account itself joins each groupchat. Which sessions have joined +which groups are kept track of. Groupchat messages are then forked to +those sessions, similar to how normal chat messages work. + +## Known issues + +- You can never leave. +- You will never see anyone leave. +- Being kicked is not handled. + +## Unknown issues + +- Probably many. + +## TODO + +- Integrate with bookmarks +- tracking outgoing presence +- leaving rooms +- nickname management +- bookmark sync + +# Compatibility + +Briefly tested with Prosody trunk (as of this writing).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_activity/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,17 @@ +--- +summary: 'XEP-0502 (MUC Activity Indicator) implementation' +... + +This module provides an implementation of [XEP-0502 (MUC Activity Indicator)](https://xmpp.org/extensions/xep-0502.html) for Prosody. + +To enable it, load it on a MUC host, for example: + +```lua +Component "chat.domain.example" "muc" + modules_enabled = { "muc_activity" } +``` + +When this module is loaded, it will expose the average number of messages per hour for all public MUCs. +The number is calculated over a 24 hour window. + +Note that this module may impact server performance on servers with many MUCs.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_activity/mod_muc_activity.lua Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,87 @@ +local os_time = os.time; +local math_floor = math.floor; +local store = module:open_store("muc_activity", "keyval"); + +local accumulator = {}; + +local field_var = "{urn:xmpp:muc-activity}message-activity"; + +module:hook("muc-room-destroyed", function (event) + local jid = event.room.jid; + module:log("debug", "deleting activity data for destroyed muc %s", jid); + store:set_keys(jid, {}); + accumulator[jid] = nil; +end) + +module:hook("muc-occupant-groupchat", function (event) + local jid = event.room.jid; + if not event.room:get_persistent() then + -- we do not count stanzas in non-persistent rooms + if accumulator[jid] then + -- if we have state for the room, drop it. + store:set_keys(jid, {}); + accumulator[jid] = nil; + end + + return + end + + if event.stanza:get_child("body") == nil then + -- we do not count stanzas without body. + return + end + + module:log("debug", "counting stanza for MUC activity in %s", jid); + accumulator[jid] = (accumulator[jid] or 0) + 1; +end) + +local function shift(data) + for i = 1, 23 do + data[i] = data[i+1] + end +end + +local function accumulate(data) + if data == nil then + return 0; + end + local accum = 0; + for i = 1, 24 do + local v = data[i]; + if v ~= nil then + accum = accum + v + end + end + return accum; +end + +module:hourly("muc-activity-shift", function () + module:log("info", "shifting MUC activity store forward by one hour"); + for jid in store:users() do + local data = store:get(jid); + local new = accumulator[jid] or 0; + shift(data); + data[24] = new; + accumulator[jid] = nil; + store:set(jid, data); + end + + -- All remaining entries in the accumulator are non-existent in the store, + -- otherwise they would have been removed earlier. + for jid, count in pairs(accumulator) do + store:set(jid, { [24] = count }); + end + accumulator = {}; +end) + +module:hook("muc-disco#info", function(event) + local room = event.room; + local jid = room.jid; + if not room:get_persistent() or not room:get_public() or room:get_members_only() or room:get_password() ~= nil then + module:log("debug", "%s is not persistent or not public, not injecting message activity", jid); + return; + end + local count = accumulate(store:get(jid)) / 24.0; + table.insert(event.form, { name = field_var, label = "Message activity" }); + event.formdata[field_var] = tostring(count); +end);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_adhoc_bots/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,22 @@ +--- +labels: +- Stage-Alpha +summary: Install adhoc command bots in MUCs +--- + +# Introduction + +This module allows you to "install" bots on a MUC service (via config for +now, via adhoc command and on just one MUC to follow). All the adhoc commands +defined on the bot become adhoc commands on the service's MUCs, and the bots +can send XEP-0356 messages to the MUC to send messages as any participant. + +# Configuration + +List all bots to install. You must specify full JID. + + adhoc_bots = { "some@bot.example.com/bot" } + +And enable the module on the MUC service as usual + + modules_enabled = { "muc_adhoc_bots" }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_anonymize_moderation_actions/LICENSE Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,235 @@ +GNU AFFERO GENERAL PUBLIC LICENSE +Version 3, 19 November 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + + Preamble + +The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. + +A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. + +The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. + +An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. + +The precise terms and conditions for copying, distribution and modification follow. + + TERMS AND CONDITIONS + +0. Definitions. + +"This License" refers to version 3 of the GNU Affero General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based on the Program. + +To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. + +1. Source Code. +The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. + +A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +2. Basic Permissions. +All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. +No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. + +4. Conveying Verbatim Copies. +You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. +You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". + + c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. + +6. Conveying Non-Source Forms. +You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: + + a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. + + d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. + +"Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. + +7. Additional Terms. +"Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or authors of the material; or + + e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. + +All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. + +8. Termination. + +You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. + +9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. + +11. Patents. + +A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. + +A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. + +13. Remote Network Interaction; Use with the GNU General Public License. + +Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. + +Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. + +14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. + +15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. + +You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see <http://www.gnu.org/licenses/>.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_anonymize_moderation_actions/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,32 @@ +<!-- +SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/> +SPDX-License-Identifier: AGPL-3.0-only +--> +# mod_muc_anonymize_moderation_actions + +This modules allows to anonymize affiliation and role changes in MUC rooms. + +Enabling this module on a MUC Virtualhost will add a settings in the roomconfig form. +When the feature is enabled, when a moderator changes the role or affiliation of an occupant (kick, ban, ...) their name will be removed from the broadcasted message, to not disclose who did the moderation action. + +This is particularly usefull to prevent some revenge when a moderator bans someone. + +This module is under AGPL-3.0 license. + +It was tested on Prosody 0.12.x. + +## Configuration + +Just enable the module on your MUC VirtualHost. +The feature will be accessible throught the room configuration form. + +You can tweak the position of the settings in the MUC configuration form using `anonymize_moderation_actions_form_position`. +This value will be passed as priority for the "muc-config-form" hook, so you can move field up by increasing the value, or down by decreasing the value. + +By default, the field will be between muc#roomconfig_changesubject and muc#roomconfig_moderatedroom (default value is `78`). + +``` lua +VirtualHost "muc.example.com" + modules_enabled = { "muc_anonymize_moderation_actions" } + anonymize_moderation_actions_form_position = 96 +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_anonymize_moderation_actions/mod_muc_anonymize_moderation_actions.lua Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,76 @@ +-- mod_muc_anonymize_moderation_actions +-- +-- SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/> +-- SPDX-License-Identifier: AGPL-3.0-only + +-- form_position: the position in the room config form (this value will be passed as priority for the "muc-config-form" hook). +-- By default, field will be between muc#roomconfig_changesubject and muc#roomconfig_moderatedroom +local form_position = module:get_option_number("anonymize_moderation_actions_form_position") or 80-2; + +local function get_anonymize_moderation_actions(room) + return room._data.anonymize_moderation_actions or false; +end + +local function set_anonymize_moderation_actions(room, anonymize_moderation_actions) + anonymize_moderation_actions = anonymize_moderation_actions and true or nil; + if get_anonymize_moderation_actions(room) == anonymize_moderation_actions then return false; end + room._data.anonymize_moderation_actions = anonymize_moderation_actions; + return true; +end + +-- Config form declaration +local function add_form_option(event) + table.insert(event.form, { + name = "muc#roomconfig_anonymize_moderation_actions"; + type = "boolean"; + label = "Anonymize moderation actions"; + desc = "When this is enabled, moderation actions will be anonymized, to avoid disclosing who is banning/kicking/… occupants."; + value = get_anonymize_moderation_actions(event.room); + }); +end + +local function config_submitted(event) + set_anonymize_moderation_actions(event.room, event.value); +end + +local function remove_actor(event) + if (event.room and get_anonymize_moderation_actions(event.room)) then + event.actor = nil; + end +end + +local function remove_moderate_actor(event) + local room, announcement, tombstone = event.room, event.announcement, event.tombstone; + if not get_anonymize_moderation_actions(room) then + return; + end + + local moderated = announcement:find("{urn:xmpp:fasten:0}apply-to/{urn:xmpp:message-moderate:0}moderated"); + if moderated then + module:log("debug", "We must anonymize the moderation announcement for stanza %s", event.stanza_id); + -- FIXME: XEP-0245 has changed. + -- urn:xmpp:message-moderate:0 requires a "by" attribute + -- urn:xmpp:message-moderate:1 do not require the "by" attribute + -- So, for now, settings the room jid, as we only implement urn:xmpp:message-moderate:0. + moderated.attr.by = room.jid; + moderated:remove_children("occupant-id", "urn:xmpp:occupant-id:0"); + end + + if tombstone then + local moderated = tombstone:get_child("moderated", "urn:xmpp:message-moderate:0"); + if moderated then + module:log("debug", "We must anonymize the moderation tombstone for stanza %s", event.stanza_id); + -- FIXME: XEP-0245 has changed. + -- urn:xmpp:message-moderate:0 requires a "by" attribute + -- urn:xmpp:message-moderate:1 do not require the "by" attribute + -- So, for now, settings the room jid, as we only implement urn:xmpp:message-moderate:0. + moderated.attr.by = room.jid; + moderated:remove_children("occupant-id", "urn:xmpp:occupant-id:0"); + end + end +end + +module:hook("muc-config-submitted/muc#roomconfig_anonymize_moderation_actions", config_submitted); +module:hook("muc-config-form", add_form_option, form_position); +module:hook("muc-broadcast-presence", remove_actor); +module:hook("muc-moderate-message", remove_moderate_actor);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_archive/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,43 @@ +--- +labels: +- 'Stage-Beta' +summary: Log MUC messages to disk +... + +# Introduction + +This module logs the conversations of chatrooms running on the server to Prosody's data store. + +This is a fork of [mod_muc_log](https://modules.prosody.im/mod_muc_log.html) which uses the newer storage API. +This allows you to also log messages to a SQL backend. + +## Changes between mod_muc_archive and mod_muc_log: + +- Use newer module storage API so that you can also store in SQL +- Adhere to config option `muc_log_all_rooms` (also used by mod_muc_mam) +- Add affiliation information in the logged stanza +- Remove code that set (and then removed) an "alreadyJoined" dummy element + +NOTE: The changes are unlikely to be entirely backwards compatible because the stanza +being logged is no longer wrapped with `<stanza time=...>`. + +Details +======= + +mod\_muc\_archive must be loaded individually for the components that need it. + +Assuming you have a MUC component already running on +conference.example.org then you can add muc\_archive to it like so: + + Component "conference.example.org" "muc" + modules_enabled = { + "muc_archive"; + } + + +Compatibility +============= + + ------ ----- + 0.11 Works + ------ -----
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_auto_member/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,33 @@ +--- +labels: +- 'Stage-Beta' +summary: "Automatically register new MUC participants as members" +... + +# Introduction + +This module automatically makes anybody who joins a MUC become a registered +member. This can be useful for certain use cases. + +Note: there is no automatic cleanup of members. If you enable this on a server +with busy public channels, your member list will perpetually increase in size. + +Also, there is currently no per-room option for this behaviour. That may be +added in the future, along with membership expiry. + +# Configuration + +There is currently no configuration for this module. The module should be +enabled on your MUC component, i.e. in the modules_enabled option under your +Component: + +``` {.lua} +Component "conference.example.com" "muc" + modules_enabled = { + "muc_auto_member"; + } +``` + +# Compatibility + +0.12 and later.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_badge/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,40 @@ +--- +depends: +- 'mod\_http' +- 'mod\_muc' +provides: +- http +title: 'mod\_muc\_badge' +--- + +# Introduction + +This module generates a badge for MUC rooms at a HTTP URL like +`https://conference.example.com:5281/muc_badge/room@conference.example.org` +containing the number of occupants. + +Inspiration +: <https://opkode.com/blog/xmpp-chat-badge/> + +# Configuration + + Option Type Default + ------------------ -------- -------------------------- + `badge_count` string `"%d online"` + `badge_template` string A SVG image (see source) + +The template must be valid XML. If it contains `{label}` then this is +replaced by `badge_label`, similarly, `{count}` is substituted by +`badge_count` with `%d` changed to the number of occupants. + +Details of the HTTP URL is determined by [standard Prosody HTTP server +configuration][doc:http]. + +# Example + +```lua +Component "conference.example.com" "muc" +modules_enabled = { + "muc_badge" +} +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_batched_probe/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,49 @@ +# mod_muc_batched_probe + +This module allows you to probe the presences of multiple MUC occupants or members. + +XEP-0045 makes provision for MUC presence probes, which allows an entity to +probe for the presence information of a MUC occupant (or offline member). + +See here: https://xmpp.org/extensions/xep-0045.html#bizrules-presence + +This module creates the possibility to probe with a single IQ stanza the +presence information of multiple JIDs, instead of having to send out a presence +probe stanza per JID. + +The IQ stanza needs to look as follows: + +``` + <iq from="hag66@shakespeare.lit/pda" + id="zb8q41f4" + to="chat.shakespeare.lit" + type="get"> + + <query xmlns="http://jabber.org/protocol/muc#user"> + <item jid="hecate@shakespeare.lit"/> + <item jid="crone1@shakespeare.lit"/> + <item jid="wiccarocks@shakespeare.lit"/> + <item jid="hag66@shakespeare.lit"/> + </query> + </iq> +``` + + + +## Configuration + +Under your MUC component, add `muc_batched_probe` to `modules_enabled` + +``` + Component "conference.example.org" "muc" + modules_enabled = { + "muc_batched_probe"; + } +``` + + +## Client Support + +Converse.js has a plugin which supports this feature. + +https://www.npmjs.com/package/@converse-plugins/muc-presence-probe
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_bot/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,50 @@ +--- +summary: Module for improving the life of bot authors +--- + +This module makes it easier to write MUC bots by removing the +requirement that the bot be online and joined to the room. + +All the bot needs to do is send a message and this module handles the +rest. + +# Configuration + +Example configuration in Prosody: + +```lua +Component "muc.example.com" "muc" + +modules_enabled = { + "muc_bot", +} +known_bots = { "bot@example.com" } +bots_get_messages = false +ignore_bot_errors = true +``` + +# Sending messages + +Simply send a stanza like this from your bot: + +```xml +<message type="groupchat" to="channel@muc.example.com"> + <body>Beep boop, I'm a bot!</body> + <nick xmlns="http://jabber.org/protocol/nick">Botty</nick> +</message> +``` + +## Use with mod_rest + +Using [mod_rest] to interact with MUC suffers from the same need to join +with an online resource, so this module helps with that as well! + +```bash +curl https://xmpp.example.com/rest/message/groupchat/room@muc.example.com \ + -d body="beep boop" \ + -d nick="Botty" +``` + +# Compatibility + +Works with Prosody 0.12 or later.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_cloud_notify/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,71 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'XEP-XXX: Cloud push notifications for MUC' +--- + +# Introduction + +This is an experimental fork of [mod_cloud_notify](https://modules.prosody.im/mod_cloud_notify.html) +which allows a [XEP-0357 Push Notifications App Servers](https://xmpp.org/extensions/xep-0357.html#general-architecture) +to be registered against a MUC domain (normally they're only registered against +your own chat server's domain). + +The goal here is to also enable push notifications also for MUCs. + +In contrast to mod_cloud_notify, this module does NOT integrate with +mod_smacks, because a MUC can't access a remote user's XEP-0198 queue. + +Configuration +============= + + Option Default Description + ------------------------------------ ----------------- ------------------------------------------------------------------------------------------------------------------- + `push_notification_with_body` `false` Whether or not to send the message body to remote pubsub node. + `push_notification_with_sender` `false` Whether or not to send the message sender to remote pubsub node. + `push_max_errors` `16` How much persistent push errors are tolerated before notifications for the identifier in question are disabled + `push_notification_important_body` `New Message!` The body text to use when the stanza is important (see above), no message body is sent if this is empty + `push_max_devices` `5` The number of allowed devices per user (the oldest devices are automatically removed if this threshold is reached) + +There are privacy implications for enabling these options because +plaintext content and metadata will be shared with centralized servers +(the pubsub node) run by arbitrary app developers. + +## To test this module: + +The [Converse](http://conversejs.org/) client has support for registering push +"app servers" against a MUC. + +You specify app servers with the [push_app_servers](https://conversejs.org/docs/html/configuration.html#push-app-servers) +config setting. + +And then you need to set [enable_muc_push](https://conversejs.org/docs/html/configuration.html#enable-muc-push) +to `true` so that these app servers are also registered against MUC domains. + +Additionally you need to set [auto_register_muc_nickname](https://conversejs.org/docs/html/configuration.html#auto-register-muc-nickname) +to true. + +Then, when you enter a MUC, Converse will try to automatically register your nickname +on that MUC. + +Note: Converse currently doesn't let you register separate app servers for +a MUC domain. The same app servers are registered for the MUC domain and your +own domain. + +## To be done: + +We currently don't handle "ghost connections", users who are currently offline +but the XMPP server is not yet aware of this and shows considers them online in +the MUC. + +Prosody already checks for error bounces from undelivered groupchat messages +and then kicks the particular user from the room. + +So these ghost connection users eventually get kicked from the room. + +We now need a module that fires an event when a groupchat messages can't be +delivered to an occupant. The module can look up the undelivered message in MAM +and include it in the event. + +In mod_muc_cloud_notify we can then listen for this event and send out a push +notification.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_config_restrict/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,62 @@ +--- +labels: +- 'Stage-Alpha' +summary: Restrict MUC configuration options to server admins +... + +Introduction +============ + +Sometimes, especially on public services, you may want to allow people +to create their own rooms, but prevent some options from being modified +by normal users. + +For example, using this module you can prevent users from making rooms +persistent, or making rooms publicly visible. + +Details +======= + +You need to supply a list of options that will be restricted to admins. +Available options can vary, but the following table lists Prosody's +built-in options (as defined in XEP-0045): + + Name Description + --------------------------------- ------------------------------------------- + muc\#roomconfig\_roomname The title/name of the room + muc\#roomconfig\_roomdesc The description of the room + muc\#roomconfig\_persistentroom Whether the room should remain when empty + muc\#roomconfig\_publicroom Whether the room is publicly visible + muc\#roomconfig\_changesubject Whether occupants can change the subject + muc\#roomconfig\_whois Control who can see occupant's real JIDs + muc\#roomconfig\_roomsecret The room password + muc\#roomconfig\_moderatedroom Whether the room is moderated + muc\#roomconfig\_membersonly Whether the room is members-only + muc\#roomconfig\_historylength The length of the room history + +Some plugins may add other options to the room config (in Prosody +0.10+), for which you will need to consult their documentation for the +full option name. + +Configuration +============= + +Enable the plugin on a MUC host (do not put it in your global +modules\_enabled list): + +``` {.lua} +Component "conference.example.com" "muc" +modules_enabled = { "muc_config_restrict" } +muc_config_restricted = { + "muc#roomconfig_persistentroom"; -- Prevent non-admins from changing a room's persistence setting + "muc#roomconfig_membersonly"; -- Prevent non-admins from changing whether rooms are members-only +} +``` + +Compatibility +============= + + ------- -------------- + trunk Doesn't work (uses is_admin) + 0.12 Works? + ------- --------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_defaults/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,39 @@ +# mod_muc_defaults + +Creates MUCs with default configuration settings upon Prosody startup. + +## Configuration + +Under your MUC component, add a `default_mucs` option with the relevant settings. + +``` +Component "conference.example.org" "muc" + modules_enabled = { + "muc_defaults"; + } + + default_mucs = { + { + jid_node = "trollbox", + affiliations = { + admin = { "admin@example.org", "superuser@example.org" }, + owner = { "owner@example.org" }, + visitors = { "visitor@example.org" } + }, + config = { + name = "General Chat", + description = "Public chatroom with no particular topic", + allow_member_invites = false, + change_subject = false, + history_length = 40, + lang = "en", + logging = true, + members_only = false, + moderated = false, + persistent = true, + public = true, + public_jids = true + } + } + }; +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_eventsource/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,79 @@ +--- +labels: +- Stage-Beta +summary: Subscribe to MUC rooms using the HTML5 EventSource API +... + +Introduction +------------ + +This module and its docs shamelessly forked from mod_pubsub_eventsource. + +[Server-Sent Events](https://en.wikipedia.org/wiki/Server-sent_events) +is a simple HTTP/line-based protocol supported in HTML5, making it easy +to receive a stream of "events" in realtime using the Javascript +[EventSource +API](https://developer.mozilla.org/en-US/docs/Web/API/EventSource). + +EventSource is supported in [most modern +browsers](http://caniuse.com/#feat=eventsource), and for the remainder +there are 'polyfill' compatibility layers such as +[EventSource.js](https://github.com/remy/polyfills/blob/master/EventSource.js) +and [jquery.eventsource](https://github.com/rwldrn/jquery.eventsource). + +Details +------- + +Subscribing to a node from Javascript is easy: + + var source = new EventSource('http://muc.example.org:5280/eventsource/myroom'); + source.onmessage = function (event) { + console.log(event.data); // Do whatever you want with the data here + }; + +### Access control + +Be warned that this module currently performs no access control. It will expose +the messages of ALL rooms on the host it is loaded on. This may be changed in +future revisions. + +### Cross-domain issues + +The same cross-domain restrictions apply to EventSource that apply to +BOSH, and support for CORS is not clearly standardized yet. You may want +to proxy connections through your web server for this reason. See [BOSH: +Cross-domain +issues](https://prosody.im/doc/setting_up_bosh#proxying_requests) for +more information. + +Configuration +------------- + +There is no special configuration for this module. Simply load it onto a +MUC component like so: + + Component "muc.example.org" "muc" + modules_enabled = { "muc_eventsource" } + +As it uses HTTP to serve the event streams, you can use Prosody's +standard [HTTP configuration options](https://prosody.im/doc/http) to +control how/where the streams are served. + +**Note about URLs:** It is important to get the event streams from the +correct hostname (that of the MUC host). An example stream URL is +`http://muc.example.org:5280/eventsource/myroom`. If you need to +access the streams using another hostname (e.g. `example.org`) you can +use the `http_host` option under the Component, e.g. +`http_host = "example.org"`. For more information see the ['Virtual +Hosts'](https://prosody.im/doc/http#virtual_hosts) section of our HTTP +documentation. + +Compatibility +------------- + + ------- -------------- + 0.10 ? + 0.9 ? + 0.8 Doesn't work + Trunk Works + ------- --------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_gateway_optimize/mod_muc_gateway_optimize.lua Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,55 @@ +local jid = require("util.jid") +local mod_muc = module:depends("muc") + +local gateway_hosts = module:get_option_array("gateway_hosts", {}) + +function optimize(remote_host, event) + local stanza = event.stanza + module:log("debug", "optimize presence event destined for " .. remote_host) + + local muc_x = stanza:get_child("x", "http://jabber.org/protocol/muc#user") + if muc_x then + for status in muc_x:childtags("status") do + if status.attr.status == "110" then + module:log("debug", "optimize delivering 110") + -- Always deliver self-presence + return + end + end + end + + local bare_jid = jid.bare(stanza.attr.to) + local room = mod_muc.get_room_from_jid(jid.bare(stanza.attr.from)) + if not room then return end + for nick, occupant in room:each_occupant() do + local occupant_host = jid.host(occupant.bare_jid) + if occupant_host == remote_host then + -- This is the "first" occupant from the host + -- which is the only one we will route non-110 + -- presence to + if occupant.bare_jid == bare_jid then + module:log("debug", "optimize found first occupant, so route") + return + else + module:log("debug", "optimize found non-first occupant, so drop") + return true + end + end + end + -- If we get here we found no occupants for this host + module:log("debug", "optimize found no occupants for host " .. remote_host) +end + +-- Note this will only affect gateways over s2s for now +module:hook("route/remote", function (event) + if event.stanza.name ~= "presence" then + return + end + + local remote_host = jid.host(event.stanza.attr.to) + for _, gateway_host in pairs(gateway_hosts) do + if remote_host == gateway_host then + return optimize(remote_host, event) + end + end +end, 1000)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_gc10/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,18 @@ +# Groupchat 1.0 usage statistics gathering + +Groupchat 1.0 was probably the protocol that predated +[XEP-0045: Multi-User Chat] and there is still some compatibility that +lives on, in the XEP and in implementations. + +This module tries to detect clients still using the GC 1.0 protocol and +what software they run, to determine if support can be removed. + +Since joins in the GC 1.0 protocol are highly ambiguous, some hits +reported will be because of desynchronized MUC clients + +# Compatibility + +Should work with Prosody 0.10.x and earlier. + +It will not work with current trunk, since the MUC code has had major +changes.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_hats_adhoc/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,37 @@ +--- +summary: Ad-hoc commands for managing MUC hats +--- + +# Introduction + +This module provides an internal API (i.e. to other modules) to manage +'hats' for users in MUC rooms. + +Hats (first defined in [XEP-0317], currently deferred) are additional identifiers +that can be attached to users in a group chat. For example in an educational +context, you may have a 'Teacher' hat that allows students to identify their +teachers. + +Hats consist of a machine-readable unique identifier (a URI), and optionally +a human-readable label. + +This module provides ad-hoc commands for MUC service admins to add/remove hats +to/from users in MUC rooms. It depends (automatically) on mod_muc_hats_api. + +## Configuration + +``` +Component "conference.example.com" "muc" + modules_enabled = { "muc_hats_adhoc" } +``` + +## Usage + +To successfully use the module you will need to use an XMPP client that is +capable of sending commands to a specific host (e.g. via the service discovery +browser in Gajim, Psi/Psi+ and other clients), and you'll find the commands +on the MUC host. + +Also note that the display of hats in clients is currently non-existent, but +will hopefully improve after [XEP-0317] is resurrected or replaced. +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_hats_api/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,129 @@ +--- +summary: API for managing MUC hats +--- + +# Introduction + +This module provides an internal API (i.e. to other modules) to manage +'hats' for users in MUC rooms. + +Hats (first defined in [XEP-0317], currently deferred) are additional identifiers +that can be attached to users in a group chat. For example in an educational +context, you may have a 'Teacher' hat that allows students to identify their +teachers. + +Hats consist of a machine-readable unique identifier (a URI), and optionally +a human-readable label. + +[XEP-0317] suggests a protocol for users to manage their own hats, but though the +API in this module allows for both user-managed and system-managed hats, there is +currently no protocol implemented for users to manage their own hats, which is +rarely desired in real-world implementations. + +The rest of this documentation is designed for developers who use this module. + +## Data model + +### User + +``` +{ + "hats": { + "urn:uuid:164c41a2-7461-4cff-bdae-3f93078a6607": { + "active": false + }, + "http://example.com/hats/foo": { + "active": true, + "required": true, + "title": "Awesome" + } +} +``` + +| Field | Type | Description | +|-------|--------|--------------------------------------------------------------| +| hats | object | An object where mapping hat ids (key) to attachments (value) | + +Hat IDs must be a URI that uniquely identifies the hat. + +### Attachment + +``` +{ + "active": true, + "required": true, + "title": "My Awesome Hat" +} +``` + +| Field | Type | Description | +|----------|---------|-------------------------------------------------------------| +| active | boolean | If true, indicates the user is currently displaying the hat | +| required | boolean | If true, indicates the user is not able to remove the hat | +| title | string | A human-readable display name or label for the hat | + +All fields are optional, omitted boolean values are equivalent to false. + +## API + +All methods return 'nil, err' on failure as standard throughout the Prosody codebase. + +Example of using this module from another module: + +``` +local muc_hats = module:depends("muc_hats_api"); + +muc_hats.add_user_hat("user@localhost", "room@conference.localhost", "urn:uuid:164c41a2-7461-4cff-bdae-3f93078a6607", { active = true }); +``` + +Note that the module only works when loaded on a MUC host, which generally means any +module that uses it must also be loaded on the MUC host that it is managing. + +### add_user_hat + +`add_user_hat(user_jid, room_jid, hat_id, attachment)` + +Adds the identified hat to a user's... wardrobe? The user must already +have an affiliation with the room (i.e. member, admin or owner). + +If `attachment` is omitted, it defaults to `{}`. + +#### Error cases + +item-not-found +: Supplied room JID was not found on the current host + +item-not-found +: Supplied user JID was not affiliated with the room + +### remove_user_hat + +`remove_user_hat(user_jid, room_jid, hat_id)` + +If the identified hat is currently available to the user, it is removed. + +#### Error cases + +item-not-found +: Supplied room JID was not found on the current host + +item-not-found +: Supplied user JID was not affiliated with the room + +### set_user_hats + +`set_user_hats(user_jid, room_jid, hats)` + +Ensures the listed hats are the hats available to a user, automatically +adding/removing as necessary. + +The `hats` parameter should be an object mapping hat ids (keys) to attachment +objects (values). + +#### Error cases + +item-not-found +: Supplied room JID was not found on the current host + +item-not-found +: Supplied user JID was not affiliated with the room
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_http_defaults/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,191 @@ +--- +summary: Seed MUC configuration from JSON REST API +--- + +# Introduction + +This module fetches configuration for MUC rooms from an API when rooms +are created. + +# Requirements + +Should work with Prosody 0.11. + +# Configuration + +`muc_create_api_url` +: URL template for the API endpoint to get settings. `{room.jid}` is + replaced by the address of the room in question. + +`muc_create_api_auth` +: The value of the Authorization header to authenticate against the + API. E.g. `"Bearer /rXU4tkQTYQMgdHfMLH6"`{.lua} + +In the URL template variable, the room JID is available as `{room.jid}`, +which would be turned into `room@muc.host`. To only get the room +localpart, `{room.jid|jid_node}` can be used, and `{room.jid|jid_host}` +splits out the `muc.host` part. + +## Example + +``` {.lua} +Component "channels.example.net" "muc" +modules_enabled = { "muc_http_defaults" } +muc_create_api_url = "https://api.example.net/muc/config?jid={room.jid}" +``` + +# API + +A RESTful JSON API is used. Any error causes the room to be destroyed. + +The returned JSON consists of two main parts, the room configuration and +the affiliations (member list). + +## Room Configuration + +The top level `config` field contains a map of properties corresponding +to the fields in the room configuration dialog, named similarly to the +[room configuration default][doc:modules:mod_muc#room-configuration-defaults] in +Prosodys config file. + +| Property | Type | Description | +|------------------------|---------|---------------------------------------------------------------------------| +| `name` | string | Name of the chat | +| `description` | string | Longer description of the chat | +| `language` | string | Language code | +| `persistent` | boolean | Whether the room should keep existing if it becomes empty | +| `public` | boolean | `true` to include in public listing | +| `members_only` | boolean | Membership or open | +| `allow_member_invites` | boolean | If members can invite others into members-only rooms | +| `public_jids` | boolean | If everyone or only moderators should see real identities | +| `subject` | string | In-room subject or topic message | +| `changesubject` | boolean | If `true` then everyone can change the subject, otherwise only moderators | +| `historylength` | integer | Number of messages to keep in memory (legacy method) | +| `moderated` | boolean | New participants start without voice privileges if set to `true` | +| `archiving` | boolean | Whether [archiving][doc:modules:mod_muc_mam] is enabled | + +## Affiliations + +The list of members go in `affiliations` which is either an object +mapping addresses to affiliations (e.g. `{"user@host":"admin"}`{.json}), +or it can be an array of address, affiliation and optionally a reserved +nickname (e.g. +`[{"jid":"user@host","affiliation":"member","nick":"joe"}]`{.json}). + +## Schema + +Here's a JSON Schema in YAML format describing the expected JSON +response data: + +``` {.yaml} +--- +type: object +properties: + config: + type: object + properties: + name: + type: string + description: + type: string + language: + type: string + persistent: + type: boolean + public: + type: boolean + members_only: + type: boolean + allow_member_invites: + type: boolean + public_jids: + type: boolean + subject: + type: string + changesubject: + type: boolean + historylength: + type: integer + moderated: + type: boolean + archiving: + type: boolean + affiliations: + oneOf: + - type: array + items: + type: object + required: + - jid + - affiliation + properties: + jid: + type: string + pattern: ^[^@/]+@[^/]+$ + affiliation: + $ref: '#/definitions/affiliation' + nick: + type: string + - type: object + additionalProperties: + $ref: '#/definitions/affiliation' +definitions: + affiliation: + type: string + enum: + - owner + - admin + - member + - none + - outcast +... +``` + +## Example + +A basic example with some config settings and a few affiliations: + +``` {.json} +GET /muc/config?jid=place@channels.example.net +Accept: application/json + +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "affiliations" : [ + { + "affiliation" : "owner", + "jid" : "bosmang@example.net", + "nick" : "bosmang" + }, + { + "affiliation" : "admin", + "jid" : "xo@example.net", + "nick" : "xo" + }, + { + "affiliation" : "member", + "jid" : "john@example.net" + } + ], + "config" : { + "archiving" : true, + "description" : "This is the place", + "members_only" : true, + "moderated" : false, + "name" : "The Place", + "persistent" : true, + "public" : false, + "subject" : "Discussions regarding The Place" + } +} +``` + +To allow the creation without making any changes, letting whoever +created it be the owner, just return an empty JSON object: + + HTTP/1.1 200 OK + Content-Type: application/json + + {}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_inject_mentions/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,132 @@ +# Introduction + +This module intercepts messages sent to a MUC, looks in the message's body if a user was mentioned and injects a mention type reference to that user implementing [XEP-0372](https://xmpp.org/extensions/xep-0372.html#usecase_mention) + +## Features + +1. Multiple mentions in the same message using affixes, including multiple mentions to the same user. + Examples: + `Hello nickname` + `@nickname hey!` + `nickname, hi :)` + `Are you sure @nickname?` + +2. Mentions are only injected if no mention was found in a message, avoiding this way, injecting mentions in messages sent from clients with mentions support. + +3. Configuration settings for customizing affixes and enabling/disabling the module for specific rooms. + + +# Configuring + +## Enabling + +```{.lua} + +Component "rooms.example.net" "muc" + +modules_enabled = { + "muc_inject_mentions"; +} + +``` + +## Settings + +Apart from just writing the nick of an occupant to trigger this module, +common affixes used when mentioning someone can be configured in Prosody's config file. +Recommended affixes: + +``` +muc_inject_mentions_prefixes = {"@"} -- Example: @bob hello! +muc_inject_mentions_suffixes = {":", ",", "!", ".", "?"} -- Example: bob! How are you doing? +``` + +This module can be enabled/disabled for specific rooms. +Only one of the following settings must be set. + +``` +-- muc_inject_mentions_enabled_rooms = {"room@conferences.server.com"} +-- muc_inject_mentions_disabled_rooms = {"room@conferences.server.com"} +``` + +If none of these is set, all rooms in the muc component will have mentions enabled. + + +By default, if a message contains at least one mention, +the module does not do anything, as it believes all mentions were already sent by the client. +In cases where it is desired the module to inspect the message and try to find extra mentions +that could be missing, the following setting can be added: + +``` +muc_inject_mentions_append_mentions = true +``` + + +Prefixes can be removed using: +``` +muc_inject_mentions_strip_out_prefixes = true +``` +Turning `Hey @someone` into `Hey someone`. +Currently, prefixes can only be removed from module added mentions. +If the client sends a mention type reference pointing to a nickname using a prefix (`Hey @someone`), the prefix will not be removed. + + +There are two lists where this module pulls the participants from. +1. Online participants +2. Participants with registered nicknames + +By default, the module will try to find mentions to online participants. +Using: +``` +muc_inject_mentions_reserved_nicks = true +``` +Will try to find mentions to participants with registered nicknames. +This is useful for setups where the nickname is reserved for all participants, +allowing the module to catch mentions to participants that might not be online at the moment of sending the message. + + +It is also possible to modify how this module detects mentions. +In short, the module will detect if a mention is actually a mention +if the nickname (with or without affixes) is between spaces, new lines, or at the beginning/end of the message. +This can be changed using: + +``` +-- muc_inject_mentions_mention_delimiters = {" ", "", "\n", "\t"} +``` +Generally speaking and unless the use-case is very specific, there should be no need to modify the defaults of this setting. + +When triggering a mention must only happen if that mention includes a prefix, this can be configured with: +``` +-- muc_inject_mentions_prefix_mandatory = true +``` + +By default, mentions use the bare jid of the participant as the URI attribute. +If the MUC jid of the participant (eg. room@chat.example.org/Romeo) is preferred, this can be set using: +``` +-- muc_inject_mentions_use_real_jid = false +``` + + +# Example stanzas + +Alice sends the following message + +``` +<message id="af6ca" to="room@conference.localhost" type="groupchat"> + <body>@bob hey! Are you there?</body> +</message> +``` + +Then, the module detects `@bob` is a mention to `bob` and injects a mention type reference to him + +``` +<message from="room@conference.localhost/alice" id="af6ca" to="alice@localhost/ThinkPad" type="groupchat"> + <body>@bob hey! Are you there?</body> + <reference xmlns="urn:xmpp:reference:0" + begin="1" + end="3" + uri="xmpp:bob@localhost" + type="mention" + /> +</message> +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_intercom/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,7 @@ +This module allows sending a message to another MUC room. + + @other-room: hello + +The message will appear in the other room as as + + <first-room/You> hello
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_lang/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,26 @@ +# Introduction + +This module adds support for advertising the language used in a room. + +# Configuring + +``` {.lua} +Component "rooms.example.net" "muc" +modules_enabled = { + "muc_lang"; +} +``` + +The room language is specified in a new field in the room configuration +dialog, accessible through compatible clients. + +Use [language codes](https://en.wikipedia.org/wiki/ISO_639) like `en`, +`fr`, `de` etc. + +# Compatibility + +Meant for use with Prosody 0.10.x + +Native support was [added in Prosody +trunk/0.11](https://hg.prosody.im/trunk/rev/9c90cd2fc4c3), so there is +no need for this module.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_limits/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,80 @@ +--- +labels: +- 'Stage-Beta' +summary: 'Impose rate-limits on a MUC' +... + +Introduction +============ + +This module allows you to control the maximum rate of 'events' in a MUC +room. This makes it useful to prevent room floods (whether malicious or +accidental). + +Details +======= + +This module limits the following events: + +- Room joins +- Nick changes +- Status changes +- Messages (including private messages) + +The limit is for the room as a whole, not individual occupants in the +room. Users with an affiliation (members, admins and owners) are not +limited. + +Configuration +============= + +Add the module to the MUC host (not the global modules\_enabled): + +```lua +Component "conference.example.com" "muc" + modules_enabled = { "muc_limits" } +``` + +You can define (globally or per-MUC component) the following options: + + Name Default value Description + --------------------------- --------------- ---------------------------------------------------------- + muc_event_rate 0.5 The maximum number of events per second. + muc_burst_factor 6 Allow temporary bursts of this multiple. + muc_max_nick_length 23 The maximum allowed length of user nicknames + muc_max_char_count 5664 The maximum allowed number of bytes in a message + muc_max_line_count 23 The maximum allowed number of lines in a message + muc_limit_base_cost 1 Base cost of sending a stanza + muc_line_count_multiplier 0.1 Additional cost of each newline in the body of a message + +For more understanding of how these values are used, see the algorithm +section below. + +Algorithm +========= + +A certain number of events are allowed per second, given by +muc\_event\_rate. An event rate of 1 allows one event per second, and +event rate of 3 allows three events per second, and 0.5 allows one event +every two seconds, and so on. + +Obviously MUC conversations are not exactly steady streams of events. +Sometimes multiple people will talk at once. This is handled by the +muc\_burst\_factor option. + +A burst factor of 2 will allow 2 times as many events at once, for 2 +seconds, before throttling will be triggered. A factor of 5, 5 times as +many events for 5 seconds. + +When the limit is reached, an error response will be generated telling +the user the MUC is overactive, and asking them to try again. + +Compatibility +============= + + ------- ------- + trunk* Works + 0.12 Works + ------- ------- + +*as of 2024-10-22
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_local_only/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,36 @@ +# Introduction + +This module allows you to make one or more MUCs as accessible to local users only. + +# Details + +Local users (anyone on the same server as the MUC) are granted automatic +membership when they first join the room. Users from other servers are +denied access (even if the room is otherwise configured to be open). + +# Configuring + +## Enabling + +``` {.lua} +Component "rooms.example.net" "muc" +modules_enabled = { + "muc_local_only"; +} +``` + +## Settings + +Specify a list of MUCs in your config like so: + +``` +muc_local_only = { "my-local-chat@conference.example.com" } +``` + +# Compatibility + +Requires Prosody 0.11.0 or later. + +# Future + +It would be good to add a room configuration option.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_log/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,45 @@ +--- +labels: +- 'Stage-Obsolete' +summary: Log chatroom messages to disk +... + +::: {.alert .alert-danger} +This module was an early and obsolete way to record group chat logs. + +For a modern approach, use [mod_muc_mam][doc:modules:mod_muc_mam]. + +For converting legacy logs stored by this module, see [mod_storage_muc_log], it can be used with [mod_migrate] or [prosody-migrator][doc:migrator]. +::: + +Introduction +============ + +This module logs the conversation of chatrooms running on the server to +Prosody's data store. To view them you will need a module such as +[mod\_muc\_log\_http](mod_muc_log_http.html). + +Details +======= + +mod\_muc\_log must be loaded individually for the components that need +it. Assuming you have a MUC component already running on +conference.example.org then you can add muc\_log to it like so: + + Component "conference.example.org" "muc" + modules_enabled = { + "muc_log"; + } + +Logging is not enabled by default. In 0.9+ logging can be enabled per +room in the room config form. + +To enable logging in older versions, or to enable logging by default for +all rooms, set + + muc_log_by_default = true -- Log all rooms by default + +Compatibility +============= + +Does **not** work with currently supported versions of Prosody.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_log_http/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,58 @@ +--- +labels: +- 'Stage-Obsolete' +summary: Provides a web interface to stored chatroom logs +... + +::: {.alert .alert-danger} +This module depends on an obsolete module for storing data in an inefficient format. + +For a modern approach, see [mod_http_muc_log] which relies on [mod_muc_mam][doc:modules:mod_muc_mam]. +::: + +Introduction +============ + +This module provides a built-in web interface to view chatroom logs +stored by [mod\_muc\_log](mod_muc_log.html). + +Installation +============ + +Just copy the folder muc\_log\_http as it is, into the modules folder of +your Prosody installation. + +Configuration Details +===================== + +Example configuration: + + Component "conference.example.com" "muc" + modules_enabled = { + ..... + "muc_log"; + "muc_log_http"; + ..... + } + + muc_log_http = { -- These are the defaults + show_join = true; + show_presences = true; + show_status = true; + theme = "prosody"; + url_base = "muc_log"; + } + +**show\_join** sets the default for showing joins or leaves. +**show\_status** sets the default for showing status changes. + +The web interface would then be reachable at the address: + + http://conference.example.com:5280/muc_log/ + +TODO +==== + +- Log bans correctly +- Quota \~ per day ?! +- Testing testing :)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_mam_hints/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,28 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'Support XEP-0334: Message Processing Hints for MUC messages' +... + +Introduction +============ + +This module will check for MUC messages with XEP-0334 Message +Processing Hints tags to qualify those messages as "historic" +for later MAM archiving or not. + +Usage +===== + +First copy the module to the prosody plugins directory. + +Then add "muc\_mam\_hints" to your modules\_enabled list in your MUC +component: + +``` {.lua} +Component "conference.example.org" "muc" +modules_enabled = { + "muc_mam", + "muc_mam_hints", +} +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_mam_markers/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,24 @@ +--- +labels: +- 'Stage-alpha' +summary: Save received chat markers into MUC archives' +... + +Introduction +============ + +Chat markers (XEP-0333) specification states that markers _SHOULD_ be +archived. This is already happening in one to one conversations in +the personal archives but not in Group Chats. This module hooks the +_muc-message-is-historic_ event to customize the `mod_muc_mam` +behavior and have the chat markers archived. + +Usage +===== + +First copy the module to the prosody plugins directory. + +Then add "muc\_mam\_markers" to your `modules\_enabled` list in your +MUC component's definition. + +No configuration options are available.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_markers/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,45 @@ +# Introduction + +This module adds an internal Prosody API to retrieve the last displayed message by MUC occupants. + +## Requirements + +The clients must support XEP-0333, and the users to be tracked must be affiliated with the room. + +Currently due to lack of clarity about which id to use in acknowledgements in XEP-0333, this module +rewrites the id attribute of stanzas to match the stanza (archive) id assigned by the MUC server. + +Oh yeah, and mod_muc_mam is required (or another module that adds a stanza-id), otherwise this module +won't do anything. + +# Configuring + +## Enabling + +``` {.lua} +Component "rooms.example.net" "muc" +modules_enabled = { + "muc_markers"; + "muc_mam"; +} +``` + +## Settings + +| Name | Description | Default | +|----------------------------|--------------------------------------------------------------------------------------|-------------| +| muc_marker_summary_on_join | Whether a summary of all the latest markers should be sent to someone entering a MUC | true | +| muc_marker_type | The type of marker to track (displayed/received/acknowledged) | "displayed" | + + +# Developers + +## Example usage + +``` +local muc_markers = module:depends("muc_markers"); + +function something() + local last_displayed_id = muc_markers.get_user_read_marker("user@localhost", "room@conference.localhost"); +end +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_mention_notifications/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,24 @@ +# Configuring + +This module lets Prosody notify users when they're mentioned in a MUC, even if they're not currently present in it. + +Users need to be explicitly mentioned via XEP-0372 references. + +In anonymous and semi-anonymous rooms, the mentioned user needs to have their nickname registered in the MUC so that Prosody can get the real JID from the referenced nickname. + +NOTE: this module is not compatible with mod_block_strangers because the latter will block the notification messages from the MUC (since they're not "groupchat" messages). + +## Enabling + +``` {.lua} +Component "rooms.example.net" "muc" +modules_enabled = { + "muc_mention_notifications"; +} +``` + +## Settings + +|Name |Description |Default | +|-----|------------|--------| +|muc_mmn_notify_unaffiliated_users| Notify mentioned users even if they are not members of the room they were mentioned in | false |
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_moderation/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,51 @@ +--- +labels: +- Stage-Beta +summary: Let moderators remove spam and abuse messages +--- + +# Introduction + +This module implements [XEP-0425: Message Moderation]. + +# Usage + +Moderation is done via a supporting client and requires a `moderator` +role in the channel / group chat. + +# Configuration + +Example [MUC component][doc:chatrooms] configuration: + +``` {.lua} +Component "channels.example.com" "muc" +modules_enabled = { + "muc_mam", + "muc_moderation", +} +``` + +# Compatibility + + ------- --------------- + trunk Works^[as of 2024-10-22] + 0.12 Works + ------- --------------- + +## XEP version + +This module implements [XEP-0425] v0.2.1 (tombstones included) and v0.3.0 +(except for tombstones). + +## Clients + +- [Converse.js](https://conversejs.org/) +- [Gajim](https://dev.gajim.org/gajim/gajim/-/issues/10107) +- [clix](https://code.zash.se/clix/rev/6c1953fbe0fa) +- [Cheogram](https://cheogram.com/) + +### Feature requests + +- [Conversations](https://codeberg.org/iNPUTmice/Conversations/issues/20) +- [Dino](https://github.com/dino/dino/issues/1133) +- [Profanity](https://github.com/profanity-im/profanity/issues/1336)
--- a/mod_muc_moderation/mod_muc_moderation.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_muc_moderation/mod_muc_moderation.lua Tue Mar 18 00:31:36 2025 +0700 @@ -27,12 +27,15 @@ -- Namespaces local xmlns_fasten = "urn:xmpp:fasten:0"; local xmlns_moderate = "urn:xmpp:message-moderate:0"; +local xmlns_moderate_1 = "urn:xmpp:message-moderate:1"; local xmlns_occupant_id = "urn:xmpp:occupant-id:0"; local xmlns_retract = "urn:xmpp:message-retract:0"; +local xmlns_retract_1 = "urn:xmpp:message-retract:1"; -- Discovering support module:hook("muc-disco#info", function (event) event.reply:tag("feature", { var = xmlns_moderate }):up(); + event.reply:tag("feature", { var = xmlns_moderate_1 }):up(); end); -- TODO error registry, requires Prosody 0.12+ @@ -83,11 +86,17 @@ end end + local actor_occupant = room:get_occupant_by_real_jid(actor) or room:new_occupant(jid.bare(actor), actor_nick); local announcement = st.message({ from = room_jid, type = "groupchat", id = id.medium(), }) :tag("apply-to", { xmlns = xmlns_fasten, id = stanza_id }) :tag("moderated", { xmlns = xmlns_moderate, by = actor_nick }) + if room.get_occupant_id then + -- This isn't a regular broadcast message going through the events occupant_id.lib hooks so we do this here + announcement:add_child(st.stanza("occupant-id", { xmlns = xmlns_occupant_id; id = room:get_occupant_id(actor_occupant) })); + end + if retract then announcement:tag("retract", { xmlns = xmlns_retract }):up(); end @@ -101,31 +110,60 @@ announcement:add_direct_child(moderated_occupant_id); end - local actor_occupant = room:get_occupant_by_real_jid(actor) or room:new_occupant(jid.bare(actor), actor_nick); - if room.get_occupant_id then - -- This isn't a regular broadcast message going through the events occupant_id.lib hooks so we do this here - announcement:add_direct_child(st.stanza("occupant-id", { xmlns = xmlns_occupant_id; id = room:get_occupant_id(actor_occupant) })) + -- XEP 0425 v0.3.0 + + announcement:reset(); + + if retract then + announcement:tag("retract", { xmlns = xmlns_retract_1; id = stanza_id }) + :tag("moderated", { xmlns = xmlns_moderate_1 }) + :tag("occupant-id", { xmlns = xmlns_occupant_id; id = room:get_occupant_id(actor_occupant) }); + if reason then + announcement:up():up():text_tag("reason", reason); + end end + + local tombstone = nil; if muc_log_archive.set and retract then - local tombstone = st.message({ from = original.attr.from, type = "groupchat", id = original.attr.id }) + tombstone = st.message({ from = original.attr.from, type = "groupchat", id = original.attr.id }) :tag("moderated", { xmlns = xmlns_moderate, by = actor_nick }) :tag("retracted", { xmlns = xmlns_retract, stamp = dt.datetime() }):up(); - if room.get_occupant_id then - tombstone:add_direct_child(st.stanza("occupant-id", { xmlns = xmlns_occupant_id; id = room:get_occupant_id(actor_occupant) })) - - if moderated_occupant_id then - -- Copy occupant id from moderated message - tombstone:add_child(moderated_occupant_id); - end - end - if reason then tombstone:text_tag("reason", reason); end + + if room.get_occupant_id then + if actor_occupant then + tombstone:add_child(st.stanza("occupant-id", { xmlns = xmlns_occupant_id; id = room:get_occupant_id(actor_occupant) })); + end + + if moderated_occupant_id then + -- Copy occupant id from moderated message + tombstone:add_direct_child(moderated_occupant_id); + end + end tombstone:reset(); + end + -- fire an event, that can be used to cancel the moderation, or modify stanzas. + local event = { + room = room; + announcement = announcement; + tombstone = tombstone; + stanza_id = stanza_id; + retract = retract; + reason = reason; + actor = actor; + actor_nick = actor_nick; + }; + if module:fire_event("muc-moderate-message", event) then + -- TODO: allow to change the error message? + return false, "wait", "internal-server-error"; + end + + if tombstone then local was_replaced = muc_log_archive:set(room_node, stanza_id, tombstone); if not was_replaced then return false, "wait", "internal-server-error"; @@ -166,6 +204,36 @@ return true; end); +module:hook("iq-set/bare/" .. xmlns_moderate_1 .. ":moderate", function (event) + local stanza, origin = event.stanza, event.origin; + + local actor = stanza.attr.from; + local room_jid = stanza.attr.to; + + local moderate_tag = stanza:get_child("moderate", xmlns_moderate_1) + local retract_tag = moderate_tag:get_child("retract", xmlns_retract_1) + + if not retract_tag then return end -- other kind of moderation? + + local reason = moderate_tag:get_child_text("reason"); + local stanza_id = moderate_tag.attr.id + + local ok, error_type, error_condition, error_text = moderate( + actor, + room_jid, + stanza_id, + retract_tag, + reason + ); + if not ok then + origin.send(st.error_reply(stanza, error_type, error_condition, error_text)); + return true; + end + + origin.send(st.reply(stanza)); + return true; +end); + module:hook("muc-message-is-historic", function (event) -- Ensure moderation messages are stored if event.stanza.attr.from == event.room.jid then
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_moderation_delay/LICENSE Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,235 @@ +GNU AFFERO GENERAL PUBLIC LICENSE +Version 3, 19 November 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + + Preamble + +The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. + +A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. + +The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. + +An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. + +The precise terms and conditions for copying, distribution and modification follow. + + TERMS AND CONDITIONS + +0. Definitions. + +"This License" refers to version 3 of the GNU Affero General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based on the Program. + +To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. + +1. Source Code. +The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. + +A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +2. Basic Permissions. +All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. +No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. + +4. Conveying Verbatim Copies. +You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. +You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". + + c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. + +6. Conveying Non-Source Forms. +You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: + + a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. + + d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. + +"Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. + +7. Additional Terms. +"Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or authors of the material; or + + e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. + +All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. + +8. Termination. + +You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. + +9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. + +11. Patents. + +A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. + +A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. + +13. Remote Network Interaction; Use with the GNU General Public License. + +Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. + +Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. + +14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. + +15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. + +You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see <http://www.gnu.org/licenses/>.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_moderation_delay/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,45 @@ +<!-- +SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/> +SPDX-License-Identifier: AGPL-3.0-only +--> +# mod_muc_moderation_delay + +With this module, you can apply a delay to groupchat messages delivery, so that room moderators can moderate them before other participants receives them. + +This module is under AGPL-3.0 license. +This module can work on any Prosody server (version >= 0.12.x). +This module is still experimental. + +## Configuration + +Just enable the module on your MUC component. +The feature will be accessible throught the room configuration form. + +The position in the room config form can be changed be setting the option `moderation_delay_form_position`. +This value will be passed as priority for the "muc-config-form" hook. +By default, the field will be between muc#roomconfig_changesubject and muc#roomconfig_moderatedroom. + +``` lua +VirtualHost "muc.example.com" + modules_enabled = { "muc_moderation_delay" } + moderation_delay_form_position = 96 +``` + +## Additional notes + +For moderators, messages that are delayed will contain an extra `moderation-delay` xml tag, with `delay` and `waiting` attribute: + +```xml +<message xmlns="jabber:client" type="groupchat" id="18821520-e49b-4e59-b6c6-b45cc133905d" to="root@example.com/QH1H89H1" xml:lang="en" from="8df24108-6e70-4fc8-b1cc-f2db7fcdd535@room.example.com/root"> + <body>Hello world</body> + <origin-id id="18821520-e49b-4e59-b6c6-b45cc133905d" xmlns="urn:xmpp:sid:0" /> + <markable xmlns="urn:xmpp:chat-markers:0" /> + <occupant-id id="V5gJudj4Ii3+LnikqUbSSH3NmPKO82zD+m7jRYushVY=" xmlns="urn:xmpp:occupant-id:0" /> + <stanza-id xmlns="urn:xmpp:sid:0" id="xkf36aYefSmQ9evPo1m6Neei" by="8df24108-6e70-4fc8-b1cc-f2db7fcdd535@room.example.com" /> + <moderation-delay delay="4" waiting="1720177157" /> +</message> +``` + +Note: the `waiting` attribute is the timestamp at which the message will be broadcasted. + +So compatible xmpp clients can display some information.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_moderation_delay/config.lib.lua Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,61 @@ +-- SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/> +-- SPDX-License-Identifier: AGPL-3.0-only + +-- Getter/Setter +local function get_moderation_delay(room) + return room._data.moderation_delay or nil; +end + +local function set_moderation_delay(room, delay) + if delay == 0 then + delay = nil; + end + if delay ~= nil then + delay = assert(tonumber(delay), "Moderation delay is not a valid number"); + if delay < 0 then + delay = nil; + end + end + + if get_moderation_delay(room) == delay then return false; end + + room._data.moderation_delay = delay; + return true; +end + +-- Discovering support +local function add_disco_form(event) + table.insert(event.form, { + name = "muc#roominfo_moderation_delay"; + value = ""; + }); + event.formdata["muc#roominfo_moderation_delay"] = get_moderation_delay(event.room); +end + + +-- Config form declaration +local function add_form_option(event) + table.insert(event.form, { + name = "muc#roomconfig_moderation_delay"; + type = "text-single"; + datatype = "xs:integer"; + range_min = 0; + range_max = 60; -- do not allow too big values, it does not make sense. + label = "Moderation delay (0=disabled, any positive integer= messages will be delayed for X seconds for non-moderator participants.)"; + -- desc = ""; + value = get_moderation_delay(event.room); + }); +end + +local function config_submitted(event) + set_moderation_delay(event.room, event.value); + -- no need to 104 status, this feature is invisible for regular participants. +end + +return { + set_moderation_delay = set_moderation_delay; + get_moderation_delay = get_moderation_delay; + add_disco_form = add_disco_form; + add_form_option = add_form_option; + config_submitted = config_submitted; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_moderation_delay/delay.lib.lua Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,151 @@ +-- SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/> +-- SPDX-License-Identifier: AGPL-3.0-only +local st = require "util.stanza"; +local timer = require "util.timer"; +local get_time = require "util.time".now; +local get_moderation_delay = module:require("config").get_moderation_delay; + +local muc_util = module:require "muc/util"; +local valid_roles = muc_util.valid_roles; + +local moderation_delay_tag = "moderation-delay"; +local xmlns_fasten = "urn:xmpp:fasten:0"; +local xmlns_moderated_0 = "urn:xmpp:message-moderate:0"; +local xmlns_retract_0 = "urn:xmpp:message-retract:0"; +local xmlns_moderated_1 = "urn:xmpp:message-moderate:1"; +local xmlns_retract_1 = "urn:xmpp:message-retract:1"; +local xmlns_st_id = "urn:xmpp:sid:0"; + +local queued_stanza_id_timers = {}; + +-- tests if a stanza is a retractation message. +local function is_retractation_for_stanza_id(stanza) + -- XEP 0425 was revised in 2023. For now, mod_muc_moderation uses the previous version. + -- But we will make the code compatible with both. + local apply_to = stanza:get_child("apply-to", xmlns_fasten); + if apply_to and apply_to.attr.id then + local moderated = apply_to:get_child("moderated", xmlns_moderated_0); + if moderated then + local retract = moderated:get_child("retract", xmlns_retract_0); + if retract then + return apply_to.attr.id; + end + end + end + + local moderated = stanza:get_child("moderated", xmlns_moderated_1); + if moderated then + if moderated:get_child("retract", xmlns_retract_1) then + return moderated.attr.id; + end + end + + return nil; +end + +-- handler for muc-broadcast-message +local function handle_broadcast_message(event) + local room, stanza = event.room, event.stanza; + local delay = get_moderation_delay(room); + if delay == nil then + -- feature disabled on the room, go for it. + return; + end + + -- only delay groupchat messages with body. + if stanza.attr.type ~= "groupchat" then + return; + end + + -- detect retractations: + local retracted_stanza_id = is_retractation_for_stanza_id(stanza); + if retracted_stanza_id then + module:log("debug", "Got a retractation message for %s", retracted_stanza_id); + if queued_stanza_id_timers[retracted_stanza_id] then + module:log("info", "Got a retractation message, for message %s that is currently waiting for broadcast. Cancelling.", retracted_stanza_id); + timer.stop(queued_stanza_id_timers[retracted_stanza_id]); + queued_stanza_id_timers[retracted_stanza_id] = nil; + -- and we continue... + end + end + + if not stanza:get_child("body") then + -- Dont want to delay message without body. + -- This is usually messages like "xxx is typing", or any other service message. + -- This also should concern retractation messages. + -- Clients that will receive retractation messages for message they never got, should just drop them. And that's ok. + return; + end + + local stanza_id = nil; -- message stanza id... can be nil! + local stanza_id_child = stanza:get_child("stanza-id", xmlns_st_id); + if not stanza_id_child then + -- this can happen when muc is not archived! + -- in such case, message retractation is not possible. + -- so, this is a normal use case, and we should handle it properly. + else + stanza_id = stanza_id_child.attr.id; + end + local id = stanza.attr.id; + if not id then + -- message should always have an id, but just in case... + module:log("warn", "Message has no id, wont delay it."); + return; + end + + -- Message must be delayed, except for: + -- * room moderators + -- * the user that sent the message (if they don't get the echo quickly, their clients could have weird behaviours) + module:log("debug", "Message %s / %s must be delayed by %i seconds, sending first broadcast wave.", id, stanza_id, delay); + local moderator_role_value = valid_roles["moderator"]; + + local cloned_stanza = st.clone(stanza); -- we must clone, to send a copy for the second wave. + + -- first of all, if the initiator occupant is not moderator, me must send to them. + -- (delaying the echo message could have some quircks in some xmpp clients) + if stanza.attr.from then + local from_occupant = room:get_occupant_by_nick(stanza.attr.from); + if from_occupant and valid_roles[from_occupant.role or "none"] < moderator_role_value then + module:log("debug", "Message %s / %s must be sent separatly to it initiator %s.", id, stanza_id, delay, stanza.attr.from); + room:route_to_occupant(from_occupant, stanza); + end + end + + -- adding a tag, so that moderators can know that this message is delayed. + stanza:tag(moderation_delay_tag, { + delay = "" .. delay; + waiting = string.format("%i", math.floor(get_time() + delay)); + }):up(); + + -- then, sending to moderators (and only moderators): + room:broadcast(stanza, function (nick, occupant) + if valid_roles[occupant.role or "none"] >= moderator_role_value then + return true; + end + return false; + end); + + local task = timer.add_task(delay, function () + module:log("debug", "Message %s has been delayed, sending to remaining participants.", id); + room:broadcast(cloned_stanza, function (nick, occupant) + if valid_roles[occupant.role or "none"] >= moderator_role_value then + return false; + end + if nick == stanza.attr.from then + -- we already sent it to them (because they are moderator, or because we sent them separately) + return false; + end + return true; + end); + end); + if stanza_id then + -- store it, so we can stop timer if there is a retractation. + queued_stanza_id_timers[stanza_id] = task; + end + + return true; -- stop the default broadcast_message processing. +end + +return { + handle_broadcast_message = handle_broadcast_message; +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_moderation_delay/mod_muc_moderation_delay.lua Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,30 @@ +-- mod_muc_moderation_delay +-- +-- SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/> +-- SPDX-License-Identifier: AGPL-3.0-only +-- +-- This file is AGPL-v3 licensed. +-- Please see the Peertube livechat plugin copyright information. +-- https://livingston.frama.io/peertube-plugin-livechat/credits/ +-- + +local add_disco_form = module:require("config").add_disco_form; +local config_submitted = module:require("config").config_submitted; +local add_form_option = module:require("config").add_form_option; +local handle_broadcast_message = module:require("delay").handle_broadcast_message; + +-- form_position: the position in the room config form (this value will be passed as priority for the "muc-config-form" hook). +-- By default, field will be between muc#roomconfig_changesubject and muc#roomconfig_moderatedroom +local form_position = module:get_option_number("moderation_delay_form_position") or 80-2; + +-- Plugin dependencies +local mod_muc = module:depends "muc"; + +-- muc-disco and muc-config to configure the feature: +module:hook("muc-disco#info", add_disco_form); +module:hook("muc-config-submitted/muc#roomconfig_moderation_delay", config_submitted); +module:hook("muc-config-form", add_form_option, form_position); + +-- intercept muc-broadcast-message, and broadcast with delay if required. +-- Priority is negative, as we want it to be the last handler. +module:hook("muc-broadcast-message", handle_broadcast_message, -1000);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_notifications/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,41 @@ +--- +labels: +- 'Stage-alpha' +summary: 'Notify of MUC messages to not present members' +... + +Introduction +============ + +This module listens to MUC messages and sends a notification to the +MUC members not present in the MUC at that moment. + +By default, the notification will be a message with a simple text as body. + +By sending this "out-of-MUC" notification, not-joined members will be able to +know that new messages are available. + +Usage +===== + +First copy the module to the prosody plugins directory. + +Then add "muc\_notifications" to your modules\_enabled list in your +MUC component: + +```{.lua} +Component "conference.example.org" "muc" +modules_enabled = { + "muc_notifications", +} +``` + +You may also want to enable "offline\_hints" module so the notification messages +sent by this module are not added to the offline storage for later delivery. + +Configuration +============= + + Option Description + --------------------------- ---------------------------------------------------------------------------------------------- + muc\_notification\_invite If set to `true`, the notification sent will take the form of a MUC invite. (default: `false`)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_occupant_id/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,8 @@ +--- +summary: 'Anonymous unique occupant identifiers for MUCs' +superseded_by: mod_muc +labels: +- 'Stage-Obsolete' +... + +Since Prosody 0.12, this module has been merged into [`mod_muc`, included in Prosody](https://prosody.im/doc/modules/mod_muc).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_ping/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,12 @@ +--- +labels: +- 'Stage-Obsolete' +superseded_by: mod_muc +summary: XEP-0410 Server Optimization (now supported by Prosody mod_muc) +--- + +Since Prosody 0.11, XEP-0410 is natively [supported by Prosody](https://prosody.im/doc/modules/mod_muc), you will be redirected shortly. + +This module implemented the [Server +Optimization](https://xmpp.org/extensions/xep-0410.html#serveroptimization) +part of [XEP-0410: MUC Self-Ping]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_require_tos/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,31 @@ +--- +labels: +- 'Stage-Alpha' +summary: Require visitors to accept something before being allowed in a room +... + +# Introduction + +This module sends a message to visitors of a room, prompting them to accept or reject it. + +They get kicked if they reject it, and become members if they accept it. + +# Setup + +```lua +Component "rooms.example.org" "muc" + modules_enabled = { + "muc_require_tos"; + } + tos_welcome_message = "Please read and accept the TOS of this service: https://lurk.org/TOS.txt" + tos_yes_message = "Thanks, and welcome here!" + tos_no_message = "Too bad." +``` + +Compatibility +============= + + ----- ----- + trunk Works + ----- ----- +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_reserve_nick_pattern/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,23 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'Require MUC occupant nicknames to no match some patterns' +--- + +Introduction +============ + +This checks the nickname of a joining user against a configurable list of +[Lua patterns](https://www.lua.org/manual/5.2/manual.html#6.4.1), and prevents +them from joining if it matches any of them. + +Configuration +============= + +There is a single configuration option, `muc_reserve_nick_patterns` and the +default is `{}` - i.e. allow everything. + +Compatibility +============= + +Requires Prosody 0.11 or higher.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_restrict_media/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,26 @@ +# Introduction + +This module adds a room configuration option to hide inline media from +unaffiliated users in MUCs and display them as links instead. + +This can be useful in public channels where content posted by users should not +be shown by default. + +# Configuring + +## Enabling + +``` {.lua} +Component "rooms.example.net" "muc" +modules_enabled = { + "muc_restrict_media"; +} +``` + +## Settings + +A default setting can be provided in the config file: + +``` {.lua} +muc_room_default_restrict_media = true +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_restrict_nick/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,23 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'Require MUC occupant nicknames to match a specific pattern' +--- + +Introduction +============ + +This checks the nickname of a joining user against a configurable +[Lua pattern](https://www.lua.org/manual/5.2/manual.html#6.4.1), and prevents +them from joining if it does not match. + +Configuration +============= + +There is a single configuration option, `muc_restrict_nick_pattern` and the +default is `"^%w+$"` - i.e. allow only alphanumeric characters in nicknames. + +Compatibility +============= + +Requires Prosody 0.11 or higher.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_restrict_pm/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,35 @@ +--- +labels: +- 'Stage-Obsolete' +summary: Limit who may send and recieve MUC PMs +... + +::: {.alert .alert-warning} +This feature has been merged into +[mod_muc][doc:modules:mod_muc] in trunk and is therefore obsolete when used with a version >0.12.x or trunk. +It can still be used with Prosody 0.12. +::: + +# Introduction + +This module adds configurable MUC options that restrict and limit who may send MUC PMs to other users. + +If a user does not have permissions to send a MUC PM, the MUC will send a policy violation stanza. + +# Setup + +```lua +Component "conference.example.org" "muc" + +modules_enabled = { + "muc_restrict_pm"; +} +``` + +Compatibility +============= + + version note + --------- --------------------------------------------------------------------------- + trunk [Integrated](https://hg.prosody.im/trunk/rev/47e1df2d0a37) into `mod_muc` + 0.12 Works
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_restrict_rooms/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,55 @@ +--- +summary: Regexp based room restriction module +... + +Introduction +============ + +This module allows disabling room creation based on regexp patterns +defined in configuration. + +Dependencies +============ + +This module depends on **muc/rooms** module. If **muc/rooms** is not +loaded, this module won't work. + +How to load the module +====================== + +Copy the module to the prosody modules/plugins directory. + +In Prosody's configuration file, under the desired MUC component +definition, add: + + modules_enabled = { + ... + "mod_muc_restrict_rooms"; + ... + } + +**Note**: This module *shouldn't* be loaded in the global +**modules\_enabled**, otherwise it won't work. + +Configuration +============= + +**mod\_muc\_restrict\_rooms** has several variables which let you +configure the patterns for room names you want to ban, establish +exceptions for those patterns and even deciding whether admins can or +not bypass the prohibition. + + Name Description Example Default value + ------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------- --------------- + muc\_restrict\_matching Table in the key/value format (keys for patterns and values for reasons) that determines which rooms shouldn't be created. The key is a regexp and must be specified between quotation marks (see example). Room names will be evaluated always lowercase, so define your patterns taking this into consideration. Users that try to join any room that matches one of those rules will get an error telling them they cannot join. muc\_restrict\_matching = { ["\^admin"] = "Rooms that start with 'admin' are reserved for staff use only" } {} + muc\_restrict\_exceptions String format table that contains exceptions to the above defined rules. Room names specified here will bypass the muc\_restrict\_matching restrictions and will be available for anyone muc\_restrict\_exceptions = { "admins\_are\_good", "admins\_rocks" } {} + muc\_restrict\_allow\_admins Boolean that determines whether users in the **admin** table are able to bypass any room restriction. If ser to *true*, they will be able to bypass those rules. muc\_restrict\_allow\_admins = true false + +Compatibility +============= + + ----- ------------- + trunk Doesn't work (uses is_admin) + 0.9 Works + 0.8 Should work + ----- -------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_slow_mode/LICENSE Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,235 @@ +GNU AFFERO GENERAL PUBLIC LICENSE +Version 3, 19 November 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + + Preamble + +The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. + +A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. + +The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. + +An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. + +The precise terms and conditions for copying, distribution and modification follow. + + TERMS AND CONDITIONS + +0. Definitions. + +"This License" refers to version 3 of the GNU Affero General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based on the Program. + +To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. + +1. Source Code. +The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. + +A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +2. Basic Permissions. +All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. +No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. + +4. Conveying Verbatim Copies. +You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. +You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". + + c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. + +6. Conveying Non-Source Forms. +You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: + + a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. + + d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. + +"Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. + +7. Additional Terms. +"Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or authors of the material; or + + e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. + +All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. + +8. Termination. + +You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. + +9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. + +11. Patents. + +A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. + +A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. + +13. Remote Network Interaction; Use with the GNU General Public License. + +Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. + +Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. + +14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. + +15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. + +You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see <http://www.gnu.org/licenses/>.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_slow_mode/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,37 @@ +<!-- +SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/> +SPDX-License-Identifier: AGPL-3.0-only +--> +# mod_muc_slow_mode + +This module is a custom module that allows slow mode for MUC rooms. + +This module is under AGPL-3.0 license. + +There will probably be a XEP proposal for this module behaviour. + +## Slow mode definition + +There are some contexts in which you want to be able to rate limit MUC messages. This could have multiple motivations: avoid flooding, garantee a better readability of the room when there are hundreds of active users, … + +This module propose a new option for MUC rooms, allowing room owners to fix a duration that users must wait between two messages. + +There is a draft XEP for this feature, that you can find here: https://github.com/JohnXLivingston/xeps/blob/xep-slow-mode/xep-slow-mode.xml + +There is a more human-readable version of this XEP here: https://livingston.frama.io/peertube-plugin-livechat/technical/slow_mode/ + +## Configuration + +Just enable the module on your MUC component. +The feature will be accessible throught the room configuration form. + +Depending on your application, it is possible that the slow mode is more important than other fields (for example for a video streaming service). +The position in the room config form can be changed be setting the option `slow_mode_duration_form_position`. +This value will be passed as priority for the "muc-config-form" hook. +By default, the field will be between muc#roomconfig_changesubject and muc#roomconfig_moderatedroom. + +``` lua +VirtualHost "muc.example.com" + modules_enabled = { "muc_slow_mode" } + slow_mode_duration_form_position = 96 +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_slow_mode/mod_muc_slow_mode.lua Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,163 @@ +-- mod_muc_slow_mode +-- +-- SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/> +-- SPDX-License-Identifier: AGPL-3.0-only +-- +-- Implements: XEP-????: MUC Slow Mode (XEP to come). +-- +-- Imports +local st = require "util.stanza"; +local jid_bare = require "util.jid".bare; +local gettime = require 'socket'.gettime; + +-- Plugin dependencies +local mod_muc = module:depends "muc"; + +local muc_util = module:require "muc/util"; +local valid_roles = muc_util.valid_roles; + +-- Namespaces +local xmlns_muc = "http://jabber.org/protocol/muc"; + +-- Options + +-- form_position: the position in the room config form (this value will be passed as priority for the "muc-config-form" hook). +-- Depending on your application, it is possible that the slow mode is more important than other fields (for example for a video streaming service). +-- So there is an option to change this. +-- By default, field will be between muc#roomconfig_changesubject and muc#roomconfig_moderatedroom +local form_position = module:get_option_number("slow_mode_duration_form_position") or 80-2; + +-- Getter/Setter +local function get_slow_mode_duration(room) + return room._data.slow_mode_duration or 0; +end + +local function set_slow_mode_duration(room, duration) + if duration then + duration = assert(tonumber(duration), "Slow mode duration is not a valid number"); + end + if duration and duration < 0 then + duration = 0; + end + + if get_slow_mode_duration(room) == duration then return false; end + + room._data.slow_mode_duration = duration; + return true; +end + +-- Discovering support +local function add_disco_form(event) + table.insert(event.form, { + name = "muc#roominfo_slow_mode_duration"; + value = ""; + }); + event.formdata["muc#roominfo_slow_mode_duration"] = get_slow_mode_duration(event.room); +end + +module:hook("muc-disco#info", add_disco_form); + +-- Config form declaration +local function add_form_option(event) + table.insert(event.form, { + name = "muc#roomconfig_slow_mode_duration"; + type = "text-single"; + datatype = "xs:integer"; + range_min = 0; + label = "Slow Mode (0=disabled, any positive integer= users can send a message every X seconds.)"; + -- desc = ""; + value = get_slow_mode_duration(event.room); + }); +end + +module:hook("muc-config-submitted/muc#roomconfig_slow_mode_duration", function(event) + if set_slow_mode_duration(event.room, event.value) then + -- status 104 = configuration change: Inform occupants that a non-privacy-related room configuration change has occurred + event.status_codes["104"] = true; + end +end); + +module:hook("muc-config-form", add_form_option, form_position); + +-- handling groupchat messages +function handle_groupchat(event) + local origin, stanza = event.origin, event.stanza; + local room = event.room; + + -- only consider messages with body (ie: ignore chatstate and other non-text xmpp messages) + local body = stanza:get_child_text("body") + if not body or #body < 1 then + -- module:log("debug", "No body, message accepted"); + return; + end + + local duration = get_slow_mode_duration(room) or 0; + if duration <= 0 then + -- no slow mode for this room + -- module:log("debug", "No slow mode for this room"); + return; + end + + -- Checking user's permissions (moderators are not subject to slow mode) + local actor = stanza.attr.from; + local actor_nick = room:get_occupant_jid(actor); + local actor_jid = jid_bare(actor); + -- Only checking role, not affiliation (slow mode only applies on users currently connected to the room) + local role = room:get_role(actor_nick); + if valid_roles[role or "none"] >= valid_roles.moderator then + -- user bypasses the slow mode. + -- module:log("debug", "User is moderator, bypassing slow mode"); + return; + end + + if not room.slow_mode_last_messages then + -- We store last message time for each users in room.slow_mode_last_messages: + -- * key: bare jid (without the nickname) + -- * value: last message timestamp + -- If room is cleared from memory, these data are lost. But should not be an issue. + -- For now, i don't clean slow_mode_last_messages, it should not use too much memory. + -- module:log("debug", "Initializing slow_mode_last_messages for the room."); + room.slow_mode_last_messages = {}; + end + + local now = gettime(); + local previous = room.slow_mode_last_messages[actor_jid]; + -- module:log( + -- "debug", + -- "Last message for user %s was at %s, now is %s, duration is %s, now - previous is %s", + -- actor_jid, + -- previous or 0, + -- now, + -- duration, + -- (now - (previous or 0)) + -- ); + if ((not previous) or (now - previous > duration)) then + -- module:log("debug", "Message accepted"); + room.slow_mode_last_messages[actor_jid] = now; + return; + end + + module:log("debug", "Bouncing message for user %s", actor_nick); + local reply = st.error_reply( + stanza, + -- error_type = 'wait' (see descriptions in RFC 6120 https://xmpp.org/rfcs/rfc6120.html#stanzas-error-syntax) + "wait", + -- error_condition = 'policy-violation' (see RFC 6120 Defined Error Conditions https://xmpp.org/rfcs/rfc6120.html#stanzas-error-conditions) + "policy-violation", + "You have exceeded the limit imposed by the slow mode in this room. You have to wait " .. duration .. " seconds between messages. Please try again later" + ); + + -- Note: following commented lines were inspired by mod_muc_limits, but it seems it is not required. + -- if body then + -- reply:up():tag("body"):text(body):up(); + -- end + -- local x = stanza:get_child("x", xmlns_muc); + -- if x then + -- reply:add_child(st.clone(x)); + -- end + + origin.send(reply); + return true; -- stoping propagation +end + +module:hook("muc-occupant-groupchat", handle_groupchat);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_webchat_url/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,40 @@ +# Introduction + +Many projects have a support room accessible via a web chat. This module +allows making the URL to such a web chat discoverable via the XMPP +service discovery protocol, enabling e.g. [search +engines](https://search.jabbercat.org/) to index and present these. + +# Configuring + +## Enabling + +``` {.lua} +Component "rooms.example.net" "muc" +modules_enabled = { + "muc_webchat_url"; +} +``` + +## Settings + +The URL is configured using the in-band MUC room configuration protocol. + +The module can optionally be configured to give all public (not +members-only, hidden or password protected) rooms gain a default value +based on a template: + +``` {.lua} +muc_webchat_baseurl = "https://chat.example.com/join?room={node}" +``` + +The following variables will be subsituted with room address details: + +`{jid}` +: The complete room address, eg `room@muc.example.com`· + +`{node}` +: The local part (before the `@`) of the room JID. + +`{host}` +: The domain name part of the room JID.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_munin/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,94 @@ +--- +labels: +- 'Stage-Beta' +- 'Statistics' +summary: Implementation of the Munin node protocol +... + +Summary +======= + +This module implements the Munin reporting protocol, allowing you to +collect statistics directly from Prosody into Munin. + +Configuration +============= + +There is only one recommended option, `munin_node_name`, which specifies +the name that Prosody will identify itself by to the Munin server. You +may want to set this to the same hostname as in the [SRV record][doc:dns] +for the machine. + +```lua +modules_enabled = { + -- your other modules + "munin", +} + +munin_node_name = "xmpp.example.com" +``` + +You will also want to enable statistics collection by setting: + +```lua +statistics_interval = 300 -- every 5 minutes, same as munin +``` + +## Summary + +All these must be in [the global section][doc:configure#overview]. + + Option Type Default + ----------------------- -------- --------------------------- + munin\_node\_name string `"localhost"` + munin\_ignored\_stats set `{ }` + munin\_ports set `{ 4949 }` + munin\_interfaces set `{ "0.0.0.0", "::" }`[^1] + +[^1]: Varies depending on availability of IPv4 and IPv6 + +## Ports and interfaces + + +`mod_munin` listens on port `4949` on all local interfaces by default. +This can be changed with the standard [port and network configuration][doc:ports]: + + +``` lua +-- defaults: +munin_ports = { 4949 } +munin_interfaces = { "::", "0.0.0.0" } +``` + +If you already have a `munin-node` instance running, you can set a +different port to avoid the conflict. + +## Configuring Munin + +Simply add `munin_node_name` surrounded by brackets to `/etc/munin/munin.conf`: + +``` ini +[xmpp.example.com] +address xmpp.example.com +port 4949 +``` + +You can leave out `address` if it equal to the name in brackets, and +leave out the `port` if it is the default (`4949`). + +Setting `address` to an IP address may sometimes be useful as the Munin +collection server is not delayed by DNS lookups in case of network +issues. + +If you set a different port, or if the hostname to connect to is +different from this hostname, make sure to add `port` and/or `address` +options. + +See [Munin documentation][muninconf] for more information. + +Compatibility +============= + +**Requires** Prosody 0.10 or above + +[muninconf]: http://guide.munin-monitoring.org/en/stable-2.0/reference/munin.conf.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_net_proxy/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,179 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'Implementation of PROXY protocol versions 1 and 2' +... + +Introduction +============ + +This module implements the PROXY protocol in versions 1 and 2, which fulfills +the following usecase as described within the official protocol specifications: + +> Relaying TCP connections through proxies generally involves a loss of the +> original TCP connection parameters such as source and destination addresses, +> ports, and so on. +> +> The PROXY protocol's goal is to fill the server's internal structures with the +> information collected by the proxy that the server would have been able to get +> by itself if the client was connecting directly to the server instead of via a +> proxy. + +You can find more information about the PROXY protocol on +[the official website](https://www.haproxy.com/blog/haproxy/proxy-protocol/) +or within +[the official protocol specifications.](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) + + +Usage +===== + +Copy the plugin into your prosody's modules directory. And add it +between your enabled modules into the global section (modules\_enabled). + +As the PROXY protocol specifications do not allow guessing if the PROXY protocol +shall be used or not, you need to configure separate ports for all the services +that should be exposed with PROXY protocol support: + +```lua +--[[ + Maps TCP ports to a specific Prosody network service. Further information about + available service names can be found further down below in the module documentation. +]]-- +proxy_port_mappings = { + [15222] = "c2s", + [15269] = "s2s" +} + +--[[ + Specifies a list of trusted hosts or networks which may use the PROXY protocol + If not specified, it will default to: 127.0.0.1, ::1 (local connections only) + An empty table ({}) can be configured to allow connections from any source. + Please read the module documentation about potential security impact. +]]-- +proxy_trusted_proxies = { + "192.168.10.1", + "172.16.0.0/16" +} + +--[[ + While you can manually override the ports this module is listening on with + the "proxy_ports" directive, it is highly recommended to not set it and instead + only configure the appropriate mappings with "proxy_port_mappings", which will + automatically start listening on all mapped ports. + + Example: proxy_ports = { 15222, 15269 } +]]-- +``` + +The above example configuration, which needs to be placed in the global section, +would listen on both tcp/15222 and tcp/15269. All incoming connections have to +originate from trusted hosts/networks (configured by _proxy_trusted_proxies_) and +must be initiated by a PROXYv1 or PROXYv2 sender. After processing the PROXY +protocol, those connections will get mapped to the configured service name. + +Please note that each port handled by _mod_net_proxy_ must be mapped to another +service name by adding an item to _proxy_port_mappings_, otherwise a warning will +be printed during module initialization and all incoming connections to unmapped ports +will be dropped after processing the PROXY protocol requests. + +The service name can be found by analyzing the source of the module, as it is the +same name as specified within the _name_ attribute when calling +`module:provides("net", ...)` to initialize a network listener. The following table +shows the names for the most commonly used Prosody modules: + + ------------- -------------------------- + **Module** **Service Name** + c2s c2s (Plain/StartTLS) + s2s s2s (Plain/StartTLS) + proxy65 proxy65 (Plain) + http http (Plain) + net_multiplex multiplex (Plain/StartTLS) + ------------- -------------------------- + +This module should work with all services that are providing ports which either +offer plaintext or StartTLS-based encryption. Please note that instead of using +this module for HTTP-based services (BOSH/WebSocket) it might be worth resorting +to use proxy which is able to process HTTP and insert a _X-Forwarded-For_ header +instead. + + +Example +======= + +This example provides you with a Prosody server that accepts regular connections on +tcp/5222 (C2S) and tcp/5269 (S2S) while also offering dedicated PROXY protocol ports +for both modules, configured as tcp/15222 (C2S) and tcp/15269 (S2S): + +```lua +c2s_ports = {5222} +s2s_ports = {5269} +proxy_port_mappings = { + [15222] = "c2s", + [15269] = "s2s" +} +``` + +After adjusting the global configuration of your Prosody server accordingly, you can +configure your desired sender accordingly. Below is an example for a working HAProxy +configuration which will listen on the default XMPP ports (5222+5269) and connect to +your XMPP backend running on 192.168.10.10 using the PROXYv2 protocol: + +``` +defaults d-xmpp + log global + mode tcp + option redispatch + option tcplog + option tcpka + option clitcpka + option srvtcpka + + timeout connect 5s + timeout client 24h + timeout server 60m + +frontend f-xmpp + bind :::5222,:::5269 v4v6 + use_backend b-xmpp-c2s if { dst_port eq 5222 } + use_backend b-xmpp-s2s if { dst_port eq 5269 } + +backend b-xmpp-c2s + balance roundrobin + option independent-streams + server mycoolprosodybox 192.168.10.10:15222 send-proxy-v2 + +backend b-xmpp-s2s + balance roundrobin + option independent-streams + server mycoolprosodybox 192.168.10.10:15269 send-proxy-v2 +``` + + +Limitations +=========== + +It is currently not possible to use this module for offering PROXY protocol support +on SSL/TLS ports, which will automatically initiate a SSL handshake. This might be +possible in the future, but it currently does not look like this could easily be +implemented due to the current handling of such connections. + + +Important Notes +=============== + +Please do not expose any ports offering PROXY protocol to the internet - while regular +clients will be unable to use them anyways, it is outright dangerous and allows anyone +to spoof the actual IP address. It is highly recommended to only allow PROXY +connections from trusted sources, e.g. your loadbalancer. + + +Compatibility +============= + + ----- ----- + trunk Works + 0.12 Works + 0.11 Works + 0.10 Works + ----- -----
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_nodeinfo2/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,51 @@ +--- +labels: +- Stage-Alpha +--- + +Introduction +============ + +This module exposes a [nodeinfo2](https://git.feneas.org/jaywink/nodeinfo2) +.well-known URL for use e.g. from +[the-federation.info](https://the-federation.info). + +Configuration +============= + +Enable the `nodeinfo` module in your global `modules_enabled` section: +``` +modules_enabled = { + ... + "nodeinfo2" + ... +} +``` + +Set the `nodeinfo2_expose_users` option to false if you don’t want to expose +statistics about the amount of users you host: +``` +nodeinfo2_expose_users = false +``` + +Set the `nodeinfo2_expose_posts` option to false if you don’t want to expose +statistics about the amount of messages being exchanged by your users: +``` +nodeinfo2_expose_posts = false +``` + +This module depends on +[mod\_lastlog](https://modules.prosody.im/mod_lastlog.html) to calculate user +activity, and [mod\_http](https://prosody.im/doc/http). Most of its +configuration actually happens in this dependency. + +Compatibility +============= + + ------- -------------------- + trunk Does not work [^1] + 0.11 Should work + ------- -------------------- + +[^1]: not after + [5f15ab7c6ae5](https://hg.prosody.im/trunk/rev/5f15ab7c6ae5)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_nooffline_noerror/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,30 @@ +--- +labels: +- 'Stage-Alpha' +summary: Discard offline stanzas instead of generating stanza errors if mod_offline is not loaded +... + +Introduction +============ + +By default without mod_offline stanzas that would go to offline storage +trigger error stanzas sent back to the sender to inform him of undeliverable stanzas. + +But if you use MAM on your server and are certain, all of your clients are using it, +you can use this module to disable the error stanzas. +If mod_offline is loaded, this module will do nothing. + +Warning +======= + +You most certainly *should not* use this module if you cannot be certain +that *all* your clients support and use MAM! + +Compatibility +============= + + ----- ------------------------------------------------------------------- + trunk Works + 0.10 Works + 0.9 Untested but should work + ----- -------------------------------------------------------------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_offline_email/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,51 @@ +--- +labels: +- 'Stage-Beta' +summary: Forward offline messages via email +... + +Introduction +============ + +Quite often when I am out and about, I'm not able to connect to Jabber. +It is usually much more likely I can access my email though (whether via +the web, or a mobile client). + +For this reason I decided it would be extremely useful to have Jabber +messages sent to me while I was offline forwarded to my email inbox. + +Usage +===== + +Simply add "offline\_email" to your modules\_enabled list. When any user +receives a message while they are offline, it will automatically be +forwarded via mail to the **same** address as their Jabber ID. e.g. +user1@example.com's offline messages will be forwarded to +user1@example.com's email inbox. + +Configuration +============= + + Option Description + ------------------------ ---------------------------------------------------------------------------------------------------------------------------------------------------- + queue\_offline\_emails The number of seconds to buffer messages for, before they are sent as an email. The default is to send each message as it arrives. + smtp\_server Address of the SMTP server to send through. Default 'localhost' (recommended, see caveats below) + smtp\_username If set, Prosody will authenticate with the SMTP server before sending (default is no authentication) + smtp\_password The password for the above user (default is none) + smtp\_from Address from which it will appear the emails came. Default is smtp\_username@smtp\_server, where smtp\_username is replaced with 'xmpp' if not set + +Compatibility +============= + + ----- ------- + 0.9 Works + ----- ------- + +Caveats/Todos/Bugs +================== + +- Currently SMTP sending blocks the whole server. This should not be + noticeable if your mail server is on the same machine as Prosody. +- There is not (yet) any way to configure forwarding to an email + address other than your JID (idea... use email address in vcard?) +- Enable/disable this feature per user?
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_offline_hints/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,22 @@ +--- +labels: +- 'Stage-alpha' +summary: Do not store in offline storage messages hinted with no-store' +... + +Introduction +============ + +`mod_offline` does not take into account XEP-334 tags. This module +will not add to the offline storage those messages tagged with +`<no-store />`. + +Usage +===== + +First copy the module to the prosody plugins directory. + +Then add "offline\_hints" to your modules\_enabled list in your +configuration. + +No configuration options are available.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_ogp/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,32 @@ +# mod_ogp + +This module adds [Open Graph Protocol](https://ogp.me) metadata to URLs sent inside a MUC. + +With mod_ogp enabled, when a user sends a URL in a MUC (where the message has its `id` equal to its `origin-id`), the module calls the URL and parses the result for `<meta>` html tags that have any `og:...` properties. +If it finds any, it sends a [XEP-0422 fastening](https://xmpp.org/extensions/xep-0422.html) applied to the original message that looks like: + +```xml +<message id="example" from="chatroom@muc.example.org" to="user@chat.example.org/resource"> +<apply-to xmlns="urn:xmpp:fasten:0" id="origin-id-X"> +<meta xmlns="http://www.w3.org/1999/xhtml" property="og:title" content="The Rock"/> +<meta xmlns="http://www.w3.org/1999/xhtml" property="og:url" content="https://www.imdb.com/title/tt0117500/"/> +<meta xmlns="http://www.w3.org/1999/xhtml" property="og:image" content="https://ia.media-imdb.com/images/rock.jpg"/> +</apply-to> +</message> +``` + +The module is intentionally simple in the sense that it is basically a transport for https://ogp.me/ + +Configuration +------------- + +You can present an allowlist or denylist of domains for which OGP metadata will be fetched +via the `ogp_domain_allowlist` and `ogp_domain_denylist` settings repectively. + +For example: + +```lua +Component "muc.example.org" "muc" + modules_enabled = { "ogp" } + ogp_domain_allowlist = { "prosody.im" } +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_omemo_all_access/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,29 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'Disable access control for all OMEMO related PEP nodes' +--- + +Introduction +============ + +Traditionally OMEMO encrypted messages could only be exchanged after gaining mutual presence subscription due to the OMEMO key material being stored in PEP. + +XEP-0060 defines a method of changing the access model of a PEP node from `presence` to `open`. However Prosody does not yet support access models on PEP nodes. + +This module disables access control for all OMEMO PEP nodes (=all nodes in the namespace of `eu.siacs.conversations.axolotl.*`), giving everyone access to the OMEMO key material and allowing them to start OMEMO sessions with users on this server. + +Disco feature +============= + +This modules annouces a disco feature on the account to allow external tools such as the [Compliance Tester](https://conversations.im/compliance/) to check if this module has been installed. + + +Compatibility +============= + + ----- ----------------------------------------------------------------------------- + trunk Not needed, mod\_pep provides this feature already + 0.11 Not needed, mod\_pep provides this feature already + 0.10 Works + ----- -----------------------------------------------------------------------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_onhold/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,26 @@ +--- +labels: +summary: 'Module enabling "on-hold" functionality' +... + +Introduction +============ + +Enable mod\_onhold to allow temporarily placing messages from particular +JIDs "on hold" -- i.e. store them, but do not deliver them until the +hold status is taken away. + +Details +======= + +Right now, it is configured through adding JIDs to a list in +prosody.cfg.lua. Eventually, more dynamically configurable support will +be added (i.e. with ad-hoc commands or some such thing). + +Simply enable mod\_onhold in your list of modules, and then add a line: + +onhold\_jids = { "someone@address.com", "someoneelse@address2.com" } + +Until those JIDs are removed, messages from those JIDs will not be +delivered. Once they are removed and prosody is restarted, they will be +delivered the next time the user to which they are directed logs on.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_onions/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,81 @@ +--- +labels: +- 'Stage-Alpha' +summary: s2s to Tor hidden services +... + +Introduction +============ + +This plugin allows Prosody to connect to other servers that are running +as a Tor hidden service. Running Prosody on a hidden service works +without this module, this module is only necessary to allow Prosody to +federate to hidden XMPP servers. + +For general info about creating a hidden service, see +[https://community.torproject.org/onion-services/setup/](https://community.torproject.org/onion-services/setup/). + +Usage +===== + +This module depends on the bit32 Lua library. + +To create a hidden service that can federate with other hidden XMPP +servers, first add a hidden serivce to Tor. It should listen on port +5269 and optionally also on 5222 (if c2s connections to the hidden +service should be allowed). + +Use the hostname that Tor gives with a virtualhost: + + VirtualHost "555abcdefhijklmn.onion" + modules_enabled = { "onions" }; + +Configuration +============= + + Name Description Type Default value + ---------------------- ----------------------------------------------------- --------- --------------- + onions\_socks5\_host the host to connect to for Tor's SOCKS5 proxy string "127.0.0.1" + onions\_socks5\_port the port to connect to for Tor's SOCKS5 proxy integer 9050 + onions\_only forbid all connection attempts to non-onion servers boolean false + onions\_tor\_all pass all s2s connections through Tor boolean false + onions\_map override the address for a host table {} + +By setting `onions_map`, it is possible to override the address used to +connect to a given host with the address of a hidden service. The +configuration of `onions_map` works as follows: + + onions_map = { + ["jabber.calyxinstitute.org"] = "ijeeynrc6x2uy5ob.onion"; + } + +or, to also specify a port: + + onions_map = { + ["jabber.calyxinstitute.org"] = { host = "ijeeynrc6x2uy5ob.onion", port = 5269 }; + } + +Compatibility +============= + + ----- -------------- + 0.8 Doesn't work + 0.9 Works + ----- -------------- + +Notes +===== + +- `onions_tor_all` does not look up SRV records first. Therefore it + will fail for many servers. +- mod\_onions currently does not support connecting to `.onion` + entries in SRV records. + +Security considerations +======================= + +- Running a hidden service on a server together with a normal server + might expose the hidden service. +- A hidden service that wants to remain hidden should either disallow + s2s to non-hidden servers or pass all s2s traffic through Tor + (setting either `onions_only` or `onions_tor_all`).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_password_reset/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,31 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'Enables users to reset their password via a link' +rockspec: + build: + copy_directories: + - password_reset +... + +Introduction +============ + +This module allows users to reset their password via a simple link to a web page. + +Reset links may be generated by an admin through their XMPP client using the ad-hoc +command that this module provides. Alternatively other modules may reuse this module +to generate links and e.g. send them via email to the user directly. + +A link is only valid for a single reset, and expires after a duration (24 hours by default). + +This module depends on Prosody's internal webserver. + +Compatibility +============= + + ----- ------- + 0.10 Works + ----- ------- + trunk Works + ----- -------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pastebin/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,108 @@ +--- +labels: +- Stage-Stable +summary: Redirect long messages to built-in pastebin +--- + +# Introduction + +Pastebins are used very often in IM, especially in chat rooms. You have +a long log or command output which you need to send to someone over IM, +and don't want to fill their message window with it. Put it on a +pastebin site, and give them the URL instead, simple. + +Not for everyone... no matter how hard you try, people will be unaware, +or not care. They may also be too lazy to visit a pastebin. This is +where mod_pastebin comes in! + +# Details + +When someone posts to a room a "large" (the actual limit is +configurable) message, Prosody will intercept the message and convert it +to a URL pointing to a built-in pastebin server. The URLs are randomly +generated, so they can be considered for most purposes to be private, +and cannot be discovered by people who are not in the room. + +# Usage + +To set up mod_pastebin for MUC rooms it **must** be explicitly loaded, +as in the example below - it won't work when loaded globally, as that +will only load it onto normal virtual hosts. + +For example: + + Component "conference.example.com" "muc" + modules_enabled = { "pastebin" } + +Pastes will be available by default at +`http://<your-prosody>:5280/pastebin/` by default. + +In Prosody 0.9 and later this can be changed with [HTTP +settings](https://prosody.im/doc/http). + +In 0.8 and older this can be changed with `pastebin_ports` (see below), +or you can forward another external URL from your web server to Prosody, +use `pastebin_url` to set that URL. + +# Discovery + +The line and character tresholds are advertised in +[service discovery][xep-0030] like this: + +``` {.xml} +<iq id="791d37e8-86d8-45df-adc2-9bcb17c45cb7" type="result" xml:lang="en" from="prosody@conference.prosody.im"> + <query xmlns="http://jabber.org/protocol/disco#info"> + <identity type="text" name="Prosŏdy IM Chatroom" category="conference"/> + <feature var="http://jabber.org/protocol/muc"/> + <feature var="https://modules.prosody.im/mod_pastebin"/> + <x xmlns="jabber:x:data" type="result"> + <field type="hidden" var="FORM_TYPE"> + <value>http://jabber.org/protocol/muc#roominfo</value> + </field> + <field label="Title" type="text-single" var="muc#roomconfig_roomname"> + <value>Prosŏdy IM Chatroom</value> + </field> + <!-- etc... --> + <field type="text-single" var="{https://modules.prosody.im/mod_pastebin}max_lines"> + <value>12</value> + </field> + <field type="text-single" var="{https://modules.prosody.im/mod_pastebin}max_characters"> + <value>1584</value> + </field> + </x> + </query> +</iq> +``` + +# Configuration + + Option Description + ------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + pastebin_threshold Maximum length (in characters) of a message that is allowed to skip the pastebin. (default 500 characters) + pastebin_line_threshold The maximum number of lines a message may have before it is sent to the pastebin. (default 4 lines) + pastebin_trigger A string of characters (e.g. "!paste ") which if detected at the start of a message, always sends the message to the pastebin, regardless of length. (default: not set) + pastebin_expire_after Number of hours after which to expire (remove) a paste, defaults to 24. Set to 0 to store pastes permanently on disk. + pastebin_ports List of ports to run the HTTP server on, same format as mod_httpserver's http_ports[^1] + pastebin_url Base URL to display for pastebin links, must end with / and redirect to Prosody's built-in HTTP server[^2] + +# Compatibility + + ------ ------- + trunk Works + 0.12 Works + 0.11 Works + 0.10 Works + 0.9 Works + 0.8 Works + ------ ------- + +# Todo + +- Maximum paste length +- Web interface to submit pastes? + +[^1]: As of Prosody 0.9, `pastebin_ports` is replaced by `http_ports`, + see [Prosody HTTP server documentation](https://prosody.im/doc/http) + +[^2]: See also + [http_external_url](https://prosody.im/doc/http#external_url)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pep_vcard_avatar/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,29 @@ +--- +labels: +- 'Stage-Alpha' +summary: Sync avatars between vCards and PEP +--- + +## Introduction + +This module pushes the users nickname and avatar from vCards into PEP, +or into vCards from PEP. This allows interop between older clients that +use [XEP-0153: vCard-Based Avatars] to see the avatars of clients that +use [XEP-0084: User Avatar] and vice versa. + +Also see [XEP-0398: User Avatar to vCard-Based Avatars Conversion]. + +## Configuration + +Simply [enable it like most other modules][doc:installing_modules#prosody-modules], +no further configuration needed. + +## Compatibility + + ------- -------------------------------- + trunk Use mod\_vcard\_legacy instead + 0.11 Use mod\_vcard\_legacy instead + 0.10 Works + 0.9 Does not work + 0.8 Does not work + ------- --------------------------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pep_vcard_png_avatar/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,28 @@ +Introduction +============ + +Conversations (an XMPP client for Android) is publishing PEP avatars in the webp file format. However Pidgin and other XMPP desktop clients can only show vcard avatars, that are in the PNG file format. This module is the [mod_pep_vcard_avatar](https://modules.prosody.im/mod_pep_vcard_avatar.html) module extended to also change the avatar file format to PNG. + +This module needs `dwebp` from `webp` package as an additional dependency. + +Configuration +============= + +Enable the module as any other: + + modules_enabled = { + "mod_pep_vcard_png_avatar"; + } + +You MUSTN'T load mod\_pep\_vcard\_avatar if this module is loaded. + +Compatibility +============= + + ----- ------------- + trunk Works + 0.10 Should work + 0.9 Should work + ----- ------------- + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_persisthosts/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,14 @@ +Introduction +============ + +This module creates stub configuration files for newly activated hosts. + +Configuration +============= + +A single option exists, `persisthosts_path`, which is the path where new +stub configuration files are created. It defaults to `"conf.d"`, and is +treated as relative to the configuration directiory [^1] unless set to +an absolute path. + +[^1]: usually `/etc/prosody` on \*nix systems
--- a/mod_persisthosts/mod_persisthosts.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_persisthosts/mod_persisthosts.lua Tue Mar 18 00:31:36 2025 +0700 @@ -1,6 +1,7 @@ -- mod_persisthosts module:set_global(); +local cm = require "core.configmanager"; local set = require"util.set"; local stat = require"lfs".attributes; local resolve_relative_path = require"core.configmanager".resolve_relative_path; @@ -9,7 +10,11 @@ local path_pattern = resolve_relative_path(prosody.paths.config, vhost_path) .. "/%s.cfg.lua"; local original = set.new(); -original:include(prosody.hosts); +for host, config in pairs(cm.getconfig()) do + if config["defined"] or config["component_module"] then + original:add(host); + end +end module:hook("host-activated", function(host) if not original:contains(host) then
--- a/mod_ping_muc/README.md Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_ping_muc/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -9,7 +9,7 @@ This module reacts to [server-to-server][doc:s2s] connections closing by -performing [XEP-0410: MUC Self-Ping] from the server side to check if +performing [XEP-0410: MUC Self-Ping] from the server side after a short delay to check if users are still connected to MUCs they have joined according [mod_track_muc_joins]. If it can't be confirmed that the user is still joined then their client devices are notified about this allowing them @@ -23,7 +23,7 @@ # Configuring -No configuration. Enable as a regular module in +Enable as a regular module in [`modules_enabled`][doc:modules_enabled] globally or under a `VirtualHost`: @@ -35,6 +35,30 @@ } ``` +The delay after which pings are sent can be adjusted with the setting `ping_muc_delay`, +from the default `60` (seconds). + +# Client facing protocol + +If the module determines that the client has dropped out a MUC, +it sends it [a stanza to indicate this](https://xmpp.org/extensions/xep-0045.html#service-error-kick): + +``` xml +<presence type="unavailable" id="random123" from="room@muc.host/nickname" to="user@example.net/resource"> + <x xmlns="http://jabber.org/protocol/muc#user"> + <item affiliation="none" role="none"> + <reason>Connection to remote server lost</reason> + </item> + <status code="110"/> + <status code="330"/> + </x> +</presence> +``` + +The `reason` message may vary. + +Upon receiving this, the client may attempt to [rejoin](https://xmpp.org/extensions/xep-0045.html#enter). + # Compatibility Requires Prosody 0.12.x or trunk
--- a/mod_ping_muc/mod_ping_muc.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_ping_muc/mod_ping_muc.lua Tue Mar 18 00:31:36 2025 +0700 @@ -28,43 +28,45 @@ for host, state in pairs(s2s_session.hosts) do if state.authed then related_hosts:add(host); end end end - for _, user_session in pairs(local_sessions) do - for _, session in pairs(user_session.sessions) do - if session.rooms_joined then - for room, info in pairs(session.rooms_joined) do - local nick = info.nick or info; - local room_nick = room .. "/" .. nick; - if related_hosts:contains(jid.host(room)) then - -- User is in a MUC room for which the s2s connection was lost. Now what? + local ping_delay = module:get_option_number("ping_muc_delay", 60, 1); + + module:add_timer(ping_delay, function () + for _, user_session in pairs(local_sessions) do + for _, session in pairs(user_session.sessions) do + if session.rooms_joined then + for room, info in pairs(session.rooms_joined) do + local nick = info.nick or info; + local room_nick = room .. "/" .. nick; + if related_hosts:contains(jid.host(room)) then + -- User is in a MUC room for which the s2s connection was lost. Now what? - -- Self-ping - -- ========= - -- - -- Response of <iq type=result> means the user is still in the room - -- (and self-ping is supported), so we do nothing. - -- - -- An error reply either means the user has fallen out of the room, - -- or that self-ping is unsupported. In the later case, whether the - -- user is still joined is indeterminate and we might as well - -- pretend they fell out. - module:send_iq(st.iq({ type = "get"; id = id.medium(); from = session.full_jid; to = room_nick }) - :tag("ping", { xmlns = "urn:xmpp:ping"; })) - :catch(function(err) - module:send( - st.presence({ type = "unavailable"; id = id.medium(); to = session.full_jid; from = room_nick }) - :tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }) - :tag("item", { affiliation = "none"; role = "none" }) - :text_tag("reason", err.text or "Connection to remote server lost") - :up() - :tag("status", { code = "110" }):up() - :tag("status", { code = "333" }):up() - :reset()); - end); - -- TODO do this with some delay? + -- Self-ping + -- ========= + -- + -- Response of <iq type=result> means the user is still in the room + -- (and self-ping is supported), so we do nothing. + -- + -- An error reply either means the user has fallen out of the room, + -- or that self-ping is unsupported. In the later case, whether the + -- user is still joined is indeterminate and we might as well + -- pretend they fell out. + module:send_iq(st.iq({ type = "get"; id = id.medium(); from = session.full_jid; to = room_nick }) + :tag("ping", { xmlns = "urn:xmpp:ping"; })) + :catch(function(err) + module:send( + st.presence({ type = "unavailable"; id = id.medium(); to = session.full_jid; from = room_nick }) + :tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }) + :tag("item", { affiliation = "none"; role = "none" }) + :text_tag("reason", err.text or "Connection to remote server lost") + :up() + :tag("status", { code = "110" }):up() + :tag("status", { code = "333" }):up() + :reset()); + end); + end end end - end end - end + end) end);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_poke_strangers/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,24 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'Query the features and version of JIDs sending messages to contacts they are not subscribed to.' +... + +Introduction +============ + +In order to build heuristics for which messages are spam, it is necessary to +log as many details as possible about the spammers. This module sends a +version and disco query whenever a message is received from a JID to a user it +is not subscribed to. The results are printed to Prosody's log file at the +'info' level. Queried full JIDs are not queried again until Prosody restarts. + +The queries are sent regardless of whether the local user exists, so it does +not reveal if an account is active. + +Compatibility +============= + + ----- ------- + 0.9 Works + ----- -------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_post_msg/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,79 @@ +--- +summary: 'Receives HTTP POST request, parses it and relays it into XMPP.' +--- + +Introduction +============ + +Sometimes it's useful to have different interfaces to access XMPP. + +This module allows sending XMPP +[`<message>`](https://xmpp.org/rfcs/rfc6121.html#message) stanzas via a +simple HTTP API. + +Example usage +------------- + + curl http://example.com:5280/msg/user -u me@example.com:mypassword -H "Content-Type: text/plain" -d "Server@host has just crashed!" + +This would send a message to user\@example.com from me\@example.com + +Details +======= + +URL format +---------- + + /msg/ [recipient [@host] ]. + +The base URL defaults to `/msg`. This can be configured via Prosodys +[HTTP path settings][doc:http]. + +Authentication +-------------- + +Authentication is done by HTTP Basic. + + Authentication: Basic BASE64( "username@virtualhost:password" ) + +Payload formats +--------------- + +Supported formats are: + +`text/plain` +: The HTTP body is used as plain text message payload, in the `<body>` + element. + +`application/x-www-form-urlencoded` +: Allows more fields to be specified. + +`application/json` +: Similar to form data. + +Which one is selected via the `Content-Type` HTTP header. + +### Data fields + +The form data and JSON formats allow the following fields: + +`to` +: Can be used instead of having the receiver in the URL. + +`type` +: [Message type.](https://xmpp.org/rfcs/rfc6121.html#message-syntax-type) + +`body` +: Plain text message payload which goes in the `<body>` element. + +Acknowledgements +================ + +Some code originally borrowed from mod\_webpresence + +See also +======== + +[mod_rest] is a more advanced way to send messages and more via HTTP, +with a very similar API. +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_presence_cache/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,32 @@ +--- +summary: Cache presence from remote users +... + +Introduction +============ + +This module stores a timestamp of the latest presence received from +users contacts so that the client can see who is online faster when they +sign in, and won't have to wait for remote servers to reply. + +Configuration +============= + +Just enable the module. + + modules_enabled = { + -- more modules + "presence_cache"; + } + +Advanced configuration +====================== + +The size of the cache is tuneable: + + presence_cache_size = 99 + +Compatibility +============= + +Requires 0.10 or later
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_presence_dedup/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,20 @@ +--- +summary: Presence deduplication module +labels: +- 'Stage-Alpha' +... + +This module tries to squash incoming identical presence stanzas to save +some bandwidth at the cost of increased memory use. + +Configuration +============= + + Option Type Default + ------------------------------ -------- --------- + presence\_dedup\_cache\_size number `100` + +The only setting controls how many presence stanzas *per session* are +kept in memory for comparing with incoming presenece. + +Requires Prosody 0.10 or later.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_privacy_lists/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,33 @@ +--- +labels: +- Stage-Deprecated +summary: Privacy lists (XEP-0016) support +--- + +::: {.alert .alert-warning} +[XEP-0016 Privacy Lists] and this module has been deprecated, instead +use [mod_blocklist][doc:modules:mod_blocklist], included with Prosody. +::: + +Introduction +------------ + +Privacy lists are a flexible method for blocking communications. + +Originally known as mod\_privacy and bundled with Prosody, this module +was phased out in favour of the newer simpler blocking (XEP-0191) +protocol, implemented in [mod\_blocklist][doc:modules:mod_blocklist]. + +Configuration +------------- + +None. Each user can specify their privacy lists using their client (if +it supports XEP-0016). + +Compatibility +------------- + + ------ ------- + 0.9 Works + 0.10 Works + ------ -------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_private_adhoc/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,15 @@ +--- +summary: Retrieve private XML data via adhoc command +... + +Introduction +------------ + +This is a very simple module which implements an adhoc commant +toretrieves the users private XML data. + +Details +------- + +Simple module which adds the adhoc command "Query private data" and will +return the users private XML data (typically muc bookmarks,.. ).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_privilege/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,173 @@ +--- +labels: +- 'Stage-Beta' +summary: 'XEP-0356 (Privileged Entity) implementation' +... + +Introduction +============ + +Privileged Entity is an extension which allows entity/component to have +privileged access to server (set/get roster, send message on behalf of server, +send IQ stanza on behalf of user, access presence information). It can be used +to build services independently of server (e.g.: PEP service). + +Details +======= + +You can have all the details by reading the +[XEP-0356](http://xmpp.org/extensions/xep-0356.html). + +Only the latest version of the XEP is implemented (using namespace +`urn:xmpp:privilege:2`), if your component use an older version, please update. + +Note that roster permission is not fully implemented yet, roster pushes are not yet sent +to privileged entity. + +Usage +===== + +To use the module, like usual add **"privilege"** to your +modules\_enabled. Note that if you use it with a local component, you +also need to activate the module in your component section: + + modules_enabled = { + [...] + + "privilege"; + } + + [...] + + Component "pubsub.yourdomain.tld" + component_secret = "yourpassword" + modules_enabled = {"privilege"} + +then specify privileged entities **in your host section** like that: + + VirtualHost "yourdomain.tld" + + privileged_entities = { + ["romeo@montaigu.lit"] = { + roster = "get"; + presence = "managed_entity"; + }, + ["juliet@capulet.lit"] = { + roster = "both"; + message = "outgoing"; + presence = "roster"; + }, + ["pubsub.yourdomain.tld"] = { + roster = "get"; + message = "outgoing"; + presence = "roster"; + iq = { + ["http://jabber.org/protocol/pubsub"] = "set"; + }; + }, + } + +Here *romeo@montaigu.lit* can **get** roster of anybody on the host, and will +**have presence for any user** of the host, while *juliet@capulet.lit* can +**get** and **set** a roster, **send messages** on behalf of the server, and +**access presence of anybody linked to the host** (not only people on the +server, but also people in rosters of users of the server). + +*pubsub.yourdomain.tld* is a Pubsub/PEP component which can **get** roster of +anybody on the host, **send messages** on the behalf of the server, **access +presence of anybody linked to the host**, and **send IQ stanza of type "set" for +the namespace "http://jabber.org/protocol/pubsub"** (this can be used to +implement XEP-0376 "Pubsub Account Management"). + +**/!\\ Be extra careful when you give a permission to an entity/component, it's +a powerful access, only do it if you absolutely trust the component/entity, and +you know where the software is coming from** + +Configuration +============= + +roster +------ + +All the permissions give access to all accounts of the virtual host. + + -------- ------------------------------------------------ ---------------------- + roster none *(default)* No access to rosters + get Allow **read** access to rosters + set Allow **write** access to rosters + both Allow **read** and **write** access to rosters + -------- ------------------------------------------------ ---------------------- + +Note that roster implementation is incomplete at the moment, roster pushes are not yet +send to privileged entity. + +message +------- + + ------------------ ------------------------------------------------------------ + none *(default)* Can't send message from server + outgoing Allow to send message on behalf of server (from bare jids) + ------------------ ------------------------------------------------------------ + +presence +-------- + + ------------------ ------------------------------------------------------------------------------------------------ + none *(default)* Do not have extra presence information + managed\_entity Receive presence stanzas (except subscriptions) from host users + roster Receive all presence stanzas (except subsciptions) from host users and people in their rosters + ------------------ ------------------------------------------------------------------------------------------------ + +iq +-- + +IQ permission is a table mapping allowed namespaces to allowed stanza type. When +a namespace is specified, IQ stanza of the specified type (see below) can be +sent if and only if the first child element of the IQ stanza has the specified +namespace. See https://xmpp.org/extensions/xep-0356.html#iq for details. + +Allowed stanza type: + + -------- ------------------------------------------- + get Allow IQ stanza of type **get** + set Allow IQ stanza of type **set** + both Allow IQ stanza of type **get** and **set** + -------- ------------------------------------------- + +Compatibility +============= + +If you use it with Prosody 0.9 and with a component, you need to patch +core/mod\_component.lua to fire a new signal. To do it, copy the +following patch in a, for example, /tmp/component.patch file: + +``` {.patch} + diff --git a/plugins/mod_component.lua b/plugins/mod_component.lua + --- a/plugins/mod_component.lua + +++ b/plugins/mod_component.lua + @@ -85,6 +85,7 @@ + session.type = "component"; + module:log("info", "External component successfully authenticated"); + session.send(st.stanza("handshake")); + + module:fire_event("component-authenticated", { session = session }); + + return true; + end +``` + +Then, at the root of prosody, enter: + +`patch -p1 < /tmp/component.patch` + + ----- -------------------------------------------------- + trunk Works + 0.12 Works + 0.11 Works + 0.10 Works + 0.9 Need a patched core/mod\_component.lua (see above) + ----- -------------------------------------------------- + +Note +==== + +This module is often used with mod\_delegation (c.f. XEP for more details)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_proctitle/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,9 @@ +--- +summary: Set process name to prosody +... + +This module sets the process name to `prosody` so it shows up as such +instead of `lua` in process management tools. + +To use this module, you'll need the proctitle Lua library: +<https://github.com/hoelzro/lua-proctitle>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_profile/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,39 @@ +--- +labels: +- 'Stage-Obsolete' +summary: 'Replacement for mod\_vcard with vcard4 support and PEP integration' +superseded_by: mod_vcard_legacy +--- + +::: {.alert .alert-warning} +[mod\_vcard\_legacy][doc:modules:mod_vcard_legacy] and +[mod\_vcard4][doc:modules:mod_vcard4] included with Prosody 0.11.x +provide equivalent functionality. +::: + +# Introduction + +This module was an experimental replacement for [mod\_vcard]. In addition to +the ageing protocol defined by [XEP-0054], it also supports the [new +vCard 4 based protocol][xep0292] and integrates with [Personal +Eventing Protocol][xep0163]. + +Also supports [XEP-0398: User Avatar to vCard-Based Avatars Conversion]. + +The vCard 4, [User Avatar][xep0084] and [User Nickname][xep0172] +PEP nodes are updated when the vCard is changed.. + +# Configuration + + modules_enabled = { + -- "vcard"; -- This module must be removed + + "profile"; + } + +# Compatibility + +Requires Prosody **trunk** as of 2014-05-29. Won't work in 0.10.x. + +It depends on the trunk version of [mod\_pep][doc:modules:mod_pep] for PEP support, +previously known as [mod\_pep\_plus][doc:modules:mod_pep_plus].
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_prometheus/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,73 @@ +--- +labels: +- Stage-Obsolete +- Statistics +summary: Implementation of the Prometheus protocol +... + +Description +=========== + +This module implements the Prometheus reporting protocol, allowing you +to collect statistics directly from Prosody into Prometheus. + +See the [Prometheus documentation][prometheusconf] on the format for +more information. + +[prometheusconf]: https://prometheus.io/docs/instrumenting/exposition_formats/ + +::: {.alert .alert-info} +**Note:** For use with Prosody 0.12 or later we recommend the bundled +[mod_http_openmetrics](https://prosody.im/doc/modules/mod_http_openmetrics) +instead. This module (mod_prometheus) will continue to be available in the +community repository for use with older Prosody versions. +::: + +Configuration +============= + +mod\_prometheus itself doesn’t have any configuration option, but it +requires Prosody’s [internal statistics +provider](https://prosody.im/doc/statistics#built-in_providers) to be +enabled. You may also want to change the default collection interval +to the one your statistics consumer is using. See below for more information. + +```lua +statistics = "internal" +statistics_interval = 15 -- in seconds +``` + +::: {.alert .alert-warning} +**NOTE:** Make sure to put the statistics variables in the global section of +the configuration, **not** in a `VirtualHost` or `Component` section. You can +use `prosodyctl check` if you are unsure and want to check your configuration. +::: + +See also the documentation of Prosody’s [HTTP +server](https://prosody.im/doc/http), since Prometheus is an HTTP +protocol that is how you can customise its URL. The default one being +http://localhost:5280/metrics + +Scrape interval vs statistics_interval +-------------------------------------- + +The `statistics_interval` should be set to `"manual"` on trunk if and only +if you have a single Prometheus instance scraping Prosody. This will allow +the internal statistics gathering to run optimally. + +If you have multiple instances scraping Prosody, set `statistics_interval` +to the scrape interval of Prometheus to avoid errors in rate calculations +and similar. + +Future work will allow the use of `"manual"` with multiple Prometheus +instances and varying scrape intervals (stay tuned). + +Compatibility +============= + + ------- ------------- + 0.12 Works (but replaced by [mod_http_openmetrics](https://prosody.im/doc/modules/mod_http_openmetrics)) + 0.11 Works + 0.10 Works + 0.9 Does not work + ------- -------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_proxy65_whitelist/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,25 @@ +--- +labels: +- Stage-Alpha +summary: Limit which file transfer users can use +... + +Introduction +------------ + +This module attempts to restrict use of non-whitelisted XEP-0065 +proxies. + +Configuration +------------- + +Without any options, the module will restrict users to local [proxy65 +components](https://prosody.im/doc/modules/mod_proxy65). + + -- additional proxies to allow + allowed_streamhosts = { "proxy.eu.jabber.org" } + +The module will add all local proxies to that list. To prevent it from +doing that, set + + allow_local_streamhosts = false
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_eventsource/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,70 @@ +--- +labels: +- Stage-Beta +summary: Subscribe to pubsub nodes using the HTML5 EventSource API +... + +Introduction +------------ + +[Server-Sent Events](https://en.wikipedia.org/wiki/Server-sent_events) +is a simple HTTP/line-based protocol supported in HTML5, making it easy +to receive a stream of "events" in realtime using the Javascript +[EventSource +API](https://developer.mozilla.org/en-US/docs/Web/API/EventSource). + +EventSource is supported in [most modern +browsers](http://caniuse.com/#feat=eventsource), and for the remainder +there are 'polyfill' compatibility layers such as +[EventSource.js](https://github.com/remy/polyfills/blob/master/EventSource.js) +and [jquery.eventsource](https://github.com/rwldrn/jquery.eventsource). + +Details +------- + +Subscribing to a node from Javascript is easy: + + var source = new EventSource('http://pubsub.example.org:5280/eventsource/mynode'); + source.onmessage = function (event) { + console.log(event.data); // Do whatever you want with the data here + }; + +### Cross-domain issues + +The same cross-domain restrictions apply to EventSource that apply to +BOSH, and support for CORS is not clearly standardized yet. You may want +to proxy connections through your web server for this reason. See [BOSH: +Cross-domain +issues](https://prosody.im/doc/setting_up_bosh#proxying_requests) for +more information. + +Configuration +------------- + +There is no special configuration for this module. Simply load it onto a +pubsub component like so: + + Component "pubsub.example.org" "pubsub" + modules_enabled = { "pubsub_eventsource" } + +As it uses HTTP to serve the event streams, you can use Prosody's +standard [HTTP configuration options](https://prosody.im/doc/http) to +control how/where the streams are served. + +**Note about URLs:** It is important to get the event streams from the +correct hostname (that of the pubsub host). An example stream URL is +`http://pubsub.example.org:5280/eventsource/mynode`. If you need to +access the streams using another hostname (e.g. `example.org`) you can +use the `http_host` option under the Component, e.g. +`http_host = "example.org"`. For more information see the ['Virtual +Hosts'](https://prosody.im/doc/http#virtual_hosts) section of our HTTP +documentation. + +Compatibility +------------- + + ------- -------------- + 0.9 Works + 0.8 Doesn't work + Trunk Works + ------- --------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_feeds/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,61 @@ +--- +summary: Subscribe to Atom and RSS feeds over pubsub +rockspec: + build: + modules: + mod_pubsub_feeds.feeds: feeds.lib.lua +--- + +# Introduction + +This module allows Prosody to fetch Atom and RSS feeds for you, and push +new results to subscribers over XMPP. + +# Configuration + +This module needs to be be loaded together with +[mod\_pubsub][doc:modules:mod\_pubsub]. + +For example, this is how you could add it to an existing pubsub +component: + +``` lua +Component "pubsub.example.com" "pubsub" +modules_enabled = { "pubsub_feeds" } + +feeds = { + -- The part before = is used as PubSub node + planet_jabber = "http://planet.jabber.org/atom.xml"; + prosody_blog = "http://blog.prosody.im/feed/atom.xml"; +} +``` + +This example creates two nodes, 'planet\_jabber' and 'prosody\_blog' +that clients can subscribe to using +[XEP-0060](http://xmpp.org/extensions/xep-0060.html). Results are in +[ATOM 1.0 format](http://atomenabled.org/) for easy consumption. + +# WebSub {#pubsubhubbub} + +This module also implements [WebSub](https://www.w3.org/TR/websub/), +formerly known as +[PubSubHubbub](http://pubsubhubbub.googlecode.com/svn/trunk/pubsubhubbub-core-0.3.html). +This allows "feed hubs" to instantly push feed updates to subscribers. + +This may be removed in the future since it does not seem to be oft used +anymore. + +# Option summary + + Option Description + ------------------------------ -------------------------------------------------------------------------- + `feeds` A list of virtual nodes to create and their associated Atom or RSS URL. + `feed_pull_interval_seconds` Number of seconds between polling for new results (default 15 *minutes*) + `use_pubsubhubub` Set to `true` to enable WebSub + +# Compatibility + + ------ ------- + 0.12 Works + 0.11 Works + ------ -------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/.hgignore Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,3 @@ +test +style.yaml +.prettierignore \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/.prettierignore Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,1 @@ +README.md \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,150 @@ +--- +labels: +- "Stage-Beta" +summary: "Turn forgejo/github/gitlab webhooks into atom-in-pubsub" +rockspec: + build: + modules: + mod_pubsub_forgejo.templates: templates.lib.lua + mod_pubsub_forgejo.format: format.lib.lua +--- + +# Introduction + +This module accepts Forgejo webhooks and publishes them to a local +pubsub component as Atom entries for XMPP clients to subscribe to. +Such entries can be viewed with a pubsub-compatible XMPP client such as +[movim](https://movim.eu/) or [libervia](https://libervia.org/), or turned +into chat messages with a bot (cf last section of this document). +It is a more customisable `mod_pubsub_github`. + +It should also work with other forges such as github and gitlab (to be tested). + +# Configuration + +## Basic setup + +Load the module on a pubsub component: + +```{.lua} +Component "pubsub.example.com" "pubsub" + modules_enabled = { "pubsub_forgejo" } + forgejo_secret = "something-very-secret" -- copy this in the Forgejo web UI +``` + +The "Target URL" to configure in the Forgejo web UI should be either: + +- `http://pubsub.example.com:5280/pubsub_forgejo` +- `https://pubsub.example.com:5281/pubsub_forgejo` + +If your HTTP host doesn't match the pubsub component's address, you will +need to inform Prosody. For more info see Prosody's [HTTP server +documentation](https://prosody.im/doc/http#virtual_hosts). + +## Advanced setup + +### Publishing settings + +#### Pubsub actor + +By default, `forgejo_actor` is unset; this results in nodes being created by the prosody superuser. +Change this if you set up access control and you know what you are doing. + +#### Pubsub node + +By default, all events are published in the same pubsub node named "forgejo". +This can be changed by setting `forgejo_node` to a different value. + +Another option is to used different nodes based on which repository emitted the webhook. +This is useful if you configured the webhook at the user (or organisation) level instead of repository-level. +To set this up, define `forgejo_node_prefix` and `forgejo_node_mapping`. +`forgejo_node_mapping` must be a key in the the webhook "repository" payload, e.g., "full*name". Example: with `forge_node_prefix = "forgejo---"` and `forgejo_node_mapping = "full_name"`, webhooks emitted by the repository \_repo-name* in the _org-name_ organisation will be published in the node _forgejo---org-name/repo-name_. + +### Customizing the atom entry + +#### Pushes with no commits + +By default, pushes without commits (i.e., pushing tags) are ignored, because it leads +to weird entries like "romeo pushed 0 commit(s) to repo". +This behaviour can be changed by setting `forgejo_skip_commitless_push = false`. + +#### Atom entry templates + +By default, 3 webhooks events are handled (push, pull_request and release), +and the payload is turned into a atom entry by +using [util.interpolation](https://prosody.im/doc/developers/util/interpolation) templates. +The default templates can be viewed in the source of this module, in the `templates.lib.lua` +file. + +You can customise them using by setting `forgejo_templates`, which is merged with the default +templates. +In this table, keys are forgejo event names (`x-forgejo-template` request header). +Values of this table are tables too, where keys are atom elements and values are the templates +passed to [util.interpolation](https://prosody.im/doc/developers/util/interpolation). + +A few filters are provided: + +- `|shorten` strips the last 32 characters: useful to get a short commit hash +- `|firstline` only keeps the first line: useful to get a commit "title" +- `|branch` strips the first 12 characters: useful to get a branch name from `data.ref` +- `|tag` strips the first 11 characters: useful to get a tag name from `data.ref` + +Example: + +```{.lua} +forgejo_templates = { + pull_request = nil, -- suppress handling of `pull_request` events + release = { -- data is the JSON payload of the webhook + title = "{data.sender.username} {data.action} a release for {data.repository.name}", + content = "{data.release.name}", + id = "release-{data.release.tag_name}", + link = "{data.release.html_url}" + } +} +``` + +Examples payloads are provided in the `webhook-examples` + +# Publishing in a MUC + +You can use a bot that listen to pubsub events and converts them to MUC messages. +MattJ's [riddim](https://matthewwild.co.uk/projects/riddim/) is well suited for that. + +Example config, single pubsub node: + +```{.lua} +jid = "forgejo-bot@example.com" +password = "top-secret-stuff" +room = "room@rooms.example.com" +autojoin = room + +pubsub2room = { + "pubsub.example.com#forgejo" = { + room = room, + template = "${title}\n${content}\n${link@href}" + } +} +``` + +Example with several nodes: + +```{.lua} +local nodes = {"forgejo---org/repo1", "forgejo---org/repo2"} +pubsub2room = {} + +for _, node in ipairs(slidge_repos) do + pubsub2room = ["pubsub.example.com#" .. node] = { + room = room, + template = "${title}\n${content}\n${link@href}" + } +end +``` + +# TODO + +- Default templates for all event types +- (x)html content + +# Compatibility + +Works with prosody 0.12
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/format.lib.lua Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,44 @@ +local st = require "util.stanza"; +local datetime = require "util.datetime"; + +local function shorten(x) return string.sub(x, 1, -32) end + +local function firstline(x) return x:match("^[^\r\n]*") end + +local function branch(x) return string.sub(x, 12) end + +local function tag(x) return string.sub(x, 11) end + +local function noop(x) return x end + +local filters = { + shorten = shorten, + firstline = firstline, + branch = branch, + tag = tag +} + +local render = require"util.interpolation".new("%b{}", noop, filters); + +local function get_item(data, templates) + local function render_tpl(name) return render(templates[name], {data = data}) end + + local now = datetime.datetime() + local id = render(templates["id"], {data = data}) + -- LuaFormatter off + return st.stanza("item", {id = id, xmlns = "http://jabber.org/protocol/pubsub"}) + :tag("entry", {xmlns = "http://www.w3.org/2005/Atom"}) + :tag("id"):text(id):up() + :tag("title"):text(render_tpl("title")):up() + :tag("content", {type = "text"}):text(render_tpl("content")):up() + :tag("link", {rel = "alternate", href = render_tpl("link")}):up() + :tag("published"):text(now):up() + :tag("updated"):text(now):up() + :tag("author") + :tag("name") + :text(data.sender.username):up() + :tag("email"):text(data.sender.email) + -- LuaFormatter on +end + +return get_item
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/mod_pubsub_forgejo.lua Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,115 @@ +module:depends("http") +local pubsub_service = module:depends("pubsub").service + +local st = require "util.stanza" +local json = require "util.json" +local hashes = require "util.hashes" +local from_hex = require"util.hex".from +local hmacs = { + sha1 = hashes.hmac_sha1, + sha256 = hashes.hmac_sha256, + sha384 = hashes.hmac_sha384, + sha512 = hashes.hmac_sha512 +} + +local format = module:require "format" +local default_templates = module:require "templates" + +-- configuration +local forgejo_secret = module:get_option("forgejo_secret") + +local default_node = module:get_option("forgejo_node", "forgejo") +local node_prefix = module:get_option_string("forgejo_node_prefix", "forgejo/") +local node_mapping = module:get_option_string("forgejo_node_mapping") +local forgejo_actor = module:get_option_string("forgejo_actor") or true + +local skip_commitless_push = module:get_option_boolean( + "forgejo_skip_commitless_push", true) +local custom_templates = module:get_option("forgejo_templates") + +local forgejo_templates = default_templates + +if custom_templates ~= nil then + for k, v in pairs(custom_templates) do forgejo_templates[k] = v end +end + +-- used for develoment, should never be set in prod! +local insecure = module:get_option_boolean("forgejo_insecure", false) +-- validation +if not insecure then assert(forgejo_secret, "Please set 'forgejo_secret'") end + +local error_mapping = { + ["forbidden"] = 403, + ["item-not-found"] = 404, + ["internal-server-error"] = 500, + ["conflict"] = 409 +} + +local function verify_signature(secret, body, signature) + if insecure then return true end + if not signature then return false end + local algo, digest = signature:match("^([^=]+)=(%x+)") + if not algo then return false end + local hmac = hmacs[algo] + if not algo then return false end + return hmac(secret, body) == from_hex(digest) +end + +function handle_POST(event) + local request, response = event.request, event.response + + if not verify_signature(forgejo_secret, request.body, + request.headers.x_hub_signature) then + module:log("debug", "Signature validation failed") + return 401 + end + + local data = json.decode(request.body) + if not data then + response.status_code = 400 + return "Invalid JSON. From you of all people..." + end + + local forgejo_event = request.headers.x_forgejo_event or data.object_kind + + if skip_commitless_push and forgejo_event == "push" and data.total_commits == 0 then + module:log("debug", "Skipping push event with 0 commits") + return 501 + end + + if forgejo_templates[forgejo_event] == nil then + module:log("debug", "Unsupported forgejo event %q", forgejo_event) + return 501 + end + + local item = format(data, forgejo_templates[forgejo_event]) + + if item == nil then + module:log("debug", "Formatter returned nil for event %q", forgejo_event) + return 501 + end + + local node = default_node + if node_mapping then node = node_prefix .. data.repository[node_mapping] end + + create_node(node) + + local ok, err = pubsub_service:publish(node, forgejo_actor, item.attr.id, item) + if not ok then return error_mapping[err] or 500 end + + response.status_code = 202 + return "Thank you forgejo.\n" .. tostring(item:indent(1, " ")) +end + +module:provides("http", {route = {POST = handle_POST}}) + +function create_node(node) + if not pubsub_service.nodes[node] then + local ok, err = pubsub_service:create(node, true) + if not ok then + module:log("error", "Error creating node: %s", err) + else + module:log("debug", "Node %q created", node) + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/style.yaml Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,2 @@ +indent_width: 1 +use_tab: true
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/templates.lib.lua Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,22 @@ +local module = { + push = { + title = "{data.sender.username} pushed {data.total_commits} commit(s) to {data.repository.name}", + content = "{data.commits#- {item.message|firstline} ({item.author.name})\n}", + id = "push-{data.head_commit.id}", + link = "{data.repository.html_url}/compare/{data.before|shorten}..{data.after|shorten}" + }, + pull_request = { + title = "{data.sender.username} {data.action} a pull request for {data.repository.name}", + content = "{data.pull_request.title}", + id = "pull-{data.pull_request.number}-{data.pull_request.updated_at}", + link = "{data.pull_request.html_url}" + }, + release = { + title = "{data.sender.username} {data.action} a release for {data.repository.name}", + content = "{data.release.name}", + id = "release-{data.release.tag_name}", + link = "{data.release.html_url}" + } +} + +return module
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/test.lua Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,40 @@ +-- CLI script to ease templates writing +-- must be launched with `lua test.lua` after setting the following env vars, +-- (assuming prosody has been clone in ../../prosody-0.12) +-- LUA_CPATH=../../prosody-0.12/\?.so +-- LUA_PATH=../../prosody-0.12/\?.lua\;\?.lua +-- allow loading ".lib.lua" modules +local function loadlib(modulename) + local filename = modulename .. ".lib.lua" + local file = io.open(filename, "rb") + if file then + return load(file:read("a")), modulename + else + return filename .. " not found" + end +end + +table.insert(package.searchers, loadlib) + +local json = require "util.json" +local format = require "format" +local templates = require "templates" + +local function read_json(fname) + local f = io.open(fname) + assert(f ~= nil, fname) + local data = json.decode(f:read("a")) + f:close() + return data +end + +local function read_payload(dirname) + return read_json("./webhook-examples/" .. dirname .. "/content.json") +end + +local function pprint(stanza) print(stanza:indent(1, " "):pretty_print()) end + +pprint(format(read_payload("push"), templates.push)) +pprint(format(read_payload("pull_request"), templates.pull_request)) +-- pprint(format(read_payload("push_tag"), templates.push)) -- this is a push with 0 commits. It's ugly! +pprint(format(read_payload("release"), templates.release))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/webhook-examples/create/content.json Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,134 @@ +{ + "sha": "4c87e2dfed00747a28d8602692ba3d73871790cb", + "ref": "refs/tags/v0.2.0", + "ref_type": "tag", + "repository": { + "id": 301855, + "owner": { + "id": 205842, + "login": "slidge", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "slidge@noreply.codeberg.org", + "avatar_url": "https://codeberg.org/avatars/aa0eeeb5fe173938bdc665be92bf605efa1e8f4a905bc3327e4d5c7eddf584e5", + "html_url": "https://codeberg.org/slidge", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2024-08-30T14:16:14Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "https://slidge.im", + "description": "", + "visibility": "public", + "followers_count": 5, + "following_count": 0, + "starred_repos_count": 0, + "username": "slidge" + }, + "name": "slidgnal", + "full_name": "slidge/slidgnal", + "description": "A feature-rich Signal to XMPP gateway", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 471, + "language": "", + "languages_url": "https://codeberg.org/api/v1/repos/slidge/slidgnal/languages", + "html_url": "https://codeberg.org/slidge/slidgnal", + "url": "https://codeberg.org/api/v1/repos/slidge/slidgnal", + "link": "", + "ssh_url": "git@codeberg.org:slidge/slidgnal.git", + "clone_url": "https://codeberg.org/slidge/slidgnal.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 0, + "watchers_count": 6, + "open_issues_count": 5, + "open_pr_counter": 0, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-01-10T14:37:52Z", + "updated_at": "2025-02-09T06:26:00Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": false, + "push": false, + "pull": false + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": false, + "wiki_branch": "main", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": false, + "has_releases": true, + "has_packages": true, + "has_actions": false, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "default_update_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": [ + "bridge", + "gateway", + "instant-messaging", + "signal", + "slidge", + "xmpp" + ] + }, + "sender": { + "id": 64076, + "login": "nicoco", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "nicoco@noreply.codeberg.org", + "avatar_url": "https://codeberg.org/avatars/32a918ca7a66e4e484ee2ccc625dc6451da728355c060ed0ed54fa69d89224a5", + "html_url": "https://codeberg.org/nicoco", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2022-09-12T11:13:13Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "Nice, France", + "pronouns": "", + "website": "https://nicoco.fr", + "description": "wannabe-hacker", + "visibility": "public", + "followers_count": 2, + "following_count": 0, + "starred_repos_count": 4, + "username": "nicoco" + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/webhook-examples/create/headers Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,18 @@ +Content-Type: application/json +X-Forgejo-Delivery: 2e3badae-779d-4627-94c7-505deeee8b55 +X-Forgejo-Event: create +X-Forgejo-Event-Type: create +X-Forgejo-Signature: 99f6adf0a156c379057735690dcfae3f483a026dde78ace6252b3da60b0e6bad +X-GitHub-Delivery: 2e3badae-779d-4627-94c7-505deeee8b55 +X-GitHub-Event: create +X-GitHub-Event-Type: create +X-Gitea-Delivery: 2e3badae-779d-4627-94c7-505deeee8b55 +X-Gitea-Event: create +X-Gitea-Event-Type: create +X-Gitea-Signature: 99f6adf0a156c379057735690dcfae3f483a026dde78ace6252b3da60b0e6bad +X-Gogs-Delivery: 2e3badae-779d-4627-94c7-505deeee8b55 +X-Gogs-Event: create +X-Gogs-Event-Type: create +X-Gogs-Signature: 99f6adf0a156c379057735690dcfae3f483a026dde78ace6252b3da60b0e6bad +X-Hub-Signature: sha1=a6edaf56463683b675cbc3a081292369a143c2c4 +X-Hub-Signature-256: sha256=99f6adf0a156c379057735690dcfae3f483a026dde78ace6252b3da60b0e6bad
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/webhook-examples/pull_request/content.json Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,501 @@ +{ + "action": "opened", + "number": 38, + "pull_request": { + "id": 270810, + "url": "https://codeberg.org/slidge/slidge/pulls/38", + "number": 38, + "user": { + "id": 64076, + "login": "nicoco", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "nicoco@noreply.codeberg.org", + "avatar_url": "https://codeberg.org/avatars/32a918ca7a66e4e484ee2ccc625dc6451da728355c060ed0ed54fa69d89224a5", + "html_url": "https://codeberg.org/nicoco", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2022-09-12T11:13:13Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "Nice, France", + "pronouns": "", + "website": "https://nicoco.fr", + "description": "wannabe-hacker", + "visibility": "public", + "followers_count": 1, + "following_count": 0, + "starred_repos_count": 4, + "username": "nicoco" + }, + "title": "dev", + "body": "test\r\n\r\nblabla", + "labels": [], + "milestone": null, + "assignee": null, + "assignees": null, + "requested_reviewers": null, + "requested_reviewers_teams": null, + "state": "open", + "draft": false, + "is_locked": false, + "comments": 0, + "review_comments": 0, + "additions": 338, + "deletions": 211, + "changed_files": 10, + "html_url": "https://codeberg.org/slidge/slidge/pulls/38", + "diff_url": "https://codeberg.org/slidge/slidge/pulls/38.diff", + "patch_url": "https://codeberg.org/slidge/slidge/pulls/38.patch", + "mergeable": true, + "merged": false, + "merged_at": null, + "merge_commit_sha": null, + "merged_by": null, + "allow_maintainer_edit": true, + "base": { + "label": "main", + "ref": "main", + "sha": "84bdc13530ef5fdd001eb55eb4ff9a4545fd1182", + "repo_id": 298187, + "repo": { + "id": 298187, + "owner": { + "id": 205842, + "login": "slidge", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://codeberg.org/avatars/aa0eeeb5fe173938bdc665be92bf605efa1e8f4a905bc3327e4d5c7eddf584e5", + "html_url": "https://codeberg.org/slidge", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2024-08-30T14:16:14Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "https://slidge.im", + "description": "", + "visibility": "public", + "followers_count": 3, + "following_count": 0, + "starred_repos_count": 0, + "username": "slidge" + }, + "name": "slidge", + "full_name": "slidge/slidge", + "description": "An XMPP/other chat networks gateway framework in python", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 7536, + "language": "", + "languages_url": "https://codeberg.org/api/v1/repos/slidge/slidge/languages", + "html_url": "https://codeberg.org/slidge/slidge", + "url": "https://codeberg.org/api/v1/repos/slidge/slidge", + "link": "", + "ssh_url": "git@codeberg.org:slidge/slidge.git", + "clone_url": "https://codeberg.org/slidge/slidge.git", + "original_url": "", + "website": "https://slidge.im/", + "stars_count": 1, + "forks_count": 1, + "watchers_count": 4, + "open_issues_count": 26, + "open_pr_counter": 0, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-01-02T05:57:37Z", + "updated_at": "2025-01-16T10:44:09Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": false, + "push": false, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": false, + "wiki_branch": "main", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": false, + "has_releases": true, + "has_packages": true, + "has_actions": false, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": true, + "default_merge_style": "rebase", + "default_allow_maintainer_edit": true, + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": [ + "bridge", + "chat", + "gateway", + "instant-messaging", + "python", + "xmpp" + ] + } + }, + "head": { + "label": "dev", + "ref": "dev", + "sha": "333f86a71bc5dbcf27de4dfaf9006890b86dee34", + "repo_id": 303404, + "repo": { + "id": 303404, + "owner": { + "id": 64076, + "login": "nicoco", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "nicoco@noreply.codeberg.org", + "avatar_url": "https://codeberg.org/avatars/32a918ca7a66e4e484ee2ccc625dc6451da728355c060ed0ed54fa69d89224a5", + "html_url": "https://codeberg.org/nicoco", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2022-09-12T11:13:13Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "Nice, France", + "pronouns": "", + "website": "https://nicoco.fr", + "description": "wannabe-hacker", + "visibility": "public", + "followers_count": 1, + "following_count": 0, + "starred_repos_count": 4, + "username": "nicoco" + }, + "name": "slidge", + "full_name": "nicoco/slidge", + "description": "An XMPP/other chat networks framework in python", + "empty": false, + "private": false, + "fork": true, + "template": false, + "parent": { + "id": 298187, + "owner": { + "id": 205842, + "login": "slidge", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://codeberg.org/avatars/aa0eeeb5fe173938bdc665be92bf605efa1e8f4a905bc3327e4d5c7eddf584e5", + "html_url": "https://codeberg.org/slidge", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2024-08-30T14:16:14Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "https://slidge.im", + "description": "", + "visibility": "public", + "followers_count": 3, + "following_count": 0, + "starred_repos_count": 0, + "username": "slidge" + }, + "name": "slidge", + "full_name": "slidge/slidge", + "description": "An XMPP/other chat networks gateway framework in python", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 7536, + "language": "", + "languages_url": "https://codeberg.org/api/v1/repos/slidge/slidge/languages", + "html_url": "https://codeberg.org/slidge/slidge", + "url": "https://codeberg.org/api/v1/repos/slidge/slidge", + "link": "", + "ssh_url": "git@codeberg.org:slidge/slidge.git", + "clone_url": "https://codeberg.org/slidge/slidge.git", + "original_url": "", + "website": "https://slidge.im/", + "stars_count": 1, + "forks_count": 1, + "watchers_count": 4, + "open_issues_count": 26, + "open_pr_counter": 1, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-01-02T05:57:37Z", + "updated_at": "2025-01-16T10:44:09Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": false, + "push": false, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": false, + "wiki_branch": "main", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": false, + "has_releases": true, + "has_packages": true, + "has_actions": false, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": true, + "default_merge_style": "rebase", + "default_allow_maintainer_edit": true, + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": [ + "bridge", + "chat", + "gateway", + "instant-messaging", + "python", + "xmpp" + ] + }, + "mirror": false, + "size": 6562, + "language": "", + "languages_url": "https://codeberg.org/api/v1/repos/nicoco/slidge/languages", + "html_url": "https://codeberg.org/nicoco/slidge", + "url": "https://codeberg.org/api/v1/repos/nicoco/slidge", + "link": "", + "ssh_url": "git@codeberg.org:nicoco/slidge.git", + "clone_url": "https://codeberg.org/nicoco/slidge.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 0, + "watchers_count": 1, + "open_issues_count": 0, + "open_pr_counter": 0, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-01-14T15:21:10Z", + "updated_at": "2025-01-17T14:04:37Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": false, + "push": false, + "pull": true + }, + "has_issues": false, + "has_wiki": false, + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": false, + "has_releases": false, + "has_packages": false, + "has_actions": false, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": null + } + }, + "merge_base": "84bdc13530ef5fdd001eb55eb4ff9a4545fd1182", + "due_date": null, + "created_at": "2025-01-17T14:04:59Z", + "updated_at": "2025-01-17T14:04:59Z", + "closed_at": null, + "pin_order": 0 + }, + "requested_reviewer": null, + "repository": { + "id": 298187, + "owner": { + "id": 205842, + "login": "slidge", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://codeberg.org/avatars/aa0eeeb5fe173938bdc665be92bf605efa1e8f4a905bc3327e4d5c7eddf584e5", + "html_url": "https://codeberg.org/slidge", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2024-08-30T14:16:14Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "https://slidge.im", + "description": "", + "visibility": "public", + "followers_count": 3, + "following_count": 0, + "starred_repos_count": 0, + "username": "slidge" + }, + "name": "slidge", + "full_name": "slidge/slidge", + "description": "An XMPP/other chat networks gateway framework in python", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 7536, + "language": "", + "languages_url": "https://codeberg.org/api/v1/repos/slidge/slidge/languages", + "html_url": "https://codeberg.org/slidge/slidge", + "url": "https://codeberg.org/api/v1/repos/slidge/slidge", + "link": "", + "ssh_url": "git@codeberg.org:slidge/slidge.git", + "clone_url": "https://codeberg.org/slidge/slidge.git", + "original_url": "", + "website": "https://slidge.im/", + "stars_count": 1, + "forks_count": 1, + "watchers_count": 4, + "open_issues_count": 26, + "open_pr_counter": 0, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-01-02T05:57:37Z", + "updated_at": "2025-01-16T10:44:09Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": false, + "wiki_branch": "main", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": false, + "has_releases": true, + "has_packages": true, + "has_actions": false, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": true, + "default_merge_style": "rebase", + "default_allow_maintainer_edit": true, + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": [ + "bridge", + "chat", + "gateway", + "instant-messaging", + "python", + "xmpp" + ] + }, + "sender": { + "id": 64076, + "login": "nicoco", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "nicoco@noreply.codeberg.org", + "avatar_url": "https://codeberg.org/avatars/32a918ca7a66e4e484ee2ccc625dc6451da728355c060ed0ed54fa69d89224a5", + "html_url": "https://codeberg.org/nicoco", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2022-09-12T11:13:13Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "Nice, France", + "pronouns": "", + "website": "https://nicoco.fr", + "description": "wannabe-hacker", + "visibility": "public", + "followers_count": 1, + "following_count": 0, + "starred_repos_count": 4, + "username": "nicoco" + }, + "commit_id": "", + "review": null +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/webhook-examples/pull_request/headers Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,18 @@ +Content-Type: application/json +X-Forgejo-Delivery: d9ef04ca-cba6-4b43-bd81-81f7b10a3415 +X-Forgejo-Event: pull_request +X-Forgejo-Event-Type: pull_request +X-Forgejo-Signature: 7963b65afbfaae02376cb5f44c4534bd28b34000e2c410badd1e3ebd922dbe4b +X-GitHub-Delivery: d9ef04ca-cba6-4b43-bd81-81f7b10a3415 +X-GitHub-Event: pull_request +X-GitHub-Event-Type: pull_request +X-Gitea-Delivery: d9ef04ca-cba6-4b43-bd81-81f7b10a3415 +X-Gitea-Event: pull_request +X-Gitea-Event-Type: pull_request +X-Gitea-Signature: 7963b65afbfaae02376cb5f44c4534bd28b34000e2c410badd1e3ebd922dbe4b +X-Gogs-Delivery: d9ef04ca-cba6-4b43-bd81-81f7b10a3415 +X-Gogs-Event: pull_request +X-Gogs-Event-Type: pull_request +X-Gogs-Signature: 7963b65afbfaae02376cb5f44c4534bd28b34000e2c410badd1e3ebd922dbe4b +X-Hub-Signature: sha1=57534fff7c23b977b2be006e66797faf31a1021b +X-Hub-Signature-256: sha256=7963b65afbfaae02376cb5f44c4534bd28b34000e2c410badd1e3ebd922dbe4b \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/webhook-examples/pull_request_comment/content.json Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,232 @@ +{ + "action": "created", + "issue": { + "id": 949460, + "url": "https://codeberg.org/api/v1/repos/slidge/slidge/issues/38", + "html_url": "https://codeberg.org/slidge/slidge/pulls/38", + "number": 38, + "user": { + "id": 64076, + "login": "nicoco", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "nicoco@nicoco.fr", + "avatar_url": "https://codeberg.org/avatars/32a918ca7a66e4e484ee2ccc625dc6451da728355c060ed0ed54fa69d89224a5", + "html_url": "https://codeberg.org/nicoco", + "language": "en-US", + "is_admin": false, + "last_login": "2025-01-16T17:43:26Z", + "created": "2022-09-12T11:13:13Z", + "restricted": false, + "active": true, + "prohibit_login": false, + "location": "Nice, France", + "pronouns": "", + "website": "https://nicoco.fr", + "description": "wannabe-hacker", + "visibility": "public", + "followers_count": 1, + "following_count": 0, + "starred_repos_count": 4, + "username": "nicoco" + }, + "original_author": "", + "original_author_id": 0, + "title": "dev", + "body": "test\r\n\r\nblabla", + "ref": "", + "assets": [], + "labels": [], + "milestone": null, + "assignee": null, + "assignees": null, + "state": "open", + "is_locked": false, + "comments": 0, + "created_at": "2025-01-17T14:04:59Z", + "updated_at": "2025-01-17T14:52:25Z", + "closed_at": null, + "due_date": null, + "pull_request": { + "merged": false, + "merged_at": null, + "draft": false, + "html_url": "https://codeberg.org/slidge/slidge/pulls/38" + }, + "repository": { + "id": 298187, + "name": "slidge", + "owner": "slidge", + "full_name": "slidge/slidge" + }, + "pin_order": 0 + }, + "comment": { + "id": 2592486, + "html_url": "https://codeberg.org/slidge/slidge/pulls/38#issuecomment-2592486", + "pull_request_url": "https://codeberg.org/slidge/slidge/pulls/38", + "issue_url": "", + "user": { + "id": 64076, + "login": "nicoco", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "nicoco@noreply.codeberg.org", + "avatar_url": "https://codeberg.org/avatars/32a918ca7a66e4e484ee2ccc625dc6451da728355c060ed0ed54fa69d89224a5", + "html_url": "https://codeberg.org/nicoco", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2022-09-12T11:13:13Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "Nice, France", + "pronouns": "", + "website": "https://nicoco.fr", + "description": "wannabe-hacker", + "visibility": "public", + "followers_count": 1, + "following_count": 0, + "starred_repos_count": 4, + "username": "nicoco" + }, + "original_author": "", + "original_author_id": 0, + "body": "a comment", + "assets": [], + "created_at": "2025-01-17T14:52:25Z", + "updated_at": "2025-01-17T14:52:25Z" + }, + "repository": { + "id": 298187, + "owner": { + "id": 205842, + "login": "slidge", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://codeberg.org/avatars/aa0eeeb5fe173938bdc665be92bf605efa1e8f4a905bc3327e4d5c7eddf584e5", + "html_url": "https://codeberg.org/slidge", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2024-08-30T14:16:14Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "https://slidge.im", + "description": "", + "visibility": "public", + "followers_count": 3, + "following_count": 0, + "starred_repos_count": 0, + "username": "slidge" + }, + "name": "slidge", + "full_name": "slidge/slidge", + "description": "An XMPP/other chat networks gateway framework in python", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 7536, + "language": "", + "languages_url": "https://codeberg.org/api/v1/repos/slidge/slidge/languages", + "html_url": "https://codeberg.org/slidge/slidge", + "url": "https://codeberg.org/api/v1/repos/slidge/slidge", + "link": "", + "ssh_url": "git@codeberg.org:slidge/slidge.git", + "clone_url": "https://codeberg.org/slidge/slidge.git", + "original_url": "", + "website": "https://slidge.im/", + "stars_count": 1, + "forks_count": 1, + "watchers_count": 4, + "open_issues_count": 26, + "open_pr_counter": 1, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-01-02T05:57:37Z", + "updated_at": "2025-01-16T10:44:09Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": false, + "wiki_branch": "main", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": false, + "has_releases": true, + "has_packages": true, + "has_actions": false, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": true, + "default_merge_style": "rebase", + "default_allow_maintainer_edit": true, + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": [ + "bridge", + "chat", + "gateway", + "instant-messaging", + "python", + "xmpp" + ] + }, + "sender": { + "id": 64076, + "login": "nicoco", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "nicoco@noreply.codeberg.org", + "avatar_url": "https://codeberg.org/avatars/32a918ca7a66e4e484ee2ccc625dc6451da728355c060ed0ed54fa69d89224a5", + "html_url": "https://codeberg.org/nicoco", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2022-09-12T11:13:13Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "Nice, France", + "pronouns": "", + "website": "https://nicoco.fr", + "description": "wannabe-hacker", + "visibility": "public", + "followers_count": 1, + "following_count": 0, + "starred_repos_count": 4, + "username": "nicoco" + }, + "is_pull": true +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/webhook-examples/pull_request_comment/headers Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,18 @@ +Content-Type: application/json +X-Forgejo-Delivery: 99fb308a-ed68-46d7-a8b5-4bf935ae294f +X-Forgejo-Event: issue_comment +X-Forgejo-Event-Type: pull_request_comment +X-Forgejo-Signature: 5fc374c851a20a0e18b65d32d9901c5f962cce950452cebc81bb5d6b0c023123 +X-GitHub-Delivery: 99fb308a-ed68-46d7-a8b5-4bf935ae294f +X-GitHub-Event: issue_comment +X-GitHub-Event-Type: pull_request_comment +X-Gitea-Delivery: 99fb308a-ed68-46d7-a8b5-4bf935ae294f +X-Gitea-Event: issue_comment +X-Gitea-Event-Type: pull_request_comment +X-Gitea-Signature: 5fc374c851a20a0e18b65d32d9901c5f962cce950452cebc81bb5d6b0c023123 +X-Gogs-Delivery: 99fb308a-ed68-46d7-a8b5-4bf935ae294f +X-Gogs-Event: issue_comment +X-Gogs-Event-Type: pull_request_comment +X-Gogs-Signature: 5fc374c851a20a0e18b65d32d9901c5f962cce950452cebc81bb5d6b0c023123 +X-Hub-Signature: sha1=ea62c579c31ae99001c1e9b769d4a71e4dbc4b7f +X-Hub-Signature-256: sha256=5fc374c851a20a0e18b65d32d9901c5f962cce950452cebc81bb5d6b0c023123 \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/webhook-examples/pull_request_rejected/content.json Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,468 @@ +{ + "action": "reviewed", + "number": 3591, + "pull_request": { + "id": 289137, + "url": "https://codeberg.org/poezio/slixmpp/pulls/3591", + "number": 3591, + "user": { + "id": 102461, + "login": "mathieui", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mathieui@noreply.codeberg.org", + "avatar_url": "https://codeberg.org/avatars/921cd14ed81b453b5eab40840af709a256e4fcb2d0c19ff47a7a33948978c3c4", + "html_url": "https://codeberg.org/mathieui", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2023-06-17T14:34:13Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 1, + "following_count": 0, + "starred_repos_count": 1, + "username": "mathieui" + }, + "title": "XEP-0410: add support", + "body": "This commit introduces an automated monitoring of MUC presence based on\r\nXEP-0410. An invisible background job is tasked with pinging inactive\r\nMUCs.", + "labels": [], + "milestone": null, + "assignee": null, + "assignees": null, + "requested_reviewers": [ + { + "id": 64076, + "login": "nicoco", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "nicoco@noreply.codeberg.org", + "avatar_url": "https://codeberg.org/avatars/32a918ca7a66e4e484ee2ccc625dc6451da728355c060ed0ed54fa69d89224a5", + "html_url": "https://codeberg.org/nicoco", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2022-09-12T11:13:13Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "Nice, France", + "pronouns": "", + "website": "https://nicoco.fr", + "description": "wannabe-hacker", + "visibility": "public", + "followers_count": 2, + "following_count": 0, + "starred_repos_count": 4, + "username": "nicoco" + } + ], + "requested_reviewers_teams": null, + "state": "open", + "draft": false, + "is_locked": false, + "comments": 0, + "review_comments": 1, + "additions": 407, + "deletions": 0, + "changed_files": 7, + "html_url": "https://codeberg.org/poezio/slixmpp/pulls/3591", + "diff_url": "https://codeberg.org/poezio/slixmpp/pulls/3591.diff", + "patch_url": "https://codeberg.org/poezio/slixmpp/pulls/3591.patch", + "mergeable": true, + "merged": false, + "merged_at": null, + "merge_commit_sha": null, + "merged_by": null, + "allow_maintainer_edit": false, + "base": { + "label": "master", + "ref": "master", + "sha": "e344947180bbfd667ac572176727f799349815db", + "repo_id": 123634, + "repo": { + "id": 123634, + "owner": { + "id": 102463, + "login": "poezio", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://codeberg.org/avatars/b9b08e0933aaa3579fb616166718292652ae8b70457e50de3a46c1e412f99631", + "html_url": "https://codeberg.org/poezio", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2023-06-17T14:35:46Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "cyberspace", + "pronouns": "", + "website": "", + "description": "Organization for poezio \u0026 slixmpp", + "visibility": "public", + "followers_count": 3, + "following_count": 0, + "starred_repos_count": 0, + "username": "poezio" + }, + "name": "slixmpp", + "full_name": "poezio/slixmpp", + "description": "Modern python XMPP library using asyncio.", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 12914, + "language": "", + "languages_url": "https://codeberg.org/api/v1/repos/poezio/slixmpp/languages", + "html_url": "https://codeberg.org/poezio/slixmpp", + "url": "https://codeberg.org/api/v1/repos/poezio/slixmpp", + "link": "", + "ssh_url": "git@codeberg.org:poezio/slixmpp.git", + "clone_url": "https://codeberg.org/poezio/slixmpp.git", + "original_url": "https://lab.louiz.org/poezio/slixmpp", + "website": "", + "stars_count": 14, + "forks_count": 9, + "watchers_count": 6, + "open_issues_count": 51, + "open_pr_counter": 12, + "release_counter": 10, + "default_branch": "master", + "archived": false, + "created_at": "2023-07-06T09:10:25Z", + "updated_at": "2025-02-13T22:17:56Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "wiki_branch": "master", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": true, + "has_releases": true, + "has_packages": true, + "has_actions": false, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": false, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "default_update_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": [ + "python", + "library", + "xmpp", + "jabber", + "asyncio" + ] + } + }, + "head": { + "label": "add-xep-0410", + "ref": "add-xep-0410", + "sha": "d768dfdc4b2f52570adfd0cdfc5480eafd5b258b", + "repo_id": 123634, + "repo": { + "id": 123634, + "owner": { + "id": 102463, + "login": "poezio", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://codeberg.org/avatars/b9b08e0933aaa3579fb616166718292652ae8b70457e50de3a46c1e412f99631", + "html_url": "https://codeberg.org/poezio", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2023-06-17T14:35:46Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "cyberspace", + "pronouns": "", + "website": "", + "description": "Organization for poezio \u0026 slixmpp", + "visibility": "public", + "followers_count": 3, + "following_count": 0, + "starred_repos_count": 0, + "username": "poezio" + }, + "name": "slixmpp", + "full_name": "poezio/slixmpp", + "description": "Modern python XMPP library using asyncio.", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 12914, + "language": "", + "languages_url": "https://codeberg.org/api/v1/repos/poezio/slixmpp/languages", + "html_url": "https://codeberg.org/poezio/slixmpp", + "url": "https://codeberg.org/api/v1/repos/poezio/slixmpp", + "link": "", + "ssh_url": "git@codeberg.org:poezio/slixmpp.git", + "clone_url": "https://codeberg.org/poezio/slixmpp.git", + "original_url": "https://lab.louiz.org/poezio/slixmpp", + "website": "", + "stars_count": 14, + "forks_count": 9, + "watchers_count": 6, + "open_issues_count": 51, + "open_pr_counter": 12, + "release_counter": 10, + "default_branch": "master", + "archived": false, + "created_at": "2023-07-06T09:10:25Z", + "updated_at": "2025-02-13T22:17:56Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "wiki_branch": "master", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": true, + "has_releases": true, + "has_packages": true, + "has_actions": false, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": false, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "default_update_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": [ + "python", + "library", + "xmpp", + "jabber", + "asyncio" + ] + } + }, + "merge_base": "e344947180bbfd667ac572176727f799349815db", + "due_date": null, + "created_at": "2025-02-13T22:18:10Z", + "updated_at": "2025-02-14T16:33:20Z", + "closed_at": null, + "pin_order": 0 + }, + "requested_reviewer": { + "id": 64076, + "login": "nicoco", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "nicoco@noreply.codeberg.org", + "avatar_url": "https://codeberg.org/avatars/32a918ca7a66e4e484ee2ccc625dc6451da728355c060ed0ed54fa69d89224a5", + "html_url": "https://codeberg.org/nicoco", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2022-09-12T11:13:13Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "Nice, France", + "pronouns": "", + "website": "https://nicoco.fr", + "description": "wannabe-hacker", + "visibility": "public", + "followers_count": 2, + "following_count": 0, + "starred_repos_count": 4, + "username": "nicoco" + }, + "repository": { + "id": 123634, + "owner": { + "id": 102463, + "login": "poezio", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://codeberg.org/avatars/b9b08e0933aaa3579fb616166718292652ae8b70457e50de3a46c1e412f99631", + "html_url": "https://codeberg.org/poezio", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2023-06-17T14:35:46Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "cyberspace", + "pronouns": "", + "website": "", + "description": "Organization for poezio \u0026 slixmpp", + "visibility": "public", + "followers_count": 3, + "following_count": 0, + "starred_repos_count": 0, + "username": "poezio" + }, + "name": "slixmpp", + "full_name": "poezio/slixmpp", + "description": "Modern python XMPP library using asyncio.", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 12914, + "language": "", + "languages_url": "https://codeberg.org/api/v1/repos/poezio/slixmpp/languages", + "html_url": "https://codeberg.org/poezio/slixmpp", + "url": "https://codeberg.org/api/v1/repos/poezio/slixmpp", + "link": "", + "ssh_url": "git@codeberg.org:poezio/slixmpp.git", + "clone_url": "https://codeberg.org/poezio/slixmpp.git", + "original_url": "https://lab.louiz.org/poezio/slixmpp", + "website": "", + "stars_count": 14, + "forks_count": 9, + "watchers_count": 6, + "open_issues_count": 51, + "open_pr_counter": 12, + "release_counter": 10, + "default_branch": "master", + "archived": false, + "created_at": "2023-07-06T09:10:25Z", + "updated_at": "2025-02-13T22:17:56Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "wiki_branch": "master", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": true, + "has_releases": true, + "has_packages": true, + "has_actions": false, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": false, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "default_update_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": [ + "python", + "library", + "xmpp", + "jabber", + "asyncio" + ] + }, + "sender": { + "id": 64076, + "login": "nicoco", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "nicoco@noreply.codeberg.org", + "avatar_url": "https://codeberg.org/avatars/32a918ca7a66e4e484ee2ccc625dc6451da728355c060ed0ed54fa69d89224a5", + "html_url": "https://codeberg.org/nicoco", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2022-09-12T11:13:13Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "Nice, France", + "pronouns": "", + "website": "https://nicoco.fr", + "description": "wannabe-hacker", + "visibility": "public", + "followers_count": 2, + "following_count": 0, + "starred_repos_count": 4, + "username": "nicoco" + }, + "commit_id": "", + "review": { + "type": "pull_request_review_rejected", + "content": "I spotted one typo, that review was not completely useless, YEAH!\r\n\r\nYou mentioned oob that you'd like more integration tests, maybe that plugin is a good candidate for them? Testing silent kick is probably near-impossible without a custom muc component, but the \"you're still joined\" path looks like it would be doable?" + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/webhook-examples/pull_request_rejected/headers Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,18 @@ +Content-Type: application/json +X-Forgejo-Delivery: 55de0e6c-593b-41d1-b208-ea00efe818c6 +X-Forgejo-Event: pull_request_rejected +X-Forgejo-Event-Type: pull_request_review_rejected +X-Forgejo-Signature: 52c66875ee76c8aba327c8bad7122e109f0b24ddeb0c291f57e071637dc60ccc +X-GitHub-Delivery: 55de0e6c-593b-41d1-b208-ea00efe818c6 +X-GitHub-Event: pull_request_rejected +X-GitHub-Event-Type: pull_request_review_rejected +X-Gitea-Delivery: 55de0e6c-593b-41d1-b208-ea00efe818c6 +X-Gitea-Event: pull_request_rejected +X-Gitea-Event-Type: pull_request_review_rejected +X-Gitea-Signature: 52c66875ee76c8aba327c8bad7122e109f0b24ddeb0c291f57e071637dc60ccc +X-Gogs-Delivery: 55de0e6c-593b-41d1-b208-ea00efe818c6 +X-Gogs-Event: pull_request_rejected +X-Gogs-Event-Type: pull_request_review_rejected +X-Gogs-Signature: 52c66875ee76c8aba327c8bad7122e109f0b24ddeb0c291f57e071637dc60ccc +X-Hub-Signature: sha1=ccb5a848dc0447d8a84b10308d5cc94d6a8dbad1 +X-Hub-Signature-256: sha256=52c66875ee76c8aba327c8bad7122e109f0b24ddeb0c291f57e071637dc60ccc
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/webhook-examples/push/content.json Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,268 @@ +{ + "ref": "refs/heads/dev", + "before": "3f8b7a2f69c6c891d8ab07700f113103db73faa3", + "after": "dd7c6923c788b38312fbc27cffba3b7007d0126e", + "compare_url": "https://codeberg.org/slidge/slidgram/compare/3f8b7a2f69c6c891d8ab07700f113103db73faa3...dd7c6923c788b38312fbc27cffba3b7007d0126e", + "commits": [ + { + "id": "dd7c6923c788b38312fbc27cffba3b7007d0126e", + "message": "build: update pre-commit hooks\n", + "url": "https://codeberg.org/slidge/slidgram/commit/dd7c6923c788b38312fbc27cffba3b7007d0126e", + "author": { + "name": "nicoco", + "email": "nicoco@nicoco.fr", + "username": "nicoco" + }, + "committer": { + "name": "nicoco", + "email": "nicoco@nicoco.fr", + "username": "nicoco" + }, + "verification": null, + "timestamp": "2025-01-16T13:33:08+01:00", + "added": [], + "removed": [], + "modified": [ + ".pre-commit-config.yaml" + ] + }, + { + "id": "790116333966f395cc268b032c7a6173486955f4", + "message": "chore: update lockfile\n\nbecause we must", + "url": "https://codeberg.org/slidge/slidgram/commit/790116333966f395cc268b032c7a6173486955f4", + "author": { + "name": "nicoco", + "email": "nicoco@nicoco.fr", + "username": "nicoco" + }, + "committer": { + "name": "nicoco", + "email": "nicoco@nicoco.fr", + "username": "nicoco" + }, + "verification": null, + "timestamp": "2025-01-13T15:47:16+01:00", + "added": [ + "uv.lock" + ], + "removed": [], + "modified": [] + }, + { + "id": "776de82113f8a860408f78c22e8539e73306ac87", + "message": "ci: use woodpecker\n", + "url": "https://codeberg.org/slidge/slidgram/commit/776de82113f8a860408f78c22e8539e73306ac87", + "author": { + "name": "nicoco", + "email": "nicoco@nicoco.fr", + "username": "nicoco" + }, + "committer": { + "name": "nicoco", + "email": "nicoco@nicoco.fr", + "username": "nicoco" + }, + "verification": null, + "timestamp": "2025-01-11T15:57:59+01:00", + "added": [ + ".woodpecker/container-ci.yaml", + ".woodpecker/container.yaml", + ".woodpecker/docs.yaml", + ".woodpecker/package.yaml", + ".woodpecker/test.yaml" + ], + "removed": [ + ".builds/ci.yml", + ".builds/container.yml", + ".copier-answers.yml" + ], + "modified": [ + ".gitignore", + "Dockerfile", + "README.md", + "docs/Makefile", + "docs/source/conf.py", + "pyproject.toml" + ] + } + ], + "total_commits": 3, + "head_commit": { + "id": "dd7c6923c788b38312fbc27cffba3b7007d0126e", + "message": "build: update pre-commit hooks\n", + "url": "https://codeberg.org/slidge/slidgram/commit/dd7c6923c788b38312fbc27cffba3b7007d0126e", + "author": { + "name": "nicoco", + "email": "nicoco@nicoco.fr", + "username": "nicoco" + }, + "committer": { + "name": "nicoco", + "email": "nicoco@nicoco.fr", + "username": "nicoco" + }, + "verification": null, + "timestamp": "2025-01-16T13:33:08+01:00", + "added": [], + "removed": [], + "modified": [ + ".pre-commit-config.yaml" + ] + }, + "repository": { + "id": 301703, + "owner": { + "id": 205842, + "login": "slidge", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://codeberg.org/avatars/aa0eeeb5fe173938bdc665be92bf605efa1e8f4a905bc3327e4d5c7eddf584e5", + "html_url": "https://codeberg.org/slidge", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2024-08-30T14:16:14Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "https://slidge.im", + "description": "", + "visibility": "public", + "followers_count": 3, + "following_count": 0, + "starred_repos_count": 0, + "username": "slidge" + }, + "name": "slidgram", + "full_name": "slidge/slidgram", + "description": "A feature-rich Telegram to XMPP gateway", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 1561, + "language": "", + "languages_url": "https://codeberg.org/api/v1/repos/slidge/slidgram/languages", + "html_url": "https://codeberg.org/slidge/slidgram", + "url": "https://codeberg.org/api/v1/repos/slidge/slidgram", + "link": "", + "ssh_url": "git@codeberg.org:slidge/slidgram.git", + "clone_url": "https://codeberg.org/slidge/slidgram.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 1, + "watchers_count": 3, + "open_issues_count": 7, + "open_pr_counter": 0, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-01-10T09:19:40Z", + "updated_at": "2025-01-16T12:15:22Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": false, + "wiki_branch": "main", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": false, + "has_releases": true, + "has_packages": true, + "has_actions": false, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": [ + "bridge", + "chat", + "gateway", + "instant-messaging", + "slidge", + "telegram", + "telegram-userbot", + "xmpp" + ] + }, + "pusher": { + "id": 64076, + "login": "nicoco", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "nicoco@noreply.codeberg.org", + "avatar_url": "https://codeberg.org/avatars/32a918ca7a66e4e484ee2ccc625dc6451da728355c060ed0ed54fa69d89224a5", + "html_url": "https://codeberg.org/nicoco", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2022-09-12T11:13:13Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "Nice, France", + "pronouns": "", + "website": "https://nicoco.fr", + "description": "wannabe-hacker", + "visibility": "public", + "followers_count": 1, + "following_count": 0, + "starred_repos_count": 3, + "username": "nicoco" + }, + "sender": { + "id": 64076, + "login": "nicoco", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "nicoco@noreply.codeberg.org", + "avatar_url": "https://codeberg.org/avatars/32a918ca7a66e4e484ee2ccc625dc6451da728355c060ed0ed54fa69d89224a5", + "html_url": "https://codeberg.org/nicoco", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2022-09-12T11:13:13Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "Nice, France", + "pronouns": "", + "website": "https://nicoco.fr", + "description": "wannabe-hacker", + "visibility": "public", + "followers_count": 1, + "following_count": 0, + "starred_repos_count": 3, + "username": "nicoco" + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/webhook-examples/push/headers Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,18 @@ +Content-Type: application/json +X-Forgejo-Delivery: eec91577-9e9a-4914-929e-b7870f77fd57 +X-Forgejo-Event: push +X-Forgejo-Event-Type: push +X-Forgejo-Signature: 0bd1c7325306a6f469df72d9966b2e89cebcf1fb790a1926ef9ff094141eed26 +X-GitHub-Delivery: eec91577-9e9a-4914-929e-b7870f77fd57 +X-GitHub-Event: push +X-GitHub-Event-Type: push +X-Gitea-Delivery: eec91577-9e9a-4914-929e-b7870f77fd57 +X-Gitea-Event: push +X-Gitea-Event-Type: push +X-Gitea-Signature: 0bd1c7325306a6f469df72d9966b2e89cebcf1fb790a1926ef9ff094141eed26 +X-Gogs-Delivery: eec91577-9e9a-4914-929e-b7870f77fd57 +X-Gogs-Event: push +X-Gogs-Event-Type: push +X-Gogs-Signature: 0bd1c7325306a6f469df72d9966b2e89cebcf1fb790a1926ef9ff094141eed26 +X-Hub-Signature: sha1=eacf3469341d9deecfcfbb3a63a4f1d55e60226c +X-Hub-Signature-256: sha256=0bd1c7325306a6f469df72d9966b2e89cebcf1fb790a1926ef9ff094141eed26 \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/webhook-examples/push/slidge.rss Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,341 @@ +<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"> + <channel> + <title>Feed of "slidge"</title> + <link>https://codeberg.org/slidge</link> + <description></description> + <pubDate>Fri, 17 Jan 2025 12:15:21 +0000</pubDate> + <item> + <title>aereaux commented on issue slidge/slidge#36</title> + <link>https://codeberg.org/slidge/slidge/issues/36#issuecomment-2590246</link> + <description>Error on startup

<p dir="auto">It does continue to run, but it doesn&#39;t set my avatar. I do seem to see my own avatar in group chats, at least in cheogram.</p>
</description> + <content:encoded><![CDATA[Error on startup + +<p dir="auto">It does continue to run, but it doesn't set my avatar. I do seem to see my own avatar in group chats, at least in cheogram.</p> +]]></content:encoded> + <author>aereaux</author> + <guid isPermaLink="false">22108577: https://codeberg.org/slidge/slidge/issues/36#issuecomment-2590246</guid> + <pubDate>Thu, 16 Jan 2025 15:32:41 +0000</pubDate> + </item> + <item> + <title>nicoco commented on issue slidge/slidge#36</title> + <link>https://codeberg.org/slidge/slidge/issues/36#issuecomment-2590128</link> + <description>Error on startup

<p dir="auto">I&#39;m not sure why this happens but it is non fatal, right? I suspect you don&#39;t see your own avatar in group chats though.</p>
</description> + <content:encoded><![CDATA[Error on startup + +<p dir="auto">I'm not sure why this happens but it is non fatal, right? I suspect you don't see your own avatar in group chats though.</p> +]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22106314: https://codeberg.org/slidge/slidge/issues/36#issuecomment-2590128</guid> + <pubDate>Thu, 16 Jan 2025 14:28:29 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidgram</title> + <link>/slidge/slidgram/compare/3f8b7a2f69c6c891d8ab07700f113103db73faa3...dd7c6923c788b38312fbc27cffba3b7007d0126e</link> + <description><a href="https://codeberg.org/slidge/slidgram/commit/dd7c6923c788b38312fbc27cffba3b7007d0126e">dd7c6923c788b38312fbc27cffba3b7007d0126e</a>
build: update pre-commit hooks

<a href="https://codeberg.org/slidge/slidgram/commit/790116333966f395cc268b032c7a6173486955f4">790116333966f395cc268b032c7a6173486955f4</a>
chore: update lockfile

<a href="https://codeberg.org/slidge/slidgram/commit/776de82113f8a860408f78c22e8539e73306ac87">776de82113f8a860408f78c22e8539e73306ac87</a>
ci: use woodpecker</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidgram/commit/dd7c6923c788b38312fbc27cffba3b7007d0126e" rel="nofollow">dd7c6923c788b38312fbc27cffba3b7007d0126e</a> +build: update pre-commit hooks + +<a href="https://codeberg.org/slidge/slidgram/commit/790116333966f395cc268b032c7a6173486955f4" rel="nofollow">790116333966f395cc268b032c7a6173486955f4</a> +chore: update lockfile + +<a href="https://codeberg.org/slidge/slidgram/commit/776de82113f8a860408f78c22e8539e73306ac87" rel="nofollow">776de82113f8a860408f78c22e8539e73306ac87</a> +ci: use woodpecker]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22100038: /slidge/slidgram/compare/3f8b7a2f69c6c891d8ab07700f113103db73faa3...dd7c6923c788b38312fbc27cffba3b7007d0126e</guid> + <pubDate>Thu, 16 Jan 2025 12:40:29 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidgram</title> + <link>https://codeberg.org/slidge/slidgram/commit/3f8b7a2f69c6c891d8ab07700f113103db73faa3</link> + <description><a href="https://codeberg.org/slidge/slidgram/commit/3f8b7a2f69c6c891d8ab07700f113103db73faa3">3f8b7a2f69c6c891d8ab07700f113103db73faa3</a>
fixup! ci: use woodpecker</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidgram/commit/3f8b7a2f69c6c891d8ab07700f113103db73faa3" rel="nofollow">3f8b7a2f69c6c891d8ab07700f113103db73faa3</a> +fixup! ci: use woodpecker]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22099622: https://codeberg.org/slidge/slidgram/commit/3f8b7a2f69c6c891d8ab07700f113103db73faa3</guid> + <pubDate>Thu, 16 Jan 2025 12:15:22 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidgram</title> + <link>https://codeberg.org/slidge/slidgram/commit/6a1c45d27839cc1d4db0edd0fbe074171b89fcc0</link> + <description><a href="https://codeberg.org/slidge/slidgram/commit/6a1c45d27839cc1d4db0edd0fbe074171b89fcc0">6a1c45d27839cc1d4db0edd0fbe074171b89fcc0</a>
fixup! ci: use woodpecker</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidgram/commit/6a1c45d27839cc1d4db0edd0fbe074171b89fcc0" rel="nofollow">6a1c45d27839cc1d4db0edd0fbe074171b89fcc0</a> +fixup! ci: use woodpecker]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22099101: https://codeberg.org/slidge/slidgram/commit/6a1c45d27839cc1d4db0edd0fbe074171b89fcc0</guid> + <pubDate>Thu, 16 Jan 2025 11:53:46 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidgram</title> + <link>/slidge/slidgram/compare/2dfc9cc3916e91cf1db677771bc1a41ba55f50aa...d818b8518ce676361e3d694ff9b14ec86cb7e31c</link> + <description><a href="https://codeberg.org/slidge/slidgram/commit/d818b8518ce676361e3d694ff9b14ec86cb7e31c">d818b8518ce676361e3d694ff9b14ec86cb7e31c</a>
fixup! ci: use woodpecker

<a href="https://codeberg.org/slidge/slidgram/commit/2e52e2170caeb1a2ac28635dd766e32a79dcbae9">2e52e2170caeb1a2ac28635dd766e32a79dcbae9</a>
chore: update lockfile

<a href="https://codeberg.org/slidge/slidgram/commit/31d0ee7595b08be34255add47e2b569ba1b05ac4">31d0ee7595b08be34255add47e2b569ba1b05ac4</a>
fixup! ci: use woodpecker

<a href="https://codeberg.org/slidge/slidgram/commit/e8530a185c5a783b1a98a3183326b1954f92997b">e8530a185c5a783b1a98a3183326b1954f92997b</a>
chore: update lockfile

<a href="https://codeberg.org/slidge/slidgram/commit/acf27d0ca32c4e193451fcb5025db0e9820380f8">acf27d0ca32c4e193451fcb5025db0e9820380f8</a>
ci: use woodpecker</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidgram/commit/d818b8518ce676361e3d694ff9b14ec86cb7e31c" rel="nofollow">d818b8518ce676361e3d694ff9b14ec86cb7e31c</a> +fixup! ci: use woodpecker + +<a href="https://codeberg.org/slidge/slidgram/commit/2e52e2170caeb1a2ac28635dd766e32a79dcbae9" rel="nofollow">2e52e2170caeb1a2ac28635dd766e32a79dcbae9</a> +chore: update lockfile + +<a href="https://codeberg.org/slidge/slidgram/commit/31d0ee7595b08be34255add47e2b569ba1b05ac4" rel="nofollow">31d0ee7595b08be34255add47e2b569ba1b05ac4</a> +fixup! ci: use woodpecker + +<a href="https://codeberg.org/slidge/slidgram/commit/e8530a185c5a783b1a98a3183326b1954f92997b" rel="nofollow">e8530a185c5a783b1a98a3183326b1954f92997b</a> +chore: update lockfile + +<a href="https://codeberg.org/slidge/slidgram/commit/acf27d0ca32c4e193451fcb5025db0e9820380f8" rel="nofollow">acf27d0ca32c4e193451fcb5025db0e9820380f8</a> +ci: use woodpecker]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22099034: /slidge/slidgram/compare/2dfc9cc3916e91cf1db677771bc1a41ba55f50aa...d818b8518ce676361e3d694ff9b14ec86cb7e31c</guid> + <pubDate>Thu, 16 Jan 2025 11:49:45 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidge</title> + <link>/slidge/slidge/compare/f2700e77b26f3a1391a3bcecc0db758398b9db73...9440989ef8c9b751eb4153f1e20e55627f8f548e</link> + <description><a href="https://codeberg.org/slidge/slidge/commit/9440989ef8c9b751eb4153f1e20e55627f8f548e">9440989ef8c9b751eb4153f1e20e55627f8f548e</a>
docs: readme: freshen up

<a href="https://codeberg.org/slidge/slidge/commit/43ea9bf36f1d74e09154e0a9d46e5f67e778fb74">43ea9bf36f1d74e09154e0a9d46e5f67e778fb74</a>
ci: factorisation, cron jobs, manual pipelines</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidge/commit/9440989ef8c9b751eb4153f1e20e55627f8f548e" rel="nofollow">9440989ef8c9b751eb4153f1e20e55627f8f548e</a> +docs: readme: freshen up + +<a href="https://codeberg.org/slidge/slidge/commit/43ea9bf36f1d74e09154e0a9d46e5f67e778fb74" rel="nofollow">43ea9bf36f1d74e09154e0a9d46e5f67e778fb74</a> +ci: factorisation, cron jobs, manual pipelines]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22097690: /slidge/slidge/compare/f2700e77b26f3a1391a3bcecc0db758398b9db73...9440989ef8c9b751eb4153f1e20e55627f8f548e</guid> + <pubDate>Thu, 16 Jan 2025 10:42:40 +0000</pubDate> + </item> + <item> + <title>nicoco deleted branch buildx from slidge/slidge</title> + <link>https://codeberg.org/slidge/slidge</link> + <description></description> + <author>nicoco</author> + <guid isPermaLink="false">22096886: https://codeberg.org/slidge/slidge</guid> + <pubDate>Thu, 16 Jan 2025 10:13:13 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidge</title> + <link>https://codeberg.org/slidge/slidge/commit/f2700e77b26f3a1391a3bcecc0db758398b9db73</link> + <description><a href="https://codeberg.org/slidge/slidge/commit/f2700e77b26f3a1391a3bcecc0db758398b9db73">f2700e77b26f3a1391a3bcecc0db758398b9db73</a>
ci: cache virtualenv; factorisation</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidge/commit/f2700e77b26f3a1391a3bcecc0db758398b9db73" rel="nofollow">f2700e77b26f3a1391a3bcecc0db758398b9db73</a> +ci: cache virtualenv; factorisation]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22096805: https://codeberg.org/slidge/slidge/commit/f2700e77b26f3a1391a3bcecc0db758398b9db73</guid> + <pubDate>Thu, 16 Jan 2025 10:08:14 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidge</title> + <link>https://codeberg.org/slidge/slidge/commit/b25334774b74b5d88572694a10ecf451282639b4</link> + <description><a href="https://codeberg.org/slidge/slidge/commit/b25334774b74b5d88572694a10ecf451282639b4">b25334774b74b5d88572694a10ecf451282639b4</a>
fixup! ci</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidge/commit/b25334774b74b5d88572694a10ecf451282639b4" rel="nofollow">b25334774b74b5d88572694a10ecf451282639b4</a> +fixup! ci]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22096514: https://codeberg.org/slidge/slidge/commit/b25334774b74b5d88572694a10ecf451282639b4</guid> + <pubDate>Thu, 16 Jan 2025 09:51:06 +0000</pubDate> + </item> + <item> + <title>c3p0-slidge pushed to main at slidge/pages</title> + <link>https://codeberg.org/slidge/pages/commit/4688fe65212fee04717f4b466185ba05ac0ccba0</link> + <description><a href="https://codeberg.org/slidge/pages/commit/4688fe65212fee04717f4b466185ba05ac0ccba0">4688fe65212fee04717f4b466185ba05ac0ccba0</a>
deploy docs for 03adac6ec948d70934756626ce9183b9321523dc</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/pages/commit/4688fe65212fee04717f4b466185ba05ac0ccba0" rel="nofollow">4688fe65212fee04717f4b466185ba05ac0ccba0</a> +deploy docs for 03adac6ec948d70934756626ce9183b9321523dc]]></content:encoded> + <author>c3p0-slidge</author> + <guid isPermaLink="false">22096350: https://codeberg.org/slidge/pages/commit/4688fe65212fee04717f4b466185ba05ac0ccba0</guid> + <pubDate>Thu, 16 Jan 2025 09:40:36 +0000</pubDate> + </item> + <item> + <title>c3p0-slidge pushed to main at slidge/pages</title> + <link>https://codeberg.org/slidge/pages/commit/c9ae413a9966ff51af5d4d4281e74001a1e8677d</link> + <description><a href="https://codeberg.org/slidge/pages/commit/c9ae413a9966ff51af5d4d4281e74001a1e8677d">c9ae413a9966ff51af5d4d4281e74001a1e8677d</a>
deploy docs for 03adac6ec948d70934756626ce9183b9321523dc</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/pages/commit/c9ae413a9966ff51af5d4d4281e74001a1e8677d" rel="nofollow">c9ae413a9966ff51af5d4d4281e74001a1e8677d</a> +deploy docs for 03adac6ec948d70934756626ce9183b9321523dc]]></content:encoded> + <author>c3p0-slidge</author> + <guid isPermaLink="false">22096294: https://codeberg.org/slidge/pages/commit/c9ae413a9966ff51af5d4d4281e74001a1e8677d</guid> + <pubDate>Thu, 16 Jan 2025 09:38:07 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidge</title> + <link>https://codeberg.org/slidge/slidge/commit/03adac6ec948d70934756626ce9183b9321523dc</link> + <description><a href="https://codeberg.org/slidge/slidge/commit/03adac6ec948d70934756626ce9183b9321523dc">03adac6ec948d70934756626ce9183b9321523dc</a>
fixup! ci</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidge/commit/03adac6ec948d70934756626ce9183b9321523dc" rel="nofollow">03adac6ec948d70934756626ce9183b9321523dc</a> +fixup! ci]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22096251: https://codeberg.org/slidge/slidge/commit/03adac6ec948d70934756626ce9183b9321523dc</guid> + <pubDate>Thu, 16 Jan 2025 09:36:07 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidge</title> + <link>https://codeberg.org/slidge/slidge/commit/6e945b3eae118271d19c5d94aa1bdbe8006b0267</link> + <description><a href="https://codeberg.org/slidge/slidge/commit/6e945b3eae118271d19c5d94aa1bdbe8006b0267">6e945b3eae118271d19c5d94aa1bdbe8006b0267</a>
fixup! ci</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidge/commit/6e945b3eae118271d19c5d94aa1bdbe8006b0267" rel="nofollow">6e945b3eae118271d19c5d94aa1bdbe8006b0267</a> +fixup! ci]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22095628: https://codeberg.org/slidge/slidge/commit/6e945b3eae118271d19c5d94aa1bdbe8006b0267</guid> + <pubDate>Thu, 16 Jan 2025 09:17:04 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidge</title> + <link>https://codeberg.org/slidge/slidge/commit/e27df12d6d6a950c4d6ba28c568f6d45fb7456ea</link> + <description><a href="https://codeberg.org/slidge/slidge/commit/e27df12d6d6a950c4d6ba28c568f6d45fb7456ea">e27df12d6d6a950c4d6ba28c568f6d45fb7456ea</a>
fixup! ci</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidge/commit/e27df12d6d6a950c4d6ba28c568f6d45fb7456ea" rel="nofollow">e27df12d6d6a950c4d6ba28c568f6d45fb7456ea</a> +fixup! ci]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22095366: https://codeberg.org/slidge/slidge/commit/e27df12d6d6a950c4d6ba28c568f6d45fb7456ea</guid> + <pubDate>Thu, 16 Jan 2025 09:02:26 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidge</title> + <link>https://codeberg.org/slidge/slidge/commit/f2c028957dbf2447ae54a0e7d9e94b7145d856d1</link> + <description><a href="https://codeberg.org/slidge/slidge/commit/f2c028957dbf2447ae54a0e7d9e94b7145d856d1">f2c028957dbf2447ae54a0e7d9e94b7145d856d1</a>
fixup! ci</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidge/commit/f2c028957dbf2447ae54a0e7d9e94b7145d856d1" rel="nofollow">f2c028957dbf2447ae54a0e7d9e94b7145d856d1</a> +fixup! ci]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22094639: https://codeberg.org/slidge/slidge/commit/f2c028957dbf2447ae54a0e7d9e94b7145d856d1</guid> + <pubDate>Thu, 16 Jan 2025 08:34:20 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to buildx at slidge/slidge</title> + <link>https://codeberg.org/slidge/slidge/commit/6c6a79b6b74c684585fca23910d29859253741e4</link> + <description><a href="https://codeberg.org/slidge/slidge/commit/6c6a79b6b74c684585fca23910d29859253741e4">6c6a79b6b74c684585fca23910d29859253741e4</a>
ci: fix auth</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidge/commit/6c6a79b6b74c684585fca23910d29859253741e4" rel="nofollow">6c6a79b6b74c684585fca23910d29859253741e4</a> +ci: fix auth]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22094450: https://codeberg.org/slidge/slidge/commit/6c6a79b6b74c684585fca23910d29859253741e4</guid> + <pubDate>Thu, 16 Jan 2025 08:26:14 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to buildx at slidge/slidge</title> + <link>https://codeberg.org/slidge/slidge/commit/af5cb61652c159ea382a3df7cc0711df82e2c6f3</link> + <description><a href="https://codeberg.org/slidge/slidge/commit/af5cb61652c159ea382a3df7cc0711df82e2c6f3">af5cb61652c159ea382a3df7cc0711df82e2c6f3</a>
ci: just build all the time</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidge/commit/af5cb61652c159ea382a3df7cc0711df82e2c6f3" rel="nofollow">af5cb61652c159ea382a3df7cc0711df82e2c6f3</a> +ci: just build all the time]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22094328: https://codeberg.org/slidge/slidge/commit/af5cb61652c159ea382a3df7cc0711df82e2c6f3</guid> + <pubDate>Thu, 16 Jan 2025 08:18:02 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to buildx at slidge/slidge</title> + <link>/slidge/slidge/compare/6e77ed7510a1dfd23f35b81affc90ded895515e6...3efecf7655b24623a122170b872782ac9cab9344</link> + <description><a href="https://codeberg.org/slidge/slidge/commit/3efecf7655b24623a122170b872782ac9cab9344">3efecf7655b24623a122170b872782ac9cab9344</a>
feat: allow multiple values for --cache-from

<a href="https://codeberg.org/slidge/slidge/commit/d689954ee5afee840d9c3ccb37eeb434c5554325">d689954ee5afee840d9c3ccb37eeb434c5554325</a>
chore(deps): update docker.io/mstruebing/editorconfig-checker docker tag to v3.1.2

<a href="https://codeberg.org/slidge/slidge/commit/3e6c614b92c907987cf50029eb4512dba44dadd2">3e6c614b92c907987cf50029eb4512dba44dadd2</a>
fix(deps): update module github.com/go-git/go-git/v5 to v5.13.1

<a href="https://codeberg.org/slidge/slidge/commit/9ff0fa678dccd7fc4be7cace72073b8d5fefd60a">9ff0fa678dccd7fc4be7cace72073b8d5fefd60a</a>
chore(deps): update davidanson/markdownlint-cli2 docker tag to v0.17.1

<a href="https://codeberg.org/slidge/slidge/commit/c8666a5831ecafa5c60450128f459191cd94c88a">c8666a5831ecafa5c60450128f459191cd94c88a</a>
fix(deps): update module github.com/go-git/go-git/v5 to v5.13.0</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidge/commit/3efecf7655b24623a122170b872782ac9cab9344" rel="nofollow">3efecf7655b24623a122170b872782ac9cab9344</a> +feat: allow multiple values for --cache-from + +<a href="https://codeberg.org/slidge/slidge/commit/d689954ee5afee840d9c3ccb37eeb434c5554325" rel="nofollow">d689954ee5afee840d9c3ccb37eeb434c5554325</a> +chore(deps): update docker.io/mstruebing/editorconfig-checker docker tag to v3.1.2 + +<a href="https://codeberg.org/slidge/slidge/commit/3e6c614b92c907987cf50029eb4512dba44dadd2" rel="nofollow">3e6c614b92c907987cf50029eb4512dba44dadd2</a> +fix(deps): update module github.com/go-git/go-git/v5 to v5.13.1 + +<a href="https://codeberg.org/slidge/slidge/commit/9ff0fa678dccd7fc4be7cace72073b8d5fefd60a" rel="nofollow">9ff0fa678dccd7fc4be7cace72073b8d5fefd60a</a> +chore(deps): update davidanson/markdownlint-cli2 docker tag to v0.17.1 + +<a href="https://codeberg.org/slidge/slidge/commit/c8666a5831ecafa5c60450128f459191cd94c88a" rel="nofollow">c8666a5831ecafa5c60450128f459191cd94c88a</a> +fix(deps): update module github.com/go-git/go-git/v5 to v5.13.0]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22094312: /slidge/slidge/compare/6e77ed7510a1dfd23f35b81affc90ded895515e6...3efecf7655b24623a122170b872782ac9cab9344</guid> + <pubDate>Thu, 16 Jan 2025 08:16:32 +0000</pubDate> + </item> + <item> + <title>nicoco created branch buildx in slidge/slidge</title> + <link>https://codeberg.org/slidge/slidge/src/branch/buildx</link> + <description></description> + <author>nicoco</author> + <guid isPermaLink="false">22094307: https://codeberg.org/slidge/slidge/src/branch/buildx</guid> + <pubDate>Thu, 16 Jan 2025 08:16:31 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidge</title> + <link>https://codeberg.org/slidge/slidge/commit/97a2c6d48ec1fc2fd085c3e4ff49ac60a17a76f9</link> + <description><a href="https://codeberg.org/slidge/slidge/commit/97a2c6d48ec1fc2fd085c3e4ff49ac60a17a76f9">97a2c6d48ec1fc2fd085c3e4ff49ac60a17a76f9</a>
fixup! ci</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidge/commit/97a2c6d48ec1fc2fd085c3e4ff49ac60a17a76f9" rel="nofollow">97a2c6d48ec1fc2fd085c3e4ff49ac60a17a76f9</a> +fixup! ci]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22077338: https://codeberg.org/slidge/slidge/commit/97a2c6d48ec1fc2fd085c3e4ff49ac60a17a76f9</guid> + <pubDate>Wed, 15 Jan 2025 21:03:37 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidge</title> + <link>https://codeberg.org/slidge/slidge/commit/3100ed625b7c10eac11161b0aa89f8947ed93df9</link> + <description><a href="https://codeberg.org/slidge/slidge/commit/3100ed625b7c10eac11161b0aa89f8947ed93df9">3100ed625b7c10eac11161b0aa89f8947ed93df9</a>
fixup! ci</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidge/commit/3100ed625b7c10eac11161b0aa89f8947ed93df9" rel="nofollow">3100ed625b7c10eac11161b0aa89f8947ed93df9</a> +fixup! ci]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22077169: https://codeberg.org/slidge/slidge/commit/3100ed625b7c10eac11161b0aa89f8947ed93df9</guid> + <pubDate>Wed, 15 Jan 2025 20:57:50 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidge</title> + <link>https://codeberg.org/slidge/slidge/commit/f1b58dc97cd7ecef06d4a37466021854ff32d801</link> + <description><a href="https://codeberg.org/slidge/slidge/commit/f1b58dc97cd7ecef06d4a37466021854ff32d801">f1b58dc97cd7ecef06d4a37466021854ff32d801</a>
fixup! ci</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidge/commit/f1b58dc97cd7ecef06d4a37466021854ff32d801" rel="nofollow">f1b58dc97cd7ecef06d4a37466021854ff32d801</a> +fixup! ci]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22076086: https://codeberg.org/slidge/slidge/commit/f1b58dc97cd7ecef06d4a37466021854ff32d801</guid> + <pubDate>Wed, 15 Jan 2025 20:22:45 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidge</title> + <link>https://codeberg.org/slidge/slidge/commit/c3445fd943872f214535621e901aaa364ed7f195</link> + <description><a href="https://codeberg.org/slidge/slidge/commit/c3445fd943872f214535621e901aaa364ed7f195">c3445fd943872f214535621e901aaa364ed7f195</a>
fixup! ci</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidge/commit/c3445fd943872f214535621e901aaa364ed7f195" rel="nofollow">c3445fd943872f214535621e901aaa364ed7f195</a> +fixup! ci]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22075973: https://codeberg.org/slidge/slidge/commit/c3445fd943872f214535621e901aaa364ed7f195</guid> + <pubDate>Wed, 15 Jan 2025 20:15:09 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidge</title> + <link>https://codeberg.org/slidge/slidge/commit/ff2e19117bc440b1de05acc78a4c6f0c6f072262</link> + <description><a href="https://codeberg.org/slidge/slidge/commit/ff2e19117bc440b1de05acc78a4c6f0c6f072262">ff2e19117bc440b1de05acc78a4c6f0c6f072262</a>
fixup! ci</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidge/commit/ff2e19117bc440b1de05acc78a4c6f0c6f072262" rel="nofollow">ff2e19117bc440b1de05acc78a4c6f0c6f072262</a> +fixup! ci]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22075651: https://codeberg.org/slidge/slidge/commit/ff2e19117bc440b1de05acc78a4c6f0c6f072262</guid> + <pubDate>Wed, 15 Jan 2025 19:59:07 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidge</title> + <link>https://codeberg.org/slidge/slidge/commit/c18cdbb3238b81ba30b259c5e31de843e0014d6f</link> + <description><a href="https://codeberg.org/slidge/slidge/commit/c18cdbb3238b81ba30b259c5e31de843e0014d6f">c18cdbb3238b81ba30b259c5e31de843e0014d6f</a>
fixup! ci</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidge/commit/c18cdbb3238b81ba30b259c5e31de843e0014d6f" rel="nofollow">c18cdbb3238b81ba30b259c5e31de843e0014d6f</a> +fixup! ci]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22075168: https://codeberg.org/slidge/slidge/commit/c18cdbb3238b81ba30b259c5e31de843e0014d6f</guid> + <pubDate>Wed, 15 Jan 2025 19:45:33 +0000</pubDate> + </item> + <item> + <title>c3p0-slidge pushed to main at slidge/pages</title> + <link>https://codeberg.org/slidge/pages/commit/40966a722929d092bb3eebc4729dfa73d47fde6d</link> + <description><a href="https://codeberg.org/slidge/pages/commit/40966a722929d092bb3eebc4729dfa73d47fde6d">40966a722929d092bb3eebc4729dfa73d47fde6d</a>
deploy docs for dfa0cd369d50fac8a4afb121a0a84dd43b310da7</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/pages/commit/40966a722929d092bb3eebc4729dfa73d47fde6d" rel="nofollow">40966a722929d092bb3eebc4729dfa73d47fde6d</a> +deploy docs for dfa0cd369d50fac8a4afb121a0a84dd43b310da7]]></content:encoded> + <author>c3p0-slidge</author> + <guid isPermaLink="false">22068872: https://codeberg.org/slidge/pages/commit/40966a722929d092bb3eebc4729dfa73d47fde6d</guid> + <pubDate>Wed, 15 Jan 2025 15:54:01 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidge</title> + <link>https://codeberg.org/slidge/slidge/commit/dfa0cd369d50fac8a4afb121a0a84dd43b310da7</link> + <description><a href="https://codeberg.org/slidge/slidge/commit/dfa0cd369d50fac8a4afb121a0a84dd43b310da7">dfa0cd369d50fac8a4afb121a0a84dd43b310da7</a>
fixup! ci</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidge/commit/dfa0cd369d50fac8a4afb121a0a84dd43b310da7" rel="nofollow">dfa0cd369d50fac8a4afb121a0a84dd43b310da7</a> +fixup! ci]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22063456: https://codeberg.org/slidge/slidge/commit/dfa0cd369d50fac8a4afb121a0a84dd43b310da7</guid> + <pubDate>Wed, 15 Jan 2025 14:27:05 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidge</title> + <link>https://codeberg.org/slidge/slidge/commit/3686603bae3b714f831e11714e0774707c26562b</link> + <description><a href="https://codeberg.org/slidge/slidge/commit/3686603bae3b714f831e11714e0774707c26562b">3686603bae3b714f831e11714e0774707c26562b</a>
fixup! ci</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidge/commit/3686603bae3b714f831e11714e0774707c26562b" rel="nofollow">3686603bae3b714f831e11714e0774707c26562b</a> +fixup! ci]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22063164: https://codeberg.org/slidge/slidge/commit/3686603bae3b714f831e11714e0774707c26562b</guid> + <pubDate>Wed, 15 Jan 2025 14:23:05 +0000</pubDate> + </item> + <item> + <title>nicoco pushed to dev at slidge/slidge</title> + <link>https://codeberg.org/slidge/slidge/commit/c6387ee4f1303de3cc3d1d1d0857c9062cbae795</link> + <description><a href="https://codeberg.org/slidge/slidge/commit/c6387ee4f1303de3cc3d1d1d0857c9062cbae795">c6387ee4f1303de3cc3d1d1d0857c9062cbae795</a>
fixup! ci</description> + <content:encoded><![CDATA[<a href="https://codeberg.org/slidge/slidge/commit/c6387ee4f1303de3cc3d1d1d0857c9062cbae795" rel="nofollow">c6387ee4f1303de3cc3d1d1d0857c9062cbae795</a> +fixup! ci]]></content:encoded> + <author>nicoco</author> + <guid isPermaLink="false">22063114: https://codeberg.org/slidge/slidge/commit/c6387ee4f1303de3cc3d1d1d0857c9062cbae795</guid> + <pubDate>Wed, 15 Jan 2025 14:20:08 +0000</pubDate> + </item> + </channel> +</rss> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/webhook-examples/push_tag/content.json Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,185 @@ +{ + "ref": "refs/tags/v0.2.0", + "before": "0000000000000000000000000000000000000000", + "after": "add5585f1b71de317aac5c54163ddb76c71856c6", + "compare_url": "https://codeberg.org/slidge/slidcord/compare/0000000000000000000000000000000000000000...add5585f1b71de317aac5c54163ddb76c71856c6", + "commits": [], + "total_commits": 0, + "head_commit": { + "id": "add5585f1b71de317aac5c54163ddb76c71856c6", + "message": "docs(readme): fix broken link to docs\n", + "url": "https://codeberg.org/slidge/slidcord/commit/add5585f1b71de317aac5c54163ddb76c71856c6", + "author": { + "name": "nicoco", + "email": "nicoco@nicoco.fr", + "username": "nicoco" + }, + "committer": { + "name": "nicoco", + "email": "nicoco@nicoco.fr", + "username": "nicoco" + }, + "verification": null, + "timestamp": "2025-02-08T14:07:44+01:00", + "added": [], + "removed": [], + "modified": [ + "README.md" + ] + }, + "repository": { + "id": 301856, + "owner": { + "id": 205842, + "login": "slidge", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://codeberg.org/avatars/aa0eeeb5fe173938bdc665be92bf605efa1e8f4a905bc3327e4d5c7eddf584e5", + "html_url": "https://codeberg.org/slidge", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2024-08-30T14:16:14Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "https://slidge.im", + "description": "", + "visibility": "public", + "followers_count": 4, + "following_count": 0, + "starred_repos_count": 0, + "username": "slidge" + }, + "name": "slidcord", + "full_name": "slidge/slidcord", + "description": "A feature-rich Discord to XMPP gateway", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 847, + "language": "", + "languages_url": "https://codeberg.org/api/v1/repos/slidge/slidcord/languages", + "html_url": "https://codeberg.org/slidge/slidcord", + "url": "https://codeberg.org/api/v1/repos/slidge/slidcord", + "link": "", + "ssh_url": "git@codeberg.org:slidge/slidcord.git", + "clone_url": "https://codeberg.org/slidge/slidcord.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 0, + "watchers_count": 6, + "open_issues_count": 5, + "open_pr_counter": 0, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-01-10T14:38:08Z", + "updated_at": "2025-02-04T19:37:54Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": false, + "wiki_branch": "main", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": false, + "has_releases": true, + "has_packages": true, + "has_actions": false, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "default_update_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": [ + "bridge", + "discord", + "gateway", + "instant-messaging", + "slidge", + "xmpp" + ] + }, + "pusher": { + "id": 64076, + "login": "nicoco", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "nicoco@noreply.codeberg.org", + "avatar_url": "https://codeberg.org/avatars/32a918ca7a66e4e484ee2ccc625dc6451da728355c060ed0ed54fa69d89224a5", + "html_url": "https://codeberg.org/nicoco", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2022-09-12T11:13:13Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "Nice, France", + "pronouns": "", + "website": "https://nicoco.fr", + "description": "wannabe-hacker", + "visibility": "public", + "followers_count": 2, + "following_count": 0, + "starred_repos_count": 4, + "username": "nicoco" + }, + "sender": { + "id": 64076, + "login": "nicoco", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "nicoco@noreply.codeberg.org", + "avatar_url": "https://codeberg.org/avatars/32a918ca7a66e4e484ee2ccc625dc6451da728355c060ed0ed54fa69d89224a5", + "html_url": "https://codeberg.org/nicoco", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2022-09-12T11:13:13Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "Nice, France", + "pronouns": "", + "website": "https://nicoco.fr", + "description": "wannabe-hacker", + "visibility": "public", + "followers_count": 2, + "following_count": 0, + "starred_repos_count": 4, + "username": "nicoco" + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/webhook-examples/push_tag/headers Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,18 @@ +Content-Type: application/json +X-Forgejo-Delivery: 82ddd28b-377f-487d-b0f7-e494db9cd763 +X-Forgejo-Event: push +X-Forgejo-Event-Type: push +X-Forgejo-Signature: 1cd5728a0b2e218c8dce3d706a2bdaaddcd9c32a0bad383d3762b95e2eef33c4 +X-GitHub-Delivery: 82ddd28b-377f-487d-b0f7-e494db9cd763 +X-GitHub-Event: push +X-GitHub-Event-Type: push +X-Gitea-Delivery: 82ddd28b-377f-487d-b0f7-e494db9cd763 +X-Gitea-Event: push +X-Gitea-Event-Type: push +X-Gitea-Signature: 1cd5728a0b2e218c8dce3d706a2bdaaddcd9c32a0bad383d3762b95e2eef33c4 +X-Gogs-Delivery: 82ddd28b-377f-487d-b0f7-e494db9cd763 +X-Gogs-Event: push +X-Gogs-Event-Type: push +X-Gogs-Signature: 1cd5728a0b2e218c8dce3d706a2bdaaddcd9c32a0bad383d3762b95e2eef33c4 +X-Hub-Signature: sha1=c8cc4aae9018b74a97f46c8766f9d5b85530baa1 +X-Hub-Signature-256: sha256=1cd5728a0b2e218c8dce3d706a2bdaaddcd9c32a0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/webhook-examples/release/content.json Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,180 @@ +{ + "action": "published", + "release": { + "id": 2802402, + "tag_name": "v0.2.0", + "target_commitish": "main", + "name": "broken", + "body": "It's broken! But I want to test the release webhook.", + "url": "https://codeberg.org/api/v1/repos/slidge/slidgnal/releases/2802402", + "html_url": "https://codeberg.org/slidge/slidgnal/releases/tag/v0.2.0", + "tarball_url": "https://codeberg.org/slidge/slidgnal/archive/v0.2.0.tar.gz", + "zipball_url": "https://codeberg.org/slidge/slidgnal/archive/v0.2.0.zip", + "hide_archive_links": false, + "upload_url": "https://codeberg.org/api/v1/repos/slidge/slidgnal/releases/2802402/assets", + "draft": false, + "prerelease": false, + "created_at": "2025-02-10T23:19:23Z", + "published_at": "2025-02-10T23:19:23Z", + "author": { + "id": 64076, + "login": "nicoco", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "nicoco@noreply.codeberg.org", + "avatar_url": "https://codeberg.org/avatars/32a918ca7a66e4e484ee2ccc625dc6451da728355c060ed0ed54fa69d89224a5", + "html_url": "https://codeberg.org/nicoco", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2022-09-12T11:13:13Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "Nice, France", + "pronouns": "", + "website": "https://nicoco.fr", + "description": "wannabe-hacker", + "visibility": "public", + "followers_count": 2, + "following_count": 0, + "starred_repos_count": 4, + "username": "nicoco" + }, + "assets": [], + "archive_download_count": { + "zip": 0, + "tar_gz": 0 + } + }, + "repository": { + "id": 301855, + "owner": { + "id": 205842, + "login": "slidge", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://codeberg.org/avatars/aa0eeeb5fe173938bdc665be92bf605efa1e8f4a905bc3327e4d5c7eddf584e5", + "html_url": "https://codeberg.org/slidge", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2024-08-30T14:16:14Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "https://slidge.im", + "description": "", + "visibility": "public", + "followers_count": 5, + "following_count": 0, + "starred_repos_count": 0, + "username": "slidge" + }, + "name": "slidgnal", + "full_name": "slidge/slidgnal", + "description": "A feature-rich Signal to XMPP gateway", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 471, + "language": "", + "languages_url": "https://codeberg.org/api/v1/repos/slidge/slidgnal/languages", + "html_url": "https://codeberg.org/slidge/slidgnal", + "url": "https://codeberg.org/api/v1/repos/slidge/slidgnal", + "link": "", + "ssh_url": "git@codeberg.org:slidge/slidgnal.git", + "clone_url": "https://codeberg.org/slidge/slidgnal.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 0, + "watchers_count": 6, + "open_issues_count": 5, + "open_pr_counter": 0, + "release_counter": 1, + "default_branch": "main", + "archived": false, + "created_at": "2025-01-10T14:37:52Z", + "updated_at": "2025-02-09T06:26:00Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": false, + "wiki_branch": "main", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": false, + "has_releases": true, + "has_packages": true, + "has_actions": false, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "default_update_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": [ + "bridge", + "gateway", + "instant-messaging", + "signal", + "slidge", + "xmpp" + ] + }, + "sender": { + "id": 64076, + "login": "nicoco", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "nicoco@noreply.codeberg.org", + "avatar_url": "https://codeberg.org/avatars/32a918ca7a66e4e484ee2ccc625dc6451da728355c060ed0ed54fa69d89224a5", + "html_url": "https://codeberg.org/nicoco", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2022-09-12T11:13:13Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "Nice, France", + "pronouns": "", + "website": "https://nicoco.fr", + "description": "wannabe-hacker", + "visibility": "public", + "followers_count": 2, + "following_count": 0, + "starred_repos_count": 4, + "username": "nicoco" + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_forgejo/webhook-examples/release/headers Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,18 @@ +Content-Type: application/json +X-Forgejo-Delivery: a5a5e377-23e0-4333-bd03-d6dac1f3e75c +X-Forgejo-Event: release +X-Forgejo-Event-Type: release +X-Forgejo-Signature: 4a2ee940e19a1c16281ce219d23be37b7cf8c5ca45107658e560ab73e958b69b +X-GitHub-Delivery: a5a5e377-23e0-4333-bd03-d6dac1f3e75c +X-GitHub-Event: release +X-GitHub-Event-Type: release +X-Gitea-Delivery: a5a5e377-23e0-4333-bd03-d6dac1f3e75c +X-Gitea-Event: release +X-Gitea-Event-Type: release +X-Gitea-Signature: 4a2ee940e19a1c16281ce219d23be37b7cf8c5ca45107658e560ab73e958b69b +X-Gogs-Delivery: a5a5e377-23e0-4333-bd03-d6dac1f3e75c +X-Gogs-Event: release +X-Gogs-Event-Type: release +X-Gogs-Signature: 4a2ee940e19a1c16281ce219d23be37b7cf8c5ca45107658e560ab73e958b69b +X-Hub-Signature: sha1=c8e893eadcd047e87270bdf930088149948c5b1b +X-Hub-Signature-256: sha256=4a2ee940e19a1c16281ce219d23be37b7cf8c5ca45107658e560ab73e958b69b \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_get/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,36 @@ +--- +labels: + - "Stage-Alpha" +summary: Get pubsub items via HTTP GET +--- + +# Introduction + +WARNING: this module does not implement any type of access control and will effectively make all +pubsub data public on the component it is loaded onto. + +This module lets you fetch the items of a specific pubsub node via an HTTP GET request. +I implemented it for a read-only view of comments published according to XEP-0277. + +# Configuration + +Nothing is configurable, just load the module on a specific component. + +```lua +Component "comments.example.com" "pubsub" + modules_enabled = { "pubsub_get" } +``` + +# Use + +To query the items of the node "urn:xmpp:microblog:0:comments/some-article", issue a GET for +`https://comments.example.com:5281/pubsub_get?node=urn:xmpp:microblog:0:comments/some-article`. +This will return a JSON object containing the items data. + +# TODO + +- Only return items with "open" access model + +# Compatibility + +Requires Prosody trunk / 0.12
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_get/mod_pubsub_get.lua Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,19 @@ +module:depends("http") +local pubsub_service = module:depends("pubsub").service +local json = require "util.json" + +function handle_GET(event) + local request, response = event.request, event.response + local query = request.url.query + + if query:sub(1, 5) ~= "node=" then return 400 end + + local node = query:sub(6) + local ok, items = pubsub_service:get_items(node, true) + + if not ok then return 404 end + response.status_code = 200 + return json.encode(items) +end + +module:provides("http", {route = {GET = handle_GET}})
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_github/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,61 @@ +--- +labels: +- 'Stage-Beta' +summary: Publish Github commits over pubsub +--- + +## Introduction + +This module accepts Github web hooks and publishes them to a local +pubsub component for XMPP clients to subscribe to. + +Entries are pushed as Atom payloads. + +It may also work with Gitlab. + +## Configuration + +Load the module on a pubsub component: + +``` {.lua} +Component "pubsub.example.com" "pubsub" + modules_enabled = { "pubsub_github" } + github_secret = "NP7bZooYSLKze96TQMpFW5ov" +``` + +The URL for Github to post to would be either: + +- `http://pubsub.example.com:5280/pubsub_github` +- `https://pubsub.example.com:5281/pubsub_github` + +The module also takes the following config options: + + Name Default Description + ----------------------- ------------------- ------------------------------------------------------------ + `github_node` `"github"`{.lua} The pubsub node to publish commits on. + `github_secret` **Required** Shared secret used to sign HTTP requests. + `github_node_prefix` `"github/"`{.lua} + `github_node_mapping` *not set* Field in repository object to use as node instead of `github_node` + `github_actor` *superuser* Which actor to do the publish as (used for access control) + +More advanced example + +``` {.lua} +Component "pubsub.example.com" "pubsub" + modules_enabled = { "pubsub_github" } + github_actor = "github.com" + github_node_mapping = "name" --> github_node_prefix .. "repo" + -- github_node_mapping = "full_name" --> github_node_prefix .. "owner/repo" + github_secret = "sekr1t" +``` + +If your HTTP host doesn't match the pubsub component's address, you will +need to inform Prosody. For more info see Prosody's [HTTP server +documentation](https://prosody.im/doc/http#virtual_hosts). + +## Compatibility + + ------ ------------- + 0.10 Should work + 0.9 Works + ------ -------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_hub/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,36 @@ +--- +summary: PubSubHubbub hub +... + +Introduction +============ + +This module implements a +[PubSubHubbub](http://pubsubhubbub.github.io/PubSubHubbub/pubsubhubbub-core-0.3.html) +(PuSH) hub, allowing PuSH clients to subscribe to local XMPP +[Publish-Subscribe](http://xmpp.org/extensions/xep-0060.html) nodes +stored by [mod\_pubsub](http://prosody.im/doc/modules/mod_pubsub) and +receive real time updates to feeds. + +Configuration +============= + + Component "pubsub.example.com" "pubsub" + + modules_enabled = { + "pubsub_hub"; + } + +The hub is then available on `http://pubsub.example.com:5280/hub`. + +Compatibility +============= + + ------- -------------- + trunk Works + 0.10 Should work + 0.9 Works + 0.8 Doesn't work + ------- -------------- + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_mqtt/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,68 @@ +--- +labels: +- 'Stage-Beta' +summary: 'MQTT interface to Prosody''s pubsub' +... + +Introduction +------------ + +[MQTT](http://mqtt.org/) is a lightweight binary pubsub protocol suited +to embedded devices. This module provides a way for MQTT clients to +connect to Prosody and publish or subscribe to local pubsub nodes. + +The module currently implements MQTT version 3.1.1. + +Details +------- + +MQTT has the concept of 'topics' (similar to XMPP's pubsub 'nodes'). +mod\_pubsub\_mqtt maps pubsub nodes to MQTT topics of the form +`<HOST>/<TYPE>/<NODE>`, e.g.`pubsub.example.org/json/mynode`. + +The 'TYPE' parameter in the topic allows the client to choose the payload +format it will send/receive. For the supported values of 'TYPE' see the +'Payloads' section below. + +### Limitations + +The current implementation is quite basic, and in particular: + +- Authentication is not supported +- Only QoS level 0 is supported + +### Payloads + +XMPP payloads are always XML, but MQTT does not define a payload format. +Therefore mod\_pubsub\_mqtt has some built-in data format translators. + +Currently supported data types: + +- `json`: See [XEP-0335](http://xmpp.org/extensions/xep-0335.html) for + the format. +- `utf8`: Plain UTF-8 text (wrapped inside + `<data xmlns="https://prosody.im/protocol/mqtt"/>`) +- `atom_title`: Returns the title of an Atom entry as UTF-8 data + +Configuration +------------- + +There is no special configuration for this module. Simply load it on +your pubsub host like so: + + Component "pubsub.example.org" "pubsub" + modules_enabled = { "pubsub_mqtt" } + +You may also configure which port(s) mod\_pubsub\_mqtt listens on using +Prosody's standard config directives, such as `mqtt_ports` and +`mqtt_tls_ports`. Network settings **must** be specified in the global section +of the config file, not under any particular pubsub component. The default +port is 1883 (MQTT's standard port number) and 8883 for TLS connections. + +Compatibility +------------- + + ------- -------------- + trunk Works + 0.12 Works + ------- --------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_post/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,157 @@ +--- +labels: +- 'Stage-Stable' +summary: Publish to PubSub nodes from via HTTP POST/WebHooks +--- + +# Introduction + +This module is a fairly generic WebHook receiver that lets you easily +publish data to PubSub using a HTTP POST request. The payload can be +Atom feeds, arbitrary XML, or arbitrary JSON. The type should be +indicated via the `Content-Type` header. + +- JSON data is wrapped in a [XEP-0335] container. +- An Atom feed may have many `<entry>` and each one is published as + its own PubSub item. +- Other XML is simply published to the item with ID `current`. + +## JSON example + +``` {.bash} +curl http://localhost:5280/pubsub_post/princely_musings \ + -H "Content-Type: application/json" \ + --data-binary '{"musing":"To be, or not to be: that is the question"}' +``` + +## Atom example + +``` {.bash} +curl http://localhost:5280/pubsub_post/princely_musings \ + -H "Content-Type: application/xml" \ + --data-binary '<feed xmlns="http://www.w3.org/2005/Atom"> + <entry><title>Hello</title></entry></feed>' +``` + +## Simple form-data + +``` {.bash} +curl http://localhost:5280/pubsub_post/princely_musings \ + --data musing="To be, or not to be: that is the question" +``` + +# Configuration + +All settings are optional. + +## Actor identification + +First we have to figure out who is making the request. +This is configured on a per-node basis like this: + +``` {.lua} +-- Per node secrets +pubsub_post_actors = { + princely_musings = "hamlet@denmark.lit" +} +pubsub_post_default_actor = "nobody@nowhere.invalid" +``` + +`pubsub_post_default_actor` is used when trying to publish to a node +that is not listed in `pubsub_post_actors`. Otherwise the IP address +of the connection is used. + +## Authentication + +[WebSub](https://www.w3.org/TR/2018/REC-websub-20180123/) [Authenticated +Content +Distribution](https://www.w3.org/TR/2018/REC-websub-20180123/#authenticated-content-distribution) +authentication is used. + +``` {.lua} +pubsub_post_secrets = { + princely_musings = "shared secret" +} +pubsub_post_default_secret = "default secret" +``` + +`pubsub_post_default_secret` is used when trying to publish to a node +that is not listed in `pubsub_post_secrets`. Otherwise the request +proceeds with the previously identified actor. + +::: {.alert .alert-danger} +If configured without a secret and a default actor that has permission +to create nodes the service becomes wide open. +::: + +## Authorization + +Authorization is handled via pubsub affiliations. Publishing requires an +affiliation with the _publish_ capability, usually `"publisher"`. + +### Setting up affiliations + +Prosodys PubSub module supports [setting affiliations via +XMPP](https://xmpp.org/extensions/xep-0060.html#owner-affiliations), +since 0.11.0, so affiliations can be configured with a capable client. + +It can however be done from another plugin: + +``` {.lua} +local mod_pubsub = module:depends("pubsub"); +local pubsub = mod_pubsub.service; + +pubsub:create("princely_musings", true); +pubsub:set_affiliation("princely_musings", true, "127.0.0.1", "publisher"); +``` + +## Data mappings + +The datamapper library added in 0.12.0 allows posting JSON and having it +converted to XML based on a special JSON Schema. + +``` json +{ + "properties" : { + "content" : { + "type" : "string" + }, + "title" : { + "type" : "string" + } + }, + "type" : "object", + "xml" : { + "name" : "musings", + "namespace" : "urn:example:princely" + } +} +``` + +And in the Prosody config file: + +``` lua +pubsub_post_mappings = { + princely_musings = "musings.json"; +} +``` + +Then, POSTing a JSON payload like + +``` json +{ + "content" : "To be, or not to be: that is the question", + "title" : "Soliloquy" +} +``` + +results in a payload like + +``` xml +<musings xmlns="urn:example:princely"> + <title>Soliloquy</title> + <content>To be, or not to be: that is the question</content> +</musings> +``` + +being published to the node.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_serverinfo/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,69 @@ +--- +labels: +- 'Statistics' +... + +Exposes server information over Pub/Sub per [XEP-0485: PubSub Server Information](https://xmpp.org/extensions/xep-0485.html). + +The module announces support (used to 'opt-in', per the XEP) and publishes the name of the local domain via a Pub/Sub node. The published data +will contain a 'remote-domain' element for inbound and outgoing s2s connections. These elements will be named only when the remote domain announces +support ('opts in') too. + +**Known issues:** + +- [Issue #1841](https://issues.prosody.im/1841): In Prosody 0.12, this module conflicts with mod_server_contact_info (both will run, but it may affect the ability of some implementations to read the server/contact information provided). To work around this issue, the [mod_server_contact_info](https://modules.prosody.im/mod_server_contact_info) community module can be used. + +Installation +============ + +Enable this module in the global or a virtual host. + +The default configuration requires the existence of a Pub/Sub component that uses the 'pubsub' subdomain of the host in which the module is enabled: + + Component "pubsub.example.org" "pubsub" + +The module will create a node and publish data, using a JID that matches the XMPP domain name of the host. Ensure that this actor is an admin of the +Pub/Sub service: + + admins = { "example.org" } + +Configuration +============= + +The Pub/Sub service on which data is published, by default, is a component addressed as the `pubsub` subdomain of the domain of the virtual host that +the module is loaded under. To change this, apply this configuration setting: + + pubsub_serverinfo_service = "anotherpubsub.example.org" + +The Pub/Sub node on which data is published is, by default, a leaf-node named `serverinfo`. To change this, apply this configuration setting: + + pubsub_serverinfo_node = "foobar" + +To prevent a surplus of event notifications, this module will only publish new data after a certain period of time has expired. The default duration +is 300 seconds (5 minutes). To change this simply put in the config: + + pubsub_serverinfo_publication_interval = 180 -- or any other number of seconds + +To detect if remote domains allow their domain name to be included in the data that this module publishes, this module will perform a service +discovery request to each remote domain. To prevent a continuous flood of disco/info requests, the response to these requests is cached. By default, +a cached value will remain in cache for one hour. This duration can be modified by adding this configuration option: + + pubsub_serverinfo_cache_ttl = 1800 -- or any other number of seconds + +To include the count of active (within the past 30 days) users: + + pubsub_serverinfo_publish_user_count = true + +Enabling this option will automatically load mod_measure_active_users. + +Compatibility +============= + +Incompatible with 0.11 or lower. + +Known Issues / TODOs +==================== + +The reported data does not contain the optional 'connection' child elements. These can be used to describe the direction of a connection. + +More generic server information (eg: user counts, software version) should be included in the data that is published.
--- a/mod_pubsub_serverinfo/mod_pubsub_serverinfo.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_pubsub_serverinfo/mod_pubsub_serverinfo.lua Tue Mar 18 00:31:36 2025 +0700 @@ -12,17 +12,26 @@ local public_providers_url = module:get_option_string(module.name.."_public_providers_url", "https://data.xmpp.net/providers/v2/providers-Ds.json"); local delete_node_on_unload = module:get_option_boolean(module.name.."_delete_node_on_unload", false); local persist_items = module:get_option_boolean(module.name.."_persist_items", true); +local include_user_count = module:get_option_boolean(module.name.."_publish_user_count", false); if not service and prosody.hosts["pubsub."..module.host] then service = "pubsub."..module.host; end -if not service +if not service then module:log_status("warn", "No pubsub service specified - module not activated"); return; end +local metric_registry = require "core.statsmanager".get_metric_registry(); +if include_user_count then + module:depends("measure_active_users"); +end + local xmlns_pubsub = "http://jabber.org/protocol/pubsub"; +-- Needed to publish server-info-fields +module:depends("server_info"); + function module.load() discover_node():next( function(exists) @@ -181,6 +190,10 @@ return domains_by_host end +local function get_gauge_metric(name) + return (metric_registry.families[name].data:get(module.host) or {}).value; +end + function publish_serverinfo() module:log("debug", "Publishing server info..."); local domains_by_host = get_remote_domain_names() @@ -208,7 +221,18 @@ end end - request:up():up() + request:up(); + + if include_user_count then + local mau = get_gauge_metric("prosody_mod_measure_active_users/active_users_30d"); + request:tag("users", { xmlns = "xmpp:prosody.im/protocol/serverinfo" }); + if mau then + request:text_tag("active", ("%d"):format(mau)); + end + request:up(); + end + + request:up() module:send_iq(request):next( function(response)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_stats/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,18 @@ +--- +labels: +- 'Statistics' +... + +This module publishes data from [Prosody's `internal` statistics][doc:statistics] + into a PubSub node in [XEP-0039] format. + +The node defaults to `stats` but can be changed with the option +`pubsub_stats_node`. + +``` lua +Component "pubsub.example.com" "pubsub" +modules_enabled = { + "pubsub_stats"; +} +pubsub_stats_node = "statistics" +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_subscription/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,85 @@ +# Introduction + +This module lets you programmatically subscribe to updates from a +[pubsub][xep0060] node, even if the pubsub service is remote. + +## Example + +``` {.lua} +module:depends("pubsub_subscription"); +module:add_item("pubsub-subscription", { + service = "pubsub.example.com"; + node = "otter_facts"; + + -- Callbacks: + on_subscribed = function() + module:log("info", "Otter facts incoming!"); + end; + + on_item = function(event) + module:log("info", "Random Otter Fact: %s", event.payload:get_text()); + end; +}); +``` + +## Usage + +Ensure the module is loaded and add your subscription via the +`:add_item` API. The item table MUST have `service` and `node` fields +and SHOULD have one or more `on_<event>` callbacks. + +The JID of the pubsub service is given in `service` (could also be the +JID of an user for advanced PEP usage) and the node is given in, +unsurprisingly, the `node` field. + +The various `on_event` callback functions, if present, gets called when +new events are received. The most interesting would be `on_item`, which +receives incoming items. Available events are: + +`on_subscribed` +: The subscription was successful, events may follow. + +`on_unsubscribed` +: Subscription was removed successfully, this happens if the + subscription is removed, which you would normally never do. + +`on_error` +: If there was an error subscribing to the pubsub service. Receives a + table with `type`, `condition`, `text`, and `extra` fields as + argument. + +`on_item` +: An item publication, the payload itself available in the `payload` + field in the table provided as argument. The ID of the item can be + found in `item.attr.id`. + +`on_retract` +: When an item gets retracted (removed by the publisher). The ID of + the item can be found in `item.attr.id` of the table argument.. + +`on_purge` +: All the items were removed by the publisher. + +`on_delete` +: The entire pubsub node was removed from the pubsub service. No + subscription exists after this. + +``` {.lua} +event_payload = { + -- Common prosody event entries: + stanza = util.stanza; + origin = util.session; + + -- PubSub service details + service = "pubsub.example.com"; + node = "otter_facts"; + + -- The pubsub event itself + item = util.stanza; -- <item/> + payload = util.stanza; -- actual payload, child of <item/> +} +``` + +# Compatibility + +Should work with Prosody \>= 0.11.x
--- a/mod_pubsub_subscription/mod_pubsub_subscription.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_pubsub_subscription/mod_pubsub_subscription.lua Tue Mar 18 00:31:36 2025 +0700 @@ -1,3 +1,4 @@ +local id = require "util.id"; local st = require "util.stanza"; local uuid = require "util.uuid"; local mt = require "util.multitable"; @@ -37,7 +38,7 @@ end item._id = uuid.generate(); - local iq_id = uuid.generate(); + local iq_id = "pubsub-sub-"..id.short(); pending_subscription:set(iq_id, item._id); active_subscriptions:set(item.service, item.node, item.from, item._id, item); @@ -51,12 +52,14 @@ for _, event_name in ipairs(valid_events) do module:hook("pubsub-event/host/"..event_name, function (event) for _, _, _, _, _, cb in active_subscriptions:iter(event.service, event.node, event.stanza.attr.to, nil, "on_"..event_name) do + event.handled = true; pcall(cb, event); end end); module:hook("pubsub-event/bare/"..event_name, function (event) for _, _, _, _, _, cb in active_subscriptions:iter(event.service, event.node, event.stanza.attr.to, nil, "on_"..event_name) do + event.handled = true; pcall(cb, event); end end); @@ -67,6 +70,7 @@ local service = stanza.attr.from; if not stanza.attr.id then return end -- shouldn't be possible + if not stanza.attr.id:match("^pubsub%-sub%-") then return end local subscribed_node = pending_subscription:get(stanza.attr.id); pending_subscription:set(stanza.attr.id, nil); @@ -118,7 +122,7 @@ local node_subs = active_subscriptions:get(item.service, item.node, item.from); if node_subs and next(node_subs) then return end - local iq_id = uuid.generate(); + local iq_id = "pubsub-sub-"..id.short(); pending_unsubscription:set(iq_id, item._id); module:send(st.iq({ type = "set", id = iq_id, from = item.from, to = item.service }) @@ -130,24 +134,33 @@ function handle_message(context, event) local origin, stanza = event.origin, event.stanza; - local ret = nil; + local handled = nil; local service = stanza.attr.from; module:log("debug", "Got message/%s: %s", context, stanza:top_tag()); for event_container in stanza:childtags("event", xmlns_pubsub_event) do for pubsub_event in event_container:childtags() do module:log("debug", "Got pubsub event %s", pubsub_event:top_tag()); local node = pubsub_event.attr.node; - module:fire_event("pubsub-event/" .. context .. "/"..pubsub_event.name, { - stanza = stanza; - origin = origin; - event = pubsub_event; - service = service; - node = node; - }); - ret = true; + local event_data = { + stanza = stanza; + origin = origin; + event = pubsub_event; + service = service; + node = node; + handled = false; + }; + module:fire_event("pubsub-event/" .. context .. "/"..pubsub_event.name, event_data); + if not handled and event_data.handled then + handled = true; + end end end - return ret; + -- If not addressed to the host, let it fall through to normal handling + -- (it may be on its way to a local client), otherwise, we'll mark the + -- event as handled to suppress an error response if we handled it. + if context == "host" and handled then + return true; + end end module:hook("message/host", function(event)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_text_interface/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,35 @@ +# Introduction + +This module lets you manage subscriptions to pubsub nodes via simple +chat messages. Subscriptions are always added based on bare JID. The +`include_body` flag is enabled so that a plain text body version of +events can be included, where supported. + +# Configuring + +``` {.lua} +Component "pubsub.example.com" "pubsub" +modules_enabled = { + "pubsub_text_interface", +} +``` + +# Commands + +The following commands are supported. Simply send a normal chat message +to the PubSub component where this module is enabled. When subscribing +or unsubscribing, be sure to replace `node` with the node you want to +subscribe to or unsubscribe from. + +- `help` - a help message, listing these commands +- `list` - list available nodes +- `subscribe node` - subscribe to a node +- `unsubscribe node` - unsubscribe from a node +- `last node` - sends the last published item from the node to you + +# Compatibility + +Should work with Prosody since 0.9, when +[mod\_pubsub][doc:modules:mod_pubsub] was introduced. + +The `last` command is available from Prosody 0.11.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pubsub_twitter/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,44 @@ +--- +labels: +- 'Stage-Alpha' +summary: Subscribe to Twitter search queries over pubsub +... + +Introduction +------------ + +Twitter has an open 'realtime' search API, but it requires polling +(within their rate limits). This module allows Prosody to poll for you, +and push new results to subscribers over XMPP. + +Configuration +------------- + +This module must be loaded on a Prosody pubsub component. Add it to +`modules_enabled` and configure like so: + + Component "pubsub.example.com" "pubsub" + modules_enabled = { "pubsub_twitter" } + + twitter_searches = { + realtime = "xmpp OR realtime"; + prosody = "prosody xmpp"; + } + +This example creates two nodes, 'realtime' and 'prosody' that clients +can subscribe to using +[XEP-0060](http://xmpp.org/extensions/xep-0060.html). Results are in +[ATOM 1.0 format](http://atomenabled.org/) for easy consumption. + + Option Description + ------------------------- -------------------------------------------------------------------------------- + twitter\_searches A list of virtual nodes to create and their associated Twitter search queries. + twitter\_pull\_interval Number of minutes between polling for new results (default 20) + twitter\_search\_url URL of the JSON search API, default: "http://search.twitter.com/search.json" + +Compatibility +------------- + + ----- ------- + 0.9 Works + ----- -------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_push2/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,47 @@ +--- +labels: +- Stage-Alpha +summary: 'Push 2.0 - New Cloud-Notify' +--- + +The way forward for push notifications? You are probably looking for +`mod_cloud_notify` for now though + +See also [push2.md](https://hg.prosody.im/prosody-modules/file/tip/mod_push2/push2.md) + +Configuration +============= + + Option Default Description + ------------------------------------ ----------------- ------------------------------------------------------------------------------------------------------------------- + `contact_uri` xmpp:server.tld Contact information for the server operator (usually as a `mailto:` URI is preferred) + `push_max_hibernation_timeout` `259200` (72h) Number of seconds to extend the smacks timeout if no push was triggered yet (default: 72 hours) + +Internal design notes +===================== + +App servers are notified about offline messages, messages stored by [mod_mam] +or messages waiting in the smacks queue. + +To cooperate with [mod_smacks] this module consumes some events: +`smacks-ack-delayed`, `smacks-hibernation-start` and `smacks-hibernation-end`. +These events allow this module to send out notifications for messages received +while the session is hibernated by [mod_smacks] or even when smacks +acknowledgements for messages are delayed by a certain amount of seconds +configurable with the [mod_smacks] setting `smacks_max_ack_delay`. + +The `smacks_max_ack_delay` setting allows to send out notifications to clients +which aren't already in smacks hibernation state (because the read timeout or +connection close didn't already happen) but also aren't responding to acknowledgement +request in a timely manner. This setting thus allows conversations to be smoother +under such circumstances. + +Compatibility +============= + +**Note:** This module should be used with Lua 5.3 and higher. + + ----- ---------------------- + trunk Works + 0.12 Does probably not work + ----- ----------------------
--- a/mod_push2/mod_push2.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_push2/mod_push2.lua Tue Mar 18 00:31:36 2025 +0700 @@ -6,9 +6,7 @@ local watchdog = require "util.watchdog"; local uuid = require "util.uuid"; local base64 = require "util.encodings".base64; -local ciphers = require "openssl.cipher"; -local pkey = require "openssl.pkey"; -local kdf = require "openssl.kdf"; +local crypto = require "util.crypto"; local jwt = require "util.jwt"; local xmlns_push = "urn:xmpp:push2:0"; @@ -108,6 +106,28 @@ end module:hook("iq-set/self/"..xmlns_push..":enable", push_enable) +local function push_disable(event) + local origin, stanza = event.origin, event.stanza; + local enable = stanza.tags[1]; + origin.log("debug", "Attempting to disable push notifications") + -- Tie registration to client, via client_id with sasl2 or else fallback to resource + local registration_id = origin.client_id or origin.resource + -- TODO: can we move to keyval+ on trunk? + local registrations = push2_registrations:get(origin.username) or {} + registrations[registration_id] = nil + if not push2_registrations:set(origin.username, registrations) then + origin.send(st.error_reply(stanza, "wait", "internal-server-error")); + else + origin.push_registration_id = nil + origin.push_registration = nil + origin.first_hibernated_push = nil + origin.log("info", "Push notifications disabled for %s (%s)", tostring(stanza.attr.from), registration_id) + origin.send(st.reply(stanza)) + end + return true +end +module:hook("iq-set/self/"..xmlns_push..":disable", push_disable) + -- urgent stanzas should be delivered without delay local function is_voip(stanza) if stanza.name == "message" then @@ -237,43 +257,22 @@ end local p256dh_raw = base64.decode(match.ua_public .. "==") - local p256dh = pkey.new(p256dh_raw, "*", "public", "prime256v1") - local one_time_key = pkey.new({ type = "EC", curve = "prime256v1" }) - local one_time_key_public = one_time_key:getParameters().pub_key:toBinary() + local p256dh = crypto.import_public_ec_raw(p256dh_raw, "prime256v1") + local one_time_key = crypto.generate_p256_keypair() + local one_time_key_public = one_time_key:public_raw() local info = "WebPush: info\0" .. p256dh_raw .. one_time_key_public local auth_secret = base64.decode(match.auth_secret .. "==") local salt = random.bytes(16) local shared_secret = one_time_key:derive(p256dh) - local ikm = kdf.derive({ - type = "HKDF", - outlen = 32, - salt = auth_secret, - key = shared_secret, - info = info, - md = "sha256" - }) - local key = kdf.derive({ - type = "HKDF", - outlen = 16, - salt = salt, - key = ikm, - info = "Content-Encoding: aes128gcm\0", - md = "sha256" - }) - local nonce = kdf.derive({ - type = "HKDF", - outlen = 12, - salt = salt, - key = ikm, - info = "Content-Encoding: nonce\0", - md = "sha256" - }) + local ikm = hashes.hkdf_hmac_sha256(32, shared_secret, auth_secret, info) + local key = hashes.hkdf_hmac_sha256(16, ikm, salt, "Content-Encoding: aes128gcm\0") + local nonce = hashes.hkdf_hmac_sha256(12, ikm, salt, "Content-Encoding: nonce\0") local header = salt .. "\0\0\16\0" .. string.char(string.len(one_time_key_public)) .. one_time_key_public - local encryptor = ciphers.new("AES-128-GCM"):encrypt(key, nonce) + local encrypted = crypto.aes_128_gcm_encrypt(key, nonce, envelope_bytes .. "\2") push_notification_payload :tag("encrypted", { xmlns = "urn:xmpp:sce:rfc8291:0" }) - :text_tag("payload", base64.encode(header .. encryptor:final(envelope_bytes .. "\2") .. encryptor:getTag(16))) + :text_tag("payload", base64.encode(header .. encrypted)) :up() end @@ -285,7 +284,7 @@ key = "-----BEGIN PRIVATE KEY-----\n"..key.."\n-----END PRIVATE KEY-----" end - local public_key = pkey.new(key):getParameters().pub_key:toBinary() + local public_key = crypto.import_private_pem(key):public_raw() local signer = jwt.new_signer(match.jwt_alg, key) local payload = {} for k, v in pairs(match.jwt_claims or {}) do @@ -306,7 +305,7 @@ for identifier, push_info in pairs(user_push_services) do for _, match in ipairs(push_info.matches) do if match.match == "urn:xmpp:push2:match:important" then - identifier_found.log("debug", "Not pushing because not important") + module:log("debug", "Not pushing because not important") else notify_push_services[identifier] = push_info; end @@ -396,7 +395,7 @@ for identifier, push_info in pairs(user_push_services) do for _, match in ipairs(push_info.matches) do if match.match == "urn:xmpp:push2:match:archived-with-body" or match.match == "urn:xmpp:push2:match:archived" then - identifier_found.log("debug", "Not pushing because we are not archiving this stanza") + module:log("debug", "Not pushing because we are not archiving this stanza") else notify_push_services[identifier] = push_info; end @@ -408,13 +407,13 @@ local function process_stanza_queue(queue, session, queue_type) if not session.push_registration_id then return; end - local user_push_services = {[session.push_registration_id] = session.push_settings}; local notified = { unimportant = false; important = false } for i=1, #queue do local stanza = queue[i]; -- fast ignore of already pushed stanzas if stanza and not (stanza._push_notify2 and stanza._push_notify2[session.push_registration_id]) then - local node = get_push_settings(stanza, session); + local node, all_push_services = get_push_settings(stanza, session) + local user_push_services = {[session.push_registration_id] = all_push_services[session.push_registration_id]} local stanza_type = "unimportant"; if is_important(stanza) then stanza_type = "important"; end if not notified[stanza_type] then -- only notify if we didn't try to push for this stanza type already @@ -532,11 +531,13 @@ stanza:tag("stanza-id", { xmlns = "urn:xmpp:sid:0", by = event.for_user.."@"..module.host, id = event.id }):up() local user_session = host_sessions[event.for_user] and host_sessions[event.for_user].sessions or {} local to = stanza.attr.to - to = to and jid.split(to) or event.origin.username + local to_user, to_host = jid.split(to) + to_user = to_user or event.origin.username + to_host = to_host or module.host -- only notify if the stanza destination is the mam user we store it for - if event.for_user == to then - local user_push_services = push2_registrations:get(to) or {} + if event.for_user == to_user and to_host == module.host then + local user_push_services = push2_registrations:get(to_user) or {} -- Urgent stanzas are time-sensitive (e.g. calls) and should -- be pushed immediately to avoid getting stuck in the smacks @@ -545,7 +546,7 @@ local notify_push_services; if is_voip_stanza then - module:log("debug", "Urgent push for %s (%s)", to, urgent_reason); + module:log("debug", "Urgent push for %s@%s (%s)", to_user, to_host, urgent_reason); notify_push_services = user_push_services; else -- only notify nodes with no active sessions (smacks is counted as active and handled separate) @@ -559,14 +560,14 @@ end end if identifier_found then - identifier_found.log("debug", "Not pushing '%s' of new MAM stanza (session still alive)", identifier) + module:log("debug", "Not pushing '%s' of new MAM stanza (session still alive)", identifier) else notify_push_services[identifier] = push_info end end end - handle_notify_request(stanza, to, notify_push_services, true); + handle_notify_request(stanza, to_user, notify_push_services, true); end end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_push2/push2.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,146 @@ +# Push 2.0 + +Adapted from notes made at [XMPP Summit 25](https://pad.nixnet.services/oy6MKVbESSycLeMJIOh6zw). + +Requirements: + +- Support for SASL2 inlining +- Extensible stanza matching rules and notification payload rules +- Simpler syntax and concept model than original specification + +## Client registers to receive push notifications + +```xml +<enable xmlns='urn:xmpp:push2:0'> + <service>pusher@push.example.com</service> + <client>https://push.example.com/adlfkjadafdasf</client> + <match profile="urn:xmpp:push2:match:archived-with-body"> + <send xmlns="urn:xmpp:push2:send:notify-only:0"/> + </match> +</enable> +``` + +The `<service/>` element contains a JID which push notifications for this client will be sent to. It may be a host, bare or full JID. + +The `<client/>` element contains an opaque string that will be included in all communication with the push service. It may be used to convey client identifiers used by the push notification service to route notifications. + +The `<match/>` and `<send/>` elements define what profiles to use for matching stanzas and sending notifications. These are described later in this document. + +## Match and send profiles + +Different clients and push services have different requirements for push notifications, often due to the differing capabilities of target platforms. + +A "profile" in the context of this specification is a set of rules for matching the kinds of stanzas that should be pushed, and how to transform them before sending the notification to the push service. + +### Match profiles + +Match profiles define which incoming stanzas will trigger a push notification. More than one match may be specified. + +Some match profiles are defined in this XEP. Other XEPs may define additional profiles with the reserved `urn:xmpp:push2:match:` prefix, following the registrar considerations explained later in this document. Custom profiles not defined in a XEP should use their own appropriate URI. + +#### `urn:xmpp:push2:match:all` + +Using this profile, all stanzas will trigger a push notification to be sent to the push service when the client is unavailable. + +#### `urn:xmpp:push2:match:important` + +Stanzas that are considered to be "important" are pushed. At the time of writing, there is no standard definition of "important", however most servers already contain such logic for traffic optimization when combined with [XEP-0352: Client State Indication](https://xmpp.org/extensions/xep-0352.html). + +#### `urn:xmpp:push2:match:archived` + +Push notifications will be sent for any stanza that is stored in the user's archive. This is a good indication that the stanza is important, and is desired on all of a user's devices. + +#### `urn:xmpp:push2:match:archived-with-body` + +Matches only archived messages that contain a body. This can be used to exclude certain message types, such as typing notifications, receipts and error replies. + +### Send profiles + +When a server has determined that a stanza should trigger a push notification (according to the client's selected 'match' profile), it proceeds to create a notification stanza following the send profiles specified in the match profiles which match this stanza. + +After constructing the notification stanza, it will then be sent to the push service JID selected by the client. + +Some send profiles are defined in this XEP. Other XEPs may define additional profiles with the `urn:xmpp:push2:send:` prefix, following the registrar considerations explained later in this document. Custom profiles not defined in a XEP should use their own appropriate URI. + +#### `urn:xmpp:push2:send:notify-only:0` + +Send an empty notification to the push service. Such notifications are useful if a push notification can trigger the client to "wake up" and connect to the server to receive the message over XMPP. + +Example: + +```xml +<message to="pusher@push.example.net"> + <notification xmlns="urn:xmpp:push2:0"> + <client>https://push.example.com/adlfkjadafdasf</client> + <priority>normal</priority> + </notification> +</message> +``` + +#### `urn:xmpp:push2:send:sce+rfc8291+rfc8292:0` + +Delivers content encrypted according to RFC8291 and with a JWT auth following RFC8292 + +```xml +<send xmlns="urn:xmpp:push2:send:sce+rfc8291+rfc8292:0"> + <ua-public>Base64 encoded P-256 ECDH public key (raw, uncompressed)</ua-public> + <auth-secret>Base64 encoded randomly generated 16 octets</auth-secret> + <jwt-alg>ES256</jwt-alg> + <jwt-key>PKCS#8 PEM encoded ECDSA keypair, without the header or footer lines</jwt-key> + <jwt-claim name="aud">https://push.example.com</jwt-claim> +</send> +``` + +The full stanza is wrapped in XEP-0297 forwarded and then that is wrapped in XEP-0420 envelope/content with optional rpad. The raw bytes of the resulting XML are encrypted according to RFC8291 using the provided `ua-public` and `auth-secret`. + +If `jwt-alg` is specified, then a JWT is computed over any provided claims plus a suitable `exp` and `sub` claim and signed using the provided key. + +Then a notification is sent: + +```xml +<message to="pusher@push.example.net"> + <notification xmlns="urn:xmpp:push2:0"> + <client>https://push.example.com/adlfkjadafdasf</client> + <priority>normal</priority> + <encrypted xmlns="urn:xmpp:sce:rfc8291:0"> + <payload>Base64 encoded ciphertext</payload> + </encrypted> + <jwt key="base64 encoded raw public key">the signed JWT, if present</jwt> + </notification> +</message> +``` + +NOTE: if the stanza exceeds the maximum size of 4096 bytes (and some implementations may wish to restrict this even more) the stanza may have some elements removed, body truncated, etc before it is delivered. Servers SHOULD ensure that at least the MAM id (if there is one) is still present after any minimization. + +## Discovering support + +A server that supports this protocol MUST advertise the `urn:xmpp:push2:0` feature in an account's service discovery information, along with the supported match and send profiles. + +```xml +<iq from='juliet@capulet.lit' + to='juliet@capulet.lit/balcony' + id='disco1' + type='result'> + <query xmlns='http://jabber.org/protocol/disco#info'> + <identity category='account' type='registered'/> + <feature var='urn:xmpp:push2:0'/> + <feature var='urn:xmpp:push2:send:'/> + </query> +</iq> +``` + +## Client disables future pushes + +```xml +<disable xmlns='urn:xmpp:push2:0' /> +``` + +## Push service interactions + +### Transient delivery errors + +The user's server might receive delivery errors while sending notifications to the user's push service. The error 'type' attribute SHOULD be honoured - errors of type 'wait' SHOULD be retried in an appropriate manner (e.g. using exponential back-off algorithm, up to a limit), discarding the notification after an appropriate length of time or number of attempts. + +Other error types MUST NOT be automatically retried. + +A user's server MAY automatically disable a push configuration for a service that has consistently failed to relay notifications for an extended period of time. This period is a matter of deployment configuration, but a default no less than 72 hours is recommended.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_query_client_ver/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,12 @@ +# Introduction + +This module sends a [version query][xep0092] to clients when they +connect, and logs the response, allowing statistics of client usage to +be recorded. + +Note that since this is per connection, there will be a bias towards +mobile clients on bad connections. + +## Example + + info Running Running Swift version 4.0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_rawdebug/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,12 @@ +--- +summary: Extra verbose logging of sent and received +--- + +Summary +======= + +Sometimes it is useful to get the raw XML logs from clients for +debugging purposes, but some clients don't expose this. + +This module logs dumps everything sent and received into debug logs, for +debugging purposes.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_readonly/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,9 @@ +This module blocks configured queries that change server state. + +E.g. to make vCard storage read-only: + +``` lua +readonly_stores = { + vcard = { "vcard-temp", "vCard" }; +} +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_register_apps/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,118 @@ +--- +labels: +- 'Stage-Beta' +summary: 'Manage list of compatible client apps' +rockspec: + build: + copy_directories: + - assets +... + +Introduction +============ + +This module provides a way to configure a list of XMPP client apps recommended +by the current server. This list is used by other modules such as mod_invites_page +and mod_invites_register_web. + +It also contains the logos of a number of popular XMPP clients, and serves +them over HTTP for other modules to reference when serving web pages. + +# Configuration + +| Field | Description | +|----------------------|--------------------------------------------------------------------------| +| site_apps | A list of apps and their metadata | +| site_apps_show | A list of app ids to only show | +| site_apps_hide | A list of app ids to never show | + +An "app id" is the lower case app name, with any spaces replaced by `-`. E.g. "My Chat" would be `"my-chat"`. + +The module comes with a preconfigured `site_apps` containing popular clients. Patches are welcome to +add/update this list as needed! + +If you want to limit to just displaying a subset of the apps on your server, use the `site_apps_show` +option, e.g. `site_apps_show = { "conversations", "siskin-im" }`. To never show specific apps, you +can use `site_apps_hide`, e.g. `site_apps_hide = { "pidgin" }`. + +# App metadata format + +The configuration option `site_apps` contains the list +of apps and their metadata. + +``` {.lua} +-- Example site_apps config with two clients +site_apps = { + { + name = "Conversations"; + text = [[Conversations is a Jabber/XMPP client for Android 4.0+ smartphones that has been optimized to provide a unique mobile experience.]]; + image = "assets/logos/conversations.svg"; + link = "https://play.google.com/store/apps/details?id=eu.siacs.conversations"; + platforms = { "Android" }; + supports_preauth_uri = true; + magic_link_format = "{app.link!}&referrer={invite.uri}"; + download = { + buttons = { + { + image = "https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png"; + url = "https://play.google.com/store/apps/details?id=eu.siacs.conversations"; + }; + }; + }; + }; + { + name = "Gajim"; + text = [[A fully-featured desktop chat client for Windows and Linux.]]; + image = "assets/logos/gajim.svg"; + link = "https://gajim.org/"; + platforms = { "Windows", "Linux" }; + download = { + buttons = { + { + text = "Download Gajim"; + url = "https://gajim.org/download/"; + target = "_blank"; + }; + }; + }; + }; +} +``` +The fields of each client entry are as follows: + +| Field | Description | +|----------------------|--------------------------------------------------------------------------| +| name | The name of the client | +| text | Description of the client | +| image | URL to a logo for the client, may also be a path in the assets/ directory| +| link | URL to the app | +| platforms | A list of platforms the app can be installed on | +| supports_preauth_uri | `true` if the client supports XEP-0401 preauth URIs | +| magic_link_format | A template to generate a magic installation link from an invite | +| download | Download instructions and buttons, described below | + +## Download metadata + +The `download` field supports an optional text prompt and one or more buttons. +Each button must contain either a `text` or `image` field and must contain +a `url` field. It is recommended to set `target = "_blank"` if the link +opens a new page, so that the user doesn't lose the invite page. + +Example download field with instructions and two buttons: + +``` {.lua} +download = { + text = "Some optional instructions about downloading the client..."; + buttons = { + { + text = "Button 1: some text"; + url = "https://example.com/"; + }; + { + image = "https://example.com/button2.png"; + url = "https://example.com/download/"; + }; + }; +} + +```
--- a/mod_register_apps/mod_register_apps.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_register_apps/mod_register_apps.lua Tue Mar 18 00:31:36 2025 +0700 @@ -12,7 +12,7 @@ local app_config = module:get_option("site_apps", { { name = "Conversations"; - text = [[Conversations is a Jabber/XMPP client for Android 4.0+ smartphones that has been optimized to provide a unique mobile experience.]]; + text = [[Conversations is a Jabber/XMPP client for Android 6.0+ smartphones that has been optimized to provide a unique mobile experience.]]; image = "assets/logos/conversations.svg"; link = "https://play.google.com/store/apps/details?id=eu.siacs.conversations"; platforms = { "Android" }; @@ -54,7 +54,7 @@ download = { buttons = { { - image = "https://linkmaker.itunes.apple.com/en-us/badge-lrg.svg?releaseDate=2017-05-31&kind=iossoftware&bubble=ios_apps"; + image = "https://toolbox.marketingtools.apple.com/api/v2/badges/download-on-the-app-store/black/en-us?releaseDate=1245024000"; url = "https://apps.apple.com/us/app/siskin-im/id1153516838"; target = "_blank"; }; @@ -120,7 +120,7 @@ download = { buttons = { { - image = "https://linkmaker.itunes.apple.com/en-us/badge-lrg.svg?releaseDate=2017-05-31&kind=iossoftware&bubble=ios_apps"; + image = "https://toolbox.marketingtools.apple.com/api/v2/badges/download-on-the-app-store/black/en-us?releaseDate=1245024000"; url = "https://apps.apple.com/app/id317711500"; target = "_blank"; }; @@ -137,7 +137,7 @@ download = { buttons = { { - image = "https://linkmaker.itunes.apple.com/en-us/badge-lrg.svg?releaseDate=2017-05-31&kind=macossoftware&bubble=macos_apps"; + image = "https://toolbox.marketingtools.apple.com/api/v2/badges/download-on-the-app-store/black/en-us?releaseDate=1245024000"; url = "https://apps.apple.com/app/id1637078500"; target = "_blank"; };
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_register_dnsbl/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,18 @@ +Introduction +============ + +This module checks the IP addresses attempting to register an account +against a DNSBL, blocking the attempt if there is a hit. + +Configuration +============= + + Option Type Default + ------------------- -------- ------------ + registration\_rbl string *Required* + +Compatibility +============= + +Prosody Trunk +[1a0b76b07b7a](https://hg.prosody.im/trunk/rev/1a0b76b07b7a) or later.
--- a/mod_register_dnsbl_firewall_mark/mod_register_dnsbl_firewall_mark.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_register_dnsbl_firewall_mark/mod_register_dnsbl_firewall_mark.lua Tue Mar 18 00:31:36 2025 +0700 @@ -17,6 +17,8 @@ return ("%d.%d.%d.%d.%s"):format(d,c,b,a, suffix); end +local store = module:open_store("firewall_marks", "map"); + module:hook("user-registered", function (event) local session = event.session; local ip = session and session.ip and cleanup_ip(session.ip); @@ -31,7 +33,7 @@ if user and user.firewall_marks then user.firewall_marks.dnsbl_hit = registration_time; else - module:open_store("firewall_marks", "map"):set(event.username, "dnsbl_hit", registration_time); + store:set(event.username, "dnsbl_hit", registration_time); end if rbl_message then module:log("debug", "Warning RBL registered user %s@%s", event.username, event.host); @@ -45,3 +47,13 @@ end, rbl_ip); end end); + +module:add_item("account-trait", { + name = "register-dnsbl-hit"; + prob_bad_true = 0.6; + prob_bad_false = 0.4; +}); + +module:hook("get-account-traits", function (event) + event.traits["register-dnsbl-hit"] = not not store:get(event.username, "dnsbl_hit"); +end);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_register_dnsbl_warn/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,14 @@ +Introduction +============ + +This module checks the IP address of newly registered users against a +DNS block list. If a positive match is found, it gets logged. + +Configuration +============= + + Option Type Default + ------------------- -------- ------------ + registration\_rbl string *Required* + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_register_json/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,95 @@ +--- +labels: +- 'Stage-Stable' +summary: 'Token based JSON registration & verification servlet.' +... + +Introduction +------------ + +This module let's you activate a httpserver interface to handle data +from webforms with POST and Base64 encoded JSON. + +Implementation Details +---------------------- + +Example Request format: + + POST /your_register_base_url HTTP/1.1 + Host: yourserveraddress.com:yourchoosenport + Content-Type: application/encoded + Content-Transfer-Encoding: base64 + + eyJ1c2VybmFtZSI6InVzZXJuYW1lb2ZjaG9pY2UiLCJwYXNzd29yZCI6InRoZXVzZXJwYXNzd29yZCIsImlwIjoidGhlcmVtb3RlYWRkcm9mdGhldXNlciIsIm1haWwiOiJ1c2VybWFpbEB1c2VybWFpbGRvbWFpbi50bGQiLCJhdXRoX3Rva2VuIjoieW91cmF1dGh0b2tlbm9mY2hvaWNlIn0= + +Where the encoded content is this (example) JSON Array: + +``` {.json} + { + "username":"john.smith", + "password":"secret-password", + "ip":"192.168.0.0", + "mail":"john.smith@mail.example.net", + "auth_token":"yourauthtokenofchoice" + } +``` + +Your form implementation needs to pass **all** parameters, the +auth\_token is needed to prevent misuses, if the request is successful +the server will answer with status code 200 and with the body of the +response containing the token which your web app can send via e-mail to +the user to complete the registration. + +Else, it will reply with the following http error codes: + +- 400 - if there's an error syntax; +- 401 - whenever an username is already pending registration or the + auth token supplied is invalid; +- 403 - whenever registration is forbidden (blacklist, filtered mail + etc.); +- 406 - if the username supplied fails nodeprepping; +- 409 - if the user already exists, or an user is associated already + with the supplied e-mail; +- 503 - whenever a request is throttled. + +The verification URL path to direct the users to will be: +**/your-base-path-of-choice/verify/** - on your Prosody's http server. + +The module for now stores a hash of the user's mail address to help slow +down duplicated registrations. + +It's strongly encouraged to have the web server communicate with the +servlet via https. + +Usage +----- + +Copy the module folder and all its contents (register\_json) into your +prosody modules' directory.Add the module your vhost of choice +modules\_enabled. + +Hint: pairing with mod\_register\_redirect is helpful, to allow server +registrations only via your webform. + + +Required configuration: + +``` + reg_servlet_auth_token = "your-secret-token" +``` + +Optional configuration directives: + +``` + reg_servlet_base = "/base-path/" -- Base path of the plugin (default is register_account) + reg_servlet_secure = true -- Have the plugin only process requests on https (default is true) + reg_servlet_ttime = seconds -- Specifies the time (in seconds) between each request coming from the same remote address. + reg_servlet_bl = { "1.2.3.4", "4.3.2.1" } -- The ip addresses in this list will be blacklisted and will not be able to submit registrations. + reg_servlet_wl = { "1.2.3.4", "4.3.2.1" } -- The ip addresses in this list will be ignored by the throttling. + reg_servlet_filtered_mails = { ".*banneddomain.tld", ".*deamailprovider.tld" } -- allows filtering of mail addresses via Lua patterns. +``` + +Compatibility +------------- + +0.9
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_register_oob_url/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,29 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'XEP-077 IBR registration URL redirect' +--- + +Introduction +============ + +Registration redirect to out of band URL as described in [XEP-0077: In-Band Registration](http://xmpp.org/extensions/xep-0077.html#redirect). + +Details +======= + +The already existing module `mod_register_redirect` doesn’t add a stream feature advertising its capabilities and thus doesn’t work with clients like Conversations. + +This module tries to take a simpler and more straight forward approach for admins who just want to redirect to an URL and do not need the features provided by `mod_register_redirect`. + +Usage +===== + +Set `allow_registration` to `false` and point `register_oob_url` to the URL that handles your registration. + +Compatibility +============= + + ----- ----------------------------------------------------------------------------- + 0.10 Works + ----- -----------------------------------------------------------------------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_register_redirect/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,52 @@ +--- +labels: +- 'Stage-Stable' +summary: 'XEP-077 IBR Registration Redirect.' +... + +Introduction +------------ + +Registration Redirect as explained in the [IBR +XEP](http://xmpp.org/extensions/xep-0077.html#redirect). + +Details +------- + +This module shows instructions on how to register to the server, should +it be necessary to perform it through other means Out-Of-Band or not, +and also let's registrations origining from ip addresses in the +whitelist to go through normally. + +Usage +----- + +Copy the module file into your Prosody modules directory. + +The module will work "out of the box" as long as at least an admin entry +is specified (see admins = {} option into prosody's documentation).These +are the optional parameters you can specify into your global +server/hostname configuration: + + registration_whitelist = { "*your whitelisted web server ip address*" } + registration_url = "*your web registration page url*" + registration_text = "Your custom instructions banner here" + registration_oob = true (default) or false, in the case there's no applicable OOB method (e.g. the server admins needs to be contacted by phone) + +To not employ any whitelisting (i.e. registration is handled +externally). + + no_registration_whitelist = true + +Compatibility +------------- + + ------ -------------- + 0.10 Works + 0.9 Works + 0.8 Works + 0.7 Might not work + 0.6 Doesn't work + 0.5 Doesn't work + ------ -------------- +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_register_web/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,67 @@ +--- +labels: +- 'Stage-Alpha' +summary: A web interface to register user accounts +rockspec: + build: + copy_directories: + - templates +... + +Introduction +------------ + +There are various reasons to prefer web registration instead of +"in-band" account registration over XMPP. For example the lack of +CAPTCHA support in clients and servers. + +Details +------- + +mod\_register\_web has Prosody serve a web page where users can sign up +for an account. It implements reCAPTCHA to prevent automated sign-ups +(from bots, etc.). + +Configuration +------------- + +The module is served on Prosody's default HTTP ports at the path +`/register_web`. More details on configuring HTTP modules in Prosody can +be found in our [HTTP documentation](http://prosody.im/doc/http). + +To configure the CAPTCHA you need to supply a 'captcha\_options' option: + + captcha_options = { + recaptcha_private_key = "12345"; + recaptcha_public_key = "78901"; + } + +The keys for reCAPTCHA are available in your reCAPTCHA account, visit +[reCAPTCHA](https://developers.google.com/recaptcha/) for more info. + +If no reCaptcha options are set, a simple built in captcha is used. + +Customization +------------- + +Copy the files in mod_register_web/templates/ to a new directory. Edit them, +and set `register_web_template = "/path/to/your/custom-templates"` in your +config file. + +Compatibility +------------- + + ----- -------------- + 0.10 Works + 0.9 Works + 0.8 Doesn't work + ----- -------------- + +Todo +---- + +Different CAPTCHA implementation support + +Collection of additional data, such as email address + +The module kept simple!
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_reload_components/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,91 @@ +Introduction +============ + +This module allows to load/unload external components after they have +been added/removed to a configuration file. It is necessary to explicitly +initiate a reload on Prosody either via prosodyctl reload or config:reload(). + +Example 1: +-------- +If Prosody has started with this configuration file: + +``` {.lua} +VirtualHost "example.com" + authentication = "internal_plain" + +Component "a.example.com" + component_secret = "a" + +Component "b.example.com" + component_secret = "b" +``` + +And the file has changed manually or dynamically to: + +``` {.lua} +VirtualHost "example.com" + authentication = "internal_plain" + +Component "a.example.com" + component_secret = "a" + +Component "c.example.com" + component_secret = "c" +``` + +Then, the following actions will occur if this module is loaded: + +1. The component c.example.com will be loaded and start bouncing for +authentication. +2. The component b.example.com will be unloaded and deactivated. The +connection with it will not be closed, but no further actions will be +executed on Prosody. + +Example 2: +-------- + +If Prosody has started with this configuration file: + +``` {.lua} +VirtualHost "example.com" + authentication = "internal_plain" + +Component "a.example.com" + component_secret = "a" +``` + +And the file has changed manually or dynamically to: + +``` {.lua} +VirtualHost "example.com" + authentication = "internal_plain" + +Component "a.example.com" + component_secret = "a" + +VirtualHost "newexample.com" + authentication = "internal_plain" + +Component "a.newexample.com" + component_secret = "a" +``` + +Then, the following actions will occur if this module is loaded: + +1. The component a.newexample.com will be loaded and start bouncing for +authentication. Note that its respective VirtualHost is not loaded. Bad +things may happen. + +Usage +===== + +Copy the module folder into your Prosody modules directory. Place the +module between your enabled modules either into the global or a vhost +section. + +No configuration directives are needed + +Info +==== + +- 0.9, works
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_reload_modules/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,34 @@ +--- +labels: +- 'Stage-Stable' +summary: Automatically reload modules with the config +... + +Introduction +------------ + +By default Prosody does not reload modules at runtime unless instructed +to via one of its admin interfaces. However sometimes you want to easily +reload a module to apply new settings when the config changes. + +mod\_reload\_modules will reload a set list of modules every time +Prosody reloads its config (e.g. on SIGHUP). + +Configuration +------------- + +Add "reload\_modules" to modules\_enabled. Then the list of modules to +reload using the 'reload\_modules' option in your config like so: + + reload_modules = { "groups", "tls" } + +This would reload mod\_groups and mod\_tls whenever the config is +reloaded. Note that on many systems this will be at least daily, due to +logrotate. + +Compatibility +------------- + + ----- ------- + 0.9 Works + ----- -------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_remote_roster/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,10 @@ +--- +summary: Remote Roster Management +... + +Introduction +============ + +This module adds support for [XEP-0321: Remote Roster Management] which +is commonly used to allow components such as transports to modify the +rosters of local users.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_report_affiliations/README.markdown Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,108 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'XEP-0489: Reporting Account Affiliations' +rockspec: + build: + modules: + mod_report_affiliations.traits: traits.lib.lua +--- + + +This module implements [XEP-0489: Reporting Account Affiliations](https://xmpp.org/extensions/xep-0489.html). +It can help with spam on the network, especially if you run a public server +that allows registration. + +## How it works + +Here is the scenario: you run a public server. Despite your best efforts, and +following the [best practices](https://prosody.im/doc/public_servers), some +spammers still occasionally manage to register on your server. Because of +this, other servers on the network start filtering messages from all accounts +on your server. + +Enabling this module will include additional information in certain kinds of +outgoing traffic, which allows other servers to judge the sending account, +rather than the whole server. + +### When is affiliation information shared? + +Affiliation is shared when a user on your server: + +- sends a message to a user that has not (yet) authorized them +- sends a subscription request to a user +- sends a "directed presence" to a remote JID (for example, when joining a + group chat). + +### What information is shared? + +The following information is included in matching traffic: + +- The affiliation of the account: + - "guest" (the account is anonymous/temporary) + - "registered" (the account was self-registered) + - "member" (the account belongs to a recognised/trusted member of the server) + - "admin" (the account belongs to a server administrator) + +For the "registered" affiliation, the following additional items are included: + +- When the account was created +- The "trust level" of the account + +### What is the trust level? + +This is a score out of 100 which indicates how trusted the account is. It is +automatically calculated, and the calculation may include various factors +provided by installed modules. At this time, in a default installation, the +reported value is always 50. + +## Configuration + +### Allowing queries + +In most cases, Prosody will automatically include the affiliation information +when necessary. However it is also possible to provide affiliation on-demand, +in response to queries. + +To avoid leaking information about the server's registered users, queries are +restricted by default. + +You can configure a list of servers from which queries are permitted, by using +the 'report_affiliations_trusted_servers' option: + +```lua +report_affiliations_trusted_servers = { "rtbl.example.net" } +``` + +In this example, permission has been granted to an RTBL service, so that it +can query the server and avoid adding legitimate users to the blocklist, even +if it receives reports about them (obviously this is just an example, RTBLs +will decide their own policies). + +### Tweaking roles + +Prosody automatically maps its standard roles to the affiliations defined by +the XEP. If your deployment uses custom roles, you can customize the mapping +by specifying the list of roles that should be mapped to a given affiliation. +This can be done using the following options: + +- report_affiliations_admin_roles +- report_affiliations_member_roles +- report_affiliations_registered_roles +- report_affiliations_anonymous_roles + +For example, to consider the 'company:staff' role as members, as well as the +built-in prosody:member role, you might set the following: + +```lua +report_affiliations_member_roles = { "prosody:member", "company:staff" } +``` + +## Compatibility + +Should work with 0.12, but has not been tested. 0.12 does not support the +"member" role, so all non-anonymous/non-admin accounts will be reported as +"registered". + +Tested with trunk (2024-11-22). +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_report_affiliations/mod_report_affiliations.lua Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,166 @@ +local dt = require "util.datetime"; +local jid = require "util.jid"; +local st = require "util.stanza"; + +local rm = require "core.rostermanager"; +local um = require "core.usermanager"; + +local traits = module:require("traits"); + +local xmlns_aff = "urn:xmpp:raa:0"; + +local is_host_anonymous = module:get_option_string("authentication") == "anonymous"; + +local trusted_servers = module:get_option_inherited_set("report_affiliations_trusted_servers", {}); + +local roles = { + -- These affiliations are defined by XEP-0489, and we map a set of Prosody roles to each one + admin = module:get_option_set("report_affiliations_admin_roles", { "prosody:admin", "prosody:operator" }); + member = module:get_option_set("report_affiliations_member_roles", { "prosody:member" }); + registered = module:get_option_set("report_affiliations_registered", { "prosody:user", "prosody:registered" }); + guest = module:get_option_set("report_affiliations_anonymous", { "prosody:guest" }); +}; + +-- Map of role to affiliation +local role_affs = { + -- [role (e.g. "prosody:guest")] = affiliation ("admin"|"member"|"registered"|"guest"); +}; + +--Build the role->affiliation map based on the config +for aff, aff_roles in pairs(roles) do + for role in aff_roles do + role_affs[role] = aff; + end +end + +local account_details_store = module:open_store("account_details"); +local lastlog2_store = module:open_store("lastlog2"); + +module:add_feature(xmlns_aff); +module:add_feature(xmlns_aff.."#embed-presence-sub"); +module:add_feature(xmlns_aff.."#embed-presence-directed"); + +local function get_registered_timestamp(username) + if um.get_account_info then + local ts = um.get_account_info(username, module.host); + if ts then return ts.created; end + end + + local account_details = account_details_store:get(username); + if account_details and account_details.registered then + return account_details.registered; + end + + local lastlog2 = lastlog2_store:get(username); + if lastlog2 and lastlog2.registered then + return lastlog2.registered.timestamp; + end + + return nil; +end + +local function get_trust_score(username) + return math.floor(100 * (1 - traits.get_probability_bad(username))); +end + + +local function get_account_type(username) + if is_host_anonymous then + return "anonymous"; + end + + if not um.get_user_role then + return "registered"; -- COMPAT w/0.12 + end + + local user_role = um.get_user_role(username, module.host); + + return role_affs[user_role] or "registered"; +end + +function get_info_element(username) + local account_type = get_account_type(username); + + local since, trust; + + if account_type == "registered" then + since = get_registered_timestamp(username); + trust = get_trust_score(username); + end + + return st.stanza("info", { + affiliation = account_type; + since = since and dt.datetime(since - (since%86400)) or nil; + trust = ("%d"):format(trust); + xmlns = xmlns_aff; + }); +end + +-- Outgoing presence + +local function embed_in_outgoing_presence(pres_type) + return function (event) + local origin, stanza = event.origin, event.stanza; + + stanza:remove_children("info", xmlns_aff); + + -- Unavailable presence is pretty harmless, and blocking it may cause + -- weird issues. + if (pres_type == "bare" and stanza.attr.type == "unavailable") + or (pres_type == "full" and stanza.attr.type ~= nil) then + return; + end + + -- Only attach info to stanzas sent to "strangers" (users that have not + -- approved us to see their presence) + if rm.is_user_subscribed(origin.username, origin.host, stanza.attr.to) then + return; + end + + local info = get_info_element(origin.username); + if not info then return; end + + stanza:add_direct_child(info); + end; +end + +module:hook("pre-presence/bare", embed_in_outgoing_presence("bare")); +module:hook("pre-presence/full", embed_in_outgoing_presence("full")); + +-- Handle direct queries + +local function should_permit_query(from_jid, to_username) --luacheck: ignore 212/to_username + local from_node, from_host = jid.split(from_jid); + if from_node then + return false; + end + + -- Who should we respond to? + -- Only respond to domains + -- Does user have a JID with this domain in directed presence? (doesn't work with bare JIDs) + -- Does this user have a JID with domain in pending subscription requests? + + if trusted_servers:contains(from_host) then + return true; + end + + return false; +end + +module:hook("iq-get/bare/urn:xmpp:raa:0:query", function (event) + local origin, stanza = event.origin, event.stanza; + local username = jid.node(stanza.attr.to); + + if not should_permit_query(stanza.attr.from, username) then + origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end + + local info = get_info_element(username); + + local reply = st.reply(stanza) + :add_child(info); + origin.send(reply); + + return true; +end);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_report_affiliations/traits.lib.lua Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,58 @@ +local known_traits = {}; + +local function trait_added(event) + local trait = event.item; + local name = trait.name; + if known_traits[name] then return; end + + known_traits[name] = trait.probabilities; +end + +local function trait_removed(event) + local trait = event.item; + known_traits[trait.name] = nil; +end + +module:handle_items("account-trait", trait_added, trait_removed); + +local function bayes_probability(prior, prob_given_true, prob_given_false) + local numerator = prob_given_true * prior; + local denominator = numerator + prob_given_false * (1 - prior); + return numerator / denominator; +end + +local function prob_is_bad(traits, prior) + prior = prior or 0.50; + + for trait, state in pairs(traits) do + local probabilities = known_traits[trait]; + if probabilities then + if state then + prior = bayes_probability( + prior, + probabilities.prob_bad_true, + probabilities.prob_bad_false + ); + else + prior = bayes_probability( + prior, + 1 - probabilities.prob_bad_true, + 1 - probabilities.prob_bad_false + ); + end + end + end + + return prior; +end + +local function get_probability_bad(username, prior) + local user_traits = {}; + module:fire_event("get-account-traits", { username = username, host = module.host, traits = user_traits }); + local result = prob_is_bad(user_traits, prior); + return result; +end + +return { + get_probability_bad = get_probability_bad; +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_report_forward/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,86 @@ +--- +labels: +- 'Stage-Beta' +summary: 'Forward spam/abuse reports to a JID' +--- + +This module forwards spam/abuse reports (e.g. those submitted by users via +XEP-0377 via mod_spam_reporting) to one or more JIDs. + +## Configuration + +Install and enable the module the same as any other: + +```lua +modules_enabled = { + --- + "report_forward"; + --- +} +``` + +There are two main options. You can set `report_forward_to` which accepts a +list of JIDs to send all reports to (default is empty): + +```lua +report_forward_to = { "admin@example.net", "antispam.example2.com" } +``` + +You can also control whether the module sends a report to the server from +which the spam/abuse originated (default is `true`): + +```lua +report_forward_to_origin = false +``` + +The module looks up an abuse report address using XEP-0157 (only XMPP +addresses are accepted). If it fails to find any suitable destination, it will +fall back to sending the report to the domain itself unless `report_forward_to_origin_fallback` +is disabled (set to `false`). If the fallback is disabled, it will log a +warning and not send the report. + + + +## Protocol + +This section is intended for developers. + +XEP-0377 assumes the report is embedded within another protocol such as +XEP-0191, and doesn't specify a format for communicating "standalone" reports. +This module transmits them inside a `<message>` stanza, and adds a `<jid/>` +element (borrowed from XEP-0268): + +```xml +<message from="prosody.example" to="destination.example"> + <report xmlns="urn:xmpp:reporting:1" reason="urn:xmpp:reporting:spam"> + <jid xmlns="urn:xmpp:jid:0">spammer@bad.example</jid> + <text> + Never came trouble to my house like this. + </text> + </report> +</message> +``` + +It may also include the reported message, if this has been indicated by the +user, wrapped in a XEP-0297 `<forwarded/>` element: + +```xml +<message from="prosody.example" to="destination.example"> + <report reason="urn:xmpp:reporting:spam" xmlns="urn:xmpp:reporting:1"> + <jid xmlns="urn:xmpp:jid:0">spammer@bad.example</jid> + <text>Never came trouble to my house like this.</text> + </report> + <forwarded xmlns="urn:xmpp:forward:0"> + <message from="spammer@bad.example" to="victim@prosody.example" type="chat" xmlns="jabber:client"> + <body>Spam, Spam, Spam, Spam, Spam, Spam, baked beans, Spam, Spam and Spam!</body> + </message> + </forwarded> +</message> +``` + +## Compability + + Prosody-Version Status + ----------------- ---------------------- + trunk Works as of 07.12.22 + 0.12 Works
--- a/mod_report_forward/mod_report_forward.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_report_forward/mod_report_forward.lua Tue Mar 18 00:31:36 2025 +0700 @@ -6,6 +6,8 @@ local new_id = require "util.id".short; local render = require"util.interpolation".new("%b{}", function (s) return s; end); +local count_report = module:metric("counter", "forwarded", "reports", "Number of spam and abuse reports forwarded to remote receivers."); + module:depends("spam_reporting"); local destinations = module:get_option_set("report_forward_to", {}); @@ -14,6 +16,7 @@ local cache_size = module:get_option_number("report_forward_contact_cache_size", 256); local report_to_origin = module:get_option_boolean("report_forward_to_origin", true); +local report_to_origin_fallback = module:get_option_boolean("report_forward_to_origin_fallback", true); local contact_lookup_timeout = module:get_option_number("report_forward_contact_lookup_timeout", 180); local body_template = module:get_option_string("report_forward_body_template", [[ @@ -30,7 +33,7 @@ -- This message contains also machine-readable payloads, including XEP-0377, in case you want to automate handling of these reports. You can receive these reports -to a different address by setting 'spam-report-addresses' in your server +to a different address by setting 'report-addresses' in your server contact info configuration. For more information, see https://xmppbl.org/reports/ ]]):gsub("^%s+", ""):gsub("(%S)\n(%S)", "%1 %2"); @@ -65,18 +68,27 @@ :next(function (result) module:log("debug", "Processing contact form..."); local response = result.stanza; - if response.attr.type ~= "result" then - module:log("warn", "Failed to query contact addresses of %s: %s", host, response); - return; + if response.attr.type == "result" then + for form in response.tags[1]:childtags("x", "jabber:x:data") do + local form_type = form:get_child_with_attr("field", nil, "var", "FORM_TYPE"); + if form_type and form_type:get_child_text("value") == "http://jabber.org/network/serverinfo" then + address = get_address(form, "report-addresses", "abuse-addresses"); + break; + end + end end - for form in response.tags[1]:childtags("x", "jabber:x:data") do - local form_type = form:get_child_with_attr("field", nil, "var", "FORM_TYPE"); - if form_type and form_type:get_child_text("value") == "http://jabber.org/network/serverinfo" then - address = get_address(form, "spam-report-addresses", "abuse-addresses"); - break; + if not address then + if report_to_origin_fallback then + -- If no contact address found, but fallback is enabled, + -- just send the report to the domain + module:log("debug", "Falling back to domain to send report to %s", host); + address = host; + else + module:log("warn", "Failed to query contact addresses of %s: %s", host, response); end end + return address; end); end @@ -108,6 +120,7 @@ reported_message, reported_message_time, reported_message_with = archive:get(reporter_username, reported_message_el.attr.id); if jid.bare(reported_message_with) ~= event.jid then reported_message = nil; + reported_message_time = nil; end end @@ -130,6 +143,7 @@ end for destination in destinations do + count_report:with_labels():add(1); send_report(destination, message); end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_report_tracker/README.markdown Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,32 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'Track abuse/spam reports from remote servers' +--- + +This module tracks reports received from remote servers about local user +accounts. The count of reports and the servers they came from is stored for +inspection by the admin or for use by other modules which might take action +against the reported accounts. + +## Configuration + +### Trusted reporters + +You can configure which servers the module will trust reports from: + +``` +trusted_reporters = { "example.com", "example.net" } +``` + +Reports from non-domain JIDs are currently always ignored (even if listed). + +Reports from domain JIDs which are not listed here are logged so the admin +can decide whether to add them to the configured list. + +## Compatibility + +Should work with 0.12, but has not been tested. + +Tested with trunk (2024-11-22). +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_report_tracker/mod_report_tracker.lua Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,93 @@ +local um = require "core.usermanager"; +local cache = require "util.cache"; +local jid = require "util.jid"; +local trusted_reporters = module:get_option_inherited_set("trusted_reporters", {}); + +local reports_received = module:open_store("reports_received"); + +local xmlns_reporting = "urn:xmpp:reporting:1"; + +local reported_users = cache.new(256); + +local function is_trusted_reporter(reporter_jid) + return trusted_reporters:contains(reporter_jid); +end + +function handle_report(event) + local stanza = event.stanza; + local report = stanza:get_child("report", xmlns_reporting); + if not report then + return; + end + local reported_jid = report:get_child_text("jid", "urn:xmpp:jid:0") + or stanza:find("{urn:xmpp:forward:0}forwarded/{jabber:client}message@from"); + if not reported_jid then + module:log("debug", "Discarding report with no JID"); + return; + elseif jid.host(reported_jid) ~= module.host then + module:log("debug", "Discarding report about non-local user"); + return; + end + + local reporter_jid = stanza.attr.from; + if jid.node(reporter_jid) then + module:log("debug", "Discarding report from non-server JID"); + return; + end + + local reported_user = jid.node(reported_jid); + if not um.user_exists(reported_user, module.host) then + module:log("debug", "Discarding report about non-existent user"); + return; + end + + if is_trusted_reporter(reporter_jid) then + local current_reports = reports_received:get(reported_user, reporter_jid); + + if not current_reports then + current_reports = { + first = os.time(); + last = os.time(); + count = 1; + }; + else + current_reports.last = os.time(); + current_reports.count = current_reports.count + 1; + end + + reports_received:set(reported_user, reporter_jid, current_reports); + reported_users:set(reported_user, true); + + module:log("info", "Received abuse report about <%s> from <%s>", reported_jid, reporter_jid); + + module:fire_event(module.name.."/account-reported", { + report_from = reporter_jid; + reported_user = reported_user; + report = report; + }); + else + module:log("warn", "Discarding abuse report about <%s> from untrusted source <%s>", reported_jid, reporter_jid); + end + + -- Message was handled + return true; +end + +module:hook("message/host", handle_report); + +module:add_item("account-trait", { + name = "reported-by-trusted-server"; + prob_bad_true = 0.80; + prob_bad_false = 0.50; +}); + +module:hook("get-account-traits", function (event) + local username = event.username; + local reported = reported_users:get(username); + if reported == nil then + -- Check storage, update cache + reported = not not reports_received:get(username); + reported_users:set(username, reported); + end + event.traits["reported-by-trusted-server"] = reported; +end);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_require_otr/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,33 @@ +--- +labels: +- 'Stage-Stable' +summary: 'Enforce a policy for OTR-encrypted messages' +... + +Introduction +------------ + +[OTR, "Off The Record"](https://otr.cypherpunks.ca/), encryption allows +clients to encrypt messages such that the server cannot read/modify +them. + +This module allows the server admin to require that all messages are +OTR-encrypted. + +Configuration +------------- + +Just enable the module by adding it to your global `modules_enabled`, or +if you only want to load it on a single host you can load it only for +one host like this: + + VirtualHost "example.com" + modules_enabled = { "require_otr" } + +#### Compatibility + + ------ ------- + 0.10 Works + 0.9 Works + 0.8 Works + ------ -------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_rest/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,629 @@ +--- +labels: +- 'Stage-Alpha' +summary: RESTful XMPP API +rockspec: + build: + modules: + mod_rest.jsonmap: jsonmap.lib.lua + copy_directories: + - example + - res +--- + +# Introduction + +This is yet another RESTful API for sending and receiving stanzas via +Prosody. It can be used to build bots and components implemented as HTTP +services. It is the spiritual successor to [mod_post_msg] and absorbs +use cases from [mod_http_rest] and [mod_component_http] and other such +modules floating around the Internet. + +# Usage + +You make a choice: install via VirtualHosts or as a Component. User authentication can +be used when installed via VirtualHost, and OAuth2 can be used for either. + +## On VirtualHosts + +This enables rest on the VirtualHost domain, enabling user authentication to secure +the endpoint. Make sure that the modules_enabled section is immediately below the +VirtualHost entry so that it's not under any Component sections. EG: + +```lua +VirtualHost "chat.example.com" +modules_enabled = {"rest"} +``` + +## As a Component + +If you install this as a component, you won't be able to use user authentication above, +and must use OAuth2 authentication outlined below. + +``` {.lua} +Component "chat.example.com" "rest" +component_secret = "dmVyeSBzZWNyZXQgdG9rZW4K" +modules_enabled = {"http_oauth2"} +``` + +## User authentication + +To enable user authentication, edit the "admins = { }" section in prosody.cfg.lua, EG: + +```lua +admins = { "admin@chat.example.com" } +``` + +To set up the admin user account: + +```lua +prosodyctl adduser admin@chat.example.com +``` + +and lastly, drop the "@host" from the username in your http queries, EG: + +```lua +curl \ + https://chat.example.com:5281/rest/version/chat.example.com \ + -k \ + --user admin \ + -H 'Accept: application/json' +``` + +## OAuth2 + +[mod_http_oauth2] can be used to grant bearer tokens which are accepted +by mod_rest. Tokens can be passed to `curl` like `--oauth2-bearer +dmVyeSBzZWNyZXQgdG9rZW4K` instead of using `--user`. + +## Sending stanzas + +The API endpoint becomes available at the path `/rest`, so the full URL +will be something like `https://conference.chat.example.com:5281/rest`. + +To try it, simply `curl` an XML stanza payload: + +``` {.sh} +curl https://prosody.example:5281/rest \ + --user username \ + -H 'Content-Type: application/xmpp+xml' \ + --data-binary '<message type="chat" to="user@example.org"> + <body>Hello!</body> + </message>' +``` + +or a JSON payload: + +``` {.sh} +curl https://prosody.example:5281/rest \ + --user username \ + -H 'Content-Type: application/json' \ + --data-binary '{ + "body" : "Hello!", + "kind" : "message", + "to" : "user@example.org", + "type" : "chat" + }' +``` + +The `Content-Type` header is important! + +### Parameters in path + +New alternative format with the parameters `kind`, `type`, and `to` +embedded in the path: + +``` +curl https://prosody.example:5281/rest/message/chat/john@example.com \ + --user username \ + -H 'Content-Type: text/plain' \ + --data-binary 'Hello John!' +``` + +### Replies + +A POST containing an `<iq>` stanza automatically wait for the reply, +long-polling style. + +``` {.sh} +curl https://prosody.example:5281/rest \ + --user username \ + -H 'Content-Type: application/xmpp+xml' \ + --data-binary '<iq type="get" to="example.net"> + <ping xmlns="urn:xmpp:ping"/> + </iq>' +``` + +Replies to other kinds of stanzas that are generated by the same Prosody +instance *MAY* be returned in the HTTP response. Replies from other +entities (connected clients or remote servers) will not be returned, but +can be forwarded via the callback API described in the next section. + +### Simple info queries + +A subset of IQ stanzas can be sent as simple GET requests + +``` +curl https://prosody.example:5281/rest/version/example.com \ + --user username \ + -H 'Accept: application/json' +``` + +The supported queries are + +- `archive` +- `disco` +- `extdisco` +- `items` +- `lastactivity` +- `oob` +- `payload` +- `ping` +- `stats` +- `version` + +## Receiving stanzas + +TL;DR: Set this webhook callback URL, get XML `POST`-ed there. + +``` {.lua} +Component "rest.example.net" "rest" +rest_callback_url = "http://my-api.example:9999/stanzas" +``` + +The callback URL supports a few variables from the stanza being sent, +namely `{kind}` (e.g. message, presence, iq or meta) and ones +corresponding to stanza attributes: `{type}`, `{to}` and `{from}`. + +The preferred format can be indicated via the Accept header in response +to an OPTIONS probe that mod_rest does on startup, or by configuring: + +``` {.lua} +rest_callback_content_type = "application/json" +``` + +Example callback looks like: + +``` {.xml} +POST /stanzas HTTP/1.1 +Content-Type: application/xmpp+xml +Content-Length: 102 + +<message to="bot@rest.example.net" from="user@example.com" type="chat"> +<body>Hello</body> +</message> +``` + +or as JSON: + +``` {.json} +POST /stanzas HTTP/1.1 +Content-Type: application/json +Content-Length: 133 + +{ + "body" : "Hello", + "from" : "user@example.com", + "kind" : "message", + "to" : "bot@rest.example.net", + "type" : "chat" +} +``` + +### Which stanzas + +The set of stanzas routed to the callback is determined by these two +settings: + +`rest_callback_stanzas` +: The stanza kinds to handle, defaults to `{ "message", "presence", "iq" }` + +`rest_callback_events` +: For the selected stanza kinds, which events to handle. When loaded +on a Component, this defaults to `{ "bare", "full", "host" }`, while on +a VirtualHost the default is `{ "host" }`. + +Events correspond to which form of address was used in the `to` +attribute of the stanza. + +bare +: `localpart@hostpart` + +full +: `localpart@hostpart/resourcepart` + +host +: `hostpart` + +The following example would handle only stanzas like `<message +to="anything@hello.example"/>` + +```lua +Component "hello.example" "rest" +rest_callback_url = "http://hello.internal.example:9003/api" +rest_callback_stanzas = { "message" } +rest_callback_events = { "bare" } +``` + +### Replying + +To accept the stanza without returning a reply, respond with HTTP status +code `202` or `204`. + +HTTP status codes in the `4xx` and `5xx` range are mapped to an +appropriate stanza error. + +For full control over the response, set the `Content-Type` header to +`application/xmpp+xml` and return an XMPP stanza as an XML snippet. + +``` {.xml} +HTTP/1.1 200 Ok +Content-Type: application/xmpp+xml + +<message type="chat"> +<body>Yes, this is bot</body> +</message> +``` + +## Payload format + +### JSON + +``` {.json} +{ + "body" : "Hello!", + "kind" : "message", + "type" : "chat" +} +``` + +Further JSON object keys as follows: + +#### Messages + +`kind` +: `"message"` + +`type` +: Commonly `"chat"` for 1-to-1 messages and `"groupchat"` for group + chat messages. Others include `"normal"`, `"headline"` and + `"error"`. + +`body` +: Human-readable message text. + +`subject` +: Message subject or MUC topic. + +`html` +: HTML. + +`oob_url` +: URL of an out-of-band resource, often used for images. + +#### Presence + +`kind` +: `"presence"` + +`type` +: Empty for online or `"unavailable"` for offline. + +`show` +: [Online + status](https://xmpp.org/rfcs/rfc6121.html#presence-syntax-children-show), + `away`, `dnd` etc. + +`status` +: Human-readable status message. + +#### Info-Queries + +Only one type of payload can be included in an `iq`. + +`kind` +: `"iq"` + +`type` +: `"get"` or `"set"` for queries, `"response"` or `"error"` for + replies. + +`ping` +: Send a ping. Get a pong. Maybe. + +`disco` +: Retrieve service discovery information about an entity. + +`items` +: Discover list of items (other services, groupchats etc). + +### XML + +``` {.xml} +<message type="" id="" to="" from="" xml:lang=""> +... +</message> +``` + +An XML declaration (`<?xml?>`) **MUST NOT** be included. + +The payload MUST contain one (1) `message`, `presence` or `iq` stanza. + +The stanzas MUST NOT have an `xmlns` attribute, and the default/empty +namespace is treated as `jabber:client`. + +# Examples + +## Python / Flask + +Simple echo bot that responds to messages as XML: + +``` {.python} +from flask import Flask, Response, request +import xml.etree.ElementTree as ET + +app = Flask("echobot") + + +@app.before_request +def parse(): + request.stanza = ET.fromstring(request.data) + + +@app.route("/", methods=["POST"]) +def hello(): + if request.stanza.tag == "message": + return Response( + "<message><body>Yes this is bot</body></message>", + content_type="application/xmpp+xml", + ) + + return Response(status=501) + + +if __name__ == "__main__": + app.run() +``` + +And a JSON variant: + +``` {.python} +from flask import Flask, Response, request, jsonify + +app = Flask("echobot") + + +@app.route("/", methods=["POST"]) +def hello(): + print(request.data) + if request.is_json: + data = request.get_json() + if data["kind"] == "message": + return jsonify({"body": "hello"}) + + return Response(status=501) + + +if __name__ == "__main__": + app.run() +``` + +Remember to set `rest_callback_content_type = "application/json"` for +this to work. + +# JSON mapping + +This section describes the JSON mapping. It can't represent any possible +stanza, for full flexibility use the XML mode. + +## Stanza basics + +`kind` +: String representing the kind of stanza, one of `"message"`, + `"presence"` or `"iq"`. + +`type` +: String with the type of stanza, appropriate values vary depending on + `kind`, see [RFC 6121]. E.g.`"chat"` for *message* stanzas etc. + +`to` +: String containing the XMPP Address of the destination / recipient of + the stanza. + +`from` +: String containing the XMPP Address of the sender the stanza. + +`id` +: String with a reasonably unique identifier for the stanza. + +## Basic Payloads + +### Messages + +`body` +: String, human readable text message. + +`subject` +: String, human readable summary equivalent to an email subject or the + chat room topic in a `type:groupchat` message. + +### Presence + +`show` +: String representing availability, e.g. `"away"`, `"dnd"`. No value + means a normal online status. See [RFC 6121] for the full list. + +`status` +: String with a human readable text message describing availability. + +## More payloads + +### Messages + +`state` +: String with current chat state, e.g. `"active"` (default) and + `"composing"` (typing). + +`html` +: String with HTML allowing rich formatting. **MUST** be contained in a + `<body>` element. + +`oob_url` +: String with an URL of an external resource. + +### Presence + +`muc` +: Object with [MUC][XEP-0045] related properties. + +### IQ + +`ping` +: Boolean, a simple ping query. "Pongs" have only basic fields + presents. + +`version` +: Map with `name`, `version` fields, and optionally an `os` field, to + describe the software. + +#### Service Discovery + +`disco` + +: Boolean `true` in a `kind:iq` `type:get` for a service discovery + query. + + Responses have a map containing an array of available features in + the `features` key and an array of "identities" in the `identities` + key. Each identity has a `category` and `type` field as well as an + optional `name` field. See [XEP-0030] for further details. + +`items` +: Boolean `true` in a `kind:iq` `type:get` for a service discovery + items list query. The response contain an array of items like + `{"jid":"xmpp.address.here","name":"Description of item"}`. + +`extensions` +: Map of extended feature discovery (see [XEP-0128]) data with + `FORM_DATA` fields as the keys pointing at maps with the rest of the + data. + +#### Ad-Hoc Commands + +Used to execute arbitrary commands on supporting entities. + +`command` + +: String representing the command `node` or Map with the following + possible fields: + + `node` + : Required string with node from disco\#items query for the + command to execute. + + `action` + : Optional enum string defaulting to `"execute"`. Multi-step + commands may involve `"next"`, `"prev"`, `"complete"` or + `"cancel"`. + + `actions` + : Set (map of strings to `true`) with available actions to proceed + with in multi-step commands. + + `status` + : String describing the status of the command, normally + `"executing"`. + + `sessionid` + : Random session ID issued by the responder to identify the + session in multi-step commands. + + `note` + : Map with `"type"` and `"text"` fields that carry simple result + information. + + `form` + : Data form with description of expected input and data types in + the next step of multi-step commands. **TODO** document format. + + `data` + : Map with only the data for result dataforms. Fields may be + strings or arrays of strings. + +##### Example + +Discovering commands: + +``` {.json} +{ + "items" : { + "node" : "http://jabber.org/protocol/commands" + }, + "id" : "8iN9hwdAAcfTBchm", + "kind" : "iq", + "to" : "example.com", + "type" : "get" +} +``` + +Response: + +``` {.json} +{ + "from" : "example.com", + "id" : "8iN9hwdAAcfTBchm", + "items" : [ + { + "jid" : "example.com", + "name" : "Get uptime", + "node" : "uptime" + } + ], + "kind" : "iq", + "type" : "result" +} +``` + +Execute the command: + +``` {.json} +{ + "command" : { + "node" : "uptime" + }, + "id" : "Jv-87nRaP6Mnrp8l", + "kind" : "iq", + "to" : "example.com", + "type" : "set" +} +``` + +Executed: + +``` {.json} +{ + "command" : { + "node" : "uptime", + "note" : { + "text" : "This server has been running for 0 days, 20 hours and 54 minutes (since Fri Feb 7 18:05:30 2020)", + "type" : "info" + }, + "sessionid" : "6380880a-93e9-4f13-8ee2-171927a40e67", + "status" : "completed" + }, + "from" : "example.com", + "id" : "Jv-87nRaP6Mnrp8l", + "kind" : "iq", + "type" : "result" +} +``` + +# TODO + +- Describe multi-step commands with dataforms. +- Versioned API, i.e. /v1/stanzas +- Bind resource to webhook/callback + +# Compatibility + +Requires Prosody trunk / 0.12
--- a/mod_rest/example/rest.sh Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_rest/example/rest.sh Tue Mar 18 00:31:36 2025 +0700 @@ -9,6 +9,12 @@ # shellcheck disable=SC1091 +SELF="${0##*/}" +function usage() { + echo "${SELF} [-h HOST] [-rw] [/path] kind=(message|presence|iq) ...." + # Last arguments are handed to HTTPie, so refer to its docs for further details +} + # Settings HOST="" DOMAIN="" @@ -25,24 +31,43 @@ fi if [[ $# == 0 ]]; then - echo "${0##*/} [-h HOST] [-rw] [/path] kind=(message|presence|iq) ...." - # Last arguments are handed to HTTPie, so refer to its docs for further details - exit 0 + usage + exit 1 fi -if [[ "$1" == "-h" ]]; then - HOST="$2" - shift 2 -elif [ -z "${HOST:-}" ]; then +while getopts 'r:h:' flag; do + case "$flag" in + r) + case "$OPTARG" in + o) + # Default + SESSION="session-read-only" + ;; + w) + # To e.g. save Accept headers to the session + SESSION="session" + ;; + *) + echo "E: -ro OR -rw" >&2 + exit 1 + ;; + esac + ;; + h) + HOST="$OPTARG" + ;; + *) + echo "E: Unknown flag '$flag'" >&2 + usage >&2 + exit 1 + esac +done +shift $((OPTIND-1)) + +if [ -z "${HOST:-}" ]; then HOST="$(hostname)" fi -if [[ "$1" == "-rw" ]]; then - # To e.g. save Accept headers to the session - SESSION="session" - shift 1 -fi - if [[ "$HOST" != *.* ]]; then # Assumes subdomain of your DOMAIN if [ -z "${DOMAIN:-}" ]; then
--- a/mod_rest/jsonmap.lib.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_rest/jsonmap.lib.lua Tue Mar 18 00:31:36 2025 +0700 @@ -550,6 +550,12 @@ t.payload.data = json.encode(t.payload.data); end + if type(t.upload_request) == "table" and type(t.upload_request.size) == "string" then + -- When using GET /rest/upload_request then the arguments from the query are all strings + t.upload_request.size = tonumber(t.upload_request.size); + end + + if kind == "presence" and t.join == true and t.muc == nil then -- COMPAT Older boolean 'join' property used with XEP-0045 t.muc = {};
--- a/mod_rest/mod_rest.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_rest/mod_rest.lua Tue Mar 18 00:31:36 2025 +0700 @@ -23,7 +23,7 @@ -- Lower than the default c2s size limit to account for possible JSON->XML size increase local stanza_size_limit = module:get_option_number("rest_stanza_size_limit", 1024 * 192); -local auth_mechanisms = module:get_option_set("rest_auth_mechanisms", { "Basic", "Bearer" }); +local auth_mechanisms = module:get_option_set("rest_auth_mechanisms", { "Basic", "Bearer" }) / string.lower; local www_authenticate_header; do @@ -34,35 +34,69 @@ www_authenticate_header = table.concat(header, ", "); end -local function check_credentials(request) +local post_errors = errors.init("mod_rest", { + noauthz = { code = 401; type = "auth"; condition = "not-authorized"; text = "No credentials provided" }; + unauthz = { code = 403; type = "auth"; condition = "not-authorized"; text = "Credentials not accepted" }; + malformauthz = { code = 403; type = "auth"; condition = "not-authorized"; text = "Credentials malformed" }; + prepauthz = { code = 403; type = "auth"; condition = "not-authorized"; text = "Credentials failed stringprep" }; + parse = { code = 400; type = "modify"; condition = "not-well-formed"; text = "Failed to parse payload" }; + xmlns = { code = 422; type = "modify"; condition = "invalid-namespace"; text = "'xmlns' attribute must be empty" }; + name = { code = 422; type = "modify"; condition = "unsupported-stanza-type"; text = "Invalid stanza, must be 'message', 'presence' or 'iq'." }; + to = { code = 422; type = "modify"; condition = "improper-addressing"; text = "Invalid destination JID" }; + from = { code = 422; type = "modify"; condition = "invalid-from"; text = "Invalid source JID" }; + from_auth = { code = 403; type = "auth"; condition = "not-authorized"; text = "Not authorized to send stanza with requested 'from'" }; + iq_type = { code = 422; type = "modify"; condition = "invalid-xml"; text = "'iq' stanza must be of type 'get' or 'set'" }; + iq_tags = { code = 422; type = "modify"; condition = "bad-format"; text = "'iq' stanza must have exactly one child tag" }; + mediatype = { code = 415; type = "cancel"; condition = "bad-format"; text = "Unsupported media type" }; + size = { code = 413; type = "modify"; condition = "resource-constraint", text = "Payload too large" }; +}); + +local token_session_errors = errors.init("mod_tokenauth", { + ["internal-error"] = { code = 500; type = "wait"; condition = "internal-server-error" }; + ["invalid-token-format"] = { code = 403; type = "auth"; condition = "not-authorized"; text = "Credentials malformed" }; + ["not-authorized"] = { code = 403; type = "auth"; condition = "not-authorized"; text = "Credentials not accepted" }; +}); + +local function check_credentials(request) -- > session | boolean, error local auth_type, auth_data = string.match(request.headers.authorization, "^(%S+)%s(.+)$"); + auth_type = auth_type and auth_type:lower(); if not (auth_type and auth_data) or not auth_mechanisms:contains(auth_type) then - return false; + return nil, post_errors.new("noauthz", { request = request }); end - if auth_type == "Basic" then + if auth_type == "basic" then local creds = base64.decode(auth_data); - if not creds then return false; end + if not creds then + return nil, post_errors.new("malformauthz", { request = request }); + end local username, password = string.match(creds, "^([^:]+):(.*)$"); - if not username then return false; end + if not username then + return nil, post_errors.new("malformauthz", { request = request }); + end username, password = encodings.stringprep.nodeprep(username), encodings.stringprep.saslprep(password); - if not username then return false; end + if not username or not password then + return false, post_errors.new("prepauthz", { request = request }); + end if not um.test_password(username, module.host, password) then - return false; + return false, post_errors.new("unauthz", { request = request }); end - return { username = username, host = module.host }; - elseif auth_type == "Bearer" then + return { username = username; host = module.host }; + elseif auth_type == "bearer" then if tokens.get_token_session then - return tokens.get_token_session(auth_data); + local token_session, err = tokens.get_token_session(auth_data); + if not token_session then + return false, token_session_errors.new(err or "not-authorized", { request = request }); + end + return token_session; else -- COMPAT w/0.12 local token_info = tokens.get_token_info(auth_data); if not token_info or not token_info.session then - return false; + return false, post_errors.new("unauthz", { request = request }); end return token_info.session; end end - return nil; + return nil, post_errors.new("noauthz", { request = request }); end if module:get_option_string("authentication") == "anonymous" and module:get_option_boolean("anonymous_rest") then @@ -125,7 +159,7 @@ -- (table, string) -> table local function amend_from_path(data, path) - local st_kind, st_type, st_to = path:match("^([mpi]%w+)/(%w+)/(.*)$"); + local st_kind, st_type, st_to = path:match("^([mpi]%w+)/([%w_]+)/(.*)$"); if not st_kind then return; end if st_kind == "iq" and st_type ~= "get" and st_type ~= "set" then -- GET /iq/disco/jid @@ -268,21 +302,6 @@ error "unsupported encoding"; end -local post_errors = errors.init("mod_rest", { - noauthz = { code = 401; type = "auth"; condition = "not-authorized"; text = "No credentials provided" }; - unauthz = { code = 403; type = "auth"; condition = "not-authorized"; text = "Credentials not accepted" }; - parse = { code = 400; type = "modify"; condition = "not-well-formed"; text = "Failed to parse payload" }; - xmlns = { code = 422; type = "modify"; condition = "invalid-namespace"; text = "'xmlns' attribute must be empty" }; - name = { code = 422; type = "modify"; condition = "unsupported-stanza-type"; text = "Invalid stanza, must be 'message', 'presence' or 'iq'." }; - to = { code = 422; type = "modify"; condition = "improper-addressing"; text = "Invalid destination JID" }; - from = { code = 422; type = "modify"; condition = "invalid-from"; text = "Invalid source JID" }; - from_auth = { code = 403; type = "auth"; condition = "not-authorized"; text = "Not authorized to send stanza with requested 'from'" }; - iq_type = { code = 422; type = "modify"; condition = "invalid-xml"; text = "'iq' stanza must be of type 'get' or 'set'" }; - iq_tags = { code = 422; type = "modify"; condition = "bad-format"; text = "'iq' stanza must have exactly one child tag" }; - mediatype = { code = 415; type = "cancel"; condition = "bad-format"; text = "Unsupported media type" }; - size = { code = 413; type = "modify"; condition = "resource-constraint", text = "Payload too large" }; -}); - -- GET → iq-get local function parse_request(request, path) if path and request.method == "GET" then @@ -308,9 +327,10 @@ response.headers.www_authenticate = www_authenticate_header; return post_errors.new("noauthz"); else - origin = check_credentials(request); + local err; + origin, err = check_credentials(request); if not origin then - return post_errors.new("unauthz"); + return err or post_errors.new("unauthz"); end from = jid.join(origin.username, origin.host, origin.resource); origin.full_jid = from; @@ -642,6 +662,17 @@ "application/json", }; +-- strip some stuff, notably the optional traceback table that casues stack overflow in util.json +local function simplify_error(e) + return { + type = e.type; + condition = e.condition; + text = e.text; + extra = e.extra; + source = e.source; + }; +end + local http_server = require "net.http.server"; module:hook_object_event(http_server, "http-error", function (event) local request, response = event.request, event.response; @@ -664,7 +695,7 @@ end return json.encode({ type = "error", - error = event.error, + error = simplify_error(event.error), code = event.code, }); end
--- a/mod_rest/res/openapi.yaml Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_rest/res/openapi.yaml Tue Mar 18 00:31:36 2025 +0700 @@ -230,16 +230,55 @@ get: tags: - query - summary: Lorem ipsum + summary: Request space for uploading a file to the server. security: - basic: [] - token: [] - oauth2: [] parameters: - $ref: '#/components/parameters/to' + - name: filename + in: query + required: true + schema: + type: string + - name: size + in: query + required: true + schema: + type: integer + - name: content-type + in: query + schema: + type: string responses: "200": - $ref: '#/components/responses/success' + description: Successful slot request. + content: + application/json: + schema: + type: object + xml: + name: iq + properties: + kind: + type: string + enum: + - iq + type: + type: string + enum: + - result + xml: + attribute: true + to: + $ref: '#/components/schemas/to' + from: + $ref: '#/components/schemas/from' + id: + $ref: '#/components/schemas/id' + upload_slot: + $ref: '#/components/schemas/upload_slot' components: schemas: stanza:
--- a/mod_rest/res/schema-xmpp.json Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_rest/res/schema-xmpp.json Tue Mar 18 00:31:36 2025 +0700 @@ -266,6 +266,71 @@ "namespace" : "urn:xmpp:mam:2" } }, + "block" : { + "items" : { + "properties" : { + "jid" : { + "type" : "string", + "xml" : { + "attribute" : true + } + }, + "report" : { + "properties" : { + "reason" : { + "enum" : [ + "urn:xmpp:reporting:spam", + "urn:xmpp:reporting:abuse" + ], + "type" : "string", + "xml" : { + "attribute" : true + } + }, + "stanza_ids" : { + "$ref" : "#/properties/message/properties/stanza_ids" + } + }, + "xml" : { + "name" : "report", + "namespace" : "urn:xmpp:reporting:1" + } + } + }, + "type" : "object", + "xml" : { + "name" : "item" + } + }, + "type" : "array", + "xml" : { + "name" : "block", + "namespace" : "urn:xmpp:blocking", + "wrapped" : true + } + }, + "blocklist" : { + "items" : { + "properties" : { + "jid" : { + "type" : "string", + "xml" : { + "attribute" : true + } + } + }, + "type" : "object", + "xml" : { + "name" : "item" + } + }, + "type" : "array", + "xml" : { + "name" : "blocklist", + "namespace" : "urn:xmpp:blocking", + "wrapped" : true + } + }, "dataform" : { "$ref" : "#/_common/dataform" },
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_restrict_xmpp/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,61 @@ +--- +labels: +- Stage-Alpha +summary: XMPP-layer access control for Prosody +--- + +Introduction +============ + +This module enforces access policies using Prosody's new [roles and +permissions framework](https://prosody.im/doc/developers/permissions). It can +be used to grant restricted access to an XMPP account or services. + +This module is still in its early stages, and prone to change. Feedback from +testers is welcome. At this early stage, it should not be solely relied upon +for account security purposes. + +Configuration +============= + +There is no configuration, apart from Prosody's normal roles and permissions +configuration. + +Permissions +=========== + +`xmpp:federate` +: Communicate with other users and services on other hosts on the XMPP + network + +`xmpp:account:messages:read` +: Read incoming messages + +`xmpp:account:messages:write` +: Send outgoing messages + +`xmpp:account:presence:write` +: Update presence for the account + +`xmpp:account:contacts:read`/`xmpp:account:contacts:write` +: Controls access to the contact list (roster) + +`xmpp:account:bookmarks:read`/`xmpp:account:bookmarks:write` +: Controls access to the bookmarks (group chats list) + +`xmpp:account:profile:read`/`xmpp:account:profile:write` +: Controls access to the user's profile (e.g. vCard/avatar) + +`xmpp:account:omemo:read`/`xmpp:account:omemo:write` +: Controls access to the user's OMEMO data + +`xmpp:account:blocklist:read`/`xmpp:account:blocklist:write` +: Controls access to the user's block list + +`xmpp:account:disco:read` +: Controls access to the user's service discovery information + +Compatibility +============= + +Requires Prosody trunk 72f431b4dc2c (build 1444) or later.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_roster_allinall/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,31 @@ +--- +labels: +- 'Stage-Beta' +summary: 'Add everyone to everyones roster on the server' +... + +Introduction +============ + +This module is similar in purpouse to mod\_groups, for when you want all +users on the server to be in each others roster. + +Details +======= + +Upon login, this module will add all currently logged in users to the +logging in users roster. + +Configuration +============= + +Just add it to the modules\_enabled, after that there is no further +configuration. + +Compatibility +============= + + version note + --------- ---------------------- + trunk Works as of 07.12.22 + 0.12 Works
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_roster_command/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,58 @@ +--- +labels: +- 'Stage-Beta' +summary: Manage rosters through prosodyctl +... + +Introduction +------------ + +This module allows you to perform various actions on user rosters via +prosodyctl. + +Details +------- + +After putting this module in your modules directory you can use it via +prosodyctl like this: + + prosodyctl mod_roster_command COMMAND [OPTIONS...] + +**Note:** Do not add mod\_roster\_command to your Prosody config file. +This is unnecessary because it will automatically be loaded by +prosodyctl when you use it. + +### Commands + + subscribe user@host contact@host + +Subscribes the user to the contact's presence. That is, the user will +see when the contact is online (but the contact won't see the user). + + subscribe_both user@host contact@host + +The same as the 'subscribe' command, but performs the subscription in +both directions, so that both the contact and user will always see each +other online. + + unsubscribe user@host contact@host + +Removes a subscription to the contact's presence. + + unsubscribe_both user@host contact@host + +Same as unsubscribe, but also revokes a contact's subscription to the +user's presence. + + rename user@host contact@host [name] [group] + +Sets or updates a name for a contact in the user's roster, and moves the +contact to the given group, if specified. + +Compatibility +------------- + + ----- ------- + 0.9 Works + 0.8 Works + ----- -------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_s2s_auth_compat/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,11 @@ +--- +labels: +summary: Workaround for servers doing EXTERNAL without proper stream headers +... + +Introduction +============ + +This module is a workaround for servers that try to do s2s +authentication with certificates and SASL EXTERNAL, but do not send +correct stream headers. Notably Openfire versions since 3.7 or 3.8.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_s2s_auth_dane/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,98 @@ +--- +labels: +- Stage-Broken +- Type-S2SAuth +summary: S2S authentication using DANE +... + +Introduction +============ + +This module implements DANE as described in [Using DNS Security +Extensions (DNSSEC) and DNS-based Authentication of Named Entities +(DANE) as a Prooftype for XMPP Domain Name +Associations](http://tools.ietf.org/html/draft-miller-xmpp-dnssec-prooftype). + +Dependencies +============ + +This module requires a DNSSEC aware DNS resolver. Prosodys internal DNS +module does not support DNSSEC. Therefore, to use this module, a +replacement is needed, such as [this +one](https://www.zash.se/luaunbound.html). + +LuaSec 0.5 or later is also required. + +Configuration +============= + +After [installing the module][doc:installing\_modules], just add it to +`modules_enabled`; + + modules_enabled = { + ... + "s2s_auth_dane"; + } + +DANE Uses +--------- + +By default, only DANE uses are enabled. + + dane_uses = { "DANE-EE", "DANE-TA" } + + Use flag Description + ----------- ------------------------------------------------------------------------------------------------------- + `DANE-EE` Most simple use, usually a fingerprint of the full certificate or public key used the service + `DANE-TA` Fingerprint of a certificate or public key that has been used to issue the service certificate + `PKIX-EE` Like `DANE-EE` but the certificate must also pass normal PKIX trust checks (ie standard certificates) + `PKIX-TA` Like `DANE-TA` but must also pass normal PKIX trust checks (ie standard certificates) + +DNS Setup +========= + +In order for other services to verify your site using using this plugin, +you need to publish TLSA records (and they need to have this plugin). +Here's an example using `DANE-EE Cert SHA2-256` for a host named +`xmpp.example.com` serving the domain `example.com`. + + $ORIGIN example.com. + ; Your standard SRV record + _xmpp-server._tcp.example.com IN SRV 0 0 5269 xmpp.example.com. + ; IPv4 and IPv6 addresses + xmpp.example.com. IN A 192.0.2.68 + xmpp.example.com. IN AAAA 2001:0db8:0000:0000:4441:4e45:544c:5341 + + ; The DANE TLSA records. + _5269._tcp.xmpp.example.com. 300 IN TLSA 3 0 1 E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 + + ; If your zone file tooling does not support TLSA records, you can try the raw binary format: + _5269._tcp.xmpp.example.com. 300 IN TYPE52 \# 35 030001E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 + +[List of DNSSEC and DANE +tools](http://www.internetsociety.org/deploy360/dnssec/tools/) + +Further reading +=============== + +- [DANE Operational Guidance][rfc7671] + +# Compatibility + + version status + --------- ------------ + trunk broken[^1] + 0.12 broken + 0.11 works + 0.10 works + 0.9 works + +**Broken** since [trunk revision 756b8821007a](https://hg.prosody.im/trunk/rev/756b8821007a). + +# Known issues + +- A race condition between the DANE lookup and completion of the TLS + handshake may cause a crash. This does not happen in **trunk** + thanks to better async support. + +[^1]: since [756b8821007a](https://hg.prosody.im/trunk/rev/756b8821007a)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_s2s_auth_fingerprint/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,48 @@ +--- +labels: +- 'Stage-Alpha' +- 'Type-S2SAuth' +summary: Fingerprint based s2s authentication +... + +Introduction +============ + +This module allows you to manually pin certificate fingerprints of +remote servers. + +Details +======= + +Servers not listed in the configuration are not affected. + +Configuration +============= + +After installing and enabling this module, you can put fingerprints of +remote servers in your config like this: + +``` {.lua} +s2s_auth_fingerprint_digest = "sha1" -- This is the default. Other options are "sha256" and "sha512" +s2s_trusted_fingerprints = { + ["jabber.org"] = "11:C2:3D:87:3F:95:F8:13:F8:CA:81:33:71:36:A7:00:E0:01:95:ED"; + ["matthewwild.co.uk"] = { + "FD:7F:B2:B9:4C:C4:CB:E2:E7:48:FB:0D:98:11:C7:D8:4D:2A:62:AA"; + "CF:F3:EC:43:A9:D5:D1:4D:D4:57:09:55:52:BC:5D:73:06:1A:A1:A0"; + }; +} + +-- If you don't want to fall back to dialback, you can list the domains s2s_secure_domains too +s2s_secure_domains = { + "jabber.org"; +} +``` + +Compatibility +============= + + ------- -------------- + trunk Works + 0.9 Works + 0.8 Doesn't work + ------- --------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_s2s_auth_monkeysphere/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,26 @@ +--- +labels: +- 'Stage-Alpha' +- 'Type-S2SAuth' +summary: Monkeysphere certificate checking for s2s +--- + +## Introduction + +[Monkeysphere](http://web.monkeysphere.info/) is a project aiming to +introduce PGP's web of trust to protocols such as SSH and TLS (which +XMPP uses). + +## Details + +This module is currently just a prototype, it has numerous issues and is +**not** suitable for production use. + +## Compatibility + + ------- ----------------------------- + trunk Works (not tested recently) + 0.11 Works (not tested) + 0.10 Does not work + 0.9 Does not work + ------- -----------------------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_s2s_auth_posh/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,22 @@ +--- +labels: +- 'Type-S2SAuth' +--- + +Introduction +============ + +[PKIX over Secure HTTP (POSH)][rfc7711] describes a method of +securely delegating a domain to a hosting provider, without that hosting +provider needing keys and certificates covering the hosted domain. + +# Validating + +This module performs POSH validation of other servers. It is *not* +needed to delegate your own domain. + +# Delegation + +You can generate the JSON delegation file from a certificate by running +`prosodyctl mod_s2s_auth_posh /path/to/example.crt`. This file needs to +be served at `https://example.com/.well-known/posh/xmpp-server.json`.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_s2s_blacklist/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,13 @@ +This module lets you block connections to remote servers at the s2s +level. + +``` {.lua} +modules_enabled = { + -- other modules -- + "s2s_blacklist", + +} +s2s_blacklist = { + "proxy.eu.jabber.org", +} +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_s2s_idle_timeout/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,33 @@ +--- +labels: +- 'Stage-Stable' +summary: 'Close idle server-to-server connections' +... + +Introduction +============ + +By default prosody does not close s2s +connections. This module changes that +behaviour by introducing an idle timeout. +It will close server-to-server connections +after they have been silent for a while. + +Configuration +============= + +The default timeout is 300 seconds (5 minutes). +To change this simply put in the config: + + s2s_idle_timeout = 180 -- time in seconds + +Compatibility +============= + + Prosody Version Status + ----------------- ----------- + trunk[^1] Works + 0.12 Works + ----------------- ----------- + +[^1]: as of 2024-10-22
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_s2s_keepalive/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,42 @@ +--- +labels: +- 'Stage-Beta' +summary: Keepalive s2s connections +... + +Introduction +============ + +This module periodically sends [XEP-0199] ping requests to remote servers to keep your connection alive. + +Configuration +============= + +Simply add the module to the `modules_enabled` list like any other module. +By default, all current s2s connections will be pinged +periodically. To ping only a subset of servers, list these in `keepalive_servers`. +The ping interval can be set using `keepalive_interval`. + +If no response to the ping has been received in about 10 minutes (or `keepalive_timeout` seconds) the s2s connections are closed. + +``` lua +modules_enabled = { + ... + "s2s_keepalive" +} + +keepalive_servers = { "conference.prosody.im"; "rooms.swift.im" } +keepalive_interval = 90 -- (in seconds, default is 60 ) +keepalive_timeout = 300 -- (in seconds, default is 593 ) +``` + +Compatibility +============= + + Prosody Version Status + ----------------- ----------- + trunk[^1] Works + 0.12 Works + ----------------- ----------- + +[^1]: as of 2024-11-11
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_s2s_keysize_policy/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,43 @@ +--- +summary: Distrust servers with too small keys +... + +Introduction +============ + +This module sets the security status of s2s connections to invalid if +their key is too small and their certificate was issued after 2014, per +CA/B Forum guidelines. + +Details +======= + +Certificate Authorities were no longer allowed to issue certificates +with public keys smaller than 2048 bits (for RSA) after December 31 +2013. This module was written to enforce this, as there were some CAs +that were slow to comply. As of 2015, it might not be very relevant +anymore, but still useful for anyone who wants to increase their +security levels. + +When a server is determined to have a "too small" key, this module sets +its chain and identity status to "invalid", so Prosody will treat it as +a self-signed certificate istead. + +"Too small" +----------- + +The definition of "too small" is based on the key type and is taken from +[RFC 4492]. + + Type bits + ------ ------ + RSA 2048 + DSA 2048 + DH 2048 + EC 233 + +Compatibility +============= + +Works with Prosody 0.9 and later. Requires LuaSec with [support for +inspecting public keys](https://github.com/brunoos/luasec/pull/19).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_s2s_log_certs/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,41 @@ +--- +summary: Log certificate status and fingerprint of remote servers +... + +Introduction +============ + +This module produces info level log messages with the certificate status +and fingerprint every time an s2s connection is established. It can also +optionally store this in persistent storage. + +**info** jabber.org has a trusted valid certificate with SHA1: +11:C2:3D:87:3F:95:F8:13:F8:CA:81:33:71:36:A7:00:E0:01:95:ED + +Fingerprints could then be added to +[mod\_s2s\_auth\_fingerprint](mod_s2s_auth_fingerprint.html). + +Configuration +============= + +Add the module to the `modules_enabled` list. + + modules_enabled = { + ... + "s2s_log_certs"; + } + +If you want to keep track of how many times, and when a certificate is +seen add + +`s2s_log_certs_persist = true` + +Compatibility +============= + + ------- -------------- + trunk Works + 0.10 Works + 0.9 Works + 0.8 Doesn't work + ------- --------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_s2s_never_encrypt_blacklist/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,38 @@ +--- +labels: +- 'Stage-Beta' +summary: | + Stops prosody from including starttls into available features for + specified remote servers. +... + +Details +------- + +Let's you stop Prosody from sending \<starttls +xmlns='urn:ietf:params:xml:ns:xmpp-tls'\> feature to choppy/buggy +servers which therefore would fail to re-negotiate and use a secure +stream. (e.g. [OpenFire +3.7.0](http://issues.igniterealtime.org/browse/OF-405)) + +Usage +----- + +Copy the plugin into your prosody's modules directory. + +And add it between your enabled modules into the global section +(modules\_enabled). + +Then list each host as follow: + + tls_s2s_blacklist = { "host1.tld", "host2.tld", "host3.tld" } + +In the unfortunate case of OpenFire... you can add the Server's ip +address directly as it may not send proper rfc6121 requests. + + tls_s2s_blacklist_ip = { "a.a.a.a", "b.b.b.b", "c.c.c.c" } + +Compatibility +------------- + +It's supposed to work with 0.7-0.8.x
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_s2s_reload_newcomponent/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,37 @@ +--- +labels: +summary: | + Module to automatically load new components when config:reload is done + in console +... + +Introduction +============ + +Currently, module:reload command in console doesn't load new components. +This module will automatically load the new components (if any) when the +config:reload command is run in the console. + +Details +======= + +In order to use the plugin, simply load the plugin by adding +"s2s\_reload\_newcomponent" to the modules enabled list. The plugin +requires configuration to be reloaded via console plugin's +config:reload() command. + +Now, add a new component in the prosody configuration and then run +config:reload() command in the console plugin. The new component should +become active in prosody at this point and can be used. + +Dependency +========== + +Needs console plugin to reload configuration. + +Compatibility +============= + + ----- ------- + 0.7 works + ----- -------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_s2s_status/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,42 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'Track the status and health of s2s connections' +... + +Introduction +============ + +Prosody already gives some insight into current s2s connections, e.g. via +the `s2s:show()` command in the console. This will tell you about all current +s2s connections. + +However sometimes this is not enough. For example if an s2s connection fails +to establish, it won't show up - you have to go digging through the log file +looking for the errors instead. + +This module maintains a record of recent connection attempts to a remote +domain. You can use this module to answer questions such as: + +- Why did the last connection attempt to `example.com` fail? +- When did I last have a successful connection with `example.com`? +- Are my s2s connections generally stable? + +**Note:** At the time of writing, this module is not yet finished, and should +be considered a proof-of-concept. + +# Configuration + +Just load the module as normal: + +``` {.lua} +modules_enabled = { + ... + "s2s_status"; + ... +} +``` + +# Compatibility + +trunk (0.12) and later, e.g. due to [60676b607b6d](https://hg.prosody.im/trunk/rev/60676b607b6d).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_s2s_whitelist/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,13 @@ +This module lets you block connections to any remote servers not on a +whitelist. + +``` {.lua} +modules_enabled = { + -- other modules -- + "s2s_whitelist", + +} +s2s_whitelist = { + "example.org", +} +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_s2soutinjection/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,31 @@ +--- +summary: S2S connection override +... + +# Introduction + +This module is similar to [mod\_srvinjection] but less of an hack. + +# Configuration + +``` lua +-- In the global section + +modules_enabled = { + --- your other modules + "s2soutinjection"; +} + +-- targets must be IPs, not hostnames +s2s_connect_overrides = { + -- This one will use the default port, 5269 + ["example.com"] = "1.2.3.4"; + + -- To set a different port: + ["another.example"] = { "127.0.0.1", 9999 }; +} +``` + +# Compatibility + +Requires 0.9.x or later. Tested on 0.12.0
--- a/mod_sasl2/README.md Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_sasl2/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -4,10 +4,7 @@ summary: "XEP-0388: Extensible SASL Profile" --- -Implementation of [XEP-0388: Extensible SASL Profile]. **Note: At the time of -writing (Nov 2022) the version of the XEP implemented by this module is still -working its way through the XSF standards process. See [PR #1214](https://github.com/xsf/xeps/pull/1214) -for the current status.** +Implementation of [XEP-0388: Extensible SASL Profile]. ## Configuration @@ -26,3 +23,14 @@ - Priority -1500: Updated <stream-features/> sent to client - `sasl2/c2s/failure` - `sasl2/c2s/error` + +# Compatibility + +This module requires Prosody **trunk** and is not compatible with 0.12 or older versions. + + + Prosody Version Status + ----------------------- ---------------- + trunk as of 2024-11-24 Works + 0.12 Does not work + ----------------------- ----------------
--- a/mod_sasl2/mod_sasl2.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_sasl2/mod_sasl2.lua Tue Mar 18 00:31:36 2025 +0700 @@ -125,6 +125,9 @@ module:hook("sasl2/c2s/failure", function (event) module:fire_event("authentication-failure", event); local session, condition, text = event.session, event.message, event.error_text; + + session.sasl_handler = session.sasl_handler:clean_clone(); + local failure = st.stanza("failure", { xmlns = xmlns_sasl2 }) :tag(condition, { xmlns = "urn:ietf:params:xml:ns:xmpp-sasl" }):up(); if text then
--- a/mod_sasl2_bind2/README.md Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_sasl2_bind2/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -8,9 +8,13 @@ --- Add support for [XEP-0386: Bind 2], which is a new method for clients to bind -resources and establish sessions in XMPP, using SASL2. **Note: At the time of -writing (November 2022), this plugin implements a version of XEP-0386 that is -still working its way through the XSF standards process. See [PR #1217](https://github.com/xsf/xeps/pull/1217) -for more information and current status.** +resources and establish sessions in XMPP, using SASL2. This module depends on [mod_sasl2]. It exposes no configuration options. + +# Compatibility + + Prosody-Version Status + --------------- ---------------------- + trunk Works as of 2024-12-21 + 0.12 Does not work
--- a/mod_sasl2_fast/README.md Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_sasl2_fast/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -16,8 +16,8 @@ | Name | Description | Default | |---------------------------|--------------------------------------------------------|-----------------------| -| sasl2_fast_token_ttl | Default token expiry (seconds) | `86400*21` (21 days) | -| sasl2_fast_token_min_ttl | Time before tokens are eligible for rotation (seconds) | `86400` (1 day) | +| sasl2_fast_token_ttl | Default token expiry (seconds) | 86400*21 (21 days) | +| sasl2_fast_token_min_ttl | Time before tokens are eligible for rotation (seconds) | 86400 (1 day) | The `sasl2_fast_token_ttl` option determines the length of time a client can remain disconnected before being "logged out" and needing to authenticate with @@ -28,3 +28,10 @@ rotated by the server. By default a token is rotated if it is older than 24 hours. This value should be less than `sasl2_fast_token_ttl` to prevent clients being logged out unexpectedly. + +# Compatibility + + Prosody-Version Status + --------------- ---------------------- + trunk Works as of 2024-12-21 + 0.12 Does not work
--- a/mod_sasl2_fast/mod_sasl2_fast.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_sasl2_fast/mod_sasl2_fast.lua Tue Mar 18 00:31:36 2025 +0700 @@ -8,6 +8,11 @@ local now = require "util.time".now; local hash = require "util.hashes"; +local sasl_mt = getmetatable(sasl.new("", { mechanisms = {} })); +local function is_util_sasl(sasl_handler) + return getmetatable(sasl_handler) == sasl_mt; +end + module:depends("sasl2"); -- Tokens expire after 21 days by default @@ -49,7 +54,7 @@ log("debug", "Looking for %s token %s/%s", mechanism, username, key); token = token_store:get(username, key); if token and token.mechanism == mechanism then - local expected_hash = hmac_f(token.secret, "Initiator"..cb_data); + local expected_hash = hmac_f(token.secret, "Initiator"..(cb_data or "")); if hash.equals(expected_hash, token_hash) then local current_time = now(); if token.expires_at < current_time then @@ -77,7 +82,7 @@ log("debug", "FAST token due for rotation (age: %d)", current_time - token.issued_at); rotation_needed = true; end - return true, username, hmac_f(token.secret, "Responder"..cb_data), rotation_needed; + return true, username, hmac_f(token.secret, "Responder"..(cb_data or "")), rotation_needed; end end if not tried_current_token then @@ -93,12 +98,19 @@ end end -function get_sasl_handler() +-- If FAST fails, we want to restore back to a non-FAST handler +local function _clean_clone_shim(self) + return self.nonfast_sasl_handler:clean_clone(); +end + +function get_sasl_handler(username, nonfast_sasl_handler) -- luacheck: ignore 212/username local token_auth_profile = { ht_sha_256 = new_token_tester(hash.hmac_sha256); }; local handler = sasl.new(module.host, token_auth_profile); handler.fast = true; + handler.nonfast_sasl_handler = nonfast_sasl_handler; + handler.clean_clone = _clean_clone_shim; return handler; end @@ -110,12 +122,14 @@ username = jid.node(event.stream.from); if not username then return; end end - local sasl_handler = get_sasl_handler(username); + local sasl_handler = get_sasl_handler(username, session.sasl_handler); if not sasl_handler then return; end sasl_handler.fast_auth = true; -- For informational purposes - -- Copy channel binding info from primary SASL handler - sasl_handler.profile.cb = session.sasl_handler.profile.cb; - sasl_handler.userdata = session.sasl_handler.userdata; + -- Copy channel binding info from primary SASL handler if it's compatible + if is_util_sasl(session.sasl_handler) then + sasl_handler.profile.cb = session.sasl_handler.profile.cb; + sasl_handler.userdata = session.sasl_handler.userdata; + end -- Store this handler, in case we later want to use it for authenticating session.fast_sasl_handler = sasl_handler; local fast = st.stanza("fast", { xmlns = xmlns_fast }); @@ -196,14 +210,17 @@ if not authc_username then return "failure", "malformed-request"; end - if not sasl_handler.profile.cb then - module:log("warn", "Attempt to use channel binding %s with SASL profile that does not support any channel binding (FAST: %s)", cb_name, sasl_handler.fast); - return "failure", "malformed-request"; - elseif not sasl_handler.profile.cb[cb_name] then - module:log("warn", "SASL profile does not support %s channel binding (FAST: %s)", cb_name, sasl_handler.fast); - return "failure", "malformed-request"; + local cb_data; + if cb_name then + if not sasl_handler.profile.cb then + module:log("warn", "Attempt to use channel binding %s with SASL profile that does not support any channel binding (FAST: %s)", cb_name, sasl_handler.fast); + return "failure", "malformed-request"; + elseif not sasl_handler.profile.cb[cb_name] then + module:log("warn", "SASL profile does not support %s channel binding (FAST: %s)", cb_name, sasl_handler.fast); + return "failure", "malformed-request"; + end + cb_data = sasl_handler.profile.cb[cb_name](sasl_handler) or ""; end - local cb_data = cb_name and sasl_handler.profile.cb[cb_name](sasl_handler) or ""; local ok, authz_username, response, rotation_needed = backend( mechanism_name, authc_username,
--- a/mod_sasl2_sm/README.md Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_sasl2_sm/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -5,13 +5,17 @@ rockspec: dependencies: - mod_sasl2 + - mod_sasl2_bind2 --- -Add support for inlining stream management negotiation into the SASL2 process. - -**Note: At the time of writing (November 2022), this module implements a -version of XEP-0198 that is still working its way through the XSF standards -process. For more information and current status, see [PR #1215](https://github.com/xsf/xeps/pull/1215).** +Add support for inlining stream management negotiation into the SASL2 process. (See [XEP-0388: Extensible SASL Profile]) This module depends on [mod_sasl2] and [mod_sasl2_bind2]. It exposes no configuration options. + +# Compatibility + + Prosody-Version Status + --------------- ---------------------- + trunk Works as of 2024-12-21 + 0.12 Does not work
--- a/mod_sasl2_sm/mod_sasl2_sm.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_sasl2_sm/mod_sasl2_sm.lua Tue Mar 18 00:31:36 2025 +0700 @@ -6,6 +6,7 @@ local xmlns_sm = "urn:xmpp:sm:3"; module:depends("sasl2"); +module:depends("sasl2_bind2"); -- Advertise what we can do
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_sasl_oauthbearer/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,12 @@ +--- +labels: +- 'Type-Auth' +summary: SASL OAuthBearer Mechanism +... + +Introduction +============ + +This module adds a new SASL mechanism OAUTHBEARER, as defined in [RFC-7628](https://tools.ietf.org/html/rfc7628). + +It's intended to be used together with the `mod_auth_oauthbearer.lua` module.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_sasl_ssdp/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,25 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'XEP-0474: SASL SCRAM Downgrade Protection' +... + +Introduction +============ + +This module implements the experimental XEP-0474: SASL SCRAM Downgrade +Protection. It provides an alternative downgrade protection mechanism to +client-side pinning which is currently the most common method of downgrade +protection. + +# Configuration + +There are no configuration options for this module, just load it as normal. + +# Compatibility + +For SASL2 (XEP-0388) clients, it is compatible with the mod_sasl2 community module. + +For clients using RFC 6120 SASL, it requires Prosody trunk 33e5edbd6a4a or +later. It is not compatible with Prosody 0.12 (it will load, but simply +won't do anything) for "legacy SASL".
--- a/mod_sasl_ssdp/mod_sasl_ssdp.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_sasl_ssdp/mod_sasl_ssdp.lua Tue Mar 18 00:31:36 2025 +0700 @@ -1,13 +1,23 @@ local array = require "util.array"; +local set = require "util.set"; local hashes = require "util.hashes"; local it = require "util.iterators"; local base64_enc = require "util.encodings".base64.encode; +-- *** The following code is copy-pasted from mod_saslauth/mod_sasl2, like requested by Zash *** +-- *** Please update, if you modify mod_saslauth or mod_sasl2! *** +local allow_unencrypted_plain_auth = module:get_option_boolean("allow_unencrypted_plain_auth", false) +local insecure_mechanisms = module:get_option_set("insecure_sasl_mechanisms", allow_unencrypted_plain_auth and {} or {"PLAIN", "LOGIN"}); +local disabled_mechanisms = module:get_option_set("disable_sasl_mechanisms", { "DIGEST-MD5" }); +-- *** End of copy-pasted code *** + local hash_functions = { ["SCRAM-SHA-1"] = hashes.sha1; ["SCRAM-SHA-1-PLUS"] = hashes.sha1; ["SCRAM-SHA-256"] = hashes.sha256; ["SCRAM-SHA-256-PLUS"] = hashes.sha256; + ["SCRAM-SHA-512"] = hashes.sha512; + ["SCRAM-SHA-512-PLUS"] = hashes.sha512; }; function add_ssdp_info(event) @@ -17,17 +27,34 @@ module:log("debug", "Not enabling SSDP for unsupported mechanism: %s", sasl_handler.selected); return; end - local mechanism_list = array.collect(it.keys(sasl_handler:mechanisms())):sort(); + + -- *** The following code is copy-pasted from mod_saslauth/mod_sasl2, like requested by Zash *** + -- *** Please update, if you modify mod_saslauth or mod_sasl2! *** + local usable_mechanisms = set.new(); + local available_mechanisms = sasl_handler:mechanisms() + for mechanism in pairs(available_mechanisms) do + if disabled_mechanisms:contains(mechanism) then + module:log("debug", "Not offering disabled mechanism %s", mechanism); + elseif not event.session.secure and insecure_mechanisms:contains(mechanism) then + module:log("debug", "Not offering mechanism %s on insecure connection", mechanism); + else + module:log("debug", "Offering mechanism %s", mechanism); + usable_mechanisms:add(mechanism); + end + end + -- *** End of copy-pasted code *** + + local mechanism_list = array.collect(usable_mechanisms):sort(); local cb = sasl_handler.profile.cb; local cb_list = cb and array.collect(it.keys(cb)):sort(); local ssdp_string; if cb_list then - ssdp_string = mechanism_list:concat(",").."|"..cb_list:concat(","); + ssdp_string = mechanism_list:concat("\30").."\31"..cb_list:concat("\30"); else - ssdp_string = mechanism_list:concat(","); + ssdp_string = mechanism_list:concat("\30"); end module:log("debug", "Calculated SSDP string: %s", ssdp_string); - event.message = event.message..",d="..base64_enc(hash(ssdp_string)); + event.message = event.message..",h="..base64_enc(hash(ssdp_string)); sasl_handler.state.server_first_message = event.message; end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_saslname/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,23 @@ +--- +labels: +- 'Stage-Stable' +- 'Type-Auth' +summary: 'XEP-0233: XMPP Server Registration for use with Kerberos V5' +... + +Introduction +============ + +This module implements a manual method for advertsing the Kerberos +principal name as per [XEP-0233]. It could be used in conjection with +a Kerberos authentication module. + +Configuration +============= + + sasl_hostname = "auth42.us.example.com" + +Compatibility +============= + +Prosody 0.7+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_seclabels/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,62 @@ +--- +labels: +- 'Stage-Alpha' +summary: Security Labels +... + +Introduction +============ + +This module implements [XEP-0258: Security Labels in XMPP], but not +actual policy enforcement. See for example [mod_firewall] for that. + +Configuration +============= + +As with all modules, you enable it by adding it to the modules\_enabled +list. + +These options exist: + + Name Description Default + ------------------------- ----------------------- ------------- + security\_catalog\_name Catalouge name "Default" + security\_catalog\_desc Catalouge description "My labels" + +You can then add your labels in a table called security\_labels. They +can be both orderd and unorderd, but ordered comes first. + +``` {.lua} +security_labels = { + { -- This label will come first + name = "Public", + label = true, -- This is a label, but without the actual label. + default = true -- This is the default label. + }, + { + name = "Private", + label = "PRIVATE", + color = "white", + bgcolor = "blue" + }, + Sensitive = { -- A Sub-selector + SECRET = { -- The index is used as name + label = true + }, + TOPSECRET = { -- The order of this and the above is not guaranteed. + color = "red", + bgcolor = "black", + } + } +} +``` + +Each label can have the following properties: + + Name Description Default + ---------------- --------------------------------------------------------- ---------------------------------------------------------- + name The name of the label. Used for selector. Required. + label The actual label, ie `<esssecuritylabel/>` Required, can be boolean for a empty label, or a string. + display The text shown as display marking. Defaults to the name + color, bgcolor The fore- and background color of the display marking None + default Boolean, true for the default. Only one may be default. false
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_secure_interfaces/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,42 @@ +--- +labels: +- 'Stage-Beta' +summary: 'Mark some network interfaces (e.g. loopback/LAN) as always secure' +... + +Introduction +============ + +Sometimes you might run clients without encryption on the same machine +or LAN as Prosody - and you want Prosody to treat them as secure (e.g. +allowing plaintext authentication) even though they are not encrypted. + +This module allows you to tell Prosody which of the current server's +interfaces (IP addresses) that you consider to be on secure networks. + +Configuration +============= + +Configuration is simple, just load the module like any other by adding +it to your modules\_enabled list: + + modules_enabled = { + ... + "secure_interfaces"; + ... + } + +Then set the list of secure interfaces (just make sure it is set in the +global section of your config file, and **not** under a VirtualHost or +Component): + + secure_interfaces = { "127.0.0.1", "::1", "192.168.1.54" } + +Compatibility +============= + + ------- --------- + 0.9 Works + 0.8 Unknown + trunk Works + ------- ---------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_server_contact_info/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,27 @@ +--- +labels: +- 'Stage-Alpha' +summary: Backported mod_server_contact_info for Prosody 0.12 +... + +## Overview + +In February 2024 we improved the internal API to allow multiple modules to +publish information about the server, this includes mod_pubsub_serverinfo. + +Although Prosody 0.12 comes with its own mod_server_contact_info, this version +uses the new API so that 0.12 users can hopefully use mod_pubsub_serverinfo +and other modules which use the new API. + +To use it, you must ensure that your Prosody 0.12 deployment is loading *both* +mod_server_contact_info **and** mod_server_info community modules. + +Configuration of contact addresses is the same, whatever version of the module +you use. See the official documentation on [Prosody's mod_server_contact_info](https://prosody.im/doc/modules/mod_server_contact_info) +page. + +## Compatibility + +This module should be compatible with Prosody 0.12, and will fail to load in +later versions (which already provide the same functionality without community +modules).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_server_contact_info/mod_server_contact_info.lua Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,54 @@ +-- XEP-0157: Contact Addresses for XMPP Services for Prosody +-- +-- Copyright (C) 2011-2018 Kim Alvefur +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +-- This module is backported from Prosody trunk for the benefit of +-- Prosody 0.12 deployments. The following line will ensure that it won't be +-- loaded in Prosody versions with built-in support for mod_server_info - +-- thus preferring the mod_server_contact_info shipped with Prosody instead. +--% conflicts: mod_server_info + + +local array = require "util.array"; +local it = require "util.iterators"; +local jid = require "util.jid"; +local url = require "socket.url"; + +module:depends("server_info"); + +-- Source: http://xmpp.org/registrar/formtypes.html#http:--jabber.org-network-serverinfo +local address_types = { + abuse = "abuse-addresses"; + admin = "admin-addresses"; + feedback = "feedback-addresses"; + sales = "sales-addresses"; + security = "security-addresses"; + status = "status-addresses"; + support = "support-addresses"; +}; + +-- JIDs of configured service admins are used as fallback +local admins = module:get_option_inherited_set("admins", {}); + +local contact_config = module:get_option("contact_info", { + admin = array.collect(admins / jid.prep / function(admin) return url.build({scheme = "xmpp"; path = admin}); end); +}); + +local fields = {}; + +for key, field_var in it.sorted_pairs(address_types) do + if contact_config[key] then + table.insert(fields, { + type = "list-multi"; + name = key; + var = field_var; + value = contact_config[key]; + }); + end +end + +module:add_item("server-info-fields", fields);
--- a/mod_server_info/mod_server_info.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_server_info/mod_server_info.lua Tue Mar 18 00:31:36 2025 +0700 @@ -3,7 +3,7 @@ -- newer Prosody versions, which include their own copy of the module. --% conflicts: mod_server_info -local dataforms = require "prosody.util.dataforms"; +local dataforms = require "util.dataforms"; local server_info_config = module:get_option("server_info", {}); local server_info_custom_fields = module:get_option_array("server_info_extensions");
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_server_status/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,37 @@ +--- +labels: +- 'Stage-Stable' +summary: Server status plugin +... + +Introduction +============ + +This module fetches the current status of configured hosts and/or stanza +statistics from +[mod\_stanza\_counter](http://code.google.com/p/prosody-modules/wiki/mod_stanza_counter#). +And outputs it in either XML or JSON format. + +Usage +===== + +Copy the file into prosody's module directory and place it into your +global's enabled modules. + +Configuration example: + + server_status_basepath = "/server-info/" + server_status_show_hosts = { "iwanttoshowifthishostisonline.com", "iwanttoshowifthishostisonline2.com" } + server_status_show_comps = { "muc.iwanttoshowifthishostisonline.com", "transport.iwanttoshowifthishostisonline.com" } + server_status_json = true + +By default the plugin's output is in XML, setting server\_status\_json +to "true" will turn it into JSON instead. if mod\_stanza\_counter isn't +loaded the plugin will require at least either +server\_status\_show\_hosts or server\_status\_show\_comps to be set. + +Info +==== + +- This is only compatible with 0.9 for older versions please look at + the 0.8-diverge branch.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_service_outage_status/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,19 @@ +This module allows advertising a machine-readable document were outages, +planned or otherwise, may be reported. + +See [XEP-0455: Service Outage Status] for further details, including +the format of the outage status document. + +```lua +modules_enabled = { + -- other modules + "service_outage_status", +} + +outage_status_urls = { + "https://uptime.example.net/status.json", +} +``` + +The outage status document should be hosted on a separate server to +ensure availability even if the XMPP server is unreachable.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_sift/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,32 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'XEP-0273: Stanza Interception and Filtering Technology' +... + +Introduction +============ + +[SIFT][XEP-0273] is a technology to allow clients to filter incoming +traffic on the server. This helps save bandwidth, etc. + +Compatibility +============= + + ----- ------- + 0.7 Works + ----- ------- + +Quirks +====== + +This implementation is a work in progress. + +- Stanzas to full JIDs get sifted correctly +- Stanzas to bare JIDs are currently allowed/disallowed for all + resources as a whole, and not for individual resources +- Presence is only sent to available resources, and probes are not + sent for unavailable reasources +- This module currently does not interact with offline messages + (filtered messages are dropped with an error reply) +- Not tested with privacy lists
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_slack_webhooks/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,91 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'Allow Slack integrations to work with Prosody MUCs' +... + +Introduction +============ + +This module provides a Slack-compatible "web hook" interface to Prosody MUCs. +Both "incoming" web hooks, which allow Slack integrations to post messages +to Prosody MUCs, and "outgoing" web hooks, which copy messages from Prosody +MUCs to Slack-style integrations by HTTP, are supported. This can also be +used, in conjunction with various Slack inter-namespace bridging tools, to +provide a bidirectional bridge between a Prosody-hosted XMPP MUC and a Slack +channel. + +If you're looking for a more versatile module, consider [mod_rest] + +Usage +===== + +First copy the module to the prosody plugins directory. + +Then add "slack\_webhooks" to your modules\_enabled list: + +``` {.lua} +Component "conference.example.org" "muc" +modules_enabled = { + "slack_webhooks", +} +``` + +Configuration +============= + +The normal use for this module is to provide an incoming webhook to allow +integrations to post to prosody MUCs: + +``` {.lua} +incoming_webhook_path = "/msg/DFSDF56587658765NBDSA" +incoming_webhook_default_nick = "Bot" -- Unless otherwise specified, posts as "Bot" +``` + +This allows Slack-style JSON messages posted to http://conference.example.org/msg/DFSDF56587658765NBDSA/chat to appear in the MUC chat@conference.example.org. A username field in the message is honored as the nick attached to the message; if no username is specified, the message will use the value of default_from_nick. +Specifying a string of random gibberish in the URL is important to prevent spam. + +In addition, there is a second operating mode equivalent to Slack's outgoing +webhooks. This allows all messages from a set of specified chat rooms to be +routed to an external server over HTTP in the format used by Slack's +outgoing webhooks. +``` {.lua} +outgoing_webhook_routing = { + -- Send all messages from chat@conference.example.org to + -- a web server. + ["chat"] = "http://example.org/cgi-bin/messagedest", +} +``` + +Known Issues +============ + +The users from whom messages delivered from integrations are apparently +delivered are not, in general, members of the MUC. Other prosody modules +that try to look up information about the users who most messages, mostly +logging modules, may become confused and fail (clients all work fine because +replayed history also can come from non-present users). In at least some cases, +such as with mod_muc_mam, this can be fixed by hiding the JIDs of the +participants in the room configuration. + +There are a few smaller UI issues: + +* If an integration posts with the same username as a room member, there is + no indication (like Slack's [bot] suffix) that the message is not from that + room member. +* It is not currently possible to prevent posting to some MUCs (this is + also true of Slack). +* It should be possible to set the webhook configuration for a room in the + room configuration rather than statically in Prosody's configuration file. + +Compatibility +============= + + Prosody Version Status + ----------------- ----------- + trunk[^1] Works + 0.12 Works + ----------------- ----------- + +[^1]: as of 2024-10-22 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_smacks/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,8 @@ +--- +summary: 'XEP-0198: Reliability and fast reconnects for XMPP' +superseded_by: mod_smacks +labels: +- 'Stage-Obsolete' +... + +Since Prosody 0.12, this module is [included in Prosody](https://prosody.im/doc/modules/mod_smacks).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_smacks_noerror/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,12 @@ +--- +labels: +- 'Stage-Alpha' +summary: Module deprecated, just use mod_smacks and mod_nooffline_noerror +... + +Introduction +============ + +This module is deprecated and superseded by mod_smacks. +If you explicitly disabled mod_offline you need the new module +mod_nooffline_noerror to regain all features of this deprecated module.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_smacks_offline/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,11 @@ +--- +superseded_by: mod_smacks +labels: +- 'Stage-Obsolete' +summary: Module deprecated, just use mod_smacks +... + +Introduction +============ + +This module is deprecated and superseded by [mod_smacks](https://prosody.im/doc/modules/mod_smacks).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_sms_clickatell/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,67 @@ +--- +labels: +- 'Stage-Alpha' +summary: XMPP to SMS gateway using the Clickatell API +... + +Introduction +============ + +This module provides and SMS gateway component which uses the Clickatell +HTTP API to deliver text messages. See clickatell.com for details on +their services. Note that at present, this is entirely one way: replies +will either go nowhere or as sms to the source number you specify. + +Configuration +============= + +In prosody.cfg.lua: + + Component "sms.example.com" "sms_clickatell" + sms_message_prefix = "some text" + +The sms\_message\_prefix is a piece of text you want prefixing to all +messages sent through the gateway. For example, I use the prefix +"`[Via XMPP]` " to indicate to recipients that I've sent the message via +the internet rather than the mobile network. Since my primary use case +for this component is to be able to send messages to people only +reachable via mobile when I myself only have internet access and no +mobile reception, this option allows me to give a hint to my recipients +that any reply they send may not reach me in a timely manner. + +Usage +===== + +Once you've installed and configured, you should be able to use service +discovery in your XMPP client to find the component service. Once found, +you need to register with the service, supplying your Clickatell +username, password, API ID, and a source number for your text messages. + +The source number is the mobile number you want messages to 'originate' +from i.e. where your recipients see messages coming from. The number +should be in international format without leading plus sign, or you can +use some other format if clickatell supports it. + +To send text messages to a target number, you need to add a contact in +the form of `[number]@sms.example.com`, where `[number]` is the mobile +number of the recipient, in international format without leading plus +sign, and sms.example.com is the name for the component you configured +above. For example: + +447999000001@sms.yourdomain.com + +You should then be able to send messages to this contact which get sent +as text messages to the number by the component. + +Compatibility +============= + + ----- ------- + 0.7 Works + ----- ------- + +Todo +==== + +- Refactor to create a framework for multiple sms gateway back ends, + and split Clickatell specific code in to its own back end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_sms_free/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,38 @@ +--- +labels: +- 'Stage-Alpha' +summary: XMPP to SMS gateway using the HTTP API provided by mobile.free.fr +... + +Introduction +============ + +This module sends an SMS to your phone when you receive a message on XMPP when +your status is xa or disconnected. + +Note that it doesn’t support sending SMS to anyone else than yourself, in that +it is quite different from other gateways. + +Configuration +============= + +In prosody.cfg.lua: + + modules_enabled = { + "sms_free", + } + +Usage +===== + +Every user who wants to use this gateway can issue an ad-hoc command to their +server, then follow the instructions and start receiving messages by SMS when +they are unavailable or xa. + +Compatibility +============= + + ----- ------------------------------------ + trunk Works + 0.11 Does not work (SNI support required) + ----- ------------------------------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_spam_reporting/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,10 @@ +--- +labels: +- 'Stage-Beta' +summary: 'XEP-0377: Spam Reporting' +--- + +This module is a very basic implementation of [XEP-0377: Spam Reporting]. + +When someone reports spam or abuse, a line about this is logged and an +event is fired so that other modules can act on the report.
--- a/mod_spam_reporting/mod_spam_reporting.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_spam_reporting/mod_spam_reporting.lua Tue Mar 18 00:31:36 2025 +0700 @@ -5,6 +5,8 @@ local jid_prep = require "util.jid".prep; +local count_report = module:metric("counter", "received", "reports", "Number of spam and abuse reports submitted by users.", { "report_type" }); + module:depends("blocklist"); module:add_feature("urn:xmpp:reporting:0"); @@ -33,6 +35,7 @@ end if report_type then + count_report:with_labels(report_type):add(1); module:log("warn", "Received report of %s from JID '%s', %s", report_type, jid, reason or "no reason given"); module:fire_event(module.name.."/"..report_type.."-report", { origin = event.origin, stanza = event.stanza, jid = jid,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_srvinjection/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,63 @@ +--- +labels: +- 'Stage-Beta' +summary: Manually specify SRV records +... + +Introduction +============ + +This Prosody plugin lets you manually override SRV records used for a +remote host. + +Usage +===== + +Simply add `"srvinjection"` to your `modules_enabled` list to enable. +Then add the `srvinjection` option to the global section. + +Configuration +============= + +The `srvinjection` option can be used as follows: + + srvinjection = { + ["example.com"] = {"localhost", 5000}; + ["jabber.org"] = {"localhost", 5001}; + }; + +The format for individual items is +`["remote-hostname"] = {"srv-hostname", srv-port};`. + +The special remote hostname `"*"` can be used as a wildcard: + + srvinjection = { ["*"] = {"xmpp-server.l.google.com", 5269} } -- Use Google's XMPP server for all hostnames + +Reloading +========= + +The module can be reloaded via the telnet console. Edit the config file +to make any updates. + +You can reload the configuration from disk: + + config:reload() + +And then reload the module to apply the configuration changes: + + module:reload("srvinjection", "*") + +Compatibility +============= + + ----- ------- + 0.8 Works + 0.7 Works + 0.6 Works + ----- ------- + +How it works +============ + +The module replaces the `lookup` function of the `net.adns` module with +its own. The original is set back when the module is unloaded.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_sslv3_warn/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,9 @@ +--- +summary: 'Warn clients that try sslv3' +labels: +- 'Stage-Obsolete' +... + +::: {.alert .alert-warning} +Prosody does not allow any sslv3 connection anymore, making this module obsolete. +:::
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_stanza_counter/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,41 @@ +--- +labels: +- 'Stage-Stable' +summary: Simple incoming and outgoing stanza counter +... + +Introduction +============ + +This module counts incoming and outgoing stanzas from when the instance +started, and makes the data available to other modules by creating a +global prosody. object + +Details +======= + +The counter module is "stanza\_counter", the example output module is +stanza\_counter\_http. + +Usage +===== + +Copy both files into prosody's module directory and place 'em into your +enabled modules (stanza\_counter\_http requires to be loaded into the +global section!) + +Config for stanza\_counter\_http: + +``` {.lua} + +stanza_counter_basepath = "/counter-path-custom/" +``` + +Info +==== + +- As of now to count components stanzas, it needs to be manually + loaded (inserted into modules\_enabled of the components' sections) + on these. +- This version isn't compatible with previous versions of prosody + (looks at 0.8-diverge branch for olders).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_stanzadebug/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,25 @@ +--- +summary: Extra verbose stanza logging +labels: +- Stage-Merged +- Type-Logging +... + +::: {.alert .alert-info} +This module has been merged into Prosody as [mod_stanza_debug][doc:modules:mod_stanza_debug]. +::: + +Summary +======= + +This module logs the full stanzas that are sent and received into debug +logs, for debugging purposes. + + +Compatibility +============= + +Prosody Version Status +--------------- ------ +trunk Merged +0.12 Merged
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_statistics/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,18 @@ +--- +labels: +- Stage-Broken +- Statistics +summary: A rough 'prosodyctl mod_statistics top' +... + +::: {.alert .alert-warning} +This module was an early protoype and is [not compatible with prosody anymore.](https://issues.prosody.im/647) +::: + +# Compatibility + + version status + -------- ------- + trunk broken + 0.12 broken + 0.11 works
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_statistics_statsman/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,27 @@ +A module providing a streaming statistics interface like +[mod_statistics] but based on the new [statistics API][doc:statistics] +introduced in Prosody 0.10. + +# Usage + +To use, enable the built-in statistics like so: + +```lua +statistics = "internal" +``` + +Then, in `modules_enabled`, replace `"statistics"` with +`"statistics_statsman"` and the various `"statistics_<something>"` +with equivalent `"measure_<something>"`. + + +# Compatibility + + ------- -------------------- + trunk Does not work [^1] + 0.11 Should work + 0.10 Should work + ------- -------------------- + +[^1]: not after + [5f15ab7c6ae5](https://hg.prosody.im/trunk/rev/5f15ab7c6ae5)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_stats39/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,74 @@ +--- +labels: +- 'Statistics' +... + +This module provides **public** access to Prosodys +[internal statistics][doc:statistics] trough the +[XEP-0039: Statistics Gathering] protocol. This is a simple protocol +that returns triplets of name, unit and value for each know statistic +collected by Prosody. The names used are the internal names assigned by +modules or statsmanager, names from the registry are **not** used. + +# Configuration + +Enabled as usual by adding to [`modules_enabled`][doc:modules_enabled]: + +```lua +-- Enable Prosodys internal statistics gathering +statistics = "internal" + +-- and enable the module +modules_enabled = { + -- other modules + "stats39"; +} +``` + +# Usage + + +## Example + +Statistics can be queried from the XML console of clients that have one: + +```xml +C: +<iq type="get" to="example.com" id="dTMERjt5"> + <query xmlns="http://jabber.org/protocol/stats"/> +</iq> + +S: +<iq type="result" to="example.com" id="dTMERjt5"> + <query xmlns="http://jabber.org/protocol/stats"> + <stat name="cpu.clock:amount" value="0.212131"/> + <stat name="cpu.percent:amount" value="0"/> + <stat name="memory.allocated:amount" value="8.30259e+06"/> + <stat name="memory.allocated_mmap:amount" value="401408"/> + <stat name="memory.lua:amount" value="6.21347e+06"/> + <stat name="memory.returnable:amount" value="13872"/> + <stat name="memory.rss:amount" value="2.03858e+07"/> + <stat name="memory.total:amount" value="6.53885e+07"/> + <stat name="memory.unused:amount" value="14864"/> + <stat name="memory.used:amount" value="8.28773e+06"/> + <stat name="/*/mod_c2s/connections:amount" value="0"/> + <stat name="/*/mod_c2s/ipv6:amount" value="0"/> + <stat name="/*/mod_s2s/connections:amount" value="0"/> + <stat name="/*/mod_s2s/ipv6:amount" value="0"/> + <stat name="stats.collection:duration" unit="seconds" value="0.000125647"/> + <stat name="stats.processing:duration" unit="seconds" value="0"/> + </query> +</iq> +``` + +# Compatibly + + Prosody version Works + ----------------- ------- + 0.9.x No + 0.10.x Yes + 0.11.x Yes + Trunk[^1] No + +[^1]: Does not work with trunk since the [change to + OpenMetrics](https://hg.prosody.im/trunk/rev/5f15ab7c6ae5)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_storage_appendmap/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,16 @@ +--- +labels: +- 'Stage-Alpha' +- 'Type-Storage' +summary: Experimental map store optimized for small incremental changes +... + +This is an experimental storage driver where changed data is appended. +Data is simply written as `key = value` pairs to the end of the file. +This allows changes to individual keys to be written without needing to +write out the entire object again, but reads would grow gradually larger +as it still needs to read old overwritten keys. This may be suitable for +e.g. rosters where individual contacts are changed at a time. In theory, +this could also allow rolling back changes. + +Requires 0.10
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_storage_ejabberdsql_readonly/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,36 @@ +--- +labels: +- 'Type-Storage' +- 'Stage-Alpha' +summary: Ejabberd SQL Read-only Storage Module +... + +Introduction +============ + +This is a storage backend using Ejabberd’s SQL backend. It depends on +[LuaDBI][doc:depends#luadbi] + +This module only works in read-only, and was made to be used by +[mod_migrate] to migrate from Ejabberd’s SQL backend. + +Configuration +============= + +Copy the module to the prosody modules/plugins directory. + +In Prosody's configuration file, set: + + storage = "ejabberdsql_readonly" + +EjabberdSQL options are the same as the [SQL +ones][doc:modules:mod_storage_sql#usage]. + +Compatibility +============= + + ------- --------------------------- + trunk Works + 0.10 Untested, but should work + 0.9 Does not work + ------- ---------------------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_storage_gdbm/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,31 @@ +--- +labels: +- 'Stage-Beta' +- 'Type-Storage' +- ArchiveStorage +summary: 'Lua-GDBM storage' +... + +Introduction +============ + +This is a storage module using GNU DBM as backend. It supports archives. + +Dependencies +============ + +[lgdbm](http://webserver2.tecgraf.puc-rio.br/~lhf/ftp/lua/#lgdbm) is +required to access gdbm databases. + +Details +======= + +Refer to [Prosodys data storage +documentation](https://prosody.im/doc/storage). + +Compatibility +============= + + -------- ------------- + \>=0.9 Should work + -------- -------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_storage_ldap/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,34 @@ +--- +labels: +- 'Type-Storage' +summary: 'LDAP storage for rosters, groups, and vcards' +... + +Introduction +============ + +See [mod\_lib\_ldap](mod_lib_ldap.html) for more information. + +Installation +============ + +You must install [mod\_lib\_ldap](mod_lib_ldap.html) to use this module. +After that, you need only copy mod\_storage\_ldap.lua and +ldap/vcard.lib.lua to your Prosody installation's plugins directory. +Make sure vcard.lib.lua is installed under plugins/ldap/. + +Configuration +============= + +In addition to the configuration that [mod\_lib\_ldap](mod_lib_ldap.html) +itself requires, this plugin also requires the following fields in the +ldap section: + +- user.namefield +- groups.memberfield +- groups.namefield +- groups.basedn +- vcard\_format (optional) + +See the README.html distributed with [mod\_lib\_ldap](mod_lib_ldap.html) for +details.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_storage_lmdb/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,31 @@ +--- +labels: +- 'Stage-Beta' +- 'Type-Storage' +summary: 'Lightning Memory-Mapped Database storage' +... + +Introduction +============ + +This is a storage module using OpenLDAP Lightning Memory-Mapped Database +as backend. + +Dependencies +============ + +[lightningdbm](https://github.com/shmul/lightningdbm) is required to +access LMDB databases. + +Details +======= + +Refer to [Prosodys data storage +documentation](https://prosody.im/doc/storage). + +Compatibility +============= + + -------- ------------- + \>=0.9 Should work + -------- -------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_storage_memory/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,36 @@ +--- +labels: +- 'Stage-Merged' +- 'Type-Storage' +- ArchiveStorage +summary: 'Simple memory-only storage module' +... + + +::: {.alert .alert-warning} +This module has been merged into +[prosodys storage backend as "memory"][doc:storage] and is therefore obsolete. +You will be redirected shortly. +::: + +Introduction +============ + +This module acts as a normal storage module for Prosody, but saves all +data in memory only. All data is lost when the server stops. This makes +it useful for testing, or certain specialized applications. + +Details +======= + +Because the accounts store will always begin empty, it is mostly useful +combined with an authentication plugin which doesn't use Prosody's +storage API, or with [mod\_auth\_any](mod_auth_any.html), or you can +create user accounts manually each time the server starts. + +Compatibility +============= + + ----- ------- + 0.9 Works + ----- -------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_storage_metronome_readonly/README.markdown Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,49 @@ +--- +labels: +- 'Type-Storage' +- 'Stage-Alpha' +summary: Metronome Read-only Storage Module +... + +Introduction +============ + +This is a storage backend using Metronome Lua storage. + +This module only works in read-only, and was made to be used by [mod\_migrate] +to migrate from Metronome’s storage. + +So far it has only been tested migrating to sqlite, because +mod\_storage\_internal relies on the same `data_path` variable as this module, +and thus would overwrite the files we just read. + +I’ve also only tested it on a dump from a Metronome configured by Yunohost, so +using LDAP and such for user accounts, I don’t yet know how to migrate from +different Metronome account storages. + +Configuration +============= + +Copy the module to the prosody modules/plugins directory. + +In Prosody's configuration file, set: + + storage = "metronome_readonly" + data_path = "/var/lib/metronome" + +To run the actual migration, run these two commands (replace `<host>` with the domain you want to migrate): + + prosodyctl mod_migrate <host> roster,vcard,private,cloud_notify,pep,pep_data-archive,offline-archive,archive-archive,uploads-archive sql + prosodyctl mod_migrate muc.<host> config,persistent,vcard_muc,muc_log-archive sql + +It will create a file in `/var/lib/metronome/prosody.sqlite`, after which you +can change your configuration file to point to it, or alternatively you can +perform a second migration to the internal storage if you prefer that. + +Compatibility +============= + + ------------------------ -------- + trunk (as of 2025-01-10) Works + 0.12 Untested + ------------------------ --------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_storage_metronome_readonly/mod_storage_metronome_readonly.lua Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,457 @@ +local datamanager = require "prosody.core.storagemanager".olddm; +local datetime = require "prosody.util.datetime"; +local st = require "prosody.util.stanza"; +local now = require "prosody.util.time".now; +local gen_id = require "prosody.util.id".medium; +local set = require "prosody.util.set"; +local envloadfile = require"prosody.util.envload".envloadfile; +local dir = require "lfs".dir; + +local host = module.host; + +local archive_item_limit = module:get_option_integer("storage_archive_item_limit", 10000, 0); + +-- Metronome doesn’t store the item publish time, so fallback to the migration time. +local time_now = math.floor(now()); + +local function encode (s) + return s and (s:gsub("%W", function (c) return string.format("%%%02x", c:byte()); end)); +end + +local file = io.open("/etc/mime.types"); +local mimes = {}; +while true do + local line = file:read("*l"); + if not line then + break; + end + if line ~= "" then + local first_char = line:sub(1, 1); + if first_char ~= "#" then + --local line:match("(%S+)%s+")); + local match = line:gmatch("%S+"); + local mime = match(); + for ext in match do + mimes[ext] = mime; + end + end + end +end +file:close(); + +local driver = {}; + +function driver:open(store, typ) + local mt = self[typ or "keyval"] + if not mt then + return nil, "unsupported-store"; + end + return setmetatable({ store = store, type = typ }, mt); +end + +function driver:stores(username) -- luacheck: ignore 212/self + if username == true then + local nodes = set.new(); + for user in datamanager.users(host, "pep") do + local data = datamanager.load(user, host, "pep"); + + for _, node in ipairs(data["nodes"]) do + nodes:add("pep_" .. node); + end + end + return function() + -- luacheck: ignore 512 + for node in nodes do + nodes:remove(node); + return node; + end + end; + end +end + +function driver:purge(user) -- luacheck: ignore 212/self user + return nil, "unsupported-store"; +end + +local keyval = { }; +driver.keyval = { __index = keyval }; + +function keyval:get(user) + if self.store == "pep" then + local ret = datamanager.load(user, host, self.store); + local nodes = ret["nodes"]; + local result = {}; + + local pep_base_path = datamanager.getpath(user, host, self.store):sub(1, -5); + + for _, node in ipairs(nodes) do + local path = ("%s/%s.dat"):format(pep_base_path, encode(node)); + local get_data = envloadfile(path, {}); + if not get_data then + module:log("error", "Failed to load metronome storage"); + return nil, "Error reading storage"; + end + local success, data = pcall(get_data); + if not success then + module:log("error", "Unable to load metronome storage"); + return nil, "Error reading storage"; + end + local new_node = {}; + new_node["name"] = node; + new_node["subscribers"] = data["subscribers"]; + new_node["affiliations"] = data["affiliations"]; + new_node["config"] = data["config"]; + result[node] = new_node; + end + return result; + elseif self.store == "cloud_notify" then + local ret = datamanager.load(user, host, "push"); + local result = {}; + for jid, data in pairs(ret) do + local secret = data["secret"]; + for node in pairs(data["nodes"]) do + -- TODO: Does Metronome store more info than that? + local options; + if secret then + options = st.preserialize(st.stanza("x", { xmlns = "jabber:x:data", type = "submit" }) + :tag("field", { var = "FORM_TYPE" }) + :text_tag("value", "http://jabber.org/protocol/pubsub#publish-options") + :up() + :tag("field", { var = "secret" }) + :text_tag("value", secret)); + end + result[jid.."<"..node] = { + jid = jid, + node = node, + options = options, + }; + end + end + return result; + elseif self.store == "roster" then + return datamanager.load(user, host, self.store); + elseif self.store == "vcard" then + return datamanager.load(user, host, self.store); + elseif self.store == "private" then + return datamanager.load(user, host, self.store); + + -- After that, handle MUC specific stuff, not tested yet whatsoever. + elseif self.store == "persistent" then + return datamanager.load(user, host, self.store); + elseif self.store == "config" then + return datamanager.load(user, host, self.store); + elseif self.store == "vcard_muc" then + local data = datamanager.load(user, host, "room_icons"); + return data and data["photo"]; + else + return nil, "unsupported-store"; + end +end + +function keyval:set(user, data) -- luacheck: ignore 212/self user data + return nil, "unsupported-store"; +end + +function keyval:users() + local store; + if self.store == "vcard_muc" then + store = "room_icons"; + elseif self.store == "cloud_notify" then + store = "push"; + else + store = self.store; + end + return datamanager.users(host, store, self.type); +end + +local function parse_logs(logs, jid) + local iter = ipairs(logs); + local i = 0; + local message; + return function() + i, message = iter(logs, i); + if not message then + return; + end + + local with; + local bare_to = message["bare_to"]; + local bare_from = message["bare_from"]; + if jid == bare_to then + -- received + with = bare_from; + else + -- sent + with = bare_to; + end + + local to = message["to"]; + local from = message["from"]; + local id = message["id"]; + local type = message["type"]; + + local key = message["uid"]; + local when = message["timestamp"]; + local item = st.message({ to = to, from = from, id = id, type = type }, message["body"]); + if message["tags"] then + for _, tag in ipairs(message["tags"]) do + setmetatable(tag, st.stanza_mt); + item:add_direct_child(tag); + end + end + if message["marker"] then + item:tag(message["marker"], { xmlns = "urn:xmpp:chat-markers:0", id = message["marker_id"] }); + end + return key, item, when, with; + end; +end + +local archive = {}; +driver.archive = { __index = archive }; + +archive.caps = { + total = true; + quota = archive_item_limit; + full_id_range = true; + ids = true; +}; + +function archive:append(username, key, value, when, with) -- luacheck: ignore 212/self username key value when with + return nil, "unsupported-store"; +end + +function archive:find(username, query) -- luacheck: ignore 212/self query + if self.store == "archive" then + local jid = username.."@"..host; + local data = datamanager.load(username, host, "archiving"); + return parse_logs(data["logs"], jid); + + elseif self.store:sub(1, 4) == "pep_" then + local node = self.store:sub(5); + + local pep_base_path = datamanager.getpath(username, host, "pep"):sub(1, -5); + local path = ("%s/%s.dat"):format(pep_base_path, encode(node)); + local get_data = envloadfile(path, {}); + if not get_data then + module:log("debug", "Failed to load metronome storage"); + return {}; + end + local success, data = pcall(get_data); + if not success then + module:log("error", "Unable to load metronome storage"); + return nil, "Error reading storage"; + end + + local iter = pairs(data["data"]); + local key = nil; + local payload; + return function() + key, payload = iter(data["data"], key); + if not key then + return; + end + local item = st.deserialize(payload[1]); + local with = data["data_author"][key]; + return key, item, time_now, with; + end; + + elseif self.store == "offline" then + -- This is mostly copy/pasted from mod_storage_internal. + local list, err = datamanager.list_open(username, host, self.store); + if not list then + if err then + return list, err; + end + return function() + end; + end + + local i = 0; + local iter = function() + i = i + 1; + return list[i]; + end + + return function() + local item = iter(); + if item == nil then + if list.close then + list:close(); + end + return + end + local key = gen_id(); + local when = item.attr and datetime.parse(item.attr.stamp); + local with = ""; + item.key, item.when, item.with = nil, nil, nil; + item.attr.stamp = nil; + -- COMPAT Stored data may still contain legacy XEP-0091 timestamp + item.attr.stamp_legacy = nil; + item = st.deserialize(item); + return key, item, when, with; + end + + elseif self.store == "uploads" then + local list = {}; + + for user in datamanager.users(host, "http_upload", "list") do + local data, err = datamanager.list_open(user, host, "http_upload"); + if not data then + if err then + return data, err; + end + return function() + end; + end + + for _, stuff in ipairs(data) do + local key = stuff.dir; + local size = tostring(stuff.size); + local time = stuff.time; + local filename = stuff.filename; + local ext = filename:match(".*%.(%S+)"):lower(); + local mime = mimes[ext] or "application/octet-stream"; + local stanza = st.stanza("request", { xmlns = "urn:xmpp:http:upload:0", size = size, ["content-type"] = mime, filename = filename }) + list[key] = {user.."@"..host, time, stanza}; + end + end + + local iter = pairs(list); + local key = nil; + local payload; + return function() + key, payload = iter(list, key); + if not key then + return; + end + local with = payload[1]; + local when = payload[2]; + local stanza = payload[3]; + return key, stanza, when, with; + end; + + elseif self.store == "muc_log" then + local base_path = datamanager.getpath("", host, "stanza_log"):sub(1, -5); + local days = {}; + for date in dir(base_path) do + if date ~= "." and date ~= ".." then + table.insert(days, date); + end + end + table.sort(days); + local list = {}; + for _, date in ipairs(days) do + local path = base_path..date.."/"..encode(username)..".dat"; + local get_data = envloadfile(path, {}); + if get_data then + local success, data = pcall(get_data); + if not success then + module:log("error", "Unable to load metronome storage"); + return nil, "Error reading storage"; + end + for key, item, when in parse_logs(data) do + table.insert(list, {key, item, when}); + end + end + end + + local i = 0; + local iter = function() + i = i + 1; + return list[i]; + end + + return function() + local item = iter(); + if item == nil then + if list.close then + list:close(); + end + return + end + return item[1], item[2], item[3], "message<groupchat" + end + + else + return nil, "unsupported-store"; + end +end + +function archive:get(username, wanted_key) + local iter, err = self:find(username, { key = wanted_key }) + if not iter then return iter, err; end + for key, stanza, when, with in iter do + if key == wanted_key then + return stanza, when, with; + end + end + return nil, "item-not-found"; +end + +function archive:set(username, key, new_value, new_when, new_with) -- luacheck: ignore 212/self username key new_value new_when new_with + return nil, "unsupported-store"; +end + +function archive:dates(username) -- luacheck: ignore 212/self username + return nil, "unsupported-store"; +end + +function archive:summary(username, query) -- luacheck: ignore 212/self username query + return nil, "unsupported-store"; +end + +function archive:users() + if self.store == "archive" then + return datamanager.users(host, "archiving"); + elseif self.store:sub(1, 4) == "pep_" then + local wanted_node = self.store:sub(5); + local iter, tbl = datamanager.users(host, "pep"); + return function() + while true do + local user = iter(tbl); + if not user then + return; + end + local data = datamanager.load(user, host, "pep"); + for _, node in ipairs(data["nodes"]) do + if node == wanted_node then + return user; + end + end + end + end; + elseif self.store == "offline" then + return datamanager.users(host, self.store, "list"); + elseif self.store == "uploads" then + local done = false; + return function() + if not done then + done = true; + return ""; + end + end; + elseif self.store == "muc_log" then + local iter, tbl = pairs(datamanager.load(nil, host, "persistent")); + local jid = nil; + return function() + jid = iter(tbl, jid); + if not jid then + return; + end + local user = jid:gsub("@.*", ""); + return user; + end; + else + return nil, "unsupported-store"; + end +end + +function archive:trim(username, to_when) -- luacheck: ignore 212/self username to_when + return nil, "unsupported-store"; +end + +function archive:delete(username, query) -- luacheck: ignore 212/self username query + return nil, "unsupported-store"; +end + +module:provides("storage", driver);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_storage_mongodb/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,41 @@ +--- +labels: +- 'Type-Storage' +- 'Stage-Alpha' +summary: MongoDB Storage Module +... + +Introduction +============ + +This is a storage backend that uses MongoDB. Depends on [luamongo +bindings](https://github.com/mwild1/luamongo) + +This module is not under active development and has a number of issues +related to limitations in MongoDB. It is not suitable for production +use. + +Configuration +============= + +Copy the module to the prosody modules/plugins directory. + +In Prosody's configuration file, set: + + storage = "mongodb" + +MongoDB options are: + + Name Description + ------------ ------------------------------------------------------------------- + server hostname:port + username your username for the given database + password your password for the given database (either raw or pre-digested) + is\_digest whether the password field has been pre-digested + +Compatibility +============= + + ------- --------------------------- + trunk Untested, but should work + ------- ---------------------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_storage_muc_log/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,43 @@ +--- +labels: +- 'Stage-Alpha' +- ArchiveStorage +summary: 'Storage module using mod\_muc\_log data with new stanza archive API' +--- + +Introduction +============ + +[mod\_muc\_log] provided logging of chatrooms running on the server to +Prosody's data store. This module gives access to this data using the +0.10+ stanza archive API, allowing legacy log data to be used with +[mod\_mam\_muc] and [mod\_http\_muc\_log]. + +Details +======= + +Replace [mod\_muc\_log] and [mod\_muc\_log\_http] in your config +with + +``` {.lua} +Component "conference.example.org" "muc" +modules_enabled = { + -- "muc_log"; -- functionality replaced by mod_mam_muc + mod_storage_muc_log + "mam_muc"; -- Does logging to storage backend configured below + + -- "muc_log_http"; -- Replaced by the mod_http_muc_log + "http_muc_log"; +} +storage = { + muc_log = "muc_log"; +} +``` + +Compatibility +============= + + version status + --------- --------------- + 0.9 unknown + 0.10 works + 0.11 does not work
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_storage_muconference_readonly/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,42 @@ +--- +labels: +- 'Type-Storage' +- 'Stage-Alpha' +summary: MU-Conference SQL Read-only Storage Module +... + +Introduction +============ + +This is a storage backend using MU-Conference’s SQL storage. It depends +on [LuaDBI][doc:depends#luadbi] + +This module only works in read-only, and was made to be used by +[mod_migrate] to migrate from MU-Conference’s SQL storage. + +You may need to convert your 'rooms' and 'rooms\_lists' tables to +utf8mb4 before running that script, in order not to end up with +mojibake. Note that MySQL doesn’t support having more than +191 characters in the jid field in this case, so you may have to change +the table schema as well. + +Configuration +============= + +Copy the module to the prosody modules/plugins directory. + +In Prosody's configuration file, set: + + storage = "muconference_readonly" + +MUConferenceSQL options are the same as the [SQL +ones][doc:modules:mod_storage_sql#usage]. + +Compatibility +============= + + ------- --------------------------- + trunk Works + 0.10 Untested, but should work + 0.9 Does not work + ------- ---------------------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_storage_multi/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,35 @@ +--- +summary: Multi-backend storage module (WIP) +labels: +- NeedDocs +- Stage-Alpha +... + +Introduction +============ + +This module attemtps to provide a storage driver that is really multiple +storage drivers. This could be used for storage error tolerance or +caching of data in a faster storage driver. + +Configuration +============= + +An example: + +``` {.lua} +storage = "multi" +storage_multi_policy = "all" +storage_multi = { + "memory", + "internal", + "sql" +} +``` + +Here data would be first read from or written to [mod\_storage\_memory], +then internal storage, then SQL storage. For reads, the first successful +read will be used. For writes, it depends on the `storage_multi_policy` +option. If set to `"all"`, then all storage backends must report success +for the write to be considered successful. Other options are `"one"` and +`"majority"`.
--- a/mod_storage_s3/README.md Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_storage_s3/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -9,6 +9,10 @@ is not yet complete. For now, this module is primarily suited for testing and finding areas where async work is incomplete. ::: +::: {.alert .alert-danger} +The data layout in S3 is not final and may change at any point in incompatible ways. +::: + This module provides storage in Amazon S3 compatible things. It has been tested primarily with MinIO. ``` lua
--- a/mod_storage_s3/mod_storage_s3.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_storage_s3/mod_storage_s3.lua Tue Mar 18 00:31:36 2025 +0700 @@ -114,7 +114,7 @@ if not mt then return nil, "unsupported-store"; end - local httpclient = http.new({ connection_pooling = true }); + local httpclient = http.default:new({ connection_pooling = true }); httpclient.events.add_handler("pre-request", aws_auth); return setmetatable({ store = store; bucket = bucket; type = typ; http = httpclient }, mt); end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_storage_xmlarchive/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,108 @@ +--- +labels: +- 'Stage-Beta' +- 'Type-Storage' +- ArchiveStorage +summary: XML file based archive storage +--- + +Introduction +============ + +This module implements stanza archives using files, similar to the +default "internal" storage. Unlike "internal", it saves messages in two +files per day (and per user), one containing metadata and one containing +the actual messages in XML format (hence the name). + +Splitting data per day improves performance for larger archives as it +does not have to look through data from other days. + +Configuration +============= + +To use this with [mod\_mam] add this to your config: + +``` lua +storage = { + archive = "xmlarchive" +} +``` + +To use it with [mod\_mam\_muc] or [mod\_http\_muc\_log]: + +``` lua +storage = { + muc_log = "xmlarchive" +} +``` + +Refer to [Prosodys data storage documentation][doc:storage] for more +information. + +Note that this module does not implement the "keyval" storage method and +can't be used by anything other than archives. + + +### Conversion to or from internal storage + +This module stores data in a way that overlaps with the more recent +archive support in `mod_storage_internal`, meaning e.g. [mod_migrate] +will not be able to cleanly convert to or from the `xmlarchive` format. + +To mitigate this, an migration command has been added to +`mod_storage_xmlarchive`: + +``` bash +prosodyctl mod_storage_xmlarchive convert $DIR internal $STORE $JID+ +``` + +Where `$DIR` is `to` or `from`, `$STORE` is e.g. `archive` or `archive2` +for MAM and `muc_log` for MUC logs. Finally, `$JID` is one or more JID +of the users or MUC rooms to be migrated. + +To migrate all users/rooms on a particular host, pass a bare hostname. + +::: {.alert .alert-danger} +Since this is a destructive command, don't forget to backup your data +first. + +Prosody should *not* be running while converting data. +::: + + +Data structure +============== + +Data is split in three kinds of files and messages are grouped by day. +Prosodys `util.datamanager` is used, so all special characters in these +filenames are escaped and reside under `hostname/store` in Prosodys Data +directory, commonly `/var/lib/prosody`. + +`username.list` +: A list of dates in `YYYY-MM-DD` format. + +`username@YYYY-MM-DD.list` +: Index containing metadata for messages stored on that day. + +`username@YYYY-MM-DD.xml` +: Messages in textual XML format, separated by newlines. + +This makes it fairly simple and fast to find messages by timestamp. +Queries that are not time based, but limited to a specific contact may +be expensive as potentially the entire archive will be read. + +Each archive ID is of the form `YYYY-MM-DD-random`, making lookups by +archive id just as simple as time based queries. + +## Limitations + +- Only XML stanzas can be stored. +- The deletion method only supports removing entire days at a time. + +Compatibility +============= + + ------ --------------- + trunk Works + 0.12 Works + ------ ---------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_strict_https/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,37 @@ +--- +summary: HTTP Strict Transport Security +--- + +# Introduction + +This module implements [RFC 6797: HTTP Strict Transport Security] and +responds to all non-HTTPS requests with a `301 Moved Permanently` +redirect to the HTTPS equivalent of the path. + +# Configuration + +Add the module to the `modules_enabled` list and optionally configure +the specific header sent. + +``` lua +modules_enabled = { + ... + "strict_https"; +} +hsts_header = "max-age=31556952" +``` + +If the redirect from `http://` to `https://` causes trouble with +internal use of HTTP APIs it can be disabled: + +``` lua +hsts_redirect = false +``` + +# Compatibility + + ------- ------------- + trunk Should work + 0.12 Should work + 0.11 Should work + ------- -------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_support_contact/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,45 @@ +--- +labels: +- 'Stage-Stable' +summary: Add a support contact to new registrations +... + +Introduction +============ + +This Prosody plugin adds a default contact to newly registered accounts. + +Usage +===== + +Simply add "support\_contact" to your modules\_enabled list. When a new +account is created, the new roster would be initialized to include a +support contact. + +Configuration +============= + + ------------------------- -------------------------------------------------------------------------------------------------------------------------------- + support\_contact The bare JID of the support contact. The default is support@hostname, where hostname is the host the new user's account is on. + support\_contact\_nick Nickname of the support contact. The default is "Support". + support\_contact\_group The roster group in the support contact's roster in which to add the new user. + ------------------------- -------------------------------------------------------------------------------------------------------------------------------- + +Compatibility +============= + + ------ ------- + 0.10 Works + 0.9 Works + 0.8 Works + 0.7 Works + 0.6 Works + ------ ------- + +**For 0.8 and older** use [version +999a4b3e699b](http://hg.prosody.im/prosody-modules/file/999a4b3e699b/mod_support_contact/mod_support_contact.lua). + +Caveats/Todos/Bugs +================== + +- This only works for accounts created via in-band registration.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_support_room/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,24 @@ +# Introduction + +This module adds newly registered users as members to a specified MUC +room and sends them an invite. In a way, this is similar in purpose to +[mod_support_contact] and [mod_default_bookmarks]. + +# Example + + VirtualHost"example.com" + modules_enabled = { "support_room" } + support_room = "room@muc.example.com" + support_room_inviter = "support@example.com" + support_room_reason = "Invite new users to the support room" + + Component "muc.example.com" + +# Compatibility + +This module + + Version Works + --------- ------- + 0.11.x Yes + 0.10.x No
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_swedishchef/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,34 @@ +--- +labels: +- Stage-Beta +summary: Silly little module to convert your conversations to "swedish" +... + +Introduction +============ + +This module does some conversions on message bodys passed through it causing them to look like our beloved swedish chef had typed them. + +Details +======= + +To load this on a MUC component do + + Component "funconference.example.com" "muc" + modules_enabled = { "swedishchef" } + swedishchef_trigger = "!chef"; -- optional, converts only when the message starts with "!chef" + +This also works for whole servers, it is not recommended ;) + +Compatibility +============= + +Prosody-Version Status +--------------- -------------------- +trunk Works as of 24-12-20 +0.12 Works + +Todo +==== + +- Possibly add xhtml-im (XEP-0071) support
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_tcpproxy/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,73 @@ +--- +labels: +- 'Stage-Beta' +summary: 'TCP-over-XMPP :)' +... + +Introduction +============ + +It happens occasionally that I would like to use the XMPP server as a +generic proxy for connecting to another service. It is especially +awkward in some environments, and impossible in (for example) Javascript +inside a web browser. + +Details +======= + +Using mod\_tcpproxy an XMPP client (including those using BOSH) can +initiate a pipe to a given TCP/IP address and port. This implementation +uses the [In-Band Bytestreams](http://xmpp.org/extensions/xep-0047.html) +XEP, simply extended with 2 new attributes in a new namespace, host and +port. + +An example Javascript client can be found in the web/ directory of +mod\_tcpproxy in the repository. + +Configuration +============= + +Just add tcpproxy as a component, for example: + +`Component "tcp.example.com" "tcpproxy"` + +Protocol +======== + +A new stream is opened like this: + +``` {.xml} +<iq type="set" id="newconn1" to="tcp.example.com"> + <open xmlns='http://jabber.org/protocol/ibb' + sid='connection1' + block-size='4096' + stanza='message' + xmlns:tcp='http://prosody.im/protocol/tcpproxy' + tcp:host='example.com' + tcp:port='80' /> +</iq> +``` + +The stanza attribute (currently) MUST be 'message', and block-size is +(currently) ignored. + +In response to this stanza you will receive a result upon connection +success, or an error if the connection failed. You can then send to the +connection by sending message stanzas as described in the IBB XEP. +Incoming data will likewise be delivered as messages. + +Compatibility +============= + + ----- -------------- + 0.7 Works + 0.6 Doesn't work + ----- -------------- + +Todo +==== + +- ACLs (restrict to certain JIDs, and/or certain target hosts/ports) +- Honour block-size +- Support iq stanzas for data transmission +- Signal to start SSL/TLS on a connection
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_telnet_tlsinfo/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,61 @@ +--- +labels: +- 'Stage-Obsolete' +summary: Telnet command for showing TLS info +--- + +Introduction +============ + +This module adds two commands to the telnet console, `c2s:showtls()` and +`s2s:showtls()`. These commands shows TLS parameters, such as ciphers +and key agreement protocols, of all c2s or s2s connections. + +Configuration +============= + +Just add the module to the `modules_enabled` list. There is no other +configuration. + + modules_enabled = { + ... + "telnet_tlsinfo"; + } + +Usage +===== + +Simply type `c2s:showtls()` to show client connections or +`s2s:showtls()` for server-to-server connections. These commands can +also take a JID for limiting output to matching users or servers. + + s2s:showtls("prosody.im") + | example.com -> prosody.im + | protocol: TLSv1.1 + | cipher: DHE-RSA-AES256-SHA + | encryption: AES(256) + | algbits: 256 + | bits: 256 + | authentication: RSA + | key: DH + | mac: SHA1 + | export: false + + Field Description + ---------------- ------------------------------------------------------------------------------------------------- + protocol The protocol used. **Note**: With older LuaSec, this is the protocol that added the used cipher + cipher The OpenSSL cipher string for the currently used cipher + encryption Encryption algorithm used + bits, algbits Secret bits involved in the cipher + authentication The authentication algorithm used + mac Message authentication algorithm used + key Key exchange mechanism used. + export Whethere an export cipher is used + +Compatibility +============= + + --------------------- ------------------ + 0.9 with LuaSec 0.5 Works + 0.10 Merged into core + --------------------- ------------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_test_data/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,12 @@ +--- +description: Generate test data +labels: +- 'Stage-Alpha' +--- + +This module is of use to developers who want to fill their Prosody storage with +test data, e.g. for debugging, testing or performance measurements of Prosody or +client code. + +Currently it only supports filling MAM archives, but patches are welcome for other +functionality.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_throttle_presence/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,35 @@ +--- +labels: +- 'Stage-Beta' +summary: Limit presence stanzas to save traffic +... + +::: {.alert .alert-info} +This module violates the xmpp protocol by discarding stanzas, some people reportet issues with it. In reality the bandwith saving is not relevant for most poeple, consider using [mod_csi_simple][doc:modules:mod_csi_simple] that is incuded in prosody since Version 0.11. +::: + +Introduction +============ + +For most people 'presence' (status changes) of contacts make up most of +the traffic received by their client. However much of the time it is not +essential to have highly accurate presence information. + +This module automatically cuts down on presence traffic when clients +indicate they are inactive (using the [CSI protocol](mod_csi.html)). + +This is extremely valuable for mobile clients that wish to save battery +power while in the background. + +Configuration +============= + +Just load the module (e.g. in modules\_enabled). There are no +configuration options. + +Compatibility +============= + + ----- ------- + 0.9 Works + ----- -------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_throttle_unsolicited/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,32 @@ +--- +rockspec: + dependencies: + - mod_track_muc_joins +summary: Limit rate of outgoing unsolicited messages +--- + +Introduction +============ + +This module limits the rate of outgoing unsolicited messages from local +clients. Optionally, unsolicited messages coming in from remote servers +may be limited per s2s conneciton. A message counts as "unsolicited" if +the receiving user hasn't added the sending user to their roster. + +The module depends on [mod\_track\_muc\_joins] in order to allow sent +messages to joined MUC rooms. + +Configuration +============= + +To set a limit on messages from local sessions: + +``` {.lua} +unsolicited_messages_per_minute = 10 +``` + +To enable limits on unsolicited messages from s2s connections: + +``` {.lua} +unsolicited_s2s_messages_per_minute = 100 +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_tls_policy/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,45 @@ +--- +summary: Cipher policy enforcement with application level error reporting +... + +# Introduction + +This module arose from discussions at the XMPP Summit about enforcing +better ciphers in TLS. It may seem attractive to disallow some insecure +ciphers or require forward secrecy, but doing this at the TLS level +would the user with an unhelpful "Encryption failed" message. This +module does this enforcing at the application level, allowing better +error messages. + +# Configuration + +First, download and add the module to `module_enabled`. Then you can +decide on what policy you want to have. + +Requiring ciphers with forward secrecy is the most simple to set up. + +``` lua +tls_policy = "FS" -- allow only ciphers that enable forward secrecy +``` + +A more complicated example: + +``` lua +tls_policy = { + c2s = { + encryption = "AES"; -- Require AES (or AESGCM) encryption + protocol = "TLSv1.2"; -- and TLSv1.2 + bits = 128; -- and at least 128 bits (FIXME: remember what this meant) + } + s2s = { + cipher = "AESGCM"; -- Require AESGCM ciphers + protocol = "TLSv1.[12]"; -- and TLSv1.1 or 1.2 + authentication = "RSA"; -- with RSA authentication + }; +} +``` + +# Compatibility + +Requires LuaSec 0.5 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_tlsfail/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,10 @@ +--- +summary: STARTTLS failure test +--- + +This module responds to `<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>` with +`<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>`, in order to test +how clients and server behave. + +See [RFC6120 Section 5.4.2.2](https://xmpp.org/rfcs/rfc6120.html#rfc.section.5.4.2.2) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_traceback/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,4 @@ +This module writes out a traceback to `traceback.txt` in Prosodys data +directory (see `prosodyctl about`) when the signal `SIGUSR1` is +received. This is useful when debugging seemingly frozen instances in +case it is stuck in Lua code.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_track_muc_joins/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,62 @@ +--- +summary: Keep track of joined chat rooms +... + +# Introduction + +This module attempts to keep track of what MUC chat rooms users have +joined. It's not very useful on its own, but can be used by other +modules to influence decisions. + +# Usage + +Rooms joined and the associated nickname is kept in a table field +`rooms_joined` on the users session. + +An example: + +``` lua +local jid_bare = require"util.jid".bare; + +module:hook("message/full", function (event) + local stanza = event.stanza; + local session = prosody.full_sessions[stanza.attr.to]; + if not session then + return -- No such session + end + + local joined_rooms = session.joined_rooms; + if not joined_rooms then + return -- This session hasn't joined any rooms at all + end + + -- joined_rooms is a map of room JID -> room nickname + local nickname = joined_rooms[jid_bare(stanza.attr.from)]; + if nickname then + session.log("info", "Got a MUC message from %s", stanza.attr.from); + + local body = stanza:get_child_text("body"); + if body and body:find(nickname, 1, true) then + session.log("info", "The message contains my nickname!"); + end + end +end); +``` + +# Known issues + +[XEP 45 § 7.2.3 Presence Broadcast][enter-pres] has the following text: + +> In particular, if roomnicks are locked down then the service MUST do +> one of the following. +> +> \[...\] +> +> If the user has connected using a MUC client (...), then the service +> MUST allow the client to enter the room, modify the nick in accordance +> with the lockdown policy, and **include a status code of "210"** in +> the presence broadcast that it sends to the new occupant. + +This case is not yet handled. + +[enter-pres]: http://xmpp.org/extensions/xep-0045.html#enter-pres
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_turn_external/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,10 @@ +--- +labels: +- 'Stage-Merged' +summary: Advertise an external TURN service +... + +::: {.alert .alert-info} +This module has been merged into Prosody since version 0.12, +see [mod_turn_external][doc:modules:mod_turn_external]. +:::
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_turncredentials/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,38 @@ +--- +summary: Implement XEP-0215 +labels: +- 'Stage-Obsolete' +... + +::: {.alert .alert-warning} +A similar module is already included in prosody since 0.12, see [mod_turn_external][doc:modules:mod_turn_external] making this module obsolete. +::: + +# Introduction + +[XEP-0215] implementation for [time-limited TURN +credentials](https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00). + +# Configuration + + Option Type Default + ------------------------- -------- ------------ + turncredentials\_secret string *required* + turncredentials\_host string *required* + turncredentials\_port number `3478` + turncredentials\_ttl number `86400` + +# Compatible TURN / STUN servers. + +- [coturn](https://github.com/coturn/coturn) - [setup guide][doc:coturn] +- [restund](http://www.creytiv.com/restund.html) +- [eturnal](https://eturnal.net/) + +# Compatibility + +Incompatible with [mod_extdisco](https://modules.prosody.im/mod_extdisco.html) + + ------- -------------- + 0.12 Works + 0.11 Works + ------- --------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_twitter/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,44 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'Simple example of working component and HTTP polling.' +... + +Introduction +============ + +Twitter has simple API to use, so I tried to deal with it via Prosody. I +didn't manage to finish this module, but it is nice example of component +that accepts registrations, unregistrations, does HTTP polling and so +on. Maybe someone will finnish this idea. + +Details +======= + +It does require some non-prosody Lua libraries: LuaJSON + +Configuration +============= + +At the moment no configuration needed, but you can configure some +variables inside code. + +TODO +==== + +- Send latest tweets to XMPP user +- Reply user's messages to Twitter +- OAuth support +- User configuration (forms) +- discuss about using cjson +- [!!!!] rewrite to be compatible with 0.9+ +- drop? (since it is mod\_twitter in spectrum) + +Compatibility +============= + + ------- --------------------- + trunk Currently Not Works + 0.9 Currently Not Works + 0.8 Works + ------- ---------------------
--- a/mod_unified_push/README.md Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_unified_push/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -1,7 +1,7 @@ --- labels: - Stage-Alpha -summary: "Unified Push provider" +summary: Unified Push provider --- This module implements a [Unified Push](https://unifiedpush.org/) Provider @@ -72,28 +72,29 @@ `openssl rand -base64 32`. Changing the secret will invalidate all existing push registrations. -### HTTP configuration +## HTTP configuration -This module exposes a HTTP endpoint (to receive push notifications from app -servers). For more information on configuring HTTP services in Prosody, see +This module exposes a HTTP endpoint, by default at the path `/push` (to receive push notifications from app +servers). **If you use a reverse proxy, make sure you proxy this path too.** +For more information on configuring HTTP services and reverse proxying in Prosody, see [Prosody HTTP documentation](https://prosody.im/doc/http). -#### Example configuration +## Example configuration -##### Normal method +### Recommended: load on Virtualhost(s) Just add just add `"unified_push"` to your `modules_enabled` option. This is the easiest and **recommended** configuration. -``` {.lua} +``` lua modules_enabled = { - --- + -- ... "unified_push"; - --- + -- ... } ``` -##### Component method +#### Component method This is an example of how to configure the module as an internal component, e.g. on a subdomain or other non-user domain. @@ -105,7 +106,7 @@ on the 'example.com' host, which avoids needing to create/update DNS records and HTTPS certificates if example.com is already set up. -``` {.lua} +``` lua Component "notify.example.com" "unified_push" unified_push_secret = "<secret string here>" http_host = "example.com" @@ -113,5 +114,7 @@ ## Compatibility -| trunk | Works | -| 0.12 | Works | + Prosody-Version Status + ----------------- ---------------------- + trunk Works as of 24-12-08 + 0.12 Works
--- a/mod_unsubscriber/README.md Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_unsubscriber/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -6,4 +6,5 @@ ```lua Component "gmail.com" "unsubscriber" +modules_disabled = { "s2s" } ```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_uptime_presence/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,13 @@ +--- +labels: +summary: Report server uptime in presence +... + +Introduction +============ + +This module simply responds to a presence probe sent to the server with +a presence staza containing a timestamp from when the server started. + +This is imagined as an alternative to the XEP-0012 "Last Activity" +protocol that can seem a bit overloaded.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_vcard_muc/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,28 @@ +--- +summary: Support for MUC vCards and avatars +labels: +- 'Stage-Stable' +... + +# Introduction + +This module adds the ability to set vCard for MUC rooms. One of the most common use case is to be able to define an avatar for your own MUC room. + +# Usage + +Add "vcard\_muc" to your modules\_enabled list: + +``` {.lua} +Component "conference.example.org" "muc" +modules_enabled = { + "vcard_muc", +} +``` + +# Compatibility + + ------------------------- ---------- + trunk^[as of 2024-10-22] Works + 0.12 Works + ------------------------- ---------- +
--- a/mod_vcard_muc/mod_vcard_muc.lua Tue Mar 18 00:19:25 2025 +0700 +++ b/mod_vcard_muc/mod_vcard_muc.lua Tue Mar 18 00:31:36 2025 +0700 @@ -6,6 +6,7 @@ -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- +--% conflicts: muc_vcard local st = require "util.stanza" local jid_split = require "util.jid".split;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_vjud/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,54 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'XEP-0055: Jabber Search' +... + +Introduction +============ + +Basic implementation of [XEP-0055: Jabber Search]. + +Details +======= + +This module has two modes. One mode requires users to opt-in to be +searchable, then allows users to search the list of those users. The +second mode allows search across all users. + +Usage +===== + +First copy the module to the prosody plugins directory. + +Then add "vjud" to your modules\_enabled list: + + modules_enabled = { + -- ... + "vjud", + -- ... + } + +Alternatively, you can load it as a component: + + Component "search.example.com" "vjud" + +(Some old clients require this) + +Configuration +============= + + Option Default Description + ------------ ---------- -------------------------------- + vjud\_mode "opt-in" Choose how users are listed in the directory ("opt-in" or "all") + +Compatibility +============= + + ------- --------------------------------- + 0.8 Works, but only the opt-in mode + 0.9 Works + trunk Works + ------- --------------------------------- + +Note that the version for 0.8 and 0.9 are slightly different.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_warn_legacy_tls/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,47 @@ +--- +labels: +- Stage-Alpha +summary: Warn users of obsolete TLS Versions in clients +--- + + +TLS 1.0 and TLS 1.1 are obsolete. This module warns clients if they are using those versions, to prepare for disabling them. (If you use the default prosody config, this module will be unnessesary in its default setting, since these protocols are not allowed anymore by any supported prosody version.) + +This module can be used to warn from TLS1.2 if you want to switch to modern security in the near future. + +# Configuration + +``` {.lua} +modules_enabled = { + -- other modules etc + "warn_legacy_tls"; +} + +-- This is the default, you can leave it out if you don't wish to +-- customise or translate the message sent. +-- '%s' will be replaced with the TLS version in use. +legacy_tls_warning = [[ +Your connection is encrypted using the %s protocol, which has been demonstrated to be insecure and will be disabled soon. Please upgrade your client. +]] + +--You may want to warn about TLS1.2 these days too (This note added 2024), by default prosody will not even allow connections from TLS <1.2 +--Example: +legacy_tls_versions = { "TLSv1", "TLSv1.1", "TLSv1.2" } +``` + +## Options + +`legacy_tls_warning` +: A string. The text of the message sent to clients that use outdated + TLS versions. Default as in the above example. + +`legacy_tls_versions` +: Set of TLS versions, defaults to + `{ "SSLv3", "TLSv1", "TLSv1.1" }`{.lua}, i.e. TLS \< 1.2. + +# Compatibility + +Prosody-Version Status +--------------- --------------------- +trunk Works as of 24-12-16 +0.12 Works
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_watch_spam_reports/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,24 @@ +--- +labels: +- 'Stage-Beta' +rockspec: + dependencies: + - mod_spam_reporting +summary: 'Notify admins about incoming XEP-0377 spam reports' +--- + +This module sends a message to the server admins for incoming +[XEP-0377][1] spam reports. It depends on [mod\_spam\_reporting][2] +and doesn't require any configuration. + +Compatibility +============= + + ----- ----------- + trunk Works + 0.11 Works + ----- ----------- + + +[1]:https://xmpp.org/extensions/xep-0377.html +[2]:https://modules.prosody.im/mod_spam_reporting.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_watchuntrusted/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,42 @@ +--- +labels: +- 'Stage-Alpha' +summary: | + Warn admins about outgoing s2s connections that are refused due to + invalid or untrusted certificates +... + +Introduction +============ + +Similar to mod\_watchregistrations, this module warns admins when an s2s +connection fails due for encryption or trust reasons. + +The certificate shows the SHA1 hash, so it can easily be used together +with mod\_s2s\_auth\_fingerprint. + +Configuration +============= + + modules_enabled = { + -- other modules -- + "watchuntrusted", + + } + + untrusted_fail_watchers = { "admin@example.lit" } + untrusted_fail_notification = "Establishing a secure connection from $from_host to $to_host failed. Certificate hash: $sha1. $errors" + + Option Default Description + ------------------------------- --------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------- + untrusted\_fail\_watchers All admins The users to send the message to + untrusted\_fail\_notification "Establishing a secure connection from \$from\_host to \$to\_host failed. Certificate hash: \$sha1. \$errors" The message to send, \$from\_host, \$to\_host, \$sha1 and \$errors are replaced + untrusted\_message\_type `"chat"` Which kind of message to send. `"normal"` or `"headline"` are other sensible options + untrusted\_ignore\_domains Empty The domains that this module should not warn about + +Compatibility +============= + + ------- ------- + trunk Works + ------- -------
--- a/mod_webpresence/README.markdown Tue Mar 18 00:19:25 2025 +0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ ---- -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 via adhoc control. - -Installation -============ - -Copy mod\_webpresence.lua to your modules directory then add it to your -modules\_enabled list: - -``` - - 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: - -``` - - webpresence_icons = "/path/to/your/icons"; - -``` - -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. - -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" /> - -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@domain.net/message` - -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.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?) -- Internal/external image generator (GD, ImageMagick) -- Display the correct boolean in the first form.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_webpresence/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,81 @@ +--- +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 via adhoc control. + +Installation +============ + +Copy mod\_webpresence.lua to your modules directory then add it to your +modules\_enabled list: + +``` + + 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: + +``` + + webpresence_icons = "/path/to/your/icons"; + +``` + +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. + +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" /> + +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@domain.net/message` + +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.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?) +- Internal/external image generator (GD, ImageMagick) +- Display the correct boolean in the first form.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_welcome_page/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,47 @@ +--- +labels: +- 'Stage-Beta' +summary: 'Serve a welcome page to users' +rockspec: + dependencies: + - mod_http_libjs + build: + copy_directories: + - html +... + +Introduction +============ + +This module serves a welcome page to users, and allows them to create an +account invite via the web on invite-only servers. + +The page template and policy of when to allow account creation are both +possible to override. + +This module is part of the suite of modules that implement invite-based +account registration for Prosody. The other modules are: + +- mod_invites +- mod_invites_adhoc +- mod_invites_page +- mod_invites_register +- mod_invites_register_web +- mod_register_apps + +For details and a full overview, start with the mod_invites documentation. + +Configuration +======= + +`welcome_page_template_path` +: The path to a directory containing the page templates and assets. See + the module source for the example template. + +`welcome_page_variables` +: Optional variables to pass to the template, available as `{var.name}` + +`welcome_page_open_registration` +: Whether to allow account creation in the absence of any other plugin + overriding the policy. Defaults to `false` unless `registration_invite_only` + is set to `false`.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_xhtmlim/README.md Tue Mar 18 00:31:36 2025 +0700 @@ -0,0 +1,15 @@ +Introduction +============ + +This module attempts to sanitize XHTML-IM messages. + +It does **not** attempt to sanitize any CSS embedded in `style` +attributes, these are instead stripped by default. + +Configuration +============= + + Option Type Default + ------------------------ --------- --------- + `strip_xhtml_style` boolean `true` + `bounce_invalid_xhtml` boolean `false`