Compare commits

...

168 Commits

Author SHA1 Message Date
Pawel Chmielowski 27b87d4a8a Add SECURITY.md in hex docs
Container / build (amd64, <nil>) (push) Failing after 45s
Installers / binaries (amd64, <nil>) (push) Failing after 1m48s
CI / compile (28) (push) Has been cancelled
Container / build (arm64, -arm) (push) Has been cancelled
Installers / binaries (arm64, -arm) (push) Has been cancelled
Runtime / rebars (25, rebar) (push) Has been cancelled
Runtime / rebars (25, rebar3) (push) Has been cancelled
Runtime / rebars (26, rebar) (push) Has been cancelled
Runtime / rebars (26, rebar3) (push) Has been cancelled
Runtime / rebars (27, rebar3) (push) Has been cancelled
Runtime / rebars (28, rebar3) (push) Has been cancelled
Runtime / rebars (29, rebar3) (push) Has been cancelled
Runtime / rebar3-elixir (1.14) (push) Has been cancelled
Runtime / rebar3-elixir (1.18) (push) Has been cancelled
Runtime / rebar3-elixir (1.19) (push) Has been cancelled
Runtime / mix (1.14) (push) Has been cancelled
Runtime / mix (1.18) (push) Has been cancelled
Runtime / mix (1.19) (push) Has been cancelled
CI / static (28) (push) Has been cancelled
CI / dynamic (28) (push) Has been cancelled
CI / ct (agnostic, 28, multi) (push) Has been cancelled
CI / ct (extauth, 28, multi) (push) Has been cancelled
CI / ct (ldap, 28, multi) (push) Has been cancelled
CI / ct (mnesia, 28, multi) (push) Has been cancelled
CI / ct (mysql, 28, multi) (push) Has been cancelled
CI / ct (mysql, 28, single) (push) Has been cancelled
CI / ct (pgsql, 28, multi) (push) Has been cancelled
CI / ct (pgsql, 28, single) (push) Has been cancelled
CI / ct (redis, 28, multi) (push) Has been cancelled
CI / ct (sqlite, 28, multi) (push) Has been cancelled
CI / ct (sqlite, 28, single) (push) Has been cancelled
CI / cover (push) Has been cancelled
CI / schema (28) (push) Has been cancelled
Container / merge (push) Has been cancelled
Installers / Release (push) Has been cancelled
2026-03-25 16:42:17 +01:00
Badlop 6e90f5e681 Set version to 26.03
Container / build (amd64, <nil>) (push) Failing after 41s
Installers / binaries (amd64, <nil>) (push) Failing after 27s
CI / compile (28) (push) Has been cancelled
Container / build (arm64, -arm) (push) Has been cancelled
Installers / binaries (arm64, -arm) (push) Has been cancelled
Runtime / rebars (25, rebar) (push) Has been cancelled
Runtime / rebars (25, rebar3) (push) Has been cancelled
Runtime / rebars (26, rebar) (push) Has been cancelled
Runtime / rebars (26, rebar3) (push) Has been cancelled
Runtime / rebars (27, rebar3) (push) Has been cancelled
Runtime / rebars (28, rebar3) (push) Has been cancelled
Runtime / rebars (29, rebar3) (push) Has been cancelled
Runtime / rebar3-elixir (1.14) (push) Has been cancelled
Runtime / rebar3-elixir (1.18) (push) Has been cancelled
Runtime / rebar3-elixir (1.19) (push) Has been cancelled
Runtime / mix (1.14) (push) Has been cancelled
Runtime / mix (1.18) (push) Has been cancelled
Runtime / mix (1.19) (push) Has been cancelled
CI / static (28) (push) Has been cancelled
CI / dynamic (28) (push) Has been cancelled
CI / ct (agnostic, 28, multi) (push) Has been cancelled
CI / ct (extauth, 28, multi) (push) Has been cancelled
CI / ct (ldap, 28, multi) (push) Has been cancelled
CI / ct (mnesia, 28, multi) (push) Has been cancelled
CI / ct (mysql, 28, multi) (push) Has been cancelled
CI / ct (mysql, 28, single) (push) Has been cancelled
CI / ct (pgsql, 28, multi) (push) Has been cancelled
CI / ct (pgsql, 28, single) (push) Has been cancelled
CI / ct (redis, 28, multi) (push) Has been cancelled
CI / ct (sqlite, 28, multi) (push) Has been cancelled
CI / ct (sqlite, 28, single) (push) Has been cancelled
CI / cover (push) Has been cancelled
CI / schema (28) (push) Has been cancelled
Container / merge (push) Has been cancelled
Installers / Release (push) Has been cancelled
2026-03-25 15:37:09 +01:00
Badlop 9a82a28c8d CHANGELOG.md: Update to 26.03 2026-03-25 15:37:09 +01:00
Badlop c44c259180 Update man page to 26.03 2026-03-25 15:29:47 +01:00
Pawel Chmielowski 13c89279a2 Include ezlib in included_applications 2026-03-25 13:27:17 +01:00
Pawel Chmielowski fcf685a4dc Use tagged deps 2026-03-25 12:22:22 +01:00
Badlop 07580e6787 mod_mam: Allow backend modules to add fulltext namespace in Service Discovery 2026-03-25 02:10:37 +01:00
Pawel Chmielowski 6ce687262e Bump idna to 7.1 as used in xmpp 2026-03-24 16:40:50 +01:00
Pawel Chmielowski 2c8156dcd0 Update xmpp with fixes for dialyzer 2026-03-24 16:26:15 +01:00
Pawel Chmielowski bb8d65b68a Improve handling of c=y flag during sasl scram authentication
With this change we will no longer abort connection if all -PLUS
methods are disabled by administrator using `disable_sasl_mechanisms`.

Additionally when using SASL2 authentication we will also allow
connections to continue, if filtering methods based on stored
user passwords, removed all offered -PLUS mechanisms.
2026-03-24 14:47:21 +01:00
Badlop c43b15b3cd make-binaries: Bump libexpat 2.7.5 2026-03-23 16:01:03 +01:00
Badlop acc6beb3f3 Result of running "make format options" 2026-03-23 15:59:50 +01:00
Badlop bf2692a710 Annotate improvements in new version 2026-03-23 15:59:49 +01:00
Badlop 42e5a443ed Update Chinese Simplified translation (thanks to Sketch6580) 2026-03-23 15:59:48 +01:00
Badlop c5221f9350 Update German translation (thanks to Stefan Strigler) 2026-03-23 15:59:47 +01:00
Badlop 16739180f5 Update Bulgarian translation (thanks to Mr. EddX) 2026-03-23 15:59:45 +01:00
Pawel Chmielowski 181465ea3e Fix dialyzer warning 2026-03-20 17:43:00 +01:00
Pawel Chmielowski 23a32cae1a Add handling of Etag and If-Modified-Since headers to files served by mod_http_upload 2026-03-20 17:01:07 +01:00
badlop b2e2bd7ca8 Merge pull request #4547 from sstrigler/invites-broken-paths
fix mod_invites broken paths
2026-03-19 10:33:55 +01:00
Badlop efdc90b7b8 Bump Erlang/OTP 28.4.1 2026-03-16 19:23:34 +01:00
Badlop 010a6901b3 mod_invites: Fix minor typos in documentation 2026-03-16 19:23:19 +01:00
Badlop 8fb6257388 Fix warning about unused variable 2026-03-16 19:23:14 +01:00
Pawel Chmielowski 2832c4f513 Don't use gen_server:call for resolving pubsub host in mod_pubsub_serverinfo
Because each disco query do call this, this can be a bottleneck,
we can now get this value straight from options.
2026-03-16 19:08:19 +01:00
Pawel Chmielowski 6022105f39 Add ability for mod_options values to depend on other options 2026-03-16 18:56:15 +01:00
Pawel Chmielowski 3a78d6cf2a Fix test on mnesia 2026-03-16 15:29:36 +01:00
Pawel Chmielowski 975dcc1128 Start with tests for sasl2 2026-03-16 15:10:44 +01:00
Pawel Chmielowski 00e61dd816 Make mod_fast_auth filter offered methods, based on available channel bindings
This is a fix for issue #4549
2026-03-16 15:10:31 +01:00
Pawel Chmielowski c0134fe820 Ignore whitespaces at end of host header 2026-03-13 14:50:42 +01:00
Pawel Chmielowski ab59302204 Revert "Use global value for default_version as failback in mod_http_api if host is not recognized"
This fail tests.
This reverts commit 89b11829ce.
2026-03-13 14:48:57 +01:00
Pawel Chmielowski 89b11829ce Use global value for default_version as failback in mod_http_api if host is not recognized 2026-03-13 14:39:55 +01:00
Holger Weiss 267d8ca124 Don't fail to classify stand-alone chat states
Ignore XEP-0359 elements when checking whether a stanza has non-XEP-0085
content.  This allows mod_client_state to filter chat states with an
<origin-id/> element.
2026-03-12 19:12:23 +01:00
Stefan Strigler 427f68daa6 fix(mod_invites): broken path when behind proxy with prefix 2026-03-12 14:34:56 +01:00
Stefan Strigler b15bae267d fix path to bootstrap files 2026-03-12 13:47:52 +01:00
Pawel Chmielowski 6bbed5854a Fix duplicate stanza-id in muc mam responses generated from local history
This fixes issue #4544
2026-03-11 13:03:53 +01:00
Pawel Chmielowski 15661f16c4 Always hide password in mod_http_api log entries 2026-03-10 11:41:06 +01:00
Pawel Chmielowski 54b30cf0c4 Improve transformation code in mod_roster_mnesia 2026-03-10 11:39:29 +01:00
Badlop b7940ba360 Workflows: Don't use jsdelivr mirror: not up-to-date, unnecessary and problematic
* Not up-to-date

jsdelivr takes some days or weeks to get latest erlang versions, compare
  https://builds.hex.pm/builds/otp/arm64/ubuntu-24.04/builds.txt
  https://cdn.jsdelivr.net/hex/builds/otp/arm64/ubuntu-24.04/builds.txt

* Unnecessary

As builds.hex.pm is already geodistributed, there's no need to add mirror
Reference: https://www.hex.pm/docs/mirrors

* Problematic in parallel jobs

The ci.yml workflow is split in separate jobs that run in parallel,
and all of them must use the exact same erlang version.
When using mirrors, different erlang versions may be used...
and then the compiled files require more recompilation, which fail
  ===> Compiling /home/runner/work/ejabberd/ejabberd/c_src/epam.c
  ===> /home/runner/work/ejabberd/ejabberd/c_src/epam.c:18:10: fatal error: security/pam_appl.h: No such file or directory
   18 | #include <security/pam_appl.h>
      |          ^~~~~~~~~~~~~~~~~~~~~

Instead of removing mirrors, an alternative solution would be
to specify exact OTP version: "28.3.2", but then we would need to update
all those strings in ci.yml for every erlang release.
2026-03-10 02:07:41 +01:00
Badlop ee2fd8446a Update Catalan and Spanish translations 2026-03-09 19:26:00 +01:00
Badlop b41a8b0925 Update Czech translation (thanks to hollymrklm) 2026-03-09 19:26:00 +01:00
Badlop 8603cd85db manage-ejabberd: Compile with test profile to get all required deps 2026-03-09 19:25:22 +01:00
Badlop 3a2d1bd12b Makefile: Run invites-deps only when files are missing 2026-03-09 19:24:53 +01:00
Badlop a04ed90e25 Rename target test-eunit to avoid name pattern test-GROUP
Example:
$ make test-eunit
Makefile:696: warning: overriding recipe for target 'test-eunit'
Makefile:692: warning: ignoring old recipe for target 'test-eunit'
...
2026-03-09 19:20:41 +01:00
Holger Weiss 383b498fbb Merge remote-tracking branch 'processone/pr/4542'
* processone/pr/4542:
  make-binaries: Enable missing crypto features
2026-03-09 17:19:34 +01:00
Pawel Chmielowski af3987fc0a Update one more place where store_mam_message hook is triggered 2026-03-09 14:57:28 +01:00
Pawel Chmielowski 9fd1ed34ee Call store_mam_message hook for messages that user_mucsub_from_muc_archive was filtering out
Just adjust mod_mam callback for it to not store them.

This callback is used by mod_push for triggering push, and previous
behaviour did disable pushes for muc messages with that option
enabled.
2026-03-09 14:05:19 +01:00
Holger Weiss a9ae72ba9f make-binaries: Download unixODBC from GitHub
The unixODBC web site (unixodbc.org) seems to be unreachable.
2026-03-07 21:38:22 +01:00
badlop aa704c0d75 Merge pull request #4540 from sstrigler/overuse_limit
fix: set overuse limits
2026-03-06 19:00:24 +01:00
Badlop bc48fa4c55 CONTRIBUTORS: Add Stefan Strigler 2026-03-06 18:19:26 +01:00
Badlop 2251cb7547 container.yml: Disable the container cleanup action; it's dry anyway 2026-03-06 18:19:23 +01:00
badlop 15b51d7a7b Merge pull request #4539 from sstrigler/security_fixes
Even more security fixes (and other minor things)
2026-03-06 17:23:12 +01:00
Stefan Strigler 5721f77d5a fix: set overuse limits
This is an effort to detect abuse where accounts get created to create more
invites (invite chaining) and so on.
2026-03-06 17:11:09 +01:00
Badlop fa32fecd97 Update French translation (thanks to Dyxux) 2026-03-06 15:58:37 +01:00
Badlop e291d84cf8 Update German translation (thanks to Stefan Strigler) 2026-03-06 15:57:20 +01:00
Badlop 934fdcdc5d Update Bulgarian translation (thanks to Mr. EddX) 2026-03-06 15:56:51 +01:00
Stefan Strigler eba6868a3f styling improvements 2026-03-06 15:18:25 +01:00
Stefan Strigler 5bb989d998 invites: bring yaxim back 2026-03-06 15:18:25 +01:00
Stefan Strigler e9645ffec0 improv(invites): add favicon and change color to match ejabberd branding 2026-03-06 15:18:25 +01:00
Stefan Strigler 179c5c4cbb feat(invites: enable dark mode 2026-03-06 15:18:25 +01:00
Stefan Strigler f210803202 fix(invites): no inline scripts 2026-03-06 15:18:25 +01:00
Stefan Strigler 793855ad91 fix(invites): make format csrf token 2026-03-06 15:18:25 +01:00
Stefan Strigler 1a423743af feat(invites): add support for webchat_url 2026-03-06 15:18:25 +01:00
Stefan Strigler b8f20fb663 fix(invites): add csrf token to failed post 2026-03-06 15:18:25 +01:00
Stefan Strigler 7b4338e7df invites: update jquery 2026-03-06 15:18:25 +01:00
Stefan Strigler e6b1e55705 invites: migrate to bootstrap5 2026-03-06 15:18:25 +01:00
Stefan Strigler eb784b4026 fix(invites): include js/css deps in static dir 2026-03-06 15:18:25 +01:00
Stefan Strigler 07d363ed0c fix(invites): correct hashes for bootstrap 4.6.2 2026-03-06 15:18:25 +01:00
Stefan Strigler 8a7cf45d0b fix(invites): hint at type for landing_page opt 2026-03-06 15:18:25 +01:00
Badlop d93ece6540 Result of running "make format" 2026-03-06 13:48:21 +01:00
Badlop 2a65a5001d Bump Erlang/OTP 28.4 2026-03-06 13:47:50 +01:00
Badlop 1ffcd1ce6e ejabberd_ctl: Document how to set empty lists in ejabberdctl and WebAdmin 2026-03-06 13:47:48 +01:00
Badlop d5c14406db mod_roster: Fix display of groups in WebAdmin when it's a list 2026-03-06 13:47:46 +01:00
Badlop 67b87ac892 mod_roster: in WebAdmin page, first execute SET actions, later GET 2026-03-06 13:47:44 +01:00
Holger Weiss a44f865432 make-binaries: Enable missing crypto features
With OTP's --enable-static-nifs, libcrypto features detected by
configure aren't propagated to the static NIF build flags.  Work
around this issue by patching OTP's Makefile until the following
PR is merged:

https://github.com/erlang/otp/pull/10817
2026-03-02 22:44:10 +01:00
Pawel Chmielowski a4ff3f3a65 Make table cleanup in test more robust 2026-02-27 11:31:59 +01:00
Pawel Chmielowski ca56f11dc6 Add abilty to mark that column can be null in e_sql_schema 2026-02-27 09:47:40 +01:00
badlop 1695385912 Merge pull request #4538 from sstrigler/security_fixes
Security fixes
2026-02-26 17:16:40 +01:00
Stefan Strigler b9e3841186 fix: check CSRF token in register form 2026-02-26 16:22:57 +01:00
Stefan Strigler 0680f6ac5e fix: add integrity hashes to scripts and css 2026-02-26 15:18:02 +01:00
Stefan Strigler ff15018b24 fix: comment unused resources 2026-02-26 15:18:02 +01:00
Stefan Strigler 635054007d fix: add security headers 2026-02-26 15:18:02 +01:00
Stefan Strigler f1527ba21e fix: remove debug log of whole query parameters (including pw) 2026-02-26 15:18:02 +01:00
Stefan Strigler 3b1a8a6c00 fix: don't crash on unknown host from http host header 2026-02-26 15:18:02 +01:00
Stefan Strigler c862f58c78 fix: make creating invite transactional 2026-02-26 15:18:02 +01:00
Badlop 6e938dcc84 CI and Weekly: If sending to coveralls fails, continue anyway 2026-02-26 13:49:55 +01:00
Badlop ecb88d9ebc Result of running "make format options" 2026-02-24 16:34:39 +01:00
Badlop b7464e19ff mod_roster_mnesia: Fix compilation warning in recent commit 2026-02-24 16:31:35 +01:00
badlop 1bcd52300f Merge pull request #4512 from sstrigler/roster-pre-approval
Roster Subscription Pre-Approval
2026-02-24 16:00:42 +01:00
Badlop 0c55beff6e Runtime and Weekly: Add Erlang/OTP 29 2026-02-24 15:47:46 +01:00
Badlop 3adb30c859 Bump Erlang/OTP 28.3.2 2026-02-24 15:46:35 +01:00
Badlop 5378d177e2 Revert "rebar.config: Bump rebar3_lint to 4.2.2, used for Elvis"
This breaks compiling ejabberd with Erlang/OTP 29 in many libraries,
for example:
    ┌─ _build/default/lib/eredis/src/eredis.erl:
    │
 10 │  -include("eredis.hrl").
    │           ╰── can't find include file "eredis.hrl"

Compiling with Erlang 28 or older works,
but Elvis fails as it includes many new tests that are not passed.

This reverts commit 5b89ee0fe8.
2026-02-24 15:46:31 +01:00
Badlop 160cd976b4 mod_mam_sql: Only provide the new XEP-0431 fulltext field, not old custom withtext 2026-02-23 16:39:46 +01:00
Badlop 95d8f6c531 mod_muc_room: Fix hook name in commit 7732984 (thanks to trixnz)(#4526) 2026-02-23 16:39:44 +01:00
Badlop cdcf3ef1b9 mod_invites: Check at start time the syntax of landing_page option (#4525) 2026-02-23 16:39:42 +01:00
Badlop afd49b9847 make-binaries: Bump zlib to 1.3.2 2026-02-23 16:39:40 +01:00
Badlop 5b89ee0fe8 rebar.config: Bump rebar3_lint to 4.2.2, used for Elvis 2026-02-23 16:39:38 +01:00
Badlop 4ce63d9a6c rebar.config: Remove nowarn_deprecated_function, seems useless nowadays 2026-02-23 16:39:33 +01:00
Pawel Chmielowski 29ac776a98 Update p1_pgsql once more 2026-02-20 11:25:01 +01:00
Pawel Chmielowski 8cf9a0b79b Update p1_pgsql 2026-02-20 11:12:35 +01:00
badlop d0d9b2f968 Merge pull request #4535 from sstrigler/no_redirect-url_if_preauth
set meta.pre-auth to skip redirect_url if token validated
2026-02-19 21:52:04 +01:00
Stefan Strigler 785412be5c fix: set meta.pre-auth to skip redirect_url if token validated
otherwise if redirect_url for mod_register is set, then we can't register even
if a valid invite token has been presented
2026-02-19 09:02:55 +01:00
badlop 3b05cb0700 Merge pull request #4531 from sstrigler/mod_invites_http_set_link_header
imprv: send 'Link' http header
2026-02-17 13:15:36 +01:00
Stefan Strigler 1fc1626d3f imprv: send 'Link' http header
Conversations' QR code scanner does a HEAD request on embedded http links and
extracts and interprets any XMPP URI if found. This way skipping the step of
having the user handle the landing page.
2026-02-17 13:02:14 +01:00
Badlop 56f47dbec7 rebar.config: Add temporary workarounds for Erlang/OTP 29 warnings (#4527) 2026-02-17 12:42:18 +01:00
Badlop 05cd172b77 Use also *.lock files to determine dependencies cache key
The cache key must change too when a dependency is updated
only in the lock file.
2026-02-17 12:42:13 +01:00
Badlop 0876662a20 rebar.lock: Update also rebar.lock 2026-02-17 12:42:11 +01:00
Badlop 9402dbb16f CI: Don't finish a parallel build in converalls in case of error
Because we may want to re-run the job in Github Actions, and coveralls
would then refuse new cover submits as the build is already closed.
2026-02-17 12:42:10 +01:00
Badlop 99f9443ec8 Fix some warnings compiling with Erlang/OTP 29 (#4527) 2026-02-17 12:42:08 +01:00
Badlop 84f698c489 Runtime: Remove obsolete step for Erlang lower than 25 2026-02-17 12:42:07 +01:00
Badlop 274a5dfbc8 Weekly: Clean useless newline 2026-02-17 12:42:05 +01:00
Badlop 641aa88bbc CI: Test with Erlang 28, the one included in installers and container 2026-02-17 12:42:03 +01:00
Badlop 029aa67ccf Fix URL in documentation to work correctly with "make archive" 2026-02-17 12:42:01 +01:00
Pawel Chmielowski 120973074a Update p1_mysql 2026-02-16 17:29:52 +01:00
Badlop 8138b48cad Set version to 26.02
Container / build (amd64, <nil>) (push) Failing after 45s
Installers / binaries (amd64, <nil>) (push) Failing after 38s
CI / compile (27) (push) Has been cancelled
Container / build (arm64, -arm) (push) Has been cancelled
Installers / binaries (arm64, -arm) (push) Has been cancelled
Runtime / rebars (25, rebar) (push) Has been cancelled
Runtime / rebars (25, rebar3) (push) Has been cancelled
Runtime / rebars (26, rebar) (push) Has been cancelled
Runtime / rebars (26, rebar3) (push) Has been cancelled
Runtime / rebars (27, rebar3) (push) Has been cancelled
Runtime / rebars (28, rebar3) (push) Has been cancelled
Runtime / rebar3-elixir (1.14) (push) Has been cancelled
Runtime / rebar3-elixir (1.18) (push) Has been cancelled
Runtime / rebar3-elixir (1.19) (push) Has been cancelled
Runtime / mix (1.14) (push) Has been cancelled
Runtime / mix (1.18) (push) Has been cancelled
Runtime / mix (1.19) (push) Has been cancelled
CI / static (27) (push) Has been cancelled
CI / dynamic (27) (push) Has been cancelled
CI / ct (agnostic, 27, multi) (push) Has been cancelled
CI / ct (extauth, 27, multi) (push) Has been cancelled
CI / ct (ldap, 27, multi) (push) Has been cancelled
CI / ct (mnesia, 27, multi) (push) Has been cancelled
CI / ct (mysql, 27, multi) (push) Has been cancelled
CI / ct (mysql, 27, single) (push) Has been cancelled
CI / ct (pgsql, 27, multi) (push) Has been cancelled
CI / ct (pgsql, 27, single) (push) Has been cancelled
CI / ct (redis, 27, multi) (push) Has been cancelled
CI / ct (sqlite, 27, multi) (push) Has been cancelled
CI / ct (sqlite, 27, single) (push) Has been cancelled
CI / cover (push) Has been cancelled
CI / schema (27) (push) Has been cancelled
Container / merge (push) Has been cancelled
Installers / Release (push) Has been cancelled
2026-02-11 10:09:39 +01:00
Badlop 7de0afb613 CHANGELOG.md: Add links to issues 2026-02-11 10:08:11 +01:00
Badlop c407e87da3 CHANGELOG.md: Update to 26.02 2026-02-11 09:33:36 +01:00
Badlop 2681f3b5d8 Update man page to 26.02 2026-02-11 09:31:06 +01:00
Badlop f9a187c3aa Update Catalan and Spanish translations 2026-02-11 09:28:39 +01:00
Badlop 40fffe6cf4 Update Bulgarian translation (thanks to Mr. EddX) 2026-02-11 09:28:38 +01:00
Badlop 903943362e Update Chinese Simplified translation (thanks to Sketch6580) 2026-02-11 09:28:36 +01:00
Badlop e6120641f9 Update German translation (thanks to Stefan Strigler) 2026-02-11 09:28:34 +01:00
Badlop 43f0b811f5 New 'configure' input in manage-ejabberd to append in ./configure 2026-02-10 15:34:38 +01:00
Badlop bd61571ce5 Result of running "make format" 2026-02-10 15:34:34 +01:00
badlop e06bdd6b30 Merge pull request #4524 from sstrigler/invites_a11y_fixes
Invites a11y fixes
2026-02-10 15:28:10 +01:00
Stefan Strigler 75c66cba9c accessibility improvements 2026-02-10 15:06:45 +01:00
Badlop 531d17ddb9 mod_muc: Document all the protocols implemented 2026-02-10 11:12:09 +01:00
Badlop 1aa2e75a21 Update Catalan and Spanish translations 2026-02-10 11:12:07 +01:00
Badlop 416bee969f Update German translation (thanks to Stefan Strigler) 2026-02-10 11:12:06 +01:00
Badlop fa75800ee1 Update Czech translation (thanks to ffunk) 2026-02-10 11:12:04 +01:00
Badlop 5426358959 weekly.yml: Don't test mssql, as it fails
Right start fails with:

2026-02-09 11:48:05.211061+00:00 [warning]
 <0.26972.0>@ejabberd_sql:handle_reconnect/2:540
 mssql connection failed:
  ** Reason: {"01000",0,
              "[unixODBC][Driver Manager]Can't open lib 'libtdsodbc.so' :
               file not found Connection to database failed."}
2026-02-09 16:47:26 +01:00
Badlop 8e01c04e96 Use manage-ejabberd action in workflows to compile ejabberd 2026-02-09 16:47:25 +01:00
Badlop 809d9d2d0f gen_mod: Fix recent commit, deleting global hook when module in no more hosts 2026-02-09 16:47:23 +01:00
Badlop 7c2db2aebb mod_muc_mnesia: Fix bug introduced in 2b7285e when upgrading old database
If mod_muc uses Mnesia database,
and there is any room created using ejabberd older than 21.12,
and its room config was never modified using newer ejabberd versions...
in that case the room configuration does not contain hats_users.

When upgrading to ejabberd 25.10 or 26.01,
it tries to upgrade the MUC rooms configuration but fails with error:
  ejabberd_mnesia:transform_fun/2:382
  Failed to transform Mnesia table muc_room
2026-02-09 16:47:22 +01:00
Badlop 4f930cb94d mod_register_web: Document that enabling the module adds link to WebAdmin 2026-02-09 16:47:20 +01:00
Badlop 9a48088d84 Remove mod_muc_occupantid in example config: feature is now in mod_muc 2026-02-09 16:47:19 +01:00
Badlop 2e9b538c27 Ammend recent commit to fix warning, also run "make format" 2026-02-09 16:47:17 +01:00
Badlop 1a6d22615d make-binaries: Bump OpenSSL 3.5.5, Expat 2.7.4 2026-02-09 16:47:16 +01:00
Pawel Chmielowski 97fc89bc7f Fix dialyzer warning 2026-02-06 12:54:11 +01:00
Pawel Chmielowski fbc3349e04 Add odbc to dialyzer only if odbc is configured 2026-02-06 12:54:01 +01:00
Pawel Chmielowski 42122b7aa7 Prevent crash in mod_pubsub_serverinfo when parsing bad disco responses 2026-02-06 12:38:29 +01:00
Pawel Chmielowski ee05a863f7 Better handling of invalid percent encoded urls in ejabberd_http 2026-02-06 12:31:11 +01:00
Badlop 44fa357534 Result of running "make doap format options" 2026-02-06 00:52:46 +01:00
Badlop 354e4920b3 mod_invites: Handle 'auto' value as an atom in the 'landing_page' option 2026-02-06 00:28:06 +01:00
Badlop 9bfdb2b8d4 mod_conversejs: Report problem in WebAdmin autologin if WebSocket is disabled 2026-02-06 00:26:51 +01:00
Badlop e31fced208 mod_conversejs: Support in autologin for WebAdmin authentication with just username
WebAdmin supports to login by providing just the username, in that case
the HTTP Host is considered to be also the JID domain.
2026-02-06 00:26:49 +01:00
Badlop f65903a181 Mark changed options/commands with same emoji already used in CONTAINER.md 2026-02-06 00:26:47 +01:00
Paweł Chmielowski f21bd9b96e Merge pull request #4515 from sstrigler/mod_invites_fixes
Fixes for mod_invites
2026-02-05 14:08:06 +01:00
Pawel Chmielowski 0647a23ea7 Delete mnesia muc_occupant_id table 2026-02-05 13:57:22 +01:00
Pawel Chmielowski 9b5ea84ee5 Check in tests if room offer occupant-id feature 2026-02-05 13:56:56 +01:00
Pawel Chmielowski 7732984133 Move occupant_id storage and processing to mod_muc_room 2026-02-05 13:56:55 +01:00
Pawel Chmielowski 6f59b7e63d Fix dialyzer warning 2026-02-04 13:58:52 +01:00
Pawel Chmielowski 40d6ef6156 Use jid from mediated invitation instead of confernce jid in mod_block_strangers checks
For issue #4523
2026-02-04 13:00:54 +01:00
Badlop b5f8bc7c15 mod_muc_occupantid: When reloading config, clean obsolete salts (#4521) 2026-02-02 16:57:54 +01:00
Badlop 97e0530be1 mod_muc_occupantid: Fix Mnesia table creation (#4521) 2026-02-02 16:57:52 +01:00
Badlop 3ab3c71cf6 Don't delete global hook if stopping module that is alive in other host 2026-02-02 16:57:47 +01:00
Pawel Chmielowski 7bc96ee464 Add tests for muc hats 2026-01-30 12:24:41 +01:00
Stefan Strigler ca07b182c6 pretty print command errors 2026-01-30 11:34:04 +01:00
Stefan Strigler 3ffe6383b2 fix privacy tests 2026-01-30 10:36:38 +01:00
Stefan Strigler 47d898bb8b add support for roster pre-approval
Implementation of RFC 6121 section 3.4: Pre-Approving a Subscription Request
https://datatracker.ietf.org/doc/html/rfc6121#section-3.4
2026-01-30 10:36:38 +01:00
Pawel Chmielowski 3a5f7e6cfe Fix exception with adding hats info in presence
This should fix issue #4516
2026-01-30 09:49:16 +01:00
Stefan Strigler c8ba793851 fix type in docs for templates_dir 2026-01-30 08:18:24 +01:00
Stefan Strigler d0646e8009 traverse priv/mod_invites on install target
Fixes processone/ejabberd#4514
2026-01-30 08:18:24 +01:00
Stefan Strigler d89da2d3eb mentione dl script to get jquery and bootstrap 2026-01-30 08:18:24 +01:00
Stefan Strigler 99b54916f4 link to more specific places for jquery and bootstrap 2026-01-30 08:18:24 +01:00
Stefan Strigler d0437ab19a fix stale comma 2026-01-30 08:18:24 +01:00
Pawel Chmielowski 12910ed023 Define :TEST when using test MIX_ENV
This is needed for invites test
2026-01-26 13:40:55 +01:00
Pawel Chmielowski ae5356b2a6 Recognize -behavior attribute in opt_types script 2026-01-22 15:07:37 +01:00
Badlop f417b9f7c3 Amend previous commit: only Rebar2 needs this override
and that override, when using Rebar3,
breaks Dialyzer for mod_avatar and mod_http_upload
2026-01-21 23:05:38 +01:00
Badlop bd3c805e67 Use erlydtl 0.14.0 from hex.pm, or 0.15.0 from git
Rebar3 and Mix support hex.pm:
download its latest package 0.14.0

Rebar2 doesn't support hex.pm:
download 0.15.0 from git and delete its require_otp_vsn option.
2026-01-21 18:08:22 +01:00
117 changed files with 3267 additions and 1567 deletions
@@ -24,6 +24,9 @@ inputs:
rel_name_vsn:
default: ""
description: 'Base name of installer files'
configure:
default: ""
description: 'Options to append to ./configure'
runs:
using: "composite"
@@ -68,6 +71,20 @@ runs:
;;
esac
############################################################# Compile #####
- if: contains(inputs.do, 'compile')
shell: sh
run: |
TOOL=${{ inputs.tool }}
[ "${TOOL%3}" = "rebar" ] && TOOL="./$TOOL"
./autogen.sh
./configure --with-rebar=$TOOL \
--prefix=/tmp/ejabberd \
--enable-all ${{ inputs.configure }}
sed -i 's|, syntax_tools||g' src/ejabberd.app.src.script
REBAR_PROFILE=test make
############################################################## Deploy #####
- if: contains(inputs.do, 'deploy') &&
+3 -11
View File
@@ -1,5 +1,5 @@
#' Define default build variables
ARG OTP_VSN='28.3.1.0'
ARG OTP_VSN='28.4.1.0'
ARG ELIXIR_VSN='1.19.5'
ARG UID='9000'
ARG USER='ejabberd'
@@ -19,7 +19,7 @@ RUN go install -v \
FROM docker.io/erlang:${OTP_VSN}-alpine AS ejabberd
RUN apk -U add --no-cache \
nodejs npm ca-certificates \
ca-certificates \
autoconf \
automake \
bash \
@@ -33,6 +33,7 @@ RUN apk -U add --no-cache \
libpng-dev \
libwebp-dev \
linux-pam-dev \
npm \
openssl-dev \
sqlite-dev \
yaml-dev \
@@ -54,15 +55,6 @@ COPY / $BUILD_DIR/
WORKDIR $BUILD_DIR
RUN npm init -y \
&& npm install --silent jquery@3.7.1 bootstrap@4.6.2 \
&& mkdir -p /rootfs/usr/share/javascript/bootstrap4/css \
&& mkdir -p /rootfs/usr/share/javascript/bootstrap4/js \
&& mkdir -p /rootfs/usr/share/javascript/jquery/ \
&& cp -r node_modules/bootstrap/dist/css/bootstrap.min* /rootfs/usr/share/javascript/bootstrap4/css \
&& cp -r node_modules/bootstrap/dist/js/bootstrap.min* /rootfs/usr/share/javascript/bootstrap4/js \
&& cp -r node_modules/jquery/dist/jquery.min* /rootfs/usr/share/javascript/jquery/
RUN mv .github/container/ejabberdctl.template . \
&& mv .github/container/ejabberd.yml.example . \
&& ./autogen.sh \
-1
View File
@@ -243,7 +243,6 @@ modules:
default_room_options:
mam: true
mod_muc_admin: {}
mod_muc_occupantid: {}
mod_offline:
access_max_user_messages: max_user_offline_messages
mod_ping: {}
+18 -32
View File
@@ -27,7 +27,7 @@ jobs:
runs-on: ubuntu-24.04-arm
strategy:
matrix:
otp: ['27']
otp: ['28']
steps:
@@ -36,9 +36,6 @@ jobs:
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ matrix.otp }}
hexpm-mirrors: |
https://cdn.jsdelivr.net/hex
https://builds.hex.pm
- uses: awalsh128/cache-apt-pkgs-action@latest
with:
@@ -51,17 +48,18 @@ jobs:
path: |
~/.cache/rebar3/
_build/default/lib/
key: ci-${{ matrix.otp }}-${{hashFiles('rebar.config')}}
key: ci-${{ matrix.otp }}-${{hashFiles('rebar.*')}}
- name: Compile
uses: ./.github/actions/manage-ejabberd
with:
for: prod
do: compile
tool: rebar3
configure: --disable-elixir
- name: Compress compiled.tar
run: |
./autogen.sh
./configure --with-rebar=./rebar3 \
--prefix=/tmp/ejabberd \
--enable-all \
--disable-elixir
sed -i 's|, syntax_tools||g' src/ejabberd.app.src.script
make
tar -cvf /tmp/compiled.tar .
- uses: actions/upload-artifact@v6
@@ -77,16 +75,13 @@ jobs:
runs-on: ubuntu-24.04-arm
strategy:
matrix:
otp: ['27']
otp: ['28']
steps:
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ matrix.otp }}
hexpm-mirrors: |
https://cdn.jsdelivr.net/hex
https://builds.hex.pm
- uses: actions/download-artifact@v7
with:
@@ -111,7 +106,7 @@ jobs:
- run: make options
- run: make xref
- run: make dialyzer
- run: make test-eunit
- run: make testeunit
- run: make elvis
if: matrix.otp > '25'
@@ -124,19 +119,16 @@ jobs:
runs-on: ubuntu-24.04-arm
strategy:
matrix:
otp: ['27']
otp: ['28']
steps:
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ matrix.otp }}
hexpm-mirrors: |
https://cdn.jsdelivr.net/hex
https://builds.hex.pm
- uses: awalsh128/cache-apt-pkgs-action@latest
if: matrix.otp < '27'
if: matrix.otp < '28'
with:
packages: libexpat1-dev libgd-dev libpam0g-dev
libsqlite3-dev libwebp-dev libyaml-dev
@@ -160,7 +152,7 @@ jobs:
username: user123
- name: Run XMPP Interoperability Tests against CI server
if: matrix.otp == '27'
if: matrix.otp == '28'
continue-on-error: true
uses: XMPP-Interop-Testing/xmpp-interop-tests-action@v1.7.2
with:
@@ -199,7 +191,7 @@ jobs:
runs-on: ubuntu-24.04-arm
strategy:
matrix:
otp: ['27']
otp: ['28']
backend: [agnostic, extauth, ldap, mnesia, mysql, pgsql, redis, sqlite]
schema: [single, multi]
exclude:
@@ -219,9 +211,6 @@ jobs:
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ matrix.otp }}
hexpm-mirrors: |
https://cdn.jsdelivr.net/hex
https://builds.hex.pm
- uses: actions/download-artifact@v7
with:
@@ -245,6 +234,7 @@ jobs:
run: CT_BACKENDS=${{ matrix.backend }} make test
- name: Send to coveralls
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
@@ -292,7 +282,6 @@ jobs:
cover:
needs: ct
if: always()
runs-on: ubuntu-24.04-arm
steps:
- name: Finish parallel upload to coveralls
@@ -313,16 +302,13 @@ jobs:
runs-on: ubuntu-24.04-arm
strategy:
matrix:
otp: ['27']
otp: ['28']
steps:
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ matrix.otp }}
hexpm-mirrors: |
https://cdn.jsdelivr.net/hex
https://builds.hex.pm
- uses: actions/download-artifact@v7
with:
+8 -8
View File
@@ -109,11 +109,11 @@ jobs:
sources: |
${{ steps.meta-amd.outputs.tags }}
${{ steps.meta-arm.outputs.tags }}
- uses: dataaxiom/ghcr-cleanup-action@v1
with:
dry-run: true
delete-tags: '*--a??64'
delete-untagged: true
delete-ghost-images: true
delete-partial-images: true
delete-orphaned-images: true
#- uses: dataaxiom/ghcr-cleanup-action@v1
# with:
# dry-run: true
# delete-tags: '*--a??64'
# delete-untagged: true
# delete-ghost-images: true
# delete-partial-images: true
# delete-orphaned-images: true
+24 -32
View File
@@ -32,13 +32,15 @@ jobs:
runs-on: ubuntu-24.04-arm
strategy:
matrix:
otp: ['25', '26', '27', '28']
otp: ['25', '26', '27', '28', '29']
rebar: ['rebar', 'rebar3']
exclude:
- otp: '27'
rebar: 'rebar'
- otp: '28'
rebar: 'rebar'
- otp: '29'
rebar: 'rebar'
container:
image: public.ecr.aws/docker/library/erlang:${{ matrix.otp }}
@@ -46,16 +48,6 @@ jobs:
- uses: actions/checkout@v6
- name: Get recent compatible Rebar binaries
if: matrix.otp < 25
run: |
rm rebar
rm rebar3
wget https://github.com/processone/ejabberd/raw/24.12/rebar
wget https://github.com/processone/ejabberd/raw/24.12/rebar3
chmod +x rebar
chmod +x rebar3
- name: Prepare libraries
run: |
apt-get -qq update
@@ -69,17 +61,15 @@ jobs:
path: |
~/.cache/rebar3/
_build/default/lib/
key: runtime-${{ matrix.otp }}-${{matrix.rebar}}-${{hashFiles('rebar.config')}}
key: runtime-${{ matrix.otp }}-${{matrix.rebar}}-${{hashFiles('rebar.*')}}
- name: Compile
run: |
./autogen.sh
./configure --with-rebar=./${{ matrix.rebar }} \
--prefix=/tmp/ejabberd \
--with-min-erlang=9.0.5 \
--enable-all \
--disable-elixir
make
uses: ./.github/actions/manage-ejabberd
with:
for: prod
do: compile
tool: ${{ matrix.rebar }}
configure: --disable-elixir
- run: make hooks
- run: make options
@@ -145,7 +135,7 @@ jobs:
with:
path: |
~/.cache/rebar3/
key: runtime-${{matrix.elixir}}-${{hashFiles('rebar.config')}}
key: runtime-${{matrix.elixir}}-${{hashFiles('rebar.*')}}
- name: Install Hex and Rebar3 manually on older Elixir
if: matrix.elixir < '1.15'
@@ -154,11 +144,14 @@ jobs:
mix local.rebar --force
- name: Compile
uses: ./.github/actions/manage-ejabberd
with:
for: prod
do: compile
tool: ./rebar3
- name: Test scripts, deps, eunit
run: |
./autogen.sh
./configure --with-rebar=./rebar3 \
--prefix=/tmp/ejabberd \
--enable-all
make scripts deps
./rebar3 eunit --verbose
@@ -231,7 +224,7 @@ jobs:
with:
path: |
~/.hex/
key: runtime-${{matrix.elixir}}-${{hashFiles('mix.exs')}}
key: runtime-${{matrix.elixir}}-${{hashFiles('mix.*')}}
- name: Install Hex and Rebar3 manually on older Elixir
if: matrix.elixir < '1.15'
@@ -240,12 +233,11 @@ jobs:
mix local.rebar --force
- name: Compile
run: |
./autogen.sh
./configure --with-rebar=mix \
--prefix=/tmp/ejabberd \
--enable-all
make
uses: ./.github/actions/manage-ejabberd
with:
for: prod
do: compile
tool: mix
- run: make hooks
- run: make options
+9 -15
View File
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-24.04-arm
strategy:
matrix:
otp: ['25', '26', '27', '28']
otp: ['25', '26', '27', '28', '29.0-rc1']
steps:
@@ -21,9 +21,6 @@ jobs:
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ matrix.otp }}
hexpm-mirrors: |
https://cdn.jsdelivr.net/hex
https://builds.hex.pm
- uses: awalsh128/cache-apt-pkgs-action@latest
with:
@@ -31,16 +28,12 @@ jobs:
libsqlite3-dev libwebp-dev libyaml-dev
- name: Compile
run: |
./autogen.sh
./configure --with-rebar=./rebar3 \
--prefix=/tmp/ejabberd \
--enable-all \
--disable-elixir \
--disable-mssql \
--disable-odbc
sed -i 's|, syntax_tools||g' src/ejabberd.app.src.script
make
uses: ./.github/actions/manage-ejabberd
with:
for: prod
do: compile
tool: rebar3
configure: --disable-elixir --disable-mssql
########################################################## Static Tests #####
@@ -62,7 +55,7 @@ jobs:
- run: make options
- run: make xref
- run: make dialyzer
- run: make test-eunit
- run: make testeunit
- run: make elvis
if: matrix.otp > '25'
@@ -121,6 +114,7 @@ jobs:
run: make test
- name: Send to coveralls
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
+3
View File
@@ -47,3 +47,6 @@ Mnesia.nonode@nohost/
/ejabberd-*.rpm
/ejabberd-*.run
/ejabberd-*.tar.gz
/priv/mod_invites/static/bootstrap/
/priv/mod_invites/static/jquery/
/node_modules/
+89
View File
@@ -1,3 +1,92 @@
## Version 26.03
#### Core
- Fix mysql authentication for tls connections that required auth plugin switch
- Improve handling of scram "wanted to use channel-bindings but was not offered one" flag
- Add ability for mod_options values to depend on other options
- Don't fail to classify stand-alone chat states
- Fix some warnings compiling with Erlang/OTP 29 ([#4527](https://github.com/processone/ejabberd/issues/4527))
- `ejabberd_ctl`: Document how to set empty lists in ejabberdctl and WebAdmin
- `ejabberd_http`: Add handling of `Etag` and `If-Modified-Since` headers to files served by `mod_http_upload`
- `ejabberd_http`: Ignore whitespaces at end of host header
- SQL: Add ability to mark that column can be null in e_sql_schema
- Tests: Add tests for sasl2
- Tests: Make table cleanup in test more robust
#### Modules
- `mod_fast_auth`: Offered methods are based on available channel bindings
- `mod_http_api`: Always hide password in log entries
- `mod_mam`: Call `store_mam_message` hook for messages that `user_mucsub_from_muc_archive` was filtering out
- `mod_mam_sql`: Only provide the new XEP-0431 `fulltext` field, not old custom `withtext`
- `mod_muc_room`: Fix duplicate stanza-id in muc mam responses generated from local history ([#4544](https://github.com/processone/ejabberd/issues/4544))
- `mod_muc_room`: Fix hook name in commit 7732984 ([#4526](https://github.com/processone/ejabberd/issues/4526))
- `mod_pubsub_serverinfo`: Don't use `gen_server:call` for resolving pubsub host
- `mod_roster`: Add support for roster pre-approval ([#4512](https://github.com/processone/ejabberd/issues/4512))
- `mod_roster`: Fix display of groups in WebAdmin when it's a list
- `mod_roster`: in WebAdmin page, first execute SET actions, later GET
- `mod_roster_mnesia`: Improve transformation code
#### mod_invites
- Makefile: Run invites-deps only when files are missing
- Fix path to bootstrap files
- Check at start time the syntax of landing_page option ([#4525](https://github.com/processone/ejabberd/issues/4525))
- Send 'Link' http header ([#4531](https://github.com/processone/ejabberd/issues/4531))
- Set meta.pre-auth to skip redirect_url if token validated ([#4535](https://github.com/processone/ejabberd/issues/4535))
- Many security fixes ([#4539](https://github.com/processone/ejabberd/issues/4539))
- Add favicon and change color to match ejabberd branding
- Enable dark mode
- Add support for webchat_url
- Migrate to bootstrap5 and update jquery
- No inline scripts
- Make format csrf token
- Add csrf token to failed post
- Include js/css deps in static dir
- Correct hashes for bootstrap 4.6.2
- Hint at type for landing_page opt
- Many more security fixes ([#4538](https://github.com/processone/ejabberd/issues/4538))
- Check CSRF token in register form
- Add integrity hashes to scripts and css
- Comment unused resources
- Add security headers
- Remove debug log of whole query parameters (including pw)
- Don't crash on unknown host from http host header
- Make creating invite transactional
- Set overuse limits ([#4540](https://github.com/processone/ejabberd/issues/4540))
- Fix broken path when behind proxy with prefix ([#4547](https://github.com/processone/ejabberd/issues/4547))
#### Container and Installers
- Bump Erlang/OTP 28.4.1
- `make-binaries`: Bump libexpat to 2.7.5
- `make-binaries`: Bump zlib to 1.3.2
- `make-binaries`: Enable missing crypto features ([#4542](https://github.com/processone/ejabberd/issues/4542))
#### Translations
- Update Bulgarian translation
- Update Catalan and Spanish translations
- Update Chinese Simplified translation
- Update Czech translation
- Update French translation
- Update German translation
## Version 26.02
- Fixes issue with adding hats data in presences send by group chats ([#4516](https://github.com/processone/ejabberd/issues/4516))
- Removes `mod_muc_occupantid` modules, and integrates its functionality directly into `mod_muc` ([#4521](https://github.com/processone/ejabberd/issues/4521))
- Fixes issue with reset occupant-id values after restart of ejabberd ([#4521](https://github.com/processone/ejabberd/issues/4521))
- Improves handling of mediated group chat invitations in `mod_block_stranger` ([#4523](https://github.com/processone/ejabberd/issues/4523))
- Properly install `mod_invites` templates in `make install` call ([#4514](https://github.com/processone/ejabberd/issues/4514))
- Better errors in `mod_invites` ([#4515](https://github.com/processone/ejabberd/issues/4515))
- Accessibility improvements in `mod_invites` ([#4524](https://github.com/processone/ejabberd/issues/4524))
- Improves handling of request with invalid url encoded values in request handled by `ejabberd_http`
- Improves handling of invalid responses to disco queries in `mod_pubsub_serverinfo`
- Fixes conversion of MUC room configs from ejabberd older than 21.12
- Fixes to autologin in WebAdmin
## Version 26.01
#### Compile and Start
+1 -1
View File
@@ -1113,7 +1113,7 @@ Let's summarize the differences between both container images. Legend:
| Generated by | [container.yml](https://github.com/processone/ejabberd/blob/master/.github/workflows/container.yml) | [tests.yml](https://github.com/processone/docker-ejabberd/blob/master/.github/workflows/tests.yml) |
| Built for | stable releases <br /> `master` branch | stable releases <br /> [`master` branch zip](https://github.com/processone/docker-ejabberd/actions/workflows/tests.yml) |
| Architectures | `linux/amd64` <br /> `linux/arm64` | `linux/amd64` |
| Software | Erlang/OTP 28.3.1.0-alpine 🟠 <br /> Elixir 1.19.5 🟠 | Alpine 3.22 <br /> Erlang/OTP 26.2 <br /> Elixir 1.18.3 |
| Software | Erlang/OTP 28.4.1.0-alpine 🟠 <br /> Elixir 1.19.5 🟠 | Alpine 3.22 <br /> Erlang/OTP 26.2 <br /> Elixir 1.18.3 |
| Published in | [ghcr.io/processone/ejabberd](https://github.com/processone/ejabberd/pkgs/container/ejabberd) | [docker.io/ejabberd/ecs](https://hub.docker.com/r/ejabberd/ecs/) <br /> [ghcr.io/processone/ecs](https://github.com/processone/docker-ejabberd/pkgs/container/ecs) |
| :black_square_button: **Additional content** |
| [ejabberd-contrib](#ejabberd-contrib) | included | not included |
+1
View File
@@ -26,6 +26,7 @@ We would like to thanks official ejabberd source code contributors:
- Sonny Scroggin
- Alexey Shchepin
- Shelley Shyan
- Stefan Strigler
- Radoslaw Szymczyszyn
- Stu Tomlinson
- Christian Ulrich
+24 -9
View File
@@ -212,7 +212,7 @@ endif
all: scripts deps src
deps: $(DEPSDIR)/.got
deps: $(DEPSDIR)/.got invites-deps
$(DEPSDIR)/.got:
rm -rf $(DEPSDIR)/.got
@@ -224,6 +224,19 @@ $(DEPSDIR)/.got:
$(DEPSDIR)/.built: $(DEPSDIR)/.got
$(REBAR) compile && :> $(DEPSDIR)/.built
ifeq (, $(shell which npm))
INSTALL_INVITES_DEPS=tools/dl_invites_page_deps.sh priv/mod_invites/static
else
INSTALL_INVITES_DEPS=npm install
endif
invites-deps: priv/mod_invites/static/bootstrap/ priv/mod_invites/static/jquery/
priv/mod_invites/static/bootstrap/:
$(INSTALL_INVITES_DEPS)
priv/mod_invites/static/jquery/:
$(INSTALL_INVITES_DEPS)
src: $(DEPSDIR)/.built
$(REBAR) $(SKIPDEPS) compile
$(EXPLICIT_ELIXIR_COMPILE)
@@ -234,13 +247,13 @@ update:
$(REBAR) $(UPDATEDEPS) && :> $(DEPSDIR)/.got
$(CONFIGURE_DEPS)
xref: all
xref: src
$(REBAR) $(SKIPDEPS) xref $(XREFOPTIONS)
hooks: all
hooks: src
tools/hook_deps.sh $(EBINDIR)
options: all
options: src
tools/opt_types.sh ejabberd_option $(EBINDIR)
translations:
@@ -311,8 +324,9 @@ BINARIES=$(DEPSDIR)/epam/priv/bin/epam $(DEPSDIR)/eimp/priv/bin/eimp $(DEPSDIR)/
DEPS_FILES_FILTERED=$(filter-out $(BINARIES) $(DEPSDIR)/elixir/ebin/elixir.app,$(DEPS_FILES))
DEPS_DIRS=$(sort $(DEPSDIR)/ $(foreach DEP,$(DEPS),$(DEPSDIR)/$(DEP)/) $(dir $(DEPS_FILES)))
MAIN_FILES=$(filter-out %/configure.beam,$(call FILES_WILDCARD,$(EBINDIR)/*.beam $(EBINDIR)/*.app priv/msgs/*.msg priv/css/*.css priv/img/*.png priv/js/*.js priv/lib/* include/*.hrl COPYING))
MAIN_DIRS=$(sort $(dir $(MAIN_FILES)) priv/bin priv/sql priv/lua)
MAIN_FILES=$(filter-out %/configure.beam,$(call FILES_WILDCARD,$(EBINDIR)/*.beam $(EBINDIR)/*.app priv/msgs/*.msg priv/css/*.css priv/img/*.png priv/js/*.js priv/lib/* priv/mod_invites/* priv/mod_invites/static/* priv/mod_invites/static/bootstrap/css/bootstrap.min.css priv/mod_invites/static/bootstrap/js/bootstrap.min.js priv/mod_invites/static/jquery/jquery.min.js \
priv/mod_invites/static/logos/* include/*.hrl COPYING))
MAIN_DIRS=$(sort $(dir $(MAIN_FILES)) priv/bin priv/sql priv/lua priv/mod_invites)
define DEP_VERSION_template
DEP_$(1)_VERSION:=$(shell $(SED) -e '/vsn/!d;s/.*, *"/$(1)-/;s/".*//' $(2) 2>/dev/null)
@@ -553,6 +567,7 @@ clean:
rm -rf test/*.beam
rm -f rebar.lock
rm -f ejabberdctl.example ejabberd.init ejabberd.service
rm -rf priv/mod_invites/static/{jquery,bootstrap4}
$(REBAR) clean $(CLEANARG)
clean-rel:
@@ -682,7 +697,7 @@ group_to_test := $(patsubst test-%,%,$(filter test-%,$(MAKECMDGOALS)))
$(eval $(call test-group-target,$(group_to_test)))
endif
test-eunit:
testeunit:
$(REBAR) $(SKIPDEPS) eunit --verbose
#.
@@ -690,7 +705,7 @@ test-eunit:
#
.PHONY: src edoc dialyzer Makefile TAGS clean clean-rel distclean prod rel \
install uninstall uninstall-binary uninstall-all translations deps test test-eunit \
install uninstall uninstall-binary uninstall-all translations deps test testeunit \
all dev doap help install-rel relive scripts uninstall-rel update \
erlang_plt deps_plt ejabberd_plt xref hooks options format indent
@@ -732,7 +747,7 @@ help:
@echo " elvis Run Elvis source code style reviewer [rebar3]"
@echo " hooks Run hooks validator"
@echo " test Run Common Tests suite [rebar3]"
@echo " test-eunit Run EUnit suite [rebar3]"
@echo " testeunit Run EUnit suite [rebar3]"
@echo " test-<group> Run Common Test suite for specific group only [rebar3]"
@echo " xref Run cross reference analysis [rebar3]"
+1 -1
View File
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59)
AC_INIT(ejabberd, m4_esyscmd([echo `git describe --tags 2>/dev/null || echo 26.01` | sed 's/-g.*//;s/-/./' | tr -d '\012']), [ejabberd@process-one.net], [ejabberd])
AC_INIT(ejabberd, m4_esyscmd([echo `git describe --tags 2>/dev/null || echo 26.03` | sed 's/-g.*//;s/-/./' | tr -d '\012']), [ejabberd@process-one.net], [ejabberd])
AC_ARG_WITH(min-erlang,
AS_HELP_STRING([--with-min-erlang=version],[set minimal required erlang version, default to OTP25]),
+1 -1
View File
@@ -770,7 +770,7 @@
<xmpp:version>1.0.1</xmpp:version>
<xmpp:since>23.10</xmpp:since>
<xmpp:status>complete</xmpp:status>
<xmpp:note>mod_muc_occupantid</xmpp:note>
<xmpp:note>mod_muc</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
-1
View File
@@ -205,7 +205,6 @@ modules:
default_room_options:
mam: true
mod_muc_admin: {}
mod_muc_occupantid: {}
mod_offline:
access_max_user_messages: max_user_offline_messages
mod_ping: {}
+1
View File
@@ -44,6 +44,7 @@
binary(),
[binary()],
both | from | to | none,
boolean(),
subscribe | unsubscribe | both | in | out | none,
binary()}]
}).
+1
View File
@@ -51,6 +51,7 @@
meta = #{}}).
-record(sql_column, {name :: binary(),
type,
nullable = false,
default = false,
opts = []}).
-record(sql_table, {name :: binary(),
+5
View File
@@ -4,6 +4,11 @@
-define(NS_INVITE_INVITE, <<"urn:xmpp:invite#invite">>).
-define(NS_INVITE_CREATE_ACCOUNT, <<"urn:xmpp:invite#create-account">>).
-define(OVERUSE_LIMIT, 1000).
-define(SPEEDY_GOAT_LEVELS, 2).
-define(SPEEDY_GOAT_SECONDS, 300).
-record(invite_token, {token :: binary(),
inviter :: {binary(), binary()},
%% A non-empty value if `invitee` indicates the invite has been used.
+4 -2
View File
@@ -81,7 +81,8 @@
role :: role(),
%%is_subscriber = false :: boolean(),
%%subscriptions = [] :: [binary()],
last_presence :: presence() | undefined
last_presence :: presence() | undefined,
occupant_id :: binary()
}).
-record(subscriber, {jid :: jid(),
@@ -132,7 +133,8 @@
activity = treap:empty() :: treap:treap(),
room_shaper = none :: ejabberd_shaper:shaper(),
room_queue :: p1_queue:queue({message | presence, jid()}) | undefined,
hibernate_timer = none :: reference() | none | hibernating
hibernate_timer = none :: reference() | none | hibernating,
salt = <<>> :: binary()
}).
-type users() :: #{ljid() => #user{}}.
+1
View File
@@ -25,6 +25,7 @@
jid = {<<>>, <<>>, <<>>} :: jid:ljid(),
name = <<>> :: binary() | '_',
subscription = none :: subscription() | '_',
approved = false :: boolean() | '_',
ask = none :: ask() | '_',
groups = [] :: [binary()] | '_',
askmessage = <<"">> :: binary() | '_',
+95 -110
View File
@@ -2,12 +2,12 @@
.\" Title: ejabberd.yml
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets vsnapshot <http://docbook.sf.net/>
.\" Date: 01/21/2026
.\" Date: 03/25/2026
.\" Manual: \ \&
.\" Source: \ \&
.\" Language: English
.\"
.TH "EJABBERD\&.YML" "5" "01/21/2026" "\ \&" "\ \&"
.TH "EJABBERD\&.YML" "5" "03/25/2026" "\ \&" "\ \&"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
@@ -82,12 +82,12 @@ All options can be changed in runtime by running \fIejabberdctl reload\-config\f
.sp
Some options can be specified for particular virtual host(s) only using \fIhost_config\fR or \fIappend_host_config\fR options\&. Such options are called \fIlocal\fR\&. Examples are \fImodules\fR, \fIauth_method\fR and \fIdefault_db\fR\&. The options that cannot be defined per virtual host are called \fIglobal\fR\&. Examples are \fIloglevel\fR, \fIcertfiles\fR and \fIlisten\fR\&. It is a configuration mistake to put \fIglobal\fR options under \fIhost_config\fR or \fIappend_host_config\fR section \- ejabberd will refuse to load such configuration\&.
.sp
It is not recommended to write ejabberd\&.yml from scratch\&. Instead it is better to start from "default" configuration file available at https://github\&.com/processone/ejabberd/blob/26\&.01/ejabberd\&.yml\&.example\&. Once you get ejabberd running you can start changing configuration options to meet your requirements\&.
It is not recommended to write ejabberd\&.yml from scratch\&. Instead it is better to start from "default" configuration file available at https://github\&.com/processone/ejabberd/blob/26\&.03/ejabberd\&.yml\&.example\&. Once you get ejabberd running you can start changing configuration options to meet your requirements\&.
.sp
Note that this document is intended to provide comprehensive description of all configuration options that can be consulted to understand the meaning of a particular option, its format and possible values\&. It will be quite hard to understand how to configure ejabberd by reading this document only \- for this purpose the reader is recommended to read online Configuration Guide available at https://docs\&.ejabberd\&.im/admin/configuration\&.
.SH "TOP LEVEL OPTIONS"
.sp
This section describes top level options of ejabberd 26\&.01\&. The options that changed in this version are marked with 🟤\&.
This section describes top level options of ejabberd 26\&.03\&. The options that changed in this version are marked with 🟠\&.
.PP
\fBaccess_rules\fR: \fI{AccessName: {allow|deny: ACLName|ACLDefinition}}\fR
.RS 4
@@ -1556,7 +1556,7 @@ This is a global option for module
\fI600 seconds\fR\&.
.RE
.PP
\fBreplaced_connection_timeout 🟤\fR: \fItimeout()\fR
\fBreplaced_connection_timeout\fR: \fItimeout()\fR
.RS 4
\fINote\fR
about this option: added in 26\&.01\&. Maximum time that new session will wait for termination of session that it\(cqs replacing\&. This allows old session to properly sends its unavailable presences, and helps with potetnial race conditions between old and new sessions presences\&.
@@ -1980,14 +1980,14 @@ if the latter is not set\&.
\fINote\fR
about this option: renamed in 25\&.10\&. Whether to use the
\fIdatabase\&.md#singlehost\-or\-multihost|multihost SQL schema\fR\&. All schemas are located at
https://github\&.com/processone/ejabberd/tree/26\&.01/sql\&. There are two schemas available\&. The legacy
https://github\&.com/processone/ejabberd/tree/26\&.03/sql\&. There are two schemas available\&. The legacy
\fIsinglehost\fR
schema stores one XMPP domain into one ejabberd database\&. The
\fImultihost\fR
schema can handle several XMPP domains in a single ejabberd database\&. The
\fImultihost\fR
schema is preferable when serving several XMPP domains and/or changing domains from time to time\&. This avoid need to manage several databases and handle complex configuration changes\&. The default depends on
\fI\&.\&./install/source\&.md#configure|\&./configure\fR
\fI\&.\&./\&.\&./admin/install/source\&.md#configure|\&./configure\fR
flag
\fI\-\-enable\-sql\-schema\-multihost\fR
which is set at compile time\&.
@@ -2142,7 +2142,7 @@ seconds\&.
.RE
.SH "MODULES"
.sp
This section describes modules options of ejabberd 26\&.01\&. The modules that changed in this version are marked with 🟤\&.
This section describes modules options of ejabberd 26\&.03\&. The modules that changed in this version are marked with 🟠\&.
.SS "mod_adhoc"
.sp
def:ad\-hoc command
@@ -2310,8 +2310,6 @@ ejabberdctl srg_create g1 example\&.org "\*(AqGroup number 1\*(Aq" this_is_g1 g1
This module can be used to convert your existing SQL database from the singlehost to the multihost schema\&. Check the section \fIdatabase\&.md#singlehost\-or\-multihost|Singlehost or Multihost\fR for details\&. Please note that only MS SQL, MySQL, and PostgreSQL are supported\&. When the module is loaded use \fIupdate_sql\fR API\&.
.sp
The module has no options\&.
.sp
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#sql|sql\fR
.SS "mod_announce"
.sp
This module enables configured users to broadcast announcements and to set the message of the day (MOTD)\&. Configured users can perform these actions with an XMPP client either using Ad\-Hoc Commands or sending messages to specific JIDs\&. Equivalent API commands are also available\&.
@@ -2629,8 +2627,6 @@ modules:
.if n \{\
.RE
.\}
.sp
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#spam|spam\fR
.RE
.SS "mod_auth_fast"
.sp
@@ -3193,6 +3189,8 @@ Make sure either \fImod_bosh\fR or \fIlisten\&.md#ejabberd_http_ws|ejabberd_http
.sp
When \fIconversejs_css\fR and \fIconversejs_script\fR are \fIauto\fR, by default they point to the public Converse client\&.
.sp
When this module is enabled in \fImodules\fR, it adds automatically a requesthandler and link in WebAdmin\&. \&.
.sp
This module is available since ejabberd 21\&.12\&.
.sp
.it 1 an-trap
@@ -3713,7 +3711,7 @@ modules:
.RE
.\}
.RE
.SS "mod_http_fileserver 🟤"
.SS "mod_http_fileserver"
.sp
\fINote\fR about this option: improved \fIdocroot\fR in 26\&.01\&.
.sp
@@ -3803,7 +3801,7 @@ Indicate one or more directory index files, similarly to Apache\(cqs
variable\&. When an HTTP request hits a directory instead of a regular file, those directory indices are looked in order, and the first one found is returned\&. The default value is an empty list\&.
.RE
.PP
\fBdocroot 🟤\fR: \fIPathDir | {PathURL, PathDir}\fR
\fBdocroot\fR: \fIPathDir | {PathURL, PathDir}\fR
.RS 4
\fINote\fR
about this option: improved in 26\&.01\&. Directory to serve the files from, or a map with several URL path (as specified in
@@ -3882,7 +3880,7 @@ modules:
.RE
.\}
.RE
.SS "mod_http_upload 🟤"
.SS "mod_http_upload"
.sp
\fINote\fR about this option: added \fIcontent_types\fR in 26\&.01\&.
.sp
@@ -3904,7 +3902,7 @@ This option defines the access rule to limit who is permitted to use the HTTP up
\fIlocal\fR\&. If no access rule of that name exists, no user will be allowed to use the service\&.
.RE
.PP
\fBcontent_types 🟤\fR: \fI{Extension: Type}\fR
\fBcontent_types\fR: \fI{Extension: Type}\fR
.RS 4
\fINote\fR
about this option: added in 26\&.01\&. Specify mappings of extension to content type, similarly to the option
@@ -4173,9 +4171,9 @@ modules:
.RE
.\}
.RE
.SS "mod_invites 🟤"
.SS "mod_invites 🟠"
.sp
\fINote\fR about this option: added in 26\&.01\&.
\fINote\fR about this option: improved in 26\&.03\&.
.sp
Allow User Invitation and Account Creation to create out\-of\-band links to onboard others onto the XMPP network and establish a mutual subscription\&. This implements XEP\-0379: Pre\-Authenticated Roster Subscription, XEP\-0401: Ad\-hoc Account Invitation Generation, and XEP\-0445: Pre\-Authenticated In\-Band Registration\&.
.sp
@@ -4183,80 +4181,7 @@ These invitations are created as XMPP URIs either via ad\-hoc commands or via AP
.sp
The receiving user should have installed a client that supports those invitations\&. Since this has proven to be a common obstacle for easy adoption, this module comes with an optional landing page parameter, that can either be some external service like an installation of easy\-xmpp\-invitation, a third\-party service like JoinJabber or for convenience a built\-in service\&. This landing page will then guide the recipient with setting up a client and creating an account if required\&.
.sp
In order to use the included landing page feature, you have to
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
have a copy of
jQuery 3
and
Bootstrap 4
in a shared directory on your system\&. If you\(cqre using Debian or derivatives this is easiest accomplished by installing both
libjs\-jquery
and
libjs\-bootstrap4
which will put them under
/usr/share/javascript/{jquery,bootstrap4}
.RE
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
in
ejabberd\&.yml
configure a listener for module
ejabberd_http
with a request handler for
/share: mod_http_fileserver
.RE
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
in the
modules
section configure
mod_http_fileserver
so that
docroot
points to the shared directory from above (e\&.g\&.
docroot: /usr/share/javascript)
.RE
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
configure
mod_invites
and set
landing_page
to either
auto
or an URL template like
https://{{ host }}/invites/{{ invite\&.token }}
if your server setup includes a so called reverse proxy
.RE
In order to use the included landing page feature, you have to set landing_page to either auto or an URL template like https://{{ host }}/invites/{{ invite\&.token }} if your server setup includes a so called reverse proxy\&.
.sp
If you\(cqd rather want to use an external service, set landing_page to something like http://{{ host }}:8080/easy\-xmpp\-invites/#{{ invite\&.uri|strip_protocol }} or https://invites\&.joinjabber\&.org/#{{ invite\&.uri|strip_protocol }}\&.
.sp
@@ -4335,7 +4260,7 @@ A human readable name for your site\&. E\&.g\&.
"My Beautiful Laundrette"\&. Used in landing page templates\&.
.RE
.PP
\fBtemplates_dir\fR: \fIbinary()\fR
\fBtemplates_dir\fR: \fIPath\fR
.RS 4
The directory containing templates and static files used for landing page and web registration form\&. Only needs to be set if you want to ship your own set of templates or list of recommended apps\&.
.RE
@@ -4347,6 +4272,17 @@ Number of seconds until token expires\&. Default value is
(that is five days:
5 * 24 * 60 * 60)
.RE
.PP
\fBwebchat_url 🟠\fR: \fInone | auto | Webchat URL\fR
.RS 4
\fINote\fR
about this option: added in 26\&.03\&. URL to a webchat client\&. Upon manual registration through web\-form this will be recommended in order to get started\&. If
auto
is chosen, we pick the
mod_conversejs
from the listeners section\&. Default is
auto\&.
.RE
.RE
.sp
.it 1 an-trap
@@ -4369,11 +4305,8 @@ listen:
module: ejabberd_http
request_handlers:
/invites: mod_invites
/share: mod_http_fileserver
# [\&.\&.\&.]
modules:
mod_http_fileserver:
docroot: /usr/share/javascript
mod_invites:
landing_page: auto
.fi
@@ -5176,7 +5109,66 @@ modules:
.RE
.SS "mod_muc"
.sp
This module provides support for XEP\-0045: Multi\-User Chat\&. Users can discover existing rooms, join or create them\&. Occupants of a room can chat in public or have private chats\&.
\fINote\fR about this option: incorporated \fImod_muc_occupantid\fR in 26\&.02\&.
.sp
This module provides support for Multi\-User Chat (MUC)\&. Users can discover existing rooms, join or create them\&. Occupants of a room can chat in public or have private chats\&.
.sp
Protocols implemented in this module:
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
XEP\-0045: Multi\-User Chat
.RE
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
XEP\-0249: Direct MUC Invitations
.RE
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
XEP\-0421: Occupant identifiers for semi\-anonymous MUCs
.RE
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
XEP\-0486: MUC Avatars
.RE
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
Muc/Sub: Multi\-User Chat Subscriptions
.RE
.sp
The MUC service allows any Jabber ID to register a nickname, so nobody else can use that nickname in any room in the MUC service\&. To register a nickname, open the Service Discovery in your XMPP client and register in the MUC service\&.
.sp
@@ -5933,15 +5925,6 @@ or a conference JID is appended to the
otherwise\&. There is no default value\&.
.RE
.RE
.SS "mod_muc_occupantid"
.sp
\fINote\fR about this option: added in 23\&.10\&.
.sp
This module implements XEP\-0421: Anonymous unique occupant identifiers for MUCs\&.
.sp
When the module is enabled, the feature is enabled in all semi\-anonymous rooms\&.
.sp
The module has no options\&.
.SS "mod_muc_rtbl"
.sp
\fINote\fR about this option: added in 23\&.04\&.
@@ -7674,7 +7657,9 @@ This module supports \fIbasic\&.md#captcha|CAPTCHA\fR to register a new account\
.sp
As an example usage, the users of the host \fIlocalhost\fR can visit the page: \fIhttps://localhost:5280/register/\fR It is important to include the last / character in the URL, otherwise the subpages URL will be incorrect\&.
.sp
This module is enabled in \fIlisten\fR → \fIejabberd_http\fR → \fIlisten\-options\&.md#request_handlers|request_handlers\fR, no need to enable in \fImodules\fR\&. The module depends on \fImod_register\fR where all the configuration is performed\&.
This module is enabled in \fIlisten\fR → \fIejabberd_http\fR → \fIlisten\-options\&.md#request_handlers|request_handlers\fR\&.
.sp
There is no need to enable this module in \fImodules\fR, but it adds a link to the register page in WebAdmin menu\&. The module depends on \fImod_register\fR where all the configuration is performed\&.
.sp
The module has no options\&.
.sp
@@ -9330,7 +9315,7 @@ Should the operating system be revealed or not\&. The default value is
.RE
.SH "LISTENERS"
.sp
This section describes listeners options of ejabberd 26\&.01\&.
This section describes listeners options of ejabberd 26\&.03\&.
.sp
TODO
.SH "AUTHOR"
@@ -9338,13 +9323,13 @@ TODO
ProcessOne\&.
.SH "VERSION"
.sp
This document describes the configuration file of ejabberd 26\&.01\&. Configuration options of other ejabberd versions may differ significantly\&.
This document describes the configuration file of ejabberd 26\&.03\&. Configuration options of other ejabberd versions may differ significantly\&.
.SH "REPORTING BUGS"
.sp
Report bugs to https://github\&.com/processone/ejabberd/issues
.SH "SEE ALSO"
.sp
Default configuration file: https://github\&.com/processone/ejabberd/blob/26\&.01/ejabberd\&.yml\&.example
Default configuration file: https://github\&.com/processone/ejabberd/blob/26\&.03/ejabberd\&.yml\&.example
.sp
Main site: https://ejabberd\&.im
.sp
+6 -4
View File
@@ -84,6 +84,7 @@ defmodule Ejabberd.MixProject do
includes = ["include", deps_include()]
result = [{:d, :ELIXIR_ENABLED}] ++
cond_options() ++
if Mix.env == :test do [{:d, :TEST}] else [] end ++
Enum.map(includes, fn (path) -> {:i, path} end) ++
if_version_below(~c"26", [{:d, :OTP_BELOW_26}]) ++
if_version_below(~c"27", [{:d, :OTP_BELOW_27}]) ++
@@ -108,19 +109,19 @@ defmodule Ejabberd.MixProject do
[{:cache_tab, "~> 1.0"},
{:dialyxir, "~> 1.2", only: [:test], runtime: false},
{:eimp, "~> 1.0"},
{:erlydtl, git: "https://github.com/erlydtl/erlydtl", tag: "0.15.0", override: true},
{:erlydtl, "~> 0.14.0"},
{:ex_doc, "~> 0.31", only: [:edoc], runtime: false},
{:fast_tls, "~> 1.1.24"},
{:fast_xml, "~> 1.1.56"},
{:fast_yaml, "~> 1.0"},
{:idna, "~> 6.0"},
{:idna, "~> 7.1"},
{:mqtree, "~> 1.0"},
{:p1_acme, ">= 1.0.28"},
{:p1_oauth2, "~> 0.6"},
{:p1_utils, "~> 1.0"},
{:pkix, "~> 1.0"},
{:stringprep, ">= 1.0.26"},
{:xmpp, ">= 1.12.0"},
{:xmpp, ">= 1.13.1"},
{:yconf, ">= 1.0.22"}]
++ cond_deps()
end
@@ -149,7 +150,7 @@ defmodule Ejabberd.MixProject do
{if_version_below(~c"26", true), {:jose, "1.11.10", override: true}},
{if_version_above(~c"25", true), {:jose, "~> 1.11.12"}},
{config(:lua), {:luerl, "~> 1.2.0"}},
{config(:mysql), {:p1_mysql, ">= 1.0.27"}},
{config(:mysql), {:p1_mysql, ">= 1.0.28"}},
{config(:pgsql), {:p1_pgsql, ">= 1.1.38"}},
{config(:sqlite), {:sqlite3, "~> 1.1"}},
{config(:stun), {:stun, "~> 1.0"}}], do:
@@ -334,6 +335,7 @@ defmodule Ejabberd.MixProject do
"CONTRIBUTORS.md": [title: "Contributors"],
"CODE_OF_CONDUCT.md": [title: "Code of Conduct"],
"CHANGELOG.md": [title: "ChangeLog"],
"SECURITY.md": [title: "Security Policy"],
"COPYING": [title: "Copying License"],
"_build/edoc/docs.md": [title: "&xrArr; ejabberd Docs"]
],
+18 -24
View File
@@ -1,40 +1,34 @@
%{
"base64url": {:hex, :base64url, "1.0.1", "f8c7f2da04ca9a5d0f5f50258f055e1d699f0e8bf4cfdb30b750865368403cf6", [:rebar3], [], "hexpm", "f9b3add4731a02a9b0410398b475b33e7566a695365237a6bdee1bb447719f5c"},
"cache_tab": {:hex, :cache_tab, "1.0.33", "e2542afb34f17ee3ca19d2b0f546a074922c2b99fb6b2acfb38160d7d0336ec3", [:rebar3], [{:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "4258009eb050b22aabe0c848e230bba58401a6895c58c2ff74dfb635e3c35900"},
"cache_tab": {:hex, :cache_tab, "1.0.34", "935902ac8300d30f17c24f4d4056175f053ccc0572acab5fb4d1ce503831bee1", [:rebar3], [{:p1_utils, "1.0.29", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "0db9f317f3941c17c9f8ea8125e25efa27bbed4cbf24a42c426fada74c82b692"},
"dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"},
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
"eimp": {:hex, :eimp, "1.0.26", "c0b05f32e35629c4d9bcfb832ff879a92b0f92b19844bc7835e0a45635f2899a", [:rebar3], [{:p1_utils, "~> 1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "d96d4e8572b9dfc40f271e47f0cb1d8849373bc98a21223268781765ed52044c"},
"epam": {:hex, :epam, "1.0.14", "aa0b85d27f4ef3a756ae995179df952a0721237e83c6b79d644347b75016681a", [:rebar3], [], "hexpm", "2f3449e72885a72a6c2a843f561add0fc2f70d7a21f61456930a547473d4d989"},
"eimp": {:hex, :eimp, "1.0.27", "14bf9e6fac791d0c098708cbc5a0928746ed575869f4ce42a266643947790a3a", [:rebar3], [{:p1_utils, "~> 1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "3c7e83e293bcfaf50a1bf054fc4b62b7b8c484a6e4218e397709a4d0d857f3fc"},
"eredis": {:hex, :eredis, "1.7.1", "39e31aa02adcd651c657f39aafd4d31a9b2f63c6c700dc9cece98d4bc3c897ab", [:mix, :rebar3], [], "hexpm", "7c2b54c566fed55feef3341ca79b0100a6348fd3f162184b7ed5118d258c3cc1"},
"erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"},
"erlydtl": {:git, "https://github.com/erlydtl/erlydtl", "aae414692b6052e96d890e03bbeeeca0f4dc01c2", [tag: "0.15.0"]},
"esip": {:hex, :esip, "1.0.59", "eb202f8c62928193588091dfedbc545fe3274c34ecd209961f86dcb6c9ebce88", [:rebar3], [{:fast_tls, "1.1.25", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.2.21", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm", "0bdf2e3c349dc0b144f173150329e675c6a51ac473d7a0b2e362245faad3fbe6"},
"ex_doc": {:hex, :ex_doc, "0.39.3", "519c6bc7e84a2918b737aec7ef48b96aa4698342927d080437f61395d361dcee", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "0590955cf7ad3b625780ee1c1ea627c28a78948c6c0a9b0322bd976a079996e1"},
"erlydtl": {:hex, :erlydtl, "0.14.0", "964b2dc84f8c17acfaa69c59ba129ef26ac45d2ba898c3c6ad9b5bdc8ba13ced", [:rebar3], [], "hexpm", "d80ec044cd8f58809c19d29ac5605be09e955040911b644505e31e9dd8143431"},
"ex_doc": {:hex, :ex_doc, "0.40.1", "67542e4b6dde74811cfd580e2c0149b78010fd13001fda7cfeb2b2c2ffb1344d", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "bcef0e2d360d93ac19f01a85d58f91752d930c0a30e2681145feea6bd3516e00"},
"exsync": {:hex, :exsync, "0.4.1", "0a14fe4bfcb80a509d8a0856be3dd070fffe619b9ba90fec13c58b316c176594", [:mix], [{:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "cefb22aa805ec97ffc5b75a4e1dc54bcaf781e8b32564bf74abbe5803d1b5178"},
"ezlib": {:hex, :ezlib, "1.0.15", "d74f5df191784744726a5b1ae9062522c606334f11086363385eb3b772d91357", [:rebar3], [{:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "dd14ba6c12521af5cfe6923e73e3d545f4a0897dc66bfab5287fbb7ae3962eab"},
"fast_tls": {:hex, :fast_tls, "1.1.25", "da8ed6f05a2452121b087158b17234749f36704c1f2b74dc51db99a1e27ed5e8", [:rebar3], [{:p1_utils, "~> 1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "59e183b5740e670e02b8aa6be673b5e7779e5fe5bfcc679fe2d4993d1949a821"},
"fast_xml": {:hex, :fast_xml, "1.1.57", "31efc0f9bceda92069704f7a25830407da5dc3dad1272b810d6f2e13e73cc11a", [:rebar3], [{:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "eec34e90adacafe467d5ddab635a014ded73b98b4061554b2d1972173d929c39"},
"fast_yaml": {:hex, :fast_yaml, "1.0.39", "2e71168091949bab0e5f583b340a99072b4d22d93eb86624e7850a12b1517be4", [:rebar3], [{:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "24c7b9ab9e2b9269d64e45f4a2a1280966adb17d31e63365cfd3ee277fb0a78d"},
"ezlib": {:hex, :ezlib, "1.0.16", "8e0f209cb61db4034a3e7c920f21dd36330ba45884e707746898d5ee0db487c1", [:rebar3], [{:p1_utils, "1.0.29", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "b4819540403d1ecb7eae645fdff142a8db2b476d893288d39babb92922250405"},
"fast_tls": {:hex, :fast_tls, "1.1.26", "5e46246020f9338c0661254eee0ff98e8b253a2331b3b51afb4eb8b0b3cf41ff", [:rebar3], [{:p1_utils, "~> 1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "6b0d4dd2309037565ebaa9acf39f02808f5f8215a39101a1da33c2a5b1b59b3f"},
"fast_xml": {:hex, :fast_xml, "1.1.58", "1fcfdfb58c4278dc1a163e68d1f83ba165553b1a68d48bf7e9954bb4fbf8571e", [:rebar3], [{:p1_utils, "1.0.29", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "a36d6e03a398c53ba189e912bf4c7559a3704ac63c5050f126d47414018b4ca0"},
"fast_yaml": {:hex, :fast_yaml, "1.0.40", "053109abed40a80fb1e4a231ebfed39e93a4872d73e462fe9a2128503b6233d8", [:rebar3], [{:p1_utils, "1.0.29", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "27705c29902c1c6f3268ba9bf387d5fa928b2e655b53110fa47b8e476d32b386"},
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jiffy": {:hex, :jiffy, "1.1.2", "a9b6c9a7ec268e7cf493d028f0a4c9144f59ccb878b1afe42841597800840a1b", [:rebar3], [], "hexpm", "bb61bc42a720bbd33cb09a410e48bb79a61012c74cb8b3e75f26d988485cf381"},
"idna": {:hex, :idna, "7.1.0", "1067a13043538129602d2f2ce6899d8713125c7d19734aa557ce2e3ea55bd4f1", [:rebar3], [], "hexpm", "6ae959a025bf36df61a8cab8508d9654891b5426a84c44d82deaffd6ddf8c71f"},
"jose": {:hex, :jose, "1.11.12", "06e62b467b61d3726cbc19e9b5489f7549c37993de846dfb3ee8259f9ed208b3", [:mix, :rebar3], [], "hexpm", "31e92b653e9210b696765cdd885437457de1add2a9011d92f8cf63e4641bab7b"},
"luerl": {:hex, :luerl, "1.2.3", "df25f41944e57a7c4d9ef09d238bc3e850276c46039cfc12b8bb42eccf36fcb1", [:rebar3], [], "hexpm", "1b4b9d0ca5d7d280d1d2787a6a5ee9f5a212641b62bff91556baa53805df3aed"},
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
"makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
"makeup_erlang": {:hex, :makeup_erlang, "1.0.3", "4252d5d4098da7415c390e847c814bad3764c94a814a0b4245176215615e1035", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "953297c02582a33411ac6208f2c6e55f0e870df7f80da724ed613f10e6706afd"},
"mqtree": {:hex, :mqtree, "1.0.19", "d769c25f898810725fc7db0dbffe5f72098647048b1be2e6d772f1c2f31d8476", [:rebar3], [{:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "c81065715c49a1882812f80a5ae2d842e80dd3f2d130530df35990248bf8ce3c"},
"mqtree": {:hex, :mqtree, "1.0.20", "aec4b0299dfe3680014d32fd44069f8273867ce8c7d8f09492d2befdddd98598", [:rebar3], [{:p1_utils, "1.0.29", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "5ec0e07b9c48a7840649600bf96e140b4fbe9e5017e1ddd3e898e15c0a383ed0"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
"p1_acme": {:hex, :p1_acme, "1.0.30", "8ce900dac15e53983b96925fbff0b519d85322fdc0a7479b7c6701d44be664c1", [:rebar3], [{:base64url, "~> 1.0", [hex: :base64url, repo: "hexpm", optional: false]}, {:idna, "~> 6.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jiffy, "~> 1.1.1", [hex: :jiffy, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}, {:yconf, "~> 1.0.17", [hex: :yconf, repo: "hexpm", optional: false]}], "hexpm", "2935e20916b806d3b1166953a4d90a690fd80781096140fe5ca1f65d59ebd68a"},
"p1_mysql": {:hex, :p1_mysql, "1.0.27", "e42eee7e9329ab762fe6ac9d47d9dc72923936f73a9a0b18f6825e825e44b366", [:rebar3], [], "hexpm", "066051f240027a76732547e69d96a0974e70dc14ded9453bef263221841eda9b"},
"p1_acme": {:hex, :p1_acme, "1.0.31", "d504620b4cb3349c9389f169481c5d88a149a44def58f4b52c6f0036cac314d0", [:rebar3], [{:idna, "~> 7.1", [hex: :idna, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.12", [hex: :jose, repo: "hexpm", optional: false]}, {:yconf, "~> 1.0.22", [hex: :yconf, repo: "hexpm", optional: false]}], "hexpm", "9b5922a90ab94da5889e64de5ccad3adc6e68a6d3c3fb708dd503757b232db1c"},
"p1_mysql": {:hex, :p1_mysql, "1.0.28", "8cffdfae7b3341dcd808fb723a3fcaeee3e55b9c971b7f75f6b0d7af6a3151e6", [:rebar3], [], "hexpm", "6e4d00e8ece505ba5091c8dab3aefe415cf2d836ff39508344c3527b366d9919"},
"p1_oauth2": {:hex, :p1_oauth2, "0.6.14", "1c5f82535574de87e2059695ac4b91f8f9aebacbc1c80287dae6f02552d47aea", [:rebar3], [], "hexpm", "1fd3ac474e43722d9d5a87c6df8d36f698ed87af7bb81cbbb66361451d99ae8f"},
"p1_pgsql": {:hex, :p1_pgsql, "1.1.38", "1580f16ea95a19f116977d13618b9eb402d8aa169fd86c65ad6f09acf37bbd74", [:rebar3], [{:xmpp, "~> 1.12.0", [hex: :xmpp, repo: "hexpm", optional: false]}], "hexpm", "06cf6443008178eba387ba2ee924d3620041bae4da1b2c969680284a05cc759e"},
"p1_utils": {:hex, :p1_utils, "1.0.28", "9a7088a98d788b4c4880fd3c82d0c135650db13f2e4ef7e10db179791bc94d59", [:rebar3], [], "hexpm", "c49bd44bc4a40ad996691af826dd7e0aa56d4d0cd730817190a1f84d1a7f0033"},
"p1_pgsql": {:hex, :p1_pgsql, "1.1.41", "b67cadb9079f809154d829a5eb5374619a60befd4da112ff59a759ea723548e1", [:rebar3], [{:stringprep, "~> 1.0.33", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm", "26ee2aba3d450464edd4b3ff9dab2c6a53fc20bbfd330a6973982b906504a172"},
"p1_utils": {:hex, :p1_utils, "1.0.29", "e3a8621c2cf5ecde52065b4c6650803e25fdd9462f92dec96f29032a163cbb0d", [:rebar3], [], "hexpm", "2071421cadb5b8fff114e91d4d944f14851c332391f6e93fc5011aad64abe1b9"},
"pkix": {:hex, :pkix, "1.0.10", "d3bfadf7b7cfe2a3636f1b256c9cce5f646a07ce31e57ee527668502850765a0", [:rebar3], [], "hexpm", "e02164f83094cb124c41b1ab28988a615d54b9adc38575f00f19a597a3ac5d0e"},
"sqlite3": {:hex, :sqlite3, "1.1.15", "e819defd280145c328457d7af897d2e45e8e5270e18812ee30b607c99cdd21af", [:rebar3], [], "hexpm", "3c0ba4e13322c2ad49de4e2ddd28311366adde54beae8dba9d9e3888f69d2857"},
"stringprep": {:hex, :stringprep, "1.0.33", "22f42866b4f6f3c238ea2b9cb6241791184ddedbab55e94a025511f46325f3ca", [:rebar3], [{:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "96f8b30bc50887f605b33b46bca1d248c19a879319b8c482790e3b4da5da98c0"},
"stun": {:hex, :stun, "1.2.21", "735855314ad22cb7816b88597d2f5ca22e24aa5e4d6010a0ef3affb33ceed6a5", [:rebar3], [{:fast_tls, "1.1.25", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "3d7fe8efb9d05b240a6aa9a6bf8b8b7bff2d802895d170443c588987dc1e12d9"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"},
"xmpp": {:hex, :xmpp, "1.12.0", "5a583fe1122f013310147cb7f36bd75de8a4ef0a836487826bf1a9f53403792e", [:rebar3], [{:ezlib, "~> 1.0.12", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "~> 1.1.19", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "~> 1.1.51", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:idna, "~> 6.0", [hex: :idna, repo: "hexpm", optional: false]}, {:p1_utils, "~> 1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "~> 1.0.29", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm", "014bae73659fba256771eb007bc5348618ac727dd1d10b9ab15a9fef871622c8"},
"yconf": {:hex, :yconf, "1.0.22", "52a435f9b60ab1e13950dfe3f7131ecdd8b3d1ca72c44bf66fc74b4571027124", [:rebar3], [{:fast_yaml, "1.0.39", [hex: :fast_yaml, repo: "hexpm", optional: false]}], "hexpm", "aca83457ceabe70756484b5c87ba7b1955f511d499168687eaeaa7c300e857f1"},
"stringprep": {:hex, :stringprep, "1.0.34", "b9d592c684e540c4cb867485742635ebc4738925bc28f411302db883baf44a49", [:rebar3], [{:p1_utils, "1.0.29", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "27e78ea371881764e056fbe845d3ad304740ff5e3131bc1f38a66f0baa8c9b47"},
"stun": {:hex, :stun, "1.2.22", "bfacc08bd3724968de6d2a4acfa9761232cebef9ff6bd9ff8b000e4bb4ce92a4", [:rebar3], [{:fast_tls, "1.1.26", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.29", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "3408b4b11d5237a088f53b260a06940c30b16f4a48094892d6a9204e1643188a"},
"xmpp": {:hex, :xmpp, "1.13.1", "b75831a205a286478aa0dc122b91199ab125e14032e452d1aca4276ee4d44c42", [:rebar3], [{:ezlib, "~> 1.0.16", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "~> 1.1.26", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "~> 1.1.58", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:idna, "~> 7.1", [hex: :idna, repo: "hexpm", optional: false]}, {:p1_utils, "~> 1.0.29", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "~> 1.0.34", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm", "a024eef7ccb4f78b9fb52e37d6385a57bdd150be75e50d51bbcbaf39a9ab883d"},
"yconf": {:hex, :yconf, "1.0.23", "1aa5ac72c007b80818ded5b65f76cfc98694b07b2da18202498ec4a4aefe191f", [:rebar3], [{:fast_yaml, "1.0.40", [hex: :fast_yaml, repo: "hexpm", optional: false]}], "hexpm", "25b4dfb75328026acaa774cf5c6fb87d48b55eebce630ddaa3362441dac31437"},
}
+53
View File
@@ -0,0 +1,53 @@
{
"name": "ejabberd",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ejabberd",
"version": "1.0.0",
"hasInstallScript": true,
"dependencies": {
"bootstrap": "^5.3.8",
"jquery": "^4.0.0"
}
},
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"license": "MIT",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/bootstrap": {
"version": "5.3.8",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz",
"integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"license": "MIT",
"peerDependencies": {
"@popperjs/core": "^2.11.8"
}
},
"node_modules/jquery": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-4.0.0.tgz",
"integrity": "sha512-TXCHVR3Lb6TZdtw1l3RTLf8RBWVGexdxL6AC8/e0xZKEpBflBsjh9/8LXw+dkNFuOyW9B7iB3O1sP7hS0Kiacg==",
"license": "MIT"
}
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"name": "ejabberd",
"version": "1.0.0",
"dependencies": {
"bootstrap": "^5.3.8",
"jquery": "^4.0.0"
},
"scripts": {
"postinstall": "npm run -s clean && npm run -s mkdir-jquery && npm run -s cp-jquery && npm run -s cp-bootstrap",
"clean": "rm -rf priv/mod_invites/static/{jquery,bootstrap}",
"mkdir-jquery": "mkdir -p priv/mod_invites/static/jquery",
"cp-jquery": "cp node_modules/jquery/dist/jquery.min.js priv/mod_invites/static/jquery/jquery.min.js",
"cp-bootstrap": "cp -r node_modules/bootstrap/dist priv/mod_invites/static/bootstrap"
}
}
+26 -26
View File
@@ -1,26 +1,26 @@
<div class="container">
<div class="row">
{% for item in apps %}
<div class="card m-3 client-card {% for platform in item.platforms %}app-platform-{{ platform|lower }} {% endfor %} flex-wrap col-sm-12 col-md-8 col-lg-5">
<div class="row no-gutters h-100">
<div class="col-md-4">
<img src="{{ static }}/{{ item.image }}" class="p-2 img-fluid" alt="{{ item.imagetext }}">
</div>
<div class="col-md-8">
<div class="card-body d-flex flex-column h-100">
<h5 class="card-title text-nowrap mb-1">{{ item.name }}</h5>
<div>
{% for platform in item.platforms %}<span class="badge badge-info client-platform-badge client-platform-badge-{{ platform|lower }} mr-1 mb-3">{{ platform }}</span>{% endfor %}
</div>
<p class="card-text">{{ item.text }}</p>
<a href="{{ item.proceed_url }}" class="btn btn-primary mt-md-auto">{% if item.select_text %}{{ item.select_text }}{% else %}{% trans "Select" %}{% endif %}</a>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<div id="show-all-clients-button-container" class="d-none alert alert-info">
{% trans "Showing apps for <span class='platform-name'>your current platform</span> only. You may also <a href='#' id='show-all-clients-button'>view all apps.</a>" %}
</div>
<div class="container mb-0">
<div class="row">
{% for item in apps %}
<div class="card mx-0 mb-3 me-3 client-card {% for platform in item.platforms %}app-platform-{{ platform|lower }} {% endfor %}flex-wrap col-sm-12 col-md-8 col-lg-5">
<div class="row no-gutters h-100">
<div class="col-md-4 pt-2 mt-1">
<img src="{{ static }}/{{ item.image }}" class="p-2 img-fluid" alt="{{ item.alttext }}">
</div>
<div class="col-md-8">
<div class="card-body d-flex flex-column h-100">
<h3 class="card-title text-nowrap mb-1 h5">{{ item.name }}</h3>
<div>
{% for platform in item.platforms %}<span class="badge text-bg-info client-platform-badge client-platform-badge-{{ platform|lower }} me-1 mb-3">{{ platform }}</span>{% endfor %}
</div>
<p class="card-text">{{ item.text }}</p>
<a href="{{ item.proceed_url }}" class="btn btn-primary mt-md-auto" tabindex="{{ forloop.counter|add:2 }}">{% if item.select_text %}{{ item.select_text }}{% else %}{% trans "Select" %}{% endif %}</a>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<div id="show-all-clients-button-container" class="d-none alert alert-info mb-0">
{% blocktrans with tabidx=apps|length|add:2 %}Showing apps for <span class='platform-name'>your current platform</span> only. You may also <a href='#' id='show-all-clients-button' tabindex="{{ tabidx }}">view all apps.</a>{% endblocktrans %}
</div>
+48 -14
View File
@@ -4,17 +4,20 @@
"buttons": [
{
"image": "{{ static }}/logos/google_ps.png",
"alttext": "{% trans 'Google Play Store Logo' %}",
"url": "https://play.google.com/store/apps/details?id=eu.siacs.conversations",
"magic_link_format": "https://play.google.com/store/apps/details?id=eu.siacs.conversations&referrer={{ uri }}"
},
{
"image": "{{ static }}/logos/fdroid.png",
"alttext": "{% trans 'F-Droid Store Logo' %}",
"url": "https://f-droid.org/en/packages/eu.siacs.conversations/",
"magic_link_format": "https://f-droid.org/packages/eu.siacs.conversations/"
}
]
},
"image": "logos/conversations.svg",
"alttext": "{% trans 'Conversations Logo' %}",
"link": "https://play.google.com/store/apps/details?id=eu.siacs.conversations",
"magic_link_format": "https://play.google.com/store/apps/details?id=eu.siacs.conversations&referrer={{ uri }}",
"name": "Conversations",
@@ -22,116 +25,146 @@
"Android"
],
"supports_preauth_uri": true,
"text": "{% trans "Conversations is a Jabber/XMPP client for Android 6.0+ smartphones that has been optimized to provide a unique mobile experience." %}"
"text": "{% trans 'Conversations is a Jabber/XMPP client for Android 6.0+ smartphones that has been optimized to provide a unique mobile experience.' %}"
},
{
"download": {
"buttons": [
{
"image": "{{ static }}/logos/apple_as.svg",
"alttext": "{% trans 'Apple Store Logo' %}",
"target": "_blank",
"url": "https://apps.apple.com/app/id317711500"
}
]
},
"image": "logos/monal-tmp.svg",
"alttext": "{% trans 'Monal Logo' %}",
"link": "https://monal-im.org/",
"name": "Monal",
"platforms": [
"iOS", "iPadOS"
],
"supports_preauth_uri": true,
"text": "{% trans "A modern open-source chat client for iPhone and iPad. It is easy to use and has a clean user interface." %}"
"text": "{% trans 'A modern open-source chat client for iPhone and iPad. It is easy to use and has a clean user interface.' %}"
},
{
"download": {
"buttons": [
{
"image": "{{ static }}/logos/apple_as.svg",
"alttext": "{% trans 'Apple Store Logo' %}",
"target": "_blank",
"url": "https://apps.apple.com/app/id1637078500"
}
]
},
"image": "logos/monal-tmp.svg",
"alttext": "{% trans 'Monal Logo' %}",
"link": "https://monal-im.org/",
"name": "Monal (macOS)",
"platforms": [
"macOS"
],
"supports_preauth_uri": true,
"text": "{% trans "A modern open-source chat client for Mac. It is easy to use and has a clean user interface." %}"
"text": "{% trans 'A modern open-source chat client for Mac. It is easy to use and has a clean user interface.' %}"
},
{
"download": {
"buttons": [
{
"image": "{{ static }}/logos/google_ps.png",
"alttext": "{% trans 'Google Play Store Logo' %}",
"url": "https://play.google.com/store/apps/details?id=org.yaxim.androidclient",
"magic_link_format": "https://play.google.com/store/apps/details?id=org.yaxim.androidclient&referrer={{ uri }}"
}
]
},
"image": "logos/yaxim.svg",
"link": "https://play.google.com/store/apps/details?id=org.yaxim.androidclient",
"magic_link_format": "https://play.google.com/store/apps/details?id=org.yaxim.androidclient&referrer={{ uri }}",
"name": "yaxim",
"platforms": [
"Android"
],
"supports_preauth_uri": true,
"text": "{% trans 'A lean Jabber/XMPP client for Android. It aims at usability, low overhead and security, and works on low-end Android devices starting with Android 4.0.' %}"
},
{
"download": {
"buttons": [
{
"image": "{{ static }}/logos/apple_as.svg",
"alttext": "{% trans 'Apple Store Logo' %}",
"target": "_blank",
"url": "https://apps.apple.com/us/app/siskin-im/id1153516838"
}
]
},
"image": "logos/siskin-im.svg",
"alttext": "{% trans 'Siskin IM Logo' %}",
"link": "https://apps.apple.com/us/app/siskin-im/id1153516838",
"name": "Siskin IM",
"platforms": [
"iOS", "iPadOS"
],
"supports_preauth_uri": true,
"text": "{% trans "A lightweight and powerful XMPP client for iPhone and iPad. It provides an easy way to talk and share moments with your friends." %}"
"text": "{% trans 'A lightweight and powerful XMPP client for iPhone and iPad. It provides an easy way to talk and share moments with your friends.' %}"
},
{
"download": {
"buttons": [
{
"target": "_blank",
"text": "{% trans "Download from Mac App Store" %}",
"text": "{% trans 'Download from Mac App Store' %}",
"url": "https://apps.apple.com/us/app/beagle-im/id1445349494"
}
]
},
"image": "logos/beagle-im.svg",
"alttext": "{% trans 'Beagle IM Logo' %}",
"link": "https://apps.apple.com/us/app/beagle-im/id1445349494",
"name": "Beagle IM",
"platforms": [
"macOS"
],
"setup": {
"text": "{% trans "Launch Beagle IM, and select 'Yes' to add a new account. Click the '+' button under the empty account list and then enter your credentials." %}"
"text": "{% trans 'Launch Beagle IM, and select \'Yes\' to add a new account. Click the \'+\' button under the empty account list and then enter your credentials.' %}"
},
"text": "{% trans "Beagle IM by Tigase, Inc. is a lightweight and powerful XMPP client for macOS." %}"
"text": "{% trans 'Beagle IM by Tigase, Inc. is a lightweight and powerful XMPP client for macOS.' %}"
},
{
"download": {
"buttons": [
{
"target": "_blank",
"text": "{% trans "Download Dino for Linux" %}",
"text": "{% trans 'Download Dino for Linux' %}",
"url": "https://dino.im/#download"
}
],
"text": "{% trans "Click the button to open the Dino website where you can download and install it on your PC." %}"
"text": "{% trans 'Click the button to open the Dino website where you can download and install it on your PC.' %}"
},
"image": "logos/dino.svg",
"alttext": "{% trans 'Dino Logo' %}",
"link": "https://dino.im/",
"name": "Dino",
"platforms": [
"Linux"
],
"text": "{% trans "A modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind." %}"
"text": "{% trans 'A modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind.' %}"
},
{
"download": {
"buttons": [
{
"target": "_blank",
"text": "{% trans "Download Gajim" %}",
"text": "{% trans 'Download Gajim' %}",
"url": "https://gajim.org/download/"
}
]
},
"image": "logos/gajim.svg",
"alttext": "{% trans 'Gajim Logo' %}",
"link": "https://gajim.org/",
"name": "Gajim",
"platforms": [
@@ -139,24 +172,25 @@
"Linux",
"macOS"
],
"text": "{% trans "A fully-featured desktop chat client for Windows and Linux." %}"
"text": "{% trans 'A fully-featured desktop chat client for Windows and Linux.' %}"
},
{
"download": {
"buttons": [
{
"target": "_blank",
"text": "{% trans "Download Renga for Haiku" %}",
"text": "{% trans 'Download Renga for Haiku' %}",
"url": "https://depot.haiku-os.org/#!/pkg/renga?bcguid=bc233-PQIA"
}
]
},
"image": "logos/renga.svg",
"alttext": "{% trans 'Renga Logo' %}",
"link": "https://pulkomandy.tk/projects/renga",
"name": "Renga",
"platforms": [
"Haiku"
],
"text": "{% trans "XMPP client for Haiku" %}"
"text": "{% trans 'XMPP client for Haiku' %}"
}
]
+30 -36
View File
@@ -1,47 +1,41 @@
{% extends "base_min.html" %}
{% block rel_alternate %}
<link rel="alternate" href="{{ uri }}">
{% endblock %}
{% block rel_alternate %}<link rel="alternate" href="{{ uri }}">{% endblock %}
{% block qr_button %}
<div id="qr-button-container" class="float-right w-25 border border-info p-3 d-none">
{% trans "<strong>Tip:</strong> You can open this invite on your mobile device by scanning a barcode with your camera." %}
<button id="qr-modal-show" class="mt-2 d-block btn btn-info" title="{% trans "Send this invite to your device" %}"
data-toggle="modal" data-target="#qr-modal">
<img src="{{ static }}/qr-logo.png" alt="{% trans "QR code icon" %}" class="align-middle h-50 mt-1" style="display:inline" >
{% trans "Scan with mobile device" %}
</button>
</div>
<div id="qr-button-container" class="float-end w-25 border border-info p-3 ms-3 d-none">
{% trans "<strong>Tip:</strong> You can open this invite on your mobile device by scanning a barcode with your camera." %}
<button id="qr-modal-show" class="mt-2 mx-auto d-block btn btn-info" title="{% trans "Send this invite to your device" %}"
data-bs-toggle="modal" data-bs-target="#qr-modal" tabindex="1">
{% trans "Scan with mobile device" %}
</button>
</div>
{% endblock %}
{% block qr_code %}
<div class="modal" tabindex="-1" role="dialog" id="qr-modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{% trans "Scan invite code" %}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>{% trans "You can transfer this invite to your mobile device by scanning a code with your camera." %}</p>
<div id="qr-info-url" class="tab-pane show active">
<p>{% trans "Use a <em>QR code</em> scanner on your mobile device to scan the code below:" %}</p>
<div id="qr-invite-page" style="width: 256px;" class="bg-light mx-auto"></div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">{% trans "Close" %}</button>
</div>
</div>
</div>
</div>
<div class="modal" tabindex="-1" role="dialog" id="qr-modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title h5">{% trans "Scan invite code" %}</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>{% trans "You can transfer this invite to your mobile device by scanning a code with your camera." %}</p>
<div id="qr-info-url" class="tab-pane show active">
<p>{% trans "Use a <em>QR code</em> scanner on your mobile device to scan the code below:" %}</p>
<div id="qr-invite-page" class="bg-light mx-auto"></div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" tabindex="2">{% trans "Close" %}</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script src="{{ static }}/qrcode.min.js"></script>
<script src="{{ static }}/platform.min.js"></script>
<script src="{{ static }}/invite.js"></script>
<script src="{{ static }}/qrcode.min.js" integrity="sha384-XfbBihCQqSDyejklP5yun2CbVxqR+2eNfx0Fhx5pQAfN5ypWGhSBjXaXr5g6X4DE"></script>
<script src="{{ static }}/platform.min.js" integrity="sha384-nziKWRrD67nso9WErLVLhgT7AobHh6aYfNgqgINmJrtZ92V9aNTaOpvDFkcneToL"></script>
{% endblock %}
+28 -33
View File
@@ -1,35 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% blocktrans %}Invite to {{ site_name }}{% endblocktrans %}{% endblock %}</title>
{% block rel_alternate %}{% endblock %}
<link rel="stylesheet" href="/share/bootstrap4/css/bootstrap.min.css">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#fbd308">
<meta name="theme-color" content="#fbd308">
</head>
<body>
<div id="background" class="fixed-top overflow-hidden"></div>
<div id="form" class="{% block form_class %}container col-md-10 col-md-offset-1 col-sm-8 col-sm-offset-2 col-lg-10 col-lg-offset-1 mt-2 mt-md-5{% endblock %}">
<div class="card rounded-lg shadow">
<h1 class="card-header rounded-lg rounded-lg">
{%block h1 %}{% blocktrans %}Invite to {{ site_name }}{% endblocktrans %}{% endblock %}<br/>
</h1>
<div class="card-body">
{% block qr_button %}{% endblock %}
{% block content %}{% endblock %}
</div>
</div>
</div>
{% block qr_code %}{% endblock %}
{% block extra_scripts %}{% endblock %}
<script src="/share/jquery/jquery.min.js"></script>
<script src="/share/bootstrap4/js/bootstrap.min.js"></script>
</body>
<html lang="{{ lang }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% trans "Invite" %} | {{ site_name }}{% endblock %}</title>
{% block rel_alternate %}{% endblock %}
<link rel="stylesheet" href="{{ static }}/bootstrap/css/bootstrap.min.css" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB">
<link rel="icon" type="image/png" sizes="32x32" href="{{ static }}/favicon.png">
<meta name="msapplication-TileColor" content="#4acac3">
<meta name="theme-color" content="#4acac3">
<link rel="stylesheet" href="{{ static }}/invite.css" integrity="sha384-k6usDEL8f5OYugdzmHwAlWjdQA/YkOZre60c604zXvE9fSJpptDg2jzE2PaQ5/c+">
</head>
<body>
<div id="form" class="{% block form_class %}container col-md-10 col-md-offset-1 col-sm-8 col-sm-offset-2 col-lg-10 col-lg-offset-1 my-3 mt-md-5{% endblock %}">
<div class="card rounded-2 shadow">
<h1 class="card-header">{%block h1 %}{% blocktrans %}Invite to {{ site_name }}{% endblocktrans %}{% endblock %}</h1>
<div class="card-body">
{% block qr_button %}{% endblock %}
{% block content %}{% endblock %}
</div>
</div>
</div>
{% block qr_code %}{% endblock %}
{% block extra_scripts %}{% endblock %}
<script src="{{ static }}/jquery/jquery.min.js" integrity="sha384-fgGyf7Mo7DURSOMnOy7ed+dkq5Job205Gnzu6QIg0BOHKaqt4D76Dt8VlDCzcMHV"></script>
<script src="{{ static }}/bootstrap/js/bootstrap.min.js" integrity="sha384-G/EV+4j2dNv+tEPo3++6LCgdCROaejBqfUeNjuKAiuXbjrxilcCdDz6ZAVfHWe1Y"></script>
<script src="{{ static }}/invite.js" integrity="sha384-ov8LmXw6nEmT+QvXBRPMol0e90WFX9ZysvQudQ7H+nYDyolH4K/+GKJhq7qmTk4T"></script>
</body>
</html>
+60 -58
View File
@@ -1,70 +1,72 @@
{% extends "base.html" %}
{% block h1 %}
{% blocktrans with app_name=app.name %}Join {{ site_name }} with {{ app_name }}{% endblocktrans %}
{% endblock %}
{% block title %}{% trans "Create Account" %} | {{ app.name }} | {{ site_name }}{% endblock %}
{% block h1 %}{% blocktrans with app_name=app.name %}Join {{ site_name }} with {{ app_name }}{% endblocktrans %}{% endblock %}
{% block content %}
<p>{% if invite.inviter|user %}
<p>{% if invite.inviter|user %}
{% blocktrans with inviter=invite.inviter|user %}You have been invited to chat with <strong>{{ inviter }}</strong> on {{ site_name }}, part of the XMPP secure and decentralized messaging network.{% endblocktrans %}
{% else %}
{% else %}
{% blocktrans %}You have been invited to chat on {{ site_name }}, part of the XMPP secure and decentralized messaging network.{% endblocktrans %}
{% endif %}</p>
<div class="bd-callout bd-callout-info col-12 col-md-8">
<svg class="svg-inline--fa fa-lightbulb fa-w-11 text-warning" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="lightbulb" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512" data-fa-i2svg=""><path fill="currentColor" d="M96.06 454.35c.01 6.29 1.87 12.45 5.36 17.69l17.09 25.69a31.99 31.99 0 0 0 26.64 14.28h61.71a31.99 31.99 0 0 0 26.64-14.28l17.09-25.69a31.989 31.989 0 0 0 5.36-17.69l.04-38.35H96.01l.05 38.35zM0 176c0 44.37 16.45 84.85 43.56 115.78 16.52 18.85 42.36 58.23 52.21 91.45.04.26.07.52.11.78h160.24c.04-.26.07-.51.11-.78 9.85-33.22 35.69-72.6 52.21-91.45C335.55 260.85 352 220.37 352 176 352 78.61 272.91-.3 175.45 0 73.44.31 0 82.97 0 176zm176-80c-44.11 0-80 35.89-80 80 0 8.84-7.16 16-16 16s-16-7.16-16-16c0-61.76 50.24-112 112-112 8.84 0 16 7.16 16 16s-7.16 16-16 16z"></path></svg>
{% blocktrans with app_name=app.name %}You can start chatting right away with {{ app_name }}. Let's get started!{% endblocktrans %}
</div>
<div class="card client-card {% for item in app.platforms %}app-platform-{{ item|lower }} {% endfor %} flex-wrap col-sm-12 col-md-8 col-lg-5 p-3 my-3">
<div class="row no-gutters h-100">
<div class="col-md-4">
<img src="{{ static }}/{{ app.image }}" class="img-fluid" alt="{{ app.alttext }}">
</div>
<div class="col-md-8 h-100 ps-md-1">
<div class="card-body d-flex flex-column h-100 pb-0 p-md-0">
<h3 class="card-title text-nowrap mb-1 h5">{{ app.name }}</h3>
<div>
{% for item in app.platforms %}<span class="badge text-bg-info client-platform-badge client-platform-badge-{{ item|lower }} me-1 mb-3">{{ item }}</span> {% endfor %}
</div>
<p class="card-text">{{ app.text }}</p>
</div>
</div>
</div>
</div>
<h2 class="h3 alert alert-info p-2" clear-both">{% blocktrans with app_name=app.name %}Step 1: Install {{ app_name }}{% endblocktrans %}</h2>
<p>{% if app.download.text %}{{ app.download.text }}{% else %}{% blocktrans with app_name=app.name %}Download and install {{ app_name }} below:{% endblocktrans %}{% endif %}</p>
<div class="ms-5">
{% for button in app.download.buttons %}
{% if button.image %}
<a href="{% if button.magic_link %}{{ button.magic_link }}{% else %}{{ button.url }}{% endif %}" {% if button.target %}target="{{ button.target }}"{% endif %} rel="noopener" tabindex="3">
<img src="{{ button.image }}" {% if button.alttext %}alt="{{ button.alttext }}"{% endif %} class="invite-download-button">
</a>
{% endif %}
</p>
{% if button.text %}
<a href="{{ button.url }}" {% if button.target %}target="{{ button.target }}"{% endif %} class="btn btn-primary" rel="noopener" tabindex="4">
{{ button.text }}
</a>
{% endif %}
{% endfor %}
</div>
<p>{% blocktrans with app_name=app.name %}You can start chatting right away with {{ app_name }}. Let's get started!{% endblocktrans %}</p>
<p class="mt-3">{% blocktrans with app_name=app.name %}After successfully installing {{ app_name }}, come back to this page and <strong>continue with Step 2</strong>.{% endblocktrans %}</p>
<div class="card m-3 client-card {% for item in app.platforms %}app-platform-{{ item|lower }} {% endfor %} flex-wrap col-sm-12 col-md-8 col-lg-5">
<div class="row no-gutters h-100">
<div class="col-md-4">
<img src="{{ static }}/{{ app.image }}" class="p-2 img-fluid" alt="{{ app.imagetext }}">
</div>
<div class="col-md-8 h-100">
<div class="card-body d-flex flex-column h-100">
<h5 class="card-title text-nowrap mb-1">{{ app.name }}</h5>
<div>
{% for item in app.platforms %}<span class="badge badge-info client-platform-badge client-platform-badge-{{ item|lower }} mr-1 mb-3">{{ item }}</span> {% endfor %}
</div>
<p class="card-text">{{ app.text }}</p>
</div>
</div>
</div>
</div>
<h2 class="h3 alert alert-info p-2">{% trans "Step 2: Activate your account" %}</h2>
<h3 style="clear:both">{% blocktrans with app_name=app.name %}Step 1: Install {{ app_name }}{% endblocktrans %}</h3>
<p>{% trans "Installed ok? Great! <strong>Click or tap the button below</strong> to accept your invite and continue with your account setup:" %}</p>
<p>{% if app.download.text %}{{ app.download.text }}{% else %}{% blocktrans with app_name=app.name %}Download and install {{ app_name }} below:{% endblocktrans %}{% endif %}</p>
<div class="w-100 text-center text-md-start">
<a href="{{ uri }}" id="uri-cta" class="btn btn-success ms-md-5 mt-1 mb-3" tabindex="5">{% blocktrans with app_name=app.name %}Accept invite using {{ app_name }}{% endblocktrans %}</a><br/>
</div>
<div class="ml-5">
{% for button in app.download.buttons %}
{% if button.image %}
<a href="{% if button.magic_link %}{{ button.magic_link }}{% else %}{{ button.url }}{% endif %}" {% if button.target %}target="{{ button.target }}"{% endif %} rel="noopener">
<img src="{{ button.image }}" {% if button.alttext %}alt="{{ button.alttext }}"{% endif %} style="max-width: 160px;">
</a>
{% endif %}
{% if button.text %}
<a href="{{ button.url }}" {% if button.target %}target="{{ button.target }}"{% endif %} class="btn btn-primary" rel="noopener">
{{ button.text }}
</a>
{% endif %}
{% endfor %}
</div>
<p class="mt-3">{% blocktrans with app_name=app.name %}After successfully installing {{ app_name }}, come back to this page and <strong>continue with Step 2</strong>.{% endblocktrans %}</p>
<h3>{% trans "Step 2: Activate your account" %}</h3>
<p>{% trans "Installed ok? Great! <strong>Click or tap the button below</strong> to accept your invite and continue with your account setup:" %}</p>
<div>
<a href="{{ uri }}" id="uri-cta" class="btn btn-primary ml-5 mt-1 mb-3">{% blocktrans with app_name=app.name %}Accept invite using {{ app_name }}{% endblocktrans %}</a><br/>
</div>
<p>{% blocktrans with app_name=app.name %}After clicking the button you will be taken to {{ app_name }} to finish setting up your new {{ site_name }} account.{% endblocktrans %}</p>
{% endblock %}
{% block extra_scripts %}
<script src="{{ static }}/qrcode.min.js"></script>
<script src="{{ static }}/platform.min.js"></script>
<script src="{{ static }}/invite.js"></script>
<p>{% blocktrans with app_name=app.name %}After clicking the button you will be taken to {{ app_name }} to finish setting up your new {{ site_name }} account.{% endblocktrans %}</p>
<nav aria-label="{% trans 'Page navigation' %}">
<ul class="pagination mb-0">
<li class="page-item"><a tabindex="6" class="page-link" href="/{{ base }}/{{ token }}" aria-label="{% trans 'Previous' %}">
<span aria-hidden="true">&laquo;</span>
<span>{% trans "Previous" %}</span>
</a></li>
</ul>
</nav>
{% endblock %}
+9 -11
View File
@@ -1,18 +1,16 @@
{% extends "base.html" %}
{% block title %}{% trans "Create Account" %} | {{ site_name }}{% endblock %}
{% block content %}
<p>{% if invite.inviter|user %}
<p>{% if invite.inviter|user %}
{% blocktrans with inviter=invite.inviter|user %}You have been invited to chat with <strong>{{ inviter }}</strong> on <strong>{{ site_name }}</strong>, part of the XMPP secure and decentralized messaging network.{% endblocktrans %}
{% else %}
{% else %}
{% blocktrans %}You have been invited to chat on <strong>{{ site_name }}</strong>, part of the XMPP secure and decentralized messaging network.{% endblocktrans %}
{% endif %}</p>
{% endif %}</p>
<h2 class="card-title h5 clear-both">{% trans "Get started" %}</h2>
<p>{% trans "To get started, you need to install an app for your platform:" %}</p>
<h5 class="card-title" style="clear:both">{% trans "Get started" %}</h5>
{% include "apps.html" %}
<p>{% trans "To get started, you need to install an app for your platform:" %}</p>
{% include "apps.html" %}
<h5>{% trans "Other software" %}</h5>
<p>{% blocktrans %}You can connect to {{ site_name }} using any XMPP-compatible software. If your preferred software is not listed above, you may still <a href="{{ registration_url }}">register an account manually</a>.{% endblocktrans %}</p>
<h2 class="h5 mt-3">{% trans "Other software" %}</h2>
<p id="other-software" class="mb-0">{% blocktrans with tabidx=apps|length|add:3 %}You can connect to {{ site_name }} using any XMPP-compatible software. If your preferred software is not listed above, you may still <a href="{{ registration_url }}" tabindex='{{ tabidx }}'>register an account manually</a>.{% endblocktrans %}</p>
{% endblock %}
+5 -4
View File
@@ -1,9 +1,10 @@
{% extends "base_min.html" %}
{% block form_class %}container col-md-8 col-md-offset-2 col-sm-8 cold-sm-offset-2 col-lg-6 col-lg-offset-3 mt-2 mt-md-5{% endblock %}
{% block title %}{% trans "Invite Expired" %} | {{ site_name }}{% endblock %}
{% block form_class %}container col-md-8 col-md-offset-2 col-sm-8 cold-sm-offset-2 col-lg-6 col-lg-offset-3 my-3 mt-md-5{% endblock %}
{% block content %}
<h5 class="card-title">{% trans "Invite expired" %}</h5>
<h2 class="card-title h5">{% trans "Invite expired" %}</h2>
<p>{% trans "Sorry, it looks like this invite code has expired!" %}</p>
<p>{% trans "Sorry, it looks like this invite code has expired!" %}</p>
<img class="w-100" alt="{% trans "Sad person holding empty box" %}" src="{{ static }}/illus-empty.svg">
<img class="w-100" alt="{% trans "Sad person holding empty box" %}" src="{{ static }}/illus-empty.svg">
{% endblock %}
+64 -56
View File
@@ -1,64 +1,72 @@
{% extends "base_min.html" %}
{% block title %}{% blocktrans %}Register on {{ site_name }}{% endblocktrans %}{% endblock %}
{% block h1 %}{% blocktrans %}Register on {{ site_name }}{% endblocktrans %}{% endblock %}
{% block title %}{% if error %}{% trans "Registration Error" %}{% else %}{% trans "Registration Form" %}{% endif %} | {{ site_name }}{% endblock %}
{% block h1 %}{% if error %}{% trans "Registration Error" %}{% else %}{% blocktrans %}Register on {{ site_name }}{% endblocktrans %}{% endif %}{% endblock %}
{% block form_class %}container col-md-8 col-md-offset-2 col-sm-8 cold-sm-offset-2 col-lg-6 col-lg-offset-3 mt-2 mt-md-5{% endblock %}
{% block form_class %}container col-md-8 col-md-offset-2 col-sm-8 cold-sm-offset-2 col-lg-6 col-lg-offset-3 my-3 mt-md-5{% endblock %}
{% block content %}
<p>{% if app %}{% blocktrans with app_name=app.name %}<strong>{{ site_name }}</strong> is part of XMPP, a secure and decentralized messaging network. To begin chatting using <strong>{{ app_name }}</strong> you need to first register an account.{% endblocktrans %}{% else %}{% blocktrans %}<strong>{{ site_name }}</strong> is part of XMPP, a secure and decentralized messaging network. To begin chatting you need to first register an account.{% endblocktrans %}{% endif %}</p>
<p>{% if app %}{% blocktrans with app_name=app.name %}<strong>{{ site_name }}</strong> is part of XMPP, a secure and decentralized messaging network. To begin chatting using <strong>{{ app_name }}</strong> you need to first register an account.{% endblocktrans %}{% else %}{% blocktrans %}<strong>{{ site_name }}</strong> is part of XMPP, a secure and decentralized messaging network. To begin chatting you need to first register an account.{% endblocktrans %}{% endif %}</p>
<p>{%if invite.inviter %}{% blocktrans with inviter=invite.inviter|user %}Creating an account will allow to communicate with <strong>{{ inviter }}</strong> and other people on <strong>{{ site_name }}</strong> and other services on the XMPP network.{% endblocktrans %}{% else %}{% blocktrans %}Creating an account will allow to communicate with other people on <strong>{{ site_name }}</strong> and other services on the XMPP network.{% endblocktrans %}{% endif %}</p>
<p>{%if invite.inviter %}{% blocktrans with inviter=invite.inviter|user %}Creating an account will allow to communicate with <strong>{{ inviter }}</strong> and other people on <strong>{{ site_name }}</strong> and other services on the XMPP network.{% endblocktrans %}{% else %}{% blocktrans %}Creating an account will allow to communicate with other people on <strong>{{ site_name }}</strong> and other services on the XMPP network.{% endblocktrans %}{% endif %}</p>
{% if app %}{% if app.supports_preauth_uri %}
<div class="alert alert-info">
<p>{% blocktrans with app_name=app.name %}If you already have {{ app_name }} installed, we recommend that you continue the account creation process using the app by clicking on the button below:{% endblocktrans %}</p>
<h6 class="text-center">{% blocktrans with app_name=app.name %}{{ app_name }} already installed?{% endblocktrans %}</h6>
<div class="text-center">
<a href="{{ uri }}"><button class="btn btn-secondary btn-sm">{% trans "Open the app" %}</button></a><br/>
<small class="text-muted">{% trans "This button works only if you have the app installed already!" %}</small>
</div>
<br/>
</div>
{% endif %}{% endif %}
<h5 class="card-title">{% trans "Create an account" %}</h5>
{%if message %}<div class="alert {%if message.class %}{{ message.class }}{% else %}alert-info{% endif %}" role="alert">
{{ message.text }}
</div>{% endif %}
<form method="post">
<div class="form-group form-row">
<label for="user" class="col-md-4 col-lg-12 col-form-label">{% trans "Username" %}:</label>
<div class="col-md-8 col-lg-12">
<div class="input-group">
<input
type="text" name="user" class="form-control" aria-describedby="usernameHelp"
required autofocus minlength="1" maxlength="30" length="30"{% if username %} value="{{ username }}"{% endif %}
>
<div class="input-group-append">
<span class="input-group-text">@{{ domain }}</span>
</div>
</div>
<small id="usernameHelp" class="d-block form-text text-muted">{% trans "Choose a username, this will become the first part of your new chat address." %}</small>
</div>
</div>
<div class="form-group form-row">
<label for="password" class="col-md-4 col-lg-12 col-form-label">{% trans "Password" %}:</label>
<div class="col-md-8 col-lg-12">
<input type="password" name="password" class="form-control" aria-describedby="passwordHelp"
autocomplete="new-password" required minlength="{{ password_policy.length }}"
>
<small id="passwordHelp" class="form-text text-muted">{% trans "Enter a secure password that you do not use anywhere else." %}</small>
</div>
</div>
<div class="form-group form-row">
<input type="hidden" name="token" value="{{ token }}">
{% if app %}<input type="hidden" name="app_id" value="{{ app.id }}">{% endif %}
<button type="submit" class="btn btn-primary btn-lg">{% trans "Submit" %}</button>
</div>
</form>
{% if app %}{% if app.supports_preauth_uri %}
<div class="alert alert-info">
<p>{% blocktrans with app_name=app.name %}If you already have {{ app_name }} installed, we recommend that you continue the account creation process using the app by clicking on the button below:{% endblocktrans %}</p>
<p class="text-center h6">{% blocktrans with app_name=app.name %}{{ app_name }} already installed?{% endblocktrans %}</p>
<div class="text-center">
<a href="{{ uri }}"><button class="btn btn-secondary btn-sm">{% trans "Open the app" %}</button></a><br/>
<small class="text-muted">{% trans "This button works only if you have the app installed already!" %}</small>
</div>
<br/>
</div>
{% endif %}{% endif %}
<h2 class="card-title h5">{% trans "Create an account" %}</h2>
{%if error and error.class == 'undefined' %}<div class="alert alert-danger" role="alert">{{ error.text }}</div>{% endif %}
<form method="post" class="needs-validation" novalidate>
<div class="input-group mb-3">
<label for="user" class="col-md-4 col-lg-12 form-label fw-bold">{% trans "Username" %}:</label>
<div class="col-md-8 col-lg-12">
<div class="input-group">
<input
type="text" name="user" class="form-control {% if error.class == 'username' %}is-invalid{% endif %}" aria-describedby="usernameHelp" tabindex="1"
required autofocus minlength="1" maxlength="30" length="30"{% if username %} value="{{ username }}"{% endif %}>
<div class="input-group-append">
<span class="input-group-text">@{{ domain }}</span>
</div>
<div class="invalid-feedback">
{% if error.class == 'username' %}{{ error.text }}{% else %}
{% blocktrans %}Please provide a valid username!{% endblocktrans %}{% endif %}
</div>
</div>
<small id="usernameHelp" class="d-block form-text text-muted">{% trans "Choose a username, this will become the first part of your new chat address." %}</small>
</div>
</div>
<div class="input-group mb-3">
<label for="password" class="col-md-4 col-lg-12 form-label col-form-label fw-bold">{% trans "Password" %}:</label>
<div class="col-md-8 col-lg-12">
<input type="password" name="password" class="form-control {% if error.class == 'password' %}is-invalid{% endif %}" aria-describedby="passwordHelp" tabindex="2"
autocomplete="new-password" required minlength="{{ password_min_length }}">
<div class="invalid-feedback">
{% if error.class == 'password' %}{{ error.text }}{% else %}
{% blocktrans %}Please provide a password! Minimum length: {{ password_min_length }}{% endblocktrans %}{% endif %}
</div>
<small id="passwordHelp" class="form-text text-muted">{% trans "Enter a secure password that you do not use anywhere else." %}</small>
</div>
</div>
<div>
<input type="hidden" name="token" value="{{ token }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
{% if app %}<input type="hidden" name="app_id" value="{{ app.id }}">{% endif %}
<button type="submit" tabindex="3" class="btn btn-primary float-end">{% trans "Submit" %}</button>
</div>
</form>
<nav aria-label="{% trans 'Page navigation' %}">
<ul class="pagination mt-3 mb-0">
<li class="page-item"><a tabindex="4" class="page-link" href="/{{ base }}/{{ token }}" aria-label="{% trans 'Previous' %}">
<span aria-hidden="true">&laquo;</span>
<span>{% trans "Previous" %}</span>
</a></li>
</ul>
</nav>
{% endblock %}
+3 -3
View File
@@ -1,7 +1,7 @@
{% extends "base_min.html" %}
{% block form_class %}container col-md-8 col-md-offset-2 col-sm-8 cold-sm-offset-2 col-lg-6 col-lg-offset-3 mt-2 mt-md-5{% endblock %}
{% block form_class %}container col-md-8 col-md-offset-2 col-sm-8 cold-sm-offset-2 col-lg-6 col-lg-offset-3 my-3 mt-md-5{% endblock %}
{% block content %}
<h5 class="card-title">{% trans "Registration error" %}</h5>
<h2 class="card-title h5">{% trans "Registration error" %}</h2>
<p>{% if message %}{{ message }}{% else %}{% trans "Sorry, there was a problem registering your account." %}{% endif %}</p>
<p>{% if message %}{{ message }}{% else %}{% trans "Sorry, there was a problem registering your account." %}{% endif %}</p>
{% endblock%}
+65 -82
View File
@@ -1,101 +1,84 @@
{% extends "base_min.html" %}
{% block form_class %}container col-md-8 col-md-offset-2 col-sm-8 cold-sm-offset-2 col-lg-6 col-lg-offset-3 mt-2 mt-md-5{% endblock %}
{% block title %}{{site_name}}{% endblock %}
{% block form_class %}container col-md-8 col-md-offset-2 col-sm-8 cold-sm-offset-2 col-lg-6 col-lg-offset-3 my-3 mt-md-5{% endblock %}
{% block title %}{% trans "Registration Success" %} | {{site_name}}{% endblock %}
{% block h1 %}{{site_name}}{% endblock %}
{% block extra_scripts %}
<script>
function toggle_password(e) {
var button = e.target;
var input = button.parentNode.parentNode.querySelector("input");
switch(input.attributes.type.value) {
case "password":
input.attributes.type.value = "text";
button.innerText = "{% trans "Hide" %}";
break;
case "text":
input.attributes.type.value = "password";
button.innerText = "{% trans "Show" %}";
break;
}
}
</script>
{% endblock %}
{% block content %}
<h5 class="card-title">{% trans "Congratulations!" %}</h5>
<h2 class="card-title h5">{% trans "Congratulations!" %}</h2>
<p>{% blocktrans %}You have created an account on <strong>{{ site_name }}</strong>.{% endblocktrans %}</p>
<p>{% blocktrans %}You have created an account on <strong>{{ site_name }}</strong>.{% endblocktrans %}</p>
<p>{% trans "To start chatting, you need to enter your new account credentials into your chosen XMPP software." %}</p>
<p>{% trans "To start chatting, you need to enter your new account credentials into your chosen XMPP software." %}</p>
{% if webchat_url %}
<div class="alert alert-success">
<div class="container">
<div class="row">
<div class="col-9">
<p>{% trans "<strong>No suitable software installed right now?</strong> You can also log in to your account through our online web chat!" %}</p>
</div>
<div class="col">
<a class="btn btn-primary" href="{{ webchat_url }}">{% trans "Log in via web" %}</a>
</div>
</div>
</div>
</div>
{% endif %}
{% if webchat_url %}
<div class="bd-callout bd-callout-info col-12">
<div class="container">
<div class="row">
<div class="col-12 col-md-9">
<svg class="svg-inline--fa fa-lightbulb fa-w-11 text-warning" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="lightbulb" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512" data-fa-i2svg=""><path fill="currentColor" d="M96.06 454.35c.01 6.29 1.87 12.45 5.36 17.69l17.09 25.69a31.99 31.99 0 0 0 26.64 14.28h61.71a31.99 31.99 0 0 0 26.64-14.28l17.09-25.69a31.989 31.989 0 0 0 5.36-17.69l.04-38.35H96.01l.05 38.35zM0 176c0 44.37 16.45 84.85 43.56 115.78 16.52 18.85 42.36 58.23 52.21 91.45.04.26.07.52.11.78h160.24c.04-.26.07-.51.11-.78 9.85-33.22 35.69-72.6 52.21-91.45C335.55 260.85 352 220.37 352 176 352 78.61 272.91-.3 175.45 0 73.44.31 0 82.97 0 176zm176-80c-44.11 0-80 35.89-80 80 0 8.84-7.16 16-16 16s-16-7.16-16-16c0-61.76 50.24-112 112-112 8.84 0 16 7.16 16 16s-7.16 16-16 16z"></path></svg> {% trans "<strong>No suitable software installed right now?</strong> You can also log in to your account through our online web chat!" %}
</div>
<div class="col text-end mt-3 mt-md-0">
<a class="btn btn-primary" href="{{ webchat_url }}" target="_blank" noopener>{% trans "Log in via web" %}</a>
</div>
</div>
</div>
</div>
{% endif %}
{% if app %}
<p>{% blocktrans with app_name=app.name %}You can now set up {{ app_name }} and connect it to your new account.{% endblocktrans %}</p>
<h5>{% blocktrans with app_name=app.name %}Step 1: Download and install {{ app_name }}{% endblocktrans %}</h5>
<h2 class="h5 alert alert-info p-2">{% blocktrans with app_name=app.name %}Step 1: Download and install {{ app_name }}{% endblocktrans %}</h2>
<p>{% if app.download.text %}{{ app.download.text }}{% else %}{% blocktrans with app_name=app.name %}Download and install {{ app_name }} below:{% endblocktrans %}{% endif %}</p>
<p>{% if app.download.text %}{{ app.download.text }}{% else %}{% blocktrans with app_name=app.name %}Download and install {{ app_name }} below:{% endblocktrans %}{% endif %}</p>
<div class="ml-5 mb-3">
{% for item in app.download.buttons %}
{% if item.image %}
<a href="{{ item.url }}" {%if item.target %}target="{{ item.target }}"{% endif %} rel="noopener">
<img src="{{ item.image }}" {% if item.alttext %}alt="{{ item.alttext }}"{% endif %}>
</a>
{% endif %}
{%if item.text %}
<a href="{{item.url}}" {% if item.target %}target="{{ item.target }}"{% endif %} rel="noopener">
<button class="btn btn-primary">
{{ item.text }}
</button>
</a>
{% endif %}
{% endfor %}
</div>
<div class="ms-5 mb-3">
{% for item in app.download.buttons %}
{% if item.image %}
<a href="{{ item.url }}" {%if item.target %}target="{{ item.target }}"{% endif %} rel="noopener">
<img src="{{ item.image }}" {% if item.alttext %}alt="{{ item.alttext }}"{% endif %}>
</a>
{% endif %}
{%if item.text %}
<a href="{{item.url}}" {% if item.target %}target="{{ item.target }}"{% endif %} rel="noopener">
<button class="btn btn-primary">
{{ item.text }}
</button>
</a>
{% endif %}
{% endfor %}
</div>
<h5>{% blocktrans with app_name=app.name %}Step 2: Connect {{ app_name }} to your new account{% endblocktrans %}</h5>
<h2 class="h5 alert alert-info p-2">{% blocktrans with app_name=app.name %}Step 2: Connect {{ app_name }} to your new account{% endblocktrans %}</h2>
<p>{% if app.setup.text %}{{ app.setup.text }}{% else %}{% blocktrans with app_name=app.name %}Launch {{ app_name }} and sign in using your account credentials.{% endblocktrans %}{% endif %}</p>
<p>{% if app.setup.text %}{{ app.setup.text }}{% else %}{% blocktrans with app_name=app.name %}Launch {{ app_name }} and sign in using your account credentials.{% endblocktrans %}{% endif %}</p>
{% endif %}
<p>{% trans "As a final reminder, your account details are shown below:" %}</p>
<p>{% trans "As a final reminder, your account details are shown below:" %}</p>
<form class="account-details col-12 col-lg-6 mx-auto">
<div class="form-group form-row">
<label for="user" class="col-md-4 col-lg-12 col-form-label">{% trans "Chat address (JID)" %}:</label>
<div class="col-md-8 col-lg-12">
<input type="text" class="form-control-plaintext" readonly value="{{ username }}@{{ domain }}">
</div>
</div>
{% if password %}
<div class="form-group form-row">
<label for="password" class="col-md-4 col-lg-12 col-form-label">{% trans "Password" %}:</label>
<div class="col-md-8 col-lg-12">
<div class="input-group">
<input type="password" readonly class="form-control" value="{{ password }}">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" onclick="toggle_password(event)">{% trans "Show" %}</button>
</div>
</div>
</div>
</div>
{% endif %}
</form>
<form class="account-details col-12 mx-auto">
<div class="input-group">
<label for="user" class="col-md-4 col-form-label fw-bold">{% trans "Chat address (JID)" %}:</label>
<div class="col-md-8 col-12">
<input type="text" class="form-control-plaintext" readonly value="{{ username }}@{{ domain }}">
</div>
</div>
{% if password %}
<div class="input-group">
<label for="password" class="col-md-4 col-12 col-form-label fw-bold">{% trans "Password" %}:</label>
<div class="col-md-8 col-12">
<div class="input-group">
<input type="password" readonly disabled aria-label="{% trans "Password" %}" class="form-control" value="{{ password }}">
<div class="input-group-append">
<button id="toggle-pw-button" class="btn btn-outline-secondary rounded-start-0" type="button"
data-text-show="{% trans "Show" %}" data-text-hide="{% trans "Hide" %}">{% trans "Show" %}</button>
</div>
</div>
</div>
</div>
{% endif %}
</form>
{% if password %}
<p>{% trans "Your password is stored encrypted on the server and will not be accessible after you close this page. Keep it safe and never share it with anyone." %}</p>
{% endif %}
{% if password %}
<p class="mt-3">{% trans "Your password is stored encrypted on the server and will not be accessible after you close this page. Keep it safe and never share it with anyone." %}</p>
{% endif %}
{% endblock %}
+11 -11
View File
@@ -1,18 +1,18 @@
{% extends "base.html" %}
{% block title %}{% blocktrans with inviter=invite.inviter|jid %}{{ inviter }} has invited you to connect!{% endblocktrans %}{% endblock %}
{% block title %}{% trans "Add Contact" %} | {{ site_name }}{% endblock %}
{% block h1 %}{% blocktrans with inviter=invite.inviter|user %}{{ inviter }} has invited you to connect!{% endblocktrans %}{% endblock %}
{% block content %}
<div class="col-md-8">
{% blocktrans with inviter=invite.inviter|jid %}This is an invite from <strong>{{ inviter }}</strong> to connect and chat on the XMPP network. If you already have an XMPP client installed just press the button below!{% endblocktrans %}
<div class="col-md-4">
<a href="{{ invite.uri }}" class="btn btn-primary my-3 mb-3">{% blocktrans with inviter=invite.inviter|user %}Add {{ inviter }} to your contact list{% endblocktrans %}</a><br/>
<!-- {{ invite.uri }} -->
</div>
</div>
<p>{% trans "If you don't have an XMPP client installed yet, here's a list of suitable clients for your platform." %}</p>
<div class="col-12 col-md-8">
{% blocktrans with inviter=invite.inviter|jid %}This is an invite from <strong>{{ inviter }}</strong> to connect and chat on the XMPP network. If you already have an XMPP client installed just press the button below!{% endblocktrans %}
<div class="text-center">
<a href="{{ invite.uri }}" class="btn btn-success my-3" tabindex="3">{% blocktrans with inviter=invite.inviter|user %}Add {{ inviter }} to your contact list{% endblocktrans %}</a><br/>
</div>
</div>
<div class="bd-callout bd-callout-info col-12 col-md-8 mb-3">
<svg class="svg-inline--fa fa-lightbulb fa-w-11 text-warning" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="lightbulb" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512" data-fa-i2svg=""><path fill="currentColor" d="M96.06 454.35c.01 6.29 1.87 12.45 5.36 17.69l17.09 25.69a31.99 31.99 0 0 0 26.64 14.28h61.71a31.99 31.99 0 0 0 26.64-14.28l17.09-25.69a31.989 31.989 0 0 0 5.36-17.69l.04-38.35H96.01l.05 38.35zM0 176c0 44.37 16.45 84.85 43.56 115.78 16.52 18.85 42.36 58.23 52.21 91.45.04.26.07.52.11.78h160.24c.04-.26.07-.51.11-.78 9.85-33.22 35.69-72.6 52.21-91.45C335.55 260.85 352 220.37 352 176 352 78.61 272.91-.3 175.45 0 73.44.31 0 82.97 0 176zm176-80c-44.11 0-80 35.89-80 80 0 8.84-7.16 16-16 16s-16-7.16-16-16c0-61.76 50.24-112 112-112 8.84 0 16 7.16 16 16s-7.16 16-16 16z"></path></svg>
<strong class="text-warning-emphasis">{% trans 'Hint' %}:</strong> {% trans "If you don't have an XMPP client installed yet, here's a list of suitable clients for your platform." %}
</div>
{% include "apps.html" %}
{% endblock %}
Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

+53
View File
@@ -0,0 +1,53 @@
#qr-invite-page{
width: 256px;
}
.invite-download-button {
max-width: 160px;
}
.clear-both {
clear: both;
}
.bd-callout {
padding: 1.25rem;
margin-top: 1.25rem;
margin-bottom: 1.25rem;
background-color: var(--bd-callout-bg, var(--bs-gray-100));
border-left:0.25rem solid var(--bd-callout-border, var(--bs-gray-300))
}
.bd-callout h4 {
margin-bottom:.25rem
}
.bd-callout > :last-child {
margin-bottom:0
}
.bd-callout + .bd-callout {
margin-top:-.25rem
}
.bd-callout .highlight {
background-color:rgba(0, 0, 0, 0.05)
}
.bd-callout-info {
--bd-callout-bg: rgba(var(--bs-info-rgb), .075);
--bd-callout-border: rgba(var(--bs-info-rgb), .5)
}
.bd-callout-warning {
--bd-callout-bg: rgba(var(--bs-warning-rgb), .075);
--bd-callout-border: rgba(var(--bs-warning-rgb), .5)
}
.bd-callout-danger {
--bd-callout-bg: rgba(var(--bs-danger-rgb), .075);
--bd-callout-border: rgba(var(--bs-danger-rgb), .5)
}
.fa-w-11 {
width: .6875em;
}
.fa-w-16 {
widht: 1em;
}
.svg-inline--fa {
display: inline-block;
font-size: inherit;
height: 1em;
overflow: visible;
vertical-align: -.125em;
}
+137 -87
View File
@@ -1,91 +1,141 @@
(function () {
// If QR lib loaded ok, show QR button on desktop devices
if(window.QRCode) {
const qrcode_opts = {
text : document.location.href,
addQuietZone: true
};
new QRCode(document.getElementById("qr-invite-page"), qrcode_opts);
document.getElementById('qr-button-container').classList.add("d-md-block");
}
document.documentElement.setAttribute('data-bs-theme', (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'));
// Detect current platform and show/hide appropriate clients
if(window.platform) {
let platform_friendly = null;
let platform_classname = null;
switch(platform.os.family) {
case "Ubuntu":
case "Linux":
case "Fedora":
case "Red Hat":
case "SuSE":
platform_friendly = platform.os.family + " (Linux)";
platform_classname = "linux";
break;
case "Linux aarch64":
platform_friendly = "Linux mobile";
platform_classname = "linux";
break;
case "Haiku R1":
platform_friendly = "Haiku";
platform_classname = "haiku";
break;
case "Windows Phone":
platform_friendly = "Windows Phone";
platform_classname = "windows-phone";
break;
case "OS X":
if (navigator.maxTouchPoints > 1) {
// looks like iPad to me!
platform_friendly = "iPadOS";
platform_classname = "ipados";
} else {
platform_friendly = "macOS";
platform_classname = "macos";
}
break;
default:
if(platform.os.family.startsWith("Windows")) {
platform_friendly = "Windows";
platform_classname = "windows";
} else {
platform_friendly = platform.os.family;
platform_classname = platform_friendly.toLowerCase();
}
}
// If QR lib loaded ok, show QR button on desktop devices
if (window.QRCode) {
const qrcode_opts = {
text: document.location.href,
addQuietZone: true
};
new QRCode(document.getElementById("qr-invite-page"), qrcode_opts);
document.getElementById('qr-button-container').classList.add("d-md-block");
}
if(platform_friendly && platform_classname) {
if(document.querySelectorAll('.client-card .client-platform-badge-'+platform_classname).length == 0) {
// No clients recognised for this platform, do nothing
return;
}
// Hide clients not for this platform
const client_cards = document.getElementsByClassName('client-card');
for (let card of client_cards) {
if (card.classList.contains('app-platform-'+platform_classname))
card.classList.add('supported-platform');
else if (!card.classList.contains('app-platform-web'))
card.hidden = true;
const badges = card.querySelectorAll('.client-platform-badge');
for (let badge of badges) {
if (badge.classList.contains('client-platform-badge-'+platform_classname)) {
badge.classList.add("badge-success");
badge.classList.remove("badge-info");
} else {
badge.classList.add("badge-secondary");
badge.classList.remove("badge-info");
}
}
}
const show_all_clients_button_container = document.getElementById('show-all-clients-button-container');
show_all_clients_button_container.querySelector('.platform-name').innerHTML = platform_friendly;
show_all_clients_button_container.classList.remove("d-none");
document.getElementById('show-all-clients-button').addEventListener('click', function (e) {
for (let card of client_cards)
card.hidden = false;
show_all_clients_button_container.hidden = true;
e.preventDefault();
});
}
}
const toggle_pw_button = document.getElementById('toggle-pw-button');
if (toggle_pw_button)
toggle_pw_button.addEventListener('click', toggle_password);
// Detect current platform and show/hide appropriate clients
if (window.platform) {
let platform_friendly = null;
let platform_classname = null;
switch (platform.os.family) {
case "Ubuntu":
case "Linux":
case "Fedora":
case "Red Hat":
case "SuSE":
platform_friendly = platform.os.family + " (Linux)";
platform_classname = "linux";
break;
case "Linux aarch64":
platform_friendly = "Linux mobile";
platform_classname = "linux";
break;
case "Haiku R1":
platform_friendly = "Haiku";
platform_classname = "haiku";
break;
case "Windows Phone":
platform_friendly = "Windows Phone";
platform_classname = "windows-phone";
break;
case "OS X":
if (navigator.maxTouchPoints > 1) {
// looks like iPad to me!
platform_friendly = "iPadOS";
platform_classname = "ipados";
} else {
platform_friendly = "macOS";
platform_classname = "macos";
}
break;
default:
if (platform.os.family.startsWith("Windows")) {
platform_friendly = "Windows";
platform_classname = "windows";
} else {
platform_friendly = platform.os.family;
platform_classname = platform_friendly.toLowerCase();
}
}
if (platform_friendly && platform_classname) {
if (document.querySelectorAll('.client-card .client-platform-badge-' + platform_classname).length == 0) {
const badges = document.querySelectorAll('.client-card .client-platform-badge');
for (let badge of badges) {
badge.classList.add("text-bg-secondary");
badge.classList.remove("text-bg-info");
}
// No clients recognised for this platform, do nothing
return;
}
// Hide clients not for this platform
const client_cards = document.getElementsByClassName('client-card');
for (let card of client_cards) {
if (card.classList.contains('app-platform-' + platform_classname))
card.classList.add('supported-platform');
else if (!card.classList.contains('app-platform-web'))
card.hidden = true;
const badges = card.querySelectorAll('.client-platform-badge');
let has_platform = false;
for (let badge of badges) {
if (badge.classList.contains('client-platform-badge-' + platform_classname)) {
badge.classList.add("text-bg-success");
badge.classList.remove("text-bg-info");
has_platform = true;
} else {
badge.classList.add("text-bg-secondary");
badge.classList.remove("text-bg-info");
}
}
if (!has_platform)
$(card).find("a.btn").removeClass("btn-primary").addClass("btn-secondary");
}
const show_all_clients_button_container = document.getElementById('show-all-clients-button-container');
if (show_all_clients_button_container) {
show_all_clients_button_container.querySelector('.platform-name').innerHTML = platform_friendly;
show_all_clients_button_container.classList.remove("d-none");
document.getElementById('show-all-clients-button').addEventListener('click', function (e) {
for (let card of client_cards)
card.hidden = false;
show_all_clients_button_container.hidden = true;
e.preventDefault();
});
}
}
}
})();
function toggle_password(e) {
var button = e.target;
var input = button.parentNode.parentNode.querySelector("input");
switch (input.attributes.type.value) {
case "password":
input.attributes.type.value = "text";
button.innerText = button.getAttribute('data-text-hide');
break;
case "text":
input.attributes.type.value = "password";
button.innerText = button.getAttribute('data-text-show');
break;
}
}
(function () {
'use strict';
window.addEventListener('load', function () {
// Fetch all the forms we want to apply custom Bootstrap validation styles to
var forms = document.getElementsByClassName('needs-validation');
// Loop over them and prevent submission
var validation = Array.prototype.filter.call(forms, function (form) {
form.addEventListener('submit', function (event) {
if (form.checkValidity() === false) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add('was-validated');
}, false);
});
}, false);
})();
+16 -13
View File
@@ -1,24 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<filter width="64" height="64" id="shadow">
<feDropShadow dx="3" dy="3" stdDeviation="0" flood-opacity="0.4"/>
</filter>
<g filter="url(#shadow)">
<path fill="none" stroke="#000" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<svg version="1.1" width="512" height="512" viewBox="0 0 64 64" color-interpolation="linearRGB"
xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg">
<g>
<path style="fill:#000000; fill-opacity:0.4"
d="M20.99 2C10.5 2 1.99 9.15 1.99 18C1.99 26.82 10.5 34 20.99 34C31.47 34 40 26.82 40 18C40 9.15 31.47 2 20.99 2z
M40 36C37 33 36 26 36 26L28 32C28 32 36 36 40 36z"
transform="matrix(1.4544,0,0,1.4544,0,4)"
transform="matrix(1.4544,0,0,1.4544,2.9135,6.9089)"
/>
<linearGradient id="gradient0" gradientUnits="userSpaceOnUse" x1="128" y1="2" x2="128" y2="40">
<stop offset="0" stop-color="#fff"/>
<path style="fill:none; stroke:#000000; stroke-width:4; stroke-linecap:round; stroke-linejoin:round"
d="M20.99 2C10.5 2 1.99 9.15 1.99 18C1.99 26.82 10.5 34 20.99 34C31.47 34 40 26.82 40 18C40 9.15 31.47 2 20.99 2z
M40 36C37 33 36 26 36 26L28 32C28 32 36 36 40 36z"
transform="matrix(1.4544,0,0,1.4544,0.0047,4)"
/>
<linearGradient id="gradient0" gradientUnits="userSpaceOnUse" x1="128" y1="1.99" x2="128" y2="40">
<stop offset="0" stop-color="#ffffff"/>
<stop offset="1" stop-color="#e5e5e5"/>
</linearGradient>
<path fill="url(#gradient0)"
<path style="fill:url(#gradient0)"
d="M20.99 2C10.5 2 1.99 9.15 1.99 18C1.99 26.82 10.5 34 20.99 34C31.47 34 40 26.82 40 18C40 9.15 31.47 2 20.99 2z
M40 36C37 33 36 26 36 26L28 32C28 32 36 36 40 36z"
transform="matrix(1.4544,0,0,1.4544,0,4)"
transform="matrix(1.4544,0,0,1.4544,0.0047,4)"
/>
<path fill="#000"
<path style="fill:#000000"
d="M22.48 26.76H26.1V28.82H22.48
M26.1 21.75V23.99H22.48V21.75
M34.03 23.99H30.29V21.75H34.03

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 B

File diff suppressed because one or more lines are too long
+112 -3
View File
@@ -4,21 +4,34 @@
%% https://docs.ejabberd.im/developer/extending-ejabberd/localization/
{" (Add * to the end of field to match substring)"," (Добавете * в края на полето, за да съответства на подниза)"}.
{"{{ app_name }} already installed?","{{ app_name }} инсталиран ли е?"}.
{" has set the subject to: "," е задал темата на: "}.
{"{{ inviter }} has invited you to connect!","{{ inviter }} ви покани да се свържете!"}.
{"# participants","# участници"}.
{"A description of the node","Описание на нода"}.
{"A friendly name for the node","Удобно име на нода"}.
{"A fully-featured desktop chat client for Windows and Linux.","Пълнофункционален чат клиент за Windows и Linux."}.
{"A lean Jabber/XMPP client for Android. It aims at usability, low overhead and security, and works on low-end Android devices starting with Android 4.0.","Изчистен Jabber/XMPP клиент за Android. Насочен е към функционалност, лек и сигурен. Работи на устройства с Android от нисък клас, започвайки с Android 4.0."}.
{"A lightweight and powerful XMPP client for iPhone and iPad. It provides an easy way to talk and share moments with your friends.","Лек и мощен XMPP клиент за iPhone и iPad. Предоставя лесен начин да говорите и споделяте моменти с приятелите си."}.
{"A modern open-source chat client for iPhone and iPad. It is easy to use and has a clean user interface.","Модерен чат клиент с отворен код за iPhone и iPad. Лесен за използване, с изчистен потребителски интерфейс."}.
{"A modern open-source chat client for Mac. It is easy to use and has a clean user interface.","Модерен чат клиент с отворен код за Mac. Лесен за използване, с изчистен потребителски интерфейс."}.
{"A modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind.","Модерен клиент за чат с отворен код за настолни компютри. Фокусира се върху предоставянето на чисто и надеждно Jabber/XMPP изживяване, като същевременно се грижи за вашата поверителност."}.
{"A password is required to enter this room","Необходима е парола за влизане в тази стая"}.
{"A Web Page","Уеб страница"}.
{"Accept invite using {{ app_name }}","Приемете поканата, използвайки {{ app_name }}"}.
{"Accept","Приемам"}.
{"Access denied by service policy","Достъпът е отказан спрямо политиката на услугата"}.
{"Access model","Модел на достъп"}.
{"Account doesn't exist","Профилът не съществува"}.
{"Action on user","Действие върху потребител"}.
{"Add {{ inviter }} to your contact list","Добавете {{ inviter }} към вашия списък с контакти"}.
{"Add Contact","Добавете контакти"}.
{"Add User","Добави потребител"}.
{"Administration of ","Администриране на "}.
{"Administration","Администриране"}.
{"Administrator privileges required","Изискватт се администраторски права"}.
{"After clicking the button you will be taken to {{ app_name }} to finish setting up your new {{ site_name }} account.","След като щракнете върху бутона, ще бъдете пренасочени към {{ app_name }}, за да завършите настройката на новия вашия нов {{ site_name }} профил."}.
{"After successfully installing {{ app_name }}, come back to this page and <strong>continue with Step 2</strong>.","След като инсталирате успешно {{ app_name }}, върнете се на тази страница и <strong>продължете със стъпка 2</strong>."}.
{"All activity","Цялата статистика"}.
{"All Users","Всички потребители"}.
{"Allow subscription","Разреши абониране"}.
@@ -46,8 +59,10 @@
{"Anyone with Voice","Всеки, с възможност за гласово обаждане"}.
{"Anyone","Всеки"}.
{"API Commands","API Команди"}.
{"Apple Store Logo","Apple Store лого"}.
{"April","Април"}.
{"Arguments","Аргументи"}.
{"As a final reminder, your account details are shown below:","Като последно напомняне, данните за вашия профил са показани по-долу:"}.
{"Assign a hat to a user","Присвои шапка към потребител"}.
{"Attribute 'channel' is required for this request","Изисква се атрибут 'канал' за тази заявка"}.
{"Attribute 'id' is mandatory for MIX messages","Атрибутът 'id' е задължителен за MIX съобщения"}.
@@ -61,6 +76,9 @@
{"Backup to File at ","Архивиране във файл на "}.
{"Backup","Резервно копие"}.
{"Bad format","Лош формат"}.
{"BAD REQUEST","BAD REQUEST"}.
{"Beagle IM by Tigase, Inc. is a lightweight and powerful XMPP client for macOS.","Beagle IM от Tigase, Inc. е лек и мощен XMPP клиент за macOS."}.
{"Beagle IM Logo","Beagle IM лого"}.
{"Birthday","Рожден ден"}.
{"Both the username and the resource are required","Изискват се потребителското име и ресурсът"}.
{"Bytestream already activated","Bytestream вече е активиран"}.
@@ -77,6 +95,7 @@
{"Channel JID","JID на канал"}.
{"Channels","Канали"}.
{"Characters not allowed:","Неразрешени символи:"}.
{"Chat address (JID)","Чат адрес (JID)"}.
{"Chatroom configuration modified","Конфигурацията на стаята за чат е променена"}.
{"Chatroom is created","Стаята за чат е създадена"}.
{"Chatroom is destroyed","Стаята за чат е унищожена"}.
@@ -84,19 +103,29 @@
{"Chatroom is stopped","Стаята за чат е спряна"}.
{"Chatrooms","Чат стаи"}.
{"Choose a username and password to register with this server","Изберете потребителско име и парола, за да се регистрирате на този сървър"}.
{"Choose a username, this will become the first part of your new chat address.","Изберете потребителско име, то ще стане първата част от новия ви чат адрес."}.
{"Choose storage type of tables","Изберете тип за съхранение на таблици"}.
{"Choose whether to approve this entity's subscription.","Изберете дали да одобрите абонамента на този субект."}.
{"City","Град"}.
{"Click the button to open the Dino website where you can download and install it on your PC.","Щракнете върху бутона, за да отворите уебсайта на Dino, където можете да го изтеглите и инсталирате на вашия компютър."}.
{"Client acknowledged more stanzas than sent by server","Клиентът потвърди повече строфи от изпратените от сървъра"}.
{"Close","Затвори"}.
{"Clustering","Клъстеризация"}.
{"Commands","Команди"}.
{"Conference room does not exist","Конферентната стая не съществува"}.
{"Configuration of room ~s","Конфигурация на стая ~s"}.
{"Configuration","Конфигурация"}.
{"Congratulations!","Поздравления!"}.
{"Contact Addresses (normally, room owner or owners)","Адреси за контакт (обикновено собственик или собственици на стая)"}.
{"Contacts","Контакти"}.
{"Conversations is a Jabber/XMPP client for Android 6.0+ smartphones that has been optimized to provide a unique mobile experience.","Conversations е Jabber/XMPP клиент за смартфони с Android 6.0+, оптимизиран да осигури уникално мобилно изживяване."}.
{"Conversations Logo","Conversations лого"}.
{"Country","Държава"}.
{"Create a Hat","Създай Шапка"}.
{"Create Account","Създай профил"}.
{"Create an account","Създайте профил"}.
{"Creating an account will allow to communicate with other people on <strong>{{ site_name }}</strong> and other services on the XMPP network.","Създаването на профил, ще Ви позволи да комуникирате с други хора в <strong>{{ site_name }}</strong> и други услуги в мрежата XMPP."}.
{"Creating an account will allow to communicate with <strong>{{ inviter }}</strong> and other people on <strong>{{ site_name }}</strong> and other services on the XMPP network.","Създаването на профил, ще ви позволи да комуникирате с <strong>{{ inviter }}</strong>, други хора в <strong>{{ site_name }}</strong> и други услуги в мрежата XMPP."}.
{"Current Discussion Topic","Текуща тема на дискусита"}.
{"Database failure","Грешка в базата данни"}.
{"Database Tables Configuration at ","Конфигурация на таблиците в базата данни при "}.
@@ -109,9 +138,15 @@
{"Deliver event notifications","Достави известията за събития"}.
{"Deliver payloads with event notifications","Достави прикачените обекти с известията за събития"}.
{"Destroy a Hat","Разруши Шапка"}.
{"Dino Logo","Dino лого"}.
{"Disable User","Деактивирай потребителя"}.
{"Disc only copy","Копие само на диска"}.
{"Don't tell your password to anybody, not even the administrators of the XMPP server.","Не казвайте паролата си на никого, дори на администраторите на XMPP сървъра."}.
{"Download and install {{ app_name }} below:","Изтеглете и инсталирайте {{ app_name }} по-долу:"}.
{"Download Dino for Linux","Свалете Dino за Linux"}.
{"Download from Mac App Store","Изтегляне от Mac App Store"}.
{"Download Gajim","Свалете Gajim"}.
{"Download Renga for Haiku","Свалете Renga за Haiku"}.
{"Dump Backup to Text File at ","Архивиране в текстов файл при "}.
{"Dump to Text File","Архивиране в текстов файл"}.
{"Duplicated groups are not allowed by RFC6121","Дублирани групи не са разрешени от RFC6121"}.
@@ -133,6 +168,7 @@
{"Enable message archiving","Активирай архивиране на съобщенията"}.
{"Enabling push without 'node' attribute is not supported","Активиране на известията без атрибут 'нод' не се поддържа"}.
{"End User Session","Прекрати сесията на потребителя"}.
{"Enter a secure password that you do not use anywhere else.","Въведете сигурна парола, която не използвате на друго място."}.
{"Enter nickname you want to register","Въведете псевдонима, който желаете да регистрирате"}.
{"Enter path to backup file","Въведете пътя към архивния файл"}.
{"Enter path to jabberd14 spool dir","Въведете пътя към jabberd14 spool директорията"}.
@@ -153,6 +189,7 @@
{"Failed to process option '~s'","Неуспешо обработена опция '~s'"}.
{"Family Name","Фамилно име"}.
{"FAQ Entry","Въвеждане на ЧЗВ"}.
{"F-Droid Store Logo","F-Droid Store лого"}.
{"February","Февруари"}.
{"File larger than ~w bytes","Файлът е по-голям от ~w байта"}.
{"Fill in the form to search for any matching XMPP User","Попълнете формата, за да търсите съвпадащ XMPP потребител"}.
@@ -161,6 +198,7 @@
{"Full List of Room Admins","Пълен списък на администраторите на стаята"}.
{"Full List of Room Owners","Пълен списък на собствениците на стаята"}.
{"Full Name","Пълно име"}.
{"Gajim Logo","Gajim лого"}.
{"Get List of Active Users","Списък с активни потребители"}.
{"Get List of Disabled Users","Списък на деактивираните потребители"}.
{"Get List of Idle Users","Списък на неактивните потребители"}.
@@ -172,10 +210,12 @@
{"Get Number of Online Users","Брой на онлайн потребителите"}.
{"Get Number of Registered Users","Брой на регистрираните потребители"}.
{"Get Pending","Виж чакащи"}.
{"Get started","Започнете"}.
{"Get User Last Login Time","Покажи времето, когато потребителят е влязъл за последно"}.
{"Get User Roster","Списък с потребители"}.
{"Get User Statistics","Покажи статистика за потребителя"}.
{"Given Name","Наименование"}.
{"Google Play Store Logo","Google Play Store лого"}.
{"Grant voice to this person?","Предоставяне на глас за потребителя?"}.
{"has been banned","е със забранен достъп"}.
{"has been kicked because of a system shutdown","е отстранен поради изключване на системата"}.
@@ -188,9 +228,14 @@
{"Hat title","Заглавие на шапката"}.
{"Hat URI","URI адрес за шапка"}.
{"Hats limit exceeded","Превишен е лимитът за шапка"}.
{"Hide","Скрий"}.
{"Hint","Подсказка"}.
{"Host unknown","Неизвестен хост"}.
{"Hostname invalid","Невалидно име за хост"}.
{"HTTP File Upload","Качване на файл по HTTP"}.
{"Idle connection","Неактивна връзка"}.
{"If you already have {{ app_name }} installed, we recommend that you continue the account creation process using the app by clicking on the button below:","Ако вече сте инсталирали {{ app_name }}, препоръчваме Ви да продължите процеса на създаване на профил, използвайки приложението, като кликнете върху бутона по-долу:"}.
{"If you don't have an XMPP client installed yet, here's a list of suitable clients for your platform.","Ако все още нямате инсталиран XMPP клиент, ето списък с подходящи клиенти за вашата платформа."}.
{"If you don't see the CAPTCHA image here, visit the web page.","Ако не виждате CAPTCHA изображението, посетете уеб страницата."}.
{"Import Directory","Импорт на директория"}.
{"Import File","Импорт на файл"}.
@@ -209,6 +254,7 @@
{"Incorrect value of 'action' in data form","Неправилна стойност на 'action' във формата за данни"}.
{"Incorrect value of 'path' in data form","Неправилна стойност на 'path' във формата за данни"}.
{"Installed Modules:","Инсталирани модули:"}.
{"Installed ok? Great! <strong>Click or tap the button below</strong> to accept your invite and continue with your account setup:","Инсталира ли се нормално? Чудесно! <strong>Кликнете или докоснете бутона по-долу</strong>, за да приемете поканата си и да продължите с настройката на вашия профил:"}.
{"Install","Инсталирай"}.
{"Insufficient privilege","Недостатъчни права"}.
{"Internal server error","Вътрешна сървърна грешка"}.
@@ -216,6 +262,11 @@
{"Invalid node name","Невалидно име на нода"}.
{"Invalid 'previd' value","Невалидна стойност на 'previd'"}.
{"Invitations are not allowed in this conference","Поканите не са разрешени в тази конференция"}.
{"Invite Expired","Изтекла покана"}.
{"Invite expired","Поканата е изтекла"}.
{"Invite to {{ site_name }}","Покани в {{ site_name }}"}.
{"Invite User","Покани потребител"}.
{"Invite","Покани"}.
{"IP addresses","IP адреси"}.
{"is now known as","е известен като"}.
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Не е позволено да изпращате съобщения за грешки в стаята. Участникът (~s) е изпратил съобщение за грешка (~s) и е бил отстранен от стаята"}.
@@ -225,6 +276,7 @@
{"January","Януари"}.
{"JID normalization denied by service policy","Политиката на услугата не допуска нормализирането на JID"}.
{"JID normalization failed","Нормализирането на JID е неуспешно"}.
{"Join {{ site_name }} with {{ app_name }}","Присъединете се към {{ site_name }} с {{ app_name }}"}.
{"Joined MIX channels of ~ts","Свързани MIX канали на ~ts"}.
{"Joined MIX channels:","Свързани MIX канали:"}.
{"joins the room","се присъединява към стаята"}.
@@ -236,10 +288,13 @@
{"Last message","Последно съобщение"}.
{"Last month","Миналия месец"}.
{"Last year","Миналата година"}.
{"Launch {{ app_name }} and sign in using your account credentials.","Стартирайте {{ app_name }} и влезте, използвайки данни за вашия профил."}.
{"Launch Beagle IM, and select \\'Yes\\' to add a new account. Click the \\'+\\' button under the empty account list and then enter your credentials.","Стартирайте Beagle IM и изберете \\'Да\\', за да добавите нов профил. Кликнете върху бутона \\'+\\' под празния списък с профили и след това въведете вашите идентификационни данни."}.
{"Least significant bits of SHA-256 hash of text should equal hexadecimal label","Най-малко значимите битове SHA-256 хеш на текст трябва да са равни на шестнайсетичния етикет"}.
{"leaves the room","напуска стаята"}.
{"List of Hats","Списък с шапки"}.
{"List users with hats","Избройте потребителите с шапки"}.
{"Log in via web","Влезте през уеб"}.
{"Logged Out","Излязъл"}.
{"Logging","Регистриране на събития"}.
{"Make participants list public","Направи списъка с участниците публичен"}.
@@ -256,9 +311,11 @@
{"Max payload size in bytes","Максимален размер на прикачения обект в байтове"}.
{"Maximum file size","Максимален размер на файла"}.
{"Maximum Number of History Messages Returned by Room","Максимален брой съобщения от хронологията, върнати от стая"}.
{"Maximum number of invites reached","Максималният брой покани е достигнат"}.
{"Maximum number of items to persist","Максимален брой елементи за запазване"}.
{"Maximum Number of Occupants","Максимален брой участници"}.
{"May","Май"}.
{"Members are allowed to invite others","Участниците могат да канят други"}.
{"Membership is required to enter this room","Изисква се членство, за вход в тази стая"}.
{"Memorize your password, or write it in a paper placed in a safe place. In XMPP there isn't an automated way to recover your password if you forget it.","Запомнете паролата си или я запишете на лист хартия, поставен на сигурно място. В XMPP няма автоматичен начин за възстановяване на паролата в случай, че я забравите."}.
{"Mere Availability in XMPP (No Show Value)","Наличност в XMPP (Не показвай стойност)"}.
@@ -273,6 +330,7 @@
{"Moderators Only","Само модератори"}.
{"Moderator","Модератор"}.
{"Module failed to handle the query","Модулът не успя да обработи заявката"}.
{"Monal Logo","Monal лого"}.
{"Monday","Понеделник"}.
{"Multicast","Мултикаст"}.
{"Multiple <item/> elements are not allowed by RFC6121","Повече от един <item/> елемента не се разрешават от RFC6121"}.
@@ -326,6 +384,7 @@
{"Node","Нод"}.
{"None","Нито един"}.
{"Not allowed","Не е разрешено"}.
{"NOT FOUND","NOT FOUND"}.
{"Not Found","Не е намерен"}.
{"Not subscribed","Няма абонамент"}.
{"Notify subscribers when items are removed from the node","Уведоми абонатите, когато елементите бъдат премахнати от нода"}.
@@ -363,12 +422,15 @@
{"Only service administrators are allowed to send service messages","Само администраторите на услуги имат право да изпращат системни съобщения"}.
{"Only those on a whitelist may associate leaf nodes with the collection","Само тези от списъка с позволени могат да свързват листови нодове с колекцията"}.
{"Only those on a whitelist may subscribe and retrieve items","Само тези от списъка с позволени могат да се абонират и да извличат елементи"}.
{"Open the app","Отворете приложението"}.
{"Organization Name","Име на организацията"}.
{"Organization Unit","Отдел"}.
{"Other Modules Available:","Други налични модули:"}.
{"Other software","Друг софтуер"}.
{"Outgoing s2s Connections","Изходящи s2s връзки"}.
{"Owner privileges required","Изискват се привилегии на собственик"}.
{"Packet relay is denied by service policy","Предаването на пакети е отказано от политиката на услугата"}.
{"Page navigation","Навигация по страниците"}.
{"Participant ID","ID на участник"}.
{"Participant","Участник"}.
{"Password Verification","Проверка на паролата"}.
@@ -384,6 +446,8 @@
{"Ping query is incorrect","Заявката за пинг е неправилна"}.
{"Ping","Пинг"}.
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","Обърнете внимание, че тези опции ще направят резервно копие само на вградената (Mnesia) база данни. Ако използвате модула ODBC, трябва да направите резервно копие на SQL базата данни отделно."}.
{"Please provide a password! Minimum length: {{ password_min_length }}","Моля, въведете парола! Минимална дължина: {{ password_min_length }}"}.
{"Please provide a valid username!","Моля, въведете валидно потребителско име!"}.
{"Please, wait for a while before sending new voice request","Моля, изчакайте известно време, преди да изпратите нова заявка за гласова връзка"}.
{"Pong","Понг"}.
{"Possessing 'ask' attribute is not allowed by RFC6121","Притежаването на атрибут 'ask' не е разрешено от RFC6121"}.
@@ -393,6 +457,7 @@
{"Previous session PID has exited","Предишният PID на сесията е излязъл"}.
{"Previous session PID is dead","PID от предишната сесия не съществува"}.
{"Previous session timed out","Времето на предишната сесия изтече"}.
{"Previous","Предишен"}.
{"private, ","частна, "}.
{"Public","Публичен"}.
{"Publish model","Модел за публикуване"}.
@@ -411,11 +476,17 @@
{"Receive notification of new nodes only","Получаване на известия само за нови нодове"}.
{"Recipient is not in the conference room","Получателят не е в конферентната стая"}.
{"Re-Enable User","Активиране на потребител"}.
{"Register an XMPP account","Регистрирай XMPP акаунт"}.
{"Register an XMPP account","Регистрирай XMPP профил"}.
{"Register on {{ site_name }}","Регистрирайте се на {{ site_name }}"}.
{"Register","Регистрирай"}.
{"Registration Error","Грешка при регистрация"}.
{"Registration error","Грешка при регистриране"}.
{"Registration Form","Форма за регистрация"}.
{"Registration Success","Успешна регистрация"}.
{"Remote copy","Отдалечено копие"}.
{"Remove a hat from a user","Премахни шапка от потребител"}.
{"Remove User","Премахни потребител"}.
{"Renga Logo","Renga лого"}.
{"Replaced by new connection","Заменен от нова връзка"}.
{"Request has timed out","Времето за заявка изтече"}.
{"Request is ignored","Заявката е игнорирано"}.
@@ -441,16 +512,21 @@
{"Roster size","Размер на списъка с контакти"}.
{"Running Nodes","Работещи нодове"}.
{"~s invites you to the room ~s","~s ви кани в стая ~s"}.
{"Sad person holding empty box","Тъжен човек, държащ празна кутия"}.
{"Saturday","Събота"}.
{"Scan invite code","Сканирайте код за покана"}.
{"Scan with mobile device","Сканиране с мобилно устройство"}.
{"Search from the date","Търси от дата"}.
{"Search Results for ","Резултати от търсенето за "}.
{"Search the text","Търси текста"}.
{"Search until the date","Търси до дата"}.
{"Search users in ","Търси потребители в "}.
{"Select","Избери"}.
{"Send announcement to all online users on all hosts","Изпрати съобщение до всички онлайн потребители на всички хостове"}.
{"Send announcement to all online users","Изпрати съобщение до всички онлайн потребители"}.
{"Send announcement to all users on all hosts","Изпрати съобщение до всички потребители на всички хостове"}.
{"Send announcement to all users","Изпрати съобщение до всички потребители"}.
{"Send this invite to your device","Изпратете тази покана до вашето устройство"}.
{"September","Септември"}.
{"Server:","Сървър:"}.
{"Service list retrieval timed out","Времето за изчакване на извличането на списъка с услуги изтече"}.
@@ -461,9 +537,14 @@
{"Show Integral Table","Покажи интегрална таблица"}.
{"Show Occupants Join/Leave","Покажи участници Влязъл/Напускнал"}.
{"Show Ordinary Table","Покажи обикновена таблица"}.
{"Showing apps for <span class='platform-name'>your current platform</span> only. You may also <a href='#' id='show-all-clients-button' tabindex=\"{{ tabidx }}\">view all apps.</a>","Показват се приложения само за <span class='platform-name'>текущата ви платформа</span>. Можете също да <a href='#' id='show-all-clients-button' tabindex=\"{{ tabidx }}\">прегледате всички приложения.</a>"}.
{"Show","Покажи"}.
{"Shut Down Service","Изключи услугата"}.
{"Siskin IM Logo","Siskin IM лого"}.
{"SOCKS5 Bytestreams","SOCKS5 байтови потоци"}.
{"Some XMPP clients can store your password in the computer, but you should do this only in your personal computer for safety reasons.","Някои XMPP клиенти могат да съхраняват паролата Ви в компютъра, но от съображения за сигурност трябва да го правите само на личния си компютър."}.
{"Sorry, it looks like this invite code has expired!","Съжалявам, изглежда, че този код за покана е изтекъл!"}.
{"Sorry, there was a problem registering your account.","За съжаление, възникна проблем с регистрацията на вашия профил."}.
{"Sources Specs:","Спецификации на източниците:"}.
{"Specify the access model","Задай модела за достъп"}.
{"Specify the event message type","Задай типа на съобщението за събитие"}.
@@ -471,11 +552,19 @@
{"Stanza id is not valid","Невалидно ID на строфата"}.
{"Stanza ID","ID на строфа"}.
{"Statically specify a replyto of the node owner(s)","Статично задаване на replyto на собственика(ците) на нода"}.
{"Step 1: Download and install {{ app_name }}","Стъпка 1: Изтеглете и инсталирайте {{ app_name }}"}.
{"Step 1: Install {{ app_name }}","Стъпка 1: Инсталирайте {{ app_name }}"}.
{"Step 2: Activate your account","Стъпка 2: Активирайте профила си"}.
{"Step 2: Connect {{ app_name }} to your new account","Стъпка 2: Свържете {{ app_name }} с новия си профил"}.
{"Stopped Nodes","Спрени нодове"}.
{"Store binary backup:","Запази бинарен архив:"}.
{"Store plain text backup:","Запази архив като обикновен текст:"}.
{"Stream management is already enabled","Управлението на потока вече е активирано"}.
{"Stream management is not enabled","Управлението на потока не е активирано"}.
{"<strong>{{ site_name }}</strong> is part of XMPP, a secure and decentralized messaging network. To begin chatting using <strong>{{ app_name }}</strong> you need to first register an account.","<strong>{{ site_name }}</strong> е част от XMPP, сигурна и децентрализирана мрежа за съобщения. За да започнете да чатите, използвайки <strong>{{ app_name }}</strong>, първо трябва да регистрирате профил."}.
{"<strong>{{ site_name }}</strong> is part of XMPP, a secure and decentralized messaging network. To begin chatting you need to first register an account.","<strong>{{ site_name }}</strong> е част от XMPP, сигурна и децентрализирана мрежа за съобщения. За да започнете да чатите, първо трябва да регистрирате профил."}.
{"<strong>No suitable software installed right now?</strong> You can also log in to your account through our online web chat!","<strong>В момента нямате инсталиран подходящ софтуер?</strong> Можете, също да влезете в профила си чрез нашия онлайн уеб чат!"}.
{"<strong>Tip:</strong> You can open this invite on your mobile device by scanning a barcode with your camera.","<strong>Съвет:</strong> Можете да отворите тази покана на мобилното си устройство, като сканирате баркод с камерата."}.
{"Subject","Тема"}.
{"Submitted","Изпратено"}.
{"Submit","Изпрати"}.
@@ -536,11 +625,14 @@
{"The sender of the last received message","Подателят на последното получено съобщение"}.
{"The stanza MUST contain only one <active/> element, one <default/> element, or one <list/> element","Строфата ТРЯБВА да съдържа само един <active/> елемент, един <default/> елемент или един <list/> елемент"}.
{"The subscription identifier associated with the subscription request","Идентификаторът на абонамента, свързан със заявката за абонамент"}.
{"The token provided is either invalid or expired.","Предоставеният токен код е невалиден или изтекъл."}.
{"The URL of an XSL transformation which can be applied to payloads in order to generate an appropriate message body element.","URL адрес на XSL трансформацията, която може да се приложи към прикачените данни, за да се генерира подходящ елемент от тялото на съобщението."}.
{"The URL of an XSL transformation which can be applied to the payload format in order to generate a valid Data Forms result that the client could display using a generic Data Forms rendering engine","URL адрес на XSL трансформацията, която може да се приложи към формата на прикачените данни, за да се генерира валиден резултат от Data Forms, който клиентът може да покаже с помощта на общ механизъм за визуализация на Data Forms"}.
{"There was an error changing the password: ","Възникна грешка при промяна на паролата: "}.
{"There was an error creating the account: ","Възникна грешка при създаването на профила: "}.
{"There was an error deleting the account: ","Възникна грешка при изтриването на профила: "}.
{"This button works only if you have the app installed already!","Този бутон работи, само ако вече имате инсталирано приложението!"}.
{"This is an invite from <strong>{{ inviter }}</strong> to connect and chat on the XMPP network. If you already have an XMPP client installed just press the button below!","Това е покана от <strong>{{ inviter }}</strong> за свързване и чат в XMPP мрежата. Ако вече имате инсталиран XMPP клиент, просто натиснете бутона по-долу!"}.
{"This is case insensitive: macbeth is the same that MacBeth and Macbeth.","Не е чувствително към регистъра (главни и малки букви): \"macbeth\"е същото като \"MacBeth\"и \"Macbeth\"."}.
{"This page allows to register an XMPP account in this XMPP server. Your JID (Jabber ID) will be of the form: username@server. Please read carefully the instructions to fill correctly the fields.","Тази страница позволява регистриране на XMPP профил на този сървър. Вашият JID (Jabber ID) ще бъде във формата: username@server. Моля, прочетете внимателно инструкциите, за да попълните правилно полетата."}.
{"This page allows to unregister an XMPP account in this XMPP server.","Тази страница позволява премахване на XMPP профил от този сървър."}.
@@ -549,7 +641,9 @@
{"Thursday","Четвъртък"}.
{"Time delay","Закъснение"}.
{"Timed out waiting for stream resumption","Времето за изчакване за възобновяване на потока изтече"}.
{"To get started, you need to install an app for your platform:","За да започнете, трябва да инсталирате приложение за вашата платформа:"}.
{"To register, visit ~s","За да се регистрирате, посетете ~s"}.
{"To start chatting, you need to enter your new account credentials into your chosen XMPP software.","За да започнете чат, трябва да въведете новите си идентификационни данни в избрания от вас XMPP софтуер."}.
{"To ~ts","До ~ts"}.
{"Token TTL","Токен TTL"}.
{"Too many active bytestreams","Твърде много активни \"bytestreams\" потоци"}.
@@ -582,6 +676,7 @@
{"Updating the vCard is not supported by the vCard storage backend","Актуализирането на vCard не се поддържа от настройката за съхранение на vCard"}.
{"Upgrade","Обнови"}.
{"URL for Archived Discussion Logs","URL адрес за дневници на архивирани дискусии"}.
{"Use a <em>QR code</em> scanner on your mobile device to scan the code below:","Използвайте скенер за <em>QR код</em> на вашето мобилно устройство, за да сканирате кода по-долу:"}.
{"User already exists","Потребителят вече съществува"}.
{"User (jid)","Потребител (jid)"}.
{"User JID","Потребител JID"}.
@@ -591,6 +686,9 @@
{"User session not found","Потребителската сесия не е намерена"}.
{"User session terminated","Потребителската сесия е прекратена"}.
{"User ~ts","Потребител ~ts"}.
{"Username invalid","Невалидно потребителско име"}.
{"Username is reserved","Потребителското име е резервирано"}.
{"Username","Потребителско име"}.
{"Username:","Потребителско име:"}.
{"Users are not allowed to register accounts so quickly","Не е разрешено потребителите да регистрират профили толкова бързо"}.
{"Users Last Activity","Последна активност на потребителите"}.
@@ -627,6 +725,7 @@
{"Wrong parameters in the web formulary","Грешни параметри в уеб формуляра"}.
{"Wrong xmlns","Грешен xmlns"}.
{"XMPP Account Registration","Регистриране на XMPP профил"}.
{"XMPP client for Haiku","XMPP клиент за Haiku"}.
{"XMPP Domains","XMPP домейни"}.
{"XMPP Show Value of Away","XMPP покажи стойност на Отсъства"}.
{"XMPP Show Value of Chat","XMPP покажи стойност на Чат"}.
@@ -636,8 +735,17 @@
{"You are being removed from the room because of a system shutdown","Премахнати сте от стаята поради изключване на системата"}.
{"You are not allowed to send private messages","Нямате право да изпращате лични съобщения"}.
{"You are not joined to the channel","Не сте присъединени към канала"}.
{"You can connect to {{ site_name }} using any XMPP-compatible software. If your preferred software is not listed above, you may still <a href=\"{{ registration_url }}\" tabindex='{{ tabidx }}'>register an account manually</a>.","Можете да се свържете с {{ site_name }}, използвайки всеки XMPP-съвместим софтуер. Ако предпочитаният от вас софтуер не е посочен по-горе, все още можете <a href=\"{{ registration_url }}\" tabindex='{{ tabidx }}'>да регистрирате акаунт ръчно</a>."}.
{"You can later change your password using an XMPP client.","По-късно можете да промените паролата си с помощта на XMPP клиент."}.
{"You can now set up {{ app_name }} and connect it to your new account.","Вече можете да настроите {{ app_name }} и да го свържете с вашия нов профил."}.
{"You can start chatting right away with {{ app_name }}. Let's get started!","Можете да започнете да чатите веднага с {{ app_name }}. Да започваме!"}.
{"You can transfer this invite to your mobile device by scanning a code with your camera.","Можете да прехвърлите тази покана на мобилното си устройство, като сканирате код с камерата на вашето устройство."}.
{"You have been banned from this room","Достъпът ви до тази стая е забранен"}.
{"You have been invited to chat on {{ site_name }}, part of the XMPP secure and decentralized messaging network.","Поканени сте да чатите в {{ site_name }}, част от защитената и децентрализирана мрежа за съобщения XMPP."}.
{"You have been invited to chat on <strong>{{ site_name }}</strong>, part of the XMPP secure and decentralized messaging network.","Поканени сте да чатите в <strong>{{ site_name }}</strong>, част от защитената и децентрализирана мрежа за съобщения XMPP."}.
{"You have been invited to chat with <strong>{{ inviter }}</strong> on {{ site_name }}, part of the XMPP secure and decentralized messaging network.","Поканени сте да чатите с <strong>{{ inviter }}</strong> на {{ site_name }}, част от защитената и децентрализирана мрежа за съобщения XMPP."}.
{"You have been invited to chat with <strong>{{ inviter }}</strong> on <strong>{{ site_name }}</strong>, part of the XMPP secure and decentralized messaging network.","Поканени сте да чатите с <strong>{{ inviter }}</strong>на <strong>{{ site_name }}</strong>, част от защитената и децентрализирана мрежа за съобщения XMPP."}.
{"You have created an account on <strong>{{ site_name }}</strong>.","Създадохте профил в <strong>{{ site_name }}</strong>."}.
{"You have joined too many conferences","Присъединили сте се към твърде много конференции"}.
{"You must fill in field \"Nickname\" in the form","Трябва да попълните полето \"Псевдоним\" във формата"}.
{"You need a client that supports x:data and CAPTCHA to register","За да се регистрирате Ви е нужен клиент, който поддържа x:data и CAPTCHA"}.
@@ -645,7 +753,8 @@
{"You need an x:data capable client to search","За да търсите, Ви е нужен клиент, който поддържа x:data"}.
{"Your active privacy list has denied the routing of this stanza.","Вашият активен списък за поверителност отказа маршрутизирането на тази строфа."}.
{"Your contact offline message queue is full. The message has been discarded.","Достигнат е максималният брой офлайн съобщения за вашия контакт. Съобщението е отхвърлено."}.
{"Your password is stored encrypted on the server and will not be accessible after you close this page. Keep it safe and never share it with anyone.","Вашата парола се съхранява криптирана на сървъра и няма да бъде достъпна, след като затворите тази страница. Пазете я в безопасност и не я споделяйте с никого."}.
{"Your subscription request and/or messages to ~s have been blocked. To unblock your subscription request, visit ~s","Вашата заявка за абонамент и/или съобщения до ~s са блокирани. За да отблокирате заявката си за абонамент, посетете ~s"}.
{"Your XMPP account was successfully registered.","Вашият XMPP акаунт, беше регистриран успешно."}.
{"Your XMPP account was successfully unregistered.","Вашият XMPP акаунт, беше успешно дерегистриран."}.
{"Your XMPP account was successfully registered.","Вашият XMPP профил, беше регистриран успешно."}.
{"Your XMPP account was successfully unregistered.","Вашият XMPP профил, беше успешно премахнат."}.
{"You're not allowed to create nodes","Нямате право да създавате нодове"}.
+118 -9
View File
@@ -4,21 +4,34 @@
%% https://docs.ejabberd.im/developer/extending-ejabberd/localization/
{" (Add * to the end of field to match substring)"," (Afegix * al final d'un camp per a buscar subcadenes)"}.
{"{{ app_name }} already installed?","Ja està instal·lat {{ app_name }}?"}.
{" has set the subject to: "," ha posat el tema: "}.
{"{{ inviter }} has invited you to connect!","{{ inviter }} t'ha invitat a connectar-te!"}.
{"# participants","# participants"}.
{"A description of the node","Una descripció del node"}.
{"A friendly name for the node","Un nom per al node"}.
{"A fully-featured desktop chat client for Windows and Linux.","Un client de xat molt complet d'escriptori per a Windows i Linux."}.
{"A lean Jabber/XMPP client for Android. It aims at usability, low overhead and security, and works on low-end Android devices starting with Android 4.0.","Un client Jabber/XMPP lleuger per a Android. Està orientat a la usabilitat, la seguretat i el baix consum, i funciona en aparats Android antics a partir de Android 4.0."}.
{"A lightweight and powerful XMPP client for iPhone and iPad. It provides an easy way to talk and share moments with your friends.","Un client XMPP lleuger i potent per a iPhone e iPad. Proporciona una forma senzilla de xarrar i compartir moments amb les teues amistats."}.
{"A modern open-source chat client for iPhone and iPad. It is easy to use and has a clean user interface.","Un client de xat modern i de software lliure per a iPhone i iPad. Es simple d'usar i te una interfície d'usuari neta."}.
{"A modern open-source chat client for Mac. It is easy to use and has a clean user interface.","Un client de xat modern i de software lliure per a Mac. Es simple d'usar i te una interfície d'usuari neta."}.
{"A modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind.","Un client de xat modern i de software lliure per a l'escriptori. Està enfocat en proporcionar una experiència Jabber/XMPP neta i comfiable, mentre mantenes la teva privacitat en ment."}.
{"A password is required to enter this room","Es necessita contrasenya per a entrar en aquesta sala"}.
{"A Web Page","Una Pàgina Web"}.
{"Accept invite using {{ app_name }}","Accepta la invitació usant {{ app_name }}"}.
{"Accept","Acceptar"}.
{"Access denied by service policy","Accés denegat per la política del servei"}.
{"Access model","Model d'Accés"}.
{"Account doesn't exist","El compte no existeix"}.
{"Action on user","Acció en l'usuari"}.
{"Add {{ inviter }} to your contact list","Afegir {{ inviter }} a la teua llista de contactes"}.
{"Add Contact","Afegir Contacte"}.
{"Add User","Afegir usuari"}.
{"Administration of ","Administració de "}.
{"Administration","Administració"}.
{"Administrator privileges required","Es necessita tenir privilegis d'administrador"}.
{"After clicking the button you will be taken to {{ app_name }} to finish setting up your new {{ site_name }} account.","Al polsar el boto, s'obrirà {{ app_name }} per a terminar de configurar el teu compte a {{ site_name }}."}.
{"After successfully installing {{ app_name }}, come back to this page and <strong>continue with Step 2</strong>.","Després d'instal·lar correctament {{ app_name }}, torna ací i <strong>continua amb el Pas 2</strong>."}.
{"All activity","Tota l'activitat"}.
{"All Users","Tots els usuaris"}.
{"Allow subscription","Permetre subscripció"}.
@@ -46,8 +59,10 @@
{"Anyone with Voice","Qualsevol amb Veu"}.
{"Anyone","Qualsevol"}.
{"API Commands","Comandaments API"}.
{"Apple Store Logo","Logo de la Apple Store"}.
{"April","Abril"}.
{"Arguments","Arguments"}.
{"As a final reminder, your account details are shown below:","Com a recordatori final, el detalls del teu compte es mostren ací:"}.
{"Assign a hat to a user","Asignar un barret a un usuari"}.
{"Attribute 'channel' is required for this request","L'atribut 'channel' és necessari per a aquesta petició"}.
{"Attribute 'id' is mandatory for MIX messages","L'atribut 'id' es necessari per a missatges MIX"}.
@@ -61,6 +76,9 @@
{"Backup to File at ","Desar còpia de seguretat a fitxer en "}.
{"Backup","Guardar còpia de seguretat"}.
{"Bad format","Format erroni"}.
{"BAD REQUEST","MALA PETICIÓ"}.
{"Beagle IM by Tigase, Inc. is a lightweight and powerful XMPP client for macOS.","Beagle IM de Tigase, Inc. es un client XMPP lleuger i poderós per a macOS."}.
{"Beagle IM Logo","Logo de Beagle IM"}.
{"Birthday","Aniversari"}.
{"Both the username and the resource are required","Es requereixen tant el nom d'usuari com el recurs"}.
{"Bytestream already activated","El Bytestream ja està activat"}.
@@ -77,6 +95,7 @@
{"Channel JID","JID del Canal"}.
{"Channels","Canals"}.
{"Characters not allowed:","Caràcters no permesos:"}.
{"Chat address (JID)","Direcció de xarrar (JID)"}.
{"Chatroom configuration modified","Configuració de la sala de xat modificada"}.
{"Chatroom is created","La sala s'ha creat"}.
{"Chatroom is destroyed","La sala s'ha destruït"}.
@@ -84,34 +103,50 @@
{"Chatroom is stopped","La sala s'ha aturat"}.
{"Chatrooms","Sales de xat"}.
{"Choose a username and password to register with this server","Tria nom d'usuari i contrasenya per a registrar-te en aquest servidor"}.
{"Choose a username, this will become the first part of your new chat address.","Elegeix un nom de compte, que serà la primera part de la direcció del teu compte."}.
{"Choose storage type of tables","Selecciona el tipus d'almacenament de les taules"}.
{"Choose whether to approve this entity's subscription.","Tria si aproves aquesta entitat de subscripció."}.
{"City","Ciutat"}.
{"Click the button to open the Dino website where you can download and install it on your PC.","Polsa el botó per a obrir la pàgina web de Dino on podràs descarregar el programa per a instalarlo al teu ordinador."}.
{"Client acknowledged more stanzas than sent by server","El client ha reconegut més paquets dels que ha enviat el servidor"}.
{"Close","Tancar"}.
{"Clustering","Clustering"}.
{"Commands","Comandaments"}.
{"Conference room does not exist","La sala de conferències no existeix"}.
{"Configuration of room ~s","Configuració de la sala ~s"}.
{"Configuration","Configuració"}.
{"Congratulations!","Enhorabona!"}.
{"Contact Addresses (normally, room owner or owners)","Adreces de contacte (normalment, propietaris de la sala)"}.
{"Contacts","Contactes"}.
{"Conversations is a Jabber/XMPP client for Android 6.0+ smartphones that has been optimized to provide a unique mobile experience.","Conversations es un client Jabber/XMPP per a mòbils Android 6.0+ que ha sigut optimitzat per a proporcionar una experiència mòbil unica."}.
{"Conversations Logo","Logo de Conversations"}.
{"Country","Pais"}.
{"Create a Hat","Crear un barret"}.
{"Create Account","Crear un Compte"}.
{"Create an account","Crear un compte"}.
{"Creating an account will allow to communicate with other people on <strong>{{ site_name }}</strong> and other services on the XMPP network.","Crear un compte et permetrà comunicar-te amb altra gent a <strong>{{ site_name }}</strong> i a altres serveis de la xarxa XMPP."}.
{"Creating an account will allow to communicate with <strong>{{ inviter }}</strong> and other people on <strong>{{ site_name }}</strong> and other services on the XMPP network.","Crear un compte e permetrà comunicar-te amb <strong>{{ inviter }}</strong> i altra gent a <strong>{{ site_name }}</strong> i a altres serveis de la xarxa XMPP."}.
{"Current Discussion Topic","Assumpte de discussió actual"}.
{"Database failure","Error a la base de dades"}.
{"Database Tables Configuration at ","Configuració de la base de dades en "}.
{"Database","Base de dades"}.
{"December","Decembre"}.
{"Default users as participants","Els usuaris són participants per defecte"}.
{"Delete message of the day on all hosts","Elimina el missatge del dis de tots els hosts"}.
{"Delete message of the day on all hosts","Elimina el missatge del dia de tots els dominis"}.
{"Delete message of the day","Eliminar el missatge del dia"}.
{"Delete User","Eliminar Usuari"}.
{"Deliver event notifications","Entrega de notificacions d'events"}.
{"Deliver payloads with event notifications","Enviar payloads junt a les notificacions d'events"}.
{"Destroy a Hat","Destruir un barret"}.
{"Dino Logo","Logo de Dino"}.
{"Disable User","Deshabilitar un Usuari"}.
{"Disc only copy","Còpia sols en disc"}.
{"Don't tell your password to anybody, not even the administrators of the XMPP server.","No li donis la teva contrasenya a ningú, ni tan sols als administradors del servidor XMPP."}.
{"Download and install {{ app_name }} below:","Descarrega e instal·la {{ app_name }} ací baix:"}.
{"Download Dino for Linux","Descarrega Dino per a Linux"}.
{"Download from Mac App Store","Descarrega de la Tenda d'Apps de Mac"}.
{"Download Gajim","Descarregar Gajim"}.
{"Download Renga for Haiku","Descarrega Renga per a Haiku"}.
{"Dump Backup to Text File at ","Exporta còpia de seguretat a fitxer de text en "}.
{"Dump to Text File","Exportar a fitxer de text"}.
{"Duplicated groups are not allowed by RFC6121","No estan permesos els grups duplicats al RFC6121"}.
@@ -133,6 +168,7 @@
{"Enable message archiving","Activar l'emmagatzematge de missatges"}.
{"Enabling push without 'node' attribute is not supported","No està suportat activar Push sense l'atribut 'node'"}.
{"End User Session","Finalitzar Sesió d'Usuari"}.
{"Enter a secure password that you do not use anywhere else.","Introdueix una contrasenya segura que no estiguis usant en cap altre lloc."}.
{"Enter nickname you want to register","Introdueix el sobrenom que vols registrar"}.
{"Enter path to backup file","Introdueix ruta al fitxer de còpia de seguretat"}.
{"Enter path to jabberd14 spool dir","Introdueix la ruta al directori de jabberd14 spools"}.
@@ -143,7 +179,7 @@
{"Exclude Jabber IDs from CAPTCHA challenge","Excloure Jabber IDs de la comprovació CAPTCHA"}.
{"Export all tables as SQL queries to a file:","Exporta totes les taules a un fitxer SQL:"}.
{"Export data of all users in the server to PIEFXIS files (XEP-0227):","Exportar dades de tots els usuaris del servidor a arxius PIEFXIS (XEP-0227):"}.
{"Export data of users in a host to PIEFXIS files (XEP-0227):","Exportar dades d'usuaris d'un host a arxius PIEFXIS (XEP-0227):"}.
{"Export data of users in a host to PIEFXIS files (XEP-0227):","Exportar dades d'usuaris d'un domini a arxius PIEFXIS (XEP-0227):"}.
{"External component failure","Error al component extern"}.
{"External component timeout","Temps esgotat al component extern"}.
{"Failed to activate bytestream","Errada al activar bytestream"}.
@@ -153,6 +189,7 @@
{"Failed to process option '~s'","Ha fallat el processat de la opció '~s'"}.
{"Family Name","Cognom"}.
{"FAQ Entry","Entrada a la FAQ"}.
{"F-Droid Store Logo","Logo de la F-Droid Store"}.
{"February","Febrer"}.
{"File larger than ~w bytes","El fitxer es més gran que ~w bytes"}.
{"Fill in the form to search for any matching XMPP User","Emplena camps per a buscar usuaris XMPP que concorden"}.
@@ -161,6 +198,7 @@
{"Full List of Room Admins","Llista completa de administradors de la sala"}.
{"Full List of Room Owners","Llista completa de propietaris de la sala"}.
{"Full Name","Nom complet"}.
{"Gajim Logo","Logo de Gajim"}.
{"Get List of Active Users","Obté la llista d'usuaris actius"}.
{"Get List of Disabled Users","Obté la llista d'usuaris deshabilitats"}.
{"Get List of Idle Users","Obté la llista d'usuaris inactius"}.
@@ -172,10 +210,12 @@
{"Get Number of Online Users","Obtenir Número d'Usuaris Connectats"}.
{"Get Number of Registered Users","Obtenir Número d'Usuaris Registrats"}.
{"Get Pending","Obtenir Pendents"}.
{"Get started","Comencem"}.
{"Get User Last Login Time","Obtenir la última connexió d'Usuari"}.
{"Get User Roster","Obtenir llista de contactes de l'usuari"}.
{"Get User Statistics","Obtenir Estadístiques d'Usuari"}.
{"Given Name","Nom propi"}.
{"Google Play Store Logo","Logo de la Google Play Store"}.
{"Grant voice to this person?","Concedir veu a aquesta persona?"}.
{"has been banned","ha sigut bloquejat"}.
{"has been kicked because of a system shutdown","ha sigut expulsat perquè el sistema va a apagar-se"}.
@@ -188,9 +228,14 @@
{"Hat title","Títol del barret"}.
{"Hat URI","URI del barret"}.
{"Hats limit exceeded","El límit de tràfic ha sigut sobrepassat"}.
{"Host unknown","Host desconegut"}.
{"Hide","Amagar"}.
{"Hint","Sugeriment"}.
{"Host unknown","Domini desconegut"}.
{"Hostname invalid","Nom de domini no vàlid"}.
{"HTTP File Upload","HTTP File Upload"}.
{"Idle connection","Connexió sense us"}.
{"If you already have {{ app_name }} installed, we recommend that you continue the account creation process using the app by clicking on the button below:","Si ja tens instal·lat {{ app_name }}, et recomanem que continues la creació del teu compte usant eixa app, polsa el boto:"}.
{"If you don't have an XMPP client installed yet, here's a list of suitable clients for your platform.","Si encara no tens un client XMPP instal·lat, mira esta llista de clients disponibles per a la teva plataforma."}.
{"If you don't see the CAPTCHA image here, visit the web page.","Si no veus la imatge CAPTCHA açí, visita la pàgina web."}.
{"Import Directory","Importar directori"}.
{"Import File","Importar fitxer"}.
@@ -209,6 +254,7 @@
{"Incorrect value of 'action' in data form","Valor incorrecte de 'action' al formulari de dades"}.
{"Incorrect value of 'path' in data form","Valor incorrecte de 'path' al formulari de dades"}.
{"Installed Modules:","Mòduls instal·lats:"}.
{"Installed ok? Great! <strong>Click or tap the button below</strong> to accept your invite and continue with your account setup:","S'ha instal·lat correctament? Perfecte! <strong>Polsa en el següent boto</strong> per a acceptar l'invitació i continuar preparant el teu compte:"}.
{"Install","Instal·lar"}.
{"Insufficient privilege","Privilegi insuficient"}.
{"Internal server error","Error intern del servidor"}.
@@ -216,6 +262,11 @@
{"Invalid node name","Nom de node no vàlid"}.
{"Invalid 'previd' value","Valor no vàlid de 'previd'"}.
{"Invitations are not allowed in this conference","Les invitacions no estan permeses en aquesta sala de conferència"}.
{"Invite expired","La invitació ha expirat"}.
{"Invite Expired","La Invitació ha Expirat"}.
{"Invite to {{ site_name }}","Invitació a {{ site_name }}"}.
{"Invite User","Invitar Usuari"}.
{"Invite","Invitar"}.
{"IP addresses","Adreça IP"}.
{"is now known as","ara es conegut com"}.
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","No està permés enviar missatges d'error a la sala. El participant (~s) ha enviat un missatge d'error (~s) i ha sigut expulsat de la sala"}.
@@ -225,6 +276,7 @@
{"January","Gener"}.
{"JID normalization denied by service policy","S'ha denegat la normalització del JID per política del servei"}.
{"JID normalization failed","Ha fallat la normalització del JID"}.
{"Join {{ site_name }} with {{ app_name }}","Uneix-te a {{ site_name }} amb {{ app_name }}"}.
{"Joined MIX channels of ~ts","Canals MIX units de ~ts"}.
{"Joined MIX channels:","Canals MIX units:"}.
{"joins the room","entra a la sala"}.
@@ -236,10 +288,13 @@
{"Last message","Últim missatge"}.
{"Last month","Últim mes"}.
{"Last year","Últim any"}.
{"Launch {{ app_name }} and sign in using your account credentials.","Inicia {{ app_name }} i entra al compte usant les teves credencials."}.
{"Launch Beagle IM, and select \\'Yes\\' to add a new account. Click the \\'+\\' button under the empty account list and then enter your credentials.","Inicia Beagle IM, i selecciona \\'Sí\\' per a afegir un nou compte. Polsa el botó \\'+\\' baix la llista de comptes buida i ja podràs introduir les teues credencials."}.
{"Least significant bits of SHA-256 hash of text should equal hexadecimal label","Els bits menys significants del hash SHA-256 del text deurien ser iguals a l'etiqueta hexadecimal"}.
{"leaves the room","surt de la sala"}.
{"List of Hats","Llista de barrets"}.
{"List users with hats","Llista d'usuaris amb barrets"}.
{"Log in via web","Entrar usant la web"}.
{"Logged Out","Desconectat"}.
{"Logging","Registre"}.
{"Make participants list public","Crear una llista de participants pública"}.
@@ -256,9 +311,11 @@
{"Max payload size in bytes","Màxim tamany del payload en bytes"}.
{"Maximum file size","Mida màxima de fitxer"}.
{"Maximum Number of History Messages Returned by Room","Numero màxim de missatges de l'historia que retorna la sala"}.
{"Maximum number of invites reached","Ja has arribat al número màxim d'invitacions"}.
{"Maximum number of items to persist","Número màxim d'elements que persistixen"}.
{"Maximum Number of Occupants","Número màxim d'ocupants"}.
{"May","Maig"}.
{"Members are allowed to invite others","Els membres poden invitar a altres"}.
{"Membership is required to enter this room","Necessites ser membre d'aquesta sala per a poder entrar"}.
{"Memorize your password, or write it in a paper placed in a safe place. In XMPP there isn't an automated way to recover your password if you forget it.","Memoritza la teva contrasenya, o escriu-la en un paper guardat a un lloc segur. A XMPP no hi ha una forma automatitzada de recuperar la teva contrasenya si la oblides."}.
{"Mere Availability in XMPP (No Show Value)","Simplement disponibilitat a XMPP (sense valor de 'show')"}.
@@ -273,6 +330,7 @@
{"Moderator","Moderador"}.
{"Moderators Only","Només moderadors"}.
{"Module failed to handle the query","El modul ha fallat al gestionar la petició"}.
{"Monal Logo","Logo de Monal"}.
{"Monday","Dilluns"}.
{"Multicast","Multicast"}.
{"Multiple <item/> elements are not allowed by RFC6121","No estan permesos múltiples elements <item/> per RFC6121"}.
@@ -327,6 +385,7 @@
{"None","Cap"}.
{"Not allowed","No permès"}.
{"Not Found","No Trobat"}.
{"NOT FOUND","NO TROBAT"}.
{"Not subscribed","No subscrit"}.
{"Notify subscribers when items are removed from the node","Notificar subscriptors quan els elements són eliminats del node"}.
{"Notify subscribers when the node configuration changes","Notificar subscriptors quan canvia la configuració del node"}.
@@ -363,12 +422,15 @@
{"Only service administrators are allowed to send service messages","Sols els administradors del servei tenen permís per a enviar missatges de servei"}.
{"Only those on a whitelist may associate leaf nodes with the collection","Només qui estiga a una llista blanca pot associar nodes fulla amb la col·lecció"}.
{"Only those on a whitelist may subscribe and retrieve items","Només qui estiga a una llista blanca pot subscriure's i recuperar elements"}.
{"Open the app","Obrir l'app"}.
{"Organization Name","Nom de la organizació"}.
{"Organization Unit","Unitat de la organizació"}.
{"Other Modules Available:","Altres mòduls disponibles:"}.
{"Other software","Altres programes"}.
{"Outgoing s2s Connections","Connexions s2s d'eixida"}.
{"Owner privileges required","Es requerixen privilegis de propietari de la sala"}.
{"Packet relay is denied by service policy","S'ha denegat el reenviament del paquet per política del servei"}.
{"Page navigation","Navegació de pàgina"}.
{"Participant ID","ID del Participant"}.
{"Participant","Participant"}.
{"Password Verification","Verificació de la Contrasenya"}.
@@ -384,6 +446,8 @@
{"Ping query is incorrect","La petició de Ping es incorrecta"}.
{"Ping","Ping"}.
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","Recorda que aquestes opcions només fan còpia de seguretat de la base de dades Mnesia. Si estàs utilitzant el mòdul d'ODBC també deus de fer una còpia de seguretat de la base de dades de SQL a part."}.
{"Please provide a password! Minimum length: {{ password_min_length }}","Per favor proporciona una contrasenya ! Llargària minima: {{ password_min_length }}"}.
{"Please provide a valid username!","Per favor proporciona un nom vàlid per al teu compte!"}.
{"Please, wait for a while before sending new voice request","Si us plau, espera una mica abans d'enviar una nova petició de veu"}.
{"Pong","Pong"}.
{"Possessing 'ask' attribute is not allowed by RFC6121","Posseir l'atribut 'ask' no està permès per RFC6121"}.
@@ -393,6 +457,7 @@
{"Previous session PID has exited","El procés de la sessió prèvia ha sortit"}.
{"Previous session PID is dead","El procés de la sessió prèvia està mort"}.
{"Previous session timed out","La sessió prèvia ha caducat"}.
{"Previous","Anterior"}.
{"private, ","privat, "}.
{"Public","Public"}.
{"Publish model","Model de publicació"}.
@@ -412,10 +477,16 @@
{"Recipient is not in the conference room","El receptor no està en la sala de conferència"}.
{"Re-Enable User","Rehabilitar usuari"}.
{"Register an XMPP account","Registrar un compte XMPP"}.
{"Register on {{ site_name }}","Registrar a {{ site_name }}"}.
{"Register","Registrar"}.
{"Registration error","Error al fer el registre"}.
{"Registration Error","Error al fer el Registre"}.
{"Registration Form","Formulari de Registre"}.
{"Registration Success","Registre completat correctament"}.
{"Remote copy","Còpia remota"}.
{"Remove a hat from a user","Eliminar un barret d'un usuari"}.
{"Remove User","Eliminar usuari"}.
{"Renga Logo","Logo de Renga"}.
{"Replaced by new connection","Reemplaçat per una nova connexió"}.
{"Request has timed out","La petició ha caducat"}.
{"Request is ignored","La petició ha sigut ignorada"}.
@@ -441,29 +512,39 @@
{"Roster size","Mida de la llista"}.
{"Running Nodes","Nodes funcionant"}.
{"~s invites you to the room ~s","~s et convida a la sala ~s"}.
{"Sad person holding empty box","Persona trista agafant una caixa buida"}.
{"Saturday","Dissabte"}.
{"Scan invite code","Escaneja el codi d'invitació"}.
{"Scan with mobile device","Escanejar amb el mòbil"}.
{"Search from the date","Buscar des de la data"}.
{"Search Results for ","Resultats de la búsqueda "}.
{"Search the text","Buscar el text"}.
{"Search until the date","Buscar fins la data"}.
{"Search users in ","Cerca usuaris en "}.
{"Send announcement to all online users on all hosts","Enviar anunci a tots els usuaris connectats a tots els hosts"}.
{"Select","Seleccionar"}.
{"Send announcement to all online users on all hosts","Enviar anunci a tots els usuaris connectats a tots els dominis"}.
{"Send announcement to all online users","Enviar anunci a tots els usuaris connectats"}.
{"Send announcement to all users on all hosts","Enviar anunci a tots els usuaris de tots els hosts"}.
{"Send announcement to all users on all hosts","Enviar anunci a tots els usuaris de tots els dominis"}.
{"Send announcement to all users","Enviar anunci a tots els usuaris"}.
{"Send this invite to your device","Envia esta invitació al teu dispositiu"}.
{"September","Setembre"}.
{"Server:","Servidor:"}.
{"Service list retrieval timed out","L'intent de recuperar la llista de serveis ha caducat"}.
{"Session state copying timed out","La copia del estat de la sessió ha caducat"}.
{"Set message of the day and send to online users","Configurar el missatge del dia i enviar a tots els usuaris"}.
{"Set message of the day on all hosts and send to online users","Escriure missatge del dia en tots els hosts i enviar-ho als usuaris connectats"}.
{"Set message of the day on all hosts and send to online users","Escriure missatge del dia en tots els dominis i enviar-ho als usuaris connectats"}.
{"Shared Roster Groups","Grups de contactes compartits"}.
{"Show Integral Table","Mostrar Taula Integral"}.
{"Show Occupants Join/Leave","Mostrar Entrades/Eixides dels Ocupants"}.
{"Show Ordinary Table","Mostrar Taula Ordinaria"}.
{"Showing apps for <span class='platform-name'>your current platform</span> only. You may also <a href='#' id='show-all-clients-button' tabindex=\"{{ tabidx }}\">view all apps.</a>","Mostrant apps només per a <span class='platform-name'>la teua plataforma actual</span>. Tambè pots <a href='#' id='show-all-clients-button' tabindex=\"{{ tabidx }}\">vore totes les apps.</a>"}.
{"Show","Mostrar"}.
{"Shut Down Service","Apager el Servei"}.
{"Siskin IM Logo","Logo de Siskin IM"}.
{"SOCKS5 Bytestreams","SOCKS5 Bytestreams"}.
{"Some XMPP clients can store your password in the computer, but you should do this only in your personal computer for safety reasons.","Alguns clients XMPP poden emmagatzemar la teva contrasenya al ordinador, però només hauries de fer això al teu ordinador personal, per raons de seguretat."}.
{"Sorry, it looks like this invite code has expired!","Perdona, però pareix que esta invitació ja ha expirat!"}.
{"Sorry, there was a problem registering your account.","Perdona, però hi ha hagut un error creant el compte."}.
{"Sources Specs:","Especificacions de Codi Font:"}.
{"Specify the access model","Especificar el model d'accés"}.
{"Specify the event message type","Especifica el tipus de missatge d'event"}.
@@ -471,11 +552,19 @@
{"Stanza id is not valid","L'identificador del paquet no es vàlid"}.
{"Stanza ID","ID del paquet"}.
{"Statically specify a replyto of the node owner(s)","Especifica estaticament una adreça on respondre al propietari del node"}.
{"Step 1: Download and install {{ app_name }}","Pas 1: Descarrega e instal·la {{ app_name }}"}.
{"Step 1: Install {{ app_name }}","Pas 1: Instal·la {{ app_name }}"}.
{"Step 2: Activate your account","Pas 2: Activa el teu compte"}.
{"Step 2: Connect {{ app_name }} to your new account","Pas 2: Connecta {{ app_name }} al teu nou compte"}.
{"Stopped Nodes","Nodes parats"}.
{"Store binary backup:","Guardar una còpia de seguretat binària:"}.
{"Store plain text backup:","Guardar una còpia de seguretat en format de text pla:"}.
{"Stream management is already enabled","L'administració de la connexió (stream management) ja està activada"}.
{"Stream management is not enabled","L'administració de la conexió (stream management) no està activada"}.
{"<strong>{{ site_name }}</strong> is part of XMPP, a secure and decentralized messaging network. To begin chatting using <strong>{{ app_name }}</strong> you need to first register an account.","<strong>{{ site_name }}</strong> és part de XMPP, una xarxa de missatgeria segura i descentralitzada. Per a començar a xarrar usant <strong>{{ app_name }}</strong> primer tens que registrar un compte."}.
{"<strong>{{ site_name }}</strong> is part of XMPP, a secure and decentralized messaging network. To begin chatting you need to first register an account.","<strong>{{ site_name }}</strong> és part de la xarxa XMPP de missatgeria segura i descentralitzada. Per a començar a xarrar, primer tindries que crear-te un compte."}.
{"<strong>No suitable software installed right now?</strong> You can also log in to your account through our online web chat!","<strong>No tens instal·lat cap programa compatible?</strong> També pots connectarte al teu compte amb el nostre xat web en linia!"}.
{"<strong>Tip:</strong> You can open this invite on your mobile device by scanning a barcode with your camera.","<strong>Truquet:</strong> Pots obrir esta invitació al teu mòbil escanejant el codi amb la camera de fotos."}.
{"Subject","Tema"}.
{"Submit","Enviar"}.
{"Submitted","Enviat"}.
@@ -495,7 +584,7 @@
{"The body text of the last received message","El contingut del text de l'ultim missatge rebut"}.
{"The CAPTCHA is valid.","El CAPTCHA es vàlid."}.
{"The CAPTCHA verification has failed","La verificació CAPTCHA ha fallat"}.
{"The captcha you entered is wrong","El CAPTCHA que has proporcionat és incorrecte"}.
{"The captcha you entered is wrong","El captcha que has proporcionat és incorrecte"}.
{"The child nodes (leaf or collection) associated with a collection","El nodes fills (fulla o col·leccions) associats amb una col·lecció"}.
{"The collections with which a node is affiliated","Les col.leccions amb les que un node està afiliat"}.
{"The DateTime at which a leased subscription will end or has ended","La Data i Hora a la que una subscripció prestada terminarà o ha terminat"}.
@@ -536,11 +625,14 @@
{"The sender of the last received message","Qui ha enviat l'ultim missatge rebut"}.
{"The stanza MUST contain only one <active/> element, one <default/> element, or one <list/> element","El paquet DEU contindre només un element <active/>, un element <default/>, o un element <list/>"}.
{"The subscription identifier associated with the subscription request","L'identificador de subscripció associat amb la petició de subscripció"}.
{"The token provided is either invalid or expired.","El token proporcionat no es vàlid o ja ha expirat."}.
{"The URL of an XSL transformation which can be applied to payloads in order to generate an appropriate message body element.","La URL de uns transformació XSL que pot ser aplicada als payloads per a generar un element apropiat de contingut de missatge."}.
{"The URL of an XSL transformation which can be applied to the payload format in order to generate a valid Data Forms result that the client could display using a generic Data Forms rendering engine","La URL de una transformació XSL que pot ser aplicada al format de payload per a generar un resultat valid de Data Forms, que el client puga mostrar usant un métode generic de Data Forms"}.
{"There was an error changing the password: ","Hi ha hagut un error canviant la contrasenya: "}.
{"There was an error creating the account: ","Hi ha hagut un error creant el compte: "}.
{"There was an error deleting the account: ","Hi ha hagut un error esborrant el compte: "}.
{"This button works only if you have the app installed already!","Aquest botó funciona només si ja tens la app instal·lada!"}.
{"This is an invite from <strong>{{ inviter }}</strong> to connect and chat on the XMPP network. If you already have an XMPP client installed just press the button below!","Esta es una invitació de <strong>{{ inviter }}</strong> per a connectar i xarrar en la xarxa XMPP. Si ja tens un client XMPP instal·lat, ja pots polsar el boto de baix!"}.
{"This is case insensitive: macbeth is the same that MacBeth and Macbeth.","Això no distingeix majúscules de minúscules: macbeth es el mateix que MacBeth i Macbeth."}.
{"This page allows to register an XMPP account in this XMPP server. Your JID (Jabber ID) will be of the form: username@server. Please read carefully the instructions to fill correctly the fields.","Aquesta pàgina permet crear un compte XMPP en aquest servidor XMPP. El teu JID (Jabber ID; Identificador Jabber) tindrà aquesta forma: usuari@servidor. Si us plau, llegeix amb cura les instruccions per emplenar correctament els camps."}.
{"This page allows to unregister an XMPP account in this XMPP server.","Aquesta pàgina permet esborrar un compte XMPP en aquest servidor XMPP."}.
@@ -549,7 +641,9 @@
{"Thursday","Dijous"}.
{"Time delay","Temps de retard"}.
{"Timed out waiting for stream resumption","Massa temps esperant que es resumisca la connexió"}.
{"To get started, you need to install an app for your platform:","Per a començar, tens que instal·lar alguna app:"}.
{"To register, visit ~s","Per a registrar-te, visita ~s"}.
{"To start chatting, you need to enter your new account credentials into your chosen XMPP software.","Per a començar a xarrar, tens que proporcionar les credencials del teu no compte al programa XMPP escollit."}.
{"To ~ts","A ~ts"}.
{"Token TTL","Token TTL"}.
{"Too many active bytestreams","N'hi ha massa Bytestreams actius"}.
@@ -576,12 +670,13 @@
{"Unsupported <index/> element","Element <index/> no soportat"}.
{"Unsupported version","Versió no suportada"}.
{"Update message of the day (don't send)","Actualitzar el missatge del dia (no enviar)"}.
{"Update message of the day on all hosts (don't send)","Actualitza el missatge del dia en tots els hosts (no enviar)"}.
{"Update message of the day on all hosts (don't send)","Actualitza el missatge del dia en tots els dominis (no enviar)"}.
{"Update specs to get modules source, then install desired ones.","Actualitza les especificacions per obtindre el codi font dels mòduls, després instal·la els que vulgues."}.
{"Update Specs","Actualitzar Especificacions"}.
{"Updating the vCard is not supported by the vCard storage backend","El sistema d'almacenament de vCard no te capacitat per a actualitzar la vCard"}.
{"Upgrade","Actualitza"}.
{"URL for Archived Discussion Logs","URL dels Arxius de Discussions"}.
{"Use a <em>QR code</em> scanner on your mobile device to scan the code below:","Usa un escanejador de <em>codis QR</em> al te mòbil per a escanejar aquest codi:"}.
{"User already exists","El usuari ja existeix"}.
{"User JID","JID del usuari"}.
{"User (jid)","Usuari (jid)"}.
@@ -591,6 +686,9 @@
{"User session not found","Sessió d'usuari no trobada"}.
{"User session terminated","Sessió d'usuari terminada"}.
{"User ~ts","Usuari ~ts"}.
{"Username invalid","El nom d'usuari no es vàlid"}.
{"Username is reserved","El nom d'usuari està reservat"}.
{"Username","Nom d'usuari"}.
{"Username:","Nom d'usuari:"}.
{"Users are not allowed to register accounts so quickly","Els usuaris no tenen permís per a crear comptes tan depresa"}.
{"Users Last Activity","Última activitat d'usuari"}.
@@ -603,7 +701,7 @@
{"Value 'set' of 'type' attribute is not allowed","El valor 'set' a l'atribut 'type' no és permès"}.
{"vCard User Search","vCard recerca d'usuari"}.
{"View joined MIX channels","Vore els canals MIX units"}.
{"Virtual Hosts","Hosts virtuals"}.
{"Virtual Hosts","Dominis Virtuals"}.
{"Visitors are not allowed to change their nicknames in this room","Els visitants no tenen permés canviar el seus Nicknames en esta sala"}.
{"Visitors are not allowed to send messages to all occupants","Els visitants no poden enviar missatges a tots els ocupants"}.
{"Visitor","Visitant"}.
@@ -627,6 +725,7 @@
{"Wrong parameters in the web formulary","Paràmetres incorrectes en el formulari web"}.
{"Wrong xmlns","El xmlns ès incorrecte"}.
{"XMPP Account Registration","Registre de compte XMPP"}.
{"XMPP client for Haiku","Client XMPP per a Haiku"}.
{"XMPP Domains","Dominis XMPP"}.
{"XMPP Show Value of Away","Valor 'show' de XMPP: Ausent"}.
{"XMPP Show Value of Chat","Valor 'show' de XMPP: Disposat per a xarrar"}.
@@ -636,8 +735,17 @@
{"You are being removed from the room because of a system shutdown","Has sigut expulsat de la sala perquè el sistema va a apagar-se"}.
{"You are not allowed to send private messages","No tens permés enviar missatges privats"}.
{"You are not joined to the channel","No t'has unit al canal"}.
{"You can connect to {{ site_name }} using any XMPP-compatible software. If your preferred software is not listed above, you may still <a href=\"{{ registration_url }}\" tabindex='{{ tabidx }}'>register an account manually</a>.","Pots connectar-te a {{ site_name }} usant qualsevol programa compatible amb XMPP. Si el teu programa preferit no està en esta llista, pots <a href=\"{{ registration_url }}\" tabindex='{{ tabidx }}'>registrarte el teu compte manualment</a>."}.
{"You can later change your password using an XMPP client.","Podràs canviar la teva contrasenya més endavant utilitzant un client XMPP."}.
{"You can now set up {{ app_name }} and connect it to your new account.","Ara pots configurar {{ app_name }} i connectar al teu nou compte."}.
{"You can start chatting right away with {{ app_name }}. Let's get started!","Ja pots començar a xarrar usant {{ app_name }}. Comencem!"}.
{"You can transfer this invite to your mobile device by scanning a code with your camera.","Pots transferir esta invitació al teu mòbil si escanejes el codi amb la càmera de fotos."}.
{"You have been banned from this room","Has sigut bloquejat en aquesta sala"}.
{"You have been invited to chat on {{ site_name }}, part of the XMPP secure and decentralized messaging network.","Has rebut una invitació per a xarrar a {{ site_name }}, que és part de la xarxa XMPP de missatgeria segura i descentralitzada."}.
{"You have been invited to chat on <strong>{{ site_name }}</strong>, part of the XMPP secure and decentralized messaging network.","Has rebut una invitació per a xarrar a <strong>{{ site_name }}</strong>, que és part de la xarxa XMPP de missatgeria segura i descentralitzada."}.
{"You have been invited to chat with <strong>{{ inviter }}</strong> on {{ site_name }}, part of the XMPP secure and decentralized messaging network.","Has rebut una invitació a xarrar amb <strong>{{ inviter }}</strong> a {{ site_name }}, que és part de la xarxa XMPP de missatgeria segura i descentralitzada."}.
{"You have been invited to chat with <strong>{{ inviter }}</strong> on <strong>{{ site_name }}</strong>, part of the XMPP secure and decentralized messaging network.","T'han invitat a xarrar amb <strong>{{ inviter }}</strong> a <strong>{{ site_name }}</strong>, que és part de la xarxa XMPP de missatgeria segura i descentralitzada."}.
{"You have created an account on <strong>{{ site_name }}</strong>.","Has creat un compte en <strong>{{ site_name }}</strong>."}.
{"You have joined too many conferences","Has entrat en massa sales de conferència"}.
{"You must fill in field \"Nickname\" in the form","Deus d'omplir el camp \"Nickname\" al formulari"}.
{"You need a client that supports x:data and CAPTCHA to register","Necessites un client amb suport x:data i de CAPTCHA para poder registrar-te"}.
@@ -645,6 +753,7 @@
{"You need an x:data capable client to search","Necessites un client amb suport x:data per a poder buscar"}.
{"Your active privacy list has denied the routing of this stanza.","La teva llista de privacitat activa ha denegat l'encaminament d'aquesta stanza."}.
{"Your contact offline message queue is full. The message has been discarded.","La teua cua de missatges offline és plena. El missatge ha sigut descartat."}.
{"Your password is stored encrypted on the server and will not be accessible after you close this page. Keep it safe and never share it with anyone.","La teva contrasenya es guardarà xifrada al servidor, i no serà accessible una vegada que tanques esta pàgina. Mantin la contrasenya segura i no la compartis amb ningú mes."}.
{"Your subscription request and/or messages to ~s have been blocked. To unblock your subscription request, visit ~s","La teua petició de subscripció i/o missatges a ~s han sigut bloquejats. Per a desbloquejar-los, visita ~s"}.
{"Your XMPP account was successfully registered.","El teu compte XMPP ha sigut creat correctament."}.
{"Your XMPP account was successfully unregistered.","El teu compte XMPP ha sigut esborrat correctament."}.
+82 -9
View File
@@ -4,15 +4,24 @@
%% https://docs.ejabberd.im/developer/extending-ejabberd/localization/
{" has set the subject to: "," změnil(a) téma na: "}.
{"A description of the node","Popis uzlu"}.
{"A friendly name for the node","Přívětivé jméno pro uzel"}.
{"A fully-featured desktop chat client for Windows and Linux.","Plně funkční desktopový chatovací klient pro Windows a Linux."}.
{"A lightweight and powerful XMPP client for iPhone and iPad. It provides an easy way to talk and share moments with your friends.","Lehký a výkonný XMPP klient pro iPhone a iPad. Nabízí snadný způsob, jak komunikovat a sdílet okamžiky s přáteli."}.
{"A modern open-source chat client for iPhone and iPad. It is easy to use and has a clean user interface.","Moderní chatovací klient s otevřeným zdrojovým kódem pro iPhone a iPad. Snadno se používá a má čisté uživatelské rozhraní."}.
{"A modern open-source chat client for Mac. It is easy to use and has a clean user interface.","Moderní chatovací klient s otevřeným zdrojovým kódem pro Mac. Snadno se používá a má čisté uživatelské rozhraní."}.
{"A password is required to enter this room","Pro vstup do místnosti musíte zadat heslo"}.
{"Accept invite using {{ app_name }}","Přijmout pozvánku pomocí {{ app_name }}"}.
{"Accept","Přijmout"}.
{"Access denied by service policy","Přístup byl zamítnut nastavením služby"}.
{"Action on user","Akce aplikovaná na uživatele"}.
{"Add Contact","Přidat kontakt"}.
{"Add User","Přidat uživatele"}.
{"Administration of ","Administrace "}.
{"Administration","Administrace"}.
{"Administrator privileges required","Potřebujete práva administrátora"}.
{"After clicking the button you will be taken to {{ app_name }} to finish setting up your new {{ site_name }} account.","Po kliknutí na tlačítko budete přesměrováni do {{ app_name }}, kde dokončíte nastavení svého nového účtu na {{ site_name }}."}.
{"After successfully installing {{ app_name }}, come back to this page and <strong>continue with Step 2</strong>.","Po úspěšné instalaci {{ app_name }} se vraťte na tuto stránku a <strong>pokračujte krokem 2</strong>."}.
{"All activity","Všechny aktivity"}.
{"All Users","Všichni uživatelé"}.
{"Allow this Jabber ID to subscribe to this pubsub node?","Povolit tomuto Jabber ID odebírat tento pubsub uzel?"}.
@@ -25,7 +34,9 @@
{"Allow visitors to send status text in presence updates","Povolit návštěvníkům posílat stavové zprávy ve statusu"}.
{"Allow visitors to send voice requests","Povolit uživatelům posílat žádosti o voice práva"}.
{"Announcements","Oznámení"}.
{"API Commands","API příkazy"}.
{"April",". dubna"}.
{"As a final reminder, your account details are shown below:","Pro připomenutí, níže jsou uvedeny údaje o vašem účtu:"}.
{"August",". srpna"}.
{"Automatic node creation is not enabled","Automatické vytváření uzlů není povoleno"}.
{"Backup Management","Správa zálohování"}.
@@ -33,6 +44,7 @@
{"Backup to File at ","Záloha do souboru na "}.
{"Backup","Zálohovat"}.
{"Bad format","Nesprávný formát"}.
{"Beagle IM by Tigase, Inc. is a lightweight and powerful XMPP client for macOS.","Beagle IM od Tigase, Inc. je lehký a výkonný klient pro macOS."}.
{"Birthday","Datum narození"}.
{"Both the username and the resource are required","Uživatelské jméno i zdroj jsou požadované položky"}.
{"Bytestream already activated","Bytestream již byl aktivován"}.
@@ -51,14 +63,18 @@
{"Chatroom is stopped","Místnost zastavena"}.
{"Chatrooms","Místnosti"}.
{"Choose a username and password to register with this server","Zadejte jméno uživatele a heslo pro registraci na tomto serveru"}.
{"Choose a username, this will become the first part of your new chat address.","Zvolte si uživatelské jméno, to se stane první částí Vaší chatovací adresy."}.
{"Choose storage type of tables","Vyberte typ úložiště pro tabulky"}.
{"Choose whether to approve this entity's subscription.","Zvolte, zda chcete schválit odebírání touto entitou."}.
{"City","Město"}.
{"Click the button to open the Dino website where you can download and install it on your PC.","Kliknutím na tlačítko se otevře webová stránka, odkud si můžete stáhnout aplikaci Dino a nainstalovat ji na svůj počítač."}.
{"Close","Zavřít"}.
{"Commands","Příkazy"}.
{"Conference room does not exist","Místnost neexistuje"}.
{"Configuration of room ~s","Konfigurace místnosti ~s"}.
{"Configuration","Konfigurace"}.
{"Country","Země"}.
{"Creating an account will allow to communicate with other people on <strong>{{ site_name }}</strong> and other services on the XMPP network.","Vytvořením účtu budete moci komunikovat s ostatními lidmi na serveru <strong>{{ site_name }}</strong> i na ostatních serverech v rámci sítě XMPP."}.
{"Database failure","Chyba databáze"}.
{"Database Tables Configuration at ","Konfigurace databázových tabulek "}.
{"Database","Databáze"}.
@@ -70,6 +86,10 @@
{"Deliver event notifications","Doručovat upozornění na události"}.
{"Deliver payloads with event notifications","Doručovat náklad s upozorněním na událost"}.
{"Disc only copy","Jen kopie disku"}.
{"Download and install {{ app_name }} below:","Stáhněte a nainstalujte {{ app_name }}:"}.
{"Download Dino for Linux","Stáhnout Dino pro Linux"}.
{"Download Gajim","Stahnout Gajim"}.
{"Download Renga for Haiku","Stáhnout Renga pro Haiku"}.
{"Dump Backup to Text File at ","Uložit zálohu do textového souboru na "}.
{"Dump to Text File","Uložit do textového souboru"}.
{"Edit Properties","Upravit vlastnosti"}.
@@ -85,6 +105,7 @@
{"Enable message archiving","Povolit ukládání historie zpráv"}.
{"Enabling push without 'node' attribute is not supported","Aktivováno push bez atributu 'node' není podporováno"}.
{"End User Session","Ukončit sezení uživatele"}.
{"Enter a secure password that you do not use anywhere else.","Zadejte bezpečné heslo, které nikde jinde nepoužíváte."}.
{"Enter nickname you want to register","Zadejte přezdívku, kterou chcete zaregistrovat"}.
{"Enter path to backup file","Zadajte cestu k souboru se zálohou"}.
{"Enter path to jabberd14 spool dir","Zadejte cestu k jabberd14 spool adresáři"}.
@@ -117,9 +138,11 @@
{"has been banned","byl(a) zablokován(a)"}.
{"has been kicked because of a system shutdown","byl(a) vyhozen(a), protože dojde k vypnutí systému"}.
{"has been kicked because of an affiliation change","byl(a) vyhozen(a) kvůli změně přiřazení"}.
{"has been kicked because the room has been changed to members-only","byl(a) vyhozen(a), protože mísnost je nyní pouze pro členy"}.
{"has been kicked because the room has been changed to members-only","byl(a) vyhozen(a), protože místnost je nyní pouze pro členy"}.
{"has been kicked","byl(a) vyhozen(a) z místnosti"}.
{"Hide","Skrýt"}.
{"Host unknown","Neznámý hostitel"}.
{"If you don't have an XMPP client installed yet, here's a list of suitable clients for your platform.","Pokud ještě nemáte nainstalovaného klienta XMPP, zde je seznam vhodných klientů pro vaši platformu."}.
{"If you don't see the CAPTCHA image here, visit the web page.","Pokud zde nevidíte obrázek CAPTCHA, přejděte na webovou stránku."}.
{"Import Directory","Import adresáře"}.
{"Import File","Import souboru"}.
@@ -141,6 +164,7 @@
{"Insufficient privilege","Nedostatečné oprávnění"}.
{"Invalid 'from' attribute in forwarded message","Nesprávný atribut 'from' v přeposlané zprávě"}.
{"Invitations are not allowed in this conference","Pozvánky nejsou povoleny v této místnosti"}.
{"Invite expired","Platnost pozvánky vypršela"}.
{"IP addresses","IP adresy"}.
{"is now known as","se přejmenoval(a) na"}.
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Není povoleno posílat chybové zprávy do místnosti. Účastník (~s) odeslal chybovou zprávu (~s) a byl vyhozen z místnosti"}.
@@ -152,21 +176,24 @@
{"joins the room","vstoupil(a) do místnosti"}.
{"July",". července"}.
{"June",". června"}.
{"Just created","Právě vytvořen"}.
{"Last Activity","Poslední aktivita"}.
{"Last login","Poslední přihlášení"}.
{"Last message","Poslední zpráva"}.
{"Last month","Poslední měsíc"}.
{"Last year","Poslední rok"}.
{"Launch {{ app_name }} and sign in using your account credentials.","Spusťte {{ app_name }} a přihlaste se pomocí Vašich přihlašovacích údajů."}.
{"leaves the room","opustil(a) místnost"}.
{"Logged Out","Odhlášen"}.
{"Make participants list public","Nastavit seznam účastníků jako veřejný"}.
{"Make room CAPTCHA protected","Chránit místnost pomocí CAPTCHA"}.
{"Make room members-only","Zpřístupnit místnost jen členům"}.
{"Make room moderated","Nastavit místnost jako moderovanou"}.
{"Make room password protected","Chránit místnost heslem"}.
{"Make room persistent","Nastavit místnost jako stálou"}.
{"Make room public searchable","Nastavit místnost jako veřejnou"}.
{"Malformed username","Chybně formátováné jméno uživatele"}.
{"March",". března"}.
{"Max payload size in bytes","Maximální náklad v bajtech"}.
{"Maximum file size","Maximální velikost souboru"}.
{"Maximum Number of Occupants","Maximální počet účastníků"}.
{"May",". května"}.
{"Membership is required to enter this room","Pro vstup do místnosti musíte být členem"}.
@@ -193,7 +220,7 @@
{"No body provided for announce message","Zpráva neobsahuje text"}.
{"No data form found","Nebyl nalezen datový formulář"}.
{"No Data","Žádná data"}.
{"No features available","Žádné funce nejsou dostupné"}.
{"No features available","Žádné funkce nejsou dostupné"}.
{"No hook has processed this command","Žádný hook nebyl zpracován tímto příkazem"}.
{"No info about last activity found","Nebyla žádná informace o poslední aktivitě"}.
{"No 'item' element found","Element 'item' nebyl nalezen"}.
@@ -219,6 +246,7 @@
{"Nodeprep has failed","Nodeprep chyboval"}.
{"Nodes","Uzly"}.
{"None","Nic"}.
{"Not allowed","Nepovoleno"}.
{"Not Found","Nenalezeno"}.
{"Not subscribed","Není odebíráno"}.
{"Notify subscribers when items are removed from the node","Upozornit odběratele na odstranění položek z uzlu"}.
@@ -236,14 +264,13 @@
{"Only deliver notifications to available users","Doručovat upozornění jen právě přihlášeným uživatelům"}.
{"Only <enable/> or <disable/> tags are allowed","Pouze značky <enable/> nebo <disable/>jsou povoleny"}.
{"Only <list/> element is allowed in this query","Pouze element <list/> je povolen v tomto dotazu"}.
{"Only members may query archives of this room","Pouze moderátoři mají povoleno měnit téma místnosti"}.
{"Only moderators and participants are allowed to change the subject in this room","Jen moderátoři a účastníci mají povoleno měnit téma této místnosti"}.
{"Only moderators are allowed to change the subject in this room","Jen moderátoři mají povoleno měnit téma místnosti"}.
{"Only moderators can approve voice requests","Pouze moderátoři mohou schválit žádosti o voice práva"}.
{"Only occupants are allowed to send messages to the conference","Jen členové mají povolené zasílat zprávy do místnosti"}.
{"Only occupants are allowed to send queries to the conference","Jen členové mohou odesílat požadavky (query) do místnosti"}.
{"Only occupants are allowed to send messages to the conference","Pouze členové mají povolené zasílat zprávy do místnosti"}.
{"Only occupants are allowed to send queries to the conference","Pouze členové mohou odesílat požadavky (query) do místnosti"}.
{"Only service administrators are allowed to send service messages","Pouze správci služby smí odesílat servisní zprávy"}.
{"Organization Name","Název firmy"}.
{"Organization Name","Název organizace"}.
{"Organization Unit","Oddělení"}.
{"Other Modules Available:","Ostatní dostupné moduly:"}.
{"Outgoing s2s Connections","Odchozí s2s spojení"}.
@@ -260,10 +287,14 @@
{"Ping query is incorrect","Ping dotaz je nesprávný"}.
{"Ping","Ping"}.
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","Podotýkáme, že tato nastavení budou zálohována do zabudované databáze Mnesia. Pokud používáte ODBC modul, musíte zálohovat svoji SQL databázi samostatně."}.
{"Please provide a password! Minimum length: {{ password_min_length }}","Prosím vyplňte heslo! Minimální délka hesla: {{ password_min_length }}"}.
{"Please provide a valid username!","Prosím, zadejte platné uživatelské jméno!"}.
{"Please, wait for a while before sending new voice request","Prosím, počkejte chvíli před posláním nové žádosti o voice práva"}.
{"Pong","Pong"}.
{"Present real Jabber IDs to","Odhalovat skutečná Jabber ID"}.
{"Previous","Předchozí"}.
{"private, ","soukromá, "}.
{"Public","Veřejný"}.
{"Publish-Subscribe","Publish-Subscribe"}.
{"PubSub subscriber request","Žádost odběratele PubSub"}.
{"Purge all items when the relevant publisher goes offline","Smazat všechny položky, pokud se příslušný poskytovatel odpojí"}.
@@ -273,10 +304,13 @@
{"RAM copy","Kopie RAM"}.
{"Really delete message of the day?","Skutečně smazat zprávu dne?"}.
{"Recipient is not in the conference room","Příjemce se nenachází v místnosti"}.
{"Re-Enable User","Znovu povolit uživatele"}.
{"Register on {{ site_name }}","Registrovat se na {{ site_name }}"}.
{"Register","Zaregistrovat se"}.
{"Remote copy","Vzdálená kopie"}.
{"Remove User","Odstranit uživatele"}.
{"Replaced by new connection","Nahrazeno novým spojením"}.
{"Request has timed out","Časový limit požadavku vypršel"}.
{"Resources","Zdroje"}.
{"Restart Service","Restartovat službu"}.
{"Restore Backup from File at ","Obnovit zálohu ze souboru na "}.
@@ -284,6 +318,7 @@
{"Restore binary backup immediately:","Okamžitě obnovit binární zálohu:"}.
{"Restore plain text backup immediately:","Okamžitě obnovit zálohu z textového souboru:"}.
{"Restore","Obnovit"}.
{"Result","Výsledek"}.
{"Roles for which Presence is Broadcasted","Role, pro které je zpráva o stavu šířena"}.
{"Room Configuration","Nastavení místnosti"}.
{"Room creation is denied by service policy","Pravidla služby nepovolují vytvořit místnost"}.
@@ -293,27 +328,43 @@
{"Roster groups allowed to subscribe","Skupiny kontaktů, které mohou odebírat"}.
{"Roster size","Velikost seznamu kontaktů"}.
{"Running Nodes","Běžící uzly"}.
{"Sad person holding empty box","Smutný člověk držící prázdnou krabici"}.
{"Saturday","Sobota"}.
{"Scan invite code","Naskenovat kód pozvánky"}.
{"Scan with mobile device","Skenovat pomocí mobilního zařízení"}.
{"Search from the date","Hledat od data"}.
{"Search Results for ","Výsledky hledání pro "}.
{"Search the text","Hledat text"}.
{"Search until the date","Hledat do data"}.
{"Search users in ","Hledat uživatele v "}.
{"Select","Vybrat"}.
{"Send announcement to all online users on all hosts","Odeslat oznámení všem online uživatelům na všech hostitelích"}.
{"Send announcement to all online users","Odeslat oznámení všem online uživatelům"}.
{"Send announcement to all users on all hosts","Odeslat oznámení všem uživatelům na všech hostitelích"}.
{"Send announcement to all users","Odeslat oznámení všem uživatelům"}.
{"Send this invite to your device","Zaslat tuto pozvánku na Vaše zařízení"}.
{"September",". září"}.
{"Server:","Server:"}.
{"Service list retrieval timed out","Časový limit pro načtení seznamu služeb vypršel"}.
{"Set message of the day and send to online users","Nastavit zprávu dne a odeslat ji online uživatelům"}.
{"Set message of the day on all hosts and send to online users","Nastavit zprávu dne na všech hostitelích a odeslat ji online uživatelům"}.
{"Shared Roster Groups","Skupiny pro sdílený seznam kontaktů"}.
{"Show Integral Table","Zobrazit kompletní tabulku"}.
{"Show Ordinary Table","Zobrazit běžnou tabulku"}.
{"Shut Down Service","Vypnout službu"}.
{"Sorry, it looks like this invite code has expired!","Je nám líto, ale zdá se, že platnost pozvánky vypršela!"}.
{"Specify the access model","Uveďte přístupový model"}.
{"Specify the event message type","Zvolte typ zpráv pro události"}.
{"Specify the publisher model","Specifikovat model pro publikování"}.
{"Step 1: Download and install {{ app_name }}","Krok 1: Stáhněte si a nainstalujte {{ app_name }}"}.
{"Step 1: Install {{ app_name }}","Krok 1: Instalace {{ app_name }}"}.
{"Step 2: Activate your account","Krok 2: Aktivujte svůj účet"}.
{"Step 2: Connect {{ app_name }} to your new account","Step 2: Připojte se pomocí {{ app_name }} k Vašemu novému účtu"}.
{"Stopped Nodes","Zastavené uzly"}.
{"Store binary backup:","Uložit binární zálohu:"}.
{"Store plain text backup:","Uložit zálohu do textového souboru:"}.
{"<strong>{{ site_name }}</strong> is part of XMPP, a secure and decentralized messaging network. To begin chatting using <strong>{{ app_name }}</strong> you need to first register an account.","<strong>{{ site_name }}</strong> je součástí XMPP, bezpečné a decentralizované sítě pro zasílání zpráv. Abyste mohli začít chatovat pomocí aplikace <strong>{{ app_name }}</strong>, musíte se nejprve registrovat."}.
{"<strong>Tip:</strong> You can open this invite on your mobile device by scanning a barcode with your camera.","<strong>Tip:</strong> Tuto pozvánku můžete otevřít na svém mobilním zařízení naskenováním kódu fotoaparátem."}.
{"Subject","Předmět"}.
{"Submit","Odeslat"}.
{"Submitted","Odeslané"}.
@@ -322,21 +373,32 @@
{"Sunday","Neděle"}.
{"That nickname is already in use by another occupant","Přezdívka je již používána jiným členem"}.
{"That nickname is registered by another person","Přezdívka je zaregistrována jinou osobou"}.
{"The account already exists","Účet již existuje"}.
{"The account was not unregistered","Účet nebyl smazán"}.
{"The CAPTCHA is valid.","CAPTCHA souhlasí."}.
{"The CAPTCHA verification has failed","Ověření CAPTCHA se nezdařilo"}.
{"The collections with which a node is affiliated","Kolekce, se kterými je uzel spřízněn"}.
{"The datetime when the node was created","Datum a čas vytvoření uzlu"}.
{"The default language of the node","Výchozí jazyk uzlu"}.
{"The feature requested is not supported by the conference","Požadovaná vlastnost není podporována touto místností"}.
{"The list of all online users","Seznam všech online uživatelů"}.
{"The list of all users","Seznam všech uživatelů"}.
{"The NodeID of the relevant node","NodeID příslušného uzlu"}.
{"The number of subscribers to the node","Počet odběratelů uzlu"}.
{"The number of unread or undelivered messages","Počet nepřečtených nebo nedoručených zpráv"}.
{"The password contains unacceptable characters","Heslo obsahuje nepovolené znaky"}.
{"The password is too weak","Heslo je příliš slabé"}.
{"the password is","heslo je"}.
{"The passwords are different","Hesla nejsou stejná"}.
{"The query is only allowed from local users","Dotaz je povolen pouze pro místní uživatele"}.
{"The query must not contain <item/> elements","Dotaz nesmí obsahovat elementy <item/>"}.
{"The role","Role"}.
{"The sender of the last received message","Odesílatel poslední doručené zprávy"}.
{"The stanza MUST contain only one <active/> element, one <default/> element, or one <list/> element","Stanza MUSÍ obsahovat pouze jeden element <active/>, jeden element <default/> nebo jeden element <list/>"}.
{"There was an error changing the password: ","Při změně hesla došlo k chybě: "}.
{"There was an error creating the account: ","Při vytváření účtu došlo k chybě: "}.
{"There was an error deleting the account: ","Při mazání účtu došlo k chybě: "}.
{"This button works only if you have the app installed already!","Toto tlačítko funguje pouze v případě, že máte nainstalovanou vhodnou aplikaci!"}.
{"This room is not anonymous","Tato místnost není anonymní"}.
{"Thursday","Čtvrtek"}.
{"Time delay","Časový posun"}.
@@ -359,10 +421,12 @@
{"Unsupported <index/> element","Nepodporovaný <index/> element"}.
{"Update message of the day (don't send)","Aktualizovat zprávu dne (neodesílat)"}.
{"Update message of the day on all hosts (don't send)","Aktualizovat zprávu dne pro všechny hostitele (neodesílat)"}.
{"Use a <em>QR code</em> scanner on your mobile device to scan the code below:","Naskenujte kód níže pomocí skeneru <em>QR kódů</em> na Vašem mobilním zařízení:"}.
{"User already exists","Uživatel již existuje"}.
{"User JID","Jabber ID uživatele"}.
{"User (jid)","Uživatel (JID)"}.
{"User Management","Správa uživatelů"}.
{"User removed","Uživatel odstraněn"}.
{"User session not found","Sezení uživatele nebylo nalezeno"}.
{"User session terminated","Sezení uživatele bylo ukončeno"}.
{"Username:","Uživatelské jméno:"}.
@@ -385,13 +449,22 @@
{"Wednesday","Středa"}.
{"When to send the last published item","Kdy odeslat poslední publikovanou položku"}.
{"Whether to allow subscriptions","Povolit odebírání"}.
{"Wrong xmlns","Chybné xmlns"}.
{"XMPP client for Haiku","XMPP klient pro Haiku"}.
{"XMPP Domains","XMPP domény"}.
{"You are being removed from the room because of a system shutdown","Budete vyloučeni z místnosti kvůli vypnutí systému"}.
{"You are not allowed to send private messages","Nemáte povoleno posílat soukromé zprávy"}.
{"You can transfer this invite to your mobile device by scanning a code with your camera.","Tuto pozvánku můžete přenést do svého mobilního zařízení naskenováním kódu pomocí fotoaparátu."}.
{"You have been banned from this room","Byl jste vyloučen z této místnosti"}.
{"You have joined too many conferences","Vstoupil jste do příliš velkého množství místností"}.
{"You have been invited to chat with <strong>{{ inviter }}</strong> on <strong>{{ site_name }}</strong>, part of the XMPP secure and decentralized messaging network.","Byli jste pozváni do chatu s <strong>{{ inviter }}</strong> na serveru <strong>{{ site_name }}</strong>, který je součástí XMPP - bezpečné a decentralizované sítě pro zasílání zpráv."}.
{"You have joined too many conferences","Vstoupili jste do příliš velkého množství místností"}.
{"You must fill in field \"Nickname\" in the form","Musíte vyplnit políčko \"Přezdívka\" ve formuláři"}.
{"You need a client that supports x:data and CAPTCHA to register","Pro registraci potřebujete klienta s podporou x:data a CAPTCHA"}.
{"You need a client that supports x:data to register the nickname","Pro registraci přezdívky potřebujete klienta s podporou x:data"}.
{"You need an x:data capable client to search","K vyhledávání potřebujete klienta podporujícího x:data"}.
{"Your active privacy list has denied the routing of this stanza.","Vaše nastavení soukromí znemožnilo směrování této stance."}.
{"Your contact offline message queue is full. The message has been discarded.","Fronta offline zpráv pro váš kontakt je plná. Zpráva byla zahozena."}.
{"Your password is stored encrypted on the server and will not be accessible after you close this page. Keep it safe and never share it with anyone.","Vaše heslo je zašifrováno a uloženo na serveru a až zavřete tuto stránku, nebude již dostupné. Uložte si ho do bezpečí a nikdy ho s nikým nesdílejte."}.
{"Your subscription request and/or messages to ~s have been blocked. To unblock your subscription request, visit ~s","Nesmíte posílat zprávy na ~s. Pro povolení navštivte ~s"}.
{"Your XMPP account was successfully registered.","Váš účet XMPP byl úspěšně zaregistrován."}.
{"You're not allowed to create nodes","Nemáte povoleno vytvářet uzly"}.
+35 -13
View File
@@ -11,19 +11,21 @@
{"A description of the node","Eine Beschreibung des Knotens"}.
{"A friendly name for the node","Ein benutzerfreundlicher Name für den Knoten"}.
{"A fully-featured desktop chat client for Windows and Linux.","Ein funktionsreicher Desktop-Client für Windows und Linux."}.
{"A lean Jabber/XMPP client for Android. It aims at usability, low overhead and security, and works on low-end Android devices starting with Android 4.0.","Ein schlanker Jabber/XMPP-Client für Android mit Fokus Benutzerfreundlichkeit und Sicherheit. Er funktioniert auch mit langsameren Android-Geräten mit Android 4.0 oder neuer."}.
{"A lightweight and powerful XMPP client for iPhone and iPad. It provides an easy way to talk and share moments with your friends.","Ein schlanker aber funktionsreicher XMPP-Client für iPhone und iPad. Ein einfacher Weg um mit Deinen Freunden zu kommunizieren und Erinnerungen zu teilen."}.
{"A modern open-source chat client for iPhone and iPad. It is easy to use and has a clean user interface.","Ein moderner open-source Chatclient für iPhone und iPad mit einfacher und übersichtlicher Benutzeroberfläche."}.
{"A modern open-source chat client for Mac. It is easy to use and has a clean user interface.","Ein moderner open-source Chatclient für Mac mit einfacher und übersichtlicher Benutzeroberfläche."}.
{"A modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind.","Ein moderner open-source Jabber/XMPP Chatclient für den Desktop mit Fokus auf Einfachheit und Zuverlässigkeit, aber auch Schutz deiner Privatsphäre."}.
{"A password is required to enter this room","Ein Passwort ist erforderlich um diesen Raum zu betreten"}.
{"A Web Page","Eine Webseite"}.
{"Accept invite using {{ app_name }}","Einladung mittels {{ app_name }} annehmen!"}.
{"Accept invite using {{ app_name }}","Einladung mittels {{ app_name }} annehmen"}.
{"Accept","Akzeptieren"}.
{"Access denied by service policy","Zugriff aufgrund der Dienstrichtlinien verweigert"}.
{"Access model","Zugriffsmodell"}.
{"Account doesn't exist","Konto existiert nicht"}.
{"Action on user","Aktion auf Benutzer"}.
{"Add {{ inviter }} to your contact list","Füge {{ inviter }} zu deiner Kontaktliste hinzu"}.
{"Add Contact","Kontakt hinzufügen"}.
{"Add User","Benutzer hinzufügen"}.
{"Administration of ","Administration von "}.
{"Administration","Verwaltung"}.
@@ -57,6 +59,7 @@
{"Anyone with Voice","Jeder mit Stimme"}.
{"Anyone","Jeder"}.
{"API Commands","API Befehle"}.
{"Apple Store Logo","Apple Store Logo"}.
{"April","April"}.
{"Arguments","Argumente"}.
{"As a final reminder, your account details are shown below:","Zur Erinnerung hier nochmal deine Kontodetails:"}.
@@ -75,6 +78,7 @@
{"Bad format","Ungültiges Format"}.
{"BAD REQUEST","UNGÜLTIGE ANFRAGE"}.
{"Beagle IM by Tigase, Inc. is a lightweight and powerful XMPP client for macOS.","Beagle IM von Tigase Inc. ist eine schlanker aber funktionsreicher XMPP-Client für macOS."}.
{"Beagle IM Logo","Beagle IM Logo"}.
{"Birthday","Geburtsdatum"}.
{"Both the username and the resource are required","Sowohl der Benutzername als auch die Ressource sind erforderlich"}.
{"Bytestream already activated","Bytestream bereits aktiviert"}.
@@ -106,6 +110,7 @@
{"Click the button to open the Dino website where you can download and install it on your PC.","Klicke auf den Button um Dino's Webseite zu öffnen, wo du ihn für deinen PC herunterladen und installieren kannst."}.
{"Client acknowledged more stanzas than sent by server","Client bestätigte mehr Stanzas als vom Server gesendet"}.
{"Close","Schließen"}.
{"Clustering","Clustering"}.
{"Commands","Befehle"}.
{"Conference room does not exist","Konferenzraum existiert nicht"}.
{"Configuration of room ~s","Konfiguration des Raumes ~s"}.
@@ -114,6 +119,7 @@
{"Contact Addresses (normally, room owner or owners)","Kontaktadresse (normalerweise Raumbesitzer)"}.
{"Contacts","Kontakte"}.
{"Conversations is a Jabber/XMPP client for Android 6.0+ smartphones that has been optimized to provide a unique mobile experience.","Conversations ist ein Jabber/XMPP-Client für Android 6.0+, der für mobile Endgeräte optimiert wurde."}.
{"Conversations Logo","Conversations Logo"}.
{"Country","Land"}.
{"Create a Hat","Einen Hut erstellen"}.
{"Create Account","Konto anlegen"}.
@@ -132,6 +138,7 @@
{"Deliver event notifications","Ereignisbenachrichtigungen zustellen"}.
{"Deliver payloads with event notifications","Nutzdaten mit Ereignisbenachrichtigungen zustellen"}.
{"Destroy a Hat","Einen Hut zerstören"}.
{"Dino Logo","Dino Logo"}.
{"Disable User","Benutzer deaktivieren"}.
{"Disc only copy","Nur auf Festplatte"}.
{"Don't tell your password to anybody, not even the administrators of the XMPP server.","Geben Sie niemandem Ihr Passwort, auch nicht den Administratoren des XMPP-Servers."}.
@@ -182,6 +189,7 @@
{"Failed to process option '~s'","Konnte Option '~s' nicht verarbeiten"}.
{"Family Name","Nachname"}.
{"FAQ Entry","FAQ-Eintrag"}.
{"F-Droid Store Logo","F-Droid Store Logo"}.
{"February","Februar"}.
{"File larger than ~w bytes","Datei größer als ~w Bytes"}.
{"Fill in the form to search for any matching XMPP User","Füllen Sie das Formular aus, um nach jeglichen passenden XMPP-Benutzern zu suchen"}.
@@ -190,6 +198,7 @@
{"Full List of Room Admins","Vollständige Liste der Raumadmins"}.
{"Full List of Room Owners","Vollständige Liste der Raumbesitzer"}.
{"Full Name","Vollständiger Name"}.
{"Gajim Logo","Gajim Logo"}.
{"Get List of Active Users","Liste der aktiven Benutzer abrufen"}.
{"Get List of Disabled Users","Liste der deaktivierten Benutzer abrufen"}.
{"Get List of Idle Users","Liste der inaktiven Benutzer abrufen"}.
@@ -206,6 +215,7 @@
{"Get User Roster","Kontaktliste abrufen"}.
{"Get User Statistics","Benutzerstatistiken abrufen"}.
{"Given Name","Vorname"}.
{"Google Play Store Logo","Google Play Store Logo"}.
{"Grant voice to this person?","Dieser Person Sprachrechte erteilen?"}.
{"has been banned","wurde gebannt"}.
{"has been kicked because of a system shutdown","wurde wegen einer Systemabschaltung hinausgeworfen"}.
@@ -219,12 +229,13 @@
{"Hat URI","Hüte Funktions-URI"}.
{"Hats limit exceeded","Hütelimit wurde überschritten"}.
{"Hide","Verbergen"}.
{"Hint","Hinweis"}.
{"Host unknown","Host unbekannt"}.
{"Hostname invalid","Hostname unbekannt"}.
{"HTTP File Upload","HTTP-Dateiupload"}.
{"Idle connection","Inaktive Verbindung"}.
{"If you already have {{ app_name }} installed, we recommend that you continue the account creation process using the app by clicking on the button below:","Solltest du {{ app_name }} bereits installiert haben, empfehlen wir dir die Einrichtung des Kontos mittels dieser App durchzuführen indem du auf den Button unten klickst:"}.
{"If you don't have an XMPP client installed yet, here's a list of suitable clients for your platform.","Solltest du noch keinen XMPP-Client installiert haben, haben wir hier eine Liste geegineter Clients für deine Platform."}.
{"If you don't have an XMPP client installed yet, here's a list of suitable clients for your platform.","Solltest du noch keinen XMPP-Client installiert haben, haben wir hier eine Liste geeigneter Clients für deine Platform."}.
{"If you don't see the CAPTCHA image here, visit the web page.","Wenn Sie das CAPTCHA-Bild nicht sehen, besuchen Sie die Webseite."}.
{"Import Directory","Verzeichnis importieren"}.
{"Import File","Datei importieren"}.
@@ -252,8 +263,10 @@
{"Invalid 'previd' value","Ungültiger 'previd'-Wert"}.
{"Invitations are not allowed in this conference","Einladungen sind in dieser Konferenz nicht erlaubt"}.
{"Invite expired","Die Einladung ist abgelaufen"}.
{"Invite Expired","Die Einladung ist abgelaufen"}.
{"Invite to {{ site_name }}","Einladung für {{ site_name }}"}.
{"Invite User","Person einladen"}.
{"Invite","Einladung"}.
{"IP addresses","IP-Adressen"}.
{"is now known as","ist nun bekannt als"}.
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Es ist nicht erlaubt Fehlermeldungen an den Raum zu senden. Der Teilnehmer (~s) hat eine Fehlermeldung (~s) gesendet und wurde aus dem Raum geworfen"}.
@@ -276,7 +289,7 @@
{"Last month","Letzter Monat"}.
{"Last year","Letztes Jahr"}.
{"Launch {{ app_name }} and sign in using your account credentials.","Starte {{ app_name }} und logge dich mit deinen Anmeldedaten ein."}.
{"Launch Beagle IM, and select 'Yes' to add a new account. Click the '+' button under the empty account list and then enter your credentials.","Starte Beagle IM und wähle 'Yes' um ein neues Konto hinzuzufügen. Drücke auf das '+' unter der leeren Account-Liste und gib dann deine Anmeldedaten ein!"}.
{"Launch Beagle IM, and select \\'Yes\\' to add a new account. Click the \\'+\\' button under the empty account list and then enter your credentials.","Starte Beagle IM und wähle \\'Yes\\' um ein neues Konto hinzuzufügen. Drücke auf das \\'+\\' unter der leeren Account-Liste und gib dann deine Anmeldedaten ein."}.
{"Least significant bits of SHA-256 hash of text should equal hexadecimal label","Niederwertigstes Bit des SHA-256-Hashes des Textes sollte hexadezimalem Label gleichen"}.
{"leaves the room","verlässt den Raum"}.
{"List of Hats","Liste aller Hüte"}.
@@ -317,6 +330,7 @@
{"Moderator","Moderator"}.
{"Moderators Only","nur Moderatoren"}.
{"Module failed to handle the query","Modul konnte die Anfrage nicht verarbeiten"}.
{"Monal Logo","Monal Logo"}.
{"Monday","Montag"}.
{"Multicast","Multicast"}.
{"Multiple <item/> elements are not allowed by RFC6121","Mehrere <item/>-Elemente sind laut RFC6121 nicht erlaubt"}.
@@ -416,6 +430,7 @@
{"Outgoing s2s Connections","Ausgehende s2s-Verbindungen"}.
{"Owner privileges required","Besitzerrechte erforderlich"}.
{"Packet relay is denied by service policy","Paket-Relay aufgrund der Dienstrichtlinien verweigert"}.
{"Page navigation","Seiten-Navigation"}.
{"Participant ID","Teilnehmer-ID"}.
{"Participant","Teilnehmer"}.
{"Password Verification","Passwort bestätigen"}.
@@ -431,6 +446,8 @@
{"Ping query is incorrect","Ping-Anfrage ist falsch"}.
{"Ping","Ping"}.
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","Beachten Sie, dass diese Optionen nur die eingebaute Mnesia-Datenbank sichern. Wenn Sie das ODBC-Modul verwenden, müssen Sie auch Ihre SQL-Datenbank separat sichern."}.
{"Please provide a password! Minimum length: {{ password_min_length }}","Bitte gibt ein Passwort ein! Mindestens {{ password_min_length }} Zeichen."}.
{"Please provide a valid username!","Bitte gib einen gültigen Benutzernamen ein!"}.
{"Please, wait for a while before sending new voice request","Bitte warten Sie ein wenig, bevor Sie eine weitere Sprachrecht-Anforderung senden"}.
{"Pong","Pong"}.
{"Possessing 'ask' attribute is not allowed by RFC6121","Ein 'ask'-Attribut zu besitzen ist laut RFC6121 nicht erlaubt"}.
@@ -440,6 +457,7 @@
{"Previous session PID has exited","Vorherige Sitzungs-PID wurde beendet"}.
{"Previous session PID is dead","Vorherige Sitzungs-PID ist tot"}.
{"Previous session timed out","Zeitüberschreitung bei vorheriger Sitzung"}.
{"Previous","Zurück"}.
{"private, ","privat, "}.
{"Public","Öffentlich"}.
{"Publish model","Veröffentlichungsmodell"}.
@@ -447,7 +465,6 @@
{"PubSub subscriber request","PubSub-Abonnenten-Anforderung"}.
{"Purge all items when the relevant publisher goes offline","Alle Items löschen, wenn der relevante Veröffentlicher offline geht"}.
{"Push record not found","Push-Eintrag nicht gefunden"}.
{"QR code icon","QR-Code Icon"}.
{"Queries to the conference members are not allowed in this room","Anfragen an die Konferenzteilnehmer sind in diesem Raum nicht erlaubt"}.
{"Query to another users is forbidden","Anfrage an andere Benutzer ist verboten"}.
{"RAM and disc copy","RAM und Festplatte"}.
@@ -462,10 +479,14 @@
{"Register an XMPP account","Ein XMPP-Konto registrieren"}.
{"Register on {{ site_name }}","Registriere dich auf {{ site_name }}"}.
{"Register","Anmelden"}.
{"Registration error","Fehler beim Registrieren"}.
{"Registration error","Fehler beim registrieren"}.
{"Registration Error","Fehler beim Registrieren"}.
{"Registration Form","Registrierungsformular"}.
{"Registration Success","Registrierung erfolgreich"}.
{"Remote copy","Fernkopie"}.
{"Remove a hat from a user","Eine Funktion bei einem Benutzer entfernen"}.
{"Remove User","Benutzer löschen"}.
{"Renga Logo","Renga Logo"}.
{"Replaced by new connection","Durch neue Verbindung ersetzt"}.
{"Request has timed out","Zeitüberschreitung bei Anforderung"}.
{"Request is ignored","Anforderung wird ignoriert"}.
@@ -517,8 +538,9 @@
{"Show Occupants Join/Leave","Betreten und Verlassen von Teilnehmern anzeigen"}.
{"Show Ordinary Table","Gewöhnliche Tabelle anzeigen"}.
{"Show","Anzeigen"}.
{"Showing apps for <span class='platform-name'>your current platform</span> only. You may also <a href='#' id='show-all-clients-button'>view all apps.</a>","Wir zeigen dir nur Apps für <span class=\"platform-name\">deine aktuelle Platform</span> an. Du kannst dir gerne auch <a href=\"#\" id=\"show-all-clients-button\">sämtliche Apps anzeigen lassen</a>."}.
{"Showing apps for <span class='platform-name'>your current platform</span> only. You may also <a href='#' id='show-all-clients-button' tabindex=\"{{ tabidx }}\">view all apps.</a>","Wir zeigen dir nur Apps für <span class='platform-name'>deine aktuelle Platform</span> an. Du kannst dir gerne auch <a href='#' id='show-all-clients-button' tabindex=\"{{ tabidx }}\">sämtliche Apps anzeigen lassen</a>."}.
{"Shut Down Service","Dienst herunterfahren"}.
{"Siskin IM Logo","Siskin IM Logo"}.
{"SOCKS5 Bytestreams","SOCKS5-Bytestreams"}.
{"Some XMPP clients can store your password in the computer, but you should do this only in your personal computer for safety reasons.","Einige XMPP-Clients speichern Ihr Passwort auf dem Computer. Aus Sicherheitsgründen sollten Sie das nur auf Ihrem persönlichen Computer tun."}.
{"Sorry, it looks like this invite code has expired!","Entschuldigung, es sieht so aus als wäre diese Einladung abgelaufen!"}.
@@ -532,7 +554,7 @@
{"Statically specify a replyto of the node owner(s)","Ein 'replyto' des/der Nodebesitzer(s) statisch angeben"}.
{"Step 1: Download and install {{ app_name }}","Schritt 1: {{ app_name }} herunterladen und installieren"}.
{"Step 1: Install {{ app_name }}","Schritt 1: Installiere {{ app_name }}"}.
{"Step 2: Activate your account","Konto aktivieren"}.
{"Step 2: Activate your account","Schritt 2: Konto aktivieren"}.
{"Step 2: Connect {{ app_name }} to your new account","Schritt 2: Verbinde {{ app_name }} mit deinem neuen Konto"}.
{"Stopped Nodes","Angehaltene Knoten"}.
{"Store binary backup:","Speichere binäres Backup:"}.
@@ -603,14 +625,14 @@
{"The sender of the last received message","Der Absender der letzten erhaltenen Nachricht"}.
{"The stanza MUST contain only one <active/> element, one <default/> element, or one <list/> element","Das Stanza darf nur ein <active/>-Element, ein <default/>-Element oder ein <list/>-Element enthalten"}.
{"The subscription identifier associated with the subscription request","Die mit der Abonnement-Anforderung verknüpfte Abonnement-Bezeichnung"}.
{"The token provided is either invalid or expired.","Der Einladungscode ist entweder ungültig oder abgelaufen"}.
{"The token provided is either invalid or expired.","Der Einladungscode ist entweder ungültig oder abgelaufen."}.
{"The URL of an XSL transformation which can be applied to payloads in order to generate an appropriate message body element.","Die URL einer XSL-Transformation welche auf Nutzdaten angewendet werden kann, um ein geeignetes Nachrichtenkörper-Element zu generieren."}.
{"The URL of an XSL transformation which can be applied to the payload format in order to generate a valid Data Forms result that the client could display using a generic Data Forms rendering engine","Die URL einer XSL-Transformation welche auf das Nutzdaten-Format angewendet werden kann, um ein gültiges Data Forms-Ergebnis zu generieren das der Client mit Hilfe einer generischen Data Forms-Rendering-Engine anzeigen könnte"}.
{"There was an error changing the password: ","Es trat ein Fehler beim Ändern des Passwortes auf: "}.
{"There was an error creating the account: ","Es trat ein Fehler beim Erstellen des Kontos auf: "}.
{"There was an error deleting the account: ","Es trat ein Fehler beim Löschen des Kontos auf: "}.
{"This button works only if you have the app installed already!","Dieser Button funktioniert nur, wenn du die App bereits installiert hast!"}.
{"This is an invite from <strong>{{ inviter }}</strong> to connect and chat on the XMPP network. If you already have an XMPP client installed just press the button below!","Die ist eine Kontakt-Einladung von <strong>{{ inviter }}</strong> um miteinander über XMPP zu chatten. Solltest du bereits einen XMPP-Client haben, so drücke einfach auf den Button hier unten!"}.
{"This is an invite from <strong>{{ inviter }}</strong> to connect and chat on the XMPP network. If you already have an XMPP client installed just press the button below!","Dies ist eine Kontakt-Einladung von <strong>{{ inviter }}</strong> um miteinander über XMPP zu chatten. Solltest du bereits einen XMPP-Client haben, so drücke einfach auf den Button hier unten!"}.
{"This is case insensitive: macbeth is the same that MacBeth and Macbeth.","Dies ist schreibungsunabhängig: macbeth ist gleich MacBeth und Macbeth."}.
{"This page allows to register an XMPP account in this XMPP server. Your JID (Jabber ID) will be of the form: username@server. Please read carefully the instructions to fill correctly the fields.","Diese Seite erlaubt das Anlegen eines XMPP-Kontos auf diesem XMPP-Server. Ihre JID (Jabber-ID) wird diese Form aufweisen: benutzername@server. Bitte lesen Sie die Anweisungen genau durch, um die Felder korrekt auszufüllen."}.
{"This page allows to unregister an XMPP account in this XMPP server.","Diese Seite erlaubt es, ein XMPP-Konto von diesem XMPP-Server zu entfernen."}.
@@ -703,7 +725,7 @@
{"Wrong parameters in the web formulary","Falsche Parameter im Webformular"}.
{"Wrong xmlns","Falscher xmlns"}.
{"XMPP Account Registration","XMPP-Konto-Registrierung"}.
{"XMPP client for Haiku","XMPP-Client für Haiku."}.
{"XMPP client for Haiku","XMPP-Client für Haiku"}.
{"XMPP Domains","XMPP-Domänen"}.
{"XMPP Show Value of Away","XMPP-Anzeigewert von Abwesend"}.
{"XMPP Show Value of Chat","XMPP-Anzeigewert von Chat"}.
@@ -713,7 +735,7 @@
{"You are being removed from the room because of a system shutdown","Sie werden wegen einer Systemabschaltung aus dem Raum entfernt"}.
{"You are not allowed to send private messages","Sie dürfen keine privaten Nachrichten senden"}.
{"You are not joined to the channel","Sie sind dem Raum nicht beigetreten"}.
{"You can connect to {{ site_name }} using any XMPP-compatible software. If your preferred software is not listed above, you may still <a href=\"{{ registration_url }}\">register an account manually</a>.","Du kannst dich mit {{ site_name }} über jede XMPP-kompatible Software verbinden. Sollte deine gewünschte Software hier oben nicht aufgeführt sein, so kannst du zumindest <a href=\"{{ registration_url }}\">einen Account manuell anlegen</a>."}.
{"You can connect to {{ site_name }} using any XMPP-compatible software. If your preferred software is not listed above, you may still <a href=\"{{ registration_url }}\" tabindex='{{ tabidx }}'>register an account manually</a>.","Du kannst dich mit {{ site_name }} über jede XMPP-kompatible Software verbinden. Sollte deine gewünschte Software hier oben nicht aufgeführt sein, so kannst du zumindest <a href=\"{{ registration_url }}\" tabindex='{{ tabidx }}'>einen Account manuell anlegen</a>."}.
{"You can later change your password using an XMPP client.","Sie können Ihr Passwort später mit einem XMPP-Client ändern."}.
{"You can now set up {{ app_name }} and connect it to your new account.","Jetzt kannst du {{ app_name }} einrichten und mit deinem neuen Konto verknüpfen."}.
{"You can start chatting right away with {{ app_name }}. Let's get started!","Mittels {{ app_name }} kannst du direkt mit dem Chatten loslegen. Auf geht's!"}.
@@ -722,7 +744,7 @@
{"You have been invited to chat on {{ site_name }}, part of the XMPP secure and decentralized messaging network.","Du wurdest auf <strong>{{ site_name }}</strong> zum Chat eingeladen, Teil des sicheren und dezentralen XMPP-Sofortnachrichten-Netzwerkes."}.
{"You have been invited to chat on <strong>{{ site_name }}</strong>, part of the XMPP secure and decentralized messaging network.","Du wurdest auf <strong>{{ site_name }}</strong> zum Chat eingeladen. <strong>{{ site_name }}</strong> ist Teil des sicheren und dezentralen XMPP-Sofortnachrichten-Netzwerkes."}.
{"You have been invited to chat with <strong>{{ inviter }}</strong> on {{ site_name }}, part of the XMPP secure and decentralized messaging network.","Du wurdest von <strong>{{ inviter }}</strong> auf {{ site_name }} zum Chat eingeladen. {{ site_name }} ist Teil des sicheren und dezentralen XMPP-Sofortnachrichten-Netzwerkes."}.
{"You have been invited to chat with <strong>{{ inviter }}</strong> on <strong>{{ site_name }}</strong>, part of the XMPP secure and decentralized messaging network.","Du wurdest von <strong>{{ inviter }}</strong> auf {{ site_name }} zum Chat eingeladen. {{ site_name }} ist Teil des sicheren und dezentralen XMPP-Sofortnachrichten-Netzwerkes."}.
{"You have been invited to chat with <strong>{{ inviter }}</strong> on <strong>{{ site_name }}</strong>, part of the XMPP secure and decentralized messaging network.","Du wurdest von <strong>{{ inviter }}</strong> auf <strong>{{ site_name }}</strong> zum Chat eingeladen. <strong>{{ site_name }}</strong> ist Teil des sicheren und dezentralen XMPP-Sofortnachrichten-Netzwerkes."}.
{"You have created an account on <strong>{{ site_name }}</strong>.","Du hast ein Konto auf <strong>{{ site_name }}</strong> angelegt."}.
{"You have joined too many conferences","Sie sind zu vielen Konferenzen beigetreten"}.
{"You must fill in field \"Nickname\" in the form","Sie müssen das Feld \"Spitzname\" im Formular ausfüllen"}.
@@ -731,7 +753,7 @@
{"You need an x:data capable client to search","Sie benötigen einen Client der x:data unterstützt, um zu suchen"}.
{"Your active privacy list has denied the routing of this stanza.","Ihre aktive Privacy-Liste hat das Routing dieses Stanzas verweigert."}.
{"Your contact offline message queue is full. The message has been discarded.","Die Offline-Nachrichten-Warteschlange Ihres Kontaktes ist voll. Die Nachricht wurde verworfen."}.
{"Your password is stored encrypted on the server and will not be accessible after you close this page. Keep it safe and never share it with anyone.","Dein Passwort wird verschlüsselt auf dem Server gespeichert und wird nicht mehr im Klartext verfügbar sein nachdem du diese Seite geschlossen hast. Verwahre es an einem sicheren Ort und teile es mit niemandem!"}.
{"Your password is stored encrypted on the server and will not be accessible after you close this page. Keep it safe and never share it with anyone.","Dein Passwort wird verschlüsselt auf dem Server gespeichert und wird nicht mehr im Klartext verfügbar sein, nachdem du diese Seite geschlossen hast. Verwahre es an einem sicheren Ort und teile es mit niemandem!"}.
{"Your subscription request and/or messages to ~s have been blocked. To unblock your subscription request, visit ~s","Ihre Abonnement-Anforderung und/oder Nachrichten an ~s wurden blockiert. Um Ihre Abonnement-Anforderungen freizugeben, besuchen Sie ~s"}.
{"Your XMPP account was successfully registered.","Ihr XMPP-Konto wurde erfolgreich registriert."}.
{"Your XMPP account was successfully unregistered.","Ihr XMPP-Konto wurde erfolgreich entfernt."}.
+29 -8
View File
@@ -11,6 +11,7 @@
{"A description of the node","Una descripción del nodo"}.
{"A friendly name for the node","Un nombre sencillo para el nodo"}.
{"A fully-featured desktop chat client for Windows and Linux.","Un cliente de charla con muchas posibilidades para Windows y Linux."}.
{"A lean Jabber/XMPP client for Android. It aims at usability, low overhead and security, and works on low-end Android devices starting with Android 4.0.","Un cliente Jabber/XMPP ligero para Android. Está orientado a la usabilidad, seguridad y bajo consumo, y funciona en aparatos Android antiguos a partir de Android 4.0."}.
{"A lightweight and powerful XMPP client for iPhone and iPad. It provides an easy way to talk and share moments with your friends.","Un cliente XMPP ligero y potente para iPhone e iPad. Proporciona una forma sencilla de charlar y compartir momentos con tus amistades."}.
{"A modern open-source chat client for iPhone and iPad. It is easy to use and has a clean user interface.","Un cliente de charla moderno y de software libre para iPhone e iPad. Es simple de usar y tiene una interface de usuario limpia."}.
{"A modern open-source chat client for Mac. It is easy to use and has a clean user interface.","Un cliente de charla moderno y de software libre para Mac. Es simple de usar y tiene una interface de usuario limpia."}.
@@ -24,6 +25,7 @@
{"Account doesn't exist","La cuenta no existe"}.
{"Action on user","Acción en el usuario"}.
{"Add {{ inviter }} to your contact list","Añade {{ inviter }} a tu lista de contactos"}.
{"Add Contact","Añadir Contacto"}.
{"Add User","Añadir usuario"}.
{"Administration of ","Administración de "}.
{"Administration","Administración"}.
@@ -57,6 +59,7 @@
{"Anyone with Voice","Cualquiera con Voz"}.
{"Anyone","Cualquiera"}.
{"API Commands","Comandos API"}.
{"Apple Store Logo","Logo de la Apple Store"}.
{"April","Abril"}.
{"Arguments","Argumentos"}.
{"As a final reminder, your account details are shown below:","Como último recordatorio, los detalles de tu cuenta se muestran aquí:"}.
@@ -75,6 +78,7 @@
{"Bad format","Mal formato"}.
{"BAD REQUEST","MALA PETICIÓN"}.
{"Beagle IM by Tigase, Inc. is a lightweight and powerful XMPP client for macOS.","Beagle IM de Tigase, Inc. es un cliente XMPP ligero y potente para macOS."}.
{"Beagle IM Logo","Logo de Beagle IM"}.
{"Birthday","Cumpleaños"}.
{"Both the username and the resource are required","Se requiere tanto el nombre de usuario como el recurso"}.
{"Bytestream already activated","Bytestream ya está activado"}.
@@ -115,6 +119,7 @@
{"Contact Addresses (normally, room owner or owners)","Direcciones de contacto (normalmente la del dueño o dueños de la sala)"}.
{"Contacts","Contactos"}.
{"Conversations is a Jabber/XMPP client for Android 6.0+ smartphones that has been optimized to provide a unique mobile experience.","Conversations es un cliente Jabber/XMPP para móviles Android 6.0+ optimizado para proporcionar una experiencia móvil única."}.
{"Conversations Logo","Logo de Conversations"}.
{"Country","País"}.
{"Create a Hat","Crear un sombrero"}.
{"Create Account","Crear Cuenta"}.
@@ -133,6 +138,7 @@
{"Deliver event notifications","Entregar notificaciones de eventos"}.
{"Deliver payloads with event notifications","Enviar contenidos junto con las notificaciones de eventos"}.
{"Destroy a Hat","Destruir un sombrero"}.
{"Dino Logo","Logo de Dino"}.
{"Disable User","Deshabilitar un usuario"}.
{"Disc only copy","Copia en disco solamente"}.
{"Don't tell your password to anybody, not even the administrators of the XMPP server.","No le digas tu contraseña a nadie, ni siquiera a los administradores del servidor XMPP."}.
@@ -183,6 +189,7 @@
{"Failed to process option '~s'","Falló el procesado de la opción '~s'"}.
{"Family Name","Apellido"}.
{"FAQ Entry","Apunte en la FAQ"}.
{"F-Droid Store Logo","Logo de la F-Droid Store"}.
{"February","Febrero"}.
{"File larger than ~w bytes","El fichero es más grande que ~w bytes"}.
{"Fill in the form to search for any matching XMPP User","Rellena campos para buscar usuarios XMPP que concuerden"}.
@@ -191,6 +198,7 @@
{"Full List of Room Admins","Lista completa de administradores de la sala"}.
{"Full List of Room Owners","Lista completa de dueños de la sala"}.
{"Full Name","Nombre completo"}.
{"Gajim Logo","Logo de Gajim"}.
{"Get List of Active Users","Ver lista de usuarios activos"}.
{"Get List of Disabled Users","Ver lista de usuarios deshabilitados"}.
{"Get List of Idle Users","Ver lista de usuarios inactivos"}.
@@ -207,6 +215,7 @@
{"Get User Roster","Ver lista de contactos del usuario"}.
{"Get User Statistics","Ver estadísticas de usuario"}.
{"Given Name","Nombre de pila"}.
{"Google Play Store Logo","Logo de la Google Play Store"}.
{"Grant voice to this person?","¿Conceder voz a esta persona?"}.
{"has been banned","ha sido bloqueado"}.
{"has been kicked because of a system shutdown","ha sido expulsado porque el sistema se va a detener"}.
@@ -220,6 +229,7 @@
{"Hat URI","Dirección del sombrero"}.
{"Hats limit exceeded","Se ha excedido el límite de sombreros"}.
{"Hide","Ocultar"}.
{"Hint","Consejo"}.
{"Host unknown","Dominio desconocido"}.
{"Hostname invalid","Dominio no válido"}.
{"HTTP File Upload","Subir fichero por HTTP"}.
@@ -253,8 +263,10 @@
{"Invalid 'previd' value","Valor de 'previd' no válido"}.
{"Invitations are not allowed in this conference","Las invitaciones no están permitidas en esta sala"}.
{"Invite expired","Invitación caducada"}.
{"Invite Expired","Invitación Caducada"}.
{"Invite to {{ site_name }}","Invitación a {{ site_name }}"}.
{"Invite User","Invitar Usuario"}.
{"Invite","Invitar"}.
{"IP addresses","Direcciones IP"}.
{"is now known as","se cambia el nombre a"}.
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","No está permitido enviar mensajes de error a la sala. Este participante (~s) ha enviado un mensaje de error (~s) y fue expulsado de la sala"}.
@@ -277,7 +289,7 @@
{"Last month","Último mes"}.
{"Last year","Último año"}.
{"Launch {{ app_name }} and sign in using your account credentials.","Lanza {{ app_name }} y entra en la cuenta usando tus credenciales."}.
{"Launch Beagle IM, and select 'Yes' to add a new account. Click the '+' button under the empty account list and then enter your credentials.","Inicia Beagle IM, y pulsa 'Sí' para añadir una nueva cuenta. Pulsa el botón '+' debajo de la lista de cuentas vacía e introduce tus credenciales."}.
{"Launch Beagle IM, and select \\'Yes\\' to add a new account. Click the \\'+\\' button under the empty account list and then enter your credentials.","Inicia Beagle IM, y pulsa \\'Sí\\' para añadir una nueva cuenta. Pulsa el botón \\'+\\' debajo de la lista de cuentas vacía e introduce tus credenciales."}.
{"Least significant bits of SHA-256 hash of text should equal hexadecimal label","Los bits menos significativos del hash SHA-256 del texto deberían ser iguales a la etiqueta hexadecimal"}.
{"leaves the room","sale de la sala"}.
{"List of Hats","Lista de sombreros"}.
@@ -318,6 +330,7 @@
{"Moderator","Moderador"}.
{"Moderators Only","Solo moderadores"}.
{"Module failed to handle the query","El módulo falló al gestionar la petición"}.
{"Monal Logo","Logo de Monal"}.
{"Monday","Lunes"}.
{"Multicast","Multidifusión"}.
{"Multiple <item/> elements are not allowed by RFC6121","No se permiten múltiples elementos <item/> en RFC6121"}.
@@ -417,6 +430,7 @@
{"Outgoing s2s Connections","Conexiones S2S salientes"}.
{"Owner privileges required","Se requieren privilegios de propietario de la sala"}.
{"Packet relay is denied by service policy","Se ha denegado el reenvío del paquete por política del servicio"}.
{"Page navigation","Navegación de Página"}.
{"Participant ID","ID del Participante"}.
{"Participant","Participante"}.
{"Password Verification","Verificación de la contraseña"}.
@@ -432,6 +446,8 @@
{"Ping query is incorrect","La petición de Ping es incorrecta"}.
{"Ping","Ping"}.
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","Ten en cuenta que estas opciones solo harán copia de seguridad de la base de datos Mnesia embebida. Si estás usando ODBC tendrás que hacer también copia de seguridad de tu base de datos SQL."}.
{"Please provide a password! Minimum length: {{ password_min_length }}","¡Por favor proporciona una contraseña! Longitud mínima: {{ password_min_length }}"}.
{"Please provide a valid username!","¡Por favor proporciona un nombre de usuario válido!"}.
{"Please, wait for a while before sending new voice request","Por favor, espera un poco antes de enviar otra petición de voz"}.
{"Pong","Pong"}.
{"Possessing 'ask' attribute is not allowed by RFC6121","Poseer el atributo 'ask' no está permitido por RFC6121"}.
@@ -441,6 +457,7 @@
{"Previous session PID has exited","El proceso de la sesión previa ha terminado"}.
{"Previous session PID is dead","El proceso de la sesión previa está muerto"}.
{"Previous session timed out","La sesión previa ha caducado"}.
{"Previous","Previo"}.
{"private, ","privado, "}.
{"Public","Público"}.
{"Publish model","Modelo de publicación"}.
@@ -448,7 +465,6 @@
{"PubSub subscriber request","Petición de subscriptor de PubSub"}.
{"Purge all items when the relevant publisher goes offline","Borra todos los elementos cuando el publicador relevante se desconecta"}.
{"Push record not found","No se encontró registro Push"}.
{"QR code icon","Icono de código QR"}.
{"Queries to the conference members are not allowed in this room","En esta sala no se permiten solicitudes a los miembros de la sala"}.
{"Query to another users is forbidden","Enviar solicitudes a otros usuarios está prohibido"}.
{"RAM and disc copy","Copia en RAM y disco"}.
@@ -463,10 +479,14 @@
{"Register an XMPP account","Registrar una cuenta XMPP"}.
{"Register on {{ site_name }}","Registrar en {{ site_name }}"}.
{"Register","Registrar"}.
{"Registration Error","Error al Registrar"}.
{"Registration error","Error en el registro"}.
{"Registration Form","Formulario de Registro"}.
{"Registration Success","Registro completado correctamente"}.
{"Remote copy","Copia remota"}.
{"Remove a hat from a user","Quitarle un sombrero a un usuario"}.
{"Remove User","Eliminar usuario"}.
{"Renga Logo","Logo de Renga"}.
{"Replaced by new connection","Reemplazado por una nueva conexión"}.
{"Request has timed out","La petición ha caducado"}.
{"Request is ignored","La petición ha sido ignorada"}.
@@ -517,9 +537,10 @@
{"Show Integral Table","Mostrar Tabla Integral"}.
{"Show Occupants Join/Leave","Mostrar personas activas Entrar/Salir"}.
{"Show Ordinary Table","Mostrar Tabla Ordinaria"}.
{"Showing apps for <span class='platform-name'>your current platform</span> only. You may also <a href='#' id='show-all-clients-button'>view all apps.</a>","Mostrando aplicaciones para <span class='platform-name'>tu plataforma actual</span> nada más. También puedes <a href='#' id='show-all-clients-button'>mostrar todas las aplicaciones.</a>"}.
{"Showing apps for <span class='platform-name'>your current platform</span> only. You may also <a href='#' id='show-all-clients-button' tabindex=\"{{ tabidx }}\">view all apps.</a>","Mostrando aplicaciones para <span class='platform-name'>tu plataforma actual</span> nada más. También puedes <a href='#' id='show-all-clients-button' tabindex=\"{{ tabidx }}\">mostrar todas las aplicaciones.</a>"}.
{"Show","Mostrar"}.
{"Shut Down Service","Detener el servicio"}.
{"Siskin IM Logo","Logo de Siskin IM"}.
{"SOCKS5 Bytestreams","SOCKS5 Bytestreams"}.
{"Some XMPP clients can store your password in the computer, but you should do this only in your personal computer for safety reasons.","Algunos clientes XMPP pueden guardar tu contraseña en la máquina, pero solo deberías hacer esto en tu propia máquina personal, por razones de seguridad."}.
{"Sorry, it looks like this invite code has expired!","Lo siento ¡parece que este código de invitación ha caducado!"}.
@@ -543,7 +564,7 @@
{"<strong>{{ site_name }}</strong> is part of XMPP, a secure and decentralized messaging network. To begin chatting using <strong>{{ app_name }}</strong> you need to first register an account.","<strong>{{ site_name }}</strong> es parte de XMPP, una red de mensajería segura y decentralizada. Para comenzar a charlar usando <strong>{{ app_name }}</strong>, primero créate una cuenta."}.
{"<strong>{{ site_name }}</strong> is part of XMPP, a secure and decentralized messaging network. To begin chatting you need to first register an account.","<strong>{{ site_name }}</strong> es parte de XMPP, una red de mensajería segura y decentralizada. Para comenzar a charlar, primero has de registrar una cuenta."}.
{"<strong>No suitable software installed right now?</strong> You can also log in to your account through our online web chat!","<strong>¿No tienes instalada ninguna aplicación ahora mismo?</strong> También puedes entrar en tu cuenta ¡usando el chat web en linea!"}.
{"<strong>Tip:</strong> You can open this invite on your mobile device by scanning a barcode with your camera.","<strong>Truco:</strong> Puedes abrir esta invitación en tu móvil escaneando el código QR con la cámara de fotos."}.
{"<strong>Tip:</strong> You can open this invite on your mobile device by scanning a barcode with your camera.","<strong>Truco:</strong> Puedes abrir esta invitación en tu móvil escaneando el código con la cámara de fotos."}.
{"Subject","Asunto"}.
{"Submit","Enviar"}.
{"Submitted","Enviado"}.
@@ -563,7 +584,7 @@
{"The body text of the last received message","El contenido de texto del último mensaje recibido"}.
{"The CAPTCHA is valid.","El CAPTCHA es válido."}.
{"The CAPTCHA verification has failed","La verificación de CAPTCHA ha fallado"}.
{"The captcha you entered is wrong","El CAPTCHA que has introducido es erróneo"}.
{"The captcha you entered is wrong","El captcha que has introducido es erróneo"}.
{"The child nodes (leaf or collection) associated with a collection","Los nodos hijos (ya sean hojas o colecciones) asociados con una colección"}.
{"The collections with which a node is affiliated","Las colecciones a las que un nodo está afiliado"}.
{"The DateTime at which a leased subscription will end or has ended","La FechayHora en la que una suscripción prestada acabará o ha terminado"}.
@@ -702,7 +723,7 @@
{"Who can send private messages","Quién puede enviar mensajes privados"}.
{"Who may associate leaf nodes with a collection","Quien puede asociar nodos hoja con una colección"}.
{"Wrong parameters in the web formulary","Parámetros incorrectos en el formulario web"}.
{"Wrong xmlns","XMLNS incorrecto"}.
{"Wrong xmlns","Xmlns incorrecto"}.
{"XMPP Account Registration","Registro de Cuenta XMPP"}.
{"XMPP client for Haiku","Cliente XMPP para Haiku"}.
{"XMPP Domains","Dominios XMPP"}.
@@ -714,11 +735,11 @@
{"You are being removed from the room because of a system shutdown","Estás siendo expulsado de la sala porque el sistema se va a detener"}.
{"You are not allowed to send private messages","No tienes permitido enviar mensajes privados"}.
{"You are not joined to the channel","No has entrado en el canal"}.
{"You can connect to {{ site_name }} using any XMPP-compatible software. If your preferred software is not listed above, you may still <a href=\"{{ registration_url }}\">register an account manually</a>.","Puedes conectarte a {{ site_name }} usando cualquier programa compatible con XMPP. Si tu programa preferido no está en esta lista, puedes <a href=\"{{ registration_url }}\">registrarte una cuenta manualmente</a>."}.
{"You can connect to {{ site_name }} using any XMPP-compatible software. If your preferred software is not listed above, you may still <a href=\"{{ registration_url }}\" tabindex='{{ tabidx }}'>register an account manually</a>.","Puedes conectarte a {{ site_name }} usando cualquier programa compatible con XMPP. Si tu programa preferido no está en esta lista, puedes <a href=\"{{ registration_url }}\" tabindex='{{ tabidx }}'>registrarte una cuenta manualmente</a>."}.
{"You can later change your password using an XMPP client.","Puedes cambiar tu contraseña después, usando un cliente XMPP."}.
{"You can now set up {{ app_name }} and connect it to your new account.","Ahora puedes configurar {{ app_name }} y conectarte a tu nueva cuenta."}.
{"You can start chatting right away with {{ app_name }}. Let's get started!","Puedes empezar ya a charlar usando {{ app_name }}. ¡Empecemos!"}.
{"You can transfer this invite to your mobile device by scanning a code with your camera.","Puedes transferir esta invitación a tu móvil si escaneas el código QR con la cámara de fotos."}.
{"You can transfer this invite to your mobile device by scanning a code with your camera.","Puedes transferir esta invitación a tu móvil si escaneas el código con la cámara de fotos."}.
{"You have been banned from this room","Has sido bloqueado en esta sala"}.
{"You have been invited to chat on {{ site_name }}, part of the XMPP secure and decentralized messaging network.","Te han invitado a charlar en {{ site_name }}, que forma parte de la red XMPP de mensajería segura y decentralizada."}.
{"You have been invited to chat on <strong>{{ site_name }}</strong>, part of the XMPP secure and decentralized messaging network.","Te han invitado a charlar en <strong>{{ site_name }}</strong>, que forma parte de la red XMPP de mensajería segura y decentralizada."}.
+28
View File
@@ -61,6 +61,7 @@
{"Backup to File at ","Sauvegarde fichier sur "}.
{"Backup","Sauvegarde"}.
{"Bad format","Mauvais format"}.
{"BAD REQUEST","MAUVAISE REQUÊTE"}.
{"Birthday","Date d'anniversaire"}.
{"Both the username and the resource are required","Le nom d'utilisateur et sa ressource sont nécessaires"}.
{"Bytestream already activated","Le flux SOCKS5 est déjà activé"}.
@@ -97,6 +98,7 @@
{"Contacts","Contacts"}.
{"Country","Pays"}.
{"Create a Hat","Créer un badge"}.
{"Create Account","Créer un Compte"}.
{"Current Discussion Topic","Sujet de discussion courant"}.
{"Database failure","Échec sur la base de données"}.
{"Database Tables Configuration at ","Configuration des tables de base de données sur "}.
@@ -212,6 +214,7 @@
{"Invalid node name","Nom de nœud invalide"}.
{"Invalid 'previd' value","Valeur 'previd' invalide"}.
{"Invitations are not allowed in this conference","Les invitations ne sont pas autorisées dans ce salon"}.
{"Invite User","Inviter un Utilisateur"}.
{"IP addresses","Adresses IP"}.
{"is now known as","est maintenant connu comme"}.
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","L'envoyer de messages d'erreur au salon n'est pas autorisé. Le participant (~s) à envoyé un message d'erreur (~s) et à été expulsé du salon"}.
@@ -219,6 +222,7 @@
{"It is not allowed to send private messages to the conference","Il n'est pas permis d'envoyer des messages privés à la conférence"}.
{"Jabber ID","Jabber ID"}.
{"January","Janvier"}.
{"JID normalization failed","Échec de la normalisation JID"}.
{"Joined MIX channels of ~ts","Canaux MIX rejoints par ~ts"}.
{"Joined MIX channels:","Canaux MIX rejoints :"}.
{"joins the room","rejoint le salon"}.
@@ -232,6 +236,8 @@
{"Last year","Dernière année"}.
{"leaves the room","quitte le salon"}.
{"List of Hats","Liste des badges"}.
{"List users with hats","Liste des utilisateurs avec des chapeaux"}.
{"Logged Out","Déconnecté"}.
{"Make participants list public","Rendre la liste des participants publique"}.
{"Make room CAPTCHA protected","Protéger le salon par un CAPTCHA"}.
{"Make room members-only","Réserver le salon aux membres uniquement"}.
@@ -240,15 +246,19 @@
{"Make room persistent","Rendre le salon persistant"}.
{"Make room public searchable","Rendre le salon public"}.
{"Malformed username","Nom d'utilisateur invalide"}.
{"MAM preference modification denied by service policy","Modification des préférences MAM refusée par la politique de service"}.
{"March","Mars"}.
{"Max payload size in bytes","Taille maximum pour le contenu du message en octet"}.
{"Maximum file size","Taille maximale du fichier"}.
{"Maximum Number of History Messages Returned by Room","Nombre maximal de messages d'historique renvoyés par salle"}.
{"Maximum number of invites reached","Nombre maximal d'invitations atteint"}.
{"Maximum number of items to persist","Nombre maximal d'éléments à conserver"}.
{"Maximum Number of Occupants","Nombre maximal d'occupants"}.
{"May","Mai"}.
{"Members are allowed to invite others","Les membres sont autorisés à inviter d'autres personnes."}.
{"Membership is required to enter this room","Vous devez être membre pour accèder à ce salon"}.
{"Memorize your password, or write it in a paper placed in a safe place. In XMPP there isn't an automated way to recover your password if you forget it.","Mémorisez votre mot de passe, ou écrivez-le sur un papier conservé dans un endroit secret. Dans XMPP il n'y a pas de mécanisme pour retrouver votre mot de passe si vous l'avez oublié."}.
{"Mere Availability in XMPP (No Show Value)","Disponible sur XMPP (sans valeur affichée)"}.
{"Message body","Corps du message"}.
{"Message not found in forwarded payload","Message non trouvé dans l'enveloppe transférée"}.
{"Messages from strangers are rejected","Les messages d'étrangers sont rejetés"}.
@@ -314,12 +324,15 @@
{"None","Aucun"}.
{"Not allowed","Non autorisé"}.
{"Not Found","Nœud non trouvé"}.
{"NOT FOUND","NON TROUVÉ"}.
{"Not subscribed","Pas abonné"}.
{"Notify subscribers when items are removed from the node","Avertir les abonnés lorsque des éléments sont supprimés sur le nœud"}.
{"Notify subscribers when the node configuration changes","Avertir les abonnés lorsque la configuration du nœud change"}.
{"Notify subscribers when the node is deleted","Avertir les abonnés lorsque le nœud est supprimé"}.
{"November","Novembre"}.
{"Number of answers required","Nombre de réponses requises"}.
{"Number of disabled users","Nombre d'utilisateurs désactivés"}.
{"Number of idle users","Nombre d'utilisateurs inactifs"}.
{"Number of occupants","Nombre d'occupants"}.
{"Number of Offline Messages","Nombre de messages hors ligne"}.
{"Number of online users","Nombre d'utilisateurs en ligne"}.
@@ -373,9 +386,11 @@
{"Previous session timed out","La session précédente a expiré"}.
{"private, ","privé, "}.
{"Public","Public"}.
{"Publish model","Modèle de publication"}.
{"Publish-Subscribe","Publication-Abonnement"}.
{"PubSub subscriber request","Demande d'abonnement PubSub"}.
{"Purge all items when the relevant publisher goes offline","Purger tous les items lorsque publieur est hors-ligne"}.
{"Push record not found","Enregistrement non trouvé"}.
{"Queries to the conference members are not allowed in this room","Les requêtes sur les membres de la conférence ne sont pas autorisé dans ce salon"}.
{"Query to another users is forbidden","Requête vers un autre utilisateur interdite"}.
{"RAM and disc copy","Copie en mémoire vive (RAM) et sur disque"}.
@@ -386,9 +401,11 @@
{"Receive notification of new items only","Recevoir les notifications des nouveaux éléments uniquement"}.
{"Receive notification of new nodes only","Recevoir les notifications de tous les nouveaux nœuds descendants"}.
{"Recipient is not in the conference room","Le destinataire n'est pas dans la conférence"}.
{"Re-Enable User","Réactiver l'utilisateur"}.
{"Register an XMPP account","Inscrire un compte XMPP"}.
{"Register","Enregistrer"}.
{"Remote copy","Copie distante"}.
{"Remove a hat from a user","Retirer un chapeau d'un utilisateur"}.
{"Remove User","Supprimer l'utilisateur"}.
{"Replaced by new connection","Remplacé par une nouvelle connexion"}.
{"Request has timed out","La demande a expiré"}.
@@ -401,16 +418,22 @@
{"Restore binary backup immediately:","Restauration immédiate d'une sauvegarde binaire :"}.
{"Restore plain text backup immediately:","Restauration immédiate d'une sauvegarde texte :"}.
{"Restore","Restauration"}.
{"Result","Résultat"}.
{"Roles and Affiliations that May Retrieve Member List","Rôles et Affiliations pouvant obtenir la liste des membres"}.
{"Roles for which Presence is Broadcasted","Rôles pour lesquels la présence est diffusée"}.
{"Roles that May Send Private Messages","Rôles pouvant envoyer des messages privés"}.
{"Room Configuration","Configuration du salon"}.
{"Room creation is denied by service policy","La création de salons est interdite par le service"}.
{"Room description","Description du salon"}.
{"Room Occupants","Occupants du salon"}.
{"Room terminates","Fermeture du salon"}.
{"Room title","Titre du salon"}.
{"Roster groups allowed to subscribe","Groupes de liste de contact autorisés à s'abonner"}.
{"Roster size","Taille de la liste de contacts"}.
{"Running Nodes","Nœuds actifs"}.
{"~s invites you to the room ~s","~s vous a invité dans le salon de discussion ~s"}.
{"Saturday","Samedi"}.
{"Search from the date","Rechercher à partir de la date"}.
{"Search Results for ","Résultats de recherche pour "}.
{"Search the text","Recherche le texte"}.
{"Search until the date","Rechercher jusqu’à la date"}.
@@ -422,18 +445,23 @@
{"September","Septembre"}.
{"Server:","Serveur :"}.
{"Service list retrieval timed out","La récupération de la liste des services a expiré"}.
{"Session state copying timed out","Le délai d'attente pour la copie de l'état de la session a expiré"}.
{"Set message of the day and send to online users","Définir le message du jour et l'envoyer aux utilisateurs en ligne"}.
{"Set message of the day on all hosts and send to online users","Définir le message du jour pour tous domaines et l'envoyer aux utilisateurs en ligne"}.
{"Shared Roster Groups","Groupes de liste de contacts partagée"}.
{"Show Integral Table","Montrer la table intégralement"}.
{"Show Occupants Join/Leave","Afficher les personnes qui rejoignent/quittent"}.
{"Show Ordinary Table","Montrer la table ordinaire"}.
{"Shut Down Service","Arrêter le service"}.
{"SOCKS5 Bytestreams","Transferts SOCKS5"}.
{"Some XMPP clients can store your password in the computer, but you should do this only in your personal computer for safety reasons.","Certains clients XMPP peuvent stocker votre mot de passe sur votre ordinateur. N'utilisez cette fonctionnalité que si vous avez confiance en la sécurité de votre ordinateur."}.
{"Sources Specs:","Spécifications du code source:"}.
{"Specify the access model","Définir le modèle d'accès"}.
{"Specify the event message type","Définir le type de message d'événement"}.
{"Specify the publisher model","Définir le modèle de publication"}.
{"Stanza id is not valid","L'identifiant de Stanza n'est pas valide."}.
{"Stanza ID","Identifiant Stanza"}.
{"Statically specify a replyto of the node owner(s)","Spécifier de manière statique une réponse au(x) propriétaire(s) du nœud"}.
{"Stopped Nodes","Nœuds arrêtés"}.
{"Store binary backup:","Sauvegarde binaire :"}.
{"Store plain text backup:","Sauvegarde texte :"}.
+35 -14
View File
@@ -10,7 +10,8 @@
{"# participants","# 参与者"}.
{"A description of the node","节点的描述"}.
{"A friendly name for the node","节点的友好名称"}.
{"A fully-featured desktop chat client for Windows and Linux.","功能齐全的 Windows 和 Linux 桌面聊天客户端。"}.
{"A fully-featured desktop chat client for Windows and Linux.","适用于 Windows 和 Linux 的功能齐全的桌面聊天客户端。"}.
{"A lean Jabber/XMPP client for Android. It aims at usability, low overhead and security, and works on low-end Android devices starting with Android 4.0.","适用于 Android 的轻量级 Jabber/XMPP 客户端,注重易用性、低开销和安全性,可在 Android 4.0 及更高版本的低端设备上运行。"}.
{"A lightweight and powerful XMPP client for iPhone and iPad. It provides an easy way to talk and share moments with your friends.","适用于 iPhone 和 iPad 的轻量高效 XMPP 客户端,让您轻松与好友畅聊并分享生活瞬间。"}.
{"A modern open-source chat client for iPhone and iPad. It is easy to use and has a clean user interface.","适用于 iPhone 和 iPad 的现代开源聊天客户端,易于使用,界面简洁。"}.
{"A modern open-source chat client for Mac. It is easy to use and has a clean user interface.","适用于 Mac 的现代开源聊天客户端,易于使用,界面简洁。"}.
@@ -24,11 +25,12 @@
{"Account doesn't exist","账号不存在"}.
{"Action on user","对用户的操作"}.
{"Add {{ inviter }} to your contact list","将 {{ inviter }} 添加到您的联系人列表"}.
{"Add Contact","添加联系人"}.
{"Add User","添加用户"}.
{"Administration of ","管理 "}.
{"Administration","管理"}.
{"Administrator privileges required","需要管理员权限"}.
{"After clicking the button you will be taken to {{ app_name }} to finish setting up your new {{ site_name }} account.","点击按钮将跳转到 {{ app_name }} 完成新 {{ site_name }} 账号的设置。"}.
{"After clicking the button you will be taken to {{ app_name }} to finish setting up your new {{ site_name }} account.","点击按钮将跳转到 {{ app_name }},完成新 {{ site_name }} 账号的设置。"}.
{"After successfully installing {{ app_name }}, come back to this page and <strong>continue with Step 2</strong>.","成功安装 {{ app_name }} 后,请返回此页面并<strong>继续执行步骤 2</strong>。"}.
{"All activity","所有活动"}.
{"All Users","所有用户"}.
@@ -41,7 +43,7 @@
{"Allow users to send private messages","允许用户发送私信"}.
{"Allow visitors to change nickname","允许参观者更改昵称"}.
{"Allow visitors to send private messages to","允许参观者发送私信至"}.
{"Allow visitors to send status text in presence updates","允许参观者在在线状态更新中发送状态文"}.
{"Allow visitors to send status text in presence updates","允许参观者在在线状态更新中发送状态文"}.
{"Allow visitors to send voice requests","允许参观者发送发言权请求"}.
{"An associated LDAP group that defines room membership; this should be an LDAP Distinguished Name according to an implementation-specific or deployment-specific definition of a group.","与定义房间成员资格相关联的 LDAP 组;根据组的特定实现或特定部署的定义,使用 LDAP 专有名称。"}.
{"Announcements","公告"}.
@@ -57,6 +59,7 @@
{"Anyone with Voice","任何有发言权的人"}.
{"Anyone","任何人"}.
{"API Commands","API 命令"}.
{"Apple Store Logo","Apple Store 徽标"}.
{"April","四月"}.
{"Arguments","参数"}.
{"As a final reminder, your account details are shown below:","最终提醒,您的账号详情如下所示:"}.
@@ -75,6 +78,7 @@
{"Bad format","格式错误"}.
{"BAD REQUEST","错误请求"}.
{"Beagle IM by Tigase, Inc. is a lightweight and powerful XMPP client for macOS.","Beagle IM 由 Tigase, Inc. 开发,是适用于 macOS 的轻量高效 XMPP 客户端。"}.
{"Beagle IM Logo","Beagle IM 徽标"}.
{"Birthday","生日"}.
{"Both the username and the resource are required","用户名和资源均为必填项"}.
{"Bytestream already activated","字节流已激活"}.
@@ -114,7 +118,8 @@
{"Congratulations!","恭喜!"}.
{"Contact Addresses (normally, room owner or owners)","联系地址(通常为房间所有者)"}.
{"Contacts","联系人"}.
{"Conversations is a Jabber/XMPP client for Android 6.0+ smartphones that has been optimized to provide a unique mobile experience.","Conversations 是专为 Android 6.0 及更高版本智能手机优化的 Jabber/XMPP 客户端,提供独特的移动端体验。"}.
{"Conversations is a Jabber/XMPP client for Android 6.0+ smartphones that has been optimized to provide a unique mobile experience.","Conversations 是适用于 Android 6.0 及更高版本智能手机的 Jabber/XMPP 客户端,经过优化以提供独特的移动端体验。"}.
{"Conversations Logo","Conversations 徽标"}.
{"Country","国家/地区"}.
{"Create a Hat","创建头衔"}.
{"Create Account","创建账号"}.
@@ -133,6 +138,7 @@
{"Deliver event notifications","传递事件通知"}.
{"Deliver payloads with event notifications","用事件通知传递有效负载"}.
{"Destroy a Hat","删除头衔"}.
{"Dino Logo","Dino 徽标"}.
{"Disable User","禁用用户"}.
{"Disc only copy","仅磁盘副本"}.
{"Don't tell your password to anybody, not even the administrators of the XMPP server.","不要将您的密码告诉任何人,甚至是 XMPP 服务器的管理员。"}.
@@ -168,7 +174,7 @@
{"Enter path to jabberd14 spool dir","请输入 jabberd14 spool 目录路径"}.
{"Enter path to jabberd14 spool file","请输入 jabberd14 spool 文件路径"}.
{"Enter path to text file","请输入文本文件路径"}.
{"Enter the text you see","请输入您看到的文"}.
{"Enter the text you see","请输入您看到的文"}.
{"Erlang XMPP Server","Erlang XMPP 服务器"}.
{"Exclude Jabber IDs from CAPTCHA challenge","从验证码挑战中排除的 Jabber ID"}.
{"Export all tables as SQL queries to a file:","将所有表以 SQL 查询导出到文件:"}.
@@ -183,6 +189,7 @@
{"Failed to process option '~s'","无法处理选项“~s”"}.
{"Family Name","姓氏"}.
{"FAQ Entry","常见问题条目"}.
{"F-Droid Store Logo","F-Droid Store 徽标"}.
{"February","二月"}.
{"File larger than ~w bytes","文件大于 ~w 字节"}.
{"Fill in the form to search for any matching XMPP User","填写表单以搜索任何匹配的 XMPP 用户"}.
@@ -191,6 +198,7 @@
{"Full List of Room Admins","房间管理员的完整列表"}.
{"Full List of Room Owners","房间所有者的完整列表"}.
{"Full Name","全名"}.
{"Gajim Logo","Gajim 徽标"}.
{"Get List of Active Users","获取活跃用户列表"}.
{"Get List of Disabled Users","获取禁用用户列表"}.
{"Get List of Idle Users","获取闲置用户列表"}.
@@ -207,6 +215,7 @@
{"Get User Roster","获取用户名册"}.
{"Get User Statistics","获取用户统计数据"}.
{"Given Name","名字"}.
{"Google Play Store Logo","Google Play 商店徽标"}.
{"Grant voice to this person?","是否授予此用户发言权?"}.
{"has been banned","已被封禁"}.
{"has been kicked because of a system shutdown","因系统关闭被踢出"}.
@@ -220,6 +229,7 @@
{"Hat URI","头衔 URI"}.
{"Hats limit exceeded","已超过头衔限制"}.
{"Hide","隐藏"}.
{"Hint","提示"}.
{"Host unknown","主机未知"}.
{"Hostname invalid","主机名无效"}.
{"HTTP File Upload","HTTP 文件上传"}.
@@ -253,8 +263,10 @@
{"Invalid 'previd' value","“previd”值无效"}.
{"Invitations are not allowed in this conference","此会议不允许邀请"}.
{"Invite expired","邀请已过期"}.
{"Invite Expired","邀请已过期"}.
{"Invite to {{ site_name }}","邀请加入 {{ site_name }}"}.
{"Invite User","邀请用户"}.
{"Invite","邀请"}.
{"IP addresses","IP 地址"}.
{"is now known as","现在昵称为"}.
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","不允许向此房间发送错误消息。参与者(~s)发送了错误消息(~s),被踢出了房间"}.
@@ -277,7 +289,7 @@
{"Last month","上个月"}.
{"Last year","去年"}.
{"Launch {{ app_name }} and sign in using your account credentials.","启动 {{ app_name }} 并使用您的账号凭据登录。"}.
{"Launch Beagle IM, and select 'Yes' to add a new account. Click the '+' button under the empty account list and then enter your credentials.","启动 Beagle IM,然后选择“是”添加新账号。点击空白账号列表下方的“+”按钮,然后输入您的凭据。"}.
{"Launch Beagle IM, and select \\'Yes\\' to add a new account. Click the \\'+\\' button under the empty account list and then enter your credentials.","启动 Beagle IM,然后选择“是”添加新账号。点击空白账号列表下方的“+”按钮,然后输入您的凭据。"}.
{"Least significant bits of SHA-256 hash of text should equal hexadecimal label","文本的 SHA-256 散列的最低有效位应等于十六进制标签"}.
{"leaves the room","离开房间"}.
{"List of Hats","头衔列表"}.
@@ -318,6 +330,7 @@
{"Moderators Only","仅主持人"}.
{"Moderator","主持人"}.
{"Module failed to handle the query","模块无法处理查询"}.
{"Monal Logo","Monal 徽标"}.
{"Monday","周一"}.
{"Multicast","多播"}.
{"Multiple <item/> elements are not allowed by RFC6121","按照 RFC6121,多个 <item/> 元素是不允许的"}.
@@ -417,6 +430,7 @@
{"Outgoing s2s Connections","传出 s2s 连接"}.
{"Owner privileges required","需要所有者权限"}.
{"Packet relay is denied by service policy","服务策略拒绝数据包中继"}.
{"Page navigation","页面导航"}.
{"Participant ID","参与者 ID"}.
{"Participant","参与者"}.
{"Password Verification","密码验证"}.
@@ -432,6 +446,8 @@
{"Ping query is incorrect","Ping 查询不正确"}.
{"Ping","Ping"}.
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","注意:这些选项只会备份内置的 Mnesia 数据库。如果使用 ODBC 模块,还需要单独备份 SQL 数据库。"}.
{"Please provide a password! Minimum length: {{ password_min_length }}","请提供密码!最小长度:{{ password_min_length }}"}.
{"Please provide a valid username!","请提供有效的用户名!"}.
{"Please, wait for a while before sending new voice request","请稍候再发送新的发言权请求"}.
{"Pong","Pong"}.
{"Possessing 'ask' attribute is not allowed by RFC6121","按照 RFC6121 不允许有“ask”属性"}.
@@ -441,6 +457,7 @@
{"Previous session PID has exited","上一个会话 PID 已退出"}.
{"Previous session PID is dead","上一个会话 PID 已失效"}.
{"Previous session timed out","上一个会话超时"}.
{"Previous","上一页"}.
{"private, ","私人, "}.
{"Public","公开"}.
{"Publish model","发布模型"}.
@@ -448,7 +465,6 @@
{"PubSub subscriber request","PubSub 订阅者请求"}.
{"Purge all items when the relevant publisher goes offline","相关发布者离线后清除所有项目"}.
{"Push record not found","未找到推送记录"}.
{"QR code icon","二维码图标"}.
{"Queries to the conference members are not allowed in this room","此房间不允许向会议成员查询"}.
{"Query to another users is forbidden","禁止查询其他用户"}.
{"RAM and disc copy","RAM 和磁盘副本"}.
@@ -464,9 +480,13 @@
{"Register on {{ site_name }}","在 {{ site_name }} 注册"}.
{"Register","注册"}.
{"Registration error","注册错误"}.
{"Registration Error","注册错误"}.
{"Registration Form","注册表单"}.
{"Registration Success","注册成功"}.
{"Remote copy","远程副本"}.
{"Remove a hat from a user","移除用户头衔"}.
{"Remove User","移除用户"}.
{"Renga Logo","Renga 徽标"}.
{"Replaced by new connection","替换为新连接"}.
{"Request has timed out","请求已超时"}.
{"Request is ignored","请求被忽略"}.
@@ -498,7 +518,7 @@
{"Scan with mobile device","使用移动设备扫描"}.
{"Search from the date","从日期搜索"}.
{"Search Results for ","搜索结果 "}.
{"Search the text","搜索文"}.
{"Search the text","搜索文"}.
{"Search until the date","搜索截至日期"}.
{"Search users in ","在以下位置搜索用户 "}.
{"Select","选择"}.
@@ -517,9 +537,10 @@
{"Show Integral Table","显示完整表"}.
{"Show Occupants Join/Leave","显示使用者加入/离开"}.
{"Show Ordinary Table","显示普通表"}.
{"Showing apps for <span class='platform-name'>your current platform</span> only. You may also <a href='#' id='show-all-clients-button'>view all apps.</a>","仅显示适用于<span class='platform-name'>您当前平台</span>的应用。您也可以<a href='#' id='show-all-clients-button'>查看所有应用。</a>"}.
{"Showing apps for <span class='platform-name'>your current platform</span> only. You may also <a href='#' id='show-all-clients-button' tabindex=\"{{ tabidx }}\">view all apps.</a>","仅显示适用于<span class='platform-name'>您当前平台</span>的应用。您也可以<a href='#' id='show-all-clients-button' tabindex=\"{{ tabidx }}\">查看所有应用。</a>"}.
{"Show","显示"}.
{"Shut Down Service","关闭服务"}.
{"Siskin IM Logo","Siskin IM 徽标"}.
{"SOCKS5 Bytestreams","SOCKS5 字节流"}.
{"Some XMPP clients can store your password in the computer, but you should do this only in your personal computer for safety reasons.","部分 XMPP 客户端可以将您的密码存储在计算机中,但出于安全考虑,您应该仅在个人计算机中存储密码。"}.
{"Sorry, it looks like this invite code has expired!","抱歉,此邀请码似乎已过期!"}.
@@ -552,10 +573,10 @@
{"Subscription requests must be approved and only subscribers may retrieve items","订阅请求必须得到批准,只有订阅者才能检索项目"}.
{"Subscriptions are not allowed","不允许订阅"}.
{"Sunday","周日"}.
{"Text associated with a picture","与图片相关的文"}.
{"Text associated with a sound","与声音相关的文"}.
{"Text associated with a video","与视频相关的文"}.
{"Text associated with speech","与语音相关的文"}.
{"Text associated with a picture","与图片相关的文"}.
{"Text associated with a sound","与声音相关的文"}.
{"Text associated with a video","与视频相关的文"}.
{"Text associated with speech","与语音相关的文"}.
{"That nickname is already in use by another occupant","该昵称已被其他使用者使用"}.
{"That nickname is registered by another person","该昵称已被其他用户注册"}.
{"The account already exists","此账号已存在"}.
@@ -714,7 +735,7 @@
{"You are being removed from the room because of a system shutdown","由于系统关闭,您将被移出房间"}.
{"You are not allowed to send private messages","不允许您发送私信"}.
{"You are not joined to the channel","您未加入频道"}.
{"You can connect to {{ site_name }} using any XMPP-compatible software. If your preferred software is not listed above, you may still <a href=\"{{ registration_url }}\">register an account manually</a>.","您可以使用任 XMPP 兼容软件连接至 {{ site_name }}。如果您常用的软件未在上方列出,您仍然可以<a href=\"{{ registration_url }}\">手动注册账号</a>。"}.
{"You can connect to {{ site_name }} using any XMPP-compatible software. If your preferred software is not listed above, you may still <a href=\"{{ registration_url }}\" tabindex='{{ tabidx }}'>register an account manually</a>.","您可以使用任何兼容 XMPP 软件连接至 {{ site_name }}。如果您首选的软件未在上方列出,您仍然可以<a href=\"{{ registration_url }}\" tabindex='{{ tabidx }}'>手动注册账号</a>。"}.
{"You can later change your password using an XMPP client.","您之后可以使用 XMPP 客户端更改密码。"}.
{"You can now set up {{ app_name }} and connect it to your new account.","您现在可以设置 {{ app_name }} 并将其连接至您的新账号。"}.
{"You can start chatting right away with {{ app_name }}. Let's get started!","您现在可以使用 {{ app_name }} 开始聊天。开始吧!"}.
+41 -29
View File
@@ -22,8 +22,8 @@
%%% Dependencies
%%%
{deps, [{cache_tab, "~> 1.0.33", {git, "https://github.com/processone/cache_tab", {tag, "1.0.33"}}},
{eimp, "~> 1.0.26", {git, "https://github.com/processone/eimp", {tag, "1.0.26"}}},
{deps, [{cache_tab, "~> 1.0.33", {git, "https://github.com/processone/cache_tab", {tag, "1.0.34"}}},
{eimp, "~> 1.0.26", {git, "https://github.com/processone/eimp", {tag, "1.0.27"}}},
{if_var_true, pam,
{epam, "~> 1.0.14", {git, "https://github.com/processone/epam", {tag, "1.0.14"}}}},
{if_var_true, redis,
@@ -34,20 +34,15 @@
{if_rebar3,
{eredis, "~> 1.7.1", {git, "https://github.com/Nordix/eredis/", {tag, "v1.7.1"}}}
}},
{if_not_rebar3,
{erlydtl, "~> 0.14.0", {git, "https://github.com/sstrigler/erlydtl.git", {tag, "0.14.0-fix.1"}}}
},
{if_rebar3,
{erlydtl, ".*", {git, "https://github.com/erlydtl/erlydtl.git", {branch, "master"}}}
},
{erlydtl, "~> 0.14.0", {git, "https://github.com/erlydtl/erlydtl", {tag, "0.15.0"}}},
{if_var_true, sip,
{esip, "~> 1.0.59", {git, "https://github.com/processone/esip", {tag, "1.0.59"}}}},
{esip, "~> 1.0.59", {git, "https://github.com/processone/esip", {tag, "1.0.60"}}}},
{if_var_true, zlib,
{ezlib, "~> 1.0.15", {git, "https://github.com/processone/ezlib", {tag, "1.0.15"}}}},
{fast_tls, "~> 1.1.25", {git, "https://github.com/processone/fast_tls", {tag, "1.1.25"}}},
{fast_xml, "~> 1.1.57", {git, "https://github.com/processone/fast_xml", {tag, "1.1.57"}}},
{fast_yaml, "~> 1.0.39", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.39"}}},
{idna, "~> 6.0", {git, "https://github.com/benoitc/erlang-idna", {tag, "6.0.0"}}},
{ezlib, "~> 1.0.16", {git, "https://github.com/processone/ezlib", {tag, "1.0.16"}}}},
{fast_tls, "~> 1.1.25", {git, "https://github.com/processone/fast_tls", {tag, "1.1.26"}}},
{fast_xml, "~> 1.1.57", {git, "https://github.com/processone/fast_xml", {tag, "1.1.58"}}},
{fast_yaml, "~> 1.0.39", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.40"}}},
{idna, "~> 7.1", {git, "https://github.com/benoitc/erlang-idna", {tag, "7.1.0"}}},
{if_version_below, "27",
{jiffy, "~> 1.1.1", {git, "https://github.com/davisp/jiffy", {tag, "1.1.1"}}}
},
@@ -58,22 +53,22 @@
{if_var_true, lua,
{luerl, "~> 1.2.0", {git, "https://github.com/rvirding/luerl", {tag, "1.2"}}}
},
{mqtree, "~> 1.0.19", {git, "https://github.com/processone/mqtree", {tag, "1.0.19"}}},
{p1_acme, "~> 1.0.30", {git, "https://github.com/processone/p1_acme", {tag, "1.0.30"}}},
{mqtree, "~> 1.0.19", {git, "https://github.com/processone/mqtree", {tag, "1.0.20"}}},
{p1_acme, "~> 1.0.30", {git, "https://github.com/processone/p1_acme", {tag, "1.0.31"}}},
{if_var_true, mysql,
{p1_mysql, "~> 1.0.27", {git, "https://github.com/processone/p1_mysql", {tag, "1.0.27"}}}},
{p1_mysql, "~> 1.0.28", {git, "https://github.com/processone/p1_mysql", {tag, "1.0.28"}}}},
{p1_oauth2, "~> 0.6.14", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.14"}}},
{if_var_true, pgsql,
{p1_pgsql, "~> 1.1.38", {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.38"}}}},
{p1_utils, "~> 1.0.28", {git, "https://github.com/processone/p1_utils", {tag, "1.0.28"}}},
{p1_pgsql, "~> 1.1.40", {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.41"}}}},
{p1_utils, "~> 1.0.28", {git, "https://github.com/processone/p1_utils", {tag, "1.0.29"}}},
{pkix, "~> 1.0.10", {git, "https://github.com/processone/pkix", {tag, "1.0.10"}}},
{if_var_true, sqlite,
{sqlite3, "~> 1.1.15", {git, "https://github.com/processone/erlang-sqlite3", {tag, "1.1.15"}}}},
{stringprep, "~> 1.0.33", {git, "https://github.com/processone/stringprep", {tag, "1.0.33"}}},
{stringprep, "~> 1.0.33", {git, "https://github.com/processone/stringprep", {tag, "1.0.34"}}},
{if_var_true, stun,
{stun, "~> 1.2.21", {git, "https://github.com/processone/stun", {tag, "1.2.21"}}}},
{xmpp, "~> 1.12.0", {git, "https://github.com/processone/xmpp", {tag, "1.12.0"}}},
{yconf, "~> 1.0.22", {git, "https://github.com/processone/yconf", {tag, "1.0.22"}}}
{stun, "~> 1.2.21", {git, "https://github.com/processone/stun", {tag, "1.2.22"}}}},
{xmpp, "~> 1.13.1", {git, "https://github.com/processone/xmpp", {tag, "1.13.1"}}},
{yconf, "~> 1.0.22", {git, "https://github.com/processone/yconf", {tag, "1.0.23"}}}
]}.
{gitonly_deps, [ejabberd_po]}.
@@ -119,12 +114,12 @@
"src/gen_mod.erl", "src/mod_muc_room.erl",
"src/mod_push.erl", "src/xmpp_socket.erl"]}.
{erl_opts, [nowarn_deprecated_function,
{i, "include"},
{erl_opts, [{i, "include"},
{if_version_below, "26", {d, 'OTP_BELOW_26'}},
{if_version_below, "27", {d, 'OTP_BELOW_27'}},
{if_version_below, "27", {feature, maybe_expr, enable}},
{if_version_below, "28", {d, 'OTP_BELOW_28'}},
{if_version_above, "28", nowarn_deprecated_catch},
{if_var_false, debug, no_debug_info},
{if_var_true, debug, debug_info},
{if_var_true, elixir, {d, 'ELIXIR_ENABLED'}},
@@ -159,8 +154,23 @@
{post, [{compile, {mix, consolidate_protocols}}]}
]}}}.
%% Compiling Jose 1.11.10 with Erlang/OTP 27.0 throws warnings on public_key deprecated functions
{if_rebar3, {overrides, [{del, jose, [{erl_opts, [warnings_as_errors]}]}]}}.
{if_rebar3, {overrides,
[
%% Temporary workaround for some warnings in Erlang/OTP 29
{if_version_above, "28",
{add, [{erl_opts, [nowarn_deprecated_catch]}]}},
{if_version_above, "28",
{add, luerl, [{erl_opts, [nowarn_export_var_subexpr]}]}},
%% Workaround for Warning: variable 'State' is unused
{add, provider_asn1, [{erl_opts, [nowarn_unused_vars]}]},
%% Compiling Jose 1.11.10 with Erlang/OTP 27.0 throws
%% warnings on public_key deprecated functions
{del, jose, [{erl_opts, [warnings_as_errors]}]}
]}}.
{if_not_rebar3,
{overrides, [{del, erlydtl, [{require_otp_vsn, "18"}]}]}
}.
{sub_dirs, ["rel"]}.
@@ -205,12 +215,13 @@
{dialyzer, [{get_warnings, false}, % Show warnings of dependencies
{if_version_above, "25",
{plt_extra_apps,
[asn1, odbc, public_key, stdlib, syntax_tools,
[asn1, public_key, stdlib, syntax_tools,
idna, jose,
cache_tab, eimp, erlydtl, fast_tls, fast_xml, fast_yaml,
mqtree, p1_acme, p1_oauth2, p1_utils, pkix,
stringprep, xmpp, yconf,
{if_version_below, "27", jiffy},
{if_var_true, odbc, odbc},
{if_var_true, pam, epam},
{if_var_true, redis, eredis},
{if_var_true, sip, esip},
@@ -289,7 +300,8 @@
{copy, "test/ejabberd_SUITE_data/ca.pem", "conf/"},
{copy, "test/ejabberd_SUITE_data/cert.pem", "conf/"}]}]}]},
{translations, [{deps, [{ejabberd_po, ".*", {git, "https://github.com/processone/ejabberd-po", {branch, "main"}}}]}]},
{test, [{erl_opts, [nowarn_export_all]}]}]}.
{test, [{erl_opts, [nowarn_export_all]},
{deps, [meck]}]}]}.
{alias, [{relive, [{shell, "--apps ejabberd \
--config rel/relive.config \
+54 -67
View File
@@ -1,90 +1,77 @@
{"1.2.0",
[{<<"base64url">>,{pkg,<<"base64url">>,<<"1.0.1">>},1},
{<<"cache_tab">>,{pkg,<<"cache_tab">>,<<"1.0.33">>},0},
{<<"eimp">>,{pkg,<<"eimp">>,<<"1.0.26">>},0},
[{<<"cache_tab">>,{pkg,<<"cache_tab">>,<<"1.0.34">>},0},
{<<"eimp">>,{pkg,<<"eimp">>,<<"1.0.27">>},0},
{<<"epam">>,{pkg,<<"epam">>,<<"1.0.14">>},0},
{<<"eredis">>,{pkg,<<"eredis">>,<<"1.7.1">>},0},
{<<"erlydtl">>,
{git,"https://github.com/erlydtl/erlydtl.git",
{ref,"aae414692b6052e96d890e03bbeeeca0f4dc01c2"}},
0},
{<<"esip">>,{pkg,<<"esip">>,<<"1.0.59">>},0},
{<<"ezlib">>,{pkg,<<"ezlib">>,<<"1.0.15">>},0},
{<<"fast_tls">>,{pkg,<<"fast_tls">>,<<"1.1.25">>},0},
{<<"fast_xml">>,{pkg,<<"fast_xml">>,<<"1.1.57">>},0},
{<<"fast_yaml">>,{pkg,<<"fast_yaml">>,<<"1.0.39">>},0},
{<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},0},
{<<"jiffy">>,{pkg,<<"jiffy">>,<<"1.1.2">>},1},
{<<"erlydtl">>,{pkg,<<"erlydtl">>,<<"0.14.0">>},0},
{<<"esip">>,{pkg,<<"esip">>,<<"1.0.60">>},0},
{<<"ezlib">>,{pkg,<<"ezlib">>,<<"1.0.16">>},0},
{<<"fast_tls">>,{pkg,<<"fast_tls">>,<<"1.1.26">>},0},
{<<"fast_xml">>,{pkg,<<"fast_xml">>,<<"1.1.58">>},0},
{<<"fast_yaml">>,{pkg,<<"fast_yaml">>,<<"1.0.40">>},0},
{<<"idna">>,{pkg,<<"idna">>,<<"7.1.0">>},0},
{<<"jose">>,{pkg,<<"jose">>,<<"1.11.12">>},0},
{<<"luerl">>,{pkg,<<"luerl">>,<<"1.2.3">>},0},
{<<"mqtree">>,{pkg,<<"mqtree">>,<<"1.0.19">>},0},
{<<"p1_acme">>,{pkg,<<"p1_acme">>,<<"1.0.30">>},0},
{<<"p1_mysql">>,{pkg,<<"p1_mysql">>,<<"1.0.27">>},0},
{<<"mqtree">>,{pkg,<<"mqtree">>,<<"1.0.20">>},0},
{<<"p1_acme">>,{pkg,<<"p1_acme">>,<<"1.0.31">>},0},
{<<"p1_mysql">>,{pkg,<<"p1_mysql">>,<<"1.0.28">>},0},
{<<"p1_oauth2">>,{pkg,<<"p1_oauth2">>,<<"0.6.14">>},0},
{<<"p1_pgsql">>,{pkg,<<"p1_pgsql">>,<<"1.1.38">>},0},
{<<"p1_utils">>,{pkg,<<"p1_utils">>,<<"1.0.28">>},0},
{<<"p1_pgsql">>,{pkg,<<"p1_pgsql">>,<<"1.1.41">>},0},
{<<"p1_utils">>,{pkg,<<"p1_utils">>,<<"1.0.29">>},0},
{<<"pkix">>,{pkg,<<"pkix">>,<<"1.0.10">>},0},
{<<"sqlite3">>,{pkg,<<"sqlite3">>,<<"1.1.15">>},0},
{<<"stringprep">>,{pkg,<<"stringprep">>,<<"1.0.33">>},0},
{<<"stun">>,{pkg,<<"stun">>,<<"1.2.21">>},0},
{<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.1">>},1},
{<<"xmpp">>,{pkg,<<"xmpp">>,<<"1.12.0">>},0},
{<<"yconf">>,{pkg,<<"yconf">>,<<"1.0.22">>},0}]}.
{<<"stringprep">>,{pkg,<<"stringprep">>,<<"1.0.34">>},0},
{<<"stun">>,{pkg,<<"stun">>,<<"1.2.22">>},0},
{<<"xmpp">>,{pkg,<<"xmpp">>,<<"1.13.1">>},0},
{<<"yconf">>,{pkg,<<"yconf">>,<<"1.0.23">>},0}]}.
[
{pkg_hash,[
{<<"base64url">>, <<"F8C7F2DA04CA9A5D0F5F50258F055E1D699F0E8BF4CFDB30B750865368403CF6">>},
{<<"cache_tab">>, <<"E2542AFB34F17EE3CA19D2B0F546A074922C2B99FB6B2ACFB38160D7D0336EC3">>},
{<<"eimp">>, <<"C0B05F32E35629C4D9BCFB832FF879A92B0F92B19844BC7835E0A45635F2899A">>},
{<<"cache_tab">>, <<"935902AC8300D30F17C24F4D4056175F053CCC0572ACAB5FB4D1CE503831BEE1">>},
{<<"eimp">>, <<"14BF9E6FAC791D0C098708CBC5A0928746ED575869F4CE42A266643947790A3A">>},
{<<"epam">>, <<"AA0B85D27F4EF3A756AE995179DF952A0721237E83C6B79D644347B75016681A">>},
{<<"eredis">>, <<"39E31AA02ADCD651C657F39AAFD4D31A9B2F63C6C700DC9CECE98D4BC3C897AB">>},
{<<"esip">>, <<"EB202F8C62928193588091DFEDBC545FE3274C34ECD209961F86DCB6C9EBCE88">>},
{<<"ezlib">>, <<"D74F5DF191784744726A5B1AE9062522C606334F11086363385EB3B772D91357">>},
{<<"fast_tls">>, <<"DA8ED6F05A2452121B087158B17234749F36704C1F2B74DC51DB99A1E27ED5E8">>},
{<<"fast_xml">>, <<"31EFC0F9BCEDA92069704F7A25830407DA5DC3DAD1272B810D6F2E13E73CC11A">>},
{<<"fast_yaml">>, <<"2E71168091949BAB0E5F583B340A99072B4D22D93EB86624E7850A12B1517BE4">>},
{<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>},
{<<"jiffy">>, <<"A9B6C9A7EC268E7CF493D028F0A4C9144F59CCB878B1AFE42841597800840A1B">>},
{<<"erlydtl">>, <<"964B2DC84F8C17ACFAA69C59BA129EF26AC45D2BA898C3C6AD9B5BDC8BA13CED">>},
{<<"esip">>, <<"E5E4DC3458BC1907D4E9DAA2AC702DAB0350BBA9EBA5C39D6B338601FD62B72D">>},
{<<"ezlib">>, <<"8E0F209CB61DB4034A3E7C920F21DD36330BA45884E707746898D5EE0DB487C1">>},
{<<"fast_tls">>, <<"5E46246020F9338C0661254EEE0FF98E8B253A2331B3B51AFB4EB8B0B3CF41FF">>},
{<<"fast_xml">>, <<"1FCFDFB58C4278DC1A163E68D1F83BA165553B1A68D48BF7E9954BB4FBF8571E">>},
{<<"fast_yaml">>, <<"053109ABED40A80FB1E4A231EBFED39E93A4872D73E462FE9A2128503B6233D8">>},
{<<"idna">>, <<"1067A13043538129602D2F2CE6899D8713125C7D19734AA557CE2E3EA55BD4F1">>},
{<<"jose">>, <<"06E62B467B61D3726CBC19E9B5489F7549C37993DE846DFB3EE8259F9ED208B3">>},
{<<"luerl">>, <<"DF25F41944E57A7C4D9EF09D238BC3E850276C46039CFC12B8BB42ECCF36FCB1">>},
{<<"mqtree">>, <<"D769C25F898810725FC7DB0DBFFE5F72098647048B1BE2E6D772F1C2F31D8476">>},
{<<"p1_acme">>, <<"8CE900DAC15E53983B96925FBFF0B519D85322FDC0A7479B7C6701D44BE664C1">>},
{<<"p1_mysql">>, <<"E42EEE7E9329AB762FE6AC9D47D9DC72923936F73A9A0B18F6825E825E44B366">>},
{<<"mqtree">>, <<"AEC4B0299DFE3680014D32FD44069F8273867CE8C7D8F09492D2BEFDDDD98598">>},
{<<"p1_acme">>, <<"D504620B4CB3349C9389F169481C5D88A149A44DEF58F4B52C6F0036CAC314D0">>},
{<<"p1_mysql">>, <<"8CFFDFAE7B3341DCD808FB723A3FCAEEE3E55B9C971B7F75F6B0D7AF6A3151E6">>},
{<<"p1_oauth2">>, <<"1C5F82535574DE87E2059695AC4B91F8F9AEBACBC1C80287DAE6F02552D47AEA">>},
{<<"p1_pgsql">>, <<"1580F16EA95A19F116977D13618B9EB402D8AA169FD86C65AD6F09ACF37BBD74">>},
{<<"p1_utils">>, <<"9A7088A98D788B4C4880FD3C82D0C135650DB13F2E4EF7E10DB179791BC94D59">>},
{<<"p1_pgsql">>, <<"B67CADB9079F809154D829A5EB5374619A60BEFD4DA112FF59A759EA723548E1">>},
{<<"p1_utils">>, <<"E3A8621C2CF5ECDE52065B4C6650803E25FDD9462F92DEC96F29032A163CBB0D">>},
{<<"pkix">>, <<"D3BFADF7B7CFE2A3636F1B256C9CCE5F646A07CE31E57EE527668502850765A0">>},
{<<"sqlite3">>, <<"E819DEFD280145C328457D7AF897D2E45E8E5270E18812EE30B607C99CDD21AF">>},
{<<"stringprep">>, <<"22F42866B4F6F3C238EA2B9CB6241791184DDEDBAB55E94A025511F46325F3CA">>},
{<<"stun">>, <<"735855314AD22CB7816B88597D2F5CA22E24AA5E4D6010A0EF3AFFB33CEED6A5">>},
{<<"unicode_util_compat">>, <<"A48703A25C170EEDADCA83B11E88985AF08D35F37C6F664D6DCFB106A97782FC">>},
{<<"xmpp">>, <<"5A583FE1122F013310147CB7F36BD75DE8A4EF0A836487826BF1A9F53403792E">>},
{<<"yconf">>, <<"52A435F9B60AB1E13950DFE3F7131ECDD8B3D1CA72C44BF66FC74B4571027124">>}]},
{<<"stringprep">>, <<"B9D592C684E540C4CB867485742635EBC4738925BC28F411302DB883BAF44A49">>},
{<<"stun">>, <<"BFACC08BD3724968DE6D2A4ACFA9761232CEBEF9FF6BD9FF8B000E4BB4CE92A4">>},
{<<"xmpp">>, <<"B75831A205A286478AA0DC122B91199AB125E14032E452D1ACA4276EE4D44C42">>},
{<<"yconf">>, <<"1AA5AC72C007B80818DED5B65F76CFC98694B07B2DA18202498EC4A4AEFE191F">>}]},
{pkg_hash_ext,[
{<<"base64url">>, <<"F9B3ADD4731A02A9B0410398B475B33E7566A695365237A6BDEE1BB447719F5C">>},
{<<"cache_tab">>, <<"4258009EB050B22AABE0C848E230BBA58401A6895C58C2FF74DFB635E3C35900">>},
{<<"eimp">>, <<"D96D4E8572B9DFC40F271E47F0CB1D8849373BC98A21223268781765ED52044C">>},
{<<"cache_tab">>, <<"0DB9F317F3941C17C9F8EA8125E25EFA27BBED4CBF24A42C426FADA74C82B692">>},
{<<"eimp">>, <<"3C7E83E293BCFAF50A1BF054FC4B62B7B8C484A6E4218E397709A4D0D857F3FC">>},
{<<"epam">>, <<"2F3449E72885A72A6C2A843F561ADD0FC2F70D7A21F61456930A547473D4D989">>},
{<<"eredis">>, <<"7C2B54C566FED55FEEF3341CA79B0100A6348FD3F162184B7ED5118D258C3CC1">>},
{<<"esip">>, <<"0BDF2E3C349DC0B144F173150329E675C6A51AC473D7A0B2E362245FAAD3FBE6">>},
{<<"ezlib">>, <<"DD14BA6C12521AF5CFE6923E73E3D545F4A0897DC66BFAB5287FBB7AE3962EAB">>},
{<<"fast_tls">>, <<"59E183B5740E670E02B8AA6BE673B5E7779E5FE5BFCC679FE2D4993D1949A821">>},
{<<"fast_xml">>, <<"EEC34E90ADACAFE467D5DDAB635A014DED73B98B4061554B2D1972173D929C39">>},
{<<"fast_yaml">>, <<"24C7B9AB9E2B9269D64E45F4A2A1280966ADB17D31E63365CFD3EE277FB0A78D">>},
{<<"idna">>, <<"92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA">>},
{<<"jiffy">>, <<"BB61BC42A720BBD33CB09A410E48BB79A61012C74CB8B3E75F26D988485CF381">>},
{<<"erlydtl">>, <<"D80EC044CD8F58809C19D29AC5605BE09E955040911B644505E31E9DD8143431">>},
{<<"esip">>, <<"0604CBD4BBC8DA1592C00830CFE30620CE4F665BAB1199D14A3B45FAFD03144F">>},
{<<"ezlib">>, <<"B4819540403D1ECB7EAE645FDFF142A8DB2B476D893288D39BABB92922250405">>},
{<<"fast_tls">>, <<"6B0D4DD2309037565EBAA9ACF39F02808F5F8215A39101A1DA33C2A5B1B59B3F">>},
{<<"fast_xml">>, <<"A36D6E03A398C53BA189E912BF4C7559A3704AC63C5050F126D47414018B4CA0">>},
{<<"fast_yaml">>, <<"27705C29902C1C6F3268BA9BF387D5FA928B2E655B53110FA47B8E476D32B386">>},
{<<"idna">>, <<"6AE959A025BF36DF61A8CAB8508D9654891B5426A84C44D82DEAFFD6DDF8C71F">>},
{<<"jose">>, <<"31E92B653E9210B696765CDD885437457DE1ADD2A9011D92F8CF63E4641BAB7B">>},
{<<"luerl">>, <<"1B4B9D0CA5D7D280D1D2787A6A5EE9F5A212641B62BFF91556BAA53805DF3AED">>},
{<<"mqtree">>, <<"C81065715C49A1882812F80A5AE2D842E80DD3F2D130530DF35990248BF8CE3C">>},
{<<"p1_acme">>, <<"2935E20916B806D3B1166953A4D90A690FD80781096140FE5CA1F65D59EBD68A">>},
{<<"p1_mysql">>, <<"066051F240027A76732547E69D96A0974E70DC14DED9453BEF263221841EDA9B">>},
{<<"mqtree">>, <<"5EC0E07B9C48A7840649600BF96E140B4FBE9E5017E1DDD3E898E15C0A383ED0">>},
{<<"p1_acme">>, <<"9B5922A90AB94DA5889E64DE5CCAD3ADC6E68A6D3C3FB708DD503757B232DB1C">>},
{<<"p1_mysql">>, <<"6E4D00E8ECE505BA5091C8DAB3AEFE415CF2D836FF39508344C3527B366D9919">>},
{<<"p1_oauth2">>, <<"1FD3AC474E43722D9D5A87C6DF8D36F698ED87AF7BB81CBBB66361451D99AE8F">>},
{<<"p1_pgsql">>, <<"06CF6443008178EBA387BA2EE924D3620041BAE4DA1B2C969680284A05CC759E">>},
{<<"p1_utils">>, <<"C49BD44BC4A40AD996691AF826DD7E0AA56D4D0CD730817190A1F84D1A7F0033">>},
{<<"p1_pgsql">>, <<"26EE2ABA3D450464EDD4B3FF9DAB2C6A53FC20BBFD330A6973982B906504A172">>},
{<<"p1_utils">>, <<"2071421CADB5B8FFF114E91D4D944F14851C332391F6E93FC5011AAD64ABE1B9">>},
{<<"pkix">>, <<"E02164F83094CB124C41B1AB28988A615D54B9ADC38575F00F19A597A3AC5D0E">>},
{<<"sqlite3">>, <<"3C0BA4E13322C2AD49DE4E2DDD28311366ADDE54BEAE8DBA9D9E3888F69D2857">>},
{<<"stringprep">>, <<"96F8B30BC50887F605B33B46BCA1D248C19A879319B8C482790E3B4DA5DA98C0">>},
{<<"stun">>, <<"3D7FE8EFB9D05B240A6AA9A6BF8B8B7BFF2D802895D170443C588987DC1E12D9">>},
{<<"unicode_util_compat">>, <<"B3A917854CE3AE233619744AD1E0102E05673136776FB2FA76234F3E03B23642">>},
{<<"xmpp">>, <<"014BAE73659FBA256771EB007BC5348618AC727DD1D10B9AB15A9FEF871622C8">>},
{<<"yconf">>, <<"ACA83457CEABE70756484B5C87BA7B1955F511D499168687EAEAA7C300E857F1">>}]}
{<<"stringprep">>, <<"27E78EA371881764E056FBE845D3AD304740FF5E3131BC1F38A66F0BAA8C9B47">>},
{<<"stun">>, <<"3408B4B11D5237A088F53B260A06940C30B16F4A48094892D6A9204E1643188A">>},
{<<"xmpp">>, <<"A024EEF7CCB4F78B9FB52E37D6385A57BDD150BE75E50D51BBCBAF39A9AB883D">>},
{<<"yconf">>, <<"25B4DFB75328026ACAA774CF5C6FB87D48B55EEBCE630DDAA3362441DAC31437">>}]}
].
+2
View File
@@ -44,6 +44,7 @@ CREATE TABLE rosterusers (
jid text NOT NULL,
nick text NOT NULL,
subscription character(1) NOT NULL,
approved boolean NOT NULL,
ask character(1) NOT NULL,
askmessage text NOT NULL,
server character(1) NOT NULL,
@@ -502,3 +503,4 @@ CREATE TABLE invite_token (
PRIMARY KEY (token)
);
CREATE INDEX i_invite_token_username_server_host ON invite_token(username, server_host);
CREATE INDEX i_invite_token_invitee ON invite_token(invitee);
+2
View File
@@ -40,6 +40,7 @@ CREATE TABLE rosterusers (
jid text NOT NULL,
nick text NOT NULL,
subscription character(1) NOT NULL,
approved boolean NOT NULL,
ask character(1) NOT NULL,
askmessage text NOT NULL,
server character(1) NOT NULL,
@@ -469,3 +470,4 @@ CREATE TABLE invite_token (
PRIMARY KEY (token)
);
CREATE INDEX i_invite_token_username ON invite_token(username);
CREATE INDEX i_invite_token_invitee ON invite_token(invitee);
+1
View File
@@ -324,6 +324,7 @@ CREATE TABLE [dbo].[rosterusers] (
[jid] [varchar] (250) NOT NULL,
[nick] [text] NOT NULL,
[subscription] [char] (1) NOT NULL,
[approved] [smallint] NOT NULL,
[ask] [char] (1) NOT NULL,
[askmessage] [text] NOT NULL,
[server] [char] (1) NOT NULL,
+1
View File
@@ -305,6 +305,7 @@ CREATE TABLE [dbo].[rosterusers] (
[jid] [varchar] (250) NOT NULL,
[nick] [text] NOT NULL,
[subscription] [char] (1) NOT NULL,
[approved] [smallint] NOT NULL,
[ask] [char] (1) NOT NULL,
[askmessage] [text] NOT NULL,
[server] [char] (1) NOT NULL,
+3 -1
View File
@@ -48,6 +48,7 @@ CREATE TABLE rosterusers (
jid varchar(191) NOT NULL,
nick text NOT NULL,
subscription character(1) NOT NULL,
approved boolean NOT NULL,
ask character(1) NOT NULL,
askmessage text NOT NULL,
server character(1) NOT NULL,
@@ -517,7 +518,8 @@ CREATE TABLE invite_token (
expires timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
type character(1) NOT NULL,
account_name text NOT NULL,
PRIMARY KEY (token(191)),
PRIMARY KEY (token(191))
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE INDEX i_invite_token_username USING BTREE ON invite_token(username(191), server_host(191));
CREATE INDEX i_invite_token_invitee USING BTREE ON invite_token(invitee(191));
+2
View File
@@ -44,6 +44,7 @@ CREATE TABLE rosterusers (
jid varchar(191) NOT NULL,
nick text NOT NULL,
subscription character(1) NOT NULL,
approved boolean NOT NULL,
ask character(1) NOT NULL,
askmessage text NOT NULL,
server character(1) NOT NULL,
@@ -486,3 +487,4 @@ CREATE TABLE invite_token (
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE INDEX i_invite_token_username USING BTREE ON invite_token(username(191));
CREATE INDEX i_invite_token_invitee USING BTREE ON invite_token(invitee(191));
+2
View File
@@ -201,6 +201,7 @@ CREATE TABLE rosterusers (
jid text NOT NULL,
nick text NOT NULL,
subscription character(1) NOT NULL,
approved boolean NOT NULL,
ask character(1) NOT NULL,
askmessage text NOT NULL,
server character(1) NOT NULL,
@@ -675,3 +676,4 @@ CREATE TABLE invite_token (
PRIMARY KEY (token)
);
CREATE INDEX i_invite_token_username_server_host ON invite_token USING btree (username, server_host);
CREATE INDEX i_invite_token_invitee ON invite_token USING btree (invitee);
+2
View File
@@ -44,6 +44,7 @@ CREATE TABLE rosterusers (
jid text NOT NULL,
nick text NOT NULL,
subscription character(1) NOT NULL,
approved boolean NOT NULL,
ask character(1) NOT NULL,
askmessage text NOT NULL,
server character(1) NOT NULL,
@@ -490,3 +491,4 @@ CREATE TABLE invite_token (
PRIMARY KEY (token)
);
CREATE INDEX i_invite_token_username ON invite_token USING btree (username);
CREATE INDEX i_invite_token_invitee ON invite_token USING btree (invitee);
+5 -1
View File
@@ -17,6 +17,10 @@
true -> lists:keystore(tools, 1, Terms, {tools, true});
false -> Terms
end,
EZlib = case lists:keyfind(zlib, 1, Terms2) of
{zlib, true} -> [ezlib];
_ -> []
end,
Tools = case lists:keyfind(tools, 1, Terms2) of
{tools, true} -> [observer];
_ -> []
@@ -25,7 +29,7 @@
{[lists:keyfind(description, 1, Terms),
lists:keyfind(vsn, 1, Terms),
{env, [{enabled_backends, EBs}]}
], Elixirs ++ Tools};
], Elixirs ++ Tools ++ EZlib};
_Err ->
{[], []}
end,
+4
View File
@@ -50,6 +50,7 @@ start(normal, _Args) ->
case ejabberd_config:load() of
ok ->
ejabberd_mnesia:start(),
delete_unused_tables(),
file_queue_init(),
maybe_add_nameservers(),
case ejabberd_sup:start_link() of
@@ -166,6 +167,9 @@ delete_pid_file() ->
file:delete(PidFilename)
end.
delete_unused_tables() ->
mnesia:delete_table(muc_occupant_id).
file_queue_init() ->
QueueDir = case ejabberd_option:queue_dir() of
undefined ->
+8 -6
View File
@@ -421,7 +421,9 @@ inline_stream_features(#{lserver := LServer} = State) ->
sasl_mechanisms(Mechs, #{lserver := LServer, stream_encrypted := Encrypted} = State) ->
Type = ejabberd_auth:store_type(LServer),
Mechs1 = ejabberd_option:disable_sasl_mechanisms(LServer),
DisabledMechs = ejabberd_option:disable_sasl_mechanisms(LServer),
PlusMechs = [<<"SCRAM-SHA-1-PLUS">>,<<"SCRAM-SHA-256-PLUS">>,<<"SCRAM-SHA-512-PLUS">>],
PlusDisabled = (PlusMechs -- DisabledMechs) == [],
{Digest, ShaAv, Sha256Av, Sha512Av} =
case ejabberd_option:auth_stored_password_types(LServer) of
@@ -454,9 +456,9 @@ sasl_mechanisms(Mechs, #{lserver := LServer, stream_encrypted := Encrypted} = St
(<<"X-OAUTH2">>) -> [ejabberd_auth_anonymous] /= ejabberd_auth:auth_modules(LServer);
(<<"EXTERNAL">>) -> maps:get(tls_verify, State, false);
(_) -> false
end, Mechs -- Mechs1),
end, Mechs -- DisabledMechs),
case ejabberd_option:auth_password_types_hidden_in_sasl1() of
[] -> Mechs2;
[] -> {Mechs2, Mechs2, PlusDisabled};
List ->
Mechs3 = lists:foldl(
fun(plain, Acc) -> Acc -- [<<"PLAIN">>];
@@ -464,7 +466,7 @@ sasl_mechanisms(Mechs, #{lserver := LServer, stream_encrypted := Encrypted} = St
(scram_sha256, Acc) -> Acc -- [<<"SCRAM-SHA-256">>, <<"SCRAM-SHA-256-PLUS">>];
(scram_sha512, Acc) -> Acc -- [<<"SCRAM-SHA-512">>, <<"SCRAM-SHA-512-PLUS">>]
end, Mechs2, List),
{Mechs3, Mechs2}
{Mechs3, Mechs2, PlusDisabled}
end.
sasl_options(#{lserver := LServer}) ->
@@ -504,10 +506,10 @@ get_fast_tokens_fun(_Mech, #{lserver := LServer}) ->
end
end.
fast_mechanisms(#{lserver := LServer}) ->
fast_mechanisms(#{lserver := LServer} = State) ->
case gen_mod:is_loaded(LServer, mod_auth_fast) of
false -> [];
_ -> mod_auth_fast:get_mechanisms(LServer)
_ -> mod_auth_fast:get_mechanisms(LServer, State)
end.
bind(
+2 -2
View File
@@ -454,7 +454,7 @@ get_version_mark(Note) ->
XXYY = string:join([XX, YY], "."),
case string:find(Note, XXYY) of
nomatch -> "";
_ -> " 🟤"
_ -> " 🟠"
end.
make_command_name(Name, Note) ->
@@ -520,7 +520,7 @@ generate_md_output(File, RegExp, Languages, Cmds) ->
Version = binary_to_list(ejabberd_config:version()),
Header = ["# API Reference\n\n"
"This section describes API commands of ejabberd ", Version, ". "
"The commands that changed in this version are marked with 🟤.\n\n"],
"The commands that changed in this version are marked with 🟠.\n\n"],
Out = lists:map(fun(C) -> gen_doc(C, false, Langs) end, Cmds4),
{ok, Fh} = file:open(File, [write, {encoding, utf8}]),
io:format(Fh, "~ts~ts", [Header, Out]),
+9 -7
View File
@@ -73,7 +73,7 @@ transform(Host, Y, Acc) ->
transform(Host, modules, ModOpts, Acc) ->
{ModOpts1, Acc2} =
lists:mapfoldr(
filtermapfoldr(
fun({Mod, Opts}, Acc1) ->
Opts1 = transform_module_options(Opts),
transform_module(Host, Mod, Opts1, Acc1)
@@ -446,7 +446,7 @@ transform_module(_Host, mod_blocking, Opts, Acc) ->
(_) ->
true
end, Opts),
{{mod_blocking, Opts1}, Acc};
{{true, {mod_blocking, Opts1}}, Acc};
transform_module(_Host, mod_carboncopy, Opts, Acc) ->
Opts1 = lists:filter(
fun({Opt, _}) when Opt == ram_db_type;
@@ -459,7 +459,7 @@ transform_module(_Host, mod_carboncopy, Opts, Acc) ->
(_) ->
true
end, Opts),
{{mod_carboncopy, Opts1}, Acc};
{{true, {mod_carboncopy, Opts1}}, Acc};
transform_module(_Host, mod_http_api, Opts, Acc) ->
Opts1 = lists:filter(
fun({admin_ip_access, _}) ->
@@ -468,7 +468,7 @@ transform_module(_Host, mod_http_api, Opts, Acc) ->
(_) ->
true
end, Opts),
{{mod_http_api, Opts1}, Acc};
{{true, {mod_http_api, Opts1}}, Acc};
transform_module(_Host, mod_http_upload, Opts, Acc) ->
Opts1 = lists:filter(
fun({service_url, _}) ->
@@ -477,7 +477,7 @@ transform_module(_Host, mod_http_upload, Opts, Acc) ->
(_) ->
true
end, Opts),
{{mod_http_upload, Opts1}, Acc};
{{true, {mod_http_upload, Opts1}}, Acc};
transform_module(_Host, mod_pubsub, Opts, Acc) ->
Opts1 = lists:map(
fun({plugins, Plugins}) ->
@@ -505,9 +505,11 @@ transform_module(_Host, mod_pubsub, Opts, Acc) ->
(Opt) ->
Opt
end, Opts),
{{mod_pubsub, Opts1}, Acc};
{{true, {mod_pubsub, Opts1}}, Acc};
transform_module(_Host, mod_muc_occupantid, _Opts, Acc) ->
{false, Acc};
transform_module(_Host, Mod, Opts, Acc) ->
{{Mod, Opts}, Acc}.
{{true, {Mod, Opts}}, Acc}.
strip_odbc_suffix(M) ->
[_|T] = lists:reverse(string:tokens(atom_to_list(M), "_")),
+5 -1
View File
@@ -946,7 +946,11 @@ get_usage_command2(Cmd, C, MaxC, ShCode) ->
end,
NoteEjabberdctlList = case has_list_args(ArgsDefPreliminary) of
true -> [" ", ?B("Note:"), " In a list argument, separate the elements using the , character for example: one,two,three\n\n"];
true -> [" ", ?B("Note:"),
"\n For argument that is a list of elements:",
"\n - To separate the elements use commas: one,two,three"
"\n - To set an empty list in ejabberdctl use two double quotes: \"\""
"\n - To set an empty list in WebAdmin use a single comma: ,\n\n"];
false -> ""
end,
NoteEjabberdctlTuple = case has_tuple_args(ArgsDefPreliminary) of
+3 -3
View File
@@ -80,7 +80,7 @@ man(Lang) ->
["TOP LEVEL OPTIONS",
"-----------------",
"This section describes top level options of ejabberd " ++ Version ++ ".",
"The options that changed in this version are marked with 🟤.",
"The options that changed in this version are marked with 🟠.",
io_lib:nl()] ++
lists:flatmap(
fun(Opt) ->
@@ -101,7 +101,7 @@ man(Lang) ->
"-------",
"[[modules]]",
"This section describes modules options of ejabberd " ++ Version ++ ".",
"The modules that changed in this version are marked with 🟤.",
"The modules that changed in this version are marked with 🟠.",
io_lib:nl()] ++
lists:flatmap(
fun({M, Descr, DocOpts, Backends, Example}) ->
@@ -176,7 +176,7 @@ get_version_mark(#{note := Note}) ->
XXYY = string:join([XX, YY], "."),
case string:find(Note, XXYY) of
nomatch -> "";
_ -> " 🟤"
_ -> " 🟠"
end;
get_version_mark(_) ->
"".
+118 -34
View File
@@ -33,7 +33,7 @@
accept/1, receive_headers/1, recv_file/2,
listen_opt_type/1, listen_options/0,
apply_custom_headers/2]).
-export([get_url/4, get_auto_url/2, get_auto_urls/2, find_handler_port_path/2]).
-export([get_url/4, get_auto_url/2, get_auto_urls/2, find_handler_port_path/2, url_decode_q_split_normalize/1]).
-export([init/3]).
-deprecate({get_auto_url, 2}).
@@ -134,7 +134,7 @@ init(SockMod, Socket, Opts) ->
[Opts]),
?DEBUG("S: ~p~n", [RequestHandlers]),
{ok, RE} = re:compile(<<"^(?:\\[(.*?)\\]|(.*?))(?::(\\d+))?$">>),
{ok, RE} = re:compile(<<"^(?:\\[(.*?)\\]|(.*?))(?::(\\d+))?\s*$">>),
CustomHeaders = proplists:get_value(custom_headers, Opts, []),
@@ -395,16 +395,15 @@ extract_path_query(#state{request_method = Method,
when Method =:= 'GET' orelse
Method =:= 'HEAD' orelse
Method =:= 'DELETE' orelse Method =:= 'OPTIONS' ->
case catch url_decode_q_split_normalize(Path) of
{'EXIT', Error} ->
?DEBUG("Error decoding URL '~p': ~p", [Path, Error]),
{State, false};
try url_decode_q_split_normalize(Path) of
{LPath, Query} ->
LQuery = case catch parse_urlencoded(Query) of
{'EXIT', _Reason} -> [];
LQ -> LQ
LQuery = try parse_urlencoded(Query)
catch _:_ -> []
end,
{State, {LPath, LQuery, <<"">>, Path}}
catch _:Error ->
?DEBUG("Error decoding URL '~p': ~p", [Path, Error]),
{State, false}
end;
extract_path_query(#state{request_method = Method,
request_path = {abs_path, Path},
@@ -413,10 +412,7 @@ extract_path_query(#state{request_method = Method,
sockmod = _SockMod,
socket = _Socket} = State)
when (Method =:= 'POST' orelse Method =:= 'PUT') andalso Len>0 ->
case catch url_decode_q_split_normalize(Path) of
{'EXIT', Error} ->
?DEBUG("Error decoding URL '~p': ~p", [Path, Error]),
{State, false};
try url_decode_q_split_normalize(Path) of
{LPath, _Query} ->
case Method of
'PUT' ->
@@ -424,15 +420,17 @@ extract_path_query(#state{request_method = Method,
'POST' ->
case recv_data(State) of
{ok, Data} ->
LQuery = case catch parse_urlencoded(Data) of
{'EXIT', _Reason} -> [];
LQ -> LQ
LQuery = try parse_urlencoded(Data)
catch _:_ -> []
end,
{State, {LPath, LQuery, Data, Path}};
error ->
{State, false}
end
end
catch _:Error ->
?DEBUG("Error decoding URL '~p': ~p", [Path, Error]),
{State, false}
end;
extract_path_query(State) ->
{State, false}.
@@ -519,7 +517,9 @@ process_request(#state{request_method = Method,
make_text_output(State, Status,
apply_custom_headers(Headers, CustomHeaders), Output);
{Status, Headers, {file, FileName}} ->
make_file_output(State, Status, Headers, FileName);
make_file_output(State, Status, Headers, FileName, []);
{Status, Headers, {file, FileName, ReqHeaders}} ->
make_file_output(State, Status, Headers, FileName, ReqHeaders);
{Status, Reason, Headers, Output}
when is_binary(Output) or is_list(Output) ->
make_text_output(State, Status, Reason,
@@ -685,22 +685,107 @@ make_text_output(State, Status, Reason, Headers, Text) ->
EncodedHdrs = make_headers(State, Status, Reason, Headers, Data2),
[EncodedHdrs, Data2].
make_file_output(State, Status, Headers, FileName) ->
parse_etags(Etags, WeakIgnore) ->
lists:filtermap(
fun(Value) ->
case string:trim(Value) of
<<"W/\"", _Rest/binary>> when WeakIgnore ->
false;
<<"W/\"", Rest/binary>> ->
case string:split(Rest, <<"\"">>, trailing) of
[Etag, _] -> {true, Etag};
_ -> false
end;
<<"\"", Rest/binary>> ->
case string:split(Rest, <<"\"">>, trailing) of
[Etag, _] -> {true, Etag};
_ -> false
end;
<<"*">> -> true;
_ -> false
end
end,
string:split(Etags, <<",">>, all)).
process_etags(Etag, RequestHeaders) ->
process_etags(Etag, RequestHeaders, if_match).
process_etags(Etag, RequestHeaders, if_match) ->
case lists:keyfind('If-Match', 1, RequestHeaders) of
{_, Header} ->
Etags = parse_etags(Header, true),
case lists:any(fun(V) -> V == <<"*">> orelse V == Etag end, Etags) of
true -> process_etags(Etag, RequestHeaders, if_none_match);
_ -> {true, 412}
end;
_ ->
process_etags(Etag, RequestHeaders, if_none_match)
end;
process_etags(Etag, RequestHeaders, if_none_match) ->
case lists:keyfind('If-None-Match', 1, RequestHeaders) of
{_, Header} ->
Etags = parse_etags(Header, false),
case lists:any(fun(V) -> V == <<"*">> orelse V == Etag end, Etags) of
true -> {true, 304};
_ -> false
end;
_ ->
false
end.
process_if_modified_since(MTime, RequestHeaders) ->
case lists:keyfind('If-Modified-Since', 1, RequestHeaders) of
{_, Header} ->
case httpd_util:convert_request_date(binary_to_list(Header)) of
bad_date ->
false;
LM ->
T1 = calendar:datetime_to_gregorian_seconds(
calendar:universal_time_to_local_time(LM)),
T2 = calendar:datetime_to_gregorian_seconds(MTime),
case T1 >= T2 of
true ->
{true, 304};
_-> false
end
end;
_ ->
false
end.
make_file_output(State, Status, Headers, FileName, RequestHeaders) ->
case file:read_file_info(FileName) of
{ok, #file_info{size = Size}} when State#state.request_method == 'HEAD' ->
make_headers(State, Status, <<"">>, Headers, Size);
{ok, #file_info{size = Size}} ->
case file:open(FileName, [raw, read]) of
{ok, Fd} ->
EncodedHdrs = make_headers(State, Status, <<"">>, Headers, Size),
send_text(State, EncodedHdrs),
send_file(State, Fd, Size, FileName),
file:close(Fd),
none;
{error, Why} ->
Reason = file_format_error(Why),
?ERROR_MSG("Failed to open ~ts: ~ts", [FileName, Reason]),
make_text_output(State, 404, Reason, [], <<>>)
{ok, #file_info{size = Size, mtime = MTime} = FI} ->
Etag = list_to_binary(httpd_util:create_etag(FI)),
ExtraHeaders = [{<<"Last-Modified">>, httpd_util:rfc1123_date(MTime)},
{<<"ETag">>, Etag}],
case process_etags(Etag, RequestHeaders) of
false ->
case process_if_modified_since(MTime, RequestHeaders) of
false ->
if
State#state.request_method == 'HEAD' ->
make_headers(State, Status, <<"">>, ExtraHeaders ++ Headers, Size);
true ->
case file:open(FileName, [raw, read]) of
{ok, Fd} ->
EncodedHdrs = make_headers(State, Status, <<"">>, ExtraHeaders ++ Headers, Size),
send_text(State, EncodedHdrs),
send_file(State, Fd, Size, FileName),
file:close(Fd),
none;
{error, Why} ->
Reason = file_format_error(Why),
?ERROR_MSG("Failed to open ~ts: ~ts", [FileName, Reason]),
make_text_output(State, 404, Reason, [], <<>>)
end
end;
{_, NewStatus} ->
make_headers(State, NewStatus, <<"">>, ExtraHeaders ++ Headers, 0)
end;
{_, NewStatus} ->
make_headers(State, NewStatus, <<"">>, ExtraHeaders ++ Headers, 0)
end;
{error, Why} ->
Reason = file_format_error(Why),
@@ -861,8 +946,7 @@ parse_urlencoded(<<$=, Tail/binary>>, _Last, Cur, key) ->
parse_urlencoded(<<H, Tail/binary>>, Last, Cur, State) ->
parse_urlencoded(Tail, Last, <<Cur/binary, H>>, State);
parse_urlencoded(<<>>, Last, Cur, _State) ->
[{Last, Cur}];
parse_urlencoded(undefined, _, _, _) -> [].
[{Last, Cur}].
apply_custom_headers(Headers, CustomHeaders) ->
{Doctype, Headers2} = case Headers -- [html] of
+1 -1
View File
@@ -1013,7 +1013,7 @@ doc() ->
"serving several XMPP domains and/or changing domains from "
"time to time. This avoid need to manage several databases and "
"handle complex configuration changes. The default depends on "
"_`../install/source.md#configure|./configure`_ "
"_`../../admin/install/source.md#configure|./configure`_ "
"flag '--enable-sql-schema-multihost' which is set "
"at compile time."),
[binary:part(ejabberd_config:version(), {0,5})]}}},
+4 -1
View File
@@ -526,7 +526,10 @@ format_column_def(SchemaInfo, Column) ->
[<<" ">>,
escape_name(SchemaInfo, Column#sql_column.name), <<" ">>,
format_type(SchemaInfo, Column),
<<" NOT NULL">>,
case Column#sql_column.nullable of
false -> <<" NOT NULL">>;
true -> []
end,
case Column#sql_column.default of
false -> [];
true ->
+32 -3
View File
@@ -34,7 +34,7 @@
loaded_modules/1, loaded_modules_with_opts/1,
get_hosts/2, get_module_proc/2, is_loaded/2, is_loaded_elsewhere/2,
start_modules/0, start_modules/1, stop_modules/0, stop_modules/1,
db_mod/2, ram_db_mod/2]).
db_mod/2, ram_db_mod/2, depend_on/2]).
-export([validate/2]).
%% Deprecated functions
@@ -367,7 +367,7 @@ prep_stop_module_keep_config(Host, Module) ->
-spec stop_module_keep_config(binary(), atom()) -> error | ok.
stop_module_keep_config(Host, Module) ->
?DEBUG("Stopping ~ts at ~ts", [Module, Host]),
?INFO_MSG("Stopping ~ts at ~ts", [Module, Host]),
Registrations =
case ets:lookup(ejabberd_modules, {Module, Host}) of
[M] ->
@@ -421,6 +421,11 @@ del_registrations(Host, Module, Registrations) ->
lists:foreach(
fun({hook, Hook, Function, Seq}) ->
ejabberd_hooks:delete(Hook, Host, Module, Function, Seq);
({hook, Hook, Function, Seq, global}) when is_integer(Seq) ->
case is_loaded_elsewhere(Host, Module) of
false -> ejabberd_hooks:delete(Hook, global, Module, Function, Seq);
true -> ok
end;
({hook, Hook, Function, Seq, Host1}) when is_integer(Seq) ->
ejabberd_hooks:delete(Hook, Host1, Module, Function, Seq);
({hook, Hook, Module1, Function, Seq}) when is_integer(Seq) ->
@@ -537,6 +542,10 @@ get_module_proc(Host, Base) ->
<<(erlang:atom_to_binary(Base, latin1))/binary, "_", Host/binary>>,
latin1).
-spec depend_on([{atom(), atom()}], fun(([term()]) -> term())) -> term().
depend_on(Dependant, Fun) ->
{depend_on, Fun, Dependant}.
-spec is_loaded(binary(), atom()) -> boolean().
is_loaded(Host, Module) ->
ets:member(ejabberd_modules, {Module, Host}).
@@ -611,10 +620,30 @@ validator(Host) ->
fun({Mod, Opts}) ->
{Mod, validator(Host, Mod, Opts)}
end, L)),
Validator = econf:options(Validators, [unique]),
Validator = econf:and_then(econf:options(Validators, [unique]),
fun resolve_depend_on/1),
Validator(L)
end)}).
-spec resolve_depend_on([{atom(), map()}]) -> [{atom(), map()}].
resolve_depend_on(Values) ->
lists:map(
fun({Mod, Opts}) ->
{Mod, maps:map(
fun(_K, {depend_on, Fun, ExtraKeys}) when is_function(Fun) ->
Resolved = lists:map(
fun({DepMod, DepOpt}) ->
case proplists:get_value(DepMod, Values, #{}) of
#{DepOpt := Val} ->
Val;
_ -> undefined
end
end, ExtraKeys),
Fun(Resolved);
(_, V) -> V
end, Opts)}
end, Values).
-spec validator(binary(), module(), [{atom(), term()}]) -> econf:validator().
validator(Host, Module, Opts) ->
{Required, {DefaultOpts1, Validators}} =
+5 -1
View File
@@ -195,7 +195,11 @@ get_mucsub_event_type(_Packet) ->
is_standalone_chat_state(Stanza) ->
case unwrap_carbon(Stanza) of
#message{body = [], subject = [], sub_els = Els} ->
IgnoreNS = [?NS_CHATSTATES, ?NS_DELAY, ?NS_EVENT, ?NS_HINTS],
IgnoreNS = [?NS_CHATSTATES,
?NS_DELAY,
?NS_EVENT,
?NS_HINTS,
?NS_SID_0],
Stripped = [El || El <- Els,
not lists:member(xmpp:get_ns(El), IgnoreNS)],
Stripped == [];
+19 -5
View File
@@ -30,7 +30,7 @@
-export([mod_doc/0]).
%% Hooks
-export([c2s_inline_features/3, c2s_handle_sasl2_inline/1,
get_tokens/3, get_mechanisms/1, remove_user_tokens/2]).
get_tokens/3, get_mechanisms/2, remove_user_tokens/2]).
-include_lib("xmpp/include/xmpp.hrl").
-include_lib("xmpp/include/scram.hrl").
@@ -117,8 +117,22 @@ mod_doc() ->
" mod_auth_fast:",
" token_lifetime: 14days"]}.
get_mechanisms(_LServer) ->
[<<"HT-SHA-256-NONE">>, <<"HT-SHA-256-UNIQ">>, <<"HT-SHA-256-EXPR">>, <<"HT-SHA-256-ENDP">>].
get_mechanisms(_LServer, #{sasl_channel_bindings := Bindings}) ->
[<<"HT-SHA-256-NONE">>] ++
case Bindings of
#{<<"tls-unique">> := _} -> [<<"HT-SHA-256-UNIQ">>];
_ -> []
end ++
case Bindings of
#{<<"tls-exporter">> := _} -> [<<"HT-SHA-256-EXPR">>];
_ -> []
end ++
case Bindings of
#{<<"tls-server-end-point">> := _} -> [<<"HT-SHA-256-ENDP">>];
_ -> []
end;
get_mechanisms(_LServer, _State) ->
[<<"HT-SHA-256-NONE">>].
ua_hash(UA) ->
crypto:hash(sha256, UA).
@@ -131,8 +145,8 @@ get_tokens(LServer, LUser, UA) ->
{{Type, CreatedAt < ToRefresh}, Token}
end, Mod:get_tokens(LServer, LUser, ua_hash(UA))).
c2s_inline_features({Sasl, Bind, Extra}, Host, _State) ->
{Sasl ++ [#fast{mechs = get_mechanisms(Host)}], Bind, Extra}.
c2s_inline_features({Sasl, Bind, Extra}, Host, State) ->
{Sasl ++ [#fast{mechs = get_mechanisms(Host, State)}], Bind, Extra}.
gen_token(#{sasl2_ua_id := UA, server := Server, user := User}) ->
Mod = gen_mod:db_mod(Server, ?MODULE),
+35 -10
View File
@@ -107,8 +107,8 @@ filter_subscription(Acc, #presence{from = From, to = To, lang = Lang,
case mod_block_strangers_opt:drop(LServer) andalso
mod_block_strangers_opt:captcha(LServer) andalso
need_check(Pres) of
true ->
case check_subscription(From, To) of
{true, Origin} ->
case check_subscription(Origin, To) of
false ->
BFrom = jid:remove_resource(From),
BTo = jid:remove_resource(To),
@@ -164,18 +164,24 @@ handle_captcha_result(captcha_failed, #presence{lang = Lang} = Pres) ->
check_message(#message{from = From, to = To, lang = Lang} = Msg) ->
LServer = To#jid.lserver,
case need_check(Msg) of
true ->
case check_subscription(From, To) of
{true, Origin} ->
case check_subscription(Origin, To) of
false ->
Drop = mod_block_strangers_opt:drop(LServer),
Log = mod_block_strangers_opt:log(LServer),
if
Log ->
Log andalso Origin == From ->
?INFO_MSG("~ts message from stranger ~ts to ~ts",
[if Drop -> "Rejecting";
true -> "Allow"
end,
jid:encode(From), jid:encode(To)]);
Log ->
?INFO_MSG("~ts message from stranger ~ts to ~ts (via ~ts)",
[if Drop -> "Rejecting";
true -> "Allow"
end,
jid:encode(Origin), jid:encode(To), jid:encode(From)]);
true ->
ok
end,
@@ -202,7 +208,7 @@ maybe_adjust_from(#message{type = groupchat, from = From} = Msg) ->
maybe_adjust_from(#message{} = Msg) ->
Msg.
-spec need_check(presence() | message()) -> boolean().
-spec need_check(presence() | message()) -> {true, #jid{}} | false.
need_check(Pkt) ->
To = xmpp:get_to(Pkt),
From = xmpp:get_from(Pkt),
@@ -218,10 +224,29 @@ need_check(Pkt) ->
IsError = (error == xmpp:get_type(Pkt)),
AllowLocalUsers = mod_block_strangers_opt:allow_local_users(LServer),
Access = mod_block_strangers_opt:access(LServer),
not (IsSelf orelse IsEmpty orelse IsError
orelse acl:match_rule(LServer, Access, From) == allow
orelse ((AllowLocalUsers orelse From#jid.luser == <<"">>)
andalso ejabberd_router:is_my_host(From#jid.lserver))).
NeedCheck = not (IsSelf orelse IsEmpty orelse IsError
orelse acl:match_rule(LServer, Access, From) == allow
orelse ((AllowLocalUsers orelse From#jid.luser == <<"">>)
andalso ejabberd_router:is_my_host(From#jid.lserver))),
case {NeedCheck, Pkt} of
{false, _} ->
false;
{_, #message{}} ->
case xmpp:get_subtag(Pkt, #muc_user{}) of
#muc_user{invites = [#muc_invite{from = #jid{} = InvFrom}]}->
case acl:match_rule(LServer, Access, InvFrom) == allow orelse
(AllowLocalUsers andalso ejabberd_router:is_my_host(InvFrom#jid.lserver)) of
true ->
false;
_ ->
{true, InvFrom}
end;
_ ->
{true, From}
end;
_ ->
{true, From}
end.
-spec check_subscription(jid(), jid()) -> boolean().
check_subscription(From, To) ->
+41 -6
View File
@@ -54,11 +54,14 @@ reload(_Host, _NewOpts, _OldOpts) ->
depends(_Host, _Opts) ->
[].
process(LocalPath, #request{auth = Auth, path = Path} = Request) ->
process(LocalPath, #request{auth = Auth, path = Path, opts = Opts} = Request) ->
AutologinPath = lists:member(?AUTOLOGIN_PATH, Path),
case {AutologinPath, Auth} of
{true, undefined} ->
HasWebsocket = has_websocket(Opts),
case {AutologinPath, Auth, HasWebsocket} of
{true, undefined, _} ->
ejabberd_web:error(not_found);
{true, _, false} ->
process_websocket();
_ ->
process2(LocalPath, Request)
end.
@@ -66,7 +69,7 @@ process(LocalPath, #request{auth = Auth, path = Path} = Request) ->
process2([], #request{method = 'GET', host = Host, auth = Auth, raw_path = RawPath1}) ->
[RawPath | _] = string:split(RawPath1, "?"),
ExtraOptions = get_auth_options(Host)
++ get_autologin_options(Auth)
++ get_autologin_options(Auth, Host)
++ get_register_options(Host)
++ get_extra_options(Host),
Domain = mod_conversejs_opt:default_domain(Host),
@@ -117,6 +120,29 @@ process2(LocalPath, #request{host = Host}) ->
false -> ejabberd_web:error(not_found)
end.
%%----------------------------------------------------------------------
%% WebSocket
%%----------------------------------------------------------------------
has_websocket(Opts) ->
maybe
{_, Handlers} ?= lists:keyfind(request_handlers, 1, Opts),
true ?= lists:keymember(ejabberd_web_admin, 2, Handlers),
true ?= lists:keymember(ejabberd_http_ws, 2, Handlers)
else
_ -> false
end.
process_websocket() ->
{200, [html],
[<<"<!DOCTYPE html>">>,
<<"<html><body>">>,
<<"<p>To use Conversejs, please enable WebSocket as a request_handler in this port, like:</p>">>,
<<"<pre> request_handlers:</pre>">>,
<<"<pre> /admin: ejabberd_web_admin</pre>">>,
<<"<pre> /websocket: ejabberd_http_ws</pre>">>,
<<"</body></html>">>]}.
%%----------------------------------------------------------------------
%% File server
%%----------------------------------------------------------------------
@@ -196,9 +222,15 @@ get_auth_options(Domain) ->
{<<"jid">>, Domain}]
end.
get_autologin_options({Jid, Password}) ->
get_autologin_options({Jid1, Password}, Host) ->
Jid = case jid:decode(Jid1) of
#jid{luser = <<>>} ->
jid:encode(jid:make(Jid1, Host));
_ ->
Jid1
end,
[{<<"auto_login">>, <<"true">>}, {<<"jid">>, Jid}, {<<"password">>, Password}];
get_autologin_options(undefined) ->
get_autologin_options(undefined, _) ->
[].
get_register_options(Server) ->
@@ -344,6 +376,9 @@ mod_doc() ->
"are enabled in at least one 'request_handlers'."), "",
?T("When 'conversejs_css' and 'conversejs_script' are 'auto', "
"by default they point to the public Converse client."), "",
?T("When this module is enabled in 'modules', "
"it adds automatically a requesthandler and link in WebAdmin. "
"."), "",
?T("This module is available since ejabberd 21.12.")
],
note => "improved in 25.07",
+5 -4
View File
@@ -559,13 +559,14 @@ json_error(HTTPCode, JSONCode, Message) ->
log(Call, Args, {Addr, Port}) ->
AddrS = misc:ip_to_list({Addr, Port}),
?INFO_MSG("API call ~ts ~p from ~ts:~p", [Call, hide_sensitive_args(Args), AddrS, Port]);
?INFO_MSG("API call ~ts ~p from ~ts", [Call, hide_sensitive_args(Args),
ejabberd_config:may_hide_data(<<AddrS/binary, ":", (integer_to_binary(Port))/binary>>)]);
log(Call, Args, IP) ->
?INFO_MSG("API call ~ts ~p (~p)", [Call, hide_sensitive_args(Args), IP]).
?INFO_MSG("API call ~ts ~p (~p)", [Call, hide_sensitive_args(Args), ejabberd_config:may_hide_data(IP)]).
hide_sensitive_args(Args=[_H|_T]) ->
lists:map(fun({<<"password">>, Password}) -> {<<"password">>, ejabberd_config:may_hide_data(Password)};
({<<"newpass">>,NewPassword}) -> {<<"newpass">>, ejabberd_config:may_hide_data(NewPassword)};
lists:map(fun({<<"password">>, _Password}) -> {<<"password">>, <<"hidden_by_ejabberd">>};
({<<"newpass">>, _NewPassword}) -> {<<"newpass">>, <<"hidden_by_ejabberd">>};
(E) -> E end,
Args);
hide_sensitive_args(NonListArgs) ->
+4 -3
View File
@@ -561,7 +561,7 @@ process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP,
[encode_addr(IP), Host, Error]),
http_response(500)
end;
process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request0)
process(_LocalPath, #request{method = Method, host = Host, ip = IP, headers = ReqHeaders} = Request0)
when Method == 'GET';
Method == 'HEAD' ->
Request = Request0#request{host = redecode_url(Host)},
@@ -584,7 +584,7 @@ process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request0)
end,
Headers2 = [{<<"Content-Type">>, ContentType} | Headers1],
Headers3 = ejabberd_http:apply_custom_headers(Headers2, CustomHeaders),
http_response(200, Headers3, {file, Path});
http_response(200, Headers3, {file, Path, ReqHeaders});
{error, eacces} ->
?WARNING_MSG("Cannot serve ~ts to ~ts: Permission denied",
[Path, encode_addr(IP)]),
@@ -1070,7 +1070,8 @@ http_response(Code, ExtraHeaders) ->
Message = <<(code_to_message(Code))/binary, $\n>>,
http_response(Code, ExtraHeaders, Message).
-type http_body() :: binary() | {file, file:filename_all()}.
-type http_body() :: binary() | {file, file:filename_all()} |
{file, file:filename_all(), [{binary(), binary()}]}.
-spec http_response(100..599, [{binary(), binary()}], http_body())
-> {pos_integer(), [{binary(), binary()}], http_body()}.
http_response(Code, ExtraHeaders, Body) ->
+255 -77
View File
@@ -43,18 +43,19 @@
-export([get_local_identity/5, get_local_features/5]).
%% commands
-export([cleanup_expired/0, expire_tokens/2, gen_invite/1, gen_invite/2, list_invites/1]).
-export([cleanup_expired/0, expire_tokens/2, generate_invite/1, generate_invite/2, list_invites/1]).
%% helpers
-export([create_account_allowed/2, get_invite/2, get_invites/2, get_max_invites/2, is_create_allowed/2,
is_expired/1, is_reserved/3, is_token_valid/2, roster_add/2, send_presence/3,
set_invitee/3, set_invitee/5, token_uri/1, xdata_field/3]).
-export([create_account_allowed/2, get_invite/2, get_invites_tree_t/2, get_max_invites/2,
is_create_allowed/2, is_expired/1, is_reserved/3, is_token_valid/2, roster_add/2,
send_presence/3, set_invitee/3, set_invitee/5, token_uri/1, transaction/2, xdata_field/3]).
%% ejabberd_http
-export([process/2]).
-ifdef(TEST).
-export([create_roster_invite/2, create_account_invite/4, is_token_valid/3]).
-export([create_roster_invite/2, create_account_invite/4, find_invites_tree_root_t/4, gen_invite/1,
gen_invite/2, get_invites/2, get_invites_tree_as_root_t/2, is_token_valid/3]).
-endif.
-include("logger.hrl").
@@ -66,13 +67,16 @@
-include("translate.hrl").
-type invite_token() :: #invite_token{}.
-export_type([invite_token/0]).
-callback cleanup_expired(Host :: binary()) -> non_neg_integer().
-callback create_invite(Invite :: invite_token()) -> invite_token().
-callback create_invite_t(Invite :: invite_token()) -> invite_token().
-callback expire_tokens(User :: binary(), Server :: binary()) -> non_neg_integer().
-callback get_invite(Host :: binary(), Token :: binary()) ->
invite_token() | {error, not_found}.
-callback get_invites(Host :: binary(), Inviter :: {User :: binary(), Host :: binary()}) ->
-callback get_invite_by_invitee_t(Host :: binary(), InviteeJid :: binary()) ->
invite_token() | {error, not_found}.
-callback get_invites_t(Host :: binary(), Inviter :: {User :: binary(), Host :: binary()}) ->
[invite_token()].
-callback init(Host :: binary(), gen_mod:opts()) -> any().
-callback is_reserved(Host :: binary(), Token :: binary(), User :: binary()) -> boolean().
@@ -85,6 +89,7 @@
Invitee :: binary(),
AccountName :: binary()) -> OkOrError | {error, conflict}
when OkOrError :: ok | {error, term()}.
-callback transaction(Host:: binary(), fun(() -> T)) -> {atomic, T} | {aborted, any()}.
%% @format-begin
@@ -124,28 +129,16 @@ mod_doc() ->
"then guide the recipient with setting up a client "
"and creating an account if required."),
"",
?T("In order to use the included landing page feature, you have to"),
?T("In order to use the included landing page feature, you have to"
" set `landing_page` to either `auto` or an URL template like "
"`https://{{ host }}/invites/{{ invite.token }}` "
" if your server setup includes a so called reverse proxy."),
"",
?T(" * have a copy of https://jquery.com[jQuery 3] and "
" https://getbootstrap.com/docs/4.6/getting-started/introduction/[Bootstrap 4] "
" in a shared directory on your system. If you're using Debian or "
" derivatives this is easiest accomplished by installing both "
" `libjs-jquery` and `libjs-bootstrap4` which will put them under "
" `/usr/share/javascript/{jquery,bootstrap4}`"),
?T(" * in `ejabberd.yml` configure a listener for module `ejabberd_http` "
" with a request handler for `/share: mod_http_fileserver`"),
?T(" * in the `modules` section configure `mod_http_fileserver` so that "
" `docroot` points to the shared directory from above "
" (e.g. `docroot: /usr/share/javascript`)"),
?T(" * configure `mod_invites` and set `landing_page` to either `auto` "
" or an URL template like `https://{{ host }}/invites/{{ invite.token }}` "
" if your server setup includes a so called reverse proxy"),
"",
"If you'd rather want to use an external service, set `landing_page` "
"to something like "
"`http://{{ host }}:8080/easy-xmpp-invites/#{{ invite.uri|strip_protocol }}` "
"or `https://invites.joinjabber.org/#{{ invite.uri|strip_protocol }}`."],
note => "added in 26.01",
?T("If you'd rather want to use an external service, set `landing_page` "
"to something like "
"`http://{{ host }}:8080/easy-xmpp-invites/#{{ invite.uri|strip_protocol }}` "
"or `https://invites.joinjabber.org/#{{ invite.uri|strip_protocol }}`.")],
note => "improved in 26.03",
opts =>
[{access_create_account,
#{value => ?T("Access Rule Name"),
@@ -186,7 +179,7 @@ mod_doc() ->
?T("A human readable name for your site. E.g. `\"My Beautiful Laundrette\"`. "
"Used in landing page templates.")}},
{templates_dir,
#{value => ?T("binary()"),
#{value => ?T("Path"),
desc =>
?T("The directory containing templates and static files used "
"for landing page and web registration form. Only needs to "
@@ -196,7 +189,14 @@ mod_doc() ->
#{value => "pos_integer()",
desc =>
?T("Number of seconds until token expires. Default value "
"is `432000` (that is five days: `5 * 24 * 60 * 60`)")}}],
"is `432000` (that is five days: `5 * 24 * 60 * 60`)")}},
{webchat_url,
#{value => "none | auto | Webchat URL",
note => "added in 26.03",
desc =>
?T("URL to a webchat client. Upon manual registration through web-form this will be "
"recommended in order to get started. If `auto` is chosen, we pick the "
"`mod_conversejs` from the listeners section. Default is `auto`.")}}],
example =>
[{?T("Basic configuration with landing page but without creating "
"accounts, just roster invites:"),
@@ -206,11 +206,8 @@ mod_doc() ->
" module: ejabberd_http",
" request_handlers:",
" /invites: mod_invites",
" /share: mod_http_fileserver",
"# [...]",
"modules:",
" mod_http_fileserver:",
" docroot: /usr/share/javascript",
" mod_invites:",
" landing_page: auto"]},
{?T("To allow only admin users to create invites of 'create account' and "
@@ -248,6 +245,7 @@ mod_doc() ->
" allow_modules:",
" - mod_invites"]}]}.
-spec mod_options(binary()) -> [{landing_page, none | auto | binary()} | {atom(), any()}].
mod_options(Host) ->
[{access_create_account, none},
{db_type, ejabberd_config:default_db(Host, ?MODULE)},
@@ -255,7 +253,8 @@ mod_options(Host) ->
{max_invites, infinity},
{site_name, Host},
{templates_dir, filename:join([code:priv_dir(ejabberd), ?MODULE, <<>>])},
{token_expire_seconds, ?INVITE_TOKEN_EXPIRE_SECONDS_DEFAULT}].
{token_expire_seconds, ?INVITE_TOKEN_EXPIRE_SECONDS_DEFAULT},
{webchat_url, auto}].
reload(ServerHost, NewOpts, OldOpts) ->
NewMod = gen_mod:db_mod(NewOpts, ?MODULE),
@@ -290,7 +289,24 @@ mod_opt_type(access_create_account) ->
mod_opt_type(db_type) ->
econf:db_type(?MODULE);
mod_opt_type(landing_page) ->
econf:either(none, econf:binary());
econf:either(
econf:enum([none, auto]),
econf:and_then(
econf:binary(),
fun(Tmpl) ->
try mod_invites_http:tmpl_to_renderer(Tmpl) of
R when is_atom(R) ->
Tmpl
catch
error:{badmatch, error} ->
T = <<"There is some problem in the value you configured "
"for option 'landing_page' in 'mod_invites'. "
"Please consult the documentation and fix it: ",
Tmpl/binary>>,
?CRITICAL_MSG(T, []),
throw({error, T})
end
end));
mod_opt_type(max_invites) ->
econf:pos_int(infinity);
mod_opt_type(site_name) ->
@@ -298,7 +314,10 @@ mod_opt_type(site_name) ->
mod_opt_type(templates_dir) ->
econf:directory();
mod_opt_type(token_expire_seconds) ->
econf:pos_int().
econf:pos_int();
mod_opt_type(webchat_url) ->
econf:either(
econf:enum([none, auto]), econf:url()).
%%--------------------------------------------------------------------
%%| ejabberd command callbacks
@@ -329,7 +348,7 @@ get_commands_spec() ->
tags = [accounts],
desc = "Create a new 'create account' invite",
module = ?MODULE,
function = gen_invite,
function = generate_invite,
note = "added in 26.01",
args = [{host, binary}],
args_desc = ["Hostname to generate 'create account' invite for."],
@@ -344,7 +363,7 @@ get_commands_spec() ->
"Create a new 'create account' invite token with a preselected "
"username",
module = ?MODULE,
function = gen_invite,
function = generate_invite,
note = "added in 26.01",
args = [{username, binary}, {host, binary}],
args_desc =
@@ -397,18 +416,28 @@ cleanup_expired() ->
expire_tokens(User0, Server0) ->
User = jid:nodeprep(User0),
Server = jid:nameprep(Server0),
db_call(Server, expire_tokens, [User, Server]).
pretty_format_command_result(try_db_call(Server, expire_tokens, [User, Server])).
-spec generate_invite(binary()) -> binary() | {error, any()}.
generate_invite(Host) ->
generate_invite(<<>>, Host).
-spec generate_invite(binary(), binary()) -> binary() | {error, any()}.
generate_invite(AccountName, Host) ->
pretty_format_command_result(gen_invite(AccountName, Host)).
-ifdef(TEST).
-spec gen_invite(binary()) -> binary() | {error, any()}.
gen_invite(Host) ->
gen_invite(<<>>, Host).
-endif.
-spec gen_invite(binary(), binary()) -> binary() | {error, any()}.
gen_invite(AccountName, Host0) ->
Host = jid:nameprep(Host0),
case create_account_invite(Host, {<<>>, Host}, AccountName, false) of
{error, {module_not_loaded, ?MODULE, Host}} ->
{error, host_unknown};
{error, _Reason} = Error ->
Error;
Invite ->
@@ -416,29 +445,32 @@ gen_invite(AccountName, Host0) ->
end.
list_invites(Host) ->
Invites = db_call(Host, list_invites, [Host]),
Format =
fun(#invite_token{token = TO,
inviter = {IU, IS},
invitee = IE,
created_at = CA,
expires = Exp,
type = TY,
account_name = AN} =
Invite) ->
{TO,
is_token_valid(Host, TO),
encode_datetime(CA),
encode_datetime(Exp),
TY,
jid:encode(
jid:make(IU, IS)),
IE,
AN,
token_uri(Invite),
landing_page(Host, Invite)}
end,
[Format(Invite) || Invite <- Invites].
Res = maybe
{ok, Invites} ?= try_db_call(Host, list_invites, [Host]),
[format_invite(Host, Invite) || Invite <- Invites]
end,
pretty_format_command_result(Res).
format_invite(Host,
#invite_token{token = TO,
inviter = {IU, IS},
invitee = IE,
created_at = CA,
expires = Exp,
type = TY,
account_name = AN} =
Invite) ->
{TO,
is_token_valid(Host, TO),
encode_datetime(CA),
encode_datetime(Exp),
TY,
jid:encode(
jid:make(IU, IS)),
IE,
AN,
token_uri(Invite),
landing_page(Host, Invite)}.
%%--------------------------------------------------------------------
%%| hooks and callbacks
@@ -715,8 +747,15 @@ process(LocalPath, Request) ->
get_invite(Host, Token) ->
db_call(Host, get_invite, [Host, Token]).
-ifdef(TEST).
get_invites(Host, Inviter) ->
db_call(Host, get_invites, [Host, Inviter]).
transaction(Host, fun() -> get_invites_t(Host, Inviter) end).
-endif.
get_invites_t(Host, Inviter) ->
db_call(Host, get_invites_t, [Host, Inviter]).
is_expired(#invite_token{expires = Expires}) ->
Now = erlang:timestamp(),
@@ -762,10 +801,13 @@ create_account_invite(Host, Inviter, AccountName, _Subcribe = false) ->
create_invite(account_only, Host, Inviter, AccountName).
create_invite(Type, Host, Inviter, AccountName) ->
try invite_token(Type, Host, Inviter, AccountName) of
F = fun() -> create_invite_t(Type, Host, Inviter, AccountName) end,
transaction(Host, F).
create_invite_t(Type, Host, Inviter, AccountName) ->
try invite_token_t(Type, Host, Inviter, AccountName) of
Invite ->
?DEBUG("Created invite: ~p", [Invite]),
db_call(Host, create_invite, [Invite])
db_call(Host, create_invite_t, [Invite])
catch
_:({error, _Reason} = Error) ->
Error;
@@ -798,10 +840,10 @@ check_account_name(AccountName, Host) ->
end
end.
check_max_invites(roster_only, _) ->
check_max_invites_t(roster_only, _) ->
ok;
check_max_invites(_Type, {User, Host}) ->
case is_create_allowed(User, Host) of
check_max_invites_t(_Type, {User, Host}) ->
case is_create_allowed_t(User, Host) of
true ->
ok;
false ->
@@ -809,11 +851,14 @@ check_max_invites(_Type, {User, Host}) ->
end.
is_create_allowed(User, Host) ->
transaction(Host, fun() -> is_create_allowed_t(User, Host) end).
is_create_allowed_t(User, Host) ->
case get_max_invites(User, Host) of
infinity ->
true;
MaxInvites ->
Invites = get_invites(Host, {User, Host}),
Invites = get_invites_t(Host, {User, Host}),
NumCreated =
lists:foldl(fun (#invite_token{type = roster_only, account_name = <<>>}, Num) ->
Num;
@@ -854,13 +899,97 @@ get_max_invites(User, Server) ->
MaxInvites
end.
check_overuse_t(roster_only, {User, Host}) ->
NumInvites = length(get_invites_t(Host, {User, Host})),
case NumInvites >= ?OVERUSE_LIMIT of
true ->
{error, num_invites_exceeded};
false ->
ok
end;
check_overuse_t(_Type, {User, Host}) ->
NumInvites = length(get_invites_tree_t(Host, {User, Host})),
case NumInvites >= ?OVERUSE_LIMIT of
true ->
{error, num_invites_exceeded};
false ->
ok
end.
get_invites_tree_t(Host, Inviter) ->
Now = calendar:datetime_to_gregorian_seconds(
calendar:now_to_datetime(
erlang:timestamp())),
Root = find_invites_tree_root_t(Now, Host, Inviter, 0),
get_invites_tree_as_root_t(Host, Root).
find_invites_tree_root_t(Now, Host, Invitee, Lvl) ->
case get_invite_by_invitee_t(Host, Invitee) of
#invite_token{inviter = Inviter, created_at = CreatedAt} ->
maybe_block_speedy_goat(Now, CreatedAt, Lvl),
find_invites_tree_root_t(Now, Host, Inviter, Lvl + 1);
{error, not_found} ->
Invitee
end.
-spec get_invite_by_invitee_t(binary(), {binary(), binary()}) ->
invite_token() | {error, not_found}.
get_invite_by_invitee_t(Host, {User, Server}) ->
InviteeJid =
jid:encode(
jid:make(User, Server)),
db_call(Host, get_invite_by_invitee_t, [Host, InviteeJid]).
maybe_block_speedy_goat(Now, CreatedAt, Lvl) when Lvl == ?SPEEDY_GOAT_LEVELS ->
Then = calendar:datetime_to_gregorian_seconds(CreatedAt),
if Now - Then < ?SPEEDY_GOAT_SECONDS ->
throw(speedy_goat);
true ->
ok
end;
maybe_block_speedy_goat(_, _, _) ->
ok.
-spec get_invites_tree_as_root_t(binary(), {binary(), binary()}) -> [invite_token()].
get_invites_tree_as_root_t(Host, Inviter) ->
Invites = get_invites_t(Host, Inviter),
get_invites_tree_as_root_t(Host, Inviter, Invites, []).
get_invites_tree_as_root_t(_Host, _Inviter, [], Acc) ->
Acc;
get_invites_tree_as_root_t(Host,
Inviter,
[#invite_token{type = roster_only, account_name = <<>>} | Invites],
Acc) ->
get_invites_tree_as_root_t(Host, Inviter, Invites, Acc);
get_invites_tree_as_root_t(Host,
Inviter,
[#invite_token{invitee = <<>>} = Invite | Invites],
Acc) ->
get_invites_tree_as_root_t(Host, Inviter, Invites, [Invite | Acc]);
get_invites_tree_as_root_t(Host,
Inviter,
[#invite_token{invitee = InviteeJID} = Invite | Invites],
Acc) ->
case jid:decode(InviteeJID) of
#jid{luser = Invitee, lserver = Host} ->
get_invites_tree_as_root_t(Host,
Inviter,
Invites,
[Invite | Acc]
++ get_invites_tree_as_root_t(Host, {Invitee, Host}));
_Nomatch ->
get_invites_tree_as_root_t(Host, Inviter, Invites, [Invite | Acc])
end.
maybe_throw({error, _} = Error) ->
throw(Error);
maybe_throw(Good) ->
Good.
invite_token(Type, Host, Inviter, AccountName0) ->
maybe_throw(check_max_invites(Type, Inviter)),
invite_token_t(Type, Host, Inviter, AccountName0) ->
maybe_throw(check_max_invites_t(Type, Inviter)),
maybe_throw(check_overuse_t(Type, Inviter)),
Token = p1_rand:get_alphanum_string(?INVITE_TOKEN_LENGTH_DEFAULT),
AccountName = maybe_throw(check_account_name(jid:nodeprep(AccountName0), Host)),
set_token_expires(#invite_token{token = Token,
@@ -902,10 +1031,46 @@ maybe_add_ibr_allowed(User, Host) ->
landing_page(Host, Invite) ->
mod_invites_http:landing_page(Host, Invite).
-spec db_call(binary(), atom(), list()) -> any().
-spec db_call(binary(), atom(), [any()]) -> any().
db_call(Host, Fun, Args) ->
Mod = gen_mod:db_mod(Host, ?MODULE),
apply(Mod, Fun, Args).
try gen_mod:db_mod(Host, ?MODULE) of
Mod ->
apply(Mod, Fun, Args)
catch
_:{module_not_loaded, ?MODULE, Host} ->
throw({error, host_unknown})
end.
%% father forgive me
lift({error, _R} = E) ->
E;
lift({ok, _V} = R) ->
R;
lift(Res) ->
{ok, Res}.
-spec try_db_call(Host :: binary(), Fun :: atom(), Args :: [any()]) ->
{ok, any()} | {error, any()}.
try_db_call(Host, Fun, Args) ->
try
lift(db_call(Host, Fun, Args))
catch
_:({error, _Reason} = Error) ->
Error;
error:Error ->
{error, Error}
end.
transaction(Host, F) ->
try db_call(Host, transaction, [Host, F]) of
{atomic, Result} ->
Result;
{aborted, Reason} ->
{error, Reason}
catch
_:Error ->
Error
end.
-spec trans(binary(), binary()) -> binary().
trans(Lang, Msg) ->
@@ -1008,3 +1173,16 @@ send_presence(From, To, Type) ->
to = To,
type = Type},
ejabberd_router:route(Presence).
pretty_format_command_result({error, {module_not_loaded, ?MODULE, Host}}) ->
{error,
lists:flatten(
io_lib:format("Virtual host not known: ~s", [binary_to_list(Host)]))};
pretty_format_command_result({error, host_unknown}) ->
{error, "Virtual host not known"};
pretty_format_command_result({error, user_exists}) ->
{error, "Username already taken"};
pretty_format_command_result({ok, Result}) ->
Result;
pretty_format_command_result(Result) ->
Result.
+179 -61
View File
@@ -28,7 +28,7 @@
-include("logger.hrl").
-export([process/2, landing_page/2]).
-export([process/2, landing_page/2, tmpl_to_renderer/1]).
-ifdef(TEST).
-export([apps_json/3]).
@@ -40,17 +40,20 @@
-include("mod_invites.hrl").
-include("translate.hrl").
-define(HTTP(Code, CT, Text), {Code, [{<<"Content-Type">>, CT}], Text}).
-define(HTTP(Code, Headers, CT, Text), {Code, [{<<"Content-Type">>, CT} | Headers], Text}).
-define(HTTP(Code, CT, Text), ?HTTP(Code, [], CT, Text)).
-define(HTTP(Code, Text), ?HTTP(Code, <<"text/plain">>, Text)).
-define(HTTP_OK(Text), ?HTTP(200, <<"text/html">>, Text)).
-define(HTTP_OK(Headers, Text), ?HTTP(200, security_headers() ++ Headers, <<"text/html">>, Text)).
-define(NOT_FOUND, ?HTTP(404, ?T("NOT FOUND"))).
-define(NOT_FOUND(Text), ?HTTP(404, <<"text/html">>, Text)).
-define(BAD_REQUEST, ?HTTP(400, ?T("BAD REQUEST"))).
-define(BAD_REQUEST(Text), ?HTTP(400, <<"text/html">>, Text)).
-define(BAD_REQUEST(Headers, Text), ?HTTP(400, security_headers() ++ Headers, <<"text/html">>, Text)).
-define(BAD_REQUEST(Text), ?HTTP(400, security_headers(), <<"text/html">>, Text)).
-define(DEFAULT_CONTENT_TYPE, <<"application/octet-stream">>).
-define(CONTENT_TYPES,
[{<<".js">>, <<"application/javascript">>},
[{<<".css">>, <<"text/css">>},
{<<".js">>, <<"application/javascript">>},
{<<".png">>, <<"image/png">>},
{<<".svg">>, <<"image/svg+xml">>}]).
@@ -58,26 +61,34 @@
-define(REGISTRATION, <<"registration">>).
-define(STATIC_CTX, {static, <<"/", Base/binary, "/", ?STATIC/binary>>}).
-define(SITE_NAME_CTX(Name), {site_name, Name}).
-define(LANG(Lang), {lang, Lang}).
%% @format-begin
landing_page(Host, Invite) ->
case landing_page_tmpl(Host) of
<<>> ->
<<>>;
Tmpl ->
render_landing_page_url(Tmpl, Host, Invite)
end.
landing_page_tmpl(Host) ->
case mod_invites_opt:landing_page(Host) of
none ->
<<>>;
<<"auto">> ->
try ejabberd_http:get_auto_url(any, mod_invites) of
AutoURL0 ->
AutoURL = misc:expand_keyword(<<"@HOST@">>, AutoURL0, Host),
render_landing_page_url(<<AutoURL/binary, "{{ invite.token }}">>, Host, Invite)
catch
_:_ ->
auto ->
case ejabberd_http:get_auto_url(any, mod_invites) of
undefined ->
?WARNING_MSG("'auto' URL configured for mod_invites but no request_handler found in your ~s listeners configuration.",
[Host]),
<<>>
<<>>;
AutoURL ->
ExpandedAutoURL = misc:expand_keyword(<<"@HOST@">>, AutoURL, Host),
<<ExpandedAutoURL/binary, "{{ invite.token }}">>
end;
Tmpl ->
render_landing_page_url(Tmpl, Host, Invite)
Tmpl
end.
render_landing_page_url(Tmpl, Host, Invite) ->
@@ -88,13 +99,18 @@ render_landing_page_url(Tmpl, Host, Invite) ->
{HTTPCode :: integer(), [{binary(), binary()}], Page :: string()}.
process([?STATIC | StaticFile], #request{host = Host} = Request) ->
?DEBUG("Static file requested ~p:~n~p", [StaticFile, Request]),
TemplatesDir = mod_invites_opt:templates_dir(Host),
Filename = filename:join([TemplatesDir, "static" | StaticFile]),
case file:read_file(Filename) of
{ok, Content} ->
CT = guess_content_type(Filename),
?HTTP(200, CT, Content);
{error, _} ->
try mod_invites_opt:templates_dir(Host) of
TemplatesDir ->
Filename = filename:join([TemplatesDir, "static" | StaticFile]),
case file:read_file(Filename) of
{ok, Content} ->
CT = guess_content_type(Filename),
?HTTP(200, CT, Content);
{error, _} ->
?NOT_FOUND
end
catch
_:{module_not_loaded, mod_invites, Host} ->
?NOT_FOUND
end;
process([Token | _] = LocalPath, #request{host = Host, lang = Lang} = Request) ->
@@ -108,9 +124,14 @@ process([Token | _] = LocalPath, #request{host = Host, lang = Lang} = Request) -
process_valid_token(LocalPath, Request, Invite)
end;
false ->
?NOT_FOUND(render(Host, Lang, <<"invite_invalid.html">>, ctx(Request, LocalPath)))
?NOT_FOUND(render(Host,
Lang,
<<"invite_invalid.html">>,
ctx(Request, LocalPath, Token)))
catch
_:not_found ->
?NOT_FOUND;
_:{error, host_unknown} ->
?NOT_FOUND
end;
process([], _Request) ->
@@ -133,7 +154,7 @@ process_valid_token([_Token, AppID] = LocalPath,
Invite) ->
try app_ctx(Host, AppID, Lang, ctx(Invite, Request, LocalPath)) of
AppCtx ->
render_ok(Host, Lang, <<"client.html">>, AppCtx)
render_ok(Host, Invite, Lang, <<"client.html">>, AppCtx)
catch
_:not_found ->
?NOT_FOUND
@@ -142,14 +163,9 @@ process_valid_token([_Token] = LocalPath,
#request{host = Host, lang = Lang} = Request,
Invite) ->
Ctx0 = ctx(Invite, Request, LocalPath),
Apps =
lists:map(fun(App0) ->
App = app_id(App0),
render_app_urls(App, [{app, App} | Ctx0])
end,
apps_json(Host, Lang, Ctx0)),
Apps = [render_app_urls(App, [{app, App} | Ctx0]) || App <- apps_json(Host, Lang, Ctx0)],
Ctx = [{apps, Apps} | Ctx0],
render_ok(Host, Lang, <<"invite.html">>, Ctx);
render_ok(Host, Invite, Lang, <<"invite.html">>, Ctx);
process_valid_token(_, _, _) ->
?NOT_FOUND.
@@ -159,15 +175,25 @@ process_register_form(Invite,
LocalPath) ->
try app_ctx(Host, AppID, Lang, ctx(Invite, Request, LocalPath)) of
AppCtx ->
Body = render_register_form(Request, AppCtx, maybe_add_username(Invite)),
?HTTP_OK(Body)
Ctx = [{csrf_token, csrf_token(Invite#invite_token.token)}, maybe_add_username(Invite)]
++ AppCtx,
Body = render_register_form(Request, Ctx),
Headers = maybe_add_hsts_header(Host, Invite),
?HTTP_OK(Headers, Body)
catch
_:not_found ->
?NOT_FOUND
end.
render_register_form(#request{host = Host, lang = Lang}, Ctx, AdditionalCtx) ->
render(Host, Lang, <<"register.html">>, Ctx ++ AdditionalCtx).
render_register_form(#request{host = Host, lang = Lang}, Ctx) ->
MinLength =
case mod_register_opt:password_strength(Host) of
0 ->
0;
_ ->
6
end,
render(Host, Lang, <<"register.html">>, [{password_min_length, MinLength} | Ctx]).
process_register_post(Invite,
AppID,
@@ -177,36 +203,42 @@ process_register_post(Invite,
ip = {Source, _}} =
Request,
LocalPath) ->
?DEBUG("got query: ~p", [Q]),
Username = proplists:get_value(<<"user">>, Q),
Password = proplists:get_value(<<"password">>, Q),
Token = Invite#invite_token.token,
CSRFToken = proplists:get_value(<<"csrf_token">>, Q),
try {app_ctx(Host, AppID, Lang, ctx(Invite, Request, LocalPath)),
ensure_same(Token, proplists:get_value(<<"token">>, Q))}
ensure_same(Token, proplists:get_value(<<"token">>, Q)),
check_csrf(Token, CSRFToken)}
of
{AppCtx, ok} ->
{AppCtx, ok, ok} ->
case mod_invites_register:try_register(Invite, Username, Host, Password, Source, Lang)
of
{ok, _UpdatedInvite} ->
Ctx = [{username, Username}, {password, Password} | AppCtx],
render_ok(Host, Lang, <<"register_success.html">>, Ctx);
{error, #stanza_error{text = Text, type = Type} = Error} ->
Ctx = maybe_add_webchat_url(Host,
[{username, Username}, {password, Password}
| AppCtx]),
render_ok(Host, Invite, Lang, <<"register_success.html">>, Ctx);
{error,
#stanza_error{text = Text,
type = Type,
reason = Reason} =
Error} ->
?DEBUG("registration failed with error: ~p", [Error]),
Msg = xmpp:get_text(Text, xmpp:prep_lang(Lang)),
case Type of
T when T == cancel; T == modify ->
Body =
render_register_form(Request,
AppCtx,
[{username, Username},
{message,
[{text, Msg},
{class, <<"alert-warning">>}]}]),
Ctx = [{username, Username},
{csrf_token, csrf_token(Invite#invite_token.token)},
{error, [{text, Msg}, {class, error_class(Reason)}]}]
++ AppCtx,
Body = render_register_form(Request, Ctx),
?BAD_REQUEST(Body);
_ ->
render_bad_request(Host,
Invite,
<<"register_error.html">>,
[{message, Msg} | ctx(Request, LocalPath)])
[{message, Msg} | ctx(Request, LocalPath, Token)])
end
end
catch
@@ -216,6 +248,58 @@ process_register_post(Invite,
?BAD_REQUEST
end.
check_csrf(_Token, undefined) ->
throw(no_match);
check_csrf(Token, Could) ->
Should = csrf_token(Token),
try crypto:hash_equals(Should, Could) of
true ->
ok;
_ ->
throw(no_match)
catch
_:_ ->
throw(no_match)
end.
csrf_token(Msg) ->
SecretKey = ejabberd_config:get_shared_key(),
base64:encode(
crypto:mac(hmac,
sha256,
str:to_hexlist(
crypto:hash(sha256, SecretKey)),
Msg)).
maybe_add_webchat_url(Host, Ctx) ->
case mod_invites_opt:webchat_url(Host) of
none ->
Ctx;
auto ->
case ejabberd_http:get_auto_url(any, mod_conversejs) of
undefined ->
?INFO_MSG("'auto' URL configured for webchat_url but no request_handler for mod_conversejs found in your ~s listeners configuration.",
[Host]),
Ctx;
WebchatUrlRaw ->
WebchatUrl = misc:expand_keyword(<<"@HOST@">>, WebchatUrlRaw, Host),
[{webchat_url, WebchatUrl} | Ctx]
end;
WebchatUrl ->
[{webchat_url, WebchatUrl} | Ctx]
end.
error_class('jid-malformed') ->
username;
error_class('not-allowed') ->
username;
error_class(conflict) ->
username;
error_class('not-acceptable') ->
password;
error_class(_) ->
undefined.
process_roster_token([_Token] = LocalPath,
#request{host = Host, lang = Lang} = Request,
Invite) ->
@@ -234,7 +318,7 @@ process_roster_token([_Token] = LocalPath,
end,
apps_json(Host, Lang, Ctx0)),
Ctx = [{apps, Apps} | Ctx0],
render_ok(Host, Lang, <<"roster.html">>, Ctx);
render_ok(Host, Invite, Lang, <<"roster.html">>, Ctx);
process_roster_token(_, _, _) ->
?NOT_FOUND.
@@ -246,8 +330,7 @@ ensure_same(_, _) ->
app_ctx(_Host, <<>>, _Lang, Ctx) ->
Ctx;
app_ctx(Host, AppID, Lang, Ctx) ->
FilteredApps =
[App || A <- apps_json(Host, Lang, Ctx), maps:get(<<"id">>, App = app_id(A)) == AppID],
FilteredApps = [A || A <- apps_json(Host, Lang, Ctx), maps:get(<<"id">>, A) == AppID],
case FilteredApps of
[App] ->
[{app, render_app_button_urls(App, Ctx)} | Ctx];
@@ -255,25 +338,39 @@ app_ctx(Host, AppID, Lang, Ctx) ->
throw(not_found)
end.
ctx(#request{host = Host, path = Path}, LocalPath) ->
ctx(#request{host = Host,
path = Path,
lang = Lang},
LocalPath,
Token) ->
OriginalPath =
case landing_page_tmpl(Host) of
<<>> ->
Path;
Tmpl ->
Url = render_url(Tmpl, [{invite, [{token, Token}]}, {host, Host}]),
#{path := OPath} = uri_string:parse(Url),
{LPath, _Q} = ejabberd_http:url_decode_q_split_normalize(OPath),
LPath
end,
Base =
iolist_to_binary(uri_string:normalize(
lists:join(<<"/">>, Path -- LocalPath))),
lists:join(<<"/">>, OriginalPath -- LocalPath))),
SiteName = mod_invites_opt:site_name(Host),
[?STATIC_CTX, ?SITE_NAME_CTX(SiteName)].
[{base, Base}, ?STATIC_CTX, ?SITE_NAME_CTX(SiteName), ?LANG(Lang)];
ctx(Invite, #request{host = Host} = Request, LocalPath) ->
[{invite, invite_to_proplist(Invite)},
{uri, mod_invites:token_uri(Invite)},
{domain, Host},
{token, Invite#invite_token.token},
{registration_url, <<(Invite#invite_token.token)/binary, "/", ?REGISTRATION/binary>>}
| ctx(Request, LocalPath)].
| ctx(Request, LocalPath, Invite#invite_token.token)].
apps_json(Host, Lang, Ctx) ->
AppsBins = render(Host, Lang, <<"apps.json">>, Ctx),
AppsBin = binary_join(AppsBins, <<>>),
misc:json_decode(AppsBin).
AppsMap = misc:json_decode(AppsBin),
[app_id(App) || App <- AppsMap].
app_id(App = #{<<"id">> := _ID}) ->
App;
@@ -355,13 +452,28 @@ lang(default) ->
lang(Lang) ->
Lang.
render_ok(Host, Lang, File, Ctx) ->
?HTTP_OK(render(Host, Lang, File, Ctx)).
render_ok(Host, Invite, Lang, File, Ctx) ->
URI = proplists:get_value(uri, Ctx),
Headers = maybe_add_hsts_header([{<<"Link">>, <<"<", URI/binary, ">">>}], Host, Invite),
?HTTP_OK(Headers, render(Host, Lang, File, Ctx)).
render_bad_request(Host, File, Ctx) ->
maybe_add_hsts_header(Host, Invite) ->
maybe_add_hsts_header([], Host, Invite).
maybe_add_hsts_header(Headers, Host, Invite) ->
LP = landing_page(Host, Invite),
case re:run(LP, "^https://") of
nomatch ->
Headers;
{match, _} ->
[{<<"Strict-Transport-Security">>, <<"max-age=31536000; includeSubDomains">>} | Headers]
end.
render_bad_request(Host, Invite, File, Ctx) ->
Headers = maybe_add_hsts_header(Host, Invite),
Renderer = file_to_renderer(Host, File),
{ok, Rendered} = Renderer:render(Ctx),
?BAD_REQUEST(Rendered).
?BAD_REQUEST(Headers, Rendered).
-spec guess_content_type(binary()) -> binary().
guess_content_type(FileName) ->
@@ -389,3 +501,9 @@ binary_join(List, Sep) ->
end,
<<>>,
List).
security_headers() ->
[{<<"Content-Security-Policy">>,
<<"default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' data:; frame-ancestors 'none'">>},
{<<"X-Content-Type-Options">>, <<"nosniff">>},
{<<"Referrer-Policy">>, <<"no-referrer">>}].
+39 -18
View File
@@ -27,11 +27,13 @@
-behaviour(mod_invites).
-export([cleanup_expired/1, create_invite/1, expire_tokens/2, get_invite/2, get_invites/2, init/2,
is_reserved/3, is_token_valid/3, list_invites/1, remove_user/2,
set_invitee/5]).
-export([cleanup_expired/1, create_invite_t/1, expire_tokens/2, get_invite/2, get_invites_t/2,
get_invite_by_invitee_t/2, init/2, is_reserved/3, is_token_valid/3, list_invites/1,
remove_user/2, set_invitee/5, transaction/2]).
-include("mod_invites.hrl").
-include("logger.hrl").
-include_lib("xmpp/include/xmpp.hrl").
%% @format-begin
@@ -52,8 +54,8 @@ cleanup_expired(_Host) ->
0,
mnesia:dirty_all_keys(invite_token)).
create_invite(Invite) ->
ok = mnesia:dirty_write(Invite),
create_invite_t(Invite) ->
ok = mnesia:write(Invite),
Invite.
expire_tokens(User, Server) ->
@@ -70,23 +72,33 @@ get_invite(_Host, Token) ->
{error, not_found}
end.
get_invites(_Host, Inviter) ->
mnesia:dirty_index_read(invite_token, Inviter, #invite_token.inviter).
get_invite_by_invitee_t(_Host, InviteeJid) ->
case mnesia:index_read(invite_token, InviteeJid, #invite_token.invitee) of
[#invite_token{type = Type} = Invite] when Type /= roster_only ->
Invite;
_ ->
{error, not_found}
end.
get_invites_t(_Host, Inviter) ->
mnesia:index_read(invite_token, Inviter, #invite_token.inviter).
init(_Host, _Opts) ->
ejabberd_mnesia:create(?MODULE,
invite_token,
[{disc_copies, [node()]},
{attributes, record_info(fields, invite_token)},
{index, [inviter]}]).
{index, [inviter, invitee]}]).
is_reserved(_Host, Token, User) ->
[T
|| T <- mnesia:dirty_all_keys(invite_token),
not mod_invites:is_expired(I = hd(mnesia:dirty_read(invite_token, T))),
I#invite_token.token /= Token,
I#invite_token.invitee == <<>>,
I#invite_token.account_name == User]
lists:filter(fun(T) ->
I = hd(mnesia:dirty_read(invite_token, T)),
not mod_invites:is_expired(I)
and (I#invite_token.token /= Token)
and (I#invite_token.invitee == <<>>)
and (I#invite_token.account_name == User)
end,
mnesia:dirty_all_keys(invite_token))
=/= [].
is_token_valid(Host, Token, Scope) ->
@@ -101,10 +113,16 @@ is_token_valid(Host, Token, Scope) ->
end.
list_invites(Host) ->
[Invite
|| Token <- mnesia:dirty_all_keys(invite_token),
element(2, (Invite = hd(mnesia:dirty_read(invite_token, Token)))#invite_token.inviter)
== Host].
lists:filtermap(fun(Token) ->
Invite = hd(mnesia:dirty_read(invite_token, Token)),
case element(2, Invite#invite_token.inviter) of
Host ->
{true, Invite};
_ ->
false
end
end,
mnesia:dirty_all_keys(invite_token)).
remove_user(User, Server) ->
Inviter = {User, Server},
@@ -141,3 +159,6 @@ set_invitee(F, _Host, Token, Invitee, AccountName) ->
end,
{atomic, Res} = mnesia:transaction(Transaction),
Res.
transaction(_Host, Fun) ->
mnesia:transaction(Fun).
+8 -1
View File
@@ -10,6 +10,7 @@
-export([site_name/1]).
-export([templates_dir/1]).
-export([token_expire_seconds/1]).
-export([webchat_url/1]).
-spec access_create_account(gen_mod:opts() | global | binary()) -> 'none' | acl:acl().
access_create_account(Opts) when is_map(Opts) ->
@@ -23,7 +24,7 @@ db_type(Opts) when is_map(Opts) ->
db_type(Host) ->
gen_mod:get_module_opt(Host, mod_invites, db_type).
-spec landing_page(gen_mod:opts() | global | binary()) -> 'none' | binary().
-spec landing_page(gen_mod:opts() | global | binary()) -> 'none' | 'auto' | binary().
landing_page(Opts) when is_map(Opts) ->
gen_mod:get_opt(landing_page, Opts);
landing_page(Host) ->
@@ -53,3 +54,9 @@ token_expire_seconds(Opts) when is_map(Opts) ->
token_expire_seconds(Host) ->
gen_mod:get_module_opt(Host, mod_invites, token_expire_seconds).
-spec webchat_url(gen_mod:opts() | global | binary()) -> 'auto' | 'none' | binary().
webchat_url(Opts) when is_map(Opts) ->
gen_mod:get_opt(webchat_url, Opts);
webchat_url(Host) ->
gen_mod:get_module_opt(Host, mod_invites, webchat_url).
+19 -2
View File
@@ -74,7 +74,10 @@ c2s_unauthenticated_packet(#{invite := Invite} = State,
IQ1 = xmpp:set_els(IQ, [Register]),
User = Invite#invite_token.account_name,
IQ2 = xmpp:set_from_to(IQ1, jid:make(User, Server), jid:make(Server)),
ResIQ = mod_register:process_iq(IQ2),
Meta = xmpp:get_meta(IQ2),
ResIQ =
mod_register:process_iq(
xmpp:set_meta(IQ2, Meta#{pre_auth => true})),
ResIQ1 = xmpp:set_from_to(ResIQ, jid:make(Server), undefined),
{stop, ejabberd_c2s:send(State, ResIQ1)}
end);
@@ -193,7 +196,17 @@ create_account_allowed(#invite_token{type = roster_only} = Invite) ->
#invite_token{inviter = {User, Host}} = Invite,
case mod_invites:is_create_allowed(User, Host) of
true ->
ok;
NumInvites =
length(mod_invites:transaction(Host,
fun() ->
mod_invites:get_invites_tree_t(Host, {User, Host})
end)),
case NumInvites >= ?OVERUSE_LIMIT of
false ->
ok;
true ->
{error, not_allowed}
end;
false ->
{error, not_allowed}
end;
@@ -213,6 +226,10 @@ try_register(Invite, User, Server, Password, Source, Lang) ->
{error,
xmpp:err_jid_malformed(
mod_register:format_error(invalid_jid), Lang)};
{_, false} ->
{error,
xmpp:err_not_allowed(
mod_register:format_error(not_allowed), Lang)};
{_, true} ->
RegF =
fun() ->
+56 -11
View File
@@ -27,9 +27,9 @@
-behaviour(mod_invites).
-export([cleanup_expired/1, create_invite/1, expire_tokens/2, get_invite/2, get_invites/2, init/2,
is_reserved/3, is_token_valid/3, list_invites/1, remove_user/2,
set_invitee/5]).
-export([cleanup_expired/1, create_invite_t/1, expire_tokens/2, get_invite/2,
get_invite_by_invitee_t/2, get_invites_t/2, init/2, is_reserved/3, is_token_valid/3,
list_invites/1, remove_user/2, set_invitee/5, transaction/2]).
-export([sql_schemas/0]).
@@ -46,7 +46,30 @@ init(Host, _Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, sql_schemas()).
sql_schemas() ->
[#sql_schema{version = 1,
[#sql_schema{version = 2,
tables =
[#sql_table{name = <<"invite_token">>,
columns =
[#sql_column{name = <<"token">>, type = text},
#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"invitee">>,
type = {text, 191},
default = true},
#sql_column{name = <<"created_at">>,
type = timestamp,
default = true},
#sql_column{name = <<"expires">>,
type = timestamp,
default = true},
#sql_column{name = <<"type">>, type = {char, 1}},
#sql_column{name = <<"account_name">>, type = text}],
indices =
[#sql_index{columns = [<<"token">>], unique = true},
#sql_index{columns = [<<"username">>, <<"server_host">>]},
#sql_index{columns = [<<"invitee">>]}]}],
update = [{create_index, <<"invite_token">>, [<<"invitee">>]}]},
#sql_schema{version = 1,
tables =
[#sql_table{name = <<"invite_token">>,
columns =
@@ -75,7 +98,7 @@ cleanup_expired(Host) ->
ejabberd_sql:sql_query(Host, ?SQL("DELETE FROM invite_token WHERE expires < %(NOW)t")),
Count.
create_invite(Invite) ->
create_invite_t(Invite) ->
#invite_token{inviter = {User, Host},
token = Token,
account_name = AccountName,
@@ -96,7 +119,7 @@ create_invite(Invite) ->
"created_at=%(CreatedAt)t",
"expires=%(Expires)t",
"account_name=%(AccountName)s"]),
{updated, 1} = ejabberd_sql:sql_query(Host, Query),
{updated, 1} = ejabberd_sql:sql_query_t(Query),
Invite.
expire_tokens(User, Server) ->
@@ -126,12 +149,31 @@ get_invite(Host, Token) ->
{error, not_found}
end.
get_invites(Host, {User, _Host}) ->
-spec get_invite_by_invitee_t(binary(), binary()) ->
mod_invites:invite_token() | {error, not_found}.
get_invite_by_invitee_t(Host, InviteeJid) ->
case ejabberd_sql:sql_query(Host,
?SQL("SELECT @(token)s, @(username)s, @(invitee)s, @(type)s, "
"@(account_name)s, @(expires)t, @(created_at)t FROM "
"invite_token WHERE invitee = %(InviteeJid)s AND %(Host)H"))
of
{selected, [{Token, User, Invitee, Type, AccountName, Expires, CreatedAt}]} ->
#invite_token{token = Token,
inviter = {User, Host},
invitee = Invitee,
type = dec_type(Type),
account_name = AccountName,
expires = Expires,
created_at = CreatedAt};
{selected, []} ->
{error, not_found}
end.
get_invites_t(Host, {User, _Host}) ->
{selected, Invites} =
ejabberd_sql:sql_query(Host,
?SQL("SELECT @(token)s, @(invitee)s, @(type)s, @(account_name)s, "
"@(expires)t, @(created_at)t FROM invite_token WHERE %(Host)H "
"AND username = %(User)s")),
ejabberd_sql:sql_query_t(?SQL("SELECT @(token)s, @(invitee)s, @(type)s, @(account_name)s, "
"@(expires)t, @(created_at)t FROM invite_token WHERE %(Host)H "
"AND username = %(User)s")),
lists:map(fun({Token, Invitee, Type, AccountName, Expires, CreatedAt}) ->
#invite_token{token = Token,
inviter = {User, Host},
@@ -205,6 +247,9 @@ set_invitee(Fun, Host, Token, Invitee, AccountName) ->
Error
end.
transaction(Host, Fun) ->
ejabberd_sql:sql_transaction(Host, Fun).
%%--------------------------------------------------------------------
%%| helpers
sql_now() ->
+23 -18
View File
@@ -30,8 +30,8 @@
-protocol({xep, 359, '0.7.0', '15.09', "complete", ""}).
-protocol({xep, 424, '0.4.2', '24.02', "partial", "Tombstones not implemented"}).
-protocol({xep, 425, '0.3.0', '24.06', "complete", ""}).
-protocol({xep, 441, '0.2.0', '15.06', "complete", ""}).
-protocol({xep, 431, '0.2.0', '24.12', "complete", ""}).
-protocol({xep, 441, '0.2.0', '15.06', "complete", ""}).
-behaviour(gen_mod).
@@ -47,14 +47,14 @@
get_room_config/4, set_room_option/3, offline_message/1, export/1,
mod_options/1, remove_mam_for_user_with_peer/3, remove_mam_for_user/2,
is_empty_for_user/2, is_empty_for_room/3, check_create_room/4,
process_iq/3, store_mam_message/7, make_id/0, wrap_as_mucsub/2, select/7,
process_iq/3, store_mam_message/8, make_id/0, wrap_as_mucsub/2, select/7,
is_archiving_enabled/2,
get_mam_count/2,
webadmin_menu_hostuser/4,
webadmin_page_hostuser/4,
get_mam_messages/2, webadmin_user/4,
delete_old_messages_batch/5, delete_old_messages_status/1, delete_old_messages_abort/1,
remove_message_from_archive/3]).
remove_message_from_archive/3, strip_my_stanza_id/2]).
-import(ejabberd_web_admin, [make_command/4, make_command/2]).
@@ -79,6 +79,7 @@
-callback delete_old_messages(binary() | global,
erlang:timestamp(),
all | chat | groupchat) -> any().
-callback additional_namespaces(binary()) -> [binary()].
-callback extended_fields(binary()) -> [mam_query:property() | #xdata_field{}].
-callback store(xmlel(), binary(), {binary(), binary()}, chat | groupchat,
jid(), binary(), recv | send, integer(), binary(),
@@ -627,12 +628,15 @@ parse_query(#mam_query{}, _Lang) ->
disco_local_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
Acc;
disco_local_features(Acc, _From, _To, <<"">>, _Lang) ->
disco_local_features(Acc, _From, #jid{lserver = LServer} = _To, <<"">>, _Lang) ->
Features = case Acc of
{result, Fs} -> Fs;
empty -> []
end,
{result, [?NS_MESSAGE_RETRACT | Features]};
Mod = gen_mod:db_mod(LServer, ?MODULE),
AdditionalNamespaces = Mod:additional_namespaces(LServer),
Namespaces = [?NS_MESSAGE_RETRACT],
{result, lists:append([Namespaces, AdditionalNamespaces, Features])};
disco_local_features(empty, _From, _To, _Node, Lang) ->
Txt = ?T("No features available"),
{error, xmpp:err_item_not_found(Txt, Lang)};
@@ -644,9 +648,11 @@ disco_sm_features(empty, From, To, Node, Lang) ->
disco_sm_features({result, OtherFeatures},
#jid{luser = U, lserver = S},
#jid{luser = U, lserver = S}, <<"">>, _Lang) ->
{result, [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1, ?NS_MAM_2, ?NS_SID_0,
?NS_MESSAGE_RETRACT |
OtherFeatures]};
Mod = gen_mod:db_mod(S, ?MODULE),
AdditionalNamespaces = Mod:additional_namespaces(S),
Namespaces = [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1, ?NS_MAM_2, ?NS_SID_0,
?NS_MESSAGE_RETRACT],
{result, lists:append([Namespaces, AdditionalNamespaces, OtherFeatures])};
disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
@@ -1138,18 +1144,16 @@ store_msg(Pkt, LUser, LServer, Peer, Dir) ->
{ok, Prefs} ->
UseMucArchive = mod_mam_opt:user_mucsub_from_muc_archive(LServer),
StoredInMucMam = UseMucArchive andalso xmpp:get_meta(Pkt, in_muc_mam, false),
case {should_archive_peer(LUser, LServer, Prefs, Peer), Pkt, StoredInMucMam} of
{true, #message{meta = #{sm_copy := true}}, _} ->
case {should_archive_peer(LUser, LServer, Prefs, Peer), Pkt} of
{true, #message{meta = #{sm_copy := true}}} ->
ok; % Already stored.
{true, _, true} ->
ok; % Stored in muc archive.
{true, _, _} ->
{true, _} ->
case ejabberd_hooks:run_fold(store_mam_message, LServer, Pkt,
[LUser, LServer, Peer, <<"">>, chat, Dir]) of
[LUser, LServer, Peer, <<"">>, chat, Dir, StoredInMucMam]) of
#message{} -> ok;
_ -> pass
end;
{false, _, _} ->
{false, _} ->
pass
end;
{error, _} ->
@@ -1164,15 +1168,16 @@ store_muc(MUCState, Pkt, RoomJID, Peer, Nick) ->
{U, S, _} = jid:tolower(RoomJID),
LServer = MUCState#state.server_host,
case ejabberd_hooks:run_fold(store_mam_message, LServer, Pkt,
[U, S, Peer, Nick, groupchat, recv]) of
[U, S, Peer, Nick, groupchat, recv, false]) of
#message{} -> ok;
_ -> pass
end;
false ->
pass
end.
store_mam_message(Pkt, U, S, Peer, Nick, Type, Dir) ->
store_mam_message(_Pkt, _U, _S, _Peer, _Nick, _Type, _Dir, true) ->
ok;
store_mam_message(Pkt, U, S, Peer, Nick, Type, Dir, false) ->
LServer = ejabberd_router:host_of_route(S),
US = {U, S},
ID = get_stanza_id(Pkt),
+4
View File
@@ -29,6 +29,7 @@
%% API
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
additional_namespaces/1,
extended_fields/1, store/10, write_prefs/4, get_prefs/2, select/6,
remove_from_archive/3,
is_empty_for_user/2, is_empty_for_room/3, delete_old_messages_batch/5,
@@ -189,6 +190,9 @@ delete_old_messages_batch(LServer, TimeStamp, Type, Batch, LastUS) ->
{error, Err}
end.
additional_namespaces(_) ->
[].
extended_fields(_) ->
[].
+10 -2
View File
@@ -30,6 +30,7 @@
%% API
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
additional_namespaces/1,
extended_fields/1, store/10, write_prefs/4, get_prefs/2, select/7, export/1, remove_from_archive/3,
is_empty_for_user/2, is_empty_for_room/3, select_with_mucsub/6,
delete_old_messages_batch/4, count_messages_to_delete/3]).
@@ -283,11 +284,18 @@ delete_old_messages(ServerHost, TimeStamp, Type) ->
end,
ok.
additional_namespaces(LServer) ->
case ejabberd_option:sql_type(LServer) of
mysql ->
[?NS_MAM_FULLTEXT_0];
_ ->
[]
end.
extended_fields(LServer) ->
case ejabberd_option:sql_type(LServer) of
mysql ->
[{withtext, <<"">>},
#xdata_field{var = <<"{urn:xmpp:fulltext:0}fulltext">>,
[#xdata_field{var = <<"{urn:xmpp:fulltext:0}fulltext">>,
type = 'text-single',
label = <<"Search the text">>,
values = []}];
+1 -1
View File
@@ -513,7 +513,7 @@ process_mix_message(#message{from = From, to = To,
Msg2 = xmpp:put_meta(Msg1, stanza_id, MamID),
case ejabberd_hooks:run_fold(
store_mam_message, ServerHost, Msg2,
[Chan, Host, BFrom, Nick, groupchat, recv]) of
[Chan, Host, BFrom, Nick, groupchat, recv, false]) of
#message{} ->
multicast(Mod, ServerHost, Chan, Host,
?NS_MIX_NODES_MESSAGES,
+19 -9
View File
@@ -26,6 +26,7 @@
-author('alexey@process-one.net').
-protocol({xep, 45, '1.35.3', '0.5.0', "complete", ""}).
-protocol({xep, 249, '1.2', '0.5.0', "complete", ""}).
-protocol({xep, 421, '1.0.1', '23.10', "complete", ""}).
-protocol({xep, 486, '0.1.0', '24.07', "complete", ""}).
-ifndef(GEN_SERVER).
-define(GEN_SERVER, gen_server).
@@ -729,10 +730,6 @@ process_disco_info(#iq{type = get, from = From, to = To, lang = Lang,
true -> [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1, ?NS_MAM_2];
false -> []
end,
OccupantIdFeatures = case gen_mod:is_loaded(ServerHost, mod_muc_occupantid) of
true -> [?NS_OCCUPANT_ID];
false -> []
end,
RSMFeatures = case RMod:rsm_supported() of
true -> [?NS_RSM];
false -> []
@@ -743,8 +740,8 @@ process_disco_info(#iq{type = get, from = From, to = To, lang = Lang,
end,
Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
?NS_MUC, ?NS_VCARD, ?NS_MUCSUB, ?NS_MUC_UNIQUE,
?NS_MUC_STABLE_ID
| RegisterFeatures ++ RSMFeatures ++ MAMFeatures ++ OccupantIdFeatures],
?NS_MUC_STABLE_ID, ?NS_OCCUPANT_ID
| RegisterFeatures ++ RSMFeatures ++ MAMFeatures],
Name = mod_muc_opt:name(ServerHost),
Identity = #identity{category = <<"conference">>,
type = <<"text">>,
@@ -1512,9 +1509,21 @@ mod_options(Host) ->
mod_doc() ->
#{desc =>
[?T("This module provides support for https://xmpp.org/extensions/xep-0045.html"
"[XEP-0045: Multi-User Chat]. Users can discover existing rooms, "
"join or create them. Occupants of a room can chat in public or have private chats."), "",
[?T("This module provides support for "
"https://xmpp.org/extensions/xep-0045.html[Multi-User Chat] (MUC). "
"Users can discover existing rooms, join or create them. "
"Occupants of a room can chat in public or have private chats."), "",
?T("Protocols implemented in this module:"), "",
"- https://xmpp.org/extensions/xep-0045.html"
"[XEP-0045: Multi-User Chat]",
"- https://xmpp.org/extensions/xep-0249.html"
"[XEP-0249: Direct MUC Invitations]",
"- https://xmpp.org/extensions/xep-0421.html"
"[XEP-0421: Occupant identifiers for semi-anonymous MUCs]",
"- https://xmpp.org/extensions/xep-0486.html"
"[XEP-0486: MUC Avatars]",
"- https://docs.ejabberd.im/developer/xmpp-clients-bots/extensions/muc-sub/"
"[Muc/Sub: Multi-User Chat Subscriptions]", "",
?T("The MUC service allows any Jabber ID to register a nickname, so "
"nobody else can use that nickname in any room in the MUC "
"service. To register a nickname, open the Service Discovery in "
@@ -1531,6 +1540,7 @@ mod_doc() ->
"are not clustered nor fault-tolerant: if the node managing a "
"set of rooms goes down, the rooms disappear and they will be "
"recreated on an available node on first connection attempt.")],
note => "incorporated 'mod_muc_occupantid' in 26.02",
opts =>
[{access,
#{value => ?T("AccessName"),
+6 -4
View File
@@ -478,9 +478,11 @@ transform(#muc_room{opts = Opts} = R) ->
Opts
end,
Opts4 =
case lists:keyfind(hats_defs, 1, Opts2) of
false ->
{hats_users, HatsUsers} = lists:keyfind(hats_users, 1, Opts2),
case {lists:keyfind(hats_defs, 1, Opts2),
lists:keyfind(hats_users, 1, Opts2)} of
{false, false} ->
[{hats_defs, []}, {hats_users, []} | Opts2];
{false, {hats_users, HatsUsers}} ->
{HatsDefs, HatsUsers2} =
lists:foldl(fun({Jid, UriTitleList}, {Defs, Assigns}) ->
Defs2 =
@@ -500,7 +502,7 @@ transform(#muc_room{opts = Opts} = R) ->
Opts3 =
lists:keyreplace(hats_users, 1, Opts2, {hats_users, maps:to_list(HatsUsers2)}),
[{hats_defs, maps:to_list(HatsDefs)} | Opts3];
{_, _} ->
{{hats_defs, _}, {hats_users, _}} ->
Opts2
end,
R#muc_room{opts = Opts4};
-128
View File
@@ -1,128 +0,0 @@
%%%----------------------------------------------------------------------
%%% File : mod_muc_occupantid.erl
%%% Author : Badlop <badlop@process-one.net>
%%% Purpose : Add Occupant Ids to stanzas in anonymous MUC rooms (XEP-0421)
%%% Created :
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2026 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 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
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(mod_muc_occupantid).
-author('badlop@process-one.net').
-protocol({xep, 421, '1.0.1', '23.10', "complete", ""}).
-behaviour(gen_mod).
-include_lib("xmpp/include/xmpp.hrl").
-include("logger.hrl").
-include("translate.hrl").
-include("mod_muc_room.hrl").
-export([start/2, stop/1,
mod_options/1, mod_doc/0, depends/2]).
-export([filter_packet/3, remove_room/3]).
%%%
%%% gen_mod
%%%
start(_Host, _Opts) ->
create_table(),
{ok, [{hook, muc_filter_presence, filter_packet, 10},
{hook, muc_filter_message, filter_packet, 10},
{hook, remove_room, remove_room, 50}]}.
stop(_Host) ->
ok.
%%%
%%% Hooks
%%%
filter_packet(Packet, State, _Nick) ->
add_occupantid_packet(Packet, State#state.jid).
remove_room(_LServer, _Name, Host) ->
delete_salt(Host).
%%%
%%% XEP-0421 Occupant-id
%%%
add_occupantid_packet(Packet, RoomJid) ->
From = xmpp:get_from(Packet),
OccupantId = calculate_occupantid(From, RoomJid),
OccupantElement = #occupant_id{id = OccupantId},
xmpp:append_subtags(xmpp:remove_subtag(Packet, OccupantElement), [OccupantElement]).
calculate_occupantid(From, RoomJid) ->
Term = {get_salt(RoomJid#jid.lserver), RoomJid, jid:remove_resource(From)},
misc:term_to_base64(crypto:hash(sha256, io_lib:format("~p", [Term]))).
%%%
%%% Table storing rooms' salt
%%%
-record(muc_occupant_id, {service_jid, salt}).
create_table() ->
ejabberd_mnesia:create(?MODULE, muc_occupant_id,
[{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, muc_occupant_id)},
{type, set}]).
get_salt(ServiceJid) ->
case mnesia:dirty_read(muc_occupant_id, ServiceJid) of
[] ->
Salt = p1_rand:get_string(),
ok = write_salt(ServiceJid, Salt),
Salt;
[#muc_occupant_id{salt = Salt}] ->
Salt
end.
write_salt(ServiceJid, Salt) ->
mnesia:dirty_write(#muc_occupant_id{service_jid = ServiceJid, salt = Salt}).
delete_salt(ServiceJid) ->
mnesia:dirty_delete(muc_occupant_id, ServiceJid).
%%%
%%% Doc
%%%
mod_options(_Host) ->
[].
mod_doc() ->
#{desc =>
[?T("This module implements "
"https://xmpp.org/extensions/xep-0421.html"
"[XEP-0421: Anonymous unique occupant identifiers for MUCs]."), "",
?T("When the module is enabled, the feature is enabled "
"in all semi-anonymous rooms.")],
note => "added in 23.10"
}.
depends(_, _) ->
[{mod_muc, hard}].
+91 -71
View File
@@ -310,6 +310,7 @@ init([Host, ServerHost, Access, Room, HistorySize,
history = lqueue_new(HistorySize, QueueType),
jid = jid:make(Room, Host),
just_created = true,
salt = p1_rand:get_string(),
room_queue = RoomQueue,
room_shaper = Shaper}),
State1 = set_affiliation(Creator, owner, State),
@@ -334,6 +335,7 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType])
room = Room,
history = lqueue_new(HistorySize, QueueType),
jid = Jid,
salt = p1_rand:get_string(),
room_queue = RoomQueue,
room_shaper = Shaper}),
add_to_log(room_existence, started, State),
@@ -638,11 +640,9 @@ normal_state({route, ToNick,
FromNick),
X = #muc_user{},
Packet2 = xmpp:set_subtag(Packet, X),
case ejabberd_hooks:run_fold(muc_filter_message,
StateData#state.server_host,
xmpp:put_meta(Packet2, mam_ignore, true),
[StateData, FromNick]) of
drop ->
case filter_message_hook(StateData, FromNick,
xmpp:put_meta(Packet2, mam_ignore, true)) of
drop ->
ok;
Packet3 ->
PrivMsg = xmpp:set_from(xmpp:del_meta(Packet3, mam_ignore), FromNickJID),
@@ -1092,30 +1092,26 @@ process_groupchat_message(#message{from = From, lang = Lang} = Packet, StateData
end,
case IsAllowed of
true ->
case
ejabberd_hooks:run_fold(muc_filter_message,
StateData#state.server_host,
Packet,
[StateData, FromNick])
of
case filter_message_hook(StateData, FromNick,
Packet) of
drop ->
{next_state, normal_state, StateData};
NewPacket1 ->
NewPacket = xmpp:put_meta(xmpp:remove_subtag(
add_stanza_id(NewPacket1, StateData), #nick{}),
muc_sender_real_jid, From),
NewPacket2 = xmpp:put_meta(NewPacket1, muc_sender_real_jid, From),
NewPacket3 = xmpp:remove_subtag(NewPacket2, #nick{}),
{ForHistory, ToSend} = add_stanza_id(NewPacket3, StateData),
Node = if Subject == [] -> ?NS_MUCSUB_NODES_MESSAGES;
true -> ?NS_MUCSUB_NODES_SUBJECT
end,
NewStateData2 = check_message_for_retractions(NewPacket1, NewStateData1),
NewStateData2 = check_message_for_retractions(ToSend, NewStateData1),
send_wrapped_multiple(
jid:replace_resource(StateData#state.jid, FromNick),
get_users_and_subscribers_with_node(Node, StateData),
NewPacket, Node, NewStateData2),
NewStateData3 = case has_body_or_subject(NewPacket) of
jid:replace_resource(StateData#state.jid, FromNick),
get_users_and_subscribers_with_node(Node, StateData),
ToSend, Node, NewStateData2),
NewStateData3 = case has_body_or_subject(ToSend) of
true ->
add_message_to_history(FromNick, From,
NewPacket,
ForHistory,
NewStateData2);
false ->
NewStateData2
@@ -1180,28 +1176,20 @@ check_message_for_retractions(Packet,
State
end.
-spec add_stanza_id(Packet :: message(), State :: state()) -> message().
add_stanza_id(Packet, #state{jid = JID}) ->
{AddId, NewPacket} =
case xmpp:get_meta(Packet, stanza_id, false) of
false ->
GenID = erlang:system_time(microsecond),
{true, xmpp:put_meta(Packet, stanza_id, GenID)};
-spec add_stanza_id(Packet :: message(), State :: state()) -> {message(), message()}.
add_stanza_id(#message{meta = Meta} = Packet, #state{jid = JID}) ->
case Meta of
#{stanza_id := _StanzaId, mam_archived := _Archived} ->
{Packet, Packet};
#{stanza_id := StanzaId} ->
ToSend = xmpp:append_subtags(Packet, [#stanza_id{by = JID, id = integer_to_binary(StanzaId)}]),
{Packet, ToSend};
_ ->
StanzaIds = xmpp:get_subtags(Packet, #stanza_id{by = #jid{}}),
HasOurStanzaId = lists:any(
fun(#stanza_id{by = JID2}) when JID == JID2 -> true;
(_) -> false
end, StanzaIds),
{not HasOurStanzaId, Packet}
end,
if
AddId ->
ID = xmpp:get_meta(NewPacket, stanza_id),
IDs = integer_to_binary(ID),
xmpp:append_subtags(NewPacket, [#stanza_id{by = JID, id = IDs}]);
true ->
Packet
StanzaId = xmpp:get_meta(Packet, stanza_id, mod_mam:make_id()),
Stripped = mod_mam:strip_my_stanza_id(Packet, JID#jid.lserver),
ForHistory = xmpp:put_meta(Stripped, stanza_id, StanzaId),
ToSend = xmpp:append_subtags(Packet, [#stanza_id{by = JID, id = integer_to_binary(StanzaId)}]),
{ForHistory, ToSend}
end.
-spec process_normal_message(jid(), message(), state()) -> state().
@@ -1401,10 +1389,7 @@ process_presence(Nick, #presence{from = From, type = Type0} = Packet0, StateData
IsOnline = is_user_online(From, StateData),
if Type0 == available;
IsOnline and ((Type0 == unavailable) or (Type0 == error)) ->
case ejabberd_hooks:run_fold(muc_filter_presence,
StateData#state.server_host,
Packet0,
[StateData, Nick]) of
case filter_presence_hook(StateData, Nick, Packet0) of
drop ->
{next_state, normal_state, StateData};
#presence{} = Packet ->
@@ -1561,7 +1546,8 @@ get_users_and_subscribers_aux(Subscribers, StateData) ->
#user{jid = jid:make(LBareJID),
nick = Nick,
role = none,
last_presence = undefined},
last_presence = undefined,
occupant_id = <<>>},
Acc);
true ->
Acc
@@ -2130,10 +2116,44 @@ set_subscriber(JID, Nick, Nodes,
end,
NewStateData.
-spec calculate_occupant_id(jid(), state()) -> binary().
calculate_occupant_id(Jid, #state{salt = Salt, jid = RoomJid}) ->
JidS = jid:encode(jid:remove_resource(Jid)),
RoomJidS = jid:encode(RoomJid),
Term = <<Salt/binary, ":", RoomJidS/binary, ":", JidS/binary>>,
misc:term_to_base64(crypto:hash(sha256, Term)).
-spec filter_message_hook(state(), binary(), #message{}) -> drop | stanza().
filter_message_hook(#state{users = Users} = StateData, Nick, #message{from = From} = Message) ->
OccupantId = case maps:find(jid:tolower(From), Users) of
{ok, #user{occupant_id = Id}} -> Id;
_ -> calculate_occupant_id(From, StateData)
end,
Message2 = xmpp:append_subtags(xmpp:remove_subtag(Message, #occupant_id{}),
[#occupant_id{id = OccupantId}]),
ejabberd_hooks:run_fold(muc_filter_message,
StateData#state.server_host,
Message2,
[StateData, Nick]).
-spec filter_presence_hook(state(), binary(), #presence{}) -> drop | #presence{}.
filter_presence_hook(#state{users = Users} = StateData, Nick, #presence{from = From} = Pres) ->
OccupantId = case maps:find(jid:tolower(From), Users) of
{ok, #user{occupant_id = Id}} -> Id;
_ -> calculate_occupant_id(From, StateData)
end,
Pres2 = xmpp:append_subtags(xmpp:remove_subtag(Pres, #occupant_id{}),
[#occupant_id{id = OccupantId}]),
ejabberd_hooks:run_fold(muc_filter_presence,
StateData#state.server_host,
Pres2,
[StateData, Nick]).
-spec add_online_user(jid(), binary(), role(), state()) -> state().
add_online_user(JID, Nick, Role, StateData) ->
tab_add_online_user(JID, StateData),
User = #user{jid = JID, nick = Nick, role = Role},
User = #user{jid = JID, nick = Nick, role = Role, occupant_id = calculate_occupant_id(JID, StateData)},
reset_hibernate_timer(update_online_user(JID, User, StateData)).
-spec remove_online_user(jid(), state()) -> state().
@@ -2969,7 +2989,8 @@ add_message_to_history(FromNick, FromJID, Packet, StateData) ->
add_to_log(text, {FromNick, Packet}, StateData),
case check_subject(Packet) of
[] ->
TimeStamp = erlang:timestamp(),
StanzaId = xmpp:get_meta(Packet, stanza_id, mod_mam:make_id()),
TimeStamp = misc:usec_to_now(StanzaId),
AddrPacket = case (StateData#state.config)#config.anonymous of
true -> Packet;
false ->
@@ -3043,10 +3064,8 @@ send_subject(JID, #state{subject_author = {Nick, AuthorJID}} = StateData) ->
end,
Packet = #message{from = AuthorJID,
to = JID, type = groupchat, subject = Subject},
case ejabberd_hooks:run_fold(muc_filter_message,
StateData#state.server_host,
xmpp:put_meta(Packet, mam_ignore, true),
[StateData, Nick]) of
case filter_message_hook(StateData, Nick,
xmpp:put_meta(Packet, mam_ignore, true)) of
drop ->
ok;
NewPacket1 ->
@@ -4271,6 +4290,8 @@ set_opts2([{Opt, Val} | Opts], StateData) ->
hats_users ->
StateData#state{hats_users = maps:from_list(Val)};
hibernation_time -> StateData;
salt ->
StateData#state{salt = Val};
Other ->
?INFO_MSG("Unknown MUC room option, will be discarded: ~p", [Other]),
StateData
@@ -4353,6 +4374,7 @@ make_opts(StateData, Hibernation) ->
{hats_defs, maps:to_list(StateData#state.hats_defs)},
{hats_users, maps:to_list(StateData#state.hats_users)},
{hibernation_time, if Hibernation -> erlang:system_time(microsecond); true -> undefined end},
{salt, StateData#state.salt},
{subscribers, Subscribers}].
expand_opts(CompactOpts) ->
@@ -4377,14 +4399,16 @@ expand_opts(CompactOpts) ->
Subject = proplists:get_value(subject, CompactOpts, <<"">>),
Subscribers = proplists:get_value(subscribers, CompactOpts, []),
HibernationTime = proplists:get_value(hibernation_time, CompactOpts, 0),
Salt = proplists:get_value(hibernation_time, CompactOpts, <<>>),
[{subject, Subject},
{subject_author, SubjectAuthor},
{subscribers, Subscribers},
{hibernation_time, HibernationTime}
{hibernation_time, HibernationTime},
{salt, Salt}
| lists:reverse(Opts1)].
config_fields() ->
[subject, subject_author, subscribers, hibernate_time | record_info(fields, config)].
[subject, subject_author, subscribers, hibernate_time, salt | record_info(fields, config)].
-spec destroy_room(muc_destroy(), state()) -> {result, undefined, stop}.
destroy_room(DEl, StateData) ->
@@ -4447,7 +4471,7 @@ make_disco_info(From, StateData) ->
?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
?NS_COMMANDS,
?NS_MESSAGE_MODERATE_0, ?NS_MESSAGE_MODERATE_1,
?NS_MESSAGE_RETRACT,
?NS_MESSAGE_RETRACT, ?NS_OCCUPANT_ID,
?CONFIG_OPT_TO_FEATURE((Config#config.public),
<<"muc_public">>, <<"muc_hidden">>),
?CONFIG_OPT_TO_FEATURE((Config#config.persistent),
@@ -4472,16 +4496,13 @@ make_disco_info(From, StateData) ->
true -> [?NS_HATS];
false -> []
end
++ case gen_mod:is_loaded(StateData#state.server_host, mod_muc_occupantid) of
true ->
[?NS_OCCUPANT_ID];
_ ->
[]
end
++ case {gen_mod:is_loaded(StateData#state.server_host, mod_mam),
++ case {gen_mod:is_loaded(ServerHost, mod_mam),
Config#config.mam} of
{true, true} ->
[?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1, ?NS_MAM_2, ?NS_SID_0];
Mod = gen_mod:db_mod(ServerHost, mod_mam),
AdditionalNamespaces = Mod:additional_namespaces(ServerHost),
[?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1, ?NS_MAM_2, ?NS_SID_0
| AdditionalNamespaces];
_ ->
[]
end,
@@ -5370,9 +5391,10 @@ add_presence_hats(JID, Pres, StateData) ->
false ->
false;
{URI, Title, Hue} ->
#muc_hat{uri = URI,
title = Title,
hue = Hue}
{true,
#muc_hat{uri = URI,
title = Title,
hue = Hue}}
end
end,
UserHats),
@@ -5423,10 +5445,8 @@ process_iq_moderate(From, #iq{type = set, lang = Lang}, Id, Reason,
from = From,
sub_els = SubEl},
{FromNick, _Role} = get_participant_data(From, StateData),
Packet = ejabberd_hooks:run_fold(muc_filter_message,
StateData#state.server_host,
xmpp:put_meta(Packet0, mam_ignore, true),
[StateData, FromNick]),
Packet = filter_message_hook(StateData, FromNick,
xmpp:put_meta(Packet0, mam_ignore, true)),
send_wrapped_multiple(JID,
get_users_and_subscribers_with_node(?NS_MUCSUB_NODES_MESSAGES, StateData),
Packet, ?NS_MUCSUB_NODES_MESSAGES, StateData),
@@ -5813,7 +5833,7 @@ send_wrapped_multiple(From, Users, Packet, Node, State) ->
ok
end;
_ ->
false
ok
end;
_ ->
ok
+19 -48
View File
@@ -49,16 +49,12 @@
%% @format-begin
start(Host, Opts) ->
case pubsub_host(Host, Opts) of
{error, _Reason} = Error ->
Error;
PubsubHost ->
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_local_features, 50),
ejabberd_hooks:add(disco_info, Host, ?MODULE, get_info, 50),
ejabberd_hooks:add(s2s_out_auth_result, Host, ?MODULE, out_auth_result, 50),
ejabberd_hooks:add(s2s_in_auth_result, Host, ?MODULE, in_auth_result, 50),
gen_mod:start_child(?MODULE, Host, PubsubHost)
end.
PubsubHost = mod_pubsub_serverinfo_opt:pubsub_host(Opts),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_local_features, 50),
ejabberd_hooks:add(disco_info, Host, ?MODULE, get_info, 50),
ejabberd_hooks:add(s2s_out_auth_result, Host, ?MODULE, out_auth_result, 50),
ejabberd_hooks:add(s2s_in_auth_result, Host, ?MODULE, in_auth_result, 50),
gen_mod:start_child(?MODULE, Host, #{pubsub => PubsubHost}).
stop(Host) ->
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_local_features, 50),
@@ -67,7 +63,7 @@ stop(Host) ->
ejabberd_hooks:delete(s2s_in_auth_result, Host, ?MODULE, in_auth_result, 50),
gen_mod:stop_child(?MODULE, Host).
init([Host, PubsubHost]) ->
init([Host, #{pubsub := PubsubHost}]) ->
TRef =
timer:send_interval(
timer:minutes(5), self(), update_pubsub),
@@ -135,7 +131,7 @@ handle_call(_Request, _From, State) ->
handle_info({iq_reply, IQReply, {LServer, RServer}}, #state{monitors = Mons} = State) ->
case IQReply of
#iq{type = result, sub_els = [El]} ->
case xmpp:decode(El) of
try xmpp:decode(El) of
#disco_info{features = Features} ->
case lists:member(?NS_URN_SERVERINFO, Features) of
true ->
@@ -156,6 +152,9 @@ handle_info({iq_reply, IQReply, {LServer, RServer}}, #state{monitors = Mons} = S
end;
_ ->
{noreply, State}
catch
_:{xmpp_codec, _Reason} ->
{noreply, State}
end;
_ ->
{noreply, State}
@@ -191,10 +190,14 @@ depends(_Host, _Opts) ->
[{mod_pubsub, hard}].
mod_options(_Host) ->
[{pubsub_host, undefined}].
[{pubsub_host,
gen_mod:depend_on([{mod_pubsub, host}, {mod_pubsub, hosts}],
fun([Host, Hosts]) ->
hd(gen_mod:get_opt_hosts(#{host => Host, hosts => Hosts}))
end)}].
mod_opt_type(pubsub_host) ->
econf:either(undefined, econf:host()).
econf:host().
mod_doc() ->
#{desc =>
@@ -337,7 +340,7 @@ get_info(Acc, Host, Mod, Node, Lang)
when Mod == undefined orelse Mod == mod_disco, Node == <<"">> ->
case mod_disco:get_info(Acc, Host, Mod, Node, Lang) of
[#xdata{fields = Fields} = XD | Rest] ->
PubsubHost = pubsub_host(Host),
PubsubHost = mod_pubsub_serverinfo_opt:pubsub_host(Host),
NodeField =
#xdata_field{var = <<"serverinfo-pubsub-node">>,
values = [<<"xmpp:", PubsubHost/binary, "?;node=serverinfo">>]},
@@ -346,7 +349,7 @@ get_info(Acc, Host, Mod, Node, Lang)
Acc
end;
get_info(Acc, Host, Mod, Node, _Lang) when Node == <<"">>, is_atom(Mod) ->
PubsubHost = pubsub_host(Host),
PubsubHost = mod_pubsub_serverinfo_opt:pubsub_host(Host),
[#xdata{type = result,
fields =
[#xdata_field{type = hidden,
@@ -357,35 +360,3 @@ get_info(Acc, Host, Mod, Node, _Lang) when Node == <<"">>, is_atom(Mod) ->
| Acc];
get_info(Acc, _Host, _Mod, _Node, _Lang) ->
Acc.
pubsub_host(Host) ->
{ok, PubsubHost} =
gen_server:call(
gen_mod:get_module_proc(Host, ?MODULE), pubsub_host),
PubsubHost.
pubsub_host(Host, Opts) ->
case gen_mod:get_opt(pubsub_host, Opts) of
undefined ->
PubsubHost = hd(get_mod_pubsub_hosts(Host)),
?INFO_MSG("No pubsub_host in configuration for ~p, choosing ~s", [?MODULE, PubsubHost]),
PubsubHost;
PubsubHost ->
case check_pubsub_host_exists(Host, PubsubHost) of
true ->
PubsubHost;
false ->
{error, {pubsub_host_does_not_exist, PubsubHost}}
end
end.
check_pubsub_host_exists(Host, PubsubHost) ->
lists:member(PubsubHost, get_mod_pubsub_hosts(Host)).
get_mod_pubsub_hosts(Host) ->
case gen_mod:get_module_opt(Host, mod_pubsub, hosts) of
[] ->
[gen_mod:get_module_opt(Host, mod_pubsub, host)];
PubsubHosts ->
PubsubHosts
end.
+1 -1
View File
@@ -5,7 +5,7 @@
-export([pubsub_host/1]).
-spec pubsub_host(gen_mod:opts() | global | binary()) -> 'undefined' | binary().
-spec pubsub_host(gen_mod:opts() | global | binary()) -> binary().
pubsub_host(Opts) when is_map(Opts) ->
gen_mod:get_opt(pubsub_host, Opts);
pubsub_host(Host) ->
+4 -4
View File
@@ -34,7 +34,7 @@
-export([mod_doc/0]).
%% ejabberd_hooks callbacks.
-export([disco_sm_features/5, c2s_session_pending/1, c2s_copy_session/2,
c2s_session_resumed/1, c2s_handle_cast/2, c2s_stanza/3, mam_message/7,
c2s_session_resumed/1, c2s_handle_cast/2, c2s_stanza/3, mam_message/8,
offline_message/1, remove_user/2]).
%% gen_iq_handler callback.
@@ -379,8 +379,8 @@ c2s_stanza(State, _Pkt, _SendResult) ->
State.
-spec mam_message(message() | drop, binary(), binary(), jid(),
binary(), chat | groupchat, recv | send) -> message().
mam_message(#message{} = Pkt, LUser, LServer, _Peer, _Nick, chat, Dir) ->
binary(), chat | groupchat, recv | send, boolean()) -> message().
mam_message(#message{} = Pkt, LUser, LServer, _Peer, _Nick, chat, Dir, _InMucMam) ->
case lookup_sessions(LUser, LServer) of
{ok, [_|_] = Clients} ->
case drop_online_sessions(LUser, LServer, Clients) of
@@ -394,7 +394,7 @@ mam_message(#message{} = Pkt, LUser, LServer, _Peer, _Nick, chat, Dir) ->
ok
end,
Pkt;
mam_message(Pkt, _LUser, _LServer, _Peer, _Nick, _Type, _Dir) ->
mam_message(Pkt, _LUser, _LServer, _Peer, _Nick, _Type, _Dir, _InMucMam) ->
Pkt.
-spec offline_message({any(), message()}) -> {any(), message()}.
+2 -1
View File
@@ -215,8 +215,9 @@ process_iq(#iq{type = get, from = From, to = To, id = ID, lang = Lang} = IQ,
Instr = translate:translate(
Lang, ?T("Choose a username and password to register "
"with this server")),
IsPreAuth = maps:get(pre_auth, xmpp:get_meta(IQ), false) == true,
URL = mod_register_opt:redirect_url(Server),
if (URL /= undefined) and not IsRegistered ->
if (URL /= undefined) and not IsRegistered and not IsPreAuth ->
Desc = str:translate_and_format(Lang, ?T("To register, visit ~s"), [URL]),
xmpp:make_iq_result(
IQ, #register{instructions = Desc,
+3 -2
View File
@@ -638,8 +638,9 @@ mod_doc() ->
"important to include the last / character in the URL, "
"otherwise the subpages URL will be incorrect."), "",
?T("This module is enabled in 'listen' -> 'ejabberd_http' -> "
"_`listen-options.md#request_handlers|request_handlers`_, "
"no need to enable in 'modules'."),
"_`listen-options.md#request_handlers|request_handlers`_."), "",
?T("There is no need to enable this module in 'modules', "
"but it adds a link to the register page in WebAdmin menu."),
?T("The module depends on _`mod_register`_ where all the "
"configuration is performed.")],
example =>
+118 -24
View File
@@ -44,9 +44,11 @@
import_info/0, process_local_iq/1, get_user_roster_items/2,
import/5, get_roster/2, push_item/3,
import_start/2, import_stop/2, is_subscribed/2,
user_send_packet/1,
c2s_self_presence/1, in_subscription/2,
out_subscription/1, set_items/3, remove_user/2,
get_jid_info/4, encode_item/1, get_versioning_feature/2,
get_jid_info/4, encode_item/1,
get_versioning_feature/2, pre_approval_stream_feature/2,
roster_version/2, mod_doc/0,
mod_opt_type/1, mod_options/1, set_roster/1, del_roster/3,
process_rosteritems/5,
@@ -101,6 +103,8 @@ start(Host, Opts) ->
{hook, remove_user, remove_user, 50},
{hook, c2s_self_presence, c2s_self_presence, 50},
{hook, c2s_post_auth_features, get_versioning_feature, 50},
{hook, c2s_post_auth_features, pre_approval_stream_feature, 50},
{hook, user_send_packet, user_send_packet, 50},
{hook, webadmin_menu_hostuser, webadmin_menu_hostuser, 50},
{hook, webadmin_page_hostuser, webadmin_page_hostuser, 50},
{hook, webadmin_user, webadmin_user, 50},
@@ -205,6 +209,16 @@ get_versioning_feature(Acc, Host) ->
Acc
end.
%% Indicate support for pre-approval as of RFC6121 section 3.4
-spec pre_approval_stream_feature([xmpp_element()], binary()) -> [xmpp_element()].
pre_approval_stream_feature(Acc, Host) ->
case gen_mod:is_loaded(Host, ?MODULE) of
true ->
[#feature_pre_approval{} | Acc];
false ->
Acc
end.
-spec roster_version(binary(), binary()) -> undefined | binary().
roster_version(LServer, LUser) ->
case mod_roster_opt:store_current_id(LServer) of
@@ -406,6 +420,7 @@ encode_item(Item) ->
both -> subscribe;
_ -> undefined
end,
approved = Item#roster.approved,
groups = Item#roster.groups}.
-spec decode_item(roster_item(), #roster{}, boolean()) -> #roster{}.
@@ -416,6 +431,7 @@ decode_item(#roster_item{subscription = remove} = Item, R, _) ->
ask = none,
groups = [],
askmessage = <<"">>,
approved = false,
xs = []};
decode_item(Item, R, Managed) ->
R#roster{jid = jid:tolower(Item#roster_item.jid),
@@ -424,6 +440,7 @@ decode_item(Item, R, Managed) ->
Sub when Managed -> Sub;
_ -> R#roster.subscription
end,
approved = Item#roster_item.approved,
groups = Item#roster_item.groups}.
-spec process_iq_set(iq()) -> iq().
@@ -574,41 +591,58 @@ process_subscription(Direction, User, Server, JID1,
Item = get_roster_item(LUser, LServer, LJID),
NewState = case Direction of
out ->
out_state_change(Item#roster.subscription,
Item#roster.ask, Type);
out_state_change(Item#roster.subscription,
Item#roster.ask, Type);
in ->
in_state_change(Item#roster.subscription,
Item#roster.ask, Type)
case {Type, Item#roster.approved} of
{subscribe, true} ->
{TSub, TAsk} = in_state_change(Item#roster.subscription,
Item#roster.ask, Type),
out_state_change(TSub, TAsk, subscribed);
_ ->
in_state_change(Item#roster.subscription,
Item#roster.ask, Type)
end
end,
AutoReply = case Direction of
out -> none;
in ->
in_auto_reply(Item#roster.subscription,
Item#roster.ask, Type)
case {Type, Item#roster.approved} of
{subscribe, true} ->
subscribed;
_ ->
in_auto_reply(Item#roster.subscription,
Item#roster.ask, Type)
end
end,
AskMessage = case NewState of
{_, both} -> Reason;
{_, in} -> Reason;
_ -> <<"">>
end,
{Unapproved, Approved} = case {Direction, Type, Item#roster.approved} of
{out, unsubscribed, true} ->
{true, false};
{_, _, Approved0} ->
{false, Approved0}
end,
case NewState of
none ->
{none, AutoReply};
NewItem = update_item(Item, Item#roster.subscription, Approved, Item#roster.ask, SubEls, AskMessage),
{maybe_push_item(Unapproved, LUser, LServer, LJID, Item, NewItem), AutoReply};
{none, none} when Item#roster.subscription == none,
Item#roster.ask == in ->
del_roster_t(LUser, LServer, LJID), {none, AutoReply};
del_roster_t(LUser, LServer, LJID),
case Unapproved of
true ->
NewItem = update_item(Item, none, Approved, none, SubEls, AskMessage),
{{push, Item, NewItem}, AutoReply};
false ->
{none, AutoReply}
end;
{Subscription, Pending} ->
NewItem = Item#roster{subscription = Subscription,
ask = Pending,
name = get_nick_subels(SubEls, Item#roster.name),
xs = SubEls,
askmessage = AskMessage},
roster_subscribe_t(LUser, LServer, LJID, NewItem),
case mod_roster_opt:store_current_id(LServer) of
true -> write_roster_version_t(LUser, LServer);
false -> ok
end,
{{push, Item, NewItem}, AutoReply}
NewItem = update_item(Item, Subscription, Approved, Pending, SubEls, AskMessage),
{prepare_push_item(LUser, LServer, LJID, Item, NewItem), AutoReply}
end
end,
case transaction(LUser, LServer, [LJID], F) of
@@ -631,7 +665,7 @@ process_subscription(Direction, User, Server, JID1,
encode_item(OldItem),
encode_item(NewItem))
end,
true;
not (Type == subscribe andalso Direction == in andalso NewItem#roster.approved);
none ->
false
end;
@@ -639,12 +673,33 @@ process_subscription(Direction, User, Server, JID1,
false
end.
update_item(Item, Subscription, Approved, Pending, SubEls, AskMessage) ->
Item#roster{subscription = Subscription,
approved = Approved,
ask = Pending,
name = get_nick_subels(SubEls, Item#roster.name),
xs = SubEls,
askmessage = AskMessage}.
get_nick_subels(SubEls, Default) ->
case xmpp:get_subtag(#presence{sub_els = SubEls}, #nick{}) of
{nick, N} -> N;
_ -> Default
end.
maybe_push_item(true, LUser, LServer, LJID, OldItem, NewItem) ->
prepare_push_item(LUser, LServer, LJID, OldItem, NewItem);
maybe_push_item(false, _LUser, _LServer, _LJID, _OldItem, _NewItem) ->
none.
prepare_push_item(LUser, LServer, LJID, OldItem, NewItem) ->
roster_subscribe_t(LUser, LServer, LJID, NewItem),
case mod_roster_opt:store_current_id(LServer) of
true -> write_roster_version_t(LUser, LServer);
false -> ok
end,
{push, OldItem, NewItem}.
%% in_state_change(Subscription, Pending, Type) -> NewState
%% NewState = none | {NewSubscription, NewPending}
-ifdef(ROSTER_GATEWAY_WORKAROUND).
@@ -946,6 +1001,43 @@ process_item_set_t(LUser, LServer, #roster_item{jid = JID1} = QueryItem) ->
end;
process_item_set_t(_LUser, _LServer, _) -> ok.
-spec user_send_packet({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}.
user_send_packet({#presence{type = subscribed, to = To} = Presence,
#{jid := #jid{luser = LUser, lserver = LServer} = Jid} = C2SState}) ->
LJID = jid:tolower(To),
{atomic, Item} = transaction(
LUser, LServer, [LJID],
fun() ->
get_roster_item(LUser, LServer, LJID)
end),
case Item of
#roster{subscription = Subscription, ask = Ask}
when Subscription == both;
Subscription == from, Ask == none;
Subscription == from, Ask == out ->
{drop, C2SState};
#roster{subscription = Subscription, ask = Ask}
when Subscription == to, Ask == in;
Subscription == none, Ask == in;
Subscription == none, Ask == both ->
{Presence, C2SState};
#roster{subscription = Subscription, ask = Ask}
when Subscription == to;
Subscription == none, Ask == none;
Subscription == none, Ask == out ->
transaction(
LUser, LServer, [LJID],
fun() ->
update_roster_t(LUser, LServer, LJID, Item#roster{approved = true})
end),
OldItem = encode_item(Item),
NewItem = OldItem#roster_item{approved = true},
push_item(Jid, OldItem, NewItem),
{drop, C2SState}
end;
user_send_packet(Acc) ->
Acc.
-spec c2s_self_presence({presence(), c2s_state()}) -> {presence(), c2s_state()}.
c2s_self_presence({_, #{pres_last := _}} = Acc) ->
Acc;
@@ -1029,12 +1121,12 @@ webadmin_page_hostuser(_, Host, Username, #request{path = [<<"roster">> | RPath]
Head = ?H1GL(<<"Roster">>, <<"modules/#mod_roster">>, <<"mod_roster">>),
%% Execute twice: first to perform the action, the second to get new roster
_ = make_webadmin_roster_table(Host, Username, R, RPath),
RV2 = make_webadmin_roster_table(Host, Username, R, RPath),
Set = [make_command(add_rosteritem,
R,
[{<<"localuser">>, Username}, {<<"localhost">>, Host}],
[]),
make_command(push_roster, R, [{<<"user">>, Username}, {<<"host">>, Host}], [])],
RV2 = make_webadmin_roster_table(Host, Username, R, RPath),
Get = [make_command(get_roster, R, [], [{only, presentation}]),
make_command(delete_rosteritem, R, [], [{only, presentation}]),
RV2],
@@ -1067,6 +1159,8 @@ make_webadmin_roster_table(Host, Username, R, RPath) ->
{{<<"000--error-parsing-jid">>, <<"localhost">>, <<"">>},
<<", Error parsing JID: ", Jid/binary>>}
end,
[Gr1 | Gs] = Groups,
GroupsSeparated = [Gr1 | lists:map(fun(Gx) -> ["," | Gx] end, Gs)],
{make_command(echo,
R,
[{<<"sentence">>, jid:encode(JidSplit)}],
@@ -1075,7 +1169,7 @@ make_webadmin_roster_table(Host, Username, R, RPath) ->
?C(<<Nick/binary, ProblematicBin/binary>>),
?C(Subscriptions),
?C(Pending),
?C(Groups),
?C(GroupsSeparated),
make_command(delete_rosteritem,
R,
[{<<"localuser">>, Username},
@@ -1179,7 +1273,7 @@ export(LServer) ->
import_info() ->
[{<<"roster_version">>, 2},
{<<"rostergroups">>, 3},
{<<"rosterusers">>, 10}].
{<<"rosterusers">>, 11}].
import_start(LServer, DBType) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
+20 -9
View File
@@ -130,17 +130,26 @@ need_transform({roster_version, {U, S}, Ver})
when is_list(U) orelse is_list(S) orelse is_list(Ver) ->
?INFO_MSG("Mnesia table 'roster_version' will be converted to binary", []),
true;
need_transform({roster, {_, _, _}, _, _, _, _, none, _, _, _, _}) ->
?INFO_MSG("Mnesia table 'roster' will be converted to use new boolean() 'approved' attribute.", []),
true;
need_transform(_) ->
false.
transform(#roster{usj = {U, S, {LU, LS, LR}},
us = {U1, S1},
jid = {U2, S2, R2},
name = Name,
groups = Gs,
askmessage = Ask,
xs = Xs} = R) ->
R#roster{usj = {iolist_to_binary(U), iolist_to_binary(S),
transform({roster, USJ, US, Jid, Name, Sub, none, Ask, Groups, AskMsg, XS}) ->
#roster{
us = US,
usj = USJ,
jid = Jid,
name = Name,
subscription = Sub,
approved = false,
ask = Ask,
groups = Groups,
askmessage = AskMsg,
xs = XS};
transform({roster, {U, S, {LU, LS, LR}}, {U1, S1}, {U2, S2, R2}, Name, Sub, Ask, Gs, AskMsg, Xs}) ->
#roster{usj = {iolist_to_binary(U), iolist_to_binary(S),
{iolist_to_binary(LU),
iolist_to_binary(LS),
iolist_to_binary(LR)}},
@@ -149,8 +158,10 @@ transform(#roster{usj = {U, S, {LU, LS, LR}},
iolist_to_binary(S2),
iolist_to_binary(R2)},
name = iolist_to_binary(Name),
subscription = Sub,
ask = Ask,
groups = [iolist_to_binary(G) || G <- Gs],
askmessage = try iolist_to_binary(Ask)
askmessage = try iolist_to_binary(AskMsg)
catch _:_ -> <<"">> end,
xs = [fxml:to_xmlel(X) || X <- Xs]};
transform(#roster_version{us = {U, S}, version = Ver} = R) ->
+59 -11
View File
@@ -53,6 +53,51 @@ init(Host, _Opts) ->
sql_schemas() ->
[#sql_schema{
version = 2,
tables =
[#sql_table{
name = <<"rosterusers">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"jid">>, type = text},
#sql_column{name = <<"nick">>, type = text},
#sql_column{name = <<"subscription">>, type = {char, 1}},
#sql_column{name = <<"approved">>, type = boolean},
#sql_column{name = <<"ask">>, type = {char, 1}},
#sql_column{name = <<"askmessage">>, type = text},
#sql_column{name = <<"server">>, type = {char, 1}},
#sql_column{name = <<"subscribe">>, type = text},
#sql_column{name = <<"type">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>,
<<"jid">>],
unique = true},
#sql_index{
columns = [<<"server_host">>, <<"jid">>]}]},
#sql_table{
name = <<"rostergroups">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"jid">>, type = text},
#sql_column{name = <<"grp">>, type = text}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>,
<<"jid">>]}]},
#sql_table{
name = <<"roster_version">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"version">>, type = text}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>],
unique = true}]}],
update = [{add_column, <<"rosterusers">>, <<"approved">>}]},
#sql_schema{
version = 1,
tables =
[#sql_table{
@@ -118,7 +163,7 @@ write_roster_version(LUser, LServer, InTransaction, Ver) ->
get_roster(LUser, LServer) ->
case ejabberd_sql:sql_query(
LServer,
?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s, "
?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s, @(approved)b, "
"@(ask)s, @(askmessage)s, @(server)s, @(subscribe)s, "
"@(type)s from rosterusers "
"where username=%(LUser)s and %(LServer)H")) of
@@ -281,7 +326,7 @@ get_roster_groups(LServer, LUser, SJID) ->
?SQL("select @(grp)s from rostergroups"
" where username=%(LUser)s and %(LServer)H and jid=%(SJID)s")).
roster_subscribe({LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage}) ->
roster_subscribe({LUser, LServer, SJID, Name, SSubscription, BApproved, SAsk, AskMessage}) ->
?SQL_UPSERT_T(
"rosterusers",
["!username=%(LUser)s",
@@ -289,6 +334,7 @@ roster_subscribe({LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage})
"!jid=%(SJID)s",
"nick=%(Name)s",
"subscription=%(SSubscription)s",
"approved=%(BApproved)b",
"ask=%(SAsk)s",
"askmessage=%(AskMessage)s",
"server='N'",
@@ -297,7 +343,7 @@ roster_subscribe({LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage})
get_roster_by_jid(LServer, LUser, SJID) ->
ejabberd_sql:sql_query_t(
?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s,"
?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s, @(approved)b, "
" @(ask)s, @(askmessage)s, @(server)s, @(subscribe)s,"
" @(type)s from rosterusers"
" where username=%(LUser)s and %(LServer)H and jid=%(SJID)s")).
@@ -314,7 +360,7 @@ get_subscription(LServer, LUser, SJID) ->
?SQL("select @(subscription)s, @(ask)s from rosterusers "
"where username=%(LUser)s and %(LServer)H and jid=%(SJID)s")).
update_roster_sql({LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage},
update_roster_sql({LUser, LServer, SJID, Name, SSubscription, BApproved, SAsk, AskMessage},
ItemGroups) ->
[?SQL("delete from rosterusers where"
" username=%(LUser)s and %(LServer)H and jid=%(SJID)s;"),
@@ -325,6 +371,7 @@ update_roster_sql({LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage},
"jid=%(SJID)s",
"nick=%(Name)s",
"subscription=%(SSubscription)s",
"approved=%(BApproved)b",
"ask=%(SAsk)s",
"askmessage=%(AskMessage)s",
"server='N'",
@@ -342,19 +389,19 @@ update_roster_sql({LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage},
|| ItemGroup <- ItemGroups].
raw_to_record(LServer,
[User, LServer, SJID, Nick, SSubscription, SAsk, SAskMessage,
[User, LServer, SJID, Nick, SSubscription, BApproved, SAsk, SAskMessage,
SServer, SSubscribe, SType]) ->
raw_to_record(LServer,
{User, LServer, SJID, Nick, SSubscription, SAsk, SAskMessage,
{User, LServer, SJID, Nick, SSubscription, BApproved, SAsk, SAskMessage,
SServer, SSubscribe, SType});
raw_to_record(LServer,
{User, SJID, Nick, SSubscription, SAsk, SAskMessage,
{User, SJID, Nick, SSubscription, BApproved, SAsk, SAskMessage,
SServer, SSubscribe, SType}) ->
raw_to_record(LServer,
{User, LServer, SJID, Nick, SSubscription, SAsk, SAskMessage,
{User, LServer, SJID, Nick, SSubscription, BApproved, SAsk, SAskMessage,
SServer, SSubscribe, SType});
raw_to_record(LServer,
{User, LServer, SJID, Nick, SSubscription, SAsk, SAskMessage,
{User, LServer, SJID, Nick, SSubscription, BApproved, SAsk, SAskMessage,
_SServer, _SSubscribe, _SType}) ->
try jid:decode(SJID) of
JID ->
@@ -363,7 +410,7 @@ raw_to_record(LServer,
Ask = decode_ask(User, LServer, SAsk),
#roster{usj = {User, LServer, LJID},
us = {User, LServer}, jid = LJID, name = Nick,
subscription = Subscription, ask = Ask,
subscription = Subscription, approved = BApproved, ask = Ask,
askmessage = SAskMessage}
catch _:{bad_jid, _} ->
?ERROR_MSG("~ts", [format_row_error(User, LServer, {jid, SJID})]),
@@ -373,13 +420,14 @@ raw_to_record(LServer,
record_to_row(
#roster{us = {LUser, LServer},
jid = JID, name = Name, subscription = Subscription,
ask = Ask, askmessage = AskMessage}) ->
approved = Approved, ask = Ask, askmessage = AskMessage}) ->
SJID = jid:encode(jid:tolower(JID)),
{LUser,
LServer,
SJID,
Name,
encode_subscription(Subscription),
Approved,
encode_ask(Ask),
AskMessage}.
+28 -14
View File
@@ -37,7 +37,7 @@
bind/1, auth/1, auth/2, open_session/1, open_session/2,
zlib/1, starttls/1, starttls/2, close_socket/1, init_stream/1,
auth_legacy/2, auth_legacy/3, tcp_connect/1, send_text/2,
set_roster/3, del_roster/1]).
set_roster/3, del_roster/1, connect_sasl2/2, auth_SASL2/3, auth_fast_token/4]).
-include("suite.hrl").
suite() ->
@@ -188,7 +188,7 @@ end_per_group(mysql, Config) ->
case catch ejabberd_sql:sql_query(?MYSQL_VHOST, [Query]) of
{selected, _, [[<<"0">>]]} ->
ok;
{selected, _, [[<<"1">>]]} ->
{selected, _, _} ->
clear_sql_tables(mysql, Config);
Other ->
ct:fail({failed_to_check_table_existence, mysql, Other})
@@ -331,6 +331,9 @@ init_per_testcase(TestCase, OrigConfig) ->
connect(Config);
"auth_plain" ->
connect(Config);
"auth_sasl2" ->
Jid = jid:encode(jid:make(User, Server)),
connect_sasl2(starttls(connect_sasl2(Config, Jid)), Jid);
"auth_external" ++ _ ->
connect(Config);
"unauthenticated_" ++ _ ->
@@ -345,21 +348,11 @@ init_per_testcase(TestCase, OrigConfig) ->
bind(auth(connect(Config)));
"replaced" ++ _ ->
auth(connect(Config));
"antispam" ++ _ ->
Password = ?config(password, Config),
ejabberd_auth:try_register(User, Server, Password),
open_session(bind(auth(connect(Config))));
"invites_" ++ _ ->
Password = ?config(password, Config),
ejabberd_auth:try_register(User, Server, Password),
open_session(bind(auth(connect(Config))));
_ when IsMaster or IsSlave ->
Password = ?config(password, Config),
ejabberd_auth:try_register(User, Server, Password),
open_session(bind(auth(connect(Config))));
_ when TestGroup == s2s_tests ->
auth(connect(starttls(connect(Config))));
_ ->
Password = ?config(password, Config),
ejabberd_auth:try_register(User, Server, Password),
open_session(bind(auth(connect(Config))))
end.
@@ -443,6 +436,7 @@ db_tests(DB) when DB == mnesia; DB == redis ->
[test_register,
legacy_auth_tests(),
auth_plain,
auth_sasl2,
auth_md5,
presence_broadcast,
last,
@@ -861,6 +855,26 @@ auth_plain(Config) ->
{skipped, 'PLAIN_not_available'}
end.
auth_sasl2(Config) ->
Mechs = ?config(mechs, Config),
case lists:member(<<"DIGEST-MD5">>, Mechs) of
true ->
Config2 = disconnect(auth_SASL2(<<"DIGEST-MD5">>, Config, false)),
case ?config(fast_token, Config2) of
<<>> -> Config2;
Token ->
User = ?config(user, Config),
Jid = jid:encode(jid:make(User, ?config(server, Config))),
Hash = crypto:mac(hmac, sha256, Token, <<"Initiator">>),
CalcToken = (<<User/binary, 0, Hash/binary>>),
Config3 = connect_sasl2(starttls(connect_sasl2(Config2, Jid)), Jid),
disconnect(auth_fast_token(<<"HT-SHA-256-NONE">>, CalcToken, Config3, false))
end;
false ->
disconnect(Config),
{skipped, 'PLAIN_not_available'}
end.
auth_external(Config0) ->
Config = connect(starttls(Config0)),
disconnect(auth_SASL(<<"EXTERNAL">>, Config)).
@@ -25,7 +25,6 @@ define_macro:
mod_muc:
db_type: internal
vcard: VCARD
mod_muc_occupantid: []
mod_offline:
db_type: internal
mod_privacy:

Some files were not shown because too many files have changed in this diff Show More