Compare commits
114 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 27b87d4a8a | |||
| 6e90f5e681 | |||
| 9a82a28c8d | |||
| c44c259180 | |||
| 13c89279a2 | |||
| fcf685a4dc | |||
| 07580e6787 | |||
| 6ce687262e | |||
| 2c8156dcd0 | |||
| bb8d65b68a | |||
| c43b15b3cd | |||
| acc6beb3f3 | |||
| bf2692a710 | |||
| 42e5a443ed | |||
| c5221f9350 | |||
| 16739180f5 | |||
| 181465ea3e | |||
| 23a32cae1a | |||
| b2e2bd7ca8 | |||
| efdc90b7b8 | |||
| 010a6901b3 | |||
| 8fb6257388 | |||
| 2832c4f513 | |||
| 6022105f39 | |||
| 3a78d6cf2a | |||
| 975dcc1128 | |||
| 00e61dd816 | |||
| c0134fe820 | |||
| ab59302204 | |||
| 89b11829ce | |||
| 267d8ca124 | |||
| 427f68daa6 | |||
| b15bae267d | |||
| 6bbed5854a | |||
| 15661f16c4 | |||
| 54b30cf0c4 | |||
| b7940ba360 | |||
| ee2fd8446a | |||
| b41a8b0925 | |||
| 8603cd85db | |||
| 3a2d1bd12b | |||
| a04ed90e25 | |||
| 383b498fbb | |||
| af3987fc0a | |||
| 9fd1ed34ee | |||
| a9ae72ba9f | |||
| aa704c0d75 | |||
| bc48fa4c55 | |||
| 2251cb7547 | |||
| 15b51d7a7b | |||
| 5721f77d5a | |||
| fa32fecd97 | |||
| e291d84cf8 | |||
| 934fdcdc5d | |||
| eba6868a3f | |||
| 5bb989d998 | |||
| e9645ffec0 | |||
| 179c5c4cbb | |||
| f210803202 | |||
| 793855ad91 | |||
| 1a423743af | |||
| b8f20fb663 | |||
| 7b4338e7df | |||
| e6b1e55705 | |||
| eb784b4026 | |||
| 07d363ed0c | |||
| 8a7cf45d0b | |||
| d93ece6540 | |||
| 2a65a5001d | |||
| 1ffcd1ce6e | |||
| d5c14406db | |||
| 67b87ac892 | |||
| a44f865432 | |||
| a4ff3f3a65 | |||
| ca56f11dc6 | |||
| 1695385912 | |||
| b9e3841186 | |||
| 0680f6ac5e | |||
| ff15018b24 | |||
| 635054007d | |||
| f1527ba21e | |||
| 3b1a8a6c00 | |||
| c862f58c78 | |||
| 6e938dcc84 | |||
| ecb88d9ebc | |||
| b7464e19ff | |||
| 1bcd52300f | |||
| 0c55beff6e | |||
| 3adb30c859 | |||
| 5378d177e2 | |||
| 160cd976b4 | |||
| 95d8f6c531 | |||
| cdcf3ef1b9 | |||
| afd49b9847 | |||
| 5b89ee0fe8 | |||
| 4ce63d9a6c | |||
| 29ac776a98 | |||
| 8cf9a0b79b | |||
| d0d9b2f968 | |||
| 785412be5c | |||
| 3b05cb0700 | |||
| 1fc1626d3f | |||
| 56f47dbec7 | |||
| 05cd172b77 | |||
| 0876662a20 | |||
| 9402dbb16f | |||
| 99f9443ec8 | |||
| 84f698c489 | |||
| 274a5dfbc8 | |||
| 641aa88bbc | |||
| 029aa67ccf | |||
| 120973074a | |||
| 3ffe6383b2 | |||
| 47d898bb8b |
@@ -83,7 +83,7 @@ runs:
|
||||
--prefix=/tmp/ejabberd \
|
||||
--enable-all ${{ inputs.configure }}
|
||||
sed -i 's|, syntax_tools||g' src/ejabberd.app.src.script
|
||||
make
|
||||
REBAR_PROFILE=test make
|
||||
|
||||
############################################################## Deploy #####
|
||||
|
||||
|
||||
@@ -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 \
|
||||
|
||||
+10
-25
@@ -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,7 +48,7 @@ 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
|
||||
@@ -78,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:
|
||||
@@ -112,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'
|
||||
|
||||
@@ -125,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
|
||||
@@ -161,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:
|
||||
@@ -200,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:
|
||||
@@ -220,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:
|
||||
@@ -246,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: |
|
||||
@@ -293,7 +282,6 @@ jobs:
|
||||
|
||||
cover:
|
||||
needs: ct
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04-arm
|
||||
steps:
|
||||
- name: Finish parallel upload to coveralls
|
||||
@@ -314,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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,7 +61,7 @@ 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
|
||||
uses: ./.github/actions/manage-ejabberd
|
||||
@@ -143,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'
|
||||
@@ -232,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'
|
||||
|
||||
@@ -12,8 +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:
|
||||
|
||||
@@ -22,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:
|
||||
@@ -59,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'
|
||||
|
||||
@@ -118,6 +114,7 @@ jobs:
|
||||
run: make test
|
||||
|
||||
- name: Send to coveralls
|
||||
continue-on-error: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -1,3 +1,78 @@
|
||||
## 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))
|
||||
|
||||
+1
-1
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
+23
-8
@@ -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,7 +324,8 @@ 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/* priv/mod_invites/* priv/mod_invites/static/* priv/mod_invites/static/logos/* include/*.hrl COPYING))
|
||||
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
|
||||
@@ -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
@@ -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.02` | 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]),
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
binary(),
|
||||
[binary()],
|
||||
both | from | to | none,
|
||||
boolean(),
|
||||
subscribe | unsubscribe | both | in | out | none,
|
||||
binary()}]
|
||||
}).
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
meta = #{}}).
|
||||
-record(sql_column, {name :: binary(),
|
||||
type,
|
||||
nullable = false,
|
||||
default = false,
|
||||
opts = []}).
|
||||
-record(sql_table, {name :: binary(),
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
jid = {<<>>, <<>>, <<>>} :: jid:ljid(),
|
||||
name = <<>> :: binary() | '_',
|
||||
subscription = none :: subscription() | '_',
|
||||
approved = false :: boolean() | '_',
|
||||
ask = none :: ask() | '_',
|
||||
groups = [] :: [binary()] | '_',
|
||||
askmessage = <<"">> :: binary() | '_',
|
||||
|
||||
+25
-91
@@ -2,12 +2,12 @@
|
||||
.\" Title: ejabberd.yml
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: DocBook XSL Stylesheets vsnapshot <http://docbook.sf.net/>
|
||||
.\" Date: 02/11/2026
|
||||
.\" Date: 03/25/2026
|
||||
.\" Manual: \ \&
|
||||
.\" Source: \ \&
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "EJABBERD\&.YML" "5" "02/11/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\&.02/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\&.02\&. 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
|
||||
@@ -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\&.02/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\&.02\&. 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
|
||||
@@ -4171,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
|
||||
@@ -4181,81 +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}\&. Alternatively you can use
|
||||
tools/dl_invites_page_deps\&.sh <outdir>\&.
|
||||
.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
|
||||
@@ -4346,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
|
||||
@@ -4368,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
|
||||
@@ -5173,7 +5107,7 @@ modules:
|
||||
.RE
|
||||
.\}
|
||||
.RE
|
||||
.SS "mod_muc 🟠"
|
||||
.SS "mod_muc"
|
||||
.sp
|
||||
\fINote\fR about this option: incorporated \fImod_muc_occupantid\fR in 26\&.02\&.
|
||||
.sp
|
||||
@@ -9381,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\&.02\&.
|
||||
This section describes listeners options of ejabberd 26\&.03\&.
|
||||
.sp
|
||||
TODO
|
||||
.SH "AUTHOR"
|
||||
@@ -9389,13 +9323,13 @@ TODO
|
||||
ProcessOne\&.
|
||||
.SH "VERSION"
|
||||
.sp
|
||||
This document describes the configuration file of ejabberd 26\&.02\&. 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\&.02/ejabberd\&.yml\&.example
|
||||
Default configuration file: https://github\&.com/processone/ejabberd/blob/26\&.03/ejabberd\&.yml\&.example
|
||||
.sp
|
||||
Main site: https://ejabberd\&.im
|
||||
.sp
|
||||
|
||||
@@ -114,14 +114,14 @@ defmodule Ejabberd.MixProject do
|
||||
{: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
|
||||
@@ -150,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:
|
||||
@@ -335,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: "⟹ ejabberd Docs"]
|
||||
],
|
||||
|
||||
@@ -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": {:hex, :erlydtl, "0.14.0", "964b2dc84f8c17acfaa69c59ba129ef26ac45d2ba898c3c6ad9b5bdc8ba13ced", [:rebar3], [], "hexpm", "d80ec044cd8f58809c19d29ac5605be09e955040911b644505e31e9dd8143431"},
|
||||
"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"},
|
||||
"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"},
|
||||
}
|
||||
|
||||
Generated
+53
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
<div class="container">
|
||||
<div class="container mb-0">
|
||||
<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="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">
|
||||
<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 badge-info client-platform-badge client-platform-badge-{{ platform|lower }} mr-1 mb-3">{{ platform }}</span>{% endfor %}
|
||||
{% 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">{% if item.select_text %}{{ item.select_text }}{% else %}{% trans "Select" %}{% endif %}</a>
|
||||
<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>
|
||||
@@ -21,6 +21,6 @@
|
||||
{% 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 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>
|
||||
|
||||
@@ -69,6 +69,27 @@
|
||||
"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.' %}"
|
||||
},
|
||||
{
|
||||
"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": [
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
{% 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">
|
||||
<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 d-block btn btn-info" title="{% trans "Send this invite to your device" %}"
|
||||
data-toggle="modal" data-target="#qr-modal">
|
||||
<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>
|
||||
@@ -17,20 +17,18 @@
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title h5">{% trans "Scan invite code" %}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<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" style="width: 256px;" class="bg-light mx-auto"></div>
|
||||
<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-dismiss="modal">{% trans "Close" %}</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" tabindex="2">{% trans "Close" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -38,7 +36,6 @@
|
||||
{% 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 %}
|
||||
|
||||
@@ -3,44 +3,18 @@
|
||||
<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>
|
||||
<title>{% block title %}{% trans "Invite" %} | {{ site_name }}{% 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">
|
||||
<style>
|
||||
#other-software a {
|
||||
text-decoration: underline;
|
||||
color: #0072ed;
|
||||
}
|
||||
a#show-all-clients-button {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.btn-primary {
|
||||
background-color: #0072ed;
|
||||
}
|
||||
.badge-success {
|
||||
background-color: #0a8927;
|
||||
}
|
||||
.alert-info, .alert-info a, .btn-info {
|
||||
background-color: #008297;
|
||||
color: white;
|
||||
}
|
||||
.border-info {
|
||||
border-color: #008297 !important;
|
||||
}
|
||||
</style>
|
||||
<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="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 %}</h1>
|
||||
<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 %}
|
||||
@@ -49,7 +23,8 @@
|
||||
</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>
|
||||
<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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% 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 %}
|
||||
@@ -9,18 +9,21 @@
|
||||
{% blocktrans %}You have been invited to chat on {{ site_name }}, part of the XMPP secure and decentralized messaging network.{% endblocktrans %}
|
||||
{% endif %}</p>
|
||||
|
||||
<h2 class="h5">{% blocktrans with app_name=app.name %}You can start chatting right away with {{ app_name }}. Let's get started!{% endblocktrans %}</h2>
|
||||
<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 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="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="p-2 img-fluid" alt="{{ app.alttext }}">
|
||||
<img src="{{ static }}/{{ app.image }}" class="img-fluid" alt="{{ app.alttext }}">
|
||||
</div>
|
||||
<div class="col-md-8 h-100">
|
||||
<div class="card-body d-flex flex-column h-100">
|
||||
<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 badge-info client-platform-badge client-platform-badge-{{ item|lower }} mr-1 mb-3">{{ item }}</span> {% endfor %}
|
||||
{% 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>
|
||||
@@ -28,19 +31,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 style="clear:both" class="h3">{% blocktrans with app_name=app.name %}Step 1: Install {{ app_name }}{% endblocktrans %}</h2>
|
||||
<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="ml-5">
|
||||
<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">
|
||||
<img src="{{ button.image }}" {% if button.alttext %}alt="{{ button.alttext }}"{% endif %} style="max-width: 160px;">
|
||||
<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 %}
|
||||
{% if button.text %}
|
||||
<a href="{{ button.url }}" {% if button.target %}target="{{ button.target }}"{% endif %} class="btn btn-primary" rel="noopener">
|
||||
<a href="{{ button.url }}" {% if button.target %}target="{{ button.target }}"{% endif %} class="btn btn-primary" rel="noopener" tabindex="4">
|
||||
{{ button.text }}
|
||||
</a>
|
||||
{% endif %}
|
||||
@@ -49,28 +52,21 @@
|
||||
|
||||
<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>
|
||||
|
||||
<h2 class="h3">{% trans "Step 2: Activate your account" %}</h2>
|
||||
<h2 class="h3 alert alert-info p-2">{% trans "Step 2: Activate your account" %}</h2>
|
||||
|
||||
<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 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>
|
||||
|
||||
<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">
|
||||
<li class="page-item"><a tabindex="4" class="page-link" href="/{{ base }}/{{ token }}" aria-label="{% trans 'Previous' %}">
|
||||
<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">«</span>
|
||||
<span>{% trans "Previous" %}</span>
|
||||
</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
<script src="{{ static }}/qrcode.min.js"></script>
|
||||
<script src="{{ static }}/platform.min.js"></script>
|
||||
<script src="{{ static }}/invite.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{% trans "Create Account" %} | {{ site_name }}{% endblock %}
|
||||
{% block content %}
|
||||
<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 %}
|
||||
{% blocktrans %}You have been invited to chat on <strong>{{ site_name }}</strong>, part of the XMPP secure and decentralized messaging network.{% endblocktrans %}
|
||||
{% endif %}</p>
|
||||
<h2 class="card-title h5" style="clear:both">{% trans "Get started" %}</h2>
|
||||
<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>
|
||||
|
||||
{% include "apps.html" %}
|
||||
|
||||
<h2 class="h5">{% trans "Other software" %}</h2>
|
||||
<p id="other-software">{% 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 %}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% 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 %}
|
||||
<h2 class="card-title h5">{% trans "Invite expired" %}</h2>
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
{% extends "base_min.html" %}
|
||||
|
||||
{% 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 title %}{% if error %}{% trans "Registration Error" %}{% else %}{% blocktrans %}Register on {{ site_name }}{% endblocktrans %}{% endif %}{% endblock %}
|
||||
{% block h1 %}{% block title %}{% endblock %}{% 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>
|
||||
@@ -25,8 +24,8 @@
|
||||
<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="form-group form-row">
|
||||
<label for="user" class="col-md-4 col-lg-12 col-form-label">{% trans "Username" %}:</label>
|
||||
<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
|
||||
@@ -43,8 +42,8 @@
|
||||
<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="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 }}">
|
||||
@@ -55,14 +54,15 @@
|
||||
<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">
|
||||
<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 btn-lg">{% trans "Submit" %}</button>
|
||||
<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">
|
||||
<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">«</span>
|
||||
<span>{% trans "Previous" %}</span>
|
||||
@@ -70,24 +70,3 @@
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
{% block extra_scripts %}
|
||||
<script>
|
||||
(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);
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% 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 %}
|
||||
<h2 class="card-title h5">{% trans "Registration error" %}</h2>
|
||||
|
||||
|
||||
@@ -1,25 +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 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 %}
|
||||
<h2 class="card-title h5">{% trans "Congratulations!" %}</h2>
|
||||
|
||||
@@ -28,14 +10,14 @@
|
||||
<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="bd-callout bd-callout-info col-12">
|
||||
<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 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">
|
||||
<a class="btn btn-primary" href="{{ webchat_url }}">{% trans "Log in via web" %}</a>
|
||||
<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>
|
||||
@@ -45,11 +27,11 @@
|
||||
{% 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>
|
||||
|
||||
<h2 class="h5">{% blocktrans with app_name=app.name %}Step 1: Download and install {{ app_name }}{% endblocktrans %}</h2>
|
||||
<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>
|
||||
|
||||
<div class="ml-5 mb-3">
|
||||
<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">
|
||||
@@ -66,28 +48,29 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<h2 class="h5">{% blocktrans with app_name=app.name %}Step 2: Connect {{ app_name }} to your new account{% endblocktrans %}</h2>
|
||||
<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>
|
||||
{% endif %}
|
||||
|
||||
<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">
|
||||
<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="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">
|
||||
<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 class="form-control" value="{{ password }}">
|
||||
<input type="password" readonly disabled aria-label="{% trans "Password" %}" 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>
|
||||
<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>
|
||||
@@ -96,6 +79,6 @@
|
||||
</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>
|
||||
<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 %}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{% trans "Add Contact" %}{% 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">
|
||||
<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="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/>
|
||||
<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>
|
||||
<h2 class="h6">{% trans "If you don't have an XMPP client installed yet, here's a list of suitable clients for your platform." %}</h2>
|
||||
|
||||
<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 |
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
})();
|
||||
|
||||
@@ -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 |
+86
-4
@@ -4,22 +4,33 @@
|
||||
%% 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","Всички потребители"}.
|
||||
@@ -48,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 съобщения"}.
|
||||
@@ -64,6 +77,8 @@
|
||||
{"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 вече е активиран"}.
|
||||
@@ -80,6 +95,7 @@
|
||||
{"Channel JID","JID на канал"}.
|
||||
{"Channels","Канали"}.
|
||||
{"Characters not allowed:","Неразрешени символи:"}.
|
||||
{"Chat address (JID)","Чат адрес (JID)"}.
|
||||
{"Chatroom configuration modified","Конфигурацията на стаята за чат е променена"}.
|
||||
{"Chatroom is created","Стаята за чат е създадена"}.
|
||||
{"Chatroom is destroyed","Стаята за чат е унищожена"}.
|
||||
@@ -87,20 +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 ","Конфигурация на таблиците в базата данни при "}.
|
||||
@@ -113,11 +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"}.
|
||||
@@ -139,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 директорията"}.
|
||||
@@ -159,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 потребител"}.
|
||||
@@ -167,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","Списък на неактивните потребители"}.
|
||||
@@ -178,6 +210,7 @@
|
||||
{"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","Покажи статистика за потребителя"}.
|
||||
@@ -195,10 +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","Импорт на файл"}.
|
||||
@@ -217,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","Вътрешна сървърна грешка"}.
|
||||
@@ -224,7 +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) и е бил отстранен от стаята"}.
|
||||
@@ -246,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","Направи списъка с участниците публичен"}.
|
||||
@@ -285,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"}.
|
||||
@@ -380,9 +426,11 @@
|
||||
{"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","Проверка на паролата"}.
|
||||
@@ -398,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"}.
|
||||
@@ -426,9 +476,13 @@
|
||||
{"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","Премахни потребител"}.
|
||||
@@ -460,15 +514,19 @@
|
||||
{"~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","Времето за изчакване на извличането на списъка с услуги изтече"}.
|
||||
@@ -479,12 +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'>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!","Съжалявам, изглежда, че този код за покана е изтекъл!"}.
|
||||
{"Sorry, there was a problem registering your account.","За съжаление, възникна проблем с регистрацията на вашия профил."}.
|
||||
{"Sources Specs:","Спецификации на източниците:"}.
|
||||
{"Specify the access model","Задай модела за достъп"}.
|
||||
{"Specify the event message type","Задай типа на съобщението за събитие"}.
|
||||
@@ -492,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","Изпрати"}.
|
||||
@@ -563,6 +631,7 @@
|
||||
{"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. Моля, прочетете внимателно инструкциите, за да попълните правилно полетата."}.
|
||||
@@ -572,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\" потоци"}.
|
||||
@@ -605,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"}.
|
||||
@@ -653,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 покажи стойност на Чат"}.
|
||||
@@ -662,8 +735,16 @@
|
||||
{"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","Трябва да попълните полето \"Псевдоним\" във формата"}.
|
||||
@@ -672,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","Нямате право да създавате нодове"}.
|
||||
|
||||
+8
-2
@@ -11,6 +11,7 @@
|
||||
{"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."}.
|
||||
@@ -228,6 +229,7 @@
|
||||
{"Hat URI","URI del barret"}.
|
||||
{"Hats limit exceeded","El límit de tràfic ha sigut sobrepassat"}.
|
||||
{"Hide","Amagar"}.
|
||||
{"Hint","Sugeriment"}.
|
||||
{"Host unknown","Domini desconegut"}.
|
||||
{"Hostname invalid","Nom de domini no vàlid"}.
|
||||
{"HTTP File Upload","HTTP File Upload"}.
|
||||
@@ -261,8 +263,10 @@
|
||||
{"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"}.
|
||||
@@ -477,6 +481,8 @@
|
||||
{"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"}.
|
||||
@@ -531,7 +537,7 @@
|
||||
{"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'>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'>vore totes les apps.</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>","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"}.
|
||||
@@ -729,7 +735,7 @@
|
||||
{"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 }}\">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 }}\">registrarte el teu compte manualment</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>.","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!"}.
|
||||
|
||||
+80
-9
@@ -6,14 +6,22 @@
|
||||
{" 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?"}.
|
||||
@@ -28,6 +36,7 @@
|
||||
{"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í"}.
|
||||
@@ -35,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"}.
|
||||
@@ -53,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"}.
|
||||
@@ -72,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"}.
|
||||
@@ -87,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"}.
|
||||
@@ -119,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"}.
|
||||
@@ -143,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"}.
|
||||
@@ -154,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"}.
|
||||
@@ -195,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"}.
|
||||
@@ -221,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"}.
|
||||
@@ -238,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í"}.
|
||||
@@ -262,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í"}.
|
||||
@@ -275,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 "}.
|
||||
@@ -286,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"}.
|
||||
@@ -295,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é"}.
|
||||
@@ -324,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"}.
|
||||
@@ -361,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:"}.
|
||||
@@ -387,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"}.
|
||||
|
||||
+9
-3
@@ -11,6 +11,7 @@
|
||||
{"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."}.
|
||||
@@ -228,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"}.
|
||||
@@ -261,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"}.
|
||||
@@ -477,6 +481,8 @@
|
||||
{"Register","Anmelden"}.
|
||||
{"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"}.
|
||||
@@ -532,7 +538,7 @@
|
||||
{"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"}.
|
||||
@@ -729,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!"}.
|
||||
|
||||
+8
-2
@@ -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."}.
|
||||
@@ -228,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"}.
|
||||
@@ -261,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"}.
|
||||
@@ -477,6 +481,8 @@
|
||||
{"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"}.
|
||||
@@ -531,7 +537,7 @@
|
||||
{"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"}.
|
||||
@@ -729,7 +735,7 @@
|
||||
{"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!"}.
|
||||
|
||||
@@ -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 :"}.
|
||||
|
||||
+10
-4
@@ -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 的现代开源聊天客户端,易于使用,界面简洁。"}.
|
||||
@@ -117,7 +118,7 @@
|
||||
{"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","创建头衔"}.
|
||||
@@ -228,6 +229,7 @@
|
||||
{"Hat URI","头衔 URI"}.
|
||||
{"Hats limit exceeded","已超过头衔限制"}.
|
||||
{"Hide","隐藏"}.
|
||||
{"Hint","提示"}.
|
||||
{"Host unknown","主机未知"}.
|
||||
{"Hostname invalid","主机名无效"}.
|
||||
{"HTTP File Upload","HTTP 文件上传"}.
|
||||
@@ -261,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),被踢出了房间"}.
|
||||
@@ -477,6 +481,8 @@
|
||||
{"Register","注册"}.
|
||||
{"Registration error","注册错误"}.
|
||||
{"Registration Error","注册错误"}.
|
||||
{"Registration Form","注册表单"}.
|
||||
{"Registration Success","注册成功"}.
|
||||
{"Remote copy","远程副本"}.
|
||||
{"Remove a hat from a user","移除用户头衔"}.
|
||||
{"Remove User","移除用户"}.
|
||||
@@ -531,7 +537,7 @@
|
||||
{"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 徽标"}.
|
||||
@@ -729,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 }} 开始聊天。开始吧!"}.
|
||||
|
||||
+34
-22
@@ -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,
|
||||
@@ -36,13 +36,13 @@
|
||||
}},
|
||||
{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"}}}
|
||||
},
|
||||
@@ -53,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]}.
|
||||
@@ -114,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'}},
|
||||
@@ -154,8 +154,19 @@
|
||||
{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"}]}]}
|
||||
@@ -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 \
|
||||
|
||||
+51
-63
@@ -1,89 +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">>,{pkg,<<"erlydtl">>,<<"0.14.0">>},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},
|
||||
{<<"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">>},
|
||||
{<<"erlydtl">>, <<"964B2DC84F8C17ACFAA69C59BA129EF26AC45D2BA898C3C6AD9B5BDC8BA13CED">>},
|
||||
{<<"esip">>, <<"EB202F8C62928193588091DFEDBC545FE3274C34ECD209961F86DCB6C9EBCE88">>},
|
||||
{<<"ezlib">>, <<"D74F5DF191784744726A5B1AE9062522C606334F11086363385EB3B772D91357">>},
|
||||
{<<"fast_tls">>, <<"DA8ED6F05A2452121B087158B17234749F36704C1F2B74DC51DB99A1E27ED5E8">>},
|
||||
{<<"fast_xml">>, <<"31EFC0F9BCEDA92069704F7A25830407DA5DC3DAD1272B810D6F2E13E73CC11A">>},
|
||||
{<<"fast_yaml">>, <<"2E71168091949BAB0E5F583B340A99072B4D22D93EB86624E7850A12B1517BE4">>},
|
||||
{<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>},
|
||||
{<<"jiffy">>, <<"A9B6C9A7EC268E7CF493D028F0A4C9144F59CCB878B1AFE42841597800840A1B">>},
|
||||
{<<"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">>},
|
||||
{<<"erlydtl">>, <<"D80EC044CD8F58809C19D29AC5605BE09E955040911B644505E31E9DD8143431">>},
|
||||
{<<"esip">>, <<"0BDF2E3C349DC0B144F173150329E675C6A51AC473D7A0B2E362245FAAD3FBE6">>},
|
||||
{<<"ezlib">>, <<"DD14BA6C12521AF5CFE6923E73E3D545F4A0897DC66BFAB5287FBB7AE3962EAB">>},
|
||||
{<<"fast_tls">>, <<"59E183B5740E670E02B8AA6BE673B5E7779E5FE5BFCC679FE2D4993D1949A821">>},
|
||||
{<<"fast_xml">>, <<"EEC34E90ADACAFE467D5DDAB635A014DED73B98B4061554B2D1972173D929C39">>},
|
||||
{<<"fast_yaml">>, <<"24C7B9AB9E2B9269D64E45F4A2A1280966ADB17D31E63365CFD3EE277FB0A78D">>},
|
||||
{<<"idna">>, <<"92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA">>},
|
||||
{<<"jiffy">>, <<"BB61BC42A720BBD33CB09A410E48BB79A61012C74CB8B3E75F26D988485CF381">>},
|
||||
{<<"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">>}]}
|
||||
].
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
@@ -521,3 +522,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), server_host(191));
|
||||
CREATE INDEX i_invite_token_invitee USING BTREE ON invite_token(invitee(191));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
+105
-18
@@ -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, []),
|
||||
|
||||
@@ -517,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,
|
||||
@@ -683,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),
|
||||
|
||||
@@ -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})]}}},
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
+26
-2
@@ -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
|
||||
@@ -542,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}).
|
||||
@@ -616,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
@@ -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
@@ -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),
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
+178
-50
@@ -46,15 +46,16 @@
|
||||
-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, gen_invite/1, gen_invite/2, 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,29 +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://code.jquery.com/jquery-3.7.1.min.js[jQuery 3] and "
|
||||
" https://github.com/twbs/bootstrap/releases/download/v4.6.2/bootstrap-4.6.2-dist.zip[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}`. Alternatively you can use "
|
||||
" `tools/dl_invites_page_deps.sh <outdir>`."),
|
||||
?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"),
|
||||
@@ -197,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:"),
|
||||
@@ -207,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 "
|
||||
@@ -249,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)},
|
||||
@@ -256,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),
|
||||
@@ -292,7 +290,23 @@ mod_opt_type(db_type) ->
|
||||
econf:db_type(?MODULE);
|
||||
mod_opt_type(landing_page) ->
|
||||
econf:either(
|
||||
econf:enum([none, auto]), econf:binary());
|
||||
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) ->
|
||||
@@ -300,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
|
||||
@@ -421,8 +438,6 @@ gen_invite(Host) ->
|
||||
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 ->
|
||||
@@ -732,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(),
|
||||
@@ -779,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;
|
||||
@@ -815,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 ->
|
||||
@@ -826,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;
|
||||
@@ -871,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,
|
||||
@@ -921,8 +1033,13 @@ landing_page(Host, Invite) ->
|
||||
|
||||
-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) ->
|
||||
@@ -938,12 +1055,23 @@ try_db_call(Host, Fun, Args) ->
|
||||
try
|
||||
lift(db_call(Host, Fun, Args))
|
||||
catch
|
||||
error:({error, _Reason} = Error) ->
|
||||
_:({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) ->
|
||||
translate:translate(Lang, Msg).
|
||||
|
||||
+151
-62
@@ -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">>}]).
|
||||
|
||||
@@ -63,22 +66,29 @@
|
||||
%% @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
|
||||
_:_ ->
|
||||
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) ->
|
||||
@@ -89,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) ->
|
||||
@@ -109,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) ->
|
||||
@@ -134,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
|
||||
@@ -143,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.
|
||||
|
||||
@@ -160,14 +175,17 @@ 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_register_form(#request{host = Host, lang = Lang}, Ctx) ->
|
||||
MinLength =
|
||||
case mod_register_opt:password_strength(Host) of
|
||||
0 ->
|
||||
@@ -175,10 +193,7 @@ render_register_form(#request{host = Host, lang = Lang}, Ctx, AdditionalCtx) ->
|
||||
_ ->
|
||||
6
|
||||
end,
|
||||
render(Host,
|
||||
Lang,
|
||||
<<"register.html">>,
|
||||
[{password_min_length, MinLength} | Ctx] ++ AdditionalCtx).
|
||||
render(Host, Lang, <<"register.html">>, [{password_min_length, MinLength} | Ctx]).
|
||||
|
||||
process_register_post(Invite,
|
||||
AppID,
|
||||
@@ -188,19 +203,22 @@ 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);
|
||||
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,
|
||||
@@ -210,18 +228,17 @@ process_register_post(Invite,
|
||||
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},
|
||||
{error,
|
||||
[{text, Msg},
|
||||
{class, error_class(Reason)}]}]),
|
||||
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
|
||||
@@ -231,6 +248,47 @@ 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') ->
|
||||
@@ -260,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.
|
||||
|
||||
@@ -272,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];
|
||||
@@ -284,25 +341,36 @@ app_ctx(Host, AppID, Lang, Ctx) ->
|
||||
ctx(#request{host = Host,
|
||||
path = Path,
|
||||
lang = Lang},
|
||||
LocalPath) ->
|
||||
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),
|
||||
[{base, Base}, ?STATIC_CTX, ?SITE_NAME_CTX(SiteName), ?LANG(Lang)].
|
||||
|
||||
[{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;
|
||||
@@ -384,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) ->
|
||||
@@ -418,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
@@ -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).
|
||||
|
||||
@@ -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()) -> 'auto' | '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).
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
+56
-11
@@ -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
@@ -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),
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
+32
-36
@@ -1097,21 +1097,21 @@ process_groupchat_message(#message{from = From, lang = Lang} = Packet, StateData
|
||||
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
|
||||
@@ -1176,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().
|
||||
@@ -2131,7 +2123,7 @@ calculate_occupant_id(Jid, #state{salt = Salt, jid = RoomJid}) ->
|
||||
Term = <<Salt/binary, ":", RoomJidS/binary, ":", JidS/binary>>,
|
||||
misc:term_to_base64(crypto:hash(sha256, Term)).
|
||||
|
||||
-spec filter_message_hook(state(), binary(), #message{}) -> drop | #message{}.
|
||||
-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;
|
||||
@@ -2152,7 +2144,7 @@ filter_presence_hook(#state{users = Users} = StateData, Nick, #presence{from = F
|
||||
end,
|
||||
Pres2 = xmpp:append_subtags(xmpp:remove_subtag(Pres, #occupant_id{}),
|
||||
[#occupant_id{id = OccupantId}]),
|
||||
ejabberd_hooks:run_fold(muc_filter_message,
|
||||
ejabberd_hooks:run_fold(muc_filter_presence,
|
||||
StateData#state.server_host,
|
||||
Pres2,
|
||||
[StateData, Nick]).
|
||||
@@ -2997,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 ->
|
||||
@@ -4503,10 +4496,13 @@ make_disco_info(From, StateData) ->
|
||||
true -> [?NS_HATS];
|
||||
false -> []
|
||||
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,
|
||||
@@ -5837,7 +5833,7 @@ send_wrapped_multiple(From, Users, Packet, Node, State) ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
|
||||
@@ -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),
|
||||
@@ -194,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 =>
|
||||
@@ -340,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">>]},
|
||||
@@ -349,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,
|
||||
@@ -360,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.
|
||||
|
||||
@@ -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
@@ -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()}.
|
||||
|
||||
@@ -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,
|
||||
|
||||
+118
-24
@@ -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),
|
||||
|
||||
@@ -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
@@ -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
@@ -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)).
|
||||
|
||||
@@ -156,6 +156,7 @@ Welcome to this XMPP server."
|
||||
get_url: GET_URL
|
||||
max_size: 10000
|
||||
vcard: VCARD
|
||||
mod_auth_fast: []
|
||||
registration_timeout: infinity
|
||||
s2s_use_starttls: false
|
||||
ca_file: CAFILE
|
||||
|
||||
+148
-10
@@ -32,6 +32,8 @@
|
||||
-include("mod_invites.hrl").
|
||||
-include("mod_roster.hrl").
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
%% killme
|
||||
-record(ejabberd_module,
|
||||
{module_host = {undefined, <<"">>} :: {atom(), binary()},
|
||||
@@ -41,6 +43,82 @@
|
||||
|
||||
%% @format-begin
|
||||
|
||||
find_invites_tree_root_t_test_() ->
|
||||
{setup,
|
||||
fun() ->
|
||||
meck:new(db, [non_strict]),
|
||||
meck:expect(db,
|
||||
get_invite_by_invitee_t,
|
||||
fun (_, <<"4@host">>) ->
|
||||
#invite_token{inviter = {<<"3">>, <<"host">>}};
|
||||
(_, <<"3@host">>) ->
|
||||
#invite_token{inviter = {<<"2">>, <<"host">>}};
|
||||
(_, <<"2@host">>) ->
|
||||
#invite_token{inviter = {<<"1">>, <<"host">>}};
|
||||
(_, _) ->
|
||||
{error, not_found}
|
||||
end),
|
||||
meck:new(gen_mod, [passthrough]),
|
||||
meck:expect(gen_mod, db_mod, 2, db),
|
||||
meck:new(calendar, [unstick, passthrough]),
|
||||
meck:expect(calendar, now_to_datetime, 1, then),
|
||||
meck:expect(calendar, datetime_to_gregorian_seconds, fun(then) -> 1 end),
|
||||
[db, gen_mod, calendar]
|
||||
end,
|
||||
fun meck:unload/1,
|
||||
fun(_) ->
|
||||
[%% lvl not reached
|
||||
?_assertMatch({<<"1">>, <<"host">>},
|
||||
mod_invites:find_invites_tree_root_t(2, host, {<<"3">>, <<"host">>}, 0)),
|
||||
%% lvl reached
|
||||
?_assertThrow(speedy_goat,
|
||||
mod_invites:find_invites_tree_root_t(2, host, {<<"4">>, <<"host">>}, 0)),
|
||||
%% lvl reached but later
|
||||
?_assertMatch({<<"1">>, <<"host">>},
|
||||
mod_invites:find_invites_tree_root_t(?SPEEDY_GOAT_SECONDS + 1,
|
||||
host,
|
||||
{<<"4">>, <<"host">>},
|
||||
0)),
|
||||
?_assert(meck:validate(db))]
|
||||
end}.
|
||||
|
||||
get_invites_tree_as_root_t_test_() ->
|
||||
{setup,
|
||||
fun() ->
|
||||
meck:new(db, [non_strict]),
|
||||
meck:expect(db,
|
||||
get_invites_t,
|
||||
fun (_, {<<"1">>, _}) ->
|
||||
[#invite_token{invitee = <<"2@host">>, type = account_only},
|
||||
#invite_token{invitee = <<"rosterinvite@forcecrash">>}];
|
||||
(_, {<<"2">>, _}) ->
|
||||
[#invite_token{invitee = <<"3@host">>, type = account_only},
|
||||
#invite_token{invitee = <<"4@host">>, type = account_only}];
|
||||
(_, {<<"3">>, _}) ->
|
||||
[#invite_token{invitee = <<"5@host">>, type = account_subscription},
|
||||
#invite_token{invitee = <<"6@host">>, account_name = <<"6">>},
|
||||
#invite_token{type = account_only}];
|
||||
(_, {_, <<"host">>}) ->
|
||||
[]
|
||||
end),
|
||||
meck:new(gen_mod, [passthrough]),
|
||||
meck:expect(gen_mod, db_mod, 2, db),
|
||||
meck:expect(jid,
|
||||
decode,
|
||||
fun(Str) ->
|
||||
[LUser, LServer] =
|
||||
[list_to_binary(T) || T <- string:tokens(binary_to_list(Str), "@")],
|
||||
#jid{luser = LUser, lserver = LServer}
|
||||
end),
|
||||
[db, gen_mod, jid]
|
||||
end,
|
||||
fun meck:unload/1,
|
||||
fun(_) ->
|
||||
[?_assertMatch(6,
|
||||
length(mod_invites:get_invites_tree_as_root_t(<<"host">>,
|
||||
{<<"1">>, <<"host">>})))]
|
||||
end}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
@@ -476,14 +554,41 @@ ibr(Config0) ->
|
||||
send_get_iq_register(Config3)),
|
||||
?match(#iq{type = result}, send_iq_register(Config3, <<"some_self_chosen_name">>)),
|
||||
|
||||
RedirectUrl = <<"http://localhost">>,
|
||||
NewRegisterOpts2 = gen_mod:set_opt(redirect_url, RedirectUrl, NewRegisterOpts),
|
||||
update_module_opts(Server, mod_register, NewRegisterOpts2),
|
||||
Config4 = reconnect(Config3),
|
||||
%% check redirect_url works
|
||||
#iq{type = result, sub_els = [#register{sub_els = [SubEl]}]} =
|
||||
send_get_iq_register(Config4),
|
||||
?match(#oob_x{url = RedirectUrl}, xmpp:decode(SubEl)),
|
||||
#invite_token{token = Token4} = create_account_invite(Server, {<<>>, Server}),
|
||||
?match(#iq{type = result}, send_pars(Config4, Token4)),
|
||||
#iq{type = result, sub_els = [#register{sub_els = SubEls}]} =
|
||||
send_get_iq_register(Config4),
|
||||
%% check for absence of redirect_url
|
||||
?match([],
|
||||
lists:filter(fun(El) ->
|
||||
Decoded = xmpp:decode(El),
|
||||
case Decoded of
|
||||
#oob_x{url = RedirectUrl} ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end,
|
||||
SubEls)),
|
||||
?match(#iq{type = result}, send_iq_register(Config4, <<"yet_another_self_chosen_name">>)),
|
||||
|
||||
ejabberd_auth:remove_user(AccountName, Server),
|
||||
ejabberd_auth:remove_user(<<"yet_another_self_chosen_name">>, Server),
|
||||
ejabberd_auth:remove_user(<<"some_self_chosen_name">>, Server),
|
||||
ejabberd_auth:remove_user(<<"some_much_better_name">>, Server),
|
||||
update_module_opts(Server, mod_register, OldRegisterOpts),
|
||||
mod_invites:remove_user(<<"inviter">>, Server),
|
||||
mod_invites:expire_tokens(<<>>, Server),
|
||||
?match(3, mod_invites:cleanup_expired()),
|
||||
disconnect(Config3).
|
||||
?match(4, mod_invites:cleanup_expired()),
|
||||
disconnect(Config4).
|
||||
|
||||
ibr_reserved(Config0) ->
|
||||
Server = ?config(server, Config0),
|
||||
@@ -673,7 +778,10 @@ http(Config) ->
|
||||
User = ?config(user, Config),
|
||||
{TokenURI, LandingPage} = mod_invites:gen_invite(Server),
|
||||
Token = token_from_uri(TokenURI),
|
||||
{ok, {{_, 200, _}, _Headers, Body}} = httpc:request(LandingPage),
|
||||
{ok, {{_, 200, _}, Headers, Body}} = httpc:request(LandingPage),
|
||||
{match, [TokenURI]} =
|
||||
re:run(
|
||||
proplists:get_value("link", Headers), "<(.+)>", [{capture, [1], binary}]),
|
||||
{match, RegistrationURLs} =
|
||||
re:run(Body,
|
||||
<<"href=\"", Token/binary, "([a-zA-Z0-9\/\-]+)\"">>,
|
||||
@@ -695,11 +803,25 @@ http(Config) ->
|
||||
|
||||
[Last] = hd(lists:reverse(RegistrationURLs)),
|
||||
RegURL = <<BaseURL/binary, Last/binary>>,
|
||||
{ok, {{_, 400, _}, _, _}} = post(RegURL, <<"badtoken">>, <<"foo">>, <<"bar">>),
|
||||
{ok, {{_, 400, _}, _, _}} = post(RegURL, Token, User, <<"bar">>),
|
||||
{ok, {{_, 400, _}, _, _}} = post(RegURL, Token, <<"@invalidUser">>, <<"bar">>),
|
||||
{ok, {{_, 200, _}, _, _}} = post(RegURL, Token, <<"foo">>, <<"bar">>),
|
||||
{ok, {{_, 404, _}, _, _}} = post(RegURL, Token, <<"foo">>, <<"bar">>),
|
||||
{ok, {{_, 200, _}, _, RegURLBody}} = httpc:request(RegURL),
|
||||
{match, [[CSRFToken]]} =
|
||||
re:run(RegURLBody,
|
||||
<<"<input.+name=\"csrf_token\" value=\"(.+)\"">>,
|
||||
[global, {capture, [1], binary}]),
|
||||
ct:pal("extracted csrf token: ~p", [CSRFToken]),
|
||||
{ok, {{_, 400, _}, _, _}} = post(RegURL, <<"badtoken">>, CSRFToken, <<"foo">>, <<"bar">>),
|
||||
{ok, {{_, 400, _}, _, _}} = post(RegURL, Token, CSRFToken, User, <<"bar">>),
|
||||
{ok, {{_, 400, _}, _, _}} = post(RegURL, Token, CSRFToken, <<"@invalidUser">>, <<"bar">>),
|
||||
{ok, {{_, 400, _}, _, _}} = post(RegURL, Token, <<"foo">>, <<"bar">>),
|
||||
{ok, {{_, 400, _}, _, _}} =
|
||||
post(RegURL,
|
||||
Token,
|
||||
<<"guLRkZZFv+CGI7UbCnyija0KwPFmob71RGvGa7dQ5G4=">>,
|
||||
<<"foo">>,
|
||||
<<"bar">>),
|
||||
{ok, {{_, 400, _}, _, _}} = post(RegURL, Token, <<"nohashtoken">>, <<"foo">>, <<"bar">>),
|
||||
{ok, {{_, 200, _}, _, _}} = post(RegURL, Token, CSRFToken, <<"foo">>, <<"bar">>),
|
||||
{ok, {{_, 404, _}, _, _}} = post(RegURL, Token, CSRFToken, <<"foo">>, <<"bar">>),
|
||||
{ok, {{_, 404, _}, _, _}} = httpc:request(LandingPage),
|
||||
lists:foreach(fun([URL]) ->
|
||||
FullURL = <<BaseURL/binary, "/", URL/binary>>,
|
||||
@@ -713,7 +835,8 @@ http(Config) ->
|
||||
RosterURL = mod_invites_http:landing_page(Server, RosterInvite),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(RosterURL),
|
||||
FakeRegURL = <<RosterURL/binary, "/registration">>,
|
||||
{ok, {{_, 404, _}, _, _}} = post(FakeRegURL, RosterToken, <<"baz">>, <<"bar">>),
|
||||
{ok, {{_, 404, _}, _, _}} =
|
||||
post(FakeRegURL, RosterToken, CSRFToken, <<"baz">>, <<"bar">>),
|
||||
ejabberd_auth:remove_user(<<"foo">>, Server),
|
||||
mod_invites:remove_user(<<"inviter">>, Server),
|
||||
mod_invites:expire_tokens(<<>>, Server),
|
||||
@@ -837,10 +960,25 @@ send_pars(Config, Token) ->
|
||||
to = ServerJID,
|
||||
sub_els = [#preauth{token = Token}]}).
|
||||
|
||||
post(URL, Token, User, Password) ->
|
||||
post(URL, Token0, User0, Password0) ->
|
||||
[Token, User, Password] = [uri_string:quote(V) || V <- [Token0, User0, Password0]],
|
||||
Data = <<"token=", Token/binary, "&user=", User/binary, "&password=", Password/binary>>,
|
||||
httpc:request(post, {URL, [], "application/x-www-form-urlencoded", Data}, [], []).
|
||||
|
||||
post(URL, Token0, CSRFToken0, User0, Password0) ->
|
||||
[Token, CSRFToken, User, Password] =
|
||||
[uri_string:quote(V) || V <- [Token0, CSRFToken0, User0, Password0]],
|
||||
Data =
|
||||
<<"token=",
|
||||
Token/binary,
|
||||
"&csrf_token=",
|
||||
CSRFToken/binary,
|
||||
"&user=",
|
||||
User/binary,
|
||||
"&password=",
|
||||
Password/binary>>,
|
||||
httpc:request(post, {URL, [], "application/x-www-form-urlencoded", Data}, [], []).
|
||||
|
||||
gen_mod_set_opts(OldOpts, NewOpts) ->
|
||||
lists:foldl(fun({Opt, Val}, Opts) -> gen_mod:set_opt(Opt, Val, Opts) end,
|
||||
OldOpts,
|
||||
|
||||
+5
-3
@@ -280,9 +280,9 @@ muc_master(Config) ->
|
||||
true = is_feature_advertised(Config, ?NS_MAM_1, Room),
|
||||
true = is_feature_advertised(Config, ?NS_MAM_2, Room),
|
||||
%% We now sending some messages again
|
||||
send_messages_to_room(Config, lists:seq(1, 5)),
|
||||
send_messages_to_room(Config, lists:seq(101, 105)),
|
||||
%% And retrieve them via MAM again.
|
||||
recv_messages_from_room(Config, lists:seq(1, 5)),
|
||||
recv_messages_from_room(Config, lists:seq(101, 105)),
|
||||
put_event(Config, disconnect),
|
||||
muc_tests:leave(Config),
|
||||
clean(disconnect(Config)).
|
||||
@@ -556,7 +556,9 @@ recv_messages_from_room(Config, Range) ->
|
||||
sub_els = [El]}]}]} = recv_message(Config),
|
||||
#message{from = MyNickJID,
|
||||
type = groupchat,
|
||||
body = Body} = xmpp:decode(El)
|
||||
body = Body} = Nested = xmpp:decode(El),
|
||||
ct:comment("Verify that there is just single stanza-id", []),
|
||||
?match([#stanza_id{}], xmpp:get_subtags(Nested, #stanza_id{}))
|
||||
end, Range),
|
||||
#iq{from = Room, id = I, type = result,
|
||||
sub_els = [#mam_fin{xmlns = ?NS_MAM_2,
|
||||
|
||||
+35
-11
@@ -308,7 +308,15 @@ deny_group_master(Config) ->
|
||||
deny_master(Config, {group, Group}).
|
||||
|
||||
deny_group_slave(Config) ->
|
||||
deny_slave(Config).
|
||||
%% [FIXME] needed to make https://github.com/processone/ejabberd/pull/4512 work, but unclear why
|
||||
case ?config(server, Config) of
|
||||
<<"mnesia", _/binary>> ->
|
||||
deny_slave(Config, 2);
|
||||
<<"redis", _/binary>> ->
|
||||
deny_slave(Config, 2);
|
||||
_ ->
|
||||
deny_slave(Config)
|
||||
end.
|
||||
|
||||
deny_sub_both_master(Config) ->
|
||||
deny_master(Config, {subscription, <<"both">>}).
|
||||
@@ -320,7 +328,7 @@ deny_sub_from_master(Config) ->
|
||||
deny_master(Config, {subscription, <<"from">>}).
|
||||
|
||||
deny_sub_from_slave(Config) ->
|
||||
deny_slave(Config, 1).
|
||||
deny_slave(Config, 2).
|
||||
|
||||
deny_sub_to_master(Config) ->
|
||||
deny_master(Config, {subscription, <<"to">>}).
|
||||
@@ -393,7 +401,7 @@ deny_master(Config, {Type, Value}) ->
|
||||
end,
|
||||
case is_other_blocked(Opts) of
|
||||
true ->
|
||||
check_other_blocked(Config, 'not-acceptable', Value);
|
||||
check_other_blocked(Config, 'not-acceptable', Value, Type);
|
||||
false -> ok
|
||||
end,
|
||||
ct:comment("Waiting for slave to finish processing our stanzas"),
|
||||
@@ -723,25 +731,41 @@ recv_err_and_roster_pushes(Config, Count) ->
|
||||
recv_presence(Config).
|
||||
|
||||
check_other_blocked(Config, Reason, Subscription) ->
|
||||
check_other_blocked(Config, Reason, Subscription, undefined).
|
||||
|
||||
check_other_blocked(Config, Reason, Subscription, TType) ->
|
||||
PeerJID = ?config(peer, Config),
|
||||
ct:comment("Checking if subscriptions and presence-errors are blocked"),
|
||||
send(Config, #presence{type = error, to = PeerJID}),
|
||||
ct:pal("checking subscription ~p for ~p", [Subscription, TType]),
|
||||
{ErrorFor, PushFor} = case Subscription of
|
||||
<<"both">> ->
|
||||
{[subscribe, subscribed],
|
||||
{[subscribe],
|
||||
[unsubscribe, unsubscribed]};
|
||||
<<"from">> ->
|
||||
{[subscribe, subscribed, unsubscribe],
|
||||
{[subscribe],
|
||||
[subscribe, unsubscribe, unsubscribed]};
|
||||
<<"to">> ->
|
||||
{[unsubscribe],
|
||||
[subscribed, unsubscribe, unsubscribed]};
|
||||
{[subscribe],
|
||||
[subscribed, unsubscribed]};
|
||||
<<"none">> ->
|
||||
{[subscribe, subscribed, unsubscribe, unsubscribed],
|
||||
[subscribe, unsubscribe]};
|
||||
{[subscribe, unsubscribe, unsubscribed],
|
||||
[subscribe, unsubscribe, unsubscribed]};
|
||||
_ ->
|
||||
{[subscribe, subscribed, unsubscribe, unsubscribed],
|
||||
[unsubscribe, unsubscribed]}
|
||||
%% [FIXME] needed to make
|
||||
%% https://github.com/processone/ejabberd/pull/4512 work, but
|
||||
%% unclear why
|
||||
case {TType, ?config(server, Config)} of
|
||||
{group, <<"mnesia", _/binary>>} ->
|
||||
{[subscribe],
|
||||
[unsubscribe, unsubscribed]};
|
||||
{group, <<"redis", _/binary>>} ->
|
||||
{[subscribe],
|
||||
[unsubscribe, unsubscribed]};
|
||||
_ ->
|
||||
{[subscribe, unsubscribe, unsubscribed],
|
||||
[unsubscribe, unsubscribed]}
|
||||
end
|
||||
end,
|
||||
lists:foreach(
|
||||
fun(Type) ->
|
||||
|
||||
+125
-69
@@ -35,7 +35,8 @@
|
||||
-record(state, {subscription = none :: none | from | to | both,
|
||||
peer_available = false,
|
||||
pending_in = false :: boolean(),
|
||||
pending_out = false :: boolean()}).
|
||||
pending_out = false :: boolean(),
|
||||
approved = false :: boolean()}).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -51,7 +52,7 @@ stop(_TestCase, Config) ->
|
||||
%%%===================================================================
|
||||
single_cases() ->
|
||||
{roster_single, [sequence],
|
||||
[single_test(feature_enabled),
|
||||
[single_test(features_enabled),
|
||||
single_test(iq_set_many_items),
|
||||
single_test(iq_set_duplicated_groups),
|
||||
single_test(iq_get_item),
|
||||
@@ -60,9 +61,10 @@ single_cases() ->
|
||||
single_test(set_item),
|
||||
single_test(version)]}.
|
||||
|
||||
feature_enabled(Config) ->
|
||||
ct:comment("Checking if roster versioning stream feature is set"),
|
||||
features_enabled(Config) ->
|
||||
ct:comment("Checking if roster stream feature (versioning and pre-approval) is set"),
|
||||
true = ?config(rosterver, Config),
|
||||
true = ?config(pre_approval, Config),
|
||||
disconnect(Config).
|
||||
|
||||
set_item(Config) ->
|
||||
@@ -160,6 +162,7 @@ subscribe_slave(Config) ->
|
||||
|
||||
process_subscriptions_master(Config, Actions) ->
|
||||
EnumeratedActions = lists:zip(lists:seq(1, length(Actions)), Actions),
|
||||
ct:pal("actions: ~p", [EnumeratedActions]),
|
||||
self_presence(Config, available),
|
||||
Peer = ?config(peer, Config),
|
||||
lists:foldl(
|
||||
@@ -265,10 +268,14 @@ recv_push(Config) ->
|
||||
{Ver, PushItem}.
|
||||
|
||||
recv_push(Config, Subscription, Ask) ->
|
||||
recv_push(Config, Subscription, Ask, false).
|
||||
|
||||
recv_push(Config, Subscription, Ask, Approved) ->
|
||||
PeerJID = ?config(peer, Config),
|
||||
PeerBareJID = jid:remove_resource(PeerJID),
|
||||
Match = #roster_item{jid = PeerBareJID,
|
||||
subscription = Subscription,
|
||||
approved = Approved,
|
||||
ask = Ask,
|
||||
groups = [],
|
||||
name = <<"">>},
|
||||
@@ -316,11 +323,12 @@ check_roster([], _Config, _State) ->
|
||||
ok;
|
||||
check_roster([Roster], _Config, State) ->
|
||||
case {Roster#roster.subscription == State#state.subscription,
|
||||
Roster#roster.approved == State#state.approved,
|
||||
Roster#roster.ask, State#state.pending_in, State#state.pending_out} of
|
||||
{true, both, true, true} -> ok;
|
||||
{true, in, true, false} -> ok;
|
||||
{true, out, false, true} -> ok;
|
||||
{true, none, false, false} -> ok;
|
||||
{true, true, both, true, true} -> ok;
|
||||
{true, true, in, true, false} -> ok;
|
||||
{true, true, out, false, true} -> ok;
|
||||
{true, true, none, false, false} -> ok;
|
||||
_ ->
|
||||
ct:fail({roster_mismatch, State, Roster})
|
||||
end.
|
||||
@@ -340,43 +348,50 @@ check_roster_item(Config, State) ->
|
||||
|
||||
%% RFC6121, A.2.1
|
||||
transition(Id, Config, out, subscribe,
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out, approved = Approved} = State) ->
|
||||
PeerJID = ?config(peer, Config),
|
||||
PeerBareJID = jid:remove_resource(PeerJID),
|
||||
{is_approved, PeerApproved} = get_event(Config),
|
||||
send(Config, #presence{id = Id, to = PeerBareJID, type = subscribe}),
|
||||
case {Sub, Out, In} of
|
||||
{none, false, _} ->
|
||||
recv_push(Config, none, subscribe),
|
||||
case {Sub, Out, In, PeerApproved} of
|
||||
{none, false, _, true} ->
|
||||
recv_push(Config, none, subscribe, Approved),
|
||||
recv_push(Config, to, undefined, Approved),
|
||||
recv_subscription(Config, subscribed),
|
||||
recv_presence(Config, available),
|
||||
State#state{subscription = to};
|
||||
{none, false, _, false} ->
|
||||
recv_push(Config, none, subscribe, Approved),
|
||||
State#state{pending_out = true};
|
||||
{none, true, false} ->
|
||||
{none, true, false, false} ->
|
||||
%% BUG: we should not receive roster push here
|
||||
recv_push(Config, none, subscribe),
|
||||
recv_push(Config, none, subscribe, Approved),
|
||||
State;
|
||||
{from, false, false} ->
|
||||
recv_push(Config, from, subscribe),
|
||||
{from, false, false, false} ->
|
||||
recv_push(Config, from, subscribe, Approved),
|
||||
State#state{pending_out = true};
|
||||
_ ->
|
||||
State
|
||||
end;
|
||||
%% RFC6121, A.2.2
|
||||
transition(Id, Config, out, unsubscribe,
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out, approved = Approved} = State) ->
|
||||
PeerJID = ?config(peer, Config),
|
||||
PeerBareJID = jid:remove_resource(PeerJID),
|
||||
send(Config, #presence{id = Id, to = PeerBareJID, type = unsubscribe}),
|
||||
case {Sub, Out, In} of
|
||||
{none, true, _} ->
|
||||
recv_push(Config, none, undefined),
|
||||
recv_push(Config, none, undefined, Approved),
|
||||
State#state{pending_out = false};
|
||||
{to, false, _} ->
|
||||
recv_push(Config, none, undefined),
|
||||
recv_push(Config, none, undefined, Approved),
|
||||
recv_presence(Config, unavailable),
|
||||
State#state{subscription = none, peer_available = false};
|
||||
{from, true, false} ->
|
||||
recv_push(Config, from, undefined),
|
||||
recv_push(Config, from, undefined, Approved),
|
||||
State#state{pending_out = false};
|
||||
{both, false, false} ->
|
||||
recv_push(Config, from, undefined),
|
||||
recv_push(Config, from, undefined, Approved),
|
||||
recv_presence(Config, unavailable),
|
||||
State#state{subscription = from, peer_available = false};
|
||||
_ ->
|
||||
@@ -390,59 +405,94 @@ transition(Id, Config, out, subscribed,
|
||||
send(Config, #presence{id = Id, to = PeerBareJID, type = subscribed}),
|
||||
case {Sub, Out, In} of
|
||||
{none, false, true} ->
|
||||
put_event(Config, {is_approved, false}),
|
||||
recv_push(Config, from, undefined),
|
||||
State#state{subscription = from, pending_in = false};
|
||||
{none, true, true} ->
|
||||
put_event(Config, {is_approved, false}),
|
||||
recv_push(Config, from, subscribe),
|
||||
State#state{subscription = from, pending_in = false};
|
||||
{to, false, true} ->
|
||||
recv_push(Config, both, undefined),
|
||||
State#state{subscription = both, pending_in = false};
|
||||
{to, false, _} ->
|
||||
put_event(Config, {is_approved, false}),
|
||||
%% BUG: we should not transition to 'both' state
|
||||
recv_push(Config, both, undefined),
|
||||
State#state{subscription = both};
|
||||
State#state{subscription = both, pending_in = false};
|
||||
{to, false, false} ->
|
||||
put_event(Config, {is_approved, true}),
|
||||
recv_push(Config, to, undefined, true),
|
||||
State#state{approved = true};
|
||||
{none, true, false} ->
|
||||
put_event(Config, {is_approved, true}),
|
||||
recv_push(Config, none, subscribe, true),
|
||||
State#state{approved = true};
|
||||
{none, false, false} ->
|
||||
put_event(Config, {is_approved, true}),
|
||||
recv_push(Config, none, undefined, true),
|
||||
State#state{approved = true};
|
||||
_ ->
|
||||
put_event(Config, {is_approved, false}),
|
||||
State
|
||||
end;
|
||||
%% RFC6121, A.2.4
|
||||
transition(Id, Config, out, unsubscribed,
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out, approved = Approved} = State) ->
|
||||
PeerJID = ?config(peer, Config),
|
||||
PeerBareJID = jid:remove_resource(PeerJID),
|
||||
send(Config, #presence{id = Id, to = PeerBareJID, type = unsubscribed}),
|
||||
case {Sub, Out, In} of
|
||||
{none, false, true} ->
|
||||
State#state{subscription = none, pending_in = false};
|
||||
{none, true, true} ->
|
||||
recv_push(Config, none, subscribe),
|
||||
State#state{subscription = none, pending_in = false};
|
||||
{to, _, true} ->
|
||||
State#state{pending_in = false};
|
||||
{from, false, _} ->
|
||||
case {Sub, Out, In, Approved} of
|
||||
{none, false, true, true} ->
|
||||
recv_push(Config, none, undefined),
|
||||
State#state{subscription = none};
|
||||
{from, true, _} ->
|
||||
State#state{subscription = none, pending_in = false, approved = false};
|
||||
{none, false, true, false} ->
|
||||
State#state{subscription = none, pending_in = false, approved = false};
|
||||
{none, true, true, _} ->
|
||||
recv_push(Config, none, subscribe),
|
||||
State#state{subscription = none};
|
||||
{both, _, _} ->
|
||||
State#state{subscription = none, pending_in = false, approved = false};
|
||||
{to, _, true, true} ->
|
||||
recv_push(Config, to, undefined),
|
||||
State#state{subscription = to};
|
||||
State#state{pending_in = false, approved = false};
|
||||
{to, _, true, false} ->
|
||||
State#state{pending_in = false, approved = false};
|
||||
{from, false, _, _} ->
|
||||
recv_push(Config, none, undefined),
|
||||
State#state{subscription = none, approved = false};
|
||||
{from, true, _, _} ->
|
||||
recv_push(Config, none, subscribe),
|
||||
State#state{subscription = none, approved = false};
|
||||
{both, _, _, _} ->
|
||||
recv_push(Config, to, undefined),
|
||||
State#state{subscription = to, approved = false};
|
||||
{_, true, _, true} ->
|
||||
recv_push(Config, Sub, subscribe),
|
||||
State#state{approved = false};
|
||||
{_, false, _, true} ->
|
||||
recv_push(Config, Sub, undefined),
|
||||
State#state{approved = false};
|
||||
_ ->
|
||||
State
|
||||
State#state{approved = false}
|
||||
end;
|
||||
%% RFC6121, A.3.1
|
||||
transition(_, Config, in, subscribe = Type,
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
|
||||
case {Sub, Out, In} of
|
||||
{none, false, false} ->
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out, approved = Approved} = State) ->
|
||||
put_event(Config, {is_approved, Approved}),
|
||||
case {Sub, Out, In, Approved} of
|
||||
{to, false, false, true} ->
|
||||
recv_push(Config, both, undefined, true),
|
||||
State#state{subscription = both};
|
||||
{none, true, false, true} ->
|
||||
recv_push(Config, from, subscribe, true),
|
||||
State#state{subscription = from};
|
||||
{none, false, false, true} ->
|
||||
recv_push(Config, from, undefined, true),
|
||||
State#state{subscription = from};
|
||||
{none, false, false, false} ->
|
||||
recv_subscription(Config, Type),
|
||||
State#state{pending_in = true};
|
||||
{none, true, false} ->
|
||||
{none, true, false, false} ->
|
||||
recv_push(Config, none, subscribe),
|
||||
recv_subscription(Config, Type),
|
||||
State#state{pending_in = true};
|
||||
{to, false, false} ->
|
||||
{to, false, false, false} ->
|
||||
%% BUG: we should not receive roster push in this state!
|
||||
recv_push(Config, to, undefined),
|
||||
recv_subscription(Config, Type),
|
||||
@@ -452,24 +502,24 @@ transition(_, Config, in, subscribe = Type,
|
||||
end;
|
||||
%% RFC6121, A.3.2
|
||||
transition(_, Config, in, unsubscribe = Type,
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out, approved = Approved} = State) ->
|
||||
case {Sub, Out, In} of
|
||||
{none, _, true} ->
|
||||
State#state{pending_in = false};
|
||||
{to, _, true} ->
|
||||
recv_push(Config, to, undefined),
|
||||
recv_push(Config, to, undefined, Approved),
|
||||
recv_subscription(Config, Type),
|
||||
State#state{pending_in = false};
|
||||
{from, false, _} ->
|
||||
recv_push(Config, none, undefined),
|
||||
recv_push(Config, none, undefined, Approved),
|
||||
recv_subscription(Config, Type),
|
||||
State#state{subscription = none};
|
||||
{from, true, _} ->
|
||||
recv_push(Config, none, subscribe),
|
||||
recv_push(Config, none, subscribe, Approved),
|
||||
recv_subscription(Config, Type),
|
||||
State#state{subscription = none};
|
||||
{both, _, _} ->
|
||||
recv_push(Config, to, undefined),
|
||||
recv_push(Config, to, undefined, Approved),
|
||||
recv_subscription(Config, Type),
|
||||
State#state{subscription = to};
|
||||
_ ->
|
||||
@@ -477,52 +527,58 @@ transition(_, Config, in, unsubscribe = Type,
|
||||
end;
|
||||
%% RFC6121, A.3.3
|
||||
transition(_, Config, in, subscribed = Type,
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out, approved = Approve} = State) ->
|
||||
{is_approved, PeerApproved} = get_event(Config),
|
||||
case {Sub, Out, In} of
|
||||
{none, true, _} ->
|
||||
recv_push(Config, to, undefined),
|
||||
recv_push(Config, to, undefined, Approve),
|
||||
recv_subscription(Config, Type),
|
||||
recv_presence(Config, available),
|
||||
State#state{subscription = to, pending_out = false, peer_available = true};
|
||||
{from, true, _} ->
|
||||
recv_push(Config, both, undefined),
|
||||
recv_push(Config, both, undefined, Approve),
|
||||
recv_subscription(Config, Type),
|
||||
recv_presence(Config, available),
|
||||
State#state{subscription = both, pending_out = false, peer_available = true};
|
||||
{from, false, _} ->
|
||||
%% BUG: we should not transition to 'both' in this state
|
||||
recv_push(Config, both, undefined),
|
||||
recv_subscription(Config, Type),
|
||||
recv_presence(Config, available),
|
||||
State#state{subscription = both, pending_out = false, peer_available = true};
|
||||
case PeerApproved of
|
||||
false ->
|
||||
%% BUG: we should not transition to 'both' in this state
|
||||
recv_push(Config, both, undefined, Approve),
|
||||
recv_subscription(Config, Type),
|
||||
recv_presence(Config, available),
|
||||
State#state{subscription = both, pending_out = false, peer_available = true};
|
||||
true ->
|
||||
State
|
||||
end;
|
||||
_ ->
|
||||
State
|
||||
end;
|
||||
%% RFC6121, A.3.4
|
||||
transition(_, Config, in, unsubscribed = Type,
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out, approved = Approved} = State) ->
|
||||
case {Sub, Out, In} of
|
||||
{none, true, true} ->
|
||||
%% BUG: we should receive roster push in this state!
|
||||
recv_subscription(Config, Type),
|
||||
State#state{subscription = none, pending_out = false};
|
||||
{none, true, false} ->
|
||||
recv_push(Config, none, undefined),
|
||||
recv_push(Config, none, undefined, Approved),
|
||||
recv_subscription(Config, Type),
|
||||
State#state{subscription = none, pending_out = false};
|
||||
{none, false, false} ->
|
||||
State;
|
||||
{to, false, _} ->
|
||||
recv_push(Config, none, undefined),
|
||||
recv_push(Config, none, undefined, Approved),
|
||||
recv_presence(Config, unavailable),
|
||||
recv_subscription(Config, Type),
|
||||
State#state{subscription = none, peer_available = false};
|
||||
{from, true, false} ->
|
||||
recv_push(Config, from, undefined),
|
||||
recv_push(Config, from, undefined, Approved),
|
||||
recv_subscription(Config, Type),
|
||||
State#state{subscription = from, pending_out = false};
|
||||
{both, _, _} ->
|
||||
recv_push(Config, from, undefined),
|
||||
recv_push(Config, from, undefined, Approved),
|
||||
recv_presence(Config, unavailable),
|
||||
recv_subscription(Config, Type),
|
||||
State#state{subscription = from, peer_available = false};
|
||||
@@ -550,15 +606,15 @@ transition(Id, Config, out, remove,
|
||||
#state{};
|
||||
%% Incoming roster remove
|
||||
transition(_, Config, in, remove,
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out, approved = Approved} = State) ->
|
||||
case {Sub, Out, In} of
|
||||
{none, true, _} ->
|
||||
ok;
|
||||
{from, false, _} ->
|
||||
recv_push(Config, none, undefined),
|
||||
recv_push(Config, none, undefined, Approved),
|
||||
recv_subscription(Config, unsubscribe);
|
||||
{from, true, _} ->
|
||||
recv_push(Config, none, subscribe),
|
||||
recv_push(Config, none, subscribe, Approved),
|
||||
recv_subscription(Config, unsubscribe);
|
||||
{to, false, _} ->
|
||||
%% BUG: we should receive push here
|
||||
@@ -567,9 +623,9 @@ transition(_, Config, in, remove,
|
||||
recv_subscription(Config, unsubscribed);
|
||||
{both, _, _} ->
|
||||
recv_presence(Config, unavailable),
|
||||
recv_push(Config, to, undefined),
|
||||
recv_push(Config, to, undefined, Approved),
|
||||
recv_subscription(Config, unsubscribe),
|
||||
recv_push(Config, none, undefined),
|
||||
recv_push(Config, none, undefined, Approved),
|
||||
recv_subscription(Config, unsubscribed);
|
||||
_ ->
|
||||
ok
|
||||
|
||||
@@ -130,6 +130,7 @@ init_config(Config) ->
|
||||
{db_xmlns, <<"">>},
|
||||
{mechs, []},
|
||||
{rosterver, false},
|
||||
{pre_approval, false},
|
||||
{lang, <<"en">>},
|
||||
{base_dir, BaseDir},
|
||||
{receiver, undefined},
|
||||
@@ -272,6 +273,15 @@ connect(Config) ->
|
||||
component -> NewConfig
|
||||
end.
|
||||
|
||||
connect_sasl2(Config, Jid) ->
|
||||
Config2 = set_opt(stream_from, Jid, Config),
|
||||
NewConfig = init_stream(Config2),
|
||||
case ?config(type, NewConfig) of
|
||||
client -> process_stream_features(NewConfig);
|
||||
server -> process_stream_features(NewConfig);
|
||||
component -> NewConfig
|
||||
end.
|
||||
|
||||
tcp_connect(Config) ->
|
||||
case ?config(receiver, Config) of
|
||||
undefined ->
|
||||
@@ -531,6 +541,8 @@ wait_auth_SASL_result(Config, ShouldFail) ->
|
||||
set_opt(csi, true, ConfigAcc);
|
||||
(#rosterver_feature{}, ConfigAcc) ->
|
||||
set_opt(rosterver, true, ConfigAcc);
|
||||
(#feature_pre_approval{}, ConfigAcc) ->
|
||||
set_opt(pre_approval, true, ConfigAcc);
|
||||
(#compression{methods = Ms}, ConfigAcc) ->
|
||||
set_opt(compression, Ms, ConfigAcc);
|
||||
(_, ConfigAcc) ->
|
||||
@@ -547,6 +559,70 @@ wait_auth_SASL_result(Config, ShouldFail) ->
|
||||
ct:fail(sasl_auth_failed)
|
||||
end.
|
||||
|
||||
auth_fast_token(Mech, Token, Config, ShouldFail) ->
|
||||
Pkt = #sasl2_authenticate{mechanism = Mech, initial_response = Token,
|
||||
sub_els = [#bind2_bind{tag = <<"TestRunner">>}],
|
||||
user_agent = #sasl2_user_agent{id = <<"9A62C3A9-FE59-43B7-80F3-9E8EE25DE6C5">>,
|
||||
software = <<"TestRunner">>, device = <<"tester">>}},
|
||||
send(Config, Pkt),
|
||||
wait_auth_SASL2_result(Config, ShouldFail).
|
||||
|
||||
auth_SASL2(Mech, Config, ShouldFail) ->
|
||||
Creds = {?config(user, Config),
|
||||
?config(server, Config),
|
||||
?config(password, Config)},
|
||||
{Response, SASL} = sasl_new(Mech, Creds),
|
||||
Pkt = #sasl2_authenticate{mechanism = Mech, initial_response = Response,
|
||||
sub_els = [#bind2_bind{tag = <<"TestRunner">>}, #fast_request_token{mech = <<"HT-SHA-256-NONE">>}],
|
||||
user_agent = #sasl2_user_agent{id = <<"9A62C3A9-FE59-43B7-80F3-9E8EE25DE6C5">>,
|
||||
software = <<"TestRunner">>, device = <<"tester">>}},
|
||||
send(Config, Pkt),
|
||||
wait_auth_SASL2_result(set_opt(sasl, SASL, Config), ShouldFail).
|
||||
|
||||
wait_auth_SASL2_result(Config, ShouldFail) ->
|
||||
receive
|
||||
#sasl2_success{} when ShouldFail ->
|
||||
ct:fail(sasl_auth_should_have_failed);
|
||||
#sasl2_success{jid = JID} = Pkt ->
|
||||
case {xmpp:get_subtag(Pkt, #bind2_bound{}), xmpp:get_subtag(Pkt, #fast_token{})} of
|
||||
{false, _} ->
|
||||
ct:fail(sasl2_bound_missing);
|
||||
{_, Token} ->
|
||||
{User, _S, Resource} = jid:tolower(JID),
|
||||
RawToken = case Token of
|
||||
#fast_token{token = T} -> T;
|
||||
_ -> <<>>
|
||||
end,
|
||||
Config2 = set_opt(user, User,
|
||||
set_opt(resource, Resource,
|
||||
set_opt(fast_token, RawToken, Config))),
|
||||
receive #stream_features{sub_els = Fs} ->
|
||||
lists:foldl(
|
||||
fun(#feature_sm{}, ConfigAcc) ->
|
||||
set_opt(sm, true, ConfigAcc);
|
||||
(#feature_csi{}, ConfigAcc) ->
|
||||
set_opt(csi, true, ConfigAcc);
|
||||
(#rosterver_feature{}, ConfigAcc) ->
|
||||
set_opt(rosterver, true, ConfigAcc);
|
||||
(#feature_pre_approval{}, ConfigAcc) ->
|
||||
set_opt(pre_approval, true, ConfigAcc);
|
||||
(#compression{methods = Ms}, ConfigAcc) ->
|
||||
set_opt(compression, Ms, ConfigAcc);
|
||||
(_, ConfigAcc) ->
|
||||
ConfigAcc
|
||||
end, Config2, Fs)
|
||||
end
|
||||
end;
|
||||
#sasl2_challenge{text = ClientIn} ->
|
||||
{Response, SASL} = (?config(sasl, Config))(ClientIn),
|
||||
send(Config, #sasl2_response{text = Response}),
|
||||
wait_auth_SASL2_result(set_opt(sasl, SASL, Config), ShouldFail);
|
||||
#sasl2_failure{} when ShouldFail ->
|
||||
Config;
|
||||
#sasl2_failure{} ->
|
||||
ct:fail(sasl_auth_failed)
|
||||
end.
|
||||
|
||||
re_register(Config) ->
|
||||
User = ?config(user, Config),
|
||||
Server = ?config(server, Config),
|
||||
|
||||
+12
-2
@@ -167,10 +167,20 @@ put_request(_Config, URL0, Data) ->
|
||||
get_request(_Config, URL0, Data) ->
|
||||
ct:comment("Getting ~B bytes from ~s", [size(Data), URL0]),
|
||||
URL = binary_to_list(URL0),
|
||||
{ok, {{"HTTP/1.1", 200, _}, _, Body}} =
|
||||
{ok, {{"HTTP/1.1", 200, _}, Headers, Body}} =
|
||||
httpc:request(get, {URL, []}, [], [{body_format, binary}]),
|
||||
ct:comment("Checking returned body"),
|
||||
Body = Data.
|
||||
Body = Data,
|
||||
ct:comment("Request had Etag"),
|
||||
Etag = ?match({_, Etag}, lists:keyfind("etag", 1, Headers), Etag),
|
||||
ct:comment("Request had Last-Modified"),
|
||||
LM = ?match({_, LM}, lists:keyfind("last-modified", 1, Headers), LM),
|
||||
ct:comment("Request with Etag are handled correctly"),
|
||||
{ok, {{"HTTP/1.1", 304, _}, _, _}} =
|
||||
httpc:request(get, {URL, [{"If-None-Match", ["\"",Etag,"\""]}]}, [], [{body_format, binary}]),
|
||||
ct:comment("Request with If-Modified-Since are handled correctly"),
|
||||
{ok, {{"HTTP/1.1", 304, _}, _, _}} =
|
||||
httpc:request(get, {URL, [{"If-Modified-Since", LM}]}, [], [{body_format, binary}]).
|
||||
|
||||
max_size_exceed(Config, NS) ->
|
||||
To = upload_jid(Config),
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
set -e
|
||||
|
||||
jquery_checksum='fc9a93dd241f6b045cbff0481cf4e1901becd0e12fb45166a8f17f95823f0b1a';
|
||||
bootstrap4_checksum='dc9b29fe7100e69d1a512860497bd2237eadccde6e813e588416429359832dce';
|
||||
jquery_checksum='39a546ea9ad97f8bfaf5d3e0e8f8556adb415e470e59007ada9759dce472adaa';
|
||||
bootstrap_checksum='3258c873cbcb1e2d81f4374afea2ea6437d9eee9077041073fd81dd579c5ba6b';
|
||||
|
||||
check() {
|
||||
echo "$1 $2" | sha256sum -c - || (echo "checksum failed: $2 (does not match $1)"; exit 1)
|
||||
@@ -17,15 +17,16 @@ install_dir="$1"
|
||||
|
||||
mkdir -p "$install_dir/jquery"
|
||||
jquery="$(mktemp /tmp/jquery.XXXXXXXXX)"
|
||||
curl -s -o $jquery https://code.jquery.com/jquery-3.7.1.min.js
|
||||
curl -s -o $jquery https://code.jquery.com/jquery-4.0.0.min.js
|
||||
check $jquery_checksum $jquery
|
||||
mv $jquery "$install_dir/jquery/jquery.min.js"
|
||||
|
||||
bootstrap4="$(mktemp /tmp/bootstrap4.XXXXXXXXX)"
|
||||
curl -L -s -o $bootstrap4 https://github.com/twbs/bootstrap/releases/download/v4.6.2/bootstrap-4.6.2-dist.zip
|
||||
check $bootstrap4_checksum $bootstrap4
|
||||
bootstrap="$(mktemp /tmp/bootstrap.XXXXXXXXX)"
|
||||
curl -L -s -o $bootstrap https://github.com/twbs/bootstrap/releases/download/v5.3.8/bootstrap-5.3.8-dist.zip
|
||||
check $bootstrap_checksum $bootstrap
|
||||
|
||||
unzip -q -d "$install_dir" $bootstrap4
|
||||
mv "$install_dir/bootstrap-4.6.2-dist" "$install_dir/bootstrap4"
|
||||
rm $bootstrap4
|
||||
rm -rf "$install_dir/bootstrap"
|
||||
unzip -q -d "$install_dir" $bootstrap
|
||||
mv "$install_dir/bootstrap-5.3.8-dist" "$install_dir/bootstrap"
|
||||
rm $bootstrap
|
||||
echo "landing page dependencies for mod_invites installed to $install_dir"
|
||||
|
||||
+9
-6
@@ -67,11 +67,11 @@ rel_vsn=$(git describe --tags | sed -e 's/-g.*//' -e 's/-/./' | tr -d '[:space:]
|
||||
mix_vsn=$(mix_version "$rel_vsn")
|
||||
crosstool_vsn='1.27.0'
|
||||
termcap_vsn='1.3.1'
|
||||
expat_vsn='2.7.4'
|
||||
zlib_vsn='1.3.1'
|
||||
expat_vsn='2.7.5'
|
||||
zlib_vsn='1.3.2'
|
||||
yaml_vsn='0.2.5'
|
||||
ssl_vsn='3.5.5'
|
||||
otp_vsn='28.3.1'
|
||||
otp_vsn='28.4.1'
|
||||
elixir_vsn='1.19.5'
|
||||
pam_vsn='1.6.1' # Newer Linux-PAM versions use Meson, we don't support that yet.
|
||||
png_vsn='1.6.45'
|
||||
@@ -197,8 +197,8 @@ check_configured_dep_vsns()
|
||||
'https://www.sqlite.org/download.html' \
|
||||
'sqlite-autoconf-\([1-9][0-9]*\)\.tar\.gz'
|
||||
check_vsn 'ODBC' "$odbc_vsn" \
|
||||
'http://www.unixodbc.org/download.html' \
|
||||
'unixODBC-\([1-9][0-9.]*\)\.tar\.gz'
|
||||
'https://github.com/lurcher/unixODBC/releases' \
|
||||
'\([1-9]\.[0-9][0-9]*\.[0-9][0-9]*\)'
|
||||
#
|
||||
# Linux-PAM uses Meson since version 1.7.0, we don't support that yet.
|
||||
#
|
||||
@@ -692,6 +692,9 @@ build_deps()
|
||||
# Revert https://github.com/erlang/otp/commit/53ef5df40c733ce3d8215c5c98805f99f378f656
|
||||
# because it breaks MSSQL, see https://github.com/processone/ejabberd/issues/4178
|
||||
sed -i 's|if(size == 0 && (sql_type == SQL_LONGVARCHAR|if((sql_type == SQL_LONGVARCHAR|g' lib/odbc/c_src/odbcserver.c
|
||||
# Enable SSL features for static NIFs: https://github.com/erlang/otp/pull/10817
|
||||
sed -i 's|^\(ALL_STATIC_CFLAGS = @DED_STATIC_CFLAGS@.*TYPE_EXTRA_CFLAGS)\) \$(CONFIGURE_ARGS)|\1 @SSL_FLAGS@ $(CONFIGURE_ARGS)|' \
|
||||
lib/crypto/c_src/Makefile.in
|
||||
# The additional CFLAGS/LIBS below are required by --enable-static-nifs.
|
||||
# The "-ldl" flag specifically is only needed for ODBC, though.
|
||||
$configure \
|
||||
@@ -891,7 +894,7 @@ else
|
||||
curl -fsSLO "https://www.ijg.org/files/$jpeg_tar"
|
||||
curl -fsSLO "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/$webp_tar"
|
||||
curl -fsSLO "https://github.com/libgd/libgd/releases/download/gd-$gd_vsn/$gd_tar"
|
||||
curl -fsSLO "http://www.unixodbc.org/$odbc_tar"
|
||||
curl -fsSLO "https://github.com/lurcher/unixODBC/releases/download/v$odbc_vsn/$odbc_tar"
|
||||
curl -fsSLO "https://www.sqlite.org/$(date '+%Y')/$sqlite_tar" \
|
||||
|| curl -fsSLO "https://www.sqlite.org/$(date -d '1 year ago' '+%Y')/$sqlite_tar" \
|
||||
|| curl -fsSLO "https://www.sqlite.org/$(date -d '2 years ago' '+%Y')/$sqlite_tar"
|
||||
|
||||
@@ -193,8 +193,6 @@ make_package()
|
||||
--provides 'xmpp-server' \
|
||||
--no-depends \
|
||||
--no-auto-depends \
|
||||
--deb-recommends 'libjs-jquery' \
|
||||
--deb-recommends 'libjs-bootstrap4' \
|
||||
--deb-maintainerscripts-force-errorchecks \
|
||||
--deb-systemd-enable \
|
||||
--deb-systemd-auto-start \
|
||||
|
||||
Reference in New Issue
Block a user