Compare commits
128 Commits
cf-priorit
...
v2.8.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aaa4976c7d | ||
|
|
2525ef71a9 | ||
|
|
75a2cb9236 | ||
|
|
6b82cb2b7a | ||
|
|
260e24f68b | ||
|
|
3c8fabe409 | ||
|
|
1c3ed3ea40 | ||
|
|
497f45e5cd | ||
|
|
f9a411c307 | ||
|
|
7d755ce604 | ||
|
|
f64dbcb6b2 | ||
|
|
0b41356179 | ||
|
|
0928d99813 | ||
|
|
e724aace49 | ||
|
|
b65044712b | ||
|
|
22f48c5ad3 | ||
|
|
1b4d55fb30 | ||
|
|
ad8b22482b | ||
|
|
780c149c81 | ||
|
|
859a4e4f79 | ||
|
|
d0c2b00fbf | ||
|
|
37091f25a8 | ||
|
|
7f1855ad4d | ||
|
|
b42958de9f | ||
|
|
73eaf7b8b6 | ||
|
|
52361a1c48 | ||
|
|
9b48d674b4 | ||
|
|
c0fab933a5 | ||
|
|
df6d760844 | ||
|
|
c6b6c81c83 | ||
|
|
c2d6e0e316 | ||
|
|
5acc6ad0c4 | ||
|
|
a533f352b2 | ||
|
|
262bb723d9 | ||
|
|
a97b6de37e | ||
|
|
9f738e4a85 | ||
|
|
8895bd77c1 | ||
|
|
404fd4c720 | ||
|
|
058de2d761 | ||
|
|
16d490474d | ||
|
|
bd2088c480 | ||
|
|
c42f6289f6 | ||
|
|
92cfa4040b | ||
|
|
3f3af275e7 | ||
|
|
28c653043e | ||
|
|
abe7275f0c | ||
|
|
d4af2be7a0 | ||
|
|
8dd4c3e3c0 | ||
|
|
af25f164ed | ||
|
|
64ede0f11c | ||
|
|
d3565c9b87 | ||
|
|
c332c132fa | ||
|
|
b3534aecda | ||
|
|
8e04912201 | ||
|
|
909f9b3d24 | ||
|
|
cad38573d7 | ||
|
|
a3663e43e4 | ||
|
|
6d451785f0 | ||
|
|
7791901b2d | ||
|
|
2afe1fbeed | ||
|
|
e2097e856e | ||
|
|
03e7a3ea65 | ||
|
|
27f8cc0e52 | ||
|
|
1aa82ff06a | ||
|
|
0ff1f6520a | ||
|
|
ff2a354333 | ||
|
|
543709336c | ||
|
|
afd6d2e0ee | ||
|
|
32efbd5823 | ||
|
|
6dbdabf9fd | ||
|
|
75d57b9f04 | ||
|
|
554547b431 | ||
|
|
b811da6b83 | ||
|
|
ca6bc1dcb0 | ||
|
|
7c3fd42a86 | ||
|
|
04f12d1e2f | ||
|
|
c6b8ea90b7 | ||
|
|
7f8fb8d571 | ||
|
|
f8cfb084e0 | ||
|
|
70b084457a | ||
|
|
6c12244587 | ||
|
|
e7c0365079 | ||
|
|
43b11de596 | ||
|
|
ef45ea5a50 | ||
|
|
483edb70bf | ||
|
|
7516d25bc6 | ||
|
|
2f2918bd3b | ||
|
|
7a5572ad7c | ||
|
|
73d2b3363b | ||
|
|
5c9cebf059 | ||
|
|
ba0cc7fbf9 | ||
|
|
b7f37138f8 | ||
|
|
53a451671c | ||
|
|
65dff6e8e3 | ||
|
|
03a2de961d | ||
|
|
b94310a4cc | ||
|
|
9c594da847 | ||
|
|
93e62de3d2 | ||
|
|
a3efbb3466 | ||
|
|
aaf01b98d2 | ||
|
|
af037b9d70 | ||
|
|
5dafd7e4a7 | ||
|
|
e2b5f4a9fb | ||
|
|
2e58f0db10 | ||
|
|
26b31acbae | ||
|
|
66e96244ef | ||
|
|
4dc0183901 | ||
|
|
d33eded060 | ||
|
|
5913142389 | ||
|
|
66ef28c2e2 | ||
|
|
19c30fc411 | ||
|
|
50bed826d0 | ||
|
|
b5851dd6d4 | ||
|
|
ff1ee7d292 | ||
|
|
9455428048 | ||
|
|
0f919f3d49 | ||
|
|
ef15b44a1b | ||
|
|
bc802bfc77 | ||
|
|
d10a5df3df | ||
|
|
b05d27ce45 | ||
|
|
68a7756621 | ||
|
|
42063cbd5c | ||
|
|
a407a2e0f8 | ||
|
|
6ec1ccf7a3 | ||
|
|
044f4182d0 | ||
|
|
bae30d79c9 | ||
|
|
25a60969fb | ||
|
|
d88dbbc90f |
4
.github/workflows/build.yaml
vendored
4
.github/workflows/build.yaml
vendored
@@ -11,12 +11,12 @@ jobs:
|
||||
name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20.x'
|
||||
go-version: '1.21.x'
|
||||
-
|
||||
name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: './web/package-lock.json'
|
||||
-
|
||||
|
||||
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
@@ -14,12 +14,12 @@ jobs:
|
||||
name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20.x'
|
||||
go-version: '1.21.x'
|
||||
-
|
||||
name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: './web/package-lock.json'
|
||||
-
|
||||
|
||||
4
.github/workflows/test.yaml
vendored
4
.github/workflows/test.yaml
vendored
@@ -11,12 +11,12 @@ jobs:
|
||||
name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20.x'
|
||||
go-version: '1.21.x'
|
||||
-
|
||||
name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: './web/package-lock.json'
|
||||
-
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -13,4 +13,5 @@ secrets/
|
||||
node_modules/
|
||||
.DS_Store
|
||||
__pycache__
|
||||
web/dev-dist/
|
||||
web/dev-dist/
|
||||
venv/
|
||||
|
||||
@@ -2,13 +2,19 @@ FROM golang:1.20-bullseye as builder
|
||||
|
||||
ARG VERSION=dev
|
||||
ARG COMMIT=unknown
|
||||
ARG NODE_MAJOR=18
|
||||
|
||||
RUN apt-get update
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash
|
||||
RUN apt-get install -y \
|
||||
build-essential \
|
||||
nodejs \
|
||||
python3-pip
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential ca-certificates curl gnupg \
|
||||
&& mkdir -p /etc/apt/keyrings \
|
||||
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
|
||||
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" >> /etc/apt/sources.list.d/nodesource.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
nodejs \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
ADD Makefile .
|
||||
@@ -19,7 +25,7 @@ RUN make docs-deps
|
||||
ADD ./mkdocs.yml .
|
||||
ADD ./docs ./docs
|
||||
RUN make docs-build
|
||||
|
||||
|
||||
# web
|
||||
ADD ./web/package.json ./web/package-lock.json ./web/
|
||||
RUN make web-deps
|
||||
|
||||
42
Makefile
42
Makefile
@@ -1,4 +1,6 @@
|
||||
MAKEFLAGS := --jobs=1
|
||||
PYTHON := python3
|
||||
PIP := pip3
|
||||
VERSION := $(shell git describe --tag)
|
||||
COMMIT := $(shell git rev-parse --short HEAD)
|
||||
|
||||
@@ -39,8 +41,8 @@ help:
|
||||
@echo " make web-deps - Install web app dependencies (npm install the universe)"
|
||||
@echo " make web-build - Actually build the web app"
|
||||
@echo " make web-lint - Run eslint on the web app"
|
||||
@echo " make web-format - Run prettier on the web app"
|
||||
@echo " make web-format-check - Run prettier on the web app, but don't change anything"
|
||||
@echo " make web-fmt - Run prettier on the web app"
|
||||
@echo " make web-fmt-check - Run prettier on the web app, but don't change anything"
|
||||
@echo
|
||||
@echo "Build documentation:"
|
||||
@echo " make docs - Build the documentation"
|
||||
@@ -95,6 +97,7 @@ docker-dev:
|
||||
--build-arg COMMIT=$(COMMIT) \
|
||||
./
|
||||
|
||||
|
||||
# Ubuntu-specific
|
||||
|
||||
build-deps-ubuntu:
|
||||
@@ -103,32 +106,27 @@ build-deps-ubuntu:
|
||||
curl \
|
||||
gcc-aarch64-linux-gnu \
|
||||
gcc-arm-linux-gnueabi \
|
||||
python3 \
|
||||
python3-venv \
|
||||
jq
|
||||
which pip3 || sudo apt-get install -y python3-pip
|
||||
|
||||
|
||||
# Documentation
|
||||
|
||||
docs: docs-deps docs-build
|
||||
|
||||
docs-build: .PHONY
|
||||
@if ! /bin/echo -e "import sys\nif sys.version_info < (3,8):\n exit(1)" | python3; then \
|
||||
if which python3.8; then \
|
||||
echo "python3.8 $(shell which mkdocs) build"; \
|
||||
python3.8 $(shell which mkdocs) build; \
|
||||
else \
|
||||
echo "ERROR: Python version too low. mkdocs-material needs >= 3.8"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
else \
|
||||
echo "mkdocs build"; \
|
||||
mkdocs build; \
|
||||
fi
|
||||
docs-venv: .PHONY
|
||||
$(PYTHON) -m venv ./venv
|
||||
|
||||
docs-deps: .PHONY
|
||||
pip3 install -r requirements.txt
|
||||
docs-build: docs-venv
|
||||
(. venv/bin/activate && $(PYTHON) -m mkdocs build)
|
||||
|
||||
docs-deps: docs-venv
|
||||
(. venv/bin/activate && $(PIP) install -r requirements.txt)
|
||||
|
||||
docs-deps-update: .PHONY
|
||||
pip3 install -r requirements.txt --upgrade
|
||||
(. venv/bin/activate && $(PIP) install -r requirements.txt --upgrade)
|
||||
|
||||
|
||||
# Web app
|
||||
@@ -151,10 +149,10 @@ web-deps:
|
||||
web-deps-update:
|
||||
cd web && npm update
|
||||
|
||||
web-format:
|
||||
web-fmt:
|
||||
cd web && npm run format
|
||||
|
||||
web-format-check:
|
||||
web-fmt-check:
|
||||
cd web && npm run format:check
|
||||
|
||||
web-lint:
|
||||
@@ -248,7 +246,7 @@ cli-build-results:
|
||||
|
||||
# Test/check targets
|
||||
|
||||
check: test web-format-check fmt-check vet web-lint lint staticcheck
|
||||
check: test web-fmt-check fmt-check vet web-lint lint staticcheck
|
||||
|
||||
test: .PHONY
|
||||
go test $(shell go list ./... | grep -vE 'ntfy/(test|examples|tools)')
|
||||
@@ -275,7 +273,7 @@ coverage-upload:
|
||||
|
||||
# Lint/formatting targets
|
||||
|
||||
fmt:
|
||||
fmt: web-fmt
|
||||
gofmt -s -w .
|
||||
|
||||
fmt-check:
|
||||
|
||||
19
README.md
19
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
# ntfy.sh | Send push notifications to your phone or desktop via PUT/POST
|
||||
[](https://github.com/binwiederhier/ntfy/releases/latest)
|
||||
[](https://pkg.go.dev/heckel.io/ntfy)
|
||||
[](https://pkg.go.dev/heckel.io/ntfy/v2)
|
||||
[](https://github.com/binwiederhier/ntfy/actions)
|
||||
[](https://goreportcard.com/report/github.com/binwiederhier/ntfy)
|
||||
[](https://codecov.io/gh/binwiederhier/ntfy)
|
||||
@@ -31,7 +31,10 @@ as well as an [open source iOS app](https://github.com/binwiederhier/ntfy-ios) a
|
||||
</p>
|
||||
|
||||
## [ntfy Pro](https://ntfy.sh/app) 💸 🎉
|
||||
I now offer paid plans for [ntfy.sh](https://ntfy.sh/) if you don't want to self-host, or you want to support the development of ntfy (→ [Purchase via web app](https://ntfy.sh/app)). You can **buy a plan for as low as $3.33/month** (if you use promo code `MYTOPIC`, limited time only). You can also donate via [GitHub Sponsors](https://github.com/sponsors/binwiederhier), and [Liberapay](https://liberapay.com/ntfy). I would be very humbled by your sponsorship. ❤️
|
||||
I now offer paid plans for [ntfy.sh](https://ntfy.sh/) if you don't want to self-host, or you want to support the development of
|
||||
ntfy (→ [Purchase via web app](https://ntfy.sh/app)). You can **buy a plan for as low as $5/month**.
|
||||
You can also donate via [GitHub Sponsors](https://github.com/sponsors/binwiederhier), and [Liberapay](https://liberapay.com/ntfy).
|
||||
I would be very humbled by your sponsorship. ❤️
|
||||
|
||||
## **[Documentation](https://ntfy.sh/docs/)**
|
||||
|
||||
@@ -150,6 +153,18 @@ account costs. Even small donations are very much appreciated. A big fat **Thank
|
||||
<a href="https://github.com/MarcMichalsky"><img src="https://github.com/MarcMichalsky.png" width="40px" /></a>
|
||||
<a href="https://github.com/LuckVintage"><img src="https://github.com/LuckVintage.png" width="40px" /></a>
|
||||
<a href="https://github.com/spartan"><img src="https://github.com/spartan.png" width="40px" /></a>
|
||||
<a href="https://github.com/alexandzors"><img src="https://github.com/alexandzors.png" width="40px" /></a>
|
||||
<a href="https://github.com/dkramer95"><img src="https://github.com/dkramer95.png" width="40px" /></a>
|
||||
<a href="https://github.com/YezGotIt"><img src="https://github.com/YezGotIt.png" width="40px" /></a>
|
||||
<a href="https://github.com/thomasskou"><img src="https://github.com/thomasskou.png" width="40px" /></a>
|
||||
<a href="https://github.com/surfernv"><img src="https://github.com/surfernv.png" width="40px" /></a>
|
||||
<a href="https://github.com/richardleach"><img src="https://github.com/richardleach.png" width="40px" /></a>
|
||||
<a href="https://github.com/bear"><img src="https://github.com/bear.png" width="40px" /></a>
|
||||
<a href="https://github.com/cminter"><img src="https://github.com/cminter.png" width="40px" /></a>
|
||||
<a href="https://github.com/bahur142"><img src="https://github.com/bahur142.png" width="40px" /></a>
|
||||
<a href="https://github.com/pgwiebes"><img src="https://github.com/pgwiebes.png" width="40px" /></a>
|
||||
<a href="https://github.com/ralhei"><img src="https://github.com/ralhei.png" width="40px" /></a>
|
||||
<a href="https://github.com/TechMDW"><img src="https://github.com/TechMDW.png" width="40px" /></a>
|
||||
|
||||
I'd also like to thank JetBrains for their awesome [IntelliJ IDEA](https://www.jetbrains.com/idea/),
|
||||
and [DigitalOcean](https://m.do.co/c/442b929528db) (*referral link*) for supporting the project:
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
@@ -3,9 +3,9 @@ package client_test
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/require"
|
||||
"heckel.io/ntfy/client"
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/test"
|
||||
"heckel.io/ntfy/v2/client"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/test"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -2,7 +2,7 @@ package client_test
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"heckel.io/ntfy/client"
|
||||
"heckel.io/ntfy/v2/client"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
@@ -2,7 +2,7 @@ package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/urfave/cli/v2"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
"heckel.io/ntfy/server"
|
||||
"heckel.io/ntfy/test"
|
||||
"heckel.io/ntfy/v2/server"
|
||||
"heckel.io/ntfy/v2/test"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/urfave/cli/v2/altsrc"
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"os"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/urfave/cli/v2"
|
||||
"heckel.io/ntfy/client"
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/v2/client"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/urfave/cli/v2/altsrc"
|
||||
"gopkg.in/yaml.v2"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"os"
|
||||
)
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/urfave/cli/v2"
|
||||
"heckel.io/ntfy/client"
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/client"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
@@ -3,8 +3,8 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/require"
|
||||
"heckel.io/ntfy/test"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/test"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/stripe/stripe-go/v74"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"io/fs"
|
||||
"math"
|
||||
"net"
|
||||
@@ -17,12 +17,12 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/urfave/cli/v2/altsrc"
|
||||
"heckel.io/ntfy/server"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/server"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -12,15 +12,11 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"heckel.io/ntfy/client"
|
||||
"heckel.io/ntfy/test"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/client"
|
||||
"heckel.io/ntfy/v2/test"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixMilli())
|
||||
}
|
||||
|
||||
func TestCLI_Serve_Unix_Curl(t *testing.T) {
|
||||
sockFile := filepath.Join(t.TempDir(), "ntfy.sock")
|
||||
configFile := newEmptyFile(t) // Avoid issues with existing server.yml file on system
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/urfave/cli/v2"
|
||||
"heckel.io/ntfy/client"
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/client"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/urfave/cli/v2"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -3,8 +3,8 @@ package cmd
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
"heckel.io/ntfy/server"
|
||||
"heckel.io/ntfy/test"
|
||||
"heckel.io/ntfy/v2/server"
|
||||
"heckel.io/ntfy/v2/test"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/urfave/cli/v2"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"net/netip"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
"heckel.io/ntfy/server"
|
||||
"heckel.io/ntfy/test"
|
||||
"heckel.io/ntfy/v2/server"
|
||||
"heckel.io/ntfy/v2/test"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -6,13 +6,13 @@ import (
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"fmt"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/urfave/cli/v2/altsrc"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -198,7 +198,6 @@ func execUserAdd(c *cli.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
password = p
|
||||
}
|
||||
if err := manager.AddUser(username, password, role); err != nil {
|
||||
@@ -343,6 +342,8 @@ func readPasswordAndConfirm(c *cli.Context) (string, error) {
|
||||
password, err := util.ReadPassword(c.App.Reader)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if len(password) == 0 {
|
||||
return "", errors.New("password cannot be empty")
|
||||
}
|
||||
fmt.Fprintf(c.App.ErrWriter, "\r%s\rconfirm: ", strings.Repeat(" ", 25))
|
||||
confirm, err := util.ReadPassword(c.App.Reader)
|
||||
|
||||
@@ -3,9 +3,9 @@ package cmd
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
"heckel.io/ntfy/server"
|
||||
"heckel.io/ntfy/test"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/v2/server"
|
||||
"heckel.io/ntfy/v2/test"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
"heckel.io/ntfy/server"
|
||||
"heckel.io/ntfy/v2/server"
|
||||
)
|
||||
|
||||
func TestCLI_WebPush_GenerateKeys(t *testing.T) {
|
||||
|
||||
@@ -24,7 +24,7 @@ get a list of [command line options](#command-line-options).
|
||||
The most basic settings are `base-url` (the external URL of the ntfy server), the HTTP/HTTPS listen address (`listen-http`
|
||||
and `listen-https`), and socket path (`listen-unix`). All the other things are additional features.
|
||||
|
||||
Here are a few working sample configs:
|
||||
Here are a few working sample configs using a `/etc/ntfy/server.yml` file:
|
||||
|
||||
=== "server.yml (HTTP-only, with cache + attachments)"
|
||||
``` yaml
|
||||
@@ -73,6 +73,58 @@ Here are a few working sample configs:
|
||||
keepalive-interval: "45s"
|
||||
```
|
||||
|
||||
Alternatively, you can also use command line arguments or environment variables to configure the server. Here's an example
|
||||
using Docker Compose (i.e. `docker-compose.yml`):
|
||||
|
||||
=== "Docker Compose (w/ auth, cache, attachments)"
|
||||
``` yaml
|
||||
version: '3'
|
||||
services:
|
||||
ntfy:
|
||||
image: binwiederhier/ntfy
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NTFY_BASE_URL: http://ntfy.example.com
|
||||
NTFY_CACHE_FILE: /var/lib/ntfy/cache.db
|
||||
NTFY_AUTH_FILE: /var/lib/ntfy/auth.db
|
||||
NTFY_AUTH_DEFAULT_ACCESS: deny-all
|
||||
NTFY_BEHIND_PROXY: true
|
||||
NTFY_ATTACHMENT_CACHE_DIR: /var/lib/ntfy/attachments
|
||||
NTFY_ENABLE_LOGIN: true
|
||||
volumes:
|
||||
- ./:/var/lib/ntfy
|
||||
ports:
|
||||
- 80:80
|
||||
command: serve
|
||||
```
|
||||
|
||||
=== "Docker Compose (w/ auth, cache, web push, iOS)"
|
||||
``` yaml
|
||||
version: '3'
|
||||
services:
|
||||
ntfy:
|
||||
image: binwiederhier/ntfy
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NTFY_BASE_URL: http://ntfy.example.com
|
||||
NTFY_CACHE_FILE: /var/lib/ntfy/cache.db
|
||||
NTFY_AUTH_FILE: /var/lib/ntfy/auth.db
|
||||
NTFY_AUTH_DEFAULT_ACCESS: deny-all
|
||||
NTFY_BEHIND_PROXY: true
|
||||
NTFY_ATTACHMENT_CACHE_DIR: /var/lib/ntfy/attachments
|
||||
NTFY_ENABLE_LOGIN: true
|
||||
NTFY_UPSTREAM_BASE_URL: https://ntfy.sh
|
||||
NTFY_WEB_PUSH_PUBLIC_KEY: <public_key>
|
||||
NTFY_WEB_PUSH_PRIVATE_KEY: <private_key>
|
||||
NTFY_WEB_PUSH_FILE: /etc/ntfy/webpush.db
|
||||
NTFY_WEB_PUSH_EMAIL_ADDRESS: <email>
|
||||
volumes:
|
||||
- ./:/var/lib/ntfy
|
||||
ports:
|
||||
- 8093:80
|
||||
command: serve
|
||||
```
|
||||
|
||||
## Message cache
|
||||
If desired, ntfy can temporarily keep notifications in an in-memory or an on-disk cache. Caching messages for a short period
|
||||
of time is important to allow [phones](subscribe/phone.md) and other devices with brittle Internet connections to be able to retrieve
|
||||
@@ -1180,10 +1232,10 @@ and [here](https://easyengine.io/tutorials/nginx/block-wp-login-php-bruteforce-a
|
||||
|
||||
## Health checks
|
||||
A preliminary health check API endpoint is exposed at `/v1/health`. The endpoint returns a `json` response in the format shown below.
|
||||
If a non-200 HTTP status code is returned or if the returned `health` field is `false` the ntfy service should be considered as unhealthy.
|
||||
If a non-200 HTTP status code is returned or if the returned `healthy` field is `false` the ntfy service should be considered as unhealthy.
|
||||
|
||||
```json
|
||||
{"health":true}
|
||||
{"healthy":true}
|
||||
```
|
||||
|
||||
See [Installation for Docker](install.md#docker) for an example of how this could be used in a `docker-compose` environment.
|
||||
|
||||
@@ -429,7 +429,7 @@ steps:
|
||||
|
||||
### XCode setup
|
||||
|
||||
1. Follow step 4 of [https://firebase.google.com/docs/ios/setup](Add Firebase to your Apple project) to install the
|
||||
1. Follow step 4 of [Add Firebase to your Apple project](https://firebase.google.com/docs/ios/setup) to install the
|
||||
`firebase-ios-sdk` in XCode, if it's not already present - you can select any packages in addition to Firebase Core / Firebase Messaging
|
||||
1. Similarly, install the SQLite.swift package dependency in XCode
|
||||
1. When running the debug build, ensure XCode is pointed to the connected iOS device - registering for push notifications does not work in the iOS simulators
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
<!-- This file was generated by scripts/emoji-convert.sh -->
|
||||
|
||||
You can [tag messages](../publish/#tags-emojis) with emojis 🥳 🎉 and other relevant strings. Matching tags are automatically
|
||||
You can [tag messages](publish.md#tags-emojis) with emojis 🥳 🎉 and other relevant strings. Matching tags are automatically
|
||||
converted to emojis. This is a reference of all supported emojis. To learn more about the feature, please refer to the
|
||||
[tagging and emojis page](../publish/#tags-emojis).
|
||||
[tagging and emojis page](publish.md#tags-emojis).
|
||||
|
||||
<table class="remove-md-box emoji-table"><tr>
|
||||
|
||||
|
||||
@@ -135,6 +135,21 @@ You can send a message during a workflow run with curl. Here is an example sendi
|
||||
${{ secrets.NTFY_URL }}
|
||||
```
|
||||
|
||||
## Changedetection.io
|
||||
ntfy is an excellent choice for getting notifications when a website has a change sent to your mobile (or desktop),
|
||||
[changedetection.io](https://changedetection.io) or on GitHub ([dgtlmoon/changedetection.io](https://github.com/dgtlmoon/changedetection.io))
|
||||
uses [apprise](https://github.com/caronc/apprise) library for notification integrations.
|
||||
|
||||
To add any ntfy(s) notification to a website change simply add the [ntfy style URL](https://github.com/caronc/apprise/wiki/Notify_ntfy)
|
||||
to the notification list.
|
||||
|
||||
For example `ntfy://{topic}` or `ntfy://{user}:{password}@{host}:{port}/{topics}`
|
||||
|
||||
In your changedetection.io installation, click `Edit` > `Notifications` on a single website watch (or group) then add
|
||||
the special ntfy Apprise Notification URL to the Notification List.
|
||||
|
||||

|
||||
|
||||
## Watchtower (shoutrrr)
|
||||
You can use [shoutrrr](https://containrrr.dev/shoutrrr/latest/services/ntfy/) to send
|
||||
[Watchtower](https://github.com/containrrr/watchtower/) notifications to your ntfy topic.
|
||||
|
||||
@@ -3,9 +3,9 @@ ntfy lets you **send push notifications to your phone or desktop via scripts fro
|
||||
or POST requests. I use it to notify myself when scripts fail, or long-running commands complete.
|
||||
|
||||
## Step 1: Get the app
|
||||
<a href="https://play.google.com/store/apps/details?id=io.heckel.ntfy"><img src="../../static/img/badge-googleplay.png"></a>
|
||||
<a href="https://f-droid.org/en/packages/io.heckel.ntfy/"><img src="../../static/img/badge-fdroid.png"></a>
|
||||
<a href="https://apps.apple.com/us/app/ntfy/id1625396347"><img src="../../static/img/badge-appstore.png"></a>
|
||||
<a href="https://play.google.com/store/apps/details?id=io.heckel.ntfy"><img src="static/img/badge-googleplay.png"></a>
|
||||
<a href="https://f-droid.org/en/packages/io.heckel.ntfy/"><img src="static/img/badge-fdroid.png"></a>
|
||||
<a href="https://apps.apple.com/us/app/ntfy/id1625396347"><img src="static/img/badge-appstore.png"></a>
|
||||
|
||||
To [receive notifications on your phone](subscribe/phone.md), install the app, either via Google Play or F-Droid.
|
||||
Once installed, open it and subscribe to a topic of your choosing. Topics don't have to explicitly be created, so just
|
||||
|
||||
@@ -14,14 +14,15 @@ We support amd64, armv7 and arm64.
|
||||
|
||||
1. Install ntfy using one of the methods described below
|
||||
2. Then (optionally) edit `/etc/ntfy/server.yml` for the server (Linux only, see [configuration](config.md) or [sample server.yml](https://github.com/binwiederhier/ntfy/blob/main/server/server.yml))
|
||||
3. Or (optionally) create/edit `~/.config/ntfy/client.yml` (for the non-root user) or `/etc/ntfy/client.yml` (for the root user), see [sample client.yml](https://github.com/binwiederhier/ntfy/blob/main/client/client.yml))
|
||||
3. Or (optionally) create/edit `~/.config/ntfy/client.yml` (for the non-root user), `~/Library/Application Support/ntfy/client.yml` (for the macOS non-root user), or `/etc/ntfy/client.yml` (for the root user), see [sample client.yml](https://github.com/binwiederhier/ntfy/blob/main/client/client.yml))
|
||||
|
||||
To run the ntfy server, then just run `ntfy serve` (or `systemctl start ntfy` when using the deb/rpm).
|
||||
To send messages, use `ntfy publish`. To subscribe to topics, use `ntfy subscribe` (see [subscribing via CLI](subscribe/cli.md)
|
||||
for details).
|
||||
|
||||
If you like video tutorials, check out :simple-youtube: [Kris Occhipinti's ntfy install guide](https://www.youtube.com/watch?v=bZzqrX05mNU).
|
||||
It's short and to the point. _I am not affiliated with Kris, I just liked the video._
|
||||
If you like tutorials, check out :simple-youtube: [Kris Occhipinti's ntfy install guide](https://www.youtube.com/watch?v=bZzqrX05mNU) on YouTube, or
|
||||
[Alex's Docker-based setup guide](https://blog.alexsguardian.net/posts/2023/09/12/selfhosting-ntfy/). Both are great
|
||||
resources to get started. _I am not affiliated with Kris or Alex, I just liked their video/post._
|
||||
|
||||
## Linux binaries
|
||||
Please check out the [releases page](https://github.com/binwiederhier/ntfy/releases) for binaries and
|
||||
@@ -29,37 +30,37 @@ deb/rpm packages.
|
||||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_amd64.tar.gz
|
||||
tar zxvf ntfy_2.7.0_linux_amd64.tar.gz
|
||||
sudo cp -a ntfy_2.7.0_linux_amd64/ntfy /usr/local/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.7.0_linux_amd64/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.8.0/ntfy_2.8.0_linux_amd64.tar.gz
|
||||
tar zxvf ntfy_2.8.0_linux_amd64.tar.gz
|
||||
sudo cp -a ntfy_2.8.0_linux_amd64/ntfy /usr/local/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.8.0_linux_amd64/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
=== "armv6"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_armv6.tar.gz
|
||||
tar zxvf ntfy_2.7.0_linux_armv6.tar.gz
|
||||
sudo cp -a ntfy_2.7.0_linux_armv6/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.7.0_linux_armv6/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.8.0/ntfy_2.8.0_linux_armv6.tar.gz
|
||||
tar zxvf ntfy_2.8.0_linux_armv6.tar.gz
|
||||
sudo cp -a ntfy_2.8.0_linux_armv6/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.8.0_linux_armv6/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_armv7.tar.gz
|
||||
tar zxvf ntfy_2.7.0_linux_armv7.tar.gz
|
||||
sudo cp -a ntfy_2.7.0_linux_armv7/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.7.0_linux_armv7/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.8.0/ntfy_2.8.0_linux_armv7.tar.gz
|
||||
tar zxvf ntfy_2.8.0_linux_armv7.tar.gz
|
||||
sudo cp -a ntfy_2.8.0_linux_armv7/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.8.0_linux_armv7/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
=== "arm64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_arm64.tar.gz
|
||||
tar zxvf ntfy_2.7.0_linux_arm64.tar.gz
|
||||
sudo cp -a ntfy_2.7.0_linux_arm64/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.7.0_linux_arm64/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.8.0/ntfy_2.8.0_linux_arm64.tar.gz
|
||||
tar zxvf ntfy_2.8.0_linux_arm64.tar.gz
|
||||
sudo cp -a ntfy_2.8.0_linux_arm64/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.8.0_linux_arm64/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
@@ -109,7 +110,7 @@ Manually installing the .deb file:
|
||||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_amd64.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.8.0/ntfy_2.8.0_linux_amd64.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
@@ -117,7 +118,7 @@ Manually installing the .deb file:
|
||||
|
||||
=== "armv6"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_armv6.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.8.0/ntfy_2.8.0_linux_armv6.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
@@ -125,7 +126,7 @@ Manually installing the .deb file:
|
||||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_armv7.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.8.0/ntfy_2.8.0_linux_armv7.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
@@ -133,7 +134,7 @@ Manually installing the .deb file:
|
||||
|
||||
=== "arm64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_arm64.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.8.0/ntfy_2.8.0_linux_arm64.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
@@ -143,28 +144,28 @@ Manually installing the .deb file:
|
||||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_amd64.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.8.0/ntfy_2.8.0_linux_amd64.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
||||
=== "armv6"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_armv6.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.8.0/ntfy_2.8.0_linux_armv6.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_armv7.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.8.0/ntfy_2.8.0_linux_armv7.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
||||
=== "arm64"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_arm64.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.8.0/ntfy_2.8.0_linux_arm64.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
@@ -194,18 +195,18 @@ NixOS also supports [declarative setup of the ntfy server](https://search.nixos.
|
||||
|
||||
## macOS
|
||||
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on macOS as well.
|
||||
To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_darwin_all.tar.gz),
|
||||
To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.8.0/ntfy_2.8.0_darwin_all.tar.gz),
|
||||
extract it and place it somewhere in your `PATH` (e.g. `/usr/local/bin/ntfy`).
|
||||
|
||||
If run as `root`, ntfy will look for its config at `/etc/ntfy/client.yml`. For all other users, it'll look for it at
|
||||
`~/Library/Application Support/ntfy/client.yml` (sample included in the tarball).
|
||||
|
||||
```bash
|
||||
curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_darwin_all.tar.gz > ntfy_2.7.0_darwin_all.tar.gz
|
||||
tar zxvf ntfy_2.7.0_darwin_all.tar.gz
|
||||
sudo cp -a ntfy_2.7.0_darwin_all/ntfy /usr/local/bin/ntfy
|
||||
curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.8.0/ntfy_2.8.0_darwin_all.tar.gz > ntfy_2.8.0_darwin_all.tar.gz
|
||||
tar zxvf ntfy_2.8.0_darwin_all.tar.gz
|
||||
sudo cp -a ntfy_2.8.0_darwin_all/ntfy /usr/local/bin/ntfy
|
||||
mkdir ~/Library/Application\ Support/ntfy
|
||||
cp ntfy_2.7.0_darwin_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
|
||||
cp ntfy_2.8.0_darwin_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
|
||||
ntfy --help
|
||||
```
|
||||
|
||||
@@ -223,7 +224,7 @@ brew install ntfy
|
||||
|
||||
## Windows
|
||||
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on Windows as well.
|
||||
To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_windows_amd64.zip),
|
||||
To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.8.0/ntfy_2.8.0_windows_amd64.zip),
|
||||
extract it and place the `ntfy.exe` binary somewhere in your `%Path%`.
|
||||
|
||||
The default path for the client config file is at `%AppData%\ntfy\client.yml` (not created automatically, sample in the ZIP file).
|
||||
|
||||
@@ -24,6 +24,7 @@ I've added a ⭐ to projects or posts that have a significant following, or had
|
||||
- [diun](https://crazymax.dev/diun/) - Docker Image Update Notifier
|
||||
- [Cloudron](https://www.cloudron.io/store/sh.ntfy.cloudronapp.html) - Platform that makes it easy to manage web apps on your server
|
||||
- [Xitoring](https://xitoring.com/docs/notifications/notification-roles/ntfy/) - Server and Uptime monitoring
|
||||
- [changedetection.io](https://changedetection.io) ⭐ - Website change detection and notification
|
||||
|
||||
## Integration via HTTP/SMTP/etc.
|
||||
|
||||
@@ -57,7 +58,8 @@ I've added a ⭐ to projects or posts that have a significant following, or had
|
||||
- [ntfy](https://github.com/ffflorian/ntfy) - Send notifications over ntfy (JS)
|
||||
- [ntfy_dart](https://github.com/jr1221/ntfy_dart) - Dart wrapper around the ntfy API (Dart)
|
||||
- [gotfy](https://github.com/AnthonyHewins/gotfy) - A Go wrapper for the ntfy API (Go)
|
||||
- [symfony/ntfy-notifier](https://symfony.com/components/NtfyNotifier) ⭐ - Symfony Notifier integration for ntfy (PHP)
|
||||
- [symfony/ntfy-notifier](https://symfony.com/components/NtfyNotifier) ⭐ - Symfony Notifier integration for ntfy (PHP)
|
||||
- [ntfy-java](https://github.com/MaheshBabu11/ntfy-java/) - A Java package to interact with a ntfy server (Java)
|
||||
|
||||
## CLIs + GUIs
|
||||
|
||||
@@ -81,7 +83,6 @@ I've added a ⭐ to projects or posts that have a significant following, or had
|
||||
- [backup-projects](https://gist.github.com/anthonyaxenov/826ba65abbabd5b00196bc3e6af76002) - Stupidly simple backup script for own projects (Shell)
|
||||
- [grav-plugin-whistleblower](https://github.com/Himmlisch-Studios/grav-plugin-whistleblower) - Grav CMS plugin to get notifications via ntfy (PHP)
|
||||
- [ntfy-server-status](https://github.com/filip2cz/ntfy-server-status) - Checking if server is online and reporting through ntfy (C)
|
||||
- [borg-based backup](https://github.com/davidhi7/backup) - Simple borg-based backup script with notifications based on ntfy.sh or Discord webhooks (Python/Shell)
|
||||
- [ntfy.sh *arr script](https://github.com/agent-squirrel/nfty-arr-script) - Quick and hacky script to get sonarr/radarr to notify the ntfy.sh service (Shell)
|
||||
- [website-watcher](https://github.com/muety/website-watcher) - A small tool to watch websites for changes (with XPath support) (Python)
|
||||
- [siteeagle](https://github.com/tpanum/siteeagle) - A small Python script to monitor websites and notify changes (Python)
|
||||
@@ -129,11 +130,32 @@ I've added a ⭐ to projects or posts that have a significant following, or had
|
||||
- [vigilant](https://github.com/VerifiedJoseph/vigilant) - Monitor RSS/ATOM and JSON feeds, and send push notifications on new entries (PHP)
|
||||
- [ansible-role-ntfy-alertmanager](https://github.com/bleetube/ansible-role-ntfy-alertmanager) - Ansible role to install xenrox/ntfy-alertmanager
|
||||
- [NtfyMe-Blender](https://github.com/NotNanook/NtfyMe-Blender) - Blender addon to send notifications to NtfyMe (Python)
|
||||
- [ntfy-ios-filesharing](https://www.icloud.com/shortcuts/fe948d151b2e4ae08fb2f9d6b27d680b) - An iOS shortcut that let's you share files from your share feed to a topic of your choice.
|
||||
|
||||
- [ntfy-ios-url-share](https://www.icloud.com/shortcuts/be8a7f49530c45f79733cfe3e41887e6) - An iOS shortcut that lets you share URLs easily and quickly.
|
||||
- [ntfy-ios-filesharing](https://www.icloud.com/shortcuts/fe948d151b2e4ae08fb2f9d6b27d680b) - An iOS shortcut that lets you share files from your share feed to a topic of your choice.
|
||||
- [systemd-ntfy](https://hackage.haskell.org/package/systemd-ntfy) - monitor a set of systemd services an send a notification to ntfy.sh whenever their status changes
|
||||
- [RouterOS Scripts](https://git.eworm.de/cgit/routeros-scripts/about/) - a collection of scripts for MikroTik RouterOS
|
||||
- [ntfy-android-builder](https://github.com/TheBlusky/ntfy-android-builder) - Script for building ntfy-android with custom Firebase configuration (Docker/Shell)
|
||||
- [jetspotter](https://github.com/vvanouytsel/jetspotter) - send notifications when planes are spotted near you (Go)
|
||||
- [monitoring_ntfy](https://www.drupal.org/project/monitoring_ntfy) - Drupal monitoring Ntfy.sh integration (PHP/Drupal)
|
||||
- [Notify](https://flathub.org/apps/com.ranfdev.Notify) - Native GTK4 client for ntfy (Rust)
|
||||
|
||||
## Blog + forum posts
|
||||
|
||||
- [Installing Self Host NTFY On Linux Using Docker Container](https://www.pinoylinux.org/topicsplus/containers/installing-self-host-ntfy-on-linux-using-docker-container/) - pinoylinux.org - 9/2023
|
||||
- [Homelab Notifications with ntfy](https://blog.alexsguardian.net/posts/2023/09/12/selfhosting-ntfy/) ⭐ - alexsguardian.net - 9/2023
|
||||
- [Why NTFY is the Ultimate Push Notification Tool for Your Needs](https://osintph.medium.com/why-ntfy-is-the-ultimate-push-notification-tool-for-your-needs-e767421c84c5) - osintph.medium.com - 9/2023
|
||||
- [Supercharge Your Alerts: Ntfy — The Ultimate Push Notification Solution](https://medium.com/spring-boot/supercharge-your-alerts-ntfy-the-ultimate-push-notification-solution-a3dda79651fe) - spring-boot.medium.com - 9/2023
|
||||
- [Deploy Ntfy using Docker](https://www.linkedin.com/pulse/deploy-ntfy-mohamed-sharfy/) - linkedin.com - 9/2023
|
||||
- [Send Notifications With Ntfy for New WordPress Posts](https://www.activepieces.com/blog/ntfy-notifications-for-wordpress-new-posts) - activepieces.com - 9/2023
|
||||
- [Get Ntfy Notifications About New Zendesk Ticket](https://www.activepieces.com/blog/ntfy-notifications-about-new-zendesk-tickets) - activepieces.com - 9/2023
|
||||
- [Set reminder for recurring events using ntfy & Cron](https://www.youtube.com/watch?v=J3O4aQ-EcYk) - youtube.com - 9/2023
|
||||
- [ntfy - Installation and full configuration setup](https://www.youtube.com/watch?v=QMy14rGmpFI) - youtube.com - 9/2023
|
||||
- [How to install Ntfy.sh on Portainer / Docker Compose](https://www.youtube.com/watch?v=utD9GNbAwyg) - youtube.com - 9/2023
|
||||
- [ntfy - Push-Benachrichtigungen // Push Notifications](https://www.youtube.com/watch?v=LE3vRPPqZOU) - youtube.com - 9/2023
|
||||
- [Podman Update Notifications via Ntfy](https://rair.dev/podman-upadte-notifications-ntfy/) - rair.dev - 9/2023
|
||||
- [How to Send Alerts From Raspberry Pi Pico W to a Phone or Tablet](https://www.tomshardware.com/how-to/send-alerts-raspberry-pi-pico-w-to-mobile-device) - tomshardware.com - 8/2023
|
||||
- [NetworkChunk - how did I NOT know about this?](https://www.youtube.com/watch?v=poDIT2ruQ9M) ⭐ - youtube.com - 8/2023
|
||||
- [NTFY - Command-Line Notifications](https://academy.networkchuck.com/blog/ntfy/) - academy.networkchuck.com - 8/2023
|
||||
- [Open Source Push Notifications! Get notified of any event you can imagine. Triggers abound!](https://www.youtube.com/watch?v=WJgwWXt79pE) ⭐ - youtube.com - 8/2023
|
||||
- [How to install and self host an Ntfy server on Linux](https://linuxconfig.org/how-to-install-and-self-host-an-ntfy-server-on-linux) - linuxconfig.org - 7/2023
|
||||
- [Basic website monitoring using cronjobs and ntfy.sh](https://burkhardt.dev/2023/website-monitoring-cron-ntfy/) - burkhardt.dev - 6/2023
|
||||
@@ -219,6 +241,7 @@ ntfy community. Thanks to everyone running a public server. **You guys rock!**
|
||||
| [ntfy.envs.net](https://ntfy.envs.net) | 🇩🇪 Germany |
|
||||
| [ntfy.mzte.de](https://ntfy.mzte.de/) | 🇩🇪 Germany |
|
||||
| [ntfy.hostux.net](https://ntfy.hostux.net/) | 🇫🇷 France |
|
||||
| [ntfy.fossman.de](https://ntfy.fossman.de/) | 🇩🇪 Germany |
|
||||
|
||||
Please be aware that **server operators can log your messages**. The project also cannot guarantee the reliability
|
||||
and uptime of third party servers, so use of each server is **at your own discretion**.
|
||||
|
||||
@@ -34,12 +34,6 @@ your iOS device and your ntfy server are either on the same network, or that you
|
||||
Turn on tracing/debugging on the server (via `log-level: trace` or `log-level: debug`, see [troubleshooting](troubleshooting.md)),
|
||||
and read docs on [iOS instant notifications](https://docs.ntfy.sh/config/#ios-instant-notifications).
|
||||
|
||||
## Firefox on Android not automatically subscribing to web push (see [#789](https://github.com/binwiederhier/ntfy/issues/789))
|
||||
ntfy defaults to web-push based subscriptions when installed as a [progressive web app](./subscribe/pwa.md). Firefox
|
||||
Android has an [open bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1796434) where it reports the PWA mode incorrectly.
|
||||
This causes ntfy to not automatically subscribe to web push, and requires you to go to the ntfy Settings page to enable
|
||||
it manually.
|
||||
|
||||
## Safari does not play sounds for web push notifications
|
||||
Safari does not support playing sounds for web push notifications, and treats them all as silent. This will be fixed with
|
||||
iOS 17 / Safari 17, which will be released later in 2023.
|
||||
|
||||
@@ -1131,7 +1131,7 @@ As of today, the following actions are supported:
|
||||
when the action button is tapped (only supported on Android)
|
||||
* [`http`](#send-http-request): Sends HTTP POST/GET/PUT request when the action button is tapped
|
||||
|
||||
Here's an example of what that a notification with actions can look like:
|
||||
Here's an example of what a notification with actions can look like:
|
||||
|
||||
<figure markdown>
|
||||
{ width=500 }
|
||||
@@ -2288,7 +2288,7 @@ You can define which URL to open when a notification is clicked. This may be use
|
||||
to a Zabbix alert or a transaction that you'd like to provide the deep-link for. Tapping the notification will open
|
||||
the web browser (or the app) and open the website.
|
||||
|
||||
To define a click action for the notification, pass a URL as the value of the `X-Click` header (or its aliase `Click`).
|
||||
To define a click action for the notification, pass a URL as the value of the `X-Click` header (or its alias `Click`).
|
||||
If you pass a website URL (`http://` or `https://`) the web browser will open. If you pass another URI that can be handled
|
||||
by another app, the responsible app may open.
|
||||
|
||||
|
||||
@@ -2,6 +2,25 @@
|
||||
Binaries for all releases can be found on the GitHub releases pages for the [ntfy server](https://github.com/binwiederhier/ntfy/releases)
|
||||
and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/releases).
|
||||
|
||||
### ntfy server v2.8.0
|
||||
Released November 19, 2023
|
||||
|
||||
This release brings a handful of random bug fixes: two unrelated access control list fixes, a fix around web app crashes for languages with underscores in the language code (e.g. `zh_Hant`, `zh_Hans`, `pt_BR`, ...), a workaround for the `Priority` header (often used in Cloudflare setups), and support among others support for HTML-only emails (finally), web app crash fixes
|
||||
|
||||
**Bug fixes + maintenance:**
|
||||
|
||||
* Support for HTML-only emails ([#690](https://github.com/binwiederhier/ntfy/issues/690)/[#693](https://github.com/binwiederhier/ntfy/pull/693), thanks to [@teastrainer](https://github.com/teastrainer) and [@CrazyWolf13](https://github.com/CrazyWolf13) for reporting)
|
||||
* Fix ACL issue with topic patterns containing underscores ([#840](https://github.com/binwiederhier/ntfy/issues/840), thanks to [@Joe-0237](https://github.com/Joe-0237) for reporting)
|
||||
* Fix ACL issue with order of read/write rules ([#914](https://github.com/binwiederhier/ntfy/issues/914)/[#917](https://github.com/binwiederhier/ntfy/pull/917), thanks to [@sandman7920](https://github.com/sandman7920))
|
||||
* Re-add `tzdata` to Docker images for amd64 image ([#894](https://github.com/binwiederhier/ntfy/issues/894), [#307](https://github.com/binwiederhier/ntfy/pull/307))
|
||||
* Add special logic to ignore `Priority` header if it resembles an RFC 9218 value ([#851](https://github.com/binwiederhier/ntfy/pull/851)/[#895](https://github.com/binwiederhier/ntfy/pull/895), thanks to [@gusdleon](https://github.com/gusdleon), see also [#351](https://github.com/binwiederhier/ntfy/issues/351), [#353](https://github.com/binwiederhier/ntfy/issues/353), [#461](https://github.com/binwiederhier/ntfy/issues/461))
|
||||
* PWA: hide install prompt on macOS 14 Safari ([#899](https://github.com/binwiederhier/ntfy/pull/899), thanks to [@nihalgonsalves](https://github.com/nihalgonsalves))
|
||||
* Fix web app crash in Edge for languages with underline in locale ([#922](https://github.com/binwiederhier/ntfy/pull/922)/[#912](https://github.com/binwiederhier/ntfy/issues/912)/[#852](https://github.com/binwiederhier/ntfy/issues/852), thanks to [@imkero](https://github.com/imkero))
|
||||
|
||||
**Additional languages:**
|
||||
|
||||
* Finnish (thanks to [@Seppo](https://hosted.weblate.org/user/Seppo/))
|
||||
|
||||
## ntfy server v2.7.0
|
||||
Released August 17, 2023
|
||||
|
||||
@@ -1273,7 +1292,7 @@ Released Dec 28, 2021
|
||||
|
||||
**Features & bug fixes:**
|
||||
|
||||
* [Publish messages via e-mail](ntfy.sh/docs/publish/#e-mail-publishing) #66
|
||||
* [Publish messages via e-mail](publish.md#e-mail-publishing) #66
|
||||
* Server-side work to support [unifiedpush.org](https://unifiedpush.org) #64
|
||||
* Fixing the Santa bug #65
|
||||
|
||||
@@ -1283,14 +1302,6 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
|
||||
|
||||
## Not released yet
|
||||
|
||||
### ntfy server v2.8.0 (UNRELEASED)
|
||||
|
||||
**Bug fixes + maintenance:**
|
||||
|
||||
* Fix ACL issue with topic patterns containing underscores ([#840](https://github.com/binwiederhier/ntfy/issues/840), thanks to [@Joe-0237](https://github.com/Joe-0237) for reporting)
|
||||
* Re-add `tzdata` to Docker images for amd64 image ([#894](https://github.com/binwiederhier/ntfy/issues/894), [#307](https://github.com/binwiederhier/ntfy/pull/307))
|
||||
* Add special logic to ignore `Priority` header if it resembled a RFC 9218 value ([#851](https://github.com/binwiederhier/ntfy/pull/851)/[#895](https://github.com/binwiederhier/ntfy/pull/895), thanks to [@gusdleon](https://github.com/gusdleon), see also [#351](https://github.com/binwiederhier/ntfy/issues/351), [#353](https://github.com/binwiederhier/ntfy/issues/353), [#461](https://github.com/binwiederhier/ntfy/issues/461))
|
||||
|
||||
### ntfy Android app v1.16.1 (UNRELEASED)
|
||||
|
||||
**Features:**
|
||||
|
||||
BIN
docs/static/img/cdio-setup.jpg
vendored
Normal file
BIN
docs/static/img/cdio-setup.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 155 KiB |
BIN
docs/static/img/pwa-install-macos-safari-add-to-dock.png
vendored
Normal file
BIN
docs/static/img/pwa-install-macos-safari-add-to-dock.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 162 KiB |
@@ -190,9 +190,10 @@ format. Keepalive messages are sent as empty lines.
|
||||
|
||||
## WebSockets
|
||||
You may also subscribe to topics via [WebSockets](https://en.wikipedia.org/wiki/WebSocket), which is also widely
|
||||
supported in many languages. Most notably, WebSockets are natively supported in JavaScript. On the command line,
|
||||
I recommend [websocat](https://github.com/vi/websocat), a fantastic tool similar to `socat` or `curl`, but specifically
|
||||
for WebSockets.
|
||||
supported in many languages. Most notably, WebSockets are natively supported in JavaScript. You may also want to
|
||||
check out the [full example on GitHub](https://github.com/binwiederhier/ntfy/tree/main/examples/web-example-websocket).
|
||||
On the command line, I recommend [websocat](https://github.com/vi/websocat), a fantastic tool similar to `socat`
|
||||
or `curl`, but specifically for WebSockets.
|
||||
|
||||
The WebSockets endpoint is available at `<topic>/ws` and returns messages as JSON objects similar to the
|
||||
[JSON stream endpoint](#subscribe-as-json-stream).
|
||||
|
||||
@@ -10,7 +10,7 @@ to topics via the ntfy CLI. The CLI is included in the same `ntfy` binary that c
|
||||
## Install + configure
|
||||
To install the ntfy CLI, simply **follow the steps outlined on the [install page](../install.md)**. The ntfy server and
|
||||
client are the same binary, so it's all very convenient. After installing, you can (optionally) configure the client
|
||||
by creating `~/.config/ntfy/client.yml` (for the non-root user), or `/etc/ntfy/client.yml` (for the root user). You
|
||||
by creating `~/.config/ntfy/client.yml` (for the non-root user), `~/Library/Application Support/ntfy/client.yml` (for the macOS non-root user), or `/etc/ntfy/client.yml` (for the root user). You
|
||||
can find a [skeleton config](https://github.com/binwiederhier/ntfy/blob/main/client/client.yml) on GitHub.
|
||||
|
||||
If you just want to use [ntfy.sh](https://ntfy.sh), you don't have to change anything. If you **self-host your own server**,
|
||||
|
||||
@@ -26,6 +26,13 @@ app drawer:
|
||||
<a href="../../static/img/pwa-badge.png"><img src="../../static/img/pwa-badge.png"/></a>
|
||||
</div>
|
||||
|
||||
### Safari on macOS
|
||||
To install and register the web app via Safari, click on the Share menu and click Add to Dock. You need to be on macOS Sonoma (14) or higher.
|
||||
|
||||
<div id="pwa-screenshots-safari-desktop" class="screenshots">
|
||||
<a href="../../static/img/pwa-install-macos-safari-add-to-dock.png"><img src="../../static/img/pwa-install-macos-safari-add-to-dock.png"/></a>
|
||||
</div>
|
||||
|
||||
### Chrome/Firefox on Android
|
||||
For Chrome on Android, either click the "Add to Home Screen" banner at the bottom of the screen, or select "Install app"
|
||||
in the menu, and then click "Install" in the popup menu. After installation, you can find the app in your app drawer,
|
||||
|
||||
56
examples/web-example-websocket/example-ws.html
Normal file
56
examples/web-example-websocket/example-ws.html
Normal file
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>ntfy.sh: WebSocket Example</title>
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<style>
|
||||
body { font-size: 1.2em; line-height: 130%; }
|
||||
#events { font-family: monospace; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>ntfy.sh: WebSocket Example</h1>
|
||||
<p>
|
||||
This is an example showing how to use <a href="https://ntfy.sh">ntfy.sh</a> with
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket">WebSocket</a>.<br/>
|
||||
This example doesn't need a server. You can just save the HTML page and run it from anywhere.
|
||||
</p>
|
||||
<button id="publishButton">Send test notification</button>
|
||||
<p><b>Log:</b></p>
|
||||
<div id="events"></div>
|
||||
|
||||
<script type="text/javascript">
|
||||
const publishURL = `https://ntfy.sh/example`;
|
||||
const subscribeURL = `wss://ntfy.sh/example/ws`;
|
||||
const events = document.getElementById('events');
|
||||
const websocket = new WebSocket(subscribeURL);
|
||||
|
||||
// Publish button
|
||||
document.getElementById("publishButton").onclick = () => {
|
||||
fetch(publishURL, {
|
||||
method: 'POST', // works with PUT as well, though that sends an OPTIONS request too!
|
||||
body: `It is ${new Date().toString()}. This is a test.`
|
||||
})
|
||||
};
|
||||
|
||||
// Incoming events
|
||||
websocket.onopen = () => {
|
||||
let event = document.createElement('div');
|
||||
event.innerHTML = `WebSocket connected to ${subscribeURL}`;
|
||||
events.appendChild(event);
|
||||
};
|
||||
websocket.onerror = (e) => {
|
||||
let event = document.createElement('div');
|
||||
event.innerHTML = `WebSocket error: Failed to connect to ${subscribeURL}`;
|
||||
events.appendChild(event);
|
||||
};
|
||||
websocket.onmessage = (e) => {
|
||||
let event = document.createElement('div');
|
||||
event.innerHTML = e.data;
|
||||
events.appendChild(event);
|
||||
};
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
78
go.mod
78
go.mod
@@ -1,25 +1,27 @@
|
||||
module heckel.io/ntfy
|
||||
module heckel.io/ntfy/v2
|
||||
|
||||
go 1.18
|
||||
go 1.21
|
||||
|
||||
toolchain go1.21.3
|
||||
|
||||
require (
|
||||
cloud.google.com/go/firestore v1.13.0 // indirect
|
||||
cloud.google.com/go/storage v1.33.0 // indirect
|
||||
cloud.google.com/go/firestore v1.14.0 // indirect
|
||||
cloud.google.com/go/storage v1.35.1 // indirect
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
||||
github.com/emersion/go-smtp v0.18.0
|
||||
github.com/gabriel-vasile/mimetype v1.4.2
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/mattn/go-sqlite3 v1.14.17
|
||||
github.com/gabriel-vasile/mimetype v1.4.3
|
||||
github.com/gorilla/websocket v1.5.1
|
||||
github.com/mattn/go-sqlite3 v1.14.18
|
||||
github.com/olebedev/when v1.0.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/urfave/cli/v2 v2.25.7
|
||||
golang.org/x/crypto v0.13.0
|
||||
golang.org/x/oauth2 v0.12.0 // indirect
|
||||
golang.org/x/sync v0.3.0
|
||||
golang.org/x/term v0.12.0
|
||||
golang.org/x/time v0.3.0
|
||||
google.golang.org/api v0.142.0
|
||||
golang.org/x/crypto v0.15.0
|
||||
golang.org/x/oauth2 v0.14.0 // indirect
|
||||
golang.org/x/sync v0.5.0
|
||||
golang.org/x/term v0.14.0
|
||||
golang.org/x/time v0.4.0
|
||||
google.golang.org/api v0.151.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
@@ -28,53 +30,55 @@ replace github.com/emersion/go-smtp => github.com/emersion/go-smtp v0.17.0 // Pi
|
||||
require github.com/pkg/errors v0.9.1 // indirect
|
||||
|
||||
require (
|
||||
firebase.google.com/go/v4 v4.12.0
|
||||
github.com/SherClockHolmes/webpush-go v1.2.0
|
||||
github.com/prometheus/client_golang v1.16.0
|
||||
firebase.google.com/go/v4 v4.12.1
|
||||
github.com/SherClockHolmes/webpush-go v1.3.0
|
||||
github.com/microcosm-cc/bluemonday v1.0.26
|
||||
github.com/prometheus/client_golang v1.17.0
|
||||
github.com/stripe/stripe-go/v74 v74.30.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.110.8 // indirect
|
||||
cloud.google.com/go/compute v1.23.0 // indirect
|
||||
cloud.google.com/go v0.110.10 // indirect
|
||||
cloud.google.com/go/compute v1.23.3 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v1.1.2 // indirect
|
||||
cloud.google.com/go/longrunning v0.5.1 // indirect
|
||||
cloud.google.com/go/iam v1.1.5 // indirect
|
||||
cloud.google.com/go/longrunning v0.5.4 // indirect
|
||||
github.com/AlekSi/pointer v1.2.0 // indirect
|
||||
github.com/MicahParks/keyfunc v1.9.0 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect
|
||||
github.com/google/uuid v1.4.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.4.0 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.45.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/net v0.15.0 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
golang.org/x/net v0.18.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/appengine/v2 v2.0.5 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect
|
||||
google.golang.org/grpc v1.58.2 // indirect
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
163
go.sum
163
go.sum
@@ -1,20 +1,20 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME=
|
||||
cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk=
|
||||
cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY=
|
||||
cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
|
||||
cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y=
|
||||
cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic=
|
||||
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
||||
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/firestore v1.13.0 h1:/3S4RssUV4GO/kvgJZB+tayjhOfyAHs+KcpJgRVu/Qk=
|
||||
cloud.google.com/go/firestore v1.13.0/go.mod h1:QojqqOh8IntInDUSTAh0c8ZsPYAr68Ma8c5DWOy8xb8=
|
||||
cloud.google.com/go/iam v1.1.2 h1:gacbrBdWcoVmGLozRuStX45YKvJtzIjJdAolzUs1sm4=
|
||||
cloud.google.com/go/iam v1.1.2/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU=
|
||||
cloud.google.com/go/longrunning v0.5.1 h1:Fr7TXftcqTudoyRJa113hyaqlGdiBQkp0Gq7tErFDWI=
|
||||
cloud.google.com/go/longrunning v0.5.1/go.mod h1:spvimkwdz6SPWKEt/XBij79E9fiTkHSQl/fRUUQJYJc=
|
||||
cloud.google.com/go/storage v1.33.0 h1:PVrDOkIC8qQVa1P3SXGpQvfuJhN2LHOoyZvWs8D2X5M=
|
||||
cloud.google.com/go/storage v1.33.0/go.mod h1:Hhh/dogNRGca7IWv1RC2YqEn0c0G77ctA/OxflYkiD8=
|
||||
firebase.google.com/go/v4 v4.12.0 h1:I6dCkcWUMFNkFdWgzlf8SLWecQnKdFgJhMv5fT9l1qI=
|
||||
firebase.google.com/go/v4 v4.12.0/go.mod h1:60c36dWLK4+j05Vw5XMllek3b3PCynU3BfI46OSwsUE=
|
||||
cloud.google.com/go/firestore v1.14.0 h1:8aLcKnMPoldYU3YHgu4t2exrKhLQkqaXAGqT0ljrFVw=
|
||||
cloud.google.com/go/firestore v1.14.0/go.mod h1:96MVaHLsEhbvkBEdZgfN+AS/GIkco1LRpH9Xp9YZfzQ=
|
||||
cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
|
||||
cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
|
||||
cloud.google.com/go/longrunning v0.5.4 h1:w8xEcbZodnA2BbW6sVirkkoC+1gP8wS57EUUgGS0GVg=
|
||||
cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI=
|
||||
cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w=
|
||||
cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
|
||||
firebase.google.com/go/v4 v4.12.1 h1:tDNvobifGsx/1HSFLnM0fmNfx/CDZSgsTO2KhZtgpcs=
|
||||
firebase.google.com/go/v4 v4.12.1/go.mod h1:60c36dWLK4+j05Vw5XMllek3b3PCynU3BfI46OSwsUE=
|
||||
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
|
||||
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
@@ -22,8 +22,10 @@ github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o=
|
||||
github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw=
|
||||
github.com/SherClockHolmes/webpush-go v1.2.0 h1:sGv0/ZWCvb1HUH+izLqrb2i68HuqD/0Y+AmGQfyqKJA=
|
||||
github.com/SherClockHolmes/webpush-go v1.2.0/go.mod h1:w6X47YApe/B9wUz2Wh8xukxlyupaxSSEbu6yKJcHN2w=
|
||||
github.com/SherClockHolmes/webpush-go v1.3.0 h1:CAu3FvEE9QS4drc3iKNgpBWFfGqNthKlZhp5QpYnu6k=
|
||||
github.com/SherClockHolmes/webpush-go v1.3.0/go.mod h1:AxRHmJuYwKGG1PVgYzToik1lphQvDnqFYDqimHvwhIw=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
@@ -31,23 +33,23 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead h1:fI1Jck0vUrXT8bnphprS1EoVRe2Q5CKCX8iDlpqjQ/Y=
|
||||
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 h1:hH4PQfOndHDlpzYfLAAfl63E8Le6F2+EL/cdhlkyRJY=
|
||||
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-smtp v0.17.0 h1:tq90evlrcyqRfE6DSXaWVH54oX6OuZOQECEmhWBMEtI=
|
||||
github.com/emersion/go-smtp v0.17.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
@@ -78,43 +80,50 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
|
||||
github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
|
||||
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
|
||||
github.com/olebedev/when v1.0.0 h1:T2DZCj8HxUhOVxcqaLOmzuTr+iZLtMHsZEim7mjIA2w=
|
||||
github.com/olebedev/when v1.0.0/go.mod h1:T0THb4kP9D3NNqlvCwIG4GyUioTAzEhB4RNVzig/43E=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
|
||||
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
|
||||
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
|
||||
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
|
||||
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
|
||||
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
|
||||
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -124,8 +133,9 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stripe/stripe-go/v74 v74.30.0 h1:0Kf0KkeFnY7iRhOwvTerX0Ia1BRw+eV1CVJ51mGYAUY=
|
||||
github.com/stripe/stripe-go/v74 v74.30.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaBRvGAimAO+udbBw=
|
||||
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
|
||||
@@ -135,17 +145,18 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -156,18 +167,20 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
|
||||
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
|
||||
golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
|
||||
golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -177,21 +190,27 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
|
||||
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY=
|
||||
golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@@ -199,12 +218,13 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
google.golang.org/api v0.142.0 h1:mf+7EJ94fi5ZcnpPy+m0Yv2dkz8bKm+UL0snTCuwXlY=
|
||||
google.golang.org/api v0.142.0/go.mod h1:zJAN5o6HRqR7O+9qJUFOWrZkYE66RH+efPBdTLA4xBA=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/api v0.151.0 h1:FhfXLO/NFdJIzQtCqjpysWwqKk8AzGWBUhMIx67cVDU=
|
||||
google.golang.org/api v0.151.0/go.mod h1:ccy+MJ6nrYFgE3WgRx/AMXOxOmU8Q4hSa+jjibzhxcg=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
@@ -214,19 +234,19 @@ google.golang.org/appengine/v2 v2.0.5/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13 h1:vlzZttNJGVqTsRFU9AmdnrcO1Znh8Ew9kCD//yjigk0=
|
||||
google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:CCviP9RmpZ1mxVr8MUjCnSiY09IbAXZxhLE6EhHIdPU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 h1:U7+wNaVuSTaUqNvK2+osJ9ejEZxbjHHk8F2b6Hpx0AE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:RdyHbowztCGQySiCvQPgWQWgWhGnouTdCflKoDBt32U=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA=
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I=
|
||||
google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -243,6 +263,7 @@ google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -3,7 +3,7 @@ package log
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
2
main.go
2
main.go
@@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/urfave/cli/v2"
|
||||
"heckel.io/ntfy/cmd"
|
||||
"heckel.io/ntfy/v2/cmd"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
@@ -64,7 +64,6 @@ markdown_extensions:
|
||||
- attr_list
|
||||
- md_in_html
|
||||
- pymdownx.emoji:
|
||||
emoji_index: !!python/name:materialx.emoji.twemoji
|
||||
emoji_generator: !!python/name:materialx.emoji.to_svg
|
||||
|
||||
plugins:
|
||||
|
||||
@@ -25,9 +25,9 @@ elif [[ "$1" == *.md ]]; then
|
||||
|
||||
<!-- This file was generated by scripts/emoji-convert.sh -->
|
||||
|
||||
You can [tag messages](../publish/#tags-emojis) with emojis 🥳 🎉 and other relevant strings. Matching tags are automatically
|
||||
You can [tag messages](publish.md#tags-emojis) with emojis 🥳 🎉 and other relevant strings. Matching tags are automatically
|
||||
converted to emojis. This is a reference of all supported emojis. To learn more about the feature, please refer to the
|
||||
[tagging and emojis page](../publish/#tags-emojis).
|
||||
[tagging and emojis page](publish.md#tags-emojis).
|
||||
|
||||
<table class=\"remove-md-box emoji-table\"><tr>
|
||||
" > "$1"
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
)
|
||||
|
||||
// Defines default config settings (excluding limits, see below)
|
||||
|
||||
@@ -2,7 +2,7 @@ package server_test
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"heckel.io/ntfy/server"
|
||||
"heckel.io/ntfy/v2/server"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package server
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ package server
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/require"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
"github.com/emersion/go-smtp"
|
||||
"github.com/gorilla/websocket"
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"net/http"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3" // SQLite driver
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -30,9 +30,9 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
)
|
||||
|
||||
// Server is the main server, providing the UI and API for ntfy
|
||||
|
||||
@@ -2,9 +2,9 @@ package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
@@ -3,9 +3,9 @@ package server
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/require"
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"io"
|
||||
"net/netip"
|
||||
"path/filepath"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ package server
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"firebase.google.com/go/v4/messaging"
|
||||
"fmt"
|
||||
"google.golang.org/api/option"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@@ -3,7 +3,7 @@ package server
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
)
|
||||
|
||||
type contextKey int
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
"github.com/stripe/stripe-go/v74/price"
|
||||
"github.com/stripe/stripe-go/v74/subscription"
|
||||
"github.com/stripe/stripe-go/v74/webhook"
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stripe/stripe-go/v74"
|
||||
"golang.org/x/time/rate"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"io"
|
||||
"net/netip"
|
||||
"path/filepath"
|
||||
|
||||
@@ -3,13 +3,13 @@ package server
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
@@ -24,8 +24,8 @@ import (
|
||||
|
||||
"github.com/SherClockHolmes/webpush-go"
|
||||
"github.com/stretchr/testify/require"
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -512,6 +512,8 @@ func TestServer_PublishAtAndPrune(t *testing.T) {
|
||||
messages := toMessages(t, response.Body.String())
|
||||
require.Equal(t, 1, len(messages)) // Not affected by pruning
|
||||
require.Equal(t, "a message", messages[0].Message)
|
||||
|
||||
time.Sleep(time.Second) // FIXME CI failing not sure why
|
||||
}
|
||||
|
||||
func TestServer_PublishAndMultiPoll(t *testing.T) {
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
@@ -2,8 +2,8 @@ package server
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/SherClockHolmes/webpush-go"
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/require"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
)
|
||||
|
||||
type mailer interface {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/emersion/go-smtp"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"io"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/mail"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
@@ -27,6 +29,11 @@ var (
|
||||
errUnsupportedContentType = errors.New("unsupported content type")
|
||||
)
|
||||
|
||||
var (
|
||||
onlySpacesRegex = regexp.MustCompile(`(?m)^\s+$`)
|
||||
consecutiveNewLinesRegex = regexp.MustCompile(`\n{3,}`)
|
||||
)
|
||||
|
||||
const (
|
||||
maxMultipartDepth = 2
|
||||
)
|
||||
@@ -232,37 +239,66 @@ func readMailBody(body io.Reader, header mail.Header) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if strings.ToLower(contentType) == "text/plain" {
|
||||
return readPlainTextMailBody(body, header.Get("Content-Transfer-Encoding"))
|
||||
} else if strings.HasPrefix(strings.ToLower(contentType), "multipart/") {
|
||||
return readMultipartMailBody(body, params, 0)
|
||||
canonicalContentType := strings.ToLower(contentType)
|
||||
if canonicalContentType == "text/plain" || canonicalContentType == "text/html" {
|
||||
return readTextMailBody(body, canonicalContentType, header.Get("Content-Transfer-Encoding"))
|
||||
} else if strings.HasPrefix(canonicalContentType, "multipart/") {
|
||||
return readMultipartMailBody(body, params)
|
||||
}
|
||||
return "", errUnsupportedContentType
|
||||
}
|
||||
|
||||
func readMultipartMailBody(body io.Reader, params map[string]string, depth int) (string, error) {
|
||||
func readMultipartMailBody(body io.Reader, params map[string]string) (string, error) {
|
||||
parts := make(map[string]string)
|
||||
if err := readMultipartMailBodyParts(body, params, 0, parts); err != nil && err != io.EOF {
|
||||
return "", err
|
||||
} else if s, ok := parts["text/plain"]; ok {
|
||||
return s, nil
|
||||
} else if s, ok := parts["text/html"]; ok {
|
||||
return s, nil
|
||||
}
|
||||
return "", io.EOF
|
||||
}
|
||||
|
||||
func readMultipartMailBodyParts(body io.Reader, params map[string]string, depth int, parts map[string]string) error {
|
||||
if depth >= maxMultipartDepth {
|
||||
return "", errMultipartNestedTooDeep
|
||||
return errMultipartNestedTooDeep
|
||||
}
|
||||
mr := multipart.NewReader(body, params["boundary"])
|
||||
for {
|
||||
part, err := mr.NextPart()
|
||||
if err != nil { // may be io.EOF
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
partContentType, partParams, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
if strings.ToLower(partContentType) == "text/plain" {
|
||||
return readPlainTextMailBody(part, part.Header.Get("Content-Transfer-Encoding"))
|
||||
canonicalPartContentType := strings.ToLower(partContentType)
|
||||
if canonicalPartContentType == "text/plain" || canonicalPartContentType == "text/html" {
|
||||
s, err := readTextMailBody(part, canonicalPartContentType, part.Header.Get("Content-Transfer-Encoding"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parts[canonicalPartContentType] = s
|
||||
} else if strings.HasPrefix(strings.ToLower(partContentType), "multipart/") {
|
||||
return readMultipartMailBody(part, partParams, depth+1)
|
||||
if err := readMultipartMailBodyParts(part, partParams, depth+1, parts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Continue with next part
|
||||
}
|
||||
}
|
||||
|
||||
func readTextMailBody(reader io.Reader, contentType, transferEncoding string) (string, error) {
|
||||
if contentType == "text/plain" {
|
||||
return readPlainTextMailBody(reader, transferEncoding)
|
||||
} else if contentType == "text/html" {
|
||||
return readHTMLMailBody(reader, transferEncoding)
|
||||
}
|
||||
return "", fmt.Errorf("unsupported content type: %s", contentType)
|
||||
}
|
||||
|
||||
func readPlainTextMailBody(reader io.Reader, transferEncoding string) (string, error) {
|
||||
if strings.ToLower(transferEncoding) == "base64" {
|
||||
reader = base64.NewDecoder(base64.StdEncoding, reader)
|
||||
@@ -275,3 +311,21 @@ func readPlainTextMailBody(reader io.Reader, transferEncoding string) (string, e
|
||||
}
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
func readHTMLMailBody(reader io.Reader, transferEncoding string) (string, error) {
|
||||
body, err := readPlainTextMailBody(reader, transferEncoding)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
stripped := bluemonday.
|
||||
StrictPolicy().
|
||||
AddSpaceWhenStrippingTag(true).
|
||||
Sanitize(body)
|
||||
return removeExtraEmptyLines(stripped), nil
|
||||
}
|
||||
|
||||
func removeExtraEmptyLines(s string) string {
|
||||
s = onlySpacesRegex.ReplaceAllString(s, "")
|
||||
s = consecutiveNewLinesRegex.ReplaceAllString(s, "\n\n")
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -568,6 +568,803 @@ L0VOIj4KClRoaXMgaXMgYSB0ZXN0IG1lc3NhZ2UgZnJvbSBUcnVlTkFTIENPUkUuCg==
|
||||
writeAndReadUntilLine(t, email, c, scanner, "554 5.0.0 Error: transaction failed, blame it on the weather: multipart message nested too deep")
|
||||
}
|
||||
|
||||
func TestSmtpBackend_HTMLEmail(t *testing.T) {
|
||||
email := `EHLO example.com
|
||||
MAIL FROM: test@mydomain.me
|
||||
RCPT TO: ntfy-mytopic@ntfy.sh
|
||||
DATA
|
||||
Message-Id: <51610934ss4.mmailer@fritz.box>
|
||||
From: <email@email.com>
|
||||
To: <email@email.com>,
|
||||
<ntfy-subjectatntfy@ntfy.sh>
|
||||
Date: Thu, 30 Mar 2023 02:56:53 +0000
|
||||
Subject: A HTML email
|
||||
Mime-Version: 1.0
|
||||
Content-Type: text/html;
|
||||
charset="utf-8"
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
<=21DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Alerttitle</title>
|
||||
<meta http-equiv=3D"content-type" content=3D"text/html;charset=3Dutf-8"/>
|
||||
</head>
|
||||
<body style=3D"color: =23000000; background-color: =23f0eee6;">
|
||||
<table width=3D"100%" align=3D"center" style=3D"border:solid 2px =23eeeeee=
|
||||
; border-collapse: collapse;">
|
||||
<tr>
|
||||
<td>
|
||||
<table style=3D"border-collapse: collapse;">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
<td style=3D"background: =23FFFFFF;">
|
||||
<table style=3D"color: =23FFFFFF; background-color: =23006EC0; border-coll=
|
||||
apse: collapse;">
|
||||
<tr>
|
||||
<td style=3D"width: 1000px; text-align: center; font-size: 18pt; font-fami=
|
||||
ly: Arial, Helvetica, sans-serif; padding: 10px;">
|
||||
|
||||
|
||||
headertext of table
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
<td style=3D"padding: 10px 20px; background: =23FFFFFF;">
|
||||
<table style=3D"border-collapse: collapse;">
|
||||
<tr>
|
||||
<td style=3D"width: 940px; font-size: 13pt; font-family: Arial, Helvetica,=
|
||||
sans-serif; text-align: left;">
|
||||
" Very important information about a change in your
|
||||
home automation setup
|
||||
|
||||
Now the light is on
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
<td style=3D"padding: 10px 20px; background: =23FFFFFF;">
|
||||
<table>
|
||||
<tr>
|
||||
<td style=3D"width: 960px; font-size: 10pt; font-family: Arial, Helvetica,=
|
||||
sans-serif; text-align: left;">
|
||||
<hr />
|
||||
If you don't want to receive this message anymore, stop the push
|
||||
services in your <a href=3D"https:fritzbox" target=3D"_=
|
||||
blank">FRITZ=21Box</a>=2E<br />
|
||||
Here you can see the active push services: "System > Push Service"=2E
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table style=3D"color: =23FFFFFF; background-color: =23006EC0;">
|
||||
<tr>
|
||||
<td style=3D"width: 1000px; font-size: 10pt; font-family: Arial, Helvetica=
|
||||
, sans-serif; text-align: center; padding: 10px;">
|
||||
This mail has ben sent by your <a style=3D"color: =23FFFFFF;" href=3D"https:=
|
||||
//fritzbox" target=3D"_blank">FRITZ=21Box</a=
|
||||
> automatically=2E
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
.
|
||||
`
|
||||
|
||||
s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, "/mytopic", r.URL.Path)
|
||||
require.Equal(t, "A HTML email", r.Header.Get("Title"))
|
||||
expected := `headertext of table
|
||||
|
||||
" Very important information about a change in your
|
||||
home automation setup
|
||||
|
||||
Now the light is on
|
||||
|
||||
If you don't want to receive this message anymore, stop the push
|
||||
services in your FRITZ!Box .
|
||||
Here you can see the active push services: "System > Push Service".
|
||||
|
||||
This mail has ben sent by your FRITZ!Box automatically.`
|
||||
require.Equal(t, expected, readAll(t, r.Body))
|
||||
})
|
||||
defer s.Close()
|
||||
defer c.Close()
|
||||
writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
|
||||
}
|
||||
|
||||
const spamEmail = `
|
||||
EHLO example.com
|
||||
MAIL FROM: test@mydomain.me
|
||||
RCPT TO: ntfy-mytopic@ntfy.sh
|
||||
DATA
|
||||
Delivered-To: somebody@gmail.com
|
||||
Received: by 2002:a05:651c:1248:b0:2bf:c263:285 with SMTP id h8csp1096496ljh;
|
||||
Mon, 30 Oct 2023 06:23:08 -0700 (PDT)
|
||||
X-Google-Smtp-Source: AGHT+IFsB3WqbwbeefbeefbeefbeefbeefiXRNDHnIy2xBeaYHZCM3EC8DfPv55qDtgq9djTeBCF
|
||||
X-Received: by 2002:a05:6808:147:b0:3af:66e5:5d3c with SMTP id h7-20020a056808014700b003af66e55d3cmr11662458oie.26.1698672188132;
|
||||
Mon, 30 Oct 2023 06:23:08 -0700 (PDT)
|
||||
ARC-Seal: i=1; a=rsa-sha256; t=1698672188; cv=none;
|
||||
d=google.com; s=arc-20160816;
|
||||
b=XM96KvnTbr4h6bqrTPTuuDNXmFCr9Be/HvVhu+UsSQjP9RxPk0wDTPUPZ/HWIJs52y
|
||||
beeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef
|
||||
BUmQ==
|
||||
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816;
|
||||
h=list-unsubscribe-post:list-unsubscribe:mime-version:subject:to
|
||||
:reply-to:from:date:message-id:dkim-signature:dkim-signature;
|
||||
bh=BERwBIp6fBgrZePFKQjyNMmgPkcnq1Zy1jPO8M0T4Ok=;
|
||||
fh=+kTCcNpX22TOI/SVSLygnrDqWeUt4zW7QKiv0TOVSGs=;
|
||||
b=lyIBRuOxPOTY2s36OqP7M7awlBKd4t5PX9mJOEJB0eTnTZqML+cplrXUIg2ZTlAAi9
|
||||
beeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef
|
||||
tgVQ==
|
||||
ARC-Authentication-Results: i=1; mx.google.com;
|
||||
dkim=pass header.i=@spamspam.com header.s=2020294246 header.b=G8y6xmtK;
|
||||
dkim=pass header.i=@auth.ccsend.com header.s=1000073432 header.b=ht8IksVK;
|
||||
spf=pass (google.com: domain of aigxeklyirlg+dvwkrmsgua==_1133104752381_suqcukvbeeynm/owplvdba==@in.constantcontact.com designates 208.75.123.226 as permitted sender) smtp.mailfrom="AigXeKlyIRLG+DvWkRMsGUA==_1133104752381_sUQcUKVBEeynm/oWPlvDBA==@in.constantcontact.com";
|
||||
dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=spamspam.com
|
||||
Return-Path: <AigXeKlyIRLG+DvWkRMsGUA==_1133104752381_sUQcUKVBEeynm/oWPlvDBA==@in.constantcontact.com>
|
||||
Received: from ccm30.constantcontact.com (ccm30.constantcontact.com. [208.75.123.226])
|
||||
by mx.google.com with ESMTPS id h2-20020a05620a21c200b0076eeed38118si5450962qka.131.2023.10.30.06.23.07
|
||||
for <somebody@gmail.com>
|
||||
(version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128);
|
||||
Mon, 30 Oct 2023 06:23:08 -0700 (PDT)
|
||||
Received-SPF: pass (google.com: domain of aigxeklyirlg+dvwkrmsgua==_1133104752381_suqcukvbeeynm/owplvdba==@in.constantcontact.com designates 208.75.123.226 as permitted sender) client-ip=208.75.123.226;
|
||||
Authentication-Results: mx.google.com;
|
||||
dkim=pass header.i=@spamspam.com header.s=2020294246 header.b=G8y6xmtK;
|
||||
dkim=pass header.i=@auth.ccsend.com header.s=1000073432 header.b=ht8IksVK;
|
||||
spf=pass (google.com: domain of aigxeklyirlg+dvwkrmsgua==_1133104752381_suqcukvbeeynm/owplvdba==@in.constantcontact.com designates 208.75.123.226 as permitted sender) smtp.mailfrom="AigXeKlyIRLG+DvWkRMsGUA==_1133104752381_sUQcUKVBEeynm/oWPlvDBA==@in.constantcontact.com";
|
||||
dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=spamspam.com
|
||||
Return-Path: <AigXeKlyIRLG+DvWkRMsGUA==_1133104752381_sUQcUKVBEeynm/oWPlvDBA==@in.constantcontact.com>
|
||||
Received: from [10.252.0.3] ([10.252.0.3:53254] helo=p2-jbemailsyndicator12.ctct.net) by 10.249.225.20 (envelope-from <AigXeKlyIRLG+DvWkRMsGUA==_1133104752381_sUQcUKVBEeynm/oWPlvDBA==@in.constantcontact.com>) (ecelerity 4.3.1.999 r(:)) with ESMTP id A4/82-60517-B3EAF356; Mon, 30 Oct 2023 09:23:07 -0400
|
||||
DKIM-Signature: v=1; q=dns/txt; a=rsa-sha256; c=relaxed/relaxed; s=2020294246; d=spamspam.com; h=date:mime-version:subject:X-Feedback-ID:X-250ok-CID:message-id:from:reply-to:list-unsubscribe:list-unsubscribe-post:to; bh=BERwBIp6fBgrZePFKQjyNMmgPkcnq1Zy1jPO8M0T4Ok=; b=G8y6xmtKv8asfEXA9o8dP+6foQjclo6j5sFREYVIJBbj5YJ5tqoiv5B04/qoRkoTBFDhmjt+BUua7AqDgPSnwbP2iPSA4fTJehnHhut1PyVUp/9vqSYlhxQehfdhma8tPg8ArKfYIKmfKJwKRaQBU0JHCaB1m+5LNQQX3UjkxAg=
|
||||
DKIM-Signature: v=1; q=dns/txt; a=rsa-sha256; c=relaxed/relaxed; s=1000073432; d=auth.ccsend.com; h=date:mime-version:subject:X-Feedback-ID:X-250ok-CID:message-id:from:reply-to:list-unsubscribe:list-unsubscribe-post:to; bh=BERwBIp6fBgrZePFKQjyNMmgPkcnq1Zy1jPO8M0T4Ok=; b=ht8IksVKYY/Kb3dUERWoeW4eVdYjKL6F4PEoIZOhfFXor6XAIbPnd3A/CPmbmoqFZjnKh5OdcUy1N5qEoj8w1Q3TmN8/ySQkqrlrmSDSZIHZMY7Qp9/TJrqUe4RMFOO1KKIN6Y0vGP1+dWe98msMAHwvi2qMjG9aEKLfFr2JUTQ=
|
||||
Message-ID: <1140728754828.1133104752381.1941549819.0.260913JL.2002@synd.ccsend.com>
|
||||
Date: Mon, 30 Oct 2023 09:23:07 -0400 (EDT)
|
||||
From: spamspam Loan Servicing <marklake@spamspam.com>
|
||||
Reply-To: marklake@spamspam.com
|
||||
To: somebody@gmail.com
|
||||
Subject: Buying a home? You deserve the confidence of Pre-Approval
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/alternative; boundary="----=_Part_75055660_144854819.1698672187348"
|
||||
List-Unsubscribe: <https://visitor.constantcontact.com/do?p=un&m=beefbeefbeef>
|
||||
List-Unsubscribe-Post: List-Unsubscribe=One-Click
|
||||
X-Campaign-Activity-ID: 8a05de2a-5c88-44b1-be0e-f5a444cb0650
|
||||
X-250ok-CID: 8a05de2a-5c88-44b1-be0e-f5a444cb0650
|
||||
X-Channel-ID: b1441c50-a541-11ec-a79b-fa163e5bc304
|
||||
X-Return-Path-Hint: AbeefbeefbeefbeefbeefUA==_1133104752381_sUQcUKVBEeynm/oWPlvDBA==@in.constantcontact.com
|
||||
X-Roving-Campaignid: 1140728754811
|
||||
X-Roving-Id: 1133104752381.1111111111
|
||||
X-Feedback-ID: b1441c50-a541-11ec-beef-beefbeefbeefbeef5de2a-5c88-44b1-be0e-f5a444cb0650:1133104752381:CTCT
|
||||
X-CTCT-ID: b13a9586-a541-11ec-beef-beefbeefbeef
|
||||
|
||||
------=_Part_75055660_144854819.1698672187348
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
When you're buying a home, Pre-Approval gives you confidence you're in the =
|
||||
right price range and shows sellers you mean business. xxxxxxxxx SELLING or=
|
||||
BUYING? Call: 844-590-2275 Get Your Homebuying PRE-APPROVAL IN 24-HOURS* G=
|
||||
et Pre-Approved When you're buying a home, Pre-Approval gives you confidenc=
|
||||
e you're in the right price range and shows sellers you mean business. xxx=
|
||||
xxxxxxGet Pre-Approved today! Click or Call to Get Pre-Approved 844-590-227=
|
||||
5 Get Pre-Approved nmlsconsumeraccess.org/ *The 24 hour timeframe is for mo=
|
||||
st approvals, however if additional information is needed or a request is o=
|
||||
n a holiday, the time for preapproval may be greater than 24 hours. This em=
|
||||
ail is for informational purposes only and is not an offer, loan approval o=
|
||||
r loan commitment. Mortgage rates are subject to change without notice. Som=
|
||||
e terms and restrictions may apply to certain loan programs. Refinancing ex=
|
||||
isting loans may result in total finance charges being higher over the life=
|
||||
of the loan, reduction in payments may partially reflect a longer loan ter=
|
||||
m. This information is provided as guidance and illustrative purposes only =
|
||||
and does not constitute legal or financial advice. We are not liable or bou=
|
||||
nd legally for any answers provided to any user for our process or position=
|
||||
on an issue. This information may change from time to time and at any time=
|
||||
without notification. The most current information will be updated periodi=
|
||||
cally and posted in the online forum. spamspam Loan Servicing, LLC. NMLS#39=
|
||||
1521. nmlsconsumeraccess.org. You are receiving this information as a curre=
|
||||
nt loan customer with spamspam Loan Servicing, LLC. Not licensed for lendin=
|
||||
g activities in any of the U.S. territories. Not authorized to originate lo=
|
||||
ans in the State of New York. Licensed by the Dept. of Financial Protection=
|
||||
and Innovation under the California Residential Mortgage .Lending Act #413=
|
||||
1216. This email was sent to somebody@gmail.com Version 103023PCHPrAp=
|
||||
9 xxxxxxxxx spamspam Loan Servicing | 4425 Ponce de Leon Blvd 5-251, Coral =
|
||||
Gables, FL 33146-1837 Unsubscribe somebody@gmail.com Update Profile |=
|
||||
Our Privacy Policy | Constant Contact Data Notice Sent by marklake@spamspa=
|
||||
m.com
|
||||
------=_Part_75055660_144854819.1698672187348
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
<!DOCTYPE HTML>
|
||||
<html lang=3D"en-US"> <head> <meta http-equiv=3D"Content-Type" content=3D"=
|
||||
text/html; charset=3Dutf-8"> <meta name=3D"viewport" content=3D"width=3Ddev=
|
||||
ice-width, initial-scale=3D1, maximum-scale=3D1"> <style type=3D"text/css=
|
||||
" data-premailer=3D"ignore">=20
|
||||
@media only screen and (max-width:480px) { .footer-main-width { width: 100%=
|
||||
!important; } .footer-mobile-hidden { display: none !important; } .foote=
|
||||
r-mobile-hidden { display: none !important; } .footer-column { display: bl=
|
||||
ock !important; } .footer-mobile-stack { display: block !important; } .fo=
|
||||
oter-mobile-stack-padding { padding-top: 3px; } }=20
|
||||
/* IE: correctly scale images with w/h attbs */ img { -ms-interpolation-mod=
|
||||
e: bicubic; }=20
|
||||
.layout { min-width: 100%; }=20
|
||||
table { table-layout: fixed; } .shell_outer-row { table-layout: auto; }=20
|
||||
/* Gmail/Web viewport fix */ u + .body .shell_outer-row { width: 620px; }=
|
||||
=20
|
||||
/* LIST AND p STYLE OVERRIDES */ .text .text_content-cell p { margin: 0; pa=
|
||||
dding: 0; margin-bottom: 0; } .text .text_content-cell ul, .text .text_cont=
|
||||
ent-cell ol { padding: 0; margin: 0 0 0 40px; } .text .text_content-cell li=
|
||||
{ padding: 0; margin: 0; /* line-height: 1.2; Remove after testing */ } /*=
|
||||
Text Link Style Reset */ a { text-decoration: underline; } /* iOS: Autolin=
|
||||
k styles inherited */ a[x-apple-data-detectors] { text-decoration: underlin=
|
||||
e !important; font-size: inherit !important; font-family: inherit !importan=
|
||||
t; font-weight: inherit !important; line-height: inherit !important; color:=
|
||||
inherit !important; } /* FF/Chrome: Smooth font rendering */ .text .text_c=
|
||||
ontent-cell { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing:=
|
||||
grayscale; }=20
|
||||
</style> <!--[if gte mso 9]> <style id=3D"ol-styles">=20
|
||||
/* OUTLOOK-SPECIFIC STYLES */ li { text-indent: -1em; padding: 0; margin: 0=
|
||||
; /* line-height: 1.2; Remove after testing */ } ul, ol { padding: 0; margi=
|
||||
n: 0 0 0 40px; } p { margin: 0; padding: 0; margin-bottom: 0; }=20
|
||||
</style> <![endif]--> <style>@media only screen and (max-width:480px) {
|
||||
.button_content-cell {
|
||||
padding-top: 10px !important; padding-right: 20px !important; padding-botto=
|
||||
m: 10px !important; padding-left: 20px !important;
|
||||
}
|
||||
.button_border-row .button_content-cell {
|
||||
padding-top: 10px !important; padding-right: 20px !important; padding-botto=
|
||||
m: 10px !important; padding-left: 20px !important;
|
||||
}
|
||||
.column .content-padding-horizontal {
|
||||
padding-left: 20px !important; padding-right: 20px !important;
|
||||
}
|
||||
.layout .column .content-padding-horizontal .content-padding-horizontal {
|
||||
padding-left: 0px !important; padding-right: 0px !important;
|
||||
}
|
||||
.layout .column .content-padding-horizontal .block-wrapper_border-row .cont=
|
||||
ent-padding-horizontal {
|
||||
padding-left: 20px !important; padding-right: 20px !important;
|
||||
}
|
||||
.dataTable {
|
||||
overflow: auto !important;
|
||||
}
|
||||
.dataTable .dataTable_content {
|
||||
width: auto !important;
|
||||
}
|
||||
.image--mobile-scale .image_container img {
|
||||
width: auto !important;
|
||||
}
|
||||
.image--mobile-center .image_container img {
|
||||
margin-left: auto !important; margin-right: auto !important;
|
||||
}
|
||||
.layout-margin .layout-margin_cell {
|
||||
padding: 0px 20px !important;
|
||||
}
|
||||
.layout-margin--uniform .layout-margin_cell {
|
||||
padding: 20px 20px !important;
|
||||
}
|
||||
.scale {
|
||||
width: 100% !important;
|
||||
}
|
||||
.stack {
|
||||
display: block !important; box-sizing: border-box;
|
||||
}
|
||||
.hide {
|
||||
display: none !important;
|
||||
}
|
||||
u + .body .shell_outer-row {
|
||||
width: 100% !important;
|
||||
}
|
||||
.socialFollow_container {
|
||||
text-align: center !important;
|
||||
}
|
||||
.text .text_content-cell {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
.text .text_content-cell h1 {
|
||||
font-size: 24px !important;
|
||||
}
|
||||
.text .text_content-cell h2 {
|
||||
font-size: 20px !important;
|
||||
}
|
||||
.text .text_content-cell h3 {
|
||||
font-size: 20px !important;
|
||||
}
|
||||
.text--sectionHeading .text_content-cell {
|
||||
font-size: 26px !important;
|
||||
}
|
||||
.text--heading .text_content-cell {
|
||||
font-size: 26px !important;
|
||||
}
|
||||
.text--feature .text_content-cell h2 {
|
||||
font-size: 20px !important;
|
||||
}
|
||||
.text--articleHeading .text_content-cell {
|
||||
font-size: 20px !important;
|
||||
}
|
||||
.text--article .text_content-cell h3 {
|
||||
font-size: 20px !important;
|
||||
}
|
||||
.text--featureHeading .text_content-cell {
|
||||
font-size: 20px !important;
|
||||
}
|
||||
.text--feature .text_content-cell h3 {
|
||||
font-size: 20px !important;
|
||||
}
|
||||
.text--dataTable .text_content-cell .dataTable .dataTable_content-cell {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
.text--dataTable .text_content-cell .dataTable th.dataTable_content-cell {
|
||||
font-size: px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head> <body class=3D"body template template--en-US" data-template-version=
|
||||
=3D"1.38.0" data-canonical-name=3D"CPE10001" lang=3D"en-US" align=3D"center=
|
||||
" style=3D"-ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; min-=
|
||||
width: 100%; width: 100%; margin: 0px; padding: 0px;"> <div id=3D"preheader=
|
||||
" style=3D"color: transparent; display: none; font-size: 1px; line-height: =
|
||||
1px; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden;"><span =
|
||||
data-entity-ref=3D"preheader">When you're buying a home, Pre-Approval =
|
||||
gives you confidence you're in the right price range and shows sellers=
|
||||
you mean business. </span></div> <div id=3D"tracking-image" style=3D"color=
|
||||
: transparent; display: none; font-size: 1px; line-height: 1px; max-height:=
|
||||
0px; max-width: 0px; opacity: 0; overflow: hidden;"><img src=3D"https://r2=
|
||||
0.rs6.net/on.jsp?ca=beefbeefbe-beef-44b1-be0e-f5a444cb0650&a=3D113310475238=
|
||||
1&c=3Db13a9586-a541-11ec-a79b-fa163e5bc304&ch=3Db1441c50-a541-11ec-a79b-fa1=
|
||||
63e5bc304" / alt=3D""></div> <div class=3D"shell" lang=3D"en-US" style=3D"b=
|
||||
ackground-color: #015288;"> <table class=3D"shell_panel-row" width=3D"100%=
|
||||
" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"background-colo=
|
||||
r: #015288;" bgcolor=3D"#015288"> <tr class=3D""> <td class=3D"shell_panel-=
|
||||
cell" style=3D"" align=3D"center" valign=3D"top"> <table class=3D"shell_wid=
|
||||
th-row scale" style=3D"width: 620px;" align=3D"center" border=3D"0" cellpad=
|
||||
ding=3D"0" cellspacing=3D"0"> <tr> <td class=3D"shell_width-cell" style=3D"=
|
||||
padding: 15px 10px;" align=3D"center" valign=3D"top"> <table class=3D"shell=
|
||||
_content-row" width=3D"100%" align=3D"center" border=3D"0" cellpadding=3D"0=
|
||||
" cellspacing=3D"0"> <tr> <td class=3D"shell_content-cell" style=3D"border-=
|
||||
radius: 0px; background-color: #FFFFFF; padding: 0; border: 0px solid #0096=
|
||||
d6;" align=3D"center" valign=3D"top" bgcolor=3D"#FFFFFF"> <table class=3D"l=
|
||||
ayout layout--1-column" style=3D"table-layout: fixed;" width=3D"100%" borde=
|
||||
r=3D"0" cellpadding=3D"0" cellspacing=3D"0"> <tr> <td class=3D"column colum=
|
||||
n--1 scale stack" style=3D"width: 100%;" align=3D"center" valign=3D"top">
|
||||
<table class=3D"divider" width=3D"100%" cellpadding=3D"0" cellspacing=3D"0"=
|
||||
border=3D"0"> <tr> <td class=3D"divider_container" style=3D"padding-top: 0=
|
||||
px; padding-bottom: 10px;" width=3D"100%" align=3D"center" valign=3D"top"> =
|
||||
<table class=3D"divider_content-row" style=3D"width: 100%; height: 1px;" ce=
|
||||
llpadding=3D"0" cellspacing=3D"0" border=3D"0"> <tr> <td class=3D"divider_c=
|
||||
ontent-cell" style=3D"padding-bottom: 5px; height: 1px; line-height: 1px; b=
|
||||
ackground-color: #0096D6; border-bottom-width: 0px;" height=3D"1" align=3D"=
|
||||
center" bgcolor=3D"#0096D6"> <img alt=3D"" width=3D"5" height=3D"1" border=
|
||||
=3D"0" hspace=3D"0" vspace=3D"0" src=3D"https://imgssl.constantcontact.com/=
|
||||
letters/images/1101116784221/S.gif" style=3D"display: block; height: 1px; w=
|
||||
idth: 5px;"> </td> </tr> </table> </td> </tr> </table> </td> </tr> </table>=
|
||||
<table class=3D"layout layout--1-column" style=3D"table-layout: fixed;" wi=
|
||||
dth=3D"100%" border=3D"0" cellpadding=3D"0" cellspacing=3D"0"> <tr> <td cla=
|
||||
ss=3D"column column--1 scale stack" style=3D"width: 100%;" align=3D"center"=
|
||||
valign=3D"top"><div class=3D"spacer" style=3D"line-height: 10px; height: 1=
|
||||
0px;"> </div></td> </tr> </table> <table class=3D"layout layout--1-c=
|
||||
olumn" style=3D"table-layout: fixed;" width=3D"100%" border=3D"0" cellpaddi=
|
||||
ng=3D"0" cellspacing=3D"0"> <tr> <td class=3D"column column--1 scale stack"=
|
||||
style=3D"width: 100%;" align=3D"center" valign=3D"top">
|
||||
<table class=3D"image image--padding-vertical image--mobile-scale image--mo=
|
||||
bile-center" width=3D"100%" border=3D"0" cellpadding=3D"0" cellspacing=3D"0=
|
||||
"> <tr> <td class=3D"image_container" align=3D"center" valign=3D"top" style=
|
||||
=3D"padding-top: 10px; padding-bottom: 10px;"> <a href=3D"https://r20.rs6.n=
|
||||
et/tn.jsp?f=3D001YKO1VR2jLW0SuSLZLfN7qCP9AwEGO0v-Vy-0SCUlMWvTEiCsv-QEMhmJe9=
|
||||
ch=3DHu9wLy0fth6D8jxFBWPA_NhdnWcZZPivk0KUTgRJoVIo_si10jiydw=3D=3D" data-tra=
|
||||
ckable=3D"true"><img data-image-content class=3D"image_content" width=3D"26=
|
||||
2" src=3D"https://files.constantcontact.com/beefbeefbee/057bff2a-bdba-4165-=
|
||||
b108-a7baa91c42c6.jpg" alt=3D"" style=3D"display: block; height: auto; max-=
|
||||
width: 100%;"></a> </td> </tr> </table> </td> </tr> </table> <table class=
|
||||
=3D"layout layout--heading layout--1-column" style=3D"background-color: #00=
|
||||
527e; table-layout: fixed;" width=3D"100%" border=3D"0" cellpadding=3D"0" c=
|
||||
ellspacing=3D"0" bgcolor=3D"#00527e"> <tr> <td class=3D"column column--1 sc=
|
||||
ale stack" style=3D"width: 100%;" align=3D"center" valign=3D"top">
|
||||
<table class=3D"text text--padding-vertical" width=3D"100%" border=3D"0" ce=
|
||||
llpadding=3D"0" cellspacing=3D"0" style=3D"table-layout: fixed;"> <tr> <td =
|
||||
class=3D"text_content-cell content-padding-horizontal" style=3D"text-align:=
|
||||
center; font-family: Arial,Verdana,Helvetica,sans-serif; color: #000000; f=
|
||||
ont-size: 14px; line-height: 1.2; display: block; word-wrap: break-word; pa=
|
||||
dding: 10px 20px;" align=3D"center" valign=3D"top">
|
||||
<h1 style=3D"font-family: Arial,Verdana,Helvetica,sans-serif; color: #606d7=
|
||||
8; font-size: 26px; font-weight: bold; margin: 0;"><span style=3D"color: rg=
|
||||
b(0, 150, 214);">SELLING or BUYING?</span></h1>
|
||||
<p style=3D"margin: 0;"><span style=3D"font-size: 16px; color: rgb(255, 255=
|
||||
, 255); font-weight: bold;">Call: 844-590-2275</span></p>
|
||||
</td> </tr> </table> </td> </tr> </table> <table class=3D"layout layout--ar=
|
||||
ticle layout--1-column" style=3D"table-layout: fixed;" width=3D"100%" borde=
|
||||
r=3D"0" cellpadding=3D"0" cellspacing=3D"0"> <tr> <td class=3D"column colum=
|
||||
n--1 scale stack" style=3D"width: 100%;" align=3D"center" valign=3D"top">
|
||||
<table class=3D"text text--heading text--padding-vertical" width=3D"100%" b=
|
||||
order=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"table-layout: fixe=
|
||||
d;"> <tr> <td class=3D"text_content-cell content-padding-horizontal" style=
|
||||
=3D"text-align: center; font-family: Arial,Verdana,Helvetica,sans-serif; co=
|
||||
lor: #606d78; font-size: 26px; line-height: 1.2; display: block; word-wrap:=
|
||||
break-word; font-weight: bold; padding: 10px 20px;" align=3D"center" valig=
|
||||
n=3D"top">
|
||||
<p style=3D"margin: 0;"><span style=3D"font-size: 30px; color: rgb(0, 150, =
|
||||
214);">Get Your Homebuying</span></p>
|
||||
<p style=3D"margin: 0;"><span style=3D"font-size: 30px; color: rgb(0, 82, 1=
|
||||
26);">PRE-APPROVAL IN 24-HOURS</span><span style=3D"font-size: 30px; color:=
|
||||
rgb(0, 82, 126); font-weight: normal;">*</span></p>
|
||||
</td> </tr> </table> <table class=3D"image image--padding-vertical image--m=
|
||||
obile-scale image--mobile-center" width=3D"100%" border=3D"0" cellpadding=
|
||||
=3D"0" cellspacing=3D"0"> <tr> <td class=3D"image_container content-padding=
|
||||
-horizontal" align=3D"center" valign=3D"top" style=3D"padding: 10px 20px;">=
|
||||
<img data-image-content class=3D"image_content" width=3D"548" src=3D"https=
|
||||
://files.constantcontact.com/df66e42d701/2092a2d7-0bda-4289-910b-bf50a2398d=
|
||||
60.jpg" alt=3D"" style=3D"display: block; height: auto; max-width: 100%;"> =
|
||||
</td> </tr> </table> <table class=3D"button button--padding-vertical" widt=
|
||||
h=3D"100%" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"table-=
|
||||
layout: fixed;"> <tr> <td class=3D"button_container content-padding-horizon=
|
||||
tal" align=3D"center" style=3D"padding: 10px 20px;"> <table class=3D"but=
|
||||
ton_content-row" style=3D"width: inherit; border-radius: 3px; border-spacin=
|
||||
g: 0; background-color: #0096D6; border: none;" border=3D"0" cellpadding=3D=
|
||||
"0" cellspacing=3D"0" bgcolor=3D"#0096D6"> <tr> <td class=3D"button_content=
|
||||
-cell" style=3D"padding: 10px 40px;" align=3D"center"> <a class=3D"button_l=
|
||||
ink" href=3D"https://r20.rs6.net/tn.jsp?f=3D001YKO1VR2jLW0SuSLZLfN7qCP9AwEG=
|
||||
O0v-Vy-0SCUlMWvTEiCsv-QEMuu9ZVVi6WGHhCias4f7-QkeggQvxIvbs-6TTaZHHhXLKf88NID=
|
||||
dci4Ge7aYN-QihEgqblie1-DQ2Fa1BKLbT3AM8rtrgeYQgVxJ6cG8POsvFzv7JstrGkCkg3a3AE=
|
||||
633LfQpAddyVLFkTv6oyS4T2j_YjYIPKDOZktqK_5rOR-Fh8cWGtUD8YPpPNnZ037z6_t9Nkemu=
|
||||
hxG&c=3DA65qX-dQJPS0J4afCS7H0Je5N-_6Q8Nh2fNHkb5-5biUYd5B9SY3zA=3D=3D&ch=3DH=
|
||||
u9wLy0fth6D8jxFBWPA_NhdnWcZZPivk0KUTgRJoVIo_si10jiydw=3D=3D" data-trackable=
|
||||
=3D"true" style=3D"font-size: 16px; font-weight: bold; color: #FFFFFF; font=
|
||||
-family: Helvetica,Arial,sans-serif; word-wrap: break-word; text-decoration=
|
||||
: none;">Get Pre-Approved</a> </td> </tr> </table> </td> </tr> </table> =
|
||||
<table class=3D"text text--padding-vertical" width=3D"100%" border=3D"0" =
|
||||
cellpadding=3D"0" cellspacing=3D"0" style=3D"table-layout: fixed;"> <tr> <t=
|
||||
d class=3D"text_content-cell content-padding-horizontal" style=3D"line-heig=
|
||||
ht: 1; text-align: center; font-family: Arial,Verdana,Helvetica,sans-serif;=
|
||||
color: #000000; font-size: 14px; display: block; word-wrap: break-word; pa=
|
||||
dding: 10px 20px;" align=3D"center" valign=3D"top">
|
||||
<p style=3D"text-align: left; margin: 0;" align=3D"left"><br></p>
|
||||
<p style=3D"margin: 0;"><span style=3D"font-size: 19px;">When you're buying=
|
||||
a home, Pre-Approval gives you confidence you're in the right price range =
|
||||
and shows sellers you mean business. </span></p>
|
||||
<p style=3D"margin: 0;"><span style=3D"font-size: 19px;">Get Pre-Ap=
|
||||
proved today!</span></p>
|
||||
</td> </tr> </table> </td> </tr> </table> <table class=3D"layout layout--1-=
|
||||
column" style=3D"table-layout: fixed;" width=3D"100%" border=3D"0" cellpadd=
|
||||
ing=3D"0" cellspacing=3D"0"> <tr> <td class=3D"column column--1 scale stack=
|
||||
" style=3D"width: 100%;" align=3D"center" valign=3D"top">
|
||||
<table class=3D"text text--padding-vertical" width=3D"100%" border=3D"0" ce=
|
||||
llpadding=3D"0" cellspacing=3D"0" style=3D"table-layout: fixed;"> <tr> <td =
|
||||
class=3D"text_content-cell content-padding-horizontal" style=3D"text-align:=
|
||||
left; font-family: Arial,Verdana,Helvetica,sans-serif; color: #000000; fon=
|
||||
t-size: 14px; line-height: 1.2; display: block; word-wrap: break-word; padd=
|
||||
ing: 10px 20px;" align=3D"left" valign=3D"top">
|
||||
<p style=3D"text-align: center; margin: 0;" align=3D"center"><br></p>
|
||||
<p style=3D"text-align: center; margin: 0;" align=3D"center"><span style=3D=
|
||||
"font-size: 23px; color: rgb(0, 82, 126); font-weight: bold; font-family: A=
|
||||
rial, Verdana, Helvetica, sans-serif;">Click or Call to Get Pre-Approved </=
|
||||
span></p>
|
||||
<p style=3D"text-align: center; margin: 0;" align=3D"center"><span style=3D=
|
||||
"font-size: 28px; color: rgb(0, 150, 214); font-weight: bold;">844-590-2275=
|
||||
</span></p>
|
||||
</td> </tr> </table> </td> </tr> </table> <table class=3D"layout layout--1-=
|
||||
column" style=3D"table-layout: fixed;" width=3D"100%" border=3D"0" cellpadd=
|
||||
ing=3D"0" cellspacing=3D"0"> <tr> <td class=3D"column column--1 scale stack=
|
||||
" style=3D"width: 100%;" align=3D"center" valign=3D"top"> <table class=3D"b=
|
||||
utton button--padding-vertical" width=3D"100%" border=3D"0" cellpadding=3D"=
|
||||
0" cellspacing=3D"0" style=3D"table-layout: fixed;"> <tr> <td class=3D"butt=
|
||||
on_container content-padding-horizontal" align=3D"center" style=3D"padding:=
|
||||
10px 20px;"> <table class=3D"button_content-row" style=3D"background-co=
|
||||
lor: #0096D6; width: inherit; border-radius: 3px; border-spacing: 0; border=
|
||||
: none;" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" bgcolor=3D"#0096D=
|
||||
6"> <tr> <td class=3D"button_content-cell" style=3D"padding: 10px 40px;" al=
|
||||
ign=3D"center"> <a class=3D"button_link" href=3D"https://r20.rs6.net/tn.jsp=
|
||||
?f=3D001thisisfakethisisfakethisisfakev-Vy-0SCUlMWvTEiCsv-QEMuu9ZVVi6WGHhCi=
|
||||
oVIo_si10jiydw=3D=3D" data-trackable=3D"true" style=3D"font-size: 16px; fon=
|
||||
t-weight: bold; color: #FFFFFF; font-family: Helvetica,Arial,sans-serif; wo=
|
||||
rd-wrap: break-word; text-decoration: none;">Get Pre-Approved</a> </td> </t=
|
||||
r> </table> </td> </tr> </table> </td> </tr> </table> <table class=3D"=
|
||||
layout layout--1-column" style=3D"table-layout: fixed;" width=3D"100%" bord=
|
||||
er=3D"0" cellpadding=3D"0" cellspacing=3D"0"> <tr> <td class=3D"column colu=
|
||||
mn--1 scale stack" style=3D"width: 100%;" align=3D"center" valign=3D"top">
|
||||
<table class=3D"image image--padding-vertical image--mobile-scale image--mo=
|
||||
bile-center" width=3D"100%" border=3D"0" cellpadding=3D"0" cellspacing=3D"0=
|
||||
"> <tr> <td class=3D"image_container" align=3D"center" valign=3D"top" style=
|
||||
=3D"padding-top: 10px; padding-bottom: 10px;"> <img data-image-content clas=
|
||||
s=3D"image_content" width=3D"87" src=3D"https://files.constantcontact.com/d=
|
||||
f66e42d701/beefbeef-beef-beef-9a13-2779ab497b8d.png" alt=3D"" style=3D"disp=
|
||||
lay: block; height: auto; max-width: 100%;"> </td> </tr> </table> </td> </t=
|
||||
r> </table> <table class=3D"layout layout--1-column" style=3D"table-layout:=
|
||||
fixed;" width=3D"100%" border=3D"0" cellpadding=3D"0" cellspacing=3D"0"> <=
|
||||
tr> <td class=3D"column column--1 scale stack" style=3D"width: 100%;" align=
|
||||
=3D"center" valign=3D"top">
|
||||
<table class=3D"text text--padding-vertical" width=3D"100%" border=3D"0" ce=
|
||||
llpadding=3D"0" cellspacing=3D"0" style=3D"table-layout: fixed;"> <tr> <td =
|
||||
class=3D"text_content-cell content-padding-horizontal" style=3D"text-align:=
|
||||
left; font-family: Arial,Verdana,Helvetica,sans-serif; color: #000000; fon=
|
||||
t-size: 14px; line-height: 1.2; display: block; word-wrap: break-word; padd=
|
||||
ing: 10px 20px;" align=3D"left" valign=3D"top">
|
||||
<p style=3D"text-align: center; margin: 0;" align=3D"center"><br></p>
|
||||
<p style=3D"text-align: center; margin: 0;" align=3D"center"><a href=3D"htt=
|
||||
ps://r20.rs6.net/tn.jsp?f=3D001YKO1VR2jLW0SuSLZLfN7qCP9AwEGO0v-Vy-0SCUlMWvT=
|
||||
EiCsv-QEMgYju54LKeEV1_a2OCyOAfG7VhZpxtOW89WM-s6S5iiXcmnbK-Z6XDc9LL569h6DE4L=
|
||||
IRMWiBWHOlFB9TZWQVuX6Ycz3505y1keCrca4QArp&c=3DA65qX-dQJPS0J4afCS7H0Je5N-_6Q=
|
||||
8Nh2fNHkb5-5biUYd5B9SY3zA=3D=3D&ch=3DHu9wLy0fth6D8jxFBWPA_NhdnWcZZPivk0KUTg=
|
||||
RJoVIo_si10jiydw=3D=3D" target=3D"_blank" style=3D"font-size: 11px; color: =
|
||||
rgb(153, 153, 153); text-decoration: underline; font-weight: normal; font-s=
|
||||
tyle: normal;">nmlsconsumeraccess.org/</a></p>
|
||||
<p style=3D"text-align: center; margin: 0;" align=3D"center"><span style=3D=
|
||||
"font-size: 11px; color: rgb(153, 153, 153);">*The 24 hour timeframe is for=
|
||||
most approvals, however if additional information is needed or a request i=
|
||||
s on a holiday, the time for preapproval may be greater than 24 hours.</spa=
|
||||
n></p>
|
||||
<p style=3D"text-align: center; margin: 0;" align=3D"center"><span style=3D=
|
||||
"font-size: 11px; color: rgb(153, 153, 153); background-color: rgb(255, 255=
|
||||
, 255);">This email is for informational purposes only and is not an offer,=
|
||||
loan approval or loan commitment. Mortgage rates are subject to change wit=
|
||||
hout notice. Some terms and restrictions may apply to certain loan programs=
|
||||
. Refinancing existing loans may result in total finance charges being high=
|
||||
er over the life of the loan, reduction in payments may partially reflect a=
|
||||
longer loan term. This information is provided as guidance and illustrativ=
|
||||
e purposes only and does not constitute legal or financial advice. We are n=
|
||||
ot liable or bound legally for any answers provided to any user for our pro=
|
||||
cess or position on an issue. This information may change from time to time=
|
||||
and at any time without notification. The most current information will be=
|
||||
updated periodically and posted in the online forum.</span></p>
|
||||
<p style=3D"text-align: center; margin: 0;" align=3D"center"><span style=3D=
|
||||
"font-size: 11px; color: rgb(153, 153, 153); background-color: rgb(255, 255=
|
||||
, 255);">spamspam Loan Servicing, LLC. NMLS#391521. nmlsconsumeraccess.org.=
|
||||
You are receiving this information as a current loan customer with spamspa=
|
||||
m Loan Servicing, LLC. Not licensed for lending activities in any of the U.=
|
||||
S. territories. Not authorized to originate loans in the State of New York.=
|
||||
Licensed by the Dept. of Financial Protection and Innovation under the Cal=
|
||||
ifornia Residential Mortgage .Lending Act #4131216.</span></p>
|
||||
<p style=3D"text-align: center; margin: 0;" align=3D"center"><br></p>
|
||||
<p style=3D"text-align: center; margin: 0;" align=3D"center"><span style=3D=
|
||||
"font-size: 11px; color: rgb(153, 153, 153);">This email was sent to <span =
|
||||
data-id=3D"emailAddress">somebody@gmail.com</span></span></p>
|
||||
<p style=3D"text-align: center; margin: 0;" align=3D"center"><span style=3D=
|
||||
"font-size: 11px; color: rgb(153, 153, 153);">Version 103023PCHPrAp9 </span=
|
||||
></p>
|
||||
<p style=3D"text-align: center; margin: 0;" align=3D"center"><span style=3D=
|
||||
"font-size: 11px; color: rgb(162, 162, 162);"></span></p>
|
||||
</td> </tr> </table> </td> </tr> </table> <table class=3D"layout layout--1-=
|
||||
column" style=3D"table-layout: fixed;" width=3D"100%" border=3D"0" cellpadd=
|
||||
ing=3D"0" cellspacing=3D"0"> <tr> <td class=3D"column column--1 scale stack=
|
||||
" style=3D"width: 100%;" align=3D"center" valign=3D"top">
|
||||
<table class=3D"divider" width=3D"100%" cellpadding=3D"0" cellspacing=3D"0"=
|
||||
border=3D"0"> <tr> <td class=3D"divider_container" style=3D"padding-top: 1=
|
||||
0px; padding-bottom: 0px;" width=3D"100%" align=3D"center" valign=3D"top"> =
|
||||
<table class=3D"divider_content-row" style=3D"width: 100%; height: 1px;" ce=
|
||||
llpadding=3D"0" cellspacing=3D"0" border=3D"0"> <tr> <td class=3D"divider_c=
|
||||
ontent-cell" style=3D"padding-bottom: 2px; height: 1px; line-height: 1px; b=
|
||||
ackground-color: #0096D6; border-bottom-width: 0px;" height=3D"1" align=3D"=
|
||||
center" bgcolor=3D"#0096D6"> <img alt=3D"" width=3D"5" height=3D"1" border=
|
||||
=3D"0" hspace=3D"0" vspace=3D"0" src=3D"https://imgssl.constantcontact.com/=
|
||||
letters/images/1111111111111/S.gif" style=3D"display: block; height: 1px; w=
|
||||
idth: 5px;"> </td> </tr> </table> </td> </tr> </table> </td> </tr> </table>=
|
||||
</td> </tr> </table> </td> </tr> </table> </td> </tr> <tr> <td class=3D"s=
|
||||
hell_panel-cell shell_panel-cell--systemFooter" style=3D"" align=3D"center"=
|
||||
valign=3D"top"> <table class=3D"shell_width-row scale" style=3D"width: 100=
|
||||
%;" align=3D"center" border=3D"0" cellpadding=3D"0" cellspacing=3D"0"> <tr>=
|
||||
<td class=3D"shell_width-cell" style=3D"padding: 0px;" align=3D"center" va=
|
||||
lign=3D"top"> <table class=3D"shell_content-row" width=3D"100%" align=3D"ce=
|
||||
nter" border=3D"0" cellpadding=3D"0" cellspacing=3D"0"> <tr> <td class=3D"s=
|
||||
hell_content-cell" style=3D"background-color: #FFFFFF; padding: 0; border: =
|
||||
0 solid #0096d6;" align=3D"center" valign=3D"top" bgcolor=3D"#FFFFFF"> <tab=
|
||||
le class=3D"layout layout--1-column" style=3D"table-layout: fixed;" width=
|
||||
=3D"100%" border=3D"0" cellpadding=3D"0" cellspacing=3D"0"> <tr> <td class=
|
||||
=3D"column column--1 scale stack" style=3D"width: 100%;" align=3D"center" v=
|
||||
align=3D"top"> <table class=3D"footer" width=3D"100%" border=3D"0" cellpadd=
|
||||
ing=3D"0" cellspacing=3D"0" style=3D"font-family: Verdana,Geneva,sans-serif=
|
||||
; color: #5d5d5d; font-size: 12px;"> <tr> <td class=3D"footer_container" al=
|
||||
ign=3D"center"> <table class=3D"footer-container" width=3D"100%" cellpaddin=
|
||||
g=3D"0" cellspacing=3D"0" border=3D"0" style=3D"background-color: #ffffff; =
|
||||
margin-left: auto; margin-right: auto; table-layout: auto !important;" bgco=
|
||||
lor=3D"#ffffff">
|
||||
<tr>
|
||||
<td width=3D"100%" align=3D"center" valign=3D"top" style=3D"width: 100%;">
|
||||
<div class=3D"footer-max-main-width" align=3D"center" style=3D"margin-left:=
|
||||
auto; margin-right: auto; max-width: 100%;">
|
||||
<table width=3D"100%" cellpadding=3D"0" cellspacing=3D"0" border=3D"0">
|
||||
<tr>
|
||||
<td class=3D"footer-layout" align=3D"center" valign=3D"top" style=3D"paddin=
|
||||
g: 16px 0px;">
|
||||
<table class=3D"footer-main-width" style=3D"width: 580px;" border=3D"0" cel=
|
||||
lpadding=3D"0" cellspacing=3D"0">
|
||||
<tr>
|
||||
<td class=3D"footer-text" align=3D"center" valign=3D"top" style=3D"color: #=
|
||||
5d5d5d; font-family: Verdana,Geneva,sans-serif; font-size: 12px; padding: 4=
|
||||
px 0px;">
|
||||
<span class=3D"footer-column">spamspam Loan Servicing<span class=3D"footer-=
|
||||
mobile-hidden"> | </span></span><span class=3D"footer-column">4425 Ponce de=
|
||||
Leon Blvd 5-251<span class=3D"footer-mobile-hidden">, </span></span><span =
|
||||
class=3D"footer-column"></span><span class=3D"footer-column"></span><span c=
|
||||
lass=3D"footer-column">Coral Gables, FL 33146-1837</span><span class=3D"foo=
|
||||
ter-column"></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=3D"footer-row" align=3D"center" valign=3D"top" style=3D"padding: =
|
||||
10px 0px;">
|
||||
<table cellpadding=3D"0" cellspacing=3D"0" border=3D"0">
|
||||
<tr>
|
||||
<td class=3D"footer-text" align=3D"center" valign=3D"top" style=3D"color: #=
|
||||
5d5d5d; font-family: Verdana,Geneva,sans-serif; font-size: 12px; padding: 4=
|
||||
px 0px;">
|
||||
<a href=3D"https://visitor.constantcontact.com/do?p=3Dun&m=3D001g3dtlqhzM3v=
|
||||
-44b1-be0e-f5a444cb0650" data-track=3D"false" style=3D"color: #5d5d5d;">Uns=
|
||||
ubscribe somebody@gmail.com<span class=3D"partnerOptOut"></span></a>
|
||||
<span class=3D"partnerOptOut"></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=3D"footer-text" align=3D"center" valign=3D"top" style=3D"color: #=
|
||||
5d5d5d; font-family: Verdana,Geneva,sans-serif; font-size: 12px; padding: 4=
|
||||
px 0px;">
|
||||
<a href=3D"https://visitor.constantcontact.com/do?p=3Doo&m=3D001g3dtlqhzM3v=
|
||||
-44b1-be0e-f5a444cb0650" data-track=3D"false" style=3D"color: #5d5d5d;">Upd=
|
||||
ate Profile</a> |
|
||||
<a href=3D"https://spamspam.com/privacy-notice/" data-track=3D"false" style=
|
||||
=3D"color: #5d5d5d;">Our Privacy Policy</a><span class=3D"footer-mobile-hid=
|
||||
den"> |</span>
|
||||
<a class=3D"footer-about-provider footer-mobile-stack footer-mobile-stack-p=
|
||||
adding" href=3D"http://www.constantcontact.com/legal/about-constant-contact=
|
||||
" data-track=3D"false" style=3D"color: #5d5d5d;">Constant Contact Data Noti=
|
||||
ce</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=3D"footer-text" align=3D"center" valign=3D"top" style=3D"color: #=
|
||||
5d5d5d; font-family: Verdana,Geneva,sans-serif; font-size: 12px; padding: 4=
|
||||
px 0px;">
|
||||
Sent by
|
||||
<a href=3D"mailto:marklake@spamspam.com" style=3D"color: #5d5d5d; text-deco=
|
||||
ration: none;">marklake@spamspam.com</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=3D"footer-text" align=3D"center" valign=3D"top" style=3D"color: #=
|
||||
5d5d5d; font-family: Verdana,Geneva,sans-serif; font-size: 12px; padding: 4=
|
||||
px 0px;">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table> </td> </tr> </table> </td> </tr> </table> </td> </tr> </table> =
|
||||
</td> </tr> </table> </td> </tr> </table> </div> </body> </html>
|
||||
|
||||
------=_Part_75055660_144854819.1698672187348--
|
||||
.
|
||||
`
|
||||
|
||||
func TestSmtpBackend_Spam_Text(t *testing.T) {
|
||||
email := spamEmail
|
||||
s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, "/mytopic", r.URL.Path)
|
||||
require.Equal(t, "Buying a home? You deserve the confidence of Pre-Approval", r.Header.Get("Title"))
|
||||
actual := readAll(t, r.Body)
|
||||
expected := "When you're buying a home, Pre-Approval gives you confidence you're in the right price range and shows sellers you mean business. xxxxxxxxx SELLING or BUYING? Call: 844-590-2275 Get Your Homebuying PRE-APPROVAL IN 24-HOURS* Get Pre-Approved When you're buying a home, Pre-Approval gives you confidence you're in the right price range and shows sellers you mean business. xxxxxxxxxGet Pre-Approved today! Click or Call to Get Pre-Approved 844-590-2275 Get Pre-Approved nmlsconsumeraccess.org/ *The 24 hour timeframe is for most approvals, however if additional information is needed or a request is on a holiday, the time for preapproval may be greater than 24 hours. This email is for informational purposes only and is not an offer, loan approval or loan commitment. Mortgage rates are subject to change without notice. Some terms and restrictions may apply to certain loan programs. Refinancing existing loans may result in total finance charges being higher over the life of the loan, reduction in payments may partially reflect a longer loan term. This information is provided as guidance and illustrative purposes only and does not constitute legal or financial advice. We are not liable or bound legally for any answers provided to any user for our process or position on an issue. This information may change from time to time and at any time without notification. The most current information will be updated periodically and posted in the online forum. spamspam Loan Servicing, LLC. NMLS#391521. nmlsconsumeraccess.org. You are receiving this information as a current loan customer with spamspam Loan Servicing, LLC. Not licensed for lending activities in any of the U.S. territories. Not authorized to originate loans in the State of New York. Licensed by the Dept. of Financial Protection and Innovation under the California Residential Mortgage .Lending Act #4131216. This email was sent to somebody@gmail.com Version 103023PCHPrAp9 xxxxxxxxx spamspam Loan Servicing | 4425 Ponce de Leon Blvd 5-251, Coral Gables, FL 33146-1837 Unsubscribe somebody@gmail.com Update Profile | Our Privacy Policy | Constant Contact Data Notice Sent by marklake@spamspam.com"
|
||||
require.Equal(t, expected, actual)
|
||||
})
|
||||
defer s.Close()
|
||||
defer c.Close()
|
||||
writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
|
||||
}
|
||||
|
||||
func TestSmtpBackend_Spam_HTML(t *testing.T) {
|
||||
email := strings.ReplaceAll(spamEmail, "text/plain", "text/not-plain-anymore") // We artificially force HTML parsing here
|
||||
s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, "/mytopic", r.URL.Path)
|
||||
require.Equal(t, "Buying a home? You deserve the confidence of Pre-Approval", r.Header.Get("Title"))
|
||||
actual := readAll(t, r.Body)
|
||||
expected := `When you're buying a home, Pre-Approval gives you confidence you're in the right price range and shows sellers you mean business.
|
||||
` + "\u200a" + `
|
||||
|
||||
SELLING or BUYING?
|
||||
Call: 844-590-2275
|
||||
|
||||
Get Your Homebuying
|
||||
PRE-APPROVAL IN 24-HOURS *
|
||||
Get Pre-Approved
|
||||
|
||||
When you're buying a home, Pre-Approval gives you confidence you're in the right price range and shows sellers you mean business.
|
||||
` + "\ufeff" + `Get Pre-Approved today!
|
||||
|
||||
Click or Call to Get Pre-Approved
|
||||
844-590-2275
|
||||
Get Pre-Approved
|
||||
|
||||
nmlsconsumeraccess.org/
|
||||
*The 24 hour timeframe is for most approvals, however if additional information is needed or a request is on a holiday, the time for preapproval may be greater than 24 hours.
|
||||
This email is for informational purposes only and is not an offer, loan approval or loan commitment. Mortgage rates are subject to change without notice. Some terms and restrictions may apply to certain loan programs Refinancing existing loans may result in total finance charges being higher over the life of the loan, reduction in payments may partially reflect a longer loan term. This information is provided as guidance and illustrative purposes only and does not constitute legal or financial advice. We are not liable or bound legally for any answers provided to any user for our process or position on an issue. This information may change from time to time and at any time without notification. The most current information will be updated periodically and posted in the online forum.
|
||||
spamspam Loan Servicing, LLC. NMLS#391521. nmlsconsumeraccess.org. You are receiving this information as a current loan customer with spamspam Loan Servicing, LLC. Not licensed for lending activities in any of the U.S. territories. Not authorized to originate loans in the State of New York. Licensed by the Dept. of Financial Protection and Innovation under the California Residential Mortgage .Lending Act #4131216.
|
||||
|
||||
This email was sent to somebody@gmail.com
|
||||
Version 103023PCHPrAp9
|
||||
` + "\ufeff" + `
|
||||
|
||||
spamspam Loan Servicing | 4425 Ponce de Leon Blvd 5-251 , Coral Gables, FL 33146-1837
|
||||
|
||||
Unsubscribe somebody@gmail.com
|
||||
|
||||
Update Profile |
|
||||
Our Privacy Policy |
|
||||
Constant Contact Data Notice
|
||||
|
||||
Sent by
|
||||
marklake@spamspam.com`
|
||||
require.Equal(t, expected, actual)
|
||||
})
|
||||
defer s.Close()
|
||||
defer c.Close()
|
||||
writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
|
||||
}
|
||||
|
||||
func TestSmtpBackend_HTMLOnly_FromDiskStation(t *testing.T) {
|
||||
email := `EHLO example.com
|
||||
MAIL FROM: synology@mydomain.me
|
||||
RCPT TO: synology@mydomain.me
|
||||
DATA
|
||||
From: "=?UTF-8?B?Um9iYmll?=" <synology@mydomain.me>
|
||||
To: <synology@mydomain.me>
|
||||
Message-Id: <640e6f562895d.6c9584bcfa491ac9c546b480b32ffc1d@mydomain.me>
|
||||
MIME-Version: 1.0
|
||||
Subject: =?UTF-8?B?W1N5bm9sb2d5IE5BU10gVGVzdCBNZXNzYWdlIGZyb20gTGl0dHNfTkFT?=
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Congratulations! You have successfully set up the email notification on Synology_NAS.<BR>For further system configurations, please visit http://192.168.1.28:5000/, http://172.16.60.5:5000/.<BR>(If you cannot connect to the server, please contact the administrator.)<BR><BR>From Synology_NAS<BR><BR><BR>
|
||||
.
|
||||
`
|
||||
s, c, conf, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, "/synology", r.URL.Path)
|
||||
require.Equal(t, "[Synology NAS] Test Message from Litts_NAS", r.Header.Get("Title"))
|
||||
actual := readAll(t, r.Body)
|
||||
expected := `Congratulations! You have successfully set up the email notification on Synology_NAS. For further system configurations, please visit http://192.168.1.28:5000/, http://172.16.60.5:5000/. (If you cannot connect to the server, please contact the administrator.) From Synology_NAS`
|
||||
require.Equal(t, expected, actual)
|
||||
})
|
||||
conf.SMTPServerDomain = "mydomain.me"
|
||||
conf.SMTPServerAddrPrefix = ""
|
||||
defer s.Close()
|
||||
defer c.Close()
|
||||
writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
|
||||
}
|
||||
|
||||
func TestSmtpBackend_PlaintextWithToken(t *testing.T) {
|
||||
email := `EHLO example.com
|
||||
MAIL FROM: phil@example.com
|
||||
@@ -639,7 +1436,6 @@ func readUntilLine(t *testing.T, conn net.Conn, scanner *bufio.Scanner, expected
|
||||
return
|
||||
}
|
||||
output += text + "\n"
|
||||
//fmt.Println(text)
|
||||
}
|
||||
t.Fatalf("Expected line '%s' not found in output:\n%s", expectedLine, output)
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -69,7 +69,7 @@ func TestTopic_Subscribe_DuplicateID(t *testing.T) {
|
||||
t.Parallel()
|
||||
to := newTopic("mytopic")
|
||||
|
||||
// Fix random seed to force same number generation
|
||||
//lint:ignore SA1019 Fix random seed to force same number generation
|
||||
rand.Seed(1)
|
||||
a := rand.Int()
|
||||
to.subscribers[a] = &topicSubscriber{
|
||||
@@ -82,7 +82,7 @@ func TestTopic_Subscribe_DuplicateID(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Force rand.Int to generate the same id once more
|
||||
//lint:ignore SA1019 Force rand.Int to generate the same id once more
|
||||
rand.Seed(1)
|
||||
id := to.Subscribe(subFn, "b", func() {})
|
||||
res := to.subscribers[id]
|
||||
|
||||
@@ -5,10 +5,10 @@ import (
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
)
|
||||
|
||||
// List of possible events
|
||||
|
||||
@@ -3,7 +3,7 @@ package server
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
|
||||
@@ -2,14 +2,14 @@ package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/user"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -3,7 +3,7 @@ package server
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
|
||||
@@ -2,18 +2,13 @@ package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"heckel.io/ntfy/server"
|
||||
"heckel.io/ntfy/v2/server"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixMilli())
|
||||
}
|
||||
|
||||
// StartServer starts a server.Server with a random port and waits for the server to be up
|
||||
func StartServer(t *testing.T) (*server.Server, int) {
|
||||
return StartServerWithConfig(t, server.NewConfig())
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/mattn/go-sqlite3"
|
||||
"github.com/stripe/stripe-go/v74"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -161,7 +161,7 @@ const (
|
||||
FROM user_access a
|
||||
JOIN user u ON u.id = a.user_id
|
||||
WHERE (u.user = ? OR u.user = ?) AND ? LIKE a.topic ESCAPE '\'
|
||||
ORDER BY u.user DESC
|
||||
ORDER BY u.user DESC, LENGTH(a.topic) DESC, a.write DESC
|
||||
`
|
||||
|
||||
insertUserQuery = `
|
||||
@@ -197,13 +197,13 @@ const (
|
||||
selectUserAllAccessQuery = `
|
||||
SELECT user_id, topic, read, write
|
||||
FROM user_access
|
||||
ORDER BY write DESC, read DESC, topic
|
||||
ORDER BY LENGTH(topic) DESC, write DESC, read DESC, topic
|
||||
`
|
||||
selectUserAccessQuery = `
|
||||
SELECT topic, read, write
|
||||
FROM user_access
|
||||
WHERE user_id = (SELECT id FROM user WHERE user = ?)
|
||||
ORDER BY write DESC, read DESC, topic
|
||||
ORDER BY LENGTH(topic) DESC, write DESC, read DESC, topic
|
||||
`
|
||||
selectUserReservationsQuery = `
|
||||
SELECT a_user.topic, a_user.read, a_user.write, a_everyone.read AS everyone_read, a_everyone.write AS everyone_write
|
||||
@@ -833,8 +833,10 @@ func (a *Manager) Authorize(user *User, topic string, perm Permission) error {
|
||||
if user != nil {
|
||||
username = user.Name
|
||||
}
|
||||
// Select the read/write permissions for this user/topic combo. The query may return two
|
||||
// rows (one for everyone, and one for the user), but prioritizes the user.
|
||||
// Select the read/write permissions for this user/topic combo.
|
||||
// - The query may return two rows (one for everyone, and one for the user), but prioritizes the user.
|
||||
// - Furthermore, the query prioritizes more specific permissions (longer!) over more generic ones, e.g. "test*" > "*"
|
||||
// - It also prioritizes write permissions over read permissions
|
||||
rows, err := a.db.Query(selectTopicPermsQuery, Everyone, username, topic)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stripe/stripe-go/v74"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"net/netip"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -20,10 +20,15 @@ func TestManager_FullScenario_Default_DenyAll(t *testing.T) {
|
||||
a := newTestManagerFromFile(t, filepath.Join(t.TempDir(), "user.db"), "", PermissionDenyAll, DefaultUserPasswordBcryptCost, DefaultUserStatsQueueWriterInterval)
|
||||
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin))
|
||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
|
||||
require.Nil(t, a.AddUser("john", "john", RoleUser))
|
||||
require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite))
|
||||
require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead))
|
||||
require.Nil(t, a.AllowAccess("ben", "writeme", PermissionWrite))
|
||||
require.Nil(t, a.AllowAccess("ben", "everyonewrite", PermissionDenyAll)) // How unfair!
|
||||
require.Nil(t, a.AllowAccess("john", "*", PermissionRead))
|
||||
require.Nil(t, a.AllowAccess("john", "mytopic*", PermissionReadWrite))
|
||||
require.Nil(t, a.AllowAccess("john", "mytopic_ro*", PermissionRead))
|
||||
require.Nil(t, a.AllowAccess("john", "mytopic_deny*", PermissionDenyAll))
|
||||
require.Nil(t, a.AllowAccess(Everyone, "announcements", PermissionRead))
|
||||
require.Nil(t, a.AllowAccess(Everyone, "everyonewrite", PermissionReadWrite))
|
||||
require.Nil(t, a.AllowAccess(Everyone, "up*", PermissionWrite)) // Everyone can write to /up*
|
||||
@@ -47,12 +52,27 @@ func TestManager_FullScenario_Default_DenyAll(t *testing.T) {
|
||||
benGrants, err := a.Grants("ben")
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, []Grant{
|
||||
{"everyonewrite", PermissionDenyAll},
|
||||
{"mytopic", PermissionReadWrite},
|
||||
{"writeme", PermissionWrite},
|
||||
{"readme", PermissionRead},
|
||||
{"everyonewrite", PermissionDenyAll},
|
||||
}, benGrants)
|
||||
|
||||
john, err := a.Authenticate("john", "john")
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "john", john.Name)
|
||||
require.True(t, strings.HasPrefix(john.Hash, "$2a$10$"))
|
||||
require.Equal(t, RoleUser, john.Role)
|
||||
|
||||
johnGrants, err := a.Grants("john")
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, []Grant{
|
||||
{"mytopic_deny*", PermissionDenyAll},
|
||||
{"mytopic_ro*", PermissionRead},
|
||||
{"mytopic*", PermissionReadWrite},
|
||||
{"*", PermissionRead},
|
||||
}, johnGrants)
|
||||
|
||||
notben, err := a.Authenticate("ben", "this is wrong")
|
||||
require.Nil(t, notben)
|
||||
require.Equal(t, ErrUnauthenticated, err)
|
||||
@@ -78,6 +98,20 @@ func TestManager_FullScenario_Default_DenyAll(t *testing.T) {
|
||||
require.Nil(t, a.Authorize(ben, "announcements", PermissionRead))
|
||||
require.Equal(t, ErrUnauthorized, a.Authorize(ben, "announcements", PermissionWrite))
|
||||
|
||||
// User john should have
|
||||
// "deny" to mytopic_deny*,
|
||||
// "ro" to mytopic_ro*,
|
||||
// "rw" to mytopic*,
|
||||
// "ro" to the rest
|
||||
require.Equal(t, ErrUnauthorized, a.Authorize(john, "mytopic_deny_case", PermissionRead))
|
||||
require.Equal(t, ErrUnauthorized, a.Authorize(john, "mytopic_deny_case", PermissionWrite))
|
||||
require.Nil(t, a.Authorize(john, "mytopic_ro_test_case", PermissionRead))
|
||||
require.Equal(t, ErrUnauthorized, a.Authorize(john, "mytopic_ro_test_case", PermissionWrite))
|
||||
require.Nil(t, a.Authorize(john, "mytopic_case1", PermissionRead))
|
||||
require.Nil(t, a.Authorize(john, "mytopic_case1", PermissionWrite))
|
||||
require.Nil(t, a.Authorize(john, "readme", PermissionRead))
|
||||
require.Equal(t, ErrUnauthorized, a.Authorize(john, "writeme", PermissionWrite))
|
||||
|
||||
// Everyone else can do barely anything
|
||||
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", PermissionRead))
|
||||
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", PermissionWrite))
|
||||
@@ -95,6 +129,22 @@ func TestManager_FullScenario_Default_DenyAll(t *testing.T) {
|
||||
require.Nil(t, a.Authorize(nil, "up5678", PermissionWrite))
|
||||
}
|
||||
|
||||
func TestManager_Access_Order_LengthWriteRead(t *testing.T) {
|
||||
// This test validates issue #914 / #917, i.e. that write permissions are prioritized over read permissions,
|
||||
// and longer ACL rules are prioritized as well.
|
||||
|
||||
a := newTestManagerFromFile(t, filepath.Join(t.TempDir(), "user.db"), "", PermissionDenyAll, DefaultUserPasswordBcryptCost, DefaultUserStatsQueueWriterInterval)
|
||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
|
||||
require.Nil(t, a.AllowAccess("ben", "test*", PermissionReadWrite))
|
||||
require.Nil(t, a.AllowAccess("ben", "*", PermissionRead))
|
||||
|
||||
ben, err := a.Authenticate("ben", "ben")
|
||||
require.Nil(t, err)
|
||||
require.Nil(t, a.Authorize(ben, "any-topic-can-be-read", PermissionRead))
|
||||
require.Nil(t, a.Authorize(ben, "this-too", PermissionRead))
|
||||
require.Nil(t, a.Authorize(ben, "test123", PermissionWrite))
|
||||
}
|
||||
|
||||
func TestManager_AddUser_Invalid(t *testing.T) {
|
||||
a := newTestManager(t, PermissionDenyAll)
|
||||
require.Equal(t, ErrInvalidArgument, a.AddUser(" invalid ", "pass", RoleAdmin))
|
||||
@@ -227,10 +277,10 @@ func TestManager_UserManagement(t *testing.T) {
|
||||
benGrants, err := a.Grants("ben")
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, []Grant{
|
||||
{"everyonewrite", PermissionDenyAll},
|
||||
{"mytopic", PermissionReadWrite},
|
||||
{"writeme", PermissionWrite},
|
||||
{"readme", PermissionRead},
|
||||
{"everyonewrite", PermissionDenyAll},
|
||||
}, benGrants)
|
||||
|
||||
everyone, err := a.User(Everyone)
|
||||
@@ -1101,10 +1151,10 @@ func TestMigrationFrom1(t *testing.T) {
|
||||
require.Equal(t, syncTopicLength, len(ben.SyncTopic))
|
||||
require.NotEqual(t, ben.SyncTopic, phil.SyncTopic)
|
||||
require.Equal(t, 2, len(benGrants))
|
||||
require.Equal(t, "stats", benGrants[0].TopicPattern)
|
||||
require.Equal(t, PermissionReadWrite, benGrants[0].Allow)
|
||||
require.Equal(t, "secret", benGrants[1].TopicPattern)
|
||||
require.Equal(t, PermissionRead, benGrants[1].Allow)
|
||||
require.Equal(t, "secret", benGrants[0].TopicPattern)
|
||||
require.Equal(t, PermissionRead, benGrants[0].Allow)
|
||||
require.Equal(t, "stats", benGrants[1].TopicPattern)
|
||||
require.Equal(t, PermissionReadWrite, benGrants[1].Allow)
|
||||
|
||||
require.Equal(t, "u_everyone", everyone.ID)
|
||||
require.Equal(t, Everyone, everyone.Name)
|
||||
@@ -1225,9 +1275,9 @@ func TestMigrationFrom4(t *testing.T) {
|
||||
require.Nil(t, err)
|
||||
|
||||
require.Equal(t, 4, len(everyoneGrants))
|
||||
require.Equal(t, "down_*", everyoneGrants[0].TopicPattern)
|
||||
require.Equal(t, "left_*", everyoneGrants[1].TopicPattern)
|
||||
require.Equal(t, "mytopic_", everyoneGrants[2].TopicPattern)
|
||||
require.Equal(t, "mytopic_", everyoneGrants[0].TopicPattern)
|
||||
require.Equal(t, "down_*", everyoneGrants[1].TopicPattern)
|
||||
require.Equal(t, "left_*", everyoneGrants[2].TopicPattern)
|
||||
require.Equal(t, "up*", everyoneGrants[3].TopicPattern)
|
||||
|
||||
// Check they are stored correctly in the database
|
||||
|
||||
@@ -3,7 +3,7 @@ package user
|
||||
import (
|
||||
"errors"
|
||||
"github.com/stripe/stripe-go/v74"
|
||||
"heckel.io/ntfy/log"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"net/netip"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -2,7 +2,7 @@ package util_test
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"heckel.io/ntfy/util"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
@@ -253,6 +253,8 @@ func ReadPassword(in io.Reader) ([]byte, error) {
|
||||
password, err := term.ReadPassword(int(f.Fd())) // This is always going to be 0
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(password) == 0 {
|
||||
return nil, errors.New("password cannot be empty")
|
||||
}
|
||||
return password, nil
|
||||
}
|
||||
@@ -272,7 +274,9 @@ func ReadPassword(in io.Reader) ([]byte, error) {
|
||||
}
|
||||
password = append(password, buf[0])
|
||||
}
|
||||
|
||||
if len(password) == 0 {
|
||||
return nil, errors.New("password cannot be empty")
|
||||
}
|
||||
return password, nil
|
||||
}
|
||||
|
||||
|
||||
1350
web/package-lock.json
generated
1350
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -329,5 +329,6 @@
|
||||
"publish_dialog_attachment_limits_quota_reached": "يتجاوز الحصة، {{remainingBytes}} متبقية",
|
||||
"account_basics_tier_paid_until": "تم دفع مبلغ الاشتراك إلى غاية {{date}}، وسيتم تجديده تِلْقائيًا",
|
||||
"account_basics_tier_canceled_subscription": "تم إلغاء اشتراكك وسيتم إعادته إلى مستوى حساب مجاني بداية مِن {{date}}.",
|
||||
"account_delete_dialog_billing_warning": "إلغاء حسابك أيضاً يلغي اشتراكك في الفوترة فوراً ولن تتمكن من الوصول إلى لوح الفوترة بعد الآن."
|
||||
"account_delete_dialog_billing_warning": "إلغاء حسابك أيضاً يلغي اشتراكك في الفوترة فوراً ولن تتمكن من الوصول إلى لوح الفوترة بعد الآن.",
|
||||
"nav_upgrade_banner_description": "حجز المواضيع والمزيد من الرسائل ورسائل البريد الإلكتروني والمرفقات الأكبر حجمًا"
|
||||
}
|
||||
|
||||
@@ -318,5 +318,20 @@
|
||||
"account_upgrade_dialog_tier_features_emails_one": "{{emails}} ел. писмо на ден",
|
||||
"account_upgrade_dialog_tier_features_emails_other": "{{emails}} ел. писма на ден",
|
||||
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} телефонни обаждания на ден",
|
||||
"account_usage_attachment_storage_description": "{{filesize}} на файл, изтриване след {{expiry}}"
|
||||
"account_usage_attachment_storage_description": "{{filesize}} на файл, изтриване след {{expiry}}",
|
||||
"account_upgrade_dialog_billing_contact_email": "За въпроси относно плащанията <Link>се свържете с нас</Link>.",
|
||||
"account_upgrade_dialog_tier_current_label": "Текущо",
|
||||
"account_upgrade_dialog_billing_contact_website": "За въпроси относно плащанията се обърнете към <Link>страницата</Link>.",
|
||||
"account_upgrade_dialog_button_cancel_subscription": "Прекратяване на абонамент",
|
||||
"account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} на файл",
|
||||
"account_upgrade_dialog_reservations_warning_one": "Избраното ниво разрешава по-малко резервирани теми, от колкото текущото. Преди промяна на нивото <strong>изтрийте най-малко една резервирана тема</strong>. Можете да премахвате теми в <Link>Настройки</Link>.",
|
||||
"account_tokens_title": "Кодове за достъп",
|
||||
"account_upgrade_dialog_tier_price_billed_monthly": "{{price}} на година. Плаща се всеки месец.",
|
||||
"account_upgrade_dialog_tier_price_billed_yearly": "{{price}} плащане на година. Спестявате {{save}}.",
|
||||
"account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} общ обем",
|
||||
"account_upgrade_dialog_tier_price_per_month": "на месец",
|
||||
"account_upgrade_dialog_button_pay_now": "Плащане и абониране",
|
||||
"account_upgrade_dialog_tier_selected_label": "Избрано",
|
||||
"account_upgrade_dialog_button_update_subscription": "Премяна на абонамент",
|
||||
"account_upgrade_dialog_reservations_warning_other": "Избраното ниво разрешава по-малко резервирани теми, от колкото текущото. Преди промяна на нивото <strong>изтрийте най-малко {{count}} резервирани теми</strong>. Можете да премахвате теми в <Link>Настройки</Link>."
|
||||
}
|
||||
|
||||
@@ -39,5 +39,10 @@
|
||||
"publish_dialog_attach_placeholder": "Atodi ffeil drwy URL, e.e. https://f-droid.org/F-Droid.apk",
|
||||
"notifications_click_copy_url_button": "Copio linc",
|
||||
"notifications_actions_open_url_title": "Ewch i {{url}}",
|
||||
"publish_dialog_email_label": "Ebost"
|
||||
"publish_dialog_email_label": "Ebost",
|
||||
"signup_form_confirm_password": "Cadarnhau cyfrinair",
|
||||
"signup_form_button_submit": "Cofrestru",
|
||||
"common_back": "Yn ôl",
|
||||
"common_copy_to_clipboard": "Copio i'r clipfwrdd",
|
||||
"signup_already_have_account": "Gyda chyfrif yn barod? Mewngofnodi!"
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"notifications_click_copy_url_title": "Link-URL in Zwischenablage kopieren",
|
||||
"publish_dialog_priority_low": "Niedrige Priorität",
|
||||
"publish_dialog_message_label": "Nachricht",
|
||||
"action_bar_unsubscribe": "Von Thema abmelden",
|
||||
"action_bar_unsubscribe": "Abmelden",
|
||||
"notifications_copied_to_clipboard": "In Zwischenablage kopiert",
|
||||
"notifications_loading": "Benachrichtigungen werden geladen …",
|
||||
"notifications_attachment_open_title": "Gehe zu {{url}}",
|
||||
@@ -154,7 +154,7 @@
|
||||
"notifications_actions_not_supported": "Diese Aktion wird in der Web-App nicht unterstützt",
|
||||
"notifications_actions_http_request_title": "Sende HTTP {{method}} an {{url}}",
|
||||
"action_bar_show_menu": "Menü anzeigen",
|
||||
"action_bar_toggle_mute": "Stummschaltung der Benachrichtigungen an/aus",
|
||||
"action_bar_toggle_mute": "Stummschaltung an/aus",
|
||||
"message_bar_show_dialog": "Dialog zur Veröffentlichung anzeigen",
|
||||
"message_bar_publish": "Benachrichtigung veröffentlichen",
|
||||
"nav_button_connecting": "verbinde",
|
||||
|
||||
368
web/public/static/langs/fi.json
Normal file
368
web/public/static/langs/fi.json
Normal file
@@ -0,0 +1,368 @@
|
||||
{
|
||||
"publish_dialog_message_placeholder": "Kirjoita viesti tähän",
|
||||
"account_upgrade_dialog_tier_features_no_calls": "Ei puheluita",
|
||||
"account_upgrade_dialog_billing_contact_email": "Laskutukseen liittyvissä kysymyksissä <Link>contact us</Link> suoraan.",
|
||||
"account_tokens_dialog_title_create": "Luo käyttöoikeustunnus",
|
||||
"prefs_reservations_dialog_title_edit": "Muokkaa varattua topikkia",
|
||||
"account_basics_tier_interval_monthly": "Kuukausittain",
|
||||
"publish_dialog_checkbox_publish_another": "Julkaise toinen",
|
||||
"publish_dialog_details_examples_description": "Katso esimerkkejä ja yksityiskohtaisen kuvauksen kaikista lähetysominaisuuksista <docsLink>dokumentaatiosta</docsLink>.",
|
||||
"account_basics_tier_canceled_subscription": "Tilauksesi peruutettiin ja se muutetaan maksuttomaksi tiliksi {{date}}.",
|
||||
"priority_default": "oletus",
|
||||
"prefs_notifications_min_priority_title": "Vähimmäisprioriteetti",
|
||||
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} päivittäisiä puheluja",
|
||||
"account_upgrade_dialog_tier_current_label": "Nykyinen",
|
||||
"action_bar_account": "Kirjautuminen",
|
||||
"publish_dialog_filename_placeholder": "Liitetiedoston nimi",
|
||||
"account_basics_password_dialog_current_password_incorrect": "Salasana virheellinen",
|
||||
"account_tokens_table_token_header": "Token",
|
||||
"prefs_notifications_delete_after_never": "Ei koskaan",
|
||||
"prefs_users_description": "Lisää/poista käyttäjiä suojatuista topikeista täällä. Huomaa, että käyttäjätunnus ja salasana on tallennettu selaimen paikalliseen tallennustilaan.",
|
||||
"account_basics_phone_numbers_dialog_number_label": "Puhelinnumero",
|
||||
"subscribe_dialog_subscribe_description": "Aiheet eivät välttämättä ole salasanasuojattuja, joten valitse nimi, jota ei ole helposti arvatavissa. Kun olet tilannut, voit käyttää PUT/POST ilmoituksia.",
|
||||
"action_bar_logo_alt": "ntfy logo",
|
||||
"account_basics_password_dialog_button_submit": "Vaihda salasana",
|
||||
"publish_dialog_emoji_picker_show": "Valitse emoji",
|
||||
"account_basics_username_title": "Käyttäjätunnus",
|
||||
"login_disabled": "Kirjautuminen poissa käytöstä",
|
||||
"account_basics_phone_numbers_dialog_check_verification_button": "Vahvista koodi",
|
||||
"account_upgrade_dialog_interval_yearly_discount_save_up_to": "säästä jopa {{discount}}%",
|
||||
"account_tokens_dialog_label": "Etiketti, esim. Tutka-ilmoitukset",
|
||||
"common_add": "Lisää",
|
||||
"account_tokens_table_expires_header": "Vanhenee",
|
||||
"account_upgrade_dialog_proration_info": "<strong>Osuus suhde</strong>: Kun päivität maksullisten pakettien välillä, hintaero <strong>veloitetaan välittömästi</strong>. Kun siirryt alemmalle tasolle, saldoa käytetään tulevien laskutuskausien maksamiseen.",
|
||||
"prefs_reservations_dialog_access_label": "Oikeudet",
|
||||
"account_usage_attachment_storage_title": "Liiteiden säilytys",
|
||||
"prefs_users_dialog_username_label": "Username, esim pena",
|
||||
"message_bar_error_publishing": "Virhe ilmoituksen julkaisemisessa",
|
||||
"publish_dialog_chip_delay_label": "Viivästytä toimitusta",
|
||||
"account_usage_messages_title": "Julkaistut viestit",
|
||||
"notifications_attachment_open_button": "Avaa liite",
|
||||
"emoji_picker_search_clear": "Tyhjennä haku",
|
||||
"prefs_reservations_table_not_subscribed": "Ei tilattu",
|
||||
"publish_dialog_topic_placeholder": "Topikin nimi, esim. erkin_hälyt",
|
||||
"account_upgrade_dialog_tier_features_emails_other": "{{emails}} päivittäisiä emaileja",
|
||||
"prefs_notifications_min_priority_max_only": "Vain maksimi prioriteetti",
|
||||
"account_upgrade_dialog_tier_features_calls_other": "{{calls}} päivittäisiä puheluja",
|
||||
"prefs_notifications_sound_description_some": "Ilmoitukset soittavat {{sound}} äänen saapuessaan",
|
||||
"prefs_reservations_edit_button": "Muokkaa topikin oikeuksia",
|
||||
"account_basics_phone_numbers_dialog_verify_button_sms": "Lähetä SMS",
|
||||
"account_basics_tier_change_button": "Vaihda",
|
||||
"account_tokens_dialog_expires_never": "Käyttöoikeus ei vanhene koskaan",
|
||||
"subscribe_dialog_login_title": "Kirjautuminen vaaditaan",
|
||||
"account_tokens_dialog_expires_x_days": "Tunnus vanhenee {{days}} päivän kuluttua",
|
||||
"notifications_new_indicator": "Uusi ilmoitus",
|
||||
"prefs_reservations_table_everyone_read_only": "Minä voin julkaista ja tilata, kaikki voivat tilata",
|
||||
"prefs_reservations_table_everyone_deny_all": "Vain minä voin julkaista ja tilata",
|
||||
"publish_dialog_chip_topic_label": "Vaihda topikkia",
|
||||
"account_basics_phone_numbers_dialog_description": "Jotta voit käyttää puheluilmoitusominaisuutta, sinun on lisättävä ja vahvistettava vähintään yksi puhelinnumero. Vahvistus voidaan tehdä tekstiviestillä tai puhelimitse.",
|
||||
"account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} varatut topikit",
|
||||
"publish_dialog_tags_placeholder": "Pilkuilla eroteltu luettelo tunnisteista, esim. varoitus, srv1-varmuuskopio",
|
||||
"account_delete_title": "Poista tili",
|
||||
"publish_dialog_attached_file_remove": "Poista liitetiedosto",
|
||||
"nav_button_connecting": "yhdistää",
|
||||
"account_delete_dialog_label": "Salasana",
|
||||
"subscribe_dialog_login_button_login": "Kirjaudu",
|
||||
"account_upgrade_dialog_tier_features_no_reservations": "Ei varattuja topikkeja",
|
||||
"message_bar_type_message": "Kirjoita viesti tähän",
|
||||
"publish_dialog_base_url_label": "Palvelun URL",
|
||||
"signup_form_confirm_password": "Vahvista salasana",
|
||||
"prefs_users_table_cannot_delete_or_edit": "Kirjautunutta käyttäjää ei voi poistaa tai muokata",
|
||||
"account_basics_tier_admin_suffix_with_tier": "(mukana {{tier}} tier)",
|
||||
"prefs_notifications_delete_after_three_hours_description": "Ilmoitukset poistetaan automaattisesti kolmen tunnin kuluttua",
|
||||
"publish_dialog_chip_email_label": "Lähetä sähköpostiin",
|
||||
"publish_dialog_attach_label": "Liitteen URL-osoite",
|
||||
"signup_form_username": "Käyttäjätunnus",
|
||||
"prefs_notifications_delete_after_three_hours": "Kolmen tunnin jälkeen",
|
||||
"nav_button_muted": "Ilmoitukset mykistetty",
|
||||
"action_bar_profile_settings": "Asetukset",
|
||||
"signup_error_creation_limit_reached": "Tilin lisäämisraja saavutettu",
|
||||
"notifications_attachment_open_title": "Siirry osoitteeseen {{url}}",
|
||||
"prefs_notifications_min_priority_description_x_or_higher": "Näytä ilmoitukset, jos prioriteetti on {{number}} ({{name}}) tai suurempi",
|
||||
"reservation_delete_dialog_description": "Varauksen poistaminen luopuu topikin omistajuudesta ja antaa muiden varata sen. Voit säilyttää tai poistaa olemassa olevia viestejä ja liitteitä.",
|
||||
"subscribe_dialog_login_username_label": "Käyttäjätunnus, esim. pentti",
|
||||
"subscribe_dialog_error_user_not_authorized": "Käyttäjää {{username}} ei ole valtuutettu",
|
||||
"prefs_reservations_table_everyone_read_write": "Jokainen voi julkaista ja tilata",
|
||||
"prefs_reservations_dialog_title_delete": "Poista topikin varaus",
|
||||
"prefs_users_table": "Käyttäjä taulukko",
|
||||
"prefs_reservations_table_topic_header": "Topikki",
|
||||
"action_bar_toggle_mute": "Hiljennä/poista hiljennys",
|
||||
"reservation_delete_dialog_submit_button": "Poista varaus",
|
||||
"account_basics_title": "Tili",
|
||||
"nav_button_documentation": "Käyttäjä oppaat",
|
||||
"prefs_reservations_limit_reached": "Olet saavuttanut varattujen topikkien rajan.",
|
||||
"account_upgrade_dialog_interval_monthly": "Kuukausittain",
|
||||
"prefs_users_add_button": "Lisää käyttäjä",
|
||||
"account_upgrade_dialog_tier_features_messages_other": "{{messages}} päivittäisiä viestejä",
|
||||
"publish_dialog_delay_reset": "Poista viivästetty toimitus",
|
||||
"account_basics_phone_numbers_no_phone_numbers_yet": "Ei puhelinnumeroita vielä",
|
||||
"action_bar_toggle_action_menu": "Avaa/sulje toiminto valikko",
|
||||
"subscribe_dialog_subscribe_button_generate_topic_name": "Luo nimi",
|
||||
"notifications_list_item": "Ilmoitus",
|
||||
"prefs_appearance_language_title": "Kieli",
|
||||
"notifications_attachment_link_expired": "latauslinkki vanhentunut",
|
||||
"subscribe_dialog_login_password_label": "Salasana",
|
||||
"prefs_notifications_delete_after_one_day_description": "Ilmoitukset poistetaan automaattisesti yhden päivän kuluttua",
|
||||
"subscribe_dialog_subscribe_button_subscribe": "Tilaa",
|
||||
"account_tokens_table_never_expires": "Ei vanhene koskaan",
|
||||
"account_tokens_delete_dialog_title": "Poista käyttöoikeustunnus",
|
||||
"prefs_notifications_delete_after_one_month": "Kuukauden kuluttua",
|
||||
"publish_dialog_chip_call_label": "Puhelu",
|
||||
"account_basics_phone_numbers_dialog_title": "Lisää puhelinnumero",
|
||||
"account_tokens_delete_dialog_description": "Ennen kuin poistat käyttöoikeustunnuksen, varmista, että mikään sovellus tai komentosarja ei käytä sitä aktiivisesti. <strong>Tätä toimintoa ei voi kumota</strong>.",
|
||||
"nav_button_all_notifications": "Kaikki ilmoitukset",
|
||||
"account_upgrade_dialog_button_cancel": "Peruuta",
|
||||
"notifications_attachment_image": "Liitekuva",
|
||||
"account_tokens_table_label_header": "Merkki",
|
||||
"notifications_attachment_file_document": "muu asiakirja",
|
||||
"publish_dialog_button_cancel": "Peruuta",
|
||||
"account_upgrade_dialog_billing_contact_website": "Laskutukseen liittyvissä kysymyksissä käy <Link>website</Link>.",
|
||||
"signup_form_button_submit": "Kirjaudu linkki",
|
||||
"account_basics_username_admin_tooltip": "Olet pääkäyttäjä",
|
||||
"prefs_notifications_delete_after_never_description": "Ilmoituksia eivät koskaan poisteta automaattisesti",
|
||||
"account_delete_dialog_description": "Tämä poistaa pysyvästi tilisi, mukaan lukien kaikki palvelimelle tallennetut tiedot. Poistamisen jälkeen käyttäjätunnuksesi on poissa käytöstä 7 päivään. Jos todella haluat jatkaa, vahvista salasanasi alla olevaan kenttään.",
|
||||
"publish_dialog_email_reset": "Poista sähköpostin edelleenlähetys",
|
||||
"account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} varatut topikit",
|
||||
"account_usage_reservations_none": "Tälle tilille ei ole varattu topikkeja",
|
||||
"prefs_notifications_sound_description_none": "Ilmoitukset eivät toista ääntä saapuessaan",
|
||||
"account_tokens_description": "Käytä käyttjätunnuksia, kun julkaiset ja tilaat ntfy API:n kautta, jotta sinun ei tarvitse lähettää tilisi tunnistetietoja. Katso lisätietoja <Link>documentation</Link>.",
|
||||
"common_back": "Takaisin",
|
||||
"prefs_reservations_table": "Varattujen topikkien taulukko",
|
||||
"emoji_picker_search_placeholder": "Etsi emoji",
|
||||
"subscribe_dialog_subscribe_topic_placeholder": "Topikin nimi, esim. pentin_hälyt",
|
||||
"account_upgrade_dialog_button_cancel_subscription": "Peruuta tilaus",
|
||||
"notifications_attachment_file_audio": "äänitiedosto",
|
||||
"account_upgrade_dialog_tier_features_emails_one": "{{emails}} päivittäisiä emaileja",
|
||||
"action_bar_sign_up": "Kirjautuminen",
|
||||
"account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} tiedostokoko",
|
||||
"notifications_mark_read": "Merkitse luetuksi",
|
||||
"prefs_reservations_description": "Voit varata topikien nimiä henkilökohtaiseen käyttöön täältä. Aiheen varaaminen antaa sinulle topikin omistajuuden ja voit määrittää topikkiin liittyviä käyttöoikeuksia muille käyttäjille.",
|
||||
"notifications_attachment_copy_url_title": "Kopioi liitteen URL-osoite leikepöydälle",
|
||||
"account_usage_title": "Käytössä",
|
||||
"account_basics_tier_upgrade_button": "Päivitä Pro versioon",
|
||||
"prefs_users_description_no_sync": "Käyttäjiä ja salasanoja ei ole synkronoitu tiliisi.",
|
||||
"account_tokens_dialog_title_edit": "Muokkaa käyttöoikeustunnusta",
|
||||
"nav_button_publish_message": "Julkaisutiedot",
|
||||
"prefs_users_table_base_url_header": "Palvelin URL",
|
||||
"notifications_click_copy_url_title": "Kopioi linkin URL-osoite leikepöydälle",
|
||||
"publish_dialog_attach_reset": "Poista liitteen URL-osoite",
|
||||
"account_upgrade_dialog_tier_features_messages_one": "{{messages}} päivittäisiä viestejä",
|
||||
"account_upgrade_dialog_reservations_warning_one": "Valittu taso sallii vähemmän varattuja topikeita kuin nykyinen tasosi. Ennen kuin muutat tasosi, <strong>poista vähintään yksi varaus</strong>. Voit poistaa varauksia <Link>Asetuksista</Link>.",
|
||||
"common_copy_to_clipboard": "Kopioi leikkelepöydälle",
|
||||
"alert_not_supported_description": "Selaimesi ei tue ilmoituksia.",
|
||||
"subscribe_dialog_error_topic_already_reserved": "Topikki on jo varattu",
|
||||
"message_bar_publish": "Julkaise viesti",
|
||||
"alert_grant_description": "Myönnä selaimelle lupa näyttää työpöytäilmoituksia.",
|
||||
"prefs_users_table_user_header": "Käyttäjä",
|
||||
"error_boundary_stack_trace": "Pinon jälki",
|
||||
"prefs_users_dialog_password_label": "Salasana",
|
||||
"prefs_notifications_delete_after_one_week": "Viikon kuluttua",
|
||||
"publish_dialog_priority_low": "Matala tärkeys",
|
||||
"publish_dialog_priority_label": "Prioriteetti",
|
||||
"prefs_reservations_delete_button": "Poista topikin oikeudet",
|
||||
"account_basics_tier_admin_suffix_no_tier": "(no tier)",
|
||||
"prefs_notifications_delete_after_one_week_description": "Ilmoitukset poistetaan automaattisesti viikon kuluttua",
|
||||
"error_boundary_unsupported_indexeddb_description": "Ntfy-verkkosovellus tarvitsee IndexedDB:n toimiakseen, eikä selaimesi tue IndexedDB:tä yksityisessä selaustilassa.<br/><br/>Vaikka tämä on valitettavaa, ntfy-verkon käyttäminen ei myöskään ole kovin järkevää yksityisessä selaustilassa, koska kaikki on tallennettu selaimen tallennustilaan. Voit lukea siitä lisää <githubLink>tästä GitHub-numerosta</githubLink> tai puhua meille <discordLink>Discordissa</discordLink> tai <matrixLink>Matrixissa</matrixLink>.",
|
||||
"subscribe_dialog_subscribe_button_cancel": "Peruuta",
|
||||
"notifications_attachment_copy_url_button": "Kopioi URL",
|
||||
"account_basics_tier_payment_overdue": "Maksusi on myöhässä. Päivitä maksutapasi, tai tilisi poistetaan pian.",
|
||||
"publish_dialog_title_placeholder": "Ilmoituksen otsikko, esim. Levytilan hälytys",
|
||||
"account_basics_tier_description": "Tilisi taso",
|
||||
"account_basics_phone_numbers_description": "Puheluilmoituksia varten",
|
||||
"prefs_reservations_dialog_title_add": "Varaa topikki",
|
||||
"account_basics_tier_free": "Vapaa",
|
||||
"account_upgrade_dialog_cancel_warning": "Tämä <strong>peruuttaa tilauksesi</strong> ja alentaa tilisi {{date}}. Tuona päivänä topikit sekä palvelimen välimuistissa olevat viestit <strong>poistetaan</strong>.",
|
||||
"notifications_click_copy_url_button": "Kopioi linkki",
|
||||
"account_basics_tier_admin": "Admin",
|
||||
"subscribe_dialog_subscribe_title": "Tilaa topikki",
|
||||
"nav_topics_title": "Tilatut topikit",
|
||||
"prefs_notifications_sound_title": "Ilmoitusääni",
|
||||
"prefs_notifications_min_priority_default_and_higher": "Oletusprioriteetti ja korkeammat",
|
||||
"prefs_reservations_table_access_header": "Oikeudet",
|
||||
"action_bar_show_menu": "Näytä menu",
|
||||
"action_bar_settings": "Asetukset",
|
||||
"notifications_copied_to_clipboard": "Kopioitu leikepöydälle",
|
||||
"account_delete_dialog_button_cancel": "Peruuta",
|
||||
"publish_dialog_delay_placeholder": "Toimituksen viivästyminen, esim. {{unixTimestamp}}, {{relativeTime}} tai \"{{naturalLanguage}}\" (vain englanti)",
|
||||
"account_tokens_table_copied_to_clipboard": "Käyttöoikeustunnus kopioitu",
|
||||
"alert_grant_title": "Ilmoitukset on poistettu käytöstä",
|
||||
"account_tokens_dialog_expires_x_hours": "Tunnus vanhenee {{hours}} tunnin kuluttua",
|
||||
"prefs_users_edit_button": "Muokkaa käyttäjää",
|
||||
"account_upgrade_dialog_title": "Muuta tilitasoa",
|
||||
"publish_dialog_chip_call_no_verified_numbers_tooltip": "Ei vahvistettuja puhelinnumeroita",
|
||||
"priority_low": "matala",
|
||||
"prefs_reservations_table_click_to_subscribe": "Tilaa napsauttamalla",
|
||||
"account_basics_password_description": "Vaihda tilisi salasana",
|
||||
"publish_dialog_call_label": "Puhelu",
|
||||
"account_usage_calls_title": "Soitetut puhelut",
|
||||
"error_boundary_description": "Näin ei selvästikään pitäisi tapahtua. Pahoittelut tästä.<br/>Jos sinulla on hetki aikaa, <githubLink>ilmoita tästä GitHubissa</githubLink> tai ilmoita meille <discordLink>Discordin</discordLink> tai <matrixLink>Matrix</matrixLink> kautta.",
|
||||
"signup_form_toggle_password_visibility": "Vaihda salasanan näkyvyys",
|
||||
"login_link_signup": "Kirjaudu linkki",
|
||||
"publish_dialog_message_label": "Viesti",
|
||||
"publish_dialog_attached_file_title": "Liitetiedosto:",
|
||||
"priority_min": "min",
|
||||
"action_bar_sign_in": "Kirjaudu sisään",
|
||||
"action_bar_unsubscribe": "Peruuta tilaus",
|
||||
"account_basics_tier_basic": "Perus",
|
||||
"signup_title": "Lisää ntfy tili",
|
||||
"prefs_notifications_min_priority_description_any": "Näytetään kaikki ilmoitukset tärkeydestä riippumatta",
|
||||
"error_boundary_gathering_info": "Kerää lisätietoja…",
|
||||
"publish_dialog_priority_max": "Max. prioriteetti",
|
||||
"error_boundary_unsupported_indexeddb_title": "Yksityistä selaamista ei tueta",
|
||||
"prefs_notifications_delete_after_one_day": "Yhden päivän jälkeen",
|
||||
"error_boundary_title": "Voi ei, ntfy kaatui",
|
||||
"action_bar_change_display_name": "Näyttönimen vaihtaminen",
|
||||
"notifications_attachment_file_app": "Android-sovellustiedosto",
|
||||
"alert_not_supported_context_description": "Ilmoituksia tuetaan vain HTTPS:n kautta. Tämä on <mdnLink>Ilmoitussovellusliittymän</mdnLink> rajoitus.",
|
||||
"reservation_delete_dialog_action_keep_description": "Palvelimelle välimuistiin tallennetut viestit ja liitteet tulevat julkiseksi topikin nimen tietävälle henkilölle.",
|
||||
"prefs_reservations_add_button": "Lisää varattu topik",
|
||||
"prefs_reservations_title": "Varatut topikit",
|
||||
"account_basics_phone_numbers_copied_to_clipboard": "Puhelinnumero kopioitu leikepöydälle",
|
||||
"prefs_reservations_dialog_description": "Topikin varaaminen antaa sinulle aiheen omistajuuden ja voit määrittää aiheeseen liittyviä käyttöoikeuksia muille käyttäjille.",
|
||||
"account_basics_tier_title": "Tilin tyyppi",
|
||||
"account_usage_cannot_create_portal_session": "Laskutusportaalin avaaminen epäonnistui",
|
||||
"account_tokens_delete_dialog_submit_button": "Poista tunnus pysyvästi",
|
||||
"account_delete_description": "Poista tilisi pysyvästi",
|
||||
"account_basics_phone_numbers_dialog_number_placeholder": "esim. +35812345678",
|
||||
"account_basics_phone_numbers_dialog_code_placeholder": "esim. 123456",
|
||||
"prefs_notifications_title": "Ilmoitukset",
|
||||
"account_basics_tier_manage_billing_button": "Hallinnoi laskutusta",
|
||||
"account_tokens_title": "Käyttöoikeudet",
|
||||
"publish_dialog_email_label": "Email",
|
||||
"account_basics_username_description": "Hei, se olet sinä ❤",
|
||||
"prefs_reservations_dialog_topic_label": "Topik",
|
||||
"account_basics_password_dialog_confirm_password_label": "Vahvista salasana",
|
||||
"action_bar_reservation_edit": "Muokkaa varatopikkia",
|
||||
"publish_dialog_base_url_placeholder": "Palvelun URL-osoite, esim. https://example.com",
|
||||
"prefs_users_title": "Hallinnoi käyttäjiä",
|
||||
"account_basics_tier_interval_yearly": "vuosittain",
|
||||
"account_upgrade_dialog_tier_price_billed_monthly": "{{price}} Laskutetaan kuukausittain.",
|
||||
"action_bar_clear_notifications": "Poista kaikki ilmoitukset",
|
||||
"account_delete_dialog_button_submit": "Poista tili pysyvästi",
|
||||
"account_basics_phone_numbers_dialog_channel_call": "Soitto",
|
||||
"account_basics_password_title": "Salasana",
|
||||
"account_basics_password_dialog_new_password_label": "Uusi salasana",
|
||||
"nav_upgrade_banner_label": "Päivitä ntfy Prohon",
|
||||
"account_tokens_dialog_expires_unchanged": "Jätä viimeinen käyttöpäivä ennalleen",
|
||||
"publish_dialog_delay_label": "Viive",
|
||||
"error_boundary_button_copy_stack_trace": "Kopioi pinon jälki",
|
||||
"publish_dialog_button_send": "Lähetä",
|
||||
"action_bar_reservation_delete": "Poista varatopikit",
|
||||
"publish_dialog_button_cancel_sending": "Peruuta lähetys",
|
||||
"account_tokens_dialog_title_delete": "Poista käyttöoikeustunnus",
|
||||
"account_usage_of_limit": "limiitistä {{limit}}",
|
||||
"publish_dialog_attach_placeholder": "Liitä tiedosto URL-osoitteen mukaan, esim. https://f-droid.org/F-Droid.apk",
|
||||
"publish_dialog_email_placeholder": "Osoite, johon ilmoitus välitetään, esim. urpo@example.com",
|
||||
"notifications_attachment_link_expires": "linkki vanhenee {{date}}",
|
||||
"action_bar_send_test_notification": "Lähetä testi ilmoitus",
|
||||
"reservation_delete_dialog_action_keep_title": "Säilytä välimuistissa olevat viestit ja liitteet",
|
||||
"prefs_notifications_sound_no_sound": "Ei ääntä",
|
||||
"account_upgrade_dialog_interval_yearly": "Vuosittain",
|
||||
"publish_dialog_tags_label": "Tagit",
|
||||
"signup_form_password": "Salasana",
|
||||
"action_bar_reservation_limit_reached": "Varatopikien raja",
|
||||
"account_upgrade_dialog_button_redirect_signup": "Kirjaudu nyt",
|
||||
"publish_dialog_click_placeholder": "URL-osoite, joka avautuu, kun ilmoitusta napsautetaan",
|
||||
"alert_not_supported_title": "Ilmoituksia ei tueta",
|
||||
"account_tokens_dialog_button_cancel": "Peruuta",
|
||||
"subscribe_dialog_error_user_anonymous": "Anonyymi",
|
||||
"account_upgrade_dialog_tier_price_billed_yearly": "{{price}} laskutetaan vuosittain. Tallenna {{save}}.",
|
||||
"prefs_notifications_min_priority_high_and_higher": "Korkea prioriteetti ja korkeammat",
|
||||
"account_usage_basis_ip_description": "Tämän tilin käyttötilastot ja rajoitukset perustuvat IP-osoitteeseesi, joten ne voidaan jakaa muiden käyttäjien kanssa. Yllä esitetyt rajat ovat likimääräisiä perustuen olemassa oleviin rajoituksiin.",
|
||||
"publish_dialog_priority_high": "Korkea prioriteetti",
|
||||
"login_form_button_submit": "Kirjaudu",
|
||||
"account_basics_password_dialog_title": "Vaihda salasana",
|
||||
"priority_max": "max",
|
||||
"notifications_attachment_file_image": "kuvatiedosto",
|
||||
"account_usage_limits_reset_daily": "Käyttörajat nollataan päivittäin keskiyöllä (UTC)",
|
||||
"account_usage_unlimited": "Rajoittamaton",
|
||||
"prefs_users_delete_button": "Poista käyttäjä",
|
||||
"publish_dialog_click_label": "Napsauta URL-osoitetta",
|
||||
"prefs_notifications_min_priority_any": "Kaikki prioriteetit",
|
||||
"account_tokens_dialog_expires_label": "Käyttöoikeustunnus vanhenee",
|
||||
"publish_dialog_filename_label": "Tiedostonimi",
|
||||
"publish_dialog_chip_attach_file_label": "Liitä paikallinen tiedosto",
|
||||
"account_basics_phone_numbers_title": "Puhelinnumerot",
|
||||
"prefs_notifications_delete_after_title": "Poista ilmoitukset",
|
||||
"account_upgrade_dialog_interval_yearly_discount_save": "säästä {{discount}}%",
|
||||
"signup_disabled": "Kirjautuminen estetty",
|
||||
"publish_dialog_drop_file_here": "Pudota tiedosto tähän",
|
||||
"prefs_users_dialog_title_edit": "Muokkaa käyttäjää",
|
||||
"account_basics_password_dialog_current_password_label": "Nykyinen salasana",
|
||||
"prefs_notifications_min_priority_low_and_higher": "Matala prioriteetti ja korkeammat",
|
||||
"action_bar_profile_title": "Profiili",
|
||||
"account_tokens_dialog_button_update": "Päivitä tunnus",
|
||||
"account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} lopullinen tiedostokoko",
|
||||
"publish_dialog_title_label": "Otsikko",
|
||||
"prefs_reservations_table_everyone_write_only": "Minä voin julkaista ja tilata, kaikki voivat julkaista",
|
||||
"prefs_appearance_title": "Näkymä",
|
||||
"publish_dialog_topic_reset": "Resetoi topikki",
|
||||
"account_tokens_table_cannot_delete_or_edit": "Nykyistä istuntotunnusta ei voi muokata tai poistaa",
|
||||
"notifications_tags": "Tagit",
|
||||
"prefs_notifications_sound_play": "Toista valittu ääni",
|
||||
"account_tokens_table_last_access_header": "Viimeinen käyty",
|
||||
"action_bar_profile_logout": "Kirjaudu ulos",
|
||||
"publish_dialog_attached_file_filename_placeholder": "Liitetiedoston nimi",
|
||||
"publish_dialog_priority_default": "Oletusprioriteetti",
|
||||
"subscribe_dialog_subscribe_base_url_label": "Palvelimen URL",
|
||||
"account_tokens_table_last_origin_tooltip": "Napsauta IP-osoitteesta {{ip}}, etsiäksesi",
|
||||
"account_usage_reservations_title": "Varatut topikit",
|
||||
"account_upgrade_dialog_tier_price_per_month": "Kuukausi",
|
||||
"message_bar_show_dialog": "Näytä julkaisu dialogi",
|
||||
"publish_dialog_chip_attach_url_label": "Liitä tiedosto URL-osoitteen mukaan",
|
||||
"account_usage_calls_none": "Tällä tilillä ei voi soittaa puheluita",
|
||||
"notifications_click_open_button": "Avaa linkki",
|
||||
"account_tokens_table_current_session": "Nykyinen selainistunto",
|
||||
"account_upgrade_dialog_button_pay_now": "Maksa nyt ja tilaa",
|
||||
"nav_upgrade_banner_description": "Varaa aiheita, lisää viestejä ja sähköposteja sekä suurempia liitteitä",
|
||||
"publish_dialog_call_reset": "Poista puhelu",
|
||||
"publish_dialog_other_features": "Muut ominaisuudet:",
|
||||
"subscribe_dialog_subscribe_use_another_label": "Käytä toista palvelinta",
|
||||
"reservation_delete_dialog_action_delete_title": "Poista välimuistissa olevat viestit ja liitteet",
|
||||
"signup_error_username_taken": "Käyttäjätunnus {{username}} on jo varattu",
|
||||
"account_basics_phone_numbers_dialog_code_label": "Vahvistuskoodi",
|
||||
"nav_button_subscribe": "Tilaa topik",
|
||||
"publish_dialog_topic_label": "Topikin nimi",
|
||||
"reservation_delete_dialog_action_delete_description": "Välimuistissa olevat viestit ja liitteet poistetaan pysyvästi. Tätä toimintoa ei voi kumota.",
|
||||
"alert_grant_button": "Myönnä nyt",
|
||||
"account_basics_tier_paid_until": "Tilaus maksettu {{date}} asti, ja se uusitaan automaattisesti",
|
||||
"account_usage_attachment_storage_description": "{{tiedostokoko}} per tiedosto, poistettu {{expiry}} jälkeen",
|
||||
"publish_dialog_chip_click_label": "Napsauta URL-osoitetta",
|
||||
"prefs_notifications_delete_after_one_month_description": "Ilmoitukset poistetaan automaattisesti kuukauden kuluttua",
|
||||
"common_cancel": "Peruuta",
|
||||
"account_basics_phone_numbers_dialog_verify_button_call": "Soita minulle",
|
||||
"signup_already_have_account": "Onko sinulla jo tili ? Kirjaudu sisään !",
|
||||
"publish_dialog_call_item": "Soita puhelinnumeroon {{number}}",
|
||||
"nav_button_account": "Tili",
|
||||
"publish_dialog_click_reset": "Poista napsautettava URL-osoite",
|
||||
"login_title": "Kirjaudu sisään ntfy-tilillesi",
|
||||
"notifications_list": "Ilmoitusluettelo",
|
||||
"common_save": "Tallenna",
|
||||
"prefs_users_dialog_base_url_label": "Palvelin URL, esim. https://ntfy.sh",
|
||||
"account_usage_emails_title": "Sähköpostit lähetetty",
|
||||
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
|
||||
"action_bar_reservation_add": "Varalla oleva topikki",
|
||||
"account_upgrade_dialog_tier_selected_label": "Valittu",
|
||||
"account_upgrade_dialog_button_update_subscription": "Päivitä tilaus",
|
||||
"notifications_attachment_file_video": "videotiedosto",
|
||||
"priority_high": "korkea",
|
||||
"notifications_priority_x": "Prioriteetti {{priority}}",
|
||||
"account_delete_dialog_billing_warning": "Tilin poistaminen peruuttaa myös laskutustilauksesi välittömästi. Et voi enää käyttää laskutuksen hallintapaneelia.",
|
||||
"prefs_notifications_min_priority_description_max": "Näytä ilmoitukset, jos prioriteetti on 5 (max)",
|
||||
"subscribe_dialog_login_description": "Tämä Topikki on suojattu salasanalla. Anna käyttäjätunnus ja salasana.",
|
||||
"account_upgrade_dialog_reservations_warning_other": "Valittu taso sallii vähemmän varattuja topikkeja kuin nykyinen tasosi. Ennen kuin muutat tasosi, <strong>poista vähintään {{count}} varausta</strong>. Voit poistaa varauksia <Link>Asetuksista</Link>.",
|
||||
"prefs_users_dialog_title_add": "Lisää käyttäjä",
|
||||
"account_tokens_dialog_button_create": "Luo tunnus",
|
||||
"nav_button_settings": "Asetukset",
|
||||
"publish_dialog_priority_min": "Min. etusijalla",
|
||||
"account_tokens_table_create_token_button": "Luo käyttöoikeustunnus",
|
||||
"notifications_delete": "Poista",
|
||||
"notifications_actions_not_supported": "Toimintoa ei tueta verkkosovelluksessa",
|
||||
"notifications_actions_open_url_title": "Siirry osoitteeseen {{url}}",
|
||||
"notifications_none_for_any_title": "Et ole saanut ilmoituksia.",
|
||||
"notifications_none_for_topic_description": "Jos haluat lähettää ilmoituksia tähän topikkiin, PUT tai POST topikin URL-osoitteeseen.",
|
||||
"notifications_none_for_any_description": "Jos haluat lähettää ilmoituksia topikkiin, PUT tai POST topikin URL-osoitteeseen. Tässä on esimerkki yhden topikin käyttämisestä.",
|
||||
"notifications_no_subscriptions_title": "Näyttää siltä, että sinulla ei ole vielä tilauksia.",
|
||||
"notifications_none_for_topic_title": "Et ole vielä saanut ilmoituksia tästä topikista.",
|
||||
"notifications_actions_http_request_title": "Lähetä HTTP {{method}} to {{url}}"
|
||||
}
|
||||
@@ -114,7 +114,7 @@
|
||||
"publish_dialog_title_topic": "Publicar en {{topic}}",
|
||||
"publish_dialog_title_no_topic": "Publicar notificación",
|
||||
"publish_dialog_progress_uploading": "Enviando…",
|
||||
"publish_dialog_progress_uploading_detail": "Enviando {{loaded}}/{{total}} ({{percent}}%) …",
|
||||
"publish_dialog_progress_uploading_detail": "Enviando {{loaded}}/{{total}} ({{percent}}%) …",
|
||||
"publish_dialog_message_published": "Notificación publicada",
|
||||
"publish_dialog_attachment_limits_file_and_quota_reached": "supera o límite de ficheiros e cota {{fileSizeLimit}}, quedan {{remainingBytes}}",
|
||||
"publish_dialog_attachment_limits_file_reached": "supera o límite para ficheiros {{fileSizeLimit}}",
|
||||
@@ -125,7 +125,7 @@
|
||||
"publish_dialog_priority_default": "Prioridade por defecto",
|
||||
"publish_dialog_priority_high": "Prioridade alta",
|
||||
"publish_dialog_priority_max": "Prioridade Máxima",
|
||||
"publish_dialog_base_url_label": "URL de servizo",
|
||||
"publish_dialog_base_url_label": "URL do servizo",
|
||||
"notifications_more_details": "Para máis información, visita o <websiteLink>sitio web</websiteLink> ou le a <docsLink>documentación</docsLink>.",
|
||||
"publish_dialog_call_label": "Chamada de teléfono",
|
||||
"publish_dialog_call_reset": "Retirar chamada de teléfono",
|
||||
@@ -160,5 +160,225 @@
|
||||
"publish_dialog_checkbox_publish_another": "Publicar outra",
|
||||
"emoji_picker_search_clear": "Limpar busca",
|
||||
"publish_dialog_chip_call_no_verified_numbers_tooltip": "Números de teléfono non verificados",
|
||||
"publish_dialog_attached_file_remove": "Retirar ficheiro anexo"
|
||||
"publish_dialog_attached_file_remove": "Retirar ficheiro anexo",
|
||||
"account_upgrade_dialog_tier_features_no_calls": "Sen chamadas",
|
||||
"account_upgrade_dialog_billing_contact_email": "Para preguntas sobre pagamentos, <Link>contacta con nós</Link> directamente.",
|
||||
"account_tokens_dialog_title_create": "Crear token de acceso",
|
||||
"prefs_reservations_dialog_title_edit": "Editar tema reservado",
|
||||
"priority_default": "por defecto",
|
||||
"prefs_notifications_min_priority_title": "Prioridade mínima",
|
||||
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} chamadas de teléfono diarias",
|
||||
"account_upgrade_dialog_tier_current_label": "Actual",
|
||||
"account_tokens_table_token_header": "Token",
|
||||
"prefs_notifications_delete_after_never": "Nunca",
|
||||
"prefs_users_description": "Engadir/eliminar usuarias dos temas protexidos. Ten en conta que as credenciais gárdanse na almacenaxe local do navegador.",
|
||||
"subscribe_dialog_subscribe_description": "Os temas poderían non estar proxetidos con contrasinal, así que elixe un nome complicado de adiviñar. Unha vez subscrita, podes PUT/POST notificacións.",
|
||||
"account_upgrade_dialog_interval_yearly_discount_save_up_to": "aforro ata un {{discount}}%",
|
||||
"account_tokens_dialog_label": "Etiqueta, ex. notificación de Radarr",
|
||||
"account_tokens_table_expires_header": "Caducidade",
|
||||
"account_upgrade_dialog_proration_info": "<strong>Axuste</strong>: ao mellorar a un plan de pagamento superior, a diferencia vaise <strong>cobrar inmediatamente</strong>. Se degradas a conta a un plan inferior a diferencia usarase para pagar futuros períodos de pagamento.",
|
||||
"prefs_reservations_dialog_access_label": "Acceso",
|
||||
"account_usage_attachment_storage_title": "Almacenaxe dos anexos",
|
||||
"prefs_users_dialog_username_label": "Identificador, ex. xoana",
|
||||
"prefs_reservations_table_not_subscribed": "Non subscrita",
|
||||
"account_upgrade_dialog_tier_features_emails_other": "{{emails}} correos diarios",
|
||||
"prefs_notifications_min_priority_max_only": "Só prioridade máxima",
|
||||
"account_upgrade_dialog_tier_features_calls_other": "{{calls}} chamadas de teléfono diarias",
|
||||
"prefs_notifications_sound_description_some": "As notificacións sonan co ton {{sound}} ao chegar",
|
||||
"prefs_reservations_edit_button": "Editar acceso ao tema",
|
||||
"account_tokens_dialog_expires_never": "O token non caduca",
|
||||
"subscribe_dialog_login_title": "Require inciar sesión",
|
||||
"account_tokens_dialog_expires_x_days": "O token caduca en {{days}} días",
|
||||
"prefs_reservations_table_everyone_read_only": "Podo publicar e subscribirme, calquera pode subscribirse",
|
||||
"prefs_reservations_table_everyone_deny_all": "Só eu podo publicar e subscribirme",
|
||||
"account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} tema reservado",
|
||||
"subscribe_dialog_login_button_login": "Acceder",
|
||||
"account_upgrade_dialog_tier_features_no_reservations": "Sen temas reservados",
|
||||
"prefs_users_table_cannot_delete_or_edit": "Non se pode eliminar ou editar unha usuaria coa sesión iniciada",
|
||||
"prefs_notifications_delete_after_three_hours_description": "As notificacións autoelimínanse após tres horas",
|
||||
"prefs_notifications_delete_after_three_hours": "Após tres horas",
|
||||
"prefs_notifications_min_priority_description_x_or_higher": "Mostrar as notificacións se a prioridade é {{number}} {{name}} ou superior",
|
||||
"reservation_delete_dialog_description": "Ao eliminar a reserva cedes a propiedade do tema, e permites que outras persoas poidan reservalo. Podes manter ou eliminar as mensaxes e anexos existentes.",
|
||||
"prefs_reservations_table_everyone_read_write": "Calquera pode publicar e subscribirse",
|
||||
"prefs_reservations_dialog_title_delete": "Eliminar a reserva do tema",
|
||||
"prefs_users_table": "Táboa de usuarias",
|
||||
"prefs_reservations_table_topic_header": "Tema",
|
||||
"reservation_delete_dialog_submit_button": "Eliminar a reserva",
|
||||
"prefs_reservations_limit_reached": "Acadaches o límite de temas que podes reservar.",
|
||||
"account_upgrade_dialog_interval_monthly": "Mensual",
|
||||
"prefs_users_add_button": "Engadir usuaria",
|
||||
"account_upgrade_dialog_tier_features_messages_other": "{{messages}} mensaxes diarias",
|
||||
"prefs_appearance_language_title": "Idioma",
|
||||
"prefs_notifications_delete_after_one_day_description": "As notificacións autoelimínanse após un día",
|
||||
"account_tokens_table_never_expires": "Non caduca",
|
||||
"account_tokens_delete_dialog_title": "Desbotar token de acceso",
|
||||
"prefs_notifications_delete_after_one_month": "Após un mes",
|
||||
"account_tokens_delete_dialog_description": "Antes de borrar o token de acceso mira que ningunha aplicación ou programa o está usando. <strong>Esta acción non pode desfacerse</strong>.",
|
||||
"account_upgrade_dialog_button_cancel": "Cancelar",
|
||||
"account_tokens_table_label_header": "Etiqueta",
|
||||
"account_upgrade_dialog_billing_contact_website": "Para preguntas sobre pagamentos, vai ao noso <Link>sitiio web</Link>.",
|
||||
"prefs_notifications_delete_after_never_description": "As notificacións non se eliminarán nunca automáticamente",
|
||||
"account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} temas reservados",
|
||||
"prefs_notifications_sound_description_none": "As notificacións non reproducen un ton ao chegar",
|
||||
"account_tokens_description": "Usar tokens de acceso ao publicar e subscribirte a través da API de ntfy, así non tes que enviar as credenciais. Le a <Link>documentación</Link> para saber máis.",
|
||||
"prefs_reservations_table": "Táboa cos temas reservados",
|
||||
"account_upgrade_dialog_button_cancel_subscription": "Cancelar subscrición",
|
||||
"account_upgrade_dialog_tier_features_emails_one": "{{emails}} correo diario",
|
||||
"account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} por ficheiro",
|
||||
"prefs_reservations_description": "Podes reservar nomes de temas para uso personal. Ao reservar un tema tes a propiedade sobre del, e permíteche definir os permisos de acceso para outras usuarias sobre o tema.",
|
||||
"prefs_users_description_no_sync": "Usuarias e contrasinais non están sincronizados coa túa conta.",
|
||||
"account_tokens_dialog_title_edit": "Editar token de acceso",
|
||||
"prefs_users_table_base_url_header": "URL do servizo",
|
||||
"account_upgrade_dialog_tier_features_messages_one": "{{mensaxes}} mensaxe diaria",
|
||||
"account_upgrade_dialog_reservations_warning_one": "O nivel seleccionado permite reservar menos temas que o nivel actual. Antes de cambiar de nivel, <strong>elimina unha reserva polo menos</strong>. Podes eliminar as reservas nos <Link>Axustes</Link>.",
|
||||
"prefs_users_table_user_header": "Usuaria",
|
||||
"error_boundary_stack_trace": "Trazas do problema",
|
||||
"prefs_users_dialog_password_label": "Contrasinal",
|
||||
"prefs_notifications_delete_after_one_week": "Após unha semana",
|
||||
"prefs_reservations_delete_button": "Restablecer acceso ao tema",
|
||||
"prefs_notifications_delete_after_one_week_description": "As notificacións autoelimínanse após unha semana",
|
||||
"error_boundary_unsupported_indexeddb_description": "A app ntfy web precisa a función IndexedDB, e o teu navegador non ten soporte para IndexedDB no modo privado.<br/><br/>Aínda que é unha mágoa, tampouco ten moito senso usar a app ntfy web en modo privado, porque todo se garda na almacenaxe do navegador. Podes aprender máis sobre isto <githubLink>neste tema de GitHub</githubLink>, ou comentarnos o que che parece en <discordLink>Discord</discordLink> ou <matrixLink>Matrix</matrixLink>.",
|
||||
"subscribe_dialog_subscribe_button_cancel": "Cancelar",
|
||||
"account_basics_tier_description": "O nivel da túa conta",
|
||||
"prefs_reservations_dialog_title_add": "Reservar tema",
|
||||
"account_upgrade_dialog_cancel_warning": "Isto vai <strong>cancelar a túa subscrición</strong>, e degradar a túa conta o {{date}}. Nesa data, as reservas de temas así como as mensaxes na caché do servidor <strong>van ser eliminadas</strong>.",
|
||||
"prefs_notifications_sound_title": "Ton da notificación",
|
||||
"prefs_notifications_min_priority_default_and_higher": "Prioridade por defecto e superior",
|
||||
"prefs_reservations_table_access_header": "Acceso",
|
||||
"account_tokens_table_copied_to_clipboard": "Copiouse o token de acceso",
|
||||
"account_tokens_dialog_expires_x_hours": "O token caduca en {{hours}} horas",
|
||||
"prefs_users_edit_button": "Editar usuaria",
|
||||
"account_upgrade_dialog_title": "Cambiar facturación da conta",
|
||||
"priority_low": "baixa",
|
||||
"prefs_reservations_table_click_to_subscribe": "Preme para subscribirte",
|
||||
"error_boundary_description": "Isto non debería pasar. Lamentámolo. <br/>Se tes un minuto, <githubLink>informa en GitHub</githubLink>, ou fáinolo saber en <discordLink>Discord</discordLink> ou <matrixLink>Matrix</matrixLink>.",
|
||||
"priority_min": "min",
|
||||
"prefs_notifications_min_priority_description_any": "Mostrar todas as notificacións, obviando a prioridade",
|
||||
"error_boundary_gathering_info": "Obter máis info…",
|
||||
"error_boundary_unsupported_indexeddb_title": "Non hai soporte para a navegación privada",
|
||||
"prefs_notifications_delete_after_one_day": "Após un día",
|
||||
"error_boundary_title": "vaite!, ntfy fallou",
|
||||
"reservation_delete_dialog_action_keep_description": "As mensaxes e anexos que están no servidor serán visibles públicamente para quen saiba o nome do tema.",
|
||||
"prefs_reservations_add_button": "Engadir tema reservado",
|
||||
"prefs_reservations_title": "Temas reservados",
|
||||
"prefs_reservations_dialog_description": "Ao reservar un tema tes a propiedade sobre el, e permíteche definir os permisos de acceso para outras usuarias.",
|
||||
"account_tokens_delete_dialog_submit_button": "Eliminar definitivamente o token",
|
||||
"prefs_notifications_title": "Notificacións",
|
||||
"account_tokens_title": "Tokens de acceso",
|
||||
"prefs_reservations_dialog_topic_label": "Tema",
|
||||
"prefs_users_title": "Xestionar usuarias",
|
||||
"account_upgrade_dialog_tier_price_billed_monthly": "{{price}} anual. Pagamento mensual.",
|
||||
"account_tokens_dialog_expires_unchanged": "Deixar a data de caducidade sen cambiar",
|
||||
"error_boundary_button_copy_stack_trace": "Copiar trazas do problema",
|
||||
"account_tokens_dialog_title_delete": "Eliminar token de acceso",
|
||||
"reservation_delete_dialog_action_keep_title": "Manter as mensaxes e anexos gardados",
|
||||
"prefs_notifications_sound_no_sound": "Sen ton",
|
||||
"account_upgrade_dialog_interval_yearly": "Anual",
|
||||
"account_upgrade_dialog_button_redirect_signup": "Crea unha conta",
|
||||
"account_tokens_dialog_button_cancel": "Cancelar",
|
||||
"account_upgrade_dialog_tier_price_billed_yearly": "{{price}} cobrado anualmente. Aforro {{save}}.",
|
||||
"prefs_notifications_min_priority_high_and_higher": "Prioridade alta e superior",
|
||||
"priority_max": "máx",
|
||||
"prefs_users_delete_button": "Eliminar usuaria",
|
||||
"prefs_notifications_min_priority_any": "Calquera prioridade",
|
||||
"account_tokens_dialog_expires_label": "O token caduca o",
|
||||
"prefs_notifications_delete_after_title": "Desbotar notificacións",
|
||||
"account_upgrade_dialog_interval_yearly_discount_save": "aforro {{discount}}%",
|
||||
"prefs_users_dialog_title_edit": "Editar usuaria",
|
||||
"prefs_notifications_min_priority_low_and_higher": "Prioridade baixa e superior",
|
||||
"account_tokens_dialog_button_update": "Actualizar token",
|
||||
"account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} almacenaxe total",
|
||||
"prefs_reservations_table_everyone_write_only": "Podo publicar e subscribirme, calquera pode publicar",
|
||||
"prefs_appearance_title": "Aparencia",
|
||||
"account_tokens_table_cannot_delete_or_edit": "Non se pode editar ou desbotar o token da sesión actual",
|
||||
"prefs_notifications_sound_play": "Reproducir ton seleccionado",
|
||||
"account_tokens_table_last_access_header": "Último acceso",
|
||||
"account_tokens_table_last_origin_tooltip": "Desde o enderezo IP {{ip}}, preme para detalles",
|
||||
"account_upgrade_dialog_tier_price_per_month": "mes",
|
||||
"account_tokens_table_current_session": "Sesión do navegador actual",
|
||||
"account_upgrade_dialog_button_pay_now": "Paga e subscríbete",
|
||||
"reservation_delete_dialog_action_delete_title": "Eliminar mensaxes e anexos gardados",
|
||||
"reservation_delete_dialog_action_delete_description": "As mensaxes e anexos vanse borrar definitivamente. Esta acción non ten volta.",
|
||||
"prefs_notifications_delete_after_one_month_description": "As notificacións autoelimínanse após un mes",
|
||||
"prefs_users_dialog_base_url_label": "URL do servizo, ex. https://ntfy.sh",
|
||||
"account_upgrade_dialog_tier_selected_label": "Seleccionado",
|
||||
"account_upgrade_dialog_button_update_subscription": "Actualizar subscrición",
|
||||
"priority_high": "alta",
|
||||
"account_delete_dialog_billing_warning": "Ao eliminar a conta tamén cancelas o pagamento das subscricións. Non poderás volver acceder ao taboleiro de pagamentos.",
|
||||
"prefs_notifications_min_priority_description_max": "Mostrar notificacións se a prioridade é 5 (máx)",
|
||||
"account_upgrade_dialog_reservations_warning_other": "O nivel seleccionado permite reservar menos temas que o nivel actual. Antes de cambiar de nivel, <strong>elimina {{count}} reservas polo menos</strong>. Podes eliminar as reservas nos <Link>Axustes</Link>.",
|
||||
"prefs_users_dialog_title_add": "Engadir usuaria",
|
||||
"account_tokens_dialog_button_create": "Crear token",
|
||||
"account_tokens_table_create_token_button": "Crear token de acceso",
|
||||
"account_basics_tier_interval_monthly": "mensual",
|
||||
"account_basics_tier_canceled_subscription": "A sua suscripción foi cancelada e vostede será degradado a unha conta gratuita o {{date}}.",
|
||||
"account_basics_password_dialog_current_password_incorrect": "Contrasinal incorrecto",
|
||||
"account_basics_phone_numbers_dialog_number_label": "Número de teléfono",
|
||||
"account_basics_password_dialog_button_submit": "Modificar contrasinal",
|
||||
"account_basics_username_title": "Usuario",
|
||||
"account_basics_phone_numbers_dialog_check_verification_button": "Código de confirmación",
|
||||
"account_usage_messages_title": "Mesaxes publicados",
|
||||
"account_basics_phone_numbers_dialog_verify_button_sms": "Enviar SMS",
|
||||
"account_basics_tier_change_button": "Cambiar",
|
||||
"account_basics_phone_numbers_dialog_description": "Para usar a característica de chamadas de teléfono, vostede debe engadir e verificar ao menos un número de teléfono. A verificación pode ser realizada vía SMS ou a través de chamada.",
|
||||
"account_delete_title": "Borrar conta",
|
||||
"account_delete_dialog_label": "Contrasinal",
|
||||
"account_basics_tier_admin_suffix_with_tier": "(con tier {{tier}})",
|
||||
"subscribe_dialog_login_username_label": "Nome de usuario, ex. phil",
|
||||
"subscribe_dialog_error_user_not_authorized": "Usuario {{username}} non autorizado",
|
||||
"account_basics_title": "Conta",
|
||||
"account_basics_phone_numbers_no_phone_numbers_yet": "Aínda non hay números de teléfono",
|
||||
"subscribe_dialog_subscribe_button_generate_topic_name": "Xerar nome",
|
||||
"subscribe_dialog_login_password_label": "Contrasinal",
|
||||
"subscribe_dialog_subscribe_button_subscribe": "Subscribirse",
|
||||
"account_basics_phone_numbers_dialog_title": "Engadir número de teléfono",
|
||||
"account_basics_username_admin_tooltip": "É vostede Admin",
|
||||
"account_delete_dialog_description": "Isto borrará permanentemente a túa conta, incluido todos os datos almacenados no servidor. Despois do borrado, o teu nome de usuario non estará dispoñible durante 7 días. Se realmente queres proceder, por favor confirme co seu contrasinal na caixa inferior.",
|
||||
"account_usage_reservations_none": "Non hai temas reservados para esta conta",
|
||||
"subscribe_dialog_subscribe_topic_placeholder": "Nome do tema, ex. phil_alertas",
|
||||
"account_usage_title": "Uso",
|
||||
"account_basics_tier_upgrade_button": "Mexorar a Pro",
|
||||
"subscribe_dialog_error_topic_already_reserved": "Tema xa reservado",
|
||||
"account_basics_tier_admin_suffix_no_tier": "(sen tier)",
|
||||
"account_basics_tier_payment_overdue": "O pago está retrasado. Por favor, revise o seu método de pago o a súa conta será degradada pronto.",
|
||||
"account_basics_phone_numbers_description": "Para notificacións telefónicas",
|
||||
"account_basics_tier_free": "De balde",
|
||||
"account_basics_tier_admin": "Admin",
|
||||
"account_delete_dialog_button_cancel": "Cancelar",
|
||||
"account_basics_password_description": "Modificar o contrasinal da conta",
|
||||
"account_usage_calls_title": "Chamadas realizadas",
|
||||
"account_basics_tier_basic": "Básico",
|
||||
"account_basics_phone_numbers_copied_to_clipboard": "Número de teléfono copiado no portapapeis",
|
||||
"account_basics_tier_title": "Tipo de conta",
|
||||
"account_usage_cannot_create_portal_session": "Non foi posible abrir o portal de pagos",
|
||||
"account_delete_description": "Borrar permanentemente a túa conta",
|
||||
"account_basics_phone_numbers_dialog_number_placeholder": "ex. +1222333444",
|
||||
"account_basics_phone_numbers_dialog_code_placeholder": "ex. 123456",
|
||||
"account_basics_tier_manage_billing_button": "Xestionar pagos",
|
||||
"account_basics_username_description": "Ei, ese eres ti ❤",
|
||||
"account_basics_password_dialog_confirm_password_label": "Confirmar contrasinal",
|
||||
"account_basics_tier_interval_yearly": "anual",
|
||||
"account_delete_dialog_button_submit": "Borrar permanentemente a conta",
|
||||
"account_basics_phone_numbers_dialog_channel_call": "Chamada",
|
||||
"account_basics_password_title": "Contrasinal",
|
||||
"account_basics_password_dialog_new_password_label": "Novo contrasinal",
|
||||
"account_usage_of_limit": "de {{limit}}",
|
||||
"subscribe_dialog_error_user_anonymous": "anónimo",
|
||||
"account_usage_basis_ip_description": "Estadísticas de uso e límites para esta conta están basados na sua IP, polo que poden estar compartidos con outros usuarios. Os limites mostrados son aproximados, basados nos ratios de limite existentes.",
|
||||
"account_basics_password_dialog_title": "Modificar contrasinal",
|
||||
"account_usage_limits_reset_daily": "Límite de uso é reiniciado diariamente a medianoite (UTC(",
|
||||
"account_usage_unlimited": "Sen límites",
|
||||
"account_basics_phone_numbers_title": "Números de teléfono",
|
||||
"account_basics_password_dialog_current_password_label": "Contrasinal actual",
|
||||
"subscribe_dialog_subscribe_base_url_label": "URL do servizo",
|
||||
"account_usage_reservations_title": "Temas reservados",
|
||||
"account_usage_calls_none": "Non se poden realizar chamadas con esta conta",
|
||||
"subscribe_dialog_subscribe_use_another_label": "Usar outro servidor",
|
||||
"account_basics_phone_numbers_dialog_code_label": "Código de verificación",
|
||||
"account_basics_tier_paid_until": "Suscripción pagada ata {{date}}, e vaise auto-renovar",
|
||||
"account_usage_attachment_storage_description": "{{filesize}} por arquivo, borrado despois de {{expiry}}",
|
||||
"account_basics_phone_numbers_dialog_verify_button_call": "Chámame",
|
||||
"account_usage_emails_title": "Emails enviados",
|
||||
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
|
||||
"subscribe_dialog_login_description": "Este tema está protexido por contrasinal. Por favor, introduza o usuario e contrasinal para subscribirse."
|
||||
}
|
||||
|
||||
@@ -268,5 +268,44 @@
|
||||
"publish_dialog_chip_call_no_verified_numbers_tooltip": "Nessun numero verificato",
|
||||
"account_basics_phone_numbers_title": "Numeri di telefono",
|
||||
"account_basics_phone_numbers_dialog_description": "Per usare la funzionalità di notifica tramite chiamata telefonica, devi aggiungere e verificare almeno un numero di telefono. La verifica può essere fatta tramite SMS o chiamata telefonica.",
|
||||
"account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} topic riservato"
|
||||
"account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} topic riservato",
|
||||
"account_upgrade_dialog_billing_contact_email": "Per domande di fatturazione, <Link>contattaci</Link> direttamente.",
|
||||
"account_upgrade_dialog_tier_current_label": "Attuale",
|
||||
"account_basics_phone_numbers_dialog_number_label": "Numero di telefono",
|
||||
"account_basics_phone_numbers_dialog_check_verification_button": "Conferma codice",
|
||||
"account_basics_phone_numbers_dialog_verify_button_sms": "Invia SMS",
|
||||
"account_basics_phone_numbers_no_phone_numbers_yet": "Ancora nessun numero di telefono",
|
||||
"account_basics_phone_numbers_dialog_title": "Aggiungi un numero di telefono",
|
||||
"account_upgrade_dialog_button_cancel": "Cancella",
|
||||
"account_upgrade_dialog_billing_contact_website": "Per domande di fatturazione, visita per favore in nostro <Link>sito</Link>.",
|
||||
"account_upgrade_dialog_button_cancel_subscription": "Cancella iscrizione",
|
||||
"account_basics_phone_numbers_description": "Per notifiche via chiamata",
|
||||
"account_basics_phone_numbers_copied_to_clipboard": "Numero di telefono copiato negli appunti",
|
||||
"account_basics_phone_numbers_dialog_number_placeholder": "p. e. +391234567890",
|
||||
"account_basics_phone_numbers_dialog_code_placeholder": "p. e. 123456",
|
||||
"account_tokens_title": "Token d'accesso",
|
||||
"account_upgrade_dialog_tier_price_billed_monthly": "{{price}} all'anno. Addebitato annualmente.",
|
||||
"account_basics_phone_numbers_dialog_channel_call": "Chiama",
|
||||
"account_upgrade_dialog_button_redirect_signup": "Iscriviti ora",
|
||||
"account_upgrade_dialog_tier_price_billed_yearly": "{{price}} addebitato annualmente. Risparmia {{save}}.",
|
||||
"account_upgrade_dialog_tier_price_per_month": "mese",
|
||||
"account_upgrade_dialog_button_pay_now": "Paga ora e isciviti",
|
||||
"account_basics_phone_numbers_dialog_code_label": "Codice di verifica",
|
||||
"account_basics_phone_numbers_dialog_verify_button_call": "Chiamami",
|
||||
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
|
||||
"account_upgrade_dialog_tier_selected_label": "Selezionato",
|
||||
"account_upgrade_dialog_button_update_subscription": "Aggiorna iscrizione",
|
||||
"account_usage_attachment_storage_title": "Archivio allegati",
|
||||
"account_delete_dialog_description": "Il tuo account sarà permanentemente cancellato assieme a tutti i tuoi dati presenti sul server. Dopo la cancellazione, la tua username non sarà disponibile per 7 giorni. Se desideri davvero procedere, inserisci la tua password nella seguente casella.",
|
||||
"account_delete_dialog_button_cancel": "Annulla",
|
||||
"account_usage_calls_title": "Chiamate effettuate",
|
||||
"account_delete_description": "Elimina permanentemente il tuo account",
|
||||
"account_delete_dialog_button_submit": "Elimina il tuo account permanentemente",
|
||||
"account_usage_basis_ip_description": "Le statistiche di utilizzo e i limiti per questo account sono basati sul tuo indirizzo IP, quindi potrebbero essere in condivisione con altri utenti. I limiti mostrati sopra sono approssimazioni basate sui limiti esistenti.",
|
||||
"account_usage_calls_none": "Questo account non può effettuare chiamate",
|
||||
"account_delete_dialog_billing_warning": "Eliminando il tuo account perderai immediatamente il tuo abbonamento. Non potrai più accedere alla dashboard di fatturazione.",
|
||||
"account_delete_dialog_label": "Password",
|
||||
"account_upgrade_dialog_tier_features_no_reservations": "Nessun argomento riservato",
|
||||
"account_upgrade_dialog_tier_features_messages_one": "{{messages}} messaggi giornalieri",
|
||||
"account_upgrade_dialog_reservations_warning_one": "Il livello selezionato consente meno argomenti riservati rispetto al livello corrente. Prima di cambiare il livello, <strong> si prega di eliminare almeno una prenotazione</strong>. È possibile rimuovere le prenotazioni nel <Link>Impostazioni</Link>."
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@
|
||||
"action_bar_reservation_add": "Reserve topic",
|
||||
"action_bar_reservation_edit": "Change reservation",
|
||||
"signup_disabled": "Registrar está desativado",
|
||||
"signup_error_username_taken": "Usuário {{username}} já existe",
|
||||
"signup_error_username_taken": "Usuário {{username}} já existe",
|
||||
"signup_error_creation_limit_reached": "Limite de criação de contas atingido",
|
||||
"action_bar_reservation_delete": "Remover reserva",
|
||||
"action_bar_account": "Conta",
|
||||
|
||||
@@ -354,7 +354,7 @@
|
||||
"account_upgrade_dialog_billing_contact_email": "По вопросам оплаты, пожалуйста <Link>свяжитесь с нами</Link>.",
|
||||
"account_upgrade_dialog_billing_contact_website": "По вопросам оплаты, пожалуйста обратитесь к нашему <Link>сайту</Link>.",
|
||||
"publish_dialog_call_reset": "Удалить вызов",
|
||||
"account_basics_phone_numbers_dialog_description": "Для использования уведомлений необходимо добавить и подтвердить хотя бы один номер телефона. Проверить можно используя SMS или звонок.",
|
||||
"account_basics_phone_numbers_dialog_description": "Для того что бы использовать возможность уведомлений о вызовах, нужно добавить и проверить хотя бы один номер телефона. Проверить можно используя SMS или звонок.",
|
||||
"account_basics_phone_numbers_dialog_title": "Добавить номер телефона",
|
||||
"account_basics_phone_numbers_dialog_number_placeholder": "например +1222333444",
|
||||
"account_basics_phone_numbers_dialog_code_placeholder": "например 123456",
|
||||
@@ -363,5 +363,22 @@
|
||||
"account_usage_calls_none": "Невозможно совершать вызовы с этим аккаунтом",
|
||||
"publish_dialog_chip_call_no_verified_numbers_tooltip": "Нет проверенных номеров",
|
||||
"account_basics_phone_numbers_copied_to_clipboard": "Номер телефона скопирован в буфер обмена",
|
||||
"account_upgrade_dialog_tier_features_no_calls": "Нет вызовов"
|
||||
"account_upgrade_dialog_tier_features_no_calls": "Нет вызовов",
|
||||
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} ежедневный звонок",
|
||||
"account_basics_phone_numbers_dialog_number_label": "Номер телефона",
|
||||
"account_basics_phone_numbers_dialog_check_verification_button": "Подтвердить код",
|
||||
"account_upgrade_dialog_tier_features_calls_other": "{{calls}} ежедневных звонков",
|
||||
"account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} зарезервированная тема",
|
||||
"account_basics_phone_numbers_no_phone_numbers_yet": "Телефонных номеров пока нет",
|
||||
"publish_dialog_chip_call_label": "Звонок",
|
||||
"account_upgrade_dialog_tier_features_emails_one": "{{emails}} ежедневное письмо",
|
||||
"account_upgrade_dialog_tier_features_messages_one": "{{messages}} ежедневное сообщения",
|
||||
"account_basics_phone_numbers_description": "Для уведомлений о телефонных звонках",
|
||||
"publish_dialog_call_label": "Звонок",
|
||||
"account_basics_phone_numbers_dialog_channel_call": "Позвонить",
|
||||
"account_basics_phone_numbers_title": "Номера телефонов",
|
||||
"account_basics_phone_numbers_dialog_code_label": "Проверочный код",
|
||||
"account_basics_phone_numbers_dialog_verify_button_call": "Позвонить мне",
|
||||
"publish_dialog_call_item": "Вызов телефонного номера {{number}}",
|
||||
"account_basics_phone_numbers_dialog_channel_sms": "SMS"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user