Compare commits
158 commits
auth-user-
...
main
Author | SHA1 | Date | |
---|---|---|---|
a06577faac | |||
668c472ee6 | |||
|
abe7275f0c | ||
|
d4af2be7a0 | ||
|
8dd4c3e3c0 | ||
|
af25f164ed | ||
|
64ede0f11c | ||
|
d3565c9b87 | ||
|
c332c132fa | ||
|
b3534aecda | ||
|
8e04912201 | ||
|
909f9b3d24 | ||
|
cad38573d7 | ||
|
a3663e43e4 | ||
|
6d451785f0 | ||
|
7791901b2d | ||
|
2afe1fbeed | ||
|
e2097e856e | ||
|
03e7a3ea65 | ||
|
27f8cc0e52 | ||
|
32efbd5823 | ||
|
6dbdabf9fd | ||
|
75d57b9f04 | ||
|
554547b431 | ||
|
b811da6b83 | ||
|
ca6bc1dcb0 | ||
|
7c3fd42a86 | ||
|
04f12d1e2f | ||
|
c6b8ea90b7 | ||
|
7f8fb8d571 | ||
|
f8cfb084e0 | ||
|
70b084457a | ||
|
6c12244587 | ||
|
e7c0365079 | ||
|
43b11de596 | ||
|
ef45ea5a50 | ||
|
483edb70bf | ||
|
7516d25bc6 | ||
|
2f2918bd3b | ||
|
73d2b3363b | ||
|
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 | ||
|
d556a675e9 | ||
|
bfc1fa5181 | ||
|
d9387dac99 | ||
|
4818ee57b6 | ||
|
addb5efebb | ||
|
8adb9ee633 | ||
|
418fc98d1a | ||
|
beffe4a1f2 | ||
|
ef15b44a1b | ||
|
bc802bfc77 | ||
|
d10a5df3df | ||
|
b05d27ce45 | ||
|
e61c9fdde9 | ||
|
d2e2791729 | ||
|
68a7756621 | ||
|
42063cbd5c | ||
|
a407a2e0f8 | ||
|
6ec1ccf7a3 | ||
|
044f4182d0 | ||
|
bae30d79c9 | ||
|
25a60969fb | ||
|
528a67722b | ||
|
d29dc95962 | ||
|
fc3d4dcf5e | ||
|
3d4218324f | ||
|
f6fbb45978 | ||
|
dee16f543d | ||
|
9959d1aa43 | ||
|
76146c4e74 | ||
|
8a8023fcf8 | ||
|
4b0d1e448d | ||
|
6748a2f2f3 | ||
|
4c4d772a5f | ||
|
85740d810b | ||
|
2305ebca24 | ||
|
59bf388534 | ||
|
3066b95a6d | ||
|
1bd77a83bd | ||
|
d0b7336da7 | ||
|
c80f71bd9b | ||
|
15fa3b7d9f | ||
|
e2d7f2cf29 | ||
|
3031fb910f | ||
|
d999dbe0a0 | ||
|
60d5e66e34 | ||
|
c6964502c4 | ||
|
ca2633ff82 | ||
|
a1625c7f15 | ||
|
30a913c05c | ||
|
1d02933481 | ||
|
62c2ec0614 | ||
|
45ca20dec9 | ||
|
de362d2322 | ||
|
115e6e9cf8 | ||
|
f17538b7df | ||
|
6f68c8cd1f | ||
|
02dd72ba57 | ||
|
63629efae7 | ||
|
9015b27803 | ||
|
a5f0670f7f | ||
|
d7db395016 | ||
|
99eef493d2 | ||
|
0d395249ff | ||
|
5cf1da974a | ||
|
2f0ec88f40 | ||
|
d9d3c4a724 | ||
|
bc4d4f424a | ||
|
67459650d4 | ||
|
c31bce1e2d | ||
|
3e3b556108 | ||
|
723daf9497 | ||
|
f77958fc35 | ||
|
ea9f2c6e35 | ||
|
7d20238423 | ||
|
31131db756 | ||
|
17e634c563 | ||
|
a7dc3d84e0 | ||
|
b80aec90d0 | ||
|
8544733048 | ||
|
140fdcca81 | ||
|
2e08c48742 | ||
|
4800bb05d2 | ||
|
384cabede5 | ||
|
4e9eeb1fa1 | ||
|
a534cc9eca | ||
|
e52132c85b | ||
|
76667ffcf9 | ||
|
8ba4b72b37 | ||
|
81e1417ce5 | ||
|
c1576b5b19 | ||
|
86cc3b9607 | ||
|
c7f85e6283 |
116 changed files with 3714 additions and 1809 deletions
|
@ -71,7 +71,7 @@ builds:
|
||||||
nfpms:
|
nfpms:
|
||||||
-
|
-
|
||||||
package_name: ntfy
|
package_name: ntfy
|
||||||
homepage: https://heckel.io/ntfy
|
homepage: https://git.zio.sh/astra/ntfy/v2
|
||||||
maintainer: Philipp C. Heckel <philipp.heckel@gmail.com>
|
maintainer: Philipp C. Heckel <philipp.heckel@gmail.com>
|
||||||
description: Simple pub-sub notification service
|
description: Simple pub-sub notification service
|
||||||
license: Apache 2.0
|
license: Apache 2.0
|
||||||
|
@ -164,14 +164,14 @@ dockers:
|
||||||
- image_templates:
|
- image_templates:
|
||||||
- &arm64v8_image "binwiederhier/ntfy:{{ .Tag }}-arm64v8"
|
- &arm64v8_image "binwiederhier/ntfy:{{ .Tag }}-arm64v8"
|
||||||
use: buildx
|
use: buildx
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile-arm
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
build_flag_templates:
|
build_flag_templates:
|
||||||
- "--platform=linux/arm64/v8"
|
- "--platform=linux/arm64/v8"
|
||||||
- image_templates:
|
- image_templates:
|
||||||
- &armv7_image "binwiederhier/ntfy:{{ .Tag }}-armv7"
|
- &armv7_image "binwiederhier/ntfy:{{ .Tag }}-armv7"
|
||||||
use: buildx
|
use: buildx
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile-arm
|
||||||
goarch: arm
|
goarch: arm
|
||||||
goarm: 7
|
goarm: 7
|
||||||
build_flag_templates:
|
build_flag_templates:
|
||||||
|
@ -179,7 +179,7 @@ dockers:
|
||||||
- image_templates:
|
- image_templates:
|
||||||
- &armv6_image "binwiederhier/ntfy:{{ .Tag }}-armv6"
|
- &armv6_image "binwiederhier/ntfy:{{ .Tag }}-armv6"
|
||||||
use: buildx
|
use: buildx
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile-arm
|
||||||
goarch: arm
|
goarch: arm
|
||||||
goarm: 6
|
goarm: 6
|
||||||
build_flag_templates:
|
build_flag_templates:
|
||||||
|
|
|
@ -9,6 +9,7 @@ LABEL org.opencontainers.image.licenses="Apache-2.0, GPL-2.0"
|
||||||
LABEL org.opencontainers.image.title="ntfy"
|
LABEL org.opencontainers.image.title="ntfy"
|
||||||
LABEL org.opencontainers.image.description="Send push notifications to your phone or desktop using PUT/POST"
|
LABEL org.opencontainers.image.description="Send push notifications to your phone or desktop using PUT/POST"
|
||||||
|
|
||||||
|
RUN apk add --no-cache tzdata
|
||||||
COPY ntfy /usr/bin
|
COPY ntfy /usr/bin
|
||||||
|
|
||||||
EXPOSE 80/tcp
|
EXPOSE 80/tcp
|
||||||
|
|
18
Dockerfile-arm
Normal file
18
Dockerfile-arm
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
FROM alpine
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.authors="philipp.heckel@gmail.com"
|
||||||
|
LABEL org.opencontainers.image.url="https://ntfy.sh/"
|
||||||
|
LABEL org.opencontainers.image.documentation="https://docs.ntfy.sh/"
|
||||||
|
LABEL org.opencontainers.image.source="https://github.com/binwiederhier/ntfy"
|
||||||
|
LABEL org.opencontainers.image.vendor="Philipp C. Heckel"
|
||||||
|
LABEL org.opencontainers.image.licenses="Apache-2.0, GPL-2.0"
|
||||||
|
LABEL org.opencontainers.image.title="ntfy"
|
||||||
|
LABEL org.opencontainers.image.description="Send push notifications to your phone or desktop using PUT/POST"
|
||||||
|
|
||||||
|
# Alpine does not support adding "tzdata" on ARM anymore, see
|
||||||
|
# https://github.com/binwiederhier/ntfy/issues/894
|
||||||
|
|
||||||
|
COPY ntfy /usr/bin
|
||||||
|
|
||||||
|
EXPOSE 80/tcp
|
||||||
|
ENTRYPOINT ["ntfy"]
|
|
@ -1,14 +1,18 @@
|
||||||
FROM golang:1.19-bullseye as builder
|
FROM golang:1.20-bullseye as builder
|
||||||
|
|
||||||
ARG VERSION=dev
|
ARG VERSION=dev
|
||||||
ARG COMMIT=unknown
|
ARG COMMIT=unknown
|
||||||
|
ARG NODE_MAJOR=18
|
||||||
|
|
||||||
RUN apt-get update
|
RUN apt-get update && apt-get install -y \
|
||||||
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash
|
build-essential ca-certificates curl gnupg \
|
||||||
RUN apt-get install -y \
|
&& mkdir -p /etc/apt/keyrings \
|
||||||
build-essential \
|
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
|
||||||
nodejs \
|
&& 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 \
|
||||||
python3-pip
|
&& apt-get update \
|
||||||
|
&& apt-get install -y \
|
||||||
|
python3-pip nodejs \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ADD Makefile .
|
ADD Makefile .
|
||||||
|
|
25
Makefile
25
Makefile
|
@ -39,8 +39,8 @@ help:
|
||||||
@echo " make web-deps - Install web app dependencies (npm install the universe)"
|
@echo " make web-deps - Install web app dependencies (npm install the universe)"
|
||||||
@echo " make web-build - Actually build the web app"
|
@echo " make web-build - Actually build the web app"
|
||||||
@echo " make web-lint - Run eslint on 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-fmt - 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-check - Run prettier on the web app, but don't change anything"
|
||||||
@echo
|
@echo
|
||||||
@echo "Build documentation:"
|
@echo "Build documentation:"
|
||||||
@echo " make docs - Build the documentation"
|
@echo " make docs - Build the documentation"
|
||||||
|
@ -111,18 +111,7 @@ build-deps-ubuntu:
|
||||||
docs: docs-deps docs-build
|
docs: docs-deps docs-build
|
||||||
|
|
||||||
docs-build: .PHONY
|
docs-build: .PHONY
|
||||||
@if ! /bin/echo -e "import sys\nif sys.version_info < (3,8):\n exit(1)" | python3; then \
|
mkdocs build
|
||||||
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-deps: .PHONY
|
docs-deps: .PHONY
|
||||||
pip3 install -r requirements.txt
|
pip3 install -r requirements.txt
|
||||||
|
@ -151,10 +140,10 @@ web-deps:
|
||||||
web-deps-update:
|
web-deps-update:
|
||||||
cd web && npm update
|
cd web && npm update
|
||||||
|
|
||||||
web-format:
|
web-fmt:
|
||||||
cd web && npm run format
|
cd web && npm run format
|
||||||
|
|
||||||
web-format-check:
|
web-fmt-check:
|
||||||
cd web && npm run format:check
|
cd web && npm run format:check
|
||||||
|
|
||||||
web-lint:
|
web-lint:
|
||||||
|
@ -248,7 +237,7 @@ cli-build-results:
|
||||||
|
|
||||||
# Test/check targets
|
# 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
|
test: .PHONY
|
||||||
go test $(shell go list ./... | grep -vE 'ntfy/(test|examples|tools)')
|
go test $(shell go list ./... | grep -vE 'ntfy/(test|examples|tools)')
|
||||||
|
@ -275,7 +264,7 @@ coverage-upload:
|
||||||
|
|
||||||
# Lint/formatting targets
|
# Lint/formatting targets
|
||||||
|
|
||||||
fmt:
|
fmt: web-fmt
|
||||||
gofmt -s -w .
|
gofmt -s -w .
|
||||||
|
|
||||||
fmt-check:
|
fmt-check:
|
||||||
|
|
174
README.md
174
README.md
|
@ -1,181 +1,9 @@
|
||||||

|
|
||||||
|
|
||||||
# ntfy.sh | Send push notifications to your phone or desktop via PUT/POST
|
# 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://github.com/binwiederhier/ntfy/actions)
|
|
||||||
[](https://goreportcard.com/report/github.com/binwiederhier/ntfy)
|
|
||||||
[](https://codecov.io/gh/binwiederhier/ntfy)
|
|
||||||
[](https://discord.gg/cT7ECsZj9w)
|
|
||||||
[](https://matrix.to/#/#ntfy:matrix.org)
|
|
||||||
[](https://matrix.to/#/#ntfy-space:matrix.org)
|
|
||||||
[](https://discuss.ntfy.sh/c/ntfy)
|
|
||||||
[](https://ntfy.statuspage.io/)
|
|
||||||
[](https://gitpod.io/#https://github.com/binwiederhier/ntfy)
|
|
||||||
|
|
||||||
**ntfy** (pronounced "*notify*") is a simple HTTP-based [pub-sub](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern)
|
**ntfy** (pronounced "*notify*") is a simple HTTP-based [pub-sub](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern)
|
||||||
notification service. With ntfy, you can **send notifications to your phone or desktop via scripts** from any computer,
|
notification service. With ntfy, you can **send notifications to your phone or desktop via scripts** from any computer,
|
||||||
**without having to sign up or pay any fees**. If you'd like to run your own instance of the service, you can easily do
|
**without having to sign up or pay any fees**. If you'd like to run your own instance of the service, you can easily do
|
||||||
so since ntfy is open source.
|
so since ntfy is open source.
|
||||||
|
|
||||||
You can access the free version of ntfy at **[ntfy.sh](https://ntfy.sh)**. There is also an [open source Android app](https://github.com/binwiederhier/ntfy-android)
|
|
||||||
available on [Google Play](https://play.google.com/store/apps/details?id=io.heckel.ntfy) or [F-Droid](https://f-droid.org/en/packages/io.heckel.ntfy/),
|
|
||||||
as well as an [open source iOS app](https://github.com/binwiederhier/ntfy-ios) available on the [App Store](https://apps.apple.com/us/app/ntfy/id1625396347).
|
|
||||||
|
|
||||||
<p>
|
### This is a fork of [github.com/binwiederhier/ntfy](https://github.com/binwiederhier/ntfy)
|
||||||
<img src=".github/images/screenshot-curl.png" height="180">
|
|
||||||
<img src=".github/images/screenshot-web-detail.png" height="180">
|
|
||||||
<img src=".github/images/screenshot-phone-main.jpg" height="180">
|
|
||||||
<img src=".github/images/screenshot-phone-detail.jpg" height="180">
|
|
||||||
<img src=".github/images/screenshot-phone-notification.jpg" height="180">
|
|
||||||
</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. ❤️
|
|
||||||
|
|
||||||
## **[Documentation](https://ntfy.sh/docs/)**
|
|
||||||
|
|
||||||
[Getting started](https://ntfy.sh/docs/) |
|
|
||||||
[Android/iOS](https://ntfy.sh/docs/subscribe/phone/) |
|
|
||||||
[API](https://ntfy.sh/docs/publish/) |
|
|
||||||
[Install / Self-hosting](https://ntfy.sh/docs/install/) |
|
|
||||||
[Building](https://ntfy.sh/docs/develop/)
|
|
||||||
|
|
||||||
## Chat / forum
|
|
||||||
There are a few ways to get in touch with me and/or the rest of the community. Feel free to use any of these methods. Whatever
|
|
||||||
works best for you:
|
|
||||||
|
|
||||||
* [Discord server](https://discord.gg/cT7ECsZj9w) - direct chat with the community
|
|
||||||
* [Matrix room #ntfy](https://matrix.to/#/#ntfy:matrix.org) (+ [Matrix space](https://matrix.to/#/#ntfy-space:matrix.org)) - same chat, bridged from Discord
|
|
||||||
* [Lemmy discussion board](https://discuss.ntfy.sh/c/ntfy) - asynchronous forum (_new as of June 2023_)
|
|
||||||
* [GitHub issues](https://github.com/binwiederhier/ntfy/issues) - questions, features, bugs
|
|
||||||
|
|
||||||
## Announcements / beta testers
|
|
||||||
For announcements of new releases and cutting-edge beta versions, please subscribe to the [ntfy.sh/announcements](https://ntfy.sh/announcements)
|
|
||||||
topic. If you'd like to test the iOS app, join [TestFlight](https://testflight.apple.com/join/P1fFnAm9). For Android betas,
|
|
||||||
join Discord/Matrix (I'll eventually make a testing channel in Google Play).
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
I welcome any and all contributions. Just create a PR or an issue. For larger features/ideas, please reach out
|
|
||||||
on Discord/Matrix first to see if I'd accept them. To contribute code, check out the [build instructions](https://ntfy.sh/docs/develop/)
|
|
||||||
for the server and the Android app. Or, if you'd like to help translate 🇩🇪 🇺🇸 🇧🇬, you can start immediately in
|
|
||||||
[Hosted Weblate](https://hosted.weblate.org/projects/ntfy/).
|
|
||||||
|
|
||||||
<a href="https://hosted.weblate.org/engage/ntfy/">
|
|
||||||
<img src="https://hosted.weblate.org/widgets/ntfy/-/multi-blue.svg" alt="Translation status" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
## Sponsors
|
|
||||||
I have just very recently started accepting donations via [GitHub Sponsors](https://github.com/sponsors/binwiederhier),
|
|
||||||
and [Liberapay](https://liberapay.com/ntfy). I would be humbled if you helped me carry the server and developer
|
|
||||||
account costs. Even small donations are very much appreciated. A big fat **Thank You** to the folks already sponsoring ntfy:
|
|
||||||
|
|
||||||
<a href="https://github.com/neutralinsomniac"><img src="https://github.com/neutralinsomniac.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/aspyct"><img src="https://github.com/aspyct.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/nickexyz"><img src="https://github.com/nickexyz.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/qcasey"><img src="https://github.com/qcasey.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/mckay115"><img src="https://github.com/mckay115.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/Salamafet"><img src="https://github.com/Salamafet.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/codinghipster"><img src="https://github.com/codinghipster.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/HinFort"><img src="https://github.com/HinFort.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/Lexevolution"><img src="https://github.com/Lexevolution.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/johnnyip"><img src="https://github.com/johnnyip.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/JonDerThan"><img src="https://github.com/JonDerThan.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/12nick12"><img src="https://github.com/12nick12.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/eanplatter"><img src="https://github.com/eanplatter.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/fnoelscher"><img src="https://github.com/fnoelscher.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/bnorick"><img src="https://github.com/bnorick.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/snh"><img src="https://github.com/snh.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/hen-x"><img src="https://github.com/hen-x.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/JamieGoodson"><img src="https://github.com/JamieGoodson.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/cremesk"><img src="https://github.com/cremesk.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/dangowans"><img src="https://github.com/dangowans.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/mnault"><img src="https://github.com/mnault.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/nwithan8"><img src="https://github.com/nwithan8.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/peterleiser"><img src="https://github.com/peterleiser.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/portothree"><img src="https://github.com/portothree.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/finngreig"><img src="https://github.com/finngreig.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/skrollme"><img src="https://github.com/skrollme.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/gergepalfi"><img src="https://github.com/gergepalfi.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/tonyakwei"><img src="https://github.com/tonyakwei.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/crosbyh"><img src="https://github.com/crosbyh.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/mdlnr"><img src="https://github.com/mdlnr.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/p-samuel"><img src="https://github.com/p-samuel.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/zugaldia"><img src="https://github.com/zugaldia.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/NathanSweet"><img src="https://github.com/NathanSweet.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/msdeibel"><img src="https://github.com/msdeibel.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/ksurl"><img src="https://github.com/ksurl.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/CodingTimeDEV"><img src="https://github.com/CodingTimeDEV.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/Terrormixer3000"><img src="https://github.com/Terrormixer3000.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/voroskoi"><img src="https://github.com/voroskoi.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/Nickwasused"><img src="https://github.com/Nickwasused.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/vinhdizzo"><img src="https://github.com/vinhdizzo.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/Ge0rg3"><img src="https://github.com/Ge0rg3.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/biopsin"><img src="https://github.com/biopsin.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/thebino"><img src="https://github.com/thebino.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/sky4055"><img src="https://github.com/sky4055.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/julianlam"><img src="https://github.com/julianlam.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/andreapx"><img src="https://github.com/andreapx.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/billycao"><img src="https://github.com/billycao.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/zoic21"><img src="https://github.com/zoic21.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/IanKulin"><img src="https://github.com/IanKulin.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/Joachim256"><img src="https://github.com/Joachim256.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/overtone1000"><img src="https://github.com/overtone1000.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/oakd"><img src="https://github.com/oakd.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/KucharczykL"><img src="https://github.com/KucharczykL.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/hansbickhofe"><img src="https://github.com/hansbickhofe.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/caseodilla"><img src="https://github.com/caseodilla.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/0xAF"><img src="https://github.com/0xAF.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/soonoo"><img src="https://github.com/soonoo.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/nichu42"><img src="https://github.com/nichu42.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/samliebow"><img src="https://github.com/samliebow.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/johman10"><img src="https://github.com/johman10.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/R-Gld"><img src="https://github.com/R-Gld.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/FingerlessGlov3s"><img src="https://github.com/FingerlessGlov3s.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/Twisterado"><img src="https://github.com/Twisterado.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/ScrumpyJack"><img src="https://github.com/ScrumpyJack.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/andrejarrell"><img src="https://github.com/andrejarrell.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/oaustegard"><img src="https://github.com/oaustegard.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/CreativeWarlock"><img src="https://github.com/CreativeWarlock.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/darkdragon-001"><img src="https://github.com/darkdragon-001.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/jonathan-kosgei"><img src="https://github.com/jonathan-kosgei.png" width="40px" /></a>
|
|
||||||
<a href="https://github.com/KevinWang15"><img src="https://github.com/KevinWang15.png" width="40px" /></a>
|
|
||||||
|
|
||||||
I'd also like to thank JetBrains for providing their awesome [IntelliJ IDEA](https://www.jetbrains.com/idea/) to me for free,
|
|
||||||
and [DigitalOcean](https://m.do.co/c/442b929528db) (*referral link*) for supporting the project:
|
|
||||||
|
|
||||||
<a href="https://m.do.co/c/442b929528db"><img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px"></a>
|
|
||||||
|
|
||||||
## Code of Conduct
|
|
||||||
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
|
|
||||||
|
|
||||||
**We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.**
|
|
||||||
|
|
||||||
_Please be sure to read the complete [Code of Conduct](CODE_OF_CONDUCT.md)._
|
|
||||||
|
|
||||||
## License
|
|
||||||
Made with ❤️ by [Philipp C. Heckel](https://heckel.io).
|
|
||||||
The project is dual licensed under the [Apache License 2.0](LICENSE) and the [GPLv2 License](LICENSE.GPLv2).
|
|
||||||
|
|
||||||
Third party libraries and resources:
|
|
||||||
* [github.com/urfave/cli](https://github.com/urfave/cli) (MIT) is used to drive the CLI
|
|
||||||
* [Mixkit sounds](https://mixkit.co/free-sound-effects/notification/) (Mixkit Free License) are used as notification sounds
|
|
||||||
* [Sounds from notificationsounds.com](https://notificationsounds.com) (Creative Commons Attribution) are used as notification sounds
|
|
||||||
* [Roboto Font](https://fonts.google.com/specimen/Roboto) (Apache 2.0) is used as a font in everything web
|
|
||||||
* [React](https://reactjs.org/) (MIT) is used for the web app
|
|
||||||
* [Material UI components](https://mui.com/) (MIT) are used in the web app
|
|
||||||
* [MUI dashboard template](https://github.com/mui/material-ui/tree/master/docs/data/material/getting-started/templates/dashboard) (MIT) was used as a basis for the web app
|
|
||||||
* [Dexie.js](https://github.com/dexie/Dexie.js) (Apache 2.0) is used for web app persistence in IndexedDB
|
|
||||||
* [GoReleaser](https://goreleaser.com/) (MIT) is used to create releases
|
|
||||||
* [go-smtp](https://github.com/emersion/go-smtp) (MIT) is used to receive e-mails
|
|
||||||
* [stretchr/testify](https://github.com/stretchr/testify) (MIT) is used for unit and integration tests
|
|
||||||
* [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) (MIT) is used to provide the persistent message cache
|
|
||||||
* [Firebase Admin SDK](https://github.com/firebase/firebase-admin-go) (Apache 2.0) is used to send FCM messages
|
|
||||||
* [github/gemoji](https://github.com/github/gemoji) (MIT) is used for emoji support (specifically the [emoji.json](https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json) file)
|
|
||||||
* [Lightbox with vanilla JS](https://yossiabramov.com/blog/vanilla-js-lightbox) as a lightbox on the landing page
|
|
||||||
* [HTTP middleware for gzip compression](https://gist.github.com/CJEnright/bc2d8b8dc0c1389a9feeddb110f822d7) (MIT) is used for serving static files
|
|
||||||
* [Regex for auto-linking](https://github.com/bryanwoods/autolink-js) (MIT) is used to highlight links (the library is not used)
|
|
||||||
* [Statically linking go-sqlite3](https://www.arp242.net/static-go.html)
|
|
||||||
* [Linked tabs in mkdocs](https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/#linked-tabs)
|
|
||||||
* [webpush-go](https://github.com/SherClockHolmes/webpush-go) (MIT) is used to send web push notifications
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"heckel.io/ntfy/log"
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
"heckel.io/ntfy/util"
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
|
@ -7,7 +7,10 @@
|
||||||
|
|
||||||
# Default credentials will be used with "ntfy publish" and "ntfy subscribe" if no other credentials are provided.
|
# Default credentials will be used with "ntfy publish" and "ntfy subscribe" if no other credentials are provided.
|
||||||
# You can set a default token to use or a default user:password combination, but not both. For an empty password,
|
# You can set a default token to use or a default user:password combination, but not both. For an empty password,
|
||||||
# use empty double-quotes ("")
|
# use empty double-quotes ("").
|
||||||
|
#
|
||||||
|
# To override the default user:password combination or default token for a particular subscription (e.g., to send
|
||||||
|
# no Authorization header), set the user:pass/token for the subscription to empty double-quotes ("").
|
||||||
|
|
||||||
# default-token:
|
# default-token:
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ package client_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/client"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/test"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"heckel.io/ntfy/client"
|
|
||||||
"heckel.io/ntfy/log"
|
|
||||||
"heckel.io/ntfy/test"
|
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
|
@ -23,9 +23,9 @@ type Config struct {
|
||||||
// Subscribe is the struct for a Subscription within Config
|
// Subscribe is the struct for a Subscription within Config
|
||||||
type Subscribe struct {
|
type Subscribe struct {
|
||||||
Topic string `yaml:"topic"`
|
Topic string `yaml:"topic"`
|
||||||
User string `yaml:"user"`
|
User *string `yaml:"user"`
|
||||||
Password *string `yaml:"password"`
|
Password *string `yaml:"password"`
|
||||||
Token string `yaml:"token"`
|
Token *string `yaml:"token"`
|
||||||
Command string `yaml:"command"`
|
Command string `yaml:"command"`
|
||||||
If map[string]string `yaml:"if"`
|
If map[string]string `yaml:"if"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package client_test
|
package client_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.zio.sh/astra/ntfy/v2/client"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"heckel.io/ntfy/client"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -37,7 +37,7 @@ subscribe:
|
||||||
require.Equal(t, 4, len(conf.Subscribe))
|
require.Equal(t, 4, len(conf.Subscribe))
|
||||||
require.Equal(t, "no-command-with-auth", conf.Subscribe[0].Topic)
|
require.Equal(t, "no-command-with-auth", conf.Subscribe[0].Topic)
|
||||||
require.Equal(t, "", conf.Subscribe[0].Command)
|
require.Equal(t, "", conf.Subscribe[0].Command)
|
||||||
require.Equal(t, "phil", conf.Subscribe[0].User)
|
require.Equal(t, "phil", *conf.Subscribe[0].User)
|
||||||
require.Equal(t, "mypass", *conf.Subscribe[0].Password)
|
require.Equal(t, "mypass", *conf.Subscribe[0].Password)
|
||||||
require.Equal(t, "echo-this", conf.Subscribe[1].Topic)
|
require.Equal(t, "echo-this", conf.Subscribe[1].Topic)
|
||||||
require.Equal(t, `echo "Message received: $message"`, conf.Subscribe[1].Command)
|
require.Equal(t, `echo "Message received: $message"`, conf.Subscribe[1].Command)
|
||||||
|
@ -67,7 +67,7 @@ subscribe:
|
||||||
require.Equal(t, 1, len(conf.Subscribe))
|
require.Equal(t, 1, len(conf.Subscribe))
|
||||||
require.Equal(t, "no-command-with-auth", conf.Subscribe[0].Topic)
|
require.Equal(t, "no-command-with-auth", conf.Subscribe[0].Topic)
|
||||||
require.Equal(t, "", conf.Subscribe[0].Command)
|
require.Equal(t, "", conf.Subscribe[0].Command)
|
||||||
require.Equal(t, "phil", conf.Subscribe[0].User)
|
require.Equal(t, "phil", *conf.Subscribe[0].User)
|
||||||
require.Equal(t, "", *conf.Subscribe[0].Password)
|
require.Equal(t, "", *conf.Subscribe[0].Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ subscribe:
|
||||||
require.Equal(t, 1, len(conf.Subscribe))
|
require.Equal(t, 1, len(conf.Subscribe))
|
||||||
require.Equal(t, "no-command-with-auth", conf.Subscribe[0].Topic)
|
require.Equal(t, "no-command-with-auth", conf.Subscribe[0].Topic)
|
||||||
require.Equal(t, "", conf.Subscribe[0].Command)
|
require.Equal(t, "", conf.Subscribe[0].Command)
|
||||||
require.Equal(t, "phil", conf.Subscribe[0].User)
|
require.Equal(t, "phil", *conf.Subscribe[0].User)
|
||||||
require.Nil(t, conf.Subscribe[0].Password)
|
require.Nil(t, conf.Subscribe[0].Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ subscribe:
|
||||||
require.Equal(t, 1, len(conf.Subscribe))
|
require.Equal(t, 1, len(conf.Subscribe))
|
||||||
require.Equal(t, "no-command-with-auth", conf.Subscribe[0].Topic)
|
require.Equal(t, "no-command-with-auth", conf.Subscribe[0].Topic)
|
||||||
require.Equal(t, "", conf.Subscribe[0].Command)
|
require.Equal(t, "", conf.Subscribe[0].Command)
|
||||||
require.Equal(t, "phil", conf.Subscribe[0].User)
|
require.Equal(t, "phil", *conf.Subscribe[0].User)
|
||||||
require.Nil(t, conf.Subscribe[0].Password)
|
require.Nil(t, conf.Subscribe[0].Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ subscribe:
|
||||||
require.Equal(t, "tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", conf.DefaultToken)
|
require.Equal(t, "tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", conf.DefaultToken)
|
||||||
require.Equal(t, 1, len(conf.Subscribe))
|
require.Equal(t, 1, len(conf.Subscribe))
|
||||||
require.Equal(t, "mytopic", conf.Subscribe[0].Topic)
|
require.Equal(t, "mytopic", conf.Subscribe[0].Topic)
|
||||||
require.Equal(t, "", conf.Subscribe[0].User)
|
require.Nil(t, conf.Subscribe[0].User)
|
||||||
require.Nil(t, conf.Subscribe[0].Password)
|
require.Nil(t, conf.Subscribe[0].Password)
|
||||||
require.Equal(t, "", conf.Subscribe[0].Token)
|
require.Nil(t, conf.Subscribe[0].Token)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"heckel.io/ntfy/util"
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -97,6 +97,11 @@ func WithBearerAuth(token string) PublishOption {
|
||||||
return WithHeader("Authorization", fmt.Sprintf("Bearer %s", token))
|
return WithHeader("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithEmptyAuth clears the Authorization header
|
||||||
|
func WithEmptyAuth() PublishOption {
|
||||||
|
return RemoveHeader("Authorization")
|
||||||
|
}
|
||||||
|
|
||||||
// WithNoCache instructs the server not to cache the message server-side
|
// WithNoCache instructs the server not to cache the message server-side
|
||||||
func WithNoCache() PublishOption {
|
func WithNoCache() PublishOption {
|
||||||
return WithHeader("X-Cache", "no")
|
return WithHeader("X-Cache", "no")
|
||||||
|
@ -187,3 +192,13 @@ func WithQueryParam(param, value string) RequestOption {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveHeader is a generic option to remove a header from a request
|
||||||
|
func RemoveHeader(header string) RequestOption {
|
||||||
|
return func(r *http.Request) error {
|
||||||
|
if header != "" {
|
||||||
|
delete(r.Header, header)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,9 +5,9 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"heckel.io/ntfy/user"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -2,10 +2,10 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/server"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/test"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"heckel.io/ntfy/server"
|
|
||||||
"heckel.io/ntfy/test"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,9 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/urfave/cli/v2/altsrc"
|
"github.com/urfave/cli/v2/altsrc"
|
||||||
"heckel.io/ntfy/log"
|
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,9 +3,9 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/client"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"heckel.io/ntfy/client"
|
|
||||||
"heckel.io/ntfy/log"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
|
@ -2,10 +2,10 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/urfave/cli/v2/altsrc"
|
"github.com/urfave/cli/v2/altsrc"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,10 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/client"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"heckel.io/ntfy/client"
|
|
||||||
"heckel.io/ntfy/log"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
|
@ -2,9 +2,9 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/test"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"heckel.io/ntfy/test"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
|
|
|
@ -5,8 +5,8 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
"github.com/stripe/stripe-go/v74"
|
"github.com/stripe/stripe-go/v74"
|
||||||
"heckel.io/ntfy/user"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
|
@ -17,12 +17,12 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"heckel.io/ntfy/log"
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
|
|
||||||
|
"git.zio.sh/astra/ntfy/v2/server"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/urfave/cli/v2/altsrc"
|
"github.com/urfave/cli/v2/altsrc"
|
||||||
"heckel.io/ntfy/server"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -9,12 +9,12 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.zio.sh/astra/ntfy/v2/client"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/test"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"heckel.io/ntfy/client"
|
|
||||||
"heckel.io/ntfy/test"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -3,10 +3,10 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/client"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"heckel.io/ntfy/client"
|
|
||||||
"heckel.io/ntfy/log"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
@ -225,12 +225,17 @@ func doSubscribe(c *cli.Context, cl *client.Client, conf *client.Config, topic,
|
||||||
}
|
}
|
||||||
|
|
||||||
func maybeAddAuthHeader(s client.Subscribe, conf *client.Config) client.SubscribeOption {
|
func maybeAddAuthHeader(s client.Subscribe, conf *client.Config) client.SubscribeOption {
|
||||||
// check for subscription token then subscription user:pass
|
// if an explicit empty token or empty user:pass is given, exit without auth
|
||||||
if s.Token != "" {
|
if (s.Token != nil && *s.Token == "") || (s.User != nil && *s.User == "" && s.Password != nil && *s.Password == "") {
|
||||||
return client.WithBearerAuth(s.Token)
|
return client.WithEmptyAuth()
|
||||||
}
|
}
|
||||||
if s.User != "" && s.Password != nil {
|
|
||||||
return client.WithBasicAuth(s.User, *s.Password)
|
// check for subscription token then subscription user:pass
|
||||||
|
if s.Token != nil && *s.Token != "" {
|
||||||
|
return client.WithBearerAuth(*s.Token)
|
||||||
|
}
|
||||||
|
if s.User != nil && *s.User != "" && s.Password != nil {
|
||||||
|
return client.WithBasicAuth(*s.User, *s.Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if no subscription token nor subscription user:pass, check for default token then default user:pass
|
// if no subscription token nor subscription user:pass, check for default token then default user:pass
|
||||||
|
|
|
@ -330,7 +330,7 @@ default-token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2
|
||||||
|
|
||||||
app, _, stdout, _ := newTestApp()
|
app, _, stdout, _ := newTestApp()
|
||||||
|
|
||||||
require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--config=" + filename, "mytopic"}))
|
require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename, "mytopic"}))
|
||||||
|
|
||||||
require.Equal(t, message, strings.TrimSpace(stdout.String()))
|
require.Equal(t, message, strings.TrimSpace(stdout.String()))
|
||||||
}
|
}
|
||||||
|
@ -355,7 +355,63 @@ default-password: mypass
|
||||||
|
|
||||||
app, _, stdout, _ := newTestApp()
|
app, _, stdout, _ := newTestApp()
|
||||||
|
|
||||||
require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--config=" + filename, "mytopic"}))
|
require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename, "mytopic"}))
|
||||||
|
|
||||||
|
require.Equal(t, message, strings.TrimSpace(stdout.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCLI_Subscribe_Override_Default_UserPass_With_Empty_UserPass(t *testing.T) {
|
||||||
|
message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}`
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
require.Equal(t, "/mytopic/json", r.URL.Path)
|
||||||
|
require.Equal(t, "", r.Header.Get("Authorization"))
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(message))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
filename := filepath.Join(t.TempDir(), "client.yml")
|
||||||
|
require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(`
|
||||||
|
default-host: %s
|
||||||
|
default-user: philipp
|
||||||
|
default-password: mypass
|
||||||
|
subscribe:
|
||||||
|
- topic: mytopic
|
||||||
|
user: ""
|
||||||
|
password: ""
|
||||||
|
`, server.URL)), 0600))
|
||||||
|
|
||||||
|
app, _, stdout, _ := newTestApp()
|
||||||
|
|
||||||
|
require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename}))
|
||||||
|
|
||||||
|
require.Equal(t, message, strings.TrimSpace(stdout.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCLI_Subscribe_Override_Default_Token_With_Empty_Token(t *testing.T) {
|
||||||
|
message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}`
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
require.Equal(t, "/mytopic/json", r.URL.Path)
|
||||||
|
require.Equal(t, "", r.Header.Get("Authorization"))
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(message))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
filename := filepath.Join(t.TempDir(), "client.yml")
|
||||||
|
require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(`
|
||||||
|
default-host: %s
|
||||||
|
default-token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2
|
||||||
|
subscribe:
|
||||||
|
- topic: mytopic
|
||||||
|
token: ""
|
||||||
|
`, server.URL)), 0600))
|
||||||
|
|
||||||
|
app, _, stdout, _ := newTestApp()
|
||||||
|
|
||||||
|
require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename}))
|
||||||
|
|
||||||
require.Equal(t, message, strings.TrimSpace(stdout.String()))
|
require.Equal(t, message, strings.TrimSpace(stdout.String()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,9 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"heckel.io/ntfy/user"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.zio.sh/astra/ntfy/v2/server"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/test"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"heckel.io/ntfy/server"
|
|
||||||
"heckel.io/ntfy/test"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,9 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"heckel.io/ntfy/user"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,10 +2,10 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/server"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/test"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"heckel.io/ntfy/server"
|
|
||||||
"heckel.io/ntfy/test"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,13 +6,13 @@ import (
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"heckel.io/ntfy/user"
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/urfave/cli/v2/altsrc"
|
"github.com/urfave/cli/v2/altsrc"
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.zio.sh/astra/ntfy/v2/server"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/test"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"heckel.io/ntfy/server"
|
|
||||||
"heckel.io/ntfy/test"
|
|
||||||
"heckel.io/ntfy/user"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
|
@ -3,9 +3,9 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.zio.sh/astra/ntfy/v2/server"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"heckel.io/ntfy/server"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCLI_WebPush_GenerateKeys(t *testing.T) {
|
func TestCLI_WebPush_GenerateKeys(t *testing.T) {
|
||||||
|
|
|
@ -44,6 +44,14 @@ Here are a few working sample configs:
|
||||||
attachment-cache-dir: "/var/cache/ntfy/attachments"
|
attachment-cache-dir: "/var/cache/ntfy/attachments"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
=== "server.yml (behind proxy, with cache + attachments)"
|
||||||
|
``` yaml
|
||||||
|
base-url: "http://ntfy.example.com"
|
||||||
|
listen-http: ":2586"
|
||||||
|
cache-file: "/var/cache/ntfy/cache.db"
|
||||||
|
attachment-cache-dir: "/var/cache/ntfy/attachments"
|
||||||
|
```
|
||||||
|
|
||||||
=== "server.yml (ntfy.sh config)"
|
=== "server.yml (ntfy.sh config)"
|
||||||
``` yaml
|
``` yaml
|
||||||
# All the things: Behind a proxy, Firebase, cache, attachments,
|
# All the things: Behind a proxy, Firebase, cache, attachments,
|
||||||
|
@ -458,6 +466,31 @@ $ dig A mx1.ntfy.sh +short
|
||||||
3.139.215.220
|
3.139.215.220
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Local-only email
|
||||||
|
If you want to send emails from an internal service on the same network as your ntfy instance, you do not need to
|
||||||
|
worry about DNS records at all. Define a port for the SMTP server and pick an SMTP server domain (can be
|
||||||
|
anything).
|
||||||
|
|
||||||
|
=== "/etc/ntfy/server.yml"
|
||||||
|
``` yaml
|
||||||
|
smtp-server-listen: ":25"
|
||||||
|
smtp-server-domain: "example.com"
|
||||||
|
smtp-server-addr-prefix: "ntfy-" # optional
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, in the email settings of your internal service, set the SMTP server address to the IP address of your
|
||||||
|
ntfy instance. Set the port to the value you defined in `smtp-server-listen`. Leave any username and password
|
||||||
|
fields empty. In the "From" address, pick anything (e.g., "alerts@ntfy.sh"); the value doesn't matter.
|
||||||
|
In the "To" address, put in an email address that follows this pattern: `[topic]@[smtp-server-domain]` (or
|
||||||
|
`[smtp-server-addr-prefix][topic]@[smtp-server-domain]` if you set `smtp-server-addr-prefix`).
|
||||||
|
|
||||||
|
So if you used `example.com` as the SMTP server domain, and you want to send a message to the `email-alerts`
|
||||||
|
topic, set the "To" address to `email-alerts@example.com`. If the topic has access restrictions, you will need
|
||||||
|
to include an access token in the "To" address, such as `email-alerts+tk_AbC123dEf456@example.com`.
|
||||||
|
|
||||||
|
If the internal service lets you use define an email "Subject", it will become the title of the notification.
|
||||||
|
The body of the email will become the message of the notification.
|
||||||
|
|
||||||
## Behind a proxy (TLS, etc.)
|
## Behind a proxy (TLS, etc.)
|
||||||
!!! warning
|
!!! warning
|
||||||
If you are running ntfy behind a proxy, you must set the `behind-proxy` flag. Otherwise, all visitors are
|
If you are running ntfy behind a proxy, you must set the `behind-proxy` flag. Otherwise, all visitors are
|
||||||
|
@ -649,8 +682,8 @@ or the root domain:
|
||||||
<VirtualHost *:80>
|
<VirtualHost *:80>
|
||||||
ServerName ntfy.sh
|
ServerName ntfy.sh
|
||||||
|
|
||||||
# Proxy connections to ntfy (requires "a2enmod proxy")
|
# Proxy connections to ntfy (requires "a2enmod proxy proxy_http")
|
||||||
ProxyPass / http://127.0.0.1:2586/
|
ProxyPass / http://127.0.0.1:2586/ upgrade=websocket
|
||||||
ProxyPassReverse / http://127.0.0.1:2586/
|
ProxyPassReverse / http://127.0.0.1:2586/
|
||||||
|
|
||||||
SetEnv proxy-nokeepalive 1
|
SetEnv proxy-nokeepalive 1
|
||||||
|
@ -658,19 +691,13 @@ or the root domain:
|
||||||
|
|
||||||
# Higher than the max message size of 4096 bytes
|
# Higher than the max message size of 4096 bytes
|
||||||
LimitRequestBody 102400
|
LimitRequestBody 102400
|
||||||
|
|
||||||
# Enable mod_rewrite (requires "a2enmod rewrite")
|
|
||||||
RewriteEngine on
|
|
||||||
|
|
||||||
# WebSockets support (requires "a2enmod rewrite proxy_wstunnel")
|
|
||||||
RewriteCond %{HTTP:Upgrade} websocket [NC]
|
|
||||||
RewriteCond %{HTTP:Connection} upgrade [NC]
|
|
||||||
RewriteRule ^/?(.*) "ws://127.0.0.1:2586/$1" [P,L]
|
|
||||||
|
|
||||||
# Redirect HTTP to HTTPS, but only for GET topic addresses, since we want
|
# Redirect HTTP to HTTPS, but only for GET topic addresses, since we want
|
||||||
# it to work with curl without the annoying https:// prefix
|
# it to work with curl without the annoying https:// prefix (requires "a2enmod alias")
|
||||||
RewriteCond %{REQUEST_METHOD} GET
|
<If "%{REQUEST_METHOD} == 'GET'">
|
||||||
RewriteRule ^/([-_A-Za-z0-9]{0,64})$ https://%{SERVER_NAME}/$1 [R,L]
|
RedirectMatch permanent "^/([-_A-Za-z0-9]{0,64})$" "https://%{SERVER_NAME}/$1"
|
||||||
|
</If>
|
||||||
|
|
||||||
</VirtualHost>
|
</VirtualHost>
|
||||||
|
|
||||||
<VirtualHost *:443>
|
<VirtualHost *:443>
|
||||||
|
@ -681,8 +708,8 @@ or the root domain:
|
||||||
SSLCertificateKeyFile /etc/letsencrypt/live/ntfy.sh/privkey.pem
|
SSLCertificateKeyFile /etc/letsencrypt/live/ntfy.sh/privkey.pem
|
||||||
Include /etc/letsencrypt/options-ssl-apache.conf
|
Include /etc/letsencrypt/options-ssl-apache.conf
|
||||||
|
|
||||||
# Proxy connections to ntfy (requires "a2enmod proxy")
|
# Proxy connections to ntfy (requires "a2enmod proxy proxy_http")
|
||||||
ProxyPass / http://127.0.0.1:2586/
|
ProxyPass / http://127.0.0.1:2586/ upgrade=websocket
|
||||||
ProxyPassReverse / http://127.0.0.1:2586/
|
ProxyPassReverse / http://127.0.0.1:2586/
|
||||||
|
|
||||||
SetEnv proxy-nokeepalive 1
|
SetEnv proxy-nokeepalive 1
|
||||||
|
@ -690,14 +717,7 @@ or the root domain:
|
||||||
|
|
||||||
# Higher than the max message size of 4096 bytes
|
# Higher than the max message size of 4096 bytes
|
||||||
LimitRequestBody 102400
|
LimitRequestBody 102400
|
||||||
|
|
||||||
# Enable mod_rewrite (requires "a2enmod rewrite")
|
|
||||||
RewriteEngine on
|
|
||||||
|
|
||||||
# WebSockets support (requires "a2enmod rewrite proxy_wstunnel")
|
|
||||||
RewriteCond %{HTTP:Upgrade} websocket [NC]
|
|
||||||
RewriteCond %{HTTP:Connection} upgrade [NC]
|
|
||||||
RewriteRule ^/?(.*) "ws://127.0.0.1:2586/$1" [P,L]
|
|
||||||
</VirtualHost>
|
</VirtualHost>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1160,10 +1180,10 @@ and [here](https://easyengine.io/tutorials/nginx/block-wp-login-php-bruteforce-a
|
||||||
|
|
||||||
## Health checks
|
## Health checks
|
||||||
A preliminary health check API endpoint is exposed at `/v1/health`. The endpoint returns a `json` response in the format shown below.
|
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
|
```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.
|
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
|
### 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
|
`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. 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
|
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 -->
|
<!-- 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
|
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>
|
<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 }}
|
${{ 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)
|
## Watchtower (shoutrrr)
|
||||||
You can use [shoutrrr](https://containrrr.dev/shoutrrr/latest/services/ntfy/) to send
|
You can use [shoutrrr](https://containrrr.dev/shoutrrr/latest/services/ntfy/) to send
|
||||||
[Watchtower](https://github.com/containrrr/watchtower/) notifications to your ntfy topic.
|
[Watchtower](https://github.com/containrrr/watchtower/) notifications to your ntfy topic.
|
||||||
|
|
12
docs/faq.md
12
docs/faq.md
|
@ -76,6 +76,18 @@ However, if you still want to disable it, you can do so with the `web-root: disa
|
||||||
Think of the ntfy web app like an Android/iOS app. It is freely available and accessible to anyone, yet useless without
|
Think of the ntfy web app like an Android/iOS app. It is freely available and accessible to anyone, yet useless without
|
||||||
a proper backend. So as long as you secure your backend with ACLs, exposing the ntfy web app to the Internet is harmless.
|
a proper backend. So as long as you secure your backend with ACLs, exposing the ntfy web app to the Internet is harmless.
|
||||||
|
|
||||||
|
## If topic names are public, could I not just brute force them?
|
||||||
|
If you don't have [ACLs set up](config.md#access-control), the topic name is your password, it says so everywhere. If you
|
||||||
|
choose a easy-to-guess/dumb topic name, people will be able to guess it. If you choose a randomly generated topic name,
|
||||||
|
the topic is as good as a good password.
|
||||||
|
|
||||||
|
As for brute forcing: It's not possible to brute force a ntfy server for very long, as you'll get quickly rate limited.
|
||||||
|
In the default configuration, you'll be able to do 60 requests as a burst, and then 1 request per 10 seconds. Assuming you
|
||||||
|
choose a random 10 digit topic name using only A-Z, a-z, 0-9, _ and -, there are 64^10 possible topic names. Even if you
|
||||||
|
could do hundreds of requests per seconds (which you cannot), it would take many years to brute force a topic name.
|
||||||
|
|
||||||
|
For ntfy.sh, there's even a fail2ban in place which will ban your IP pretty quickly.
|
||||||
|
|
||||||
## Where can I donate?
|
## Where can I donate?
|
||||||
I have just very recently started accepting donations via [GitHub Sponsors](https://github.com/sponsors/binwiederhier).
|
I have just very recently started accepting donations via [GitHub Sponsors](https://github.com/sponsors/binwiederhier).
|
||||||
I would be humbled if you helped me carry the server and developer account costs. Even small donations are very much
|
I would be humbled if you helped me carry the server and developer account costs. Even small donations are very much
|
||||||
|
|
|
@ -14,14 +14,15 @@ We support amd64, armv7 and arm64.
|
||||||
|
|
||||||
1. Install ntfy using one of the methods described below
|
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))
|
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 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)
|
To send messages, use `ntfy publish`. To subscribe to topics, use `ntfy subscribe` (see [subscribing via CLI](subscribe/cli.md)
|
||||||
for details).
|
for details).
|
||||||
|
|
||||||
If you like video tutorials, check out :simple-youtube: [Kris Occhipinti's ntfy install guide](https://www.youtube.com/watch?v=bZzqrX05mNU).
|
If you like tutorials, check out :simple-youtube: [Kris Occhipinti's ntfy install guide](https://www.youtube.com/watch?v=bZzqrX05mNU) on YouTube, or
|
||||||
It's short and to the point. _I am not affiliated with Kris, I just liked the video._
|
[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
|
## Linux binaries
|
||||||
Please check out the [releases page](https://github.com/binwiederhier/ntfy/releases) for binaries and
|
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"
|
=== "x86_64/amd64"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_amd64.tar.gz
|
wget https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_amd64.tar.gz
|
||||||
tar zxvf ntfy_2.6.2_linux_amd64.tar.gz
|
tar zxvf ntfy_2.7.0_linux_amd64.tar.gz
|
||||||
sudo cp -a ntfy_2.6.2_linux_amd64/ntfy /usr/local/bin/ntfy
|
sudo cp -a ntfy_2.7.0_linux_amd64/ntfy /usr/local/bin/ntfy
|
||||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.2_linux_amd64/{client,server}/*.yml /etc/ntfy
|
sudo mkdir /etc/ntfy && sudo cp ntfy_2.7.0_linux_amd64/{client,server}/*.yml /etc/ntfy
|
||||||
sudo ntfy serve
|
sudo ntfy serve
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "armv6"
|
=== "armv6"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_armv6.tar.gz
|
wget https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_armv6.tar.gz
|
||||||
tar zxvf ntfy_2.6.2_linux_armv6.tar.gz
|
tar zxvf ntfy_2.7.0_linux_armv6.tar.gz
|
||||||
sudo cp -a ntfy_2.6.2_linux_armv6/ntfy /usr/bin/ntfy
|
sudo cp -a ntfy_2.7.0_linux_armv6/ntfy /usr/bin/ntfy
|
||||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.2_linux_armv6/{client,server}/*.yml /etc/ntfy
|
sudo mkdir /etc/ntfy && sudo cp ntfy_2.7.0_linux_armv6/{client,server}/*.yml /etc/ntfy
|
||||||
sudo ntfy serve
|
sudo ntfy serve
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "armv7/armhf"
|
=== "armv7/armhf"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_armv7.tar.gz
|
wget https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_armv7.tar.gz
|
||||||
tar zxvf ntfy_2.6.2_linux_armv7.tar.gz
|
tar zxvf ntfy_2.7.0_linux_armv7.tar.gz
|
||||||
sudo cp -a ntfy_2.6.2_linux_armv7/ntfy /usr/bin/ntfy
|
sudo cp -a ntfy_2.7.0_linux_armv7/ntfy /usr/bin/ntfy
|
||||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.2_linux_armv7/{client,server}/*.yml /etc/ntfy
|
sudo mkdir /etc/ntfy && sudo cp ntfy_2.7.0_linux_armv7/{client,server}/*.yml /etc/ntfy
|
||||||
sudo ntfy serve
|
sudo ntfy serve
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "arm64"
|
=== "arm64"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_arm64.tar.gz
|
wget https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_arm64.tar.gz
|
||||||
tar zxvf ntfy_2.6.2_linux_arm64.tar.gz
|
tar zxvf ntfy_2.7.0_linux_arm64.tar.gz
|
||||||
sudo cp -a ntfy_2.6.2_linux_arm64/ntfy /usr/bin/ntfy
|
sudo cp -a ntfy_2.7.0_linux_arm64/ntfy /usr/bin/ntfy
|
||||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.2_linux_arm64/{client,server}/*.yml /etc/ntfy
|
sudo mkdir /etc/ntfy && sudo cp ntfy_2.7.0_linux_arm64/{client,server}/*.yml /etc/ntfy
|
||||||
sudo ntfy serve
|
sudo ntfy serve
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -109,7 +110,7 @@ Manually installing the .deb file:
|
||||||
|
|
||||||
=== "x86_64/amd64"
|
=== "x86_64/amd64"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_amd64.deb
|
wget https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_amd64.deb
|
||||||
sudo dpkg -i ntfy_*.deb
|
sudo dpkg -i ntfy_*.deb
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
|
@ -117,7 +118,7 @@ Manually installing the .deb file:
|
||||||
|
|
||||||
=== "armv6"
|
=== "armv6"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_armv6.deb
|
wget https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_armv6.deb
|
||||||
sudo dpkg -i ntfy_*.deb
|
sudo dpkg -i ntfy_*.deb
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
|
@ -125,7 +126,7 @@ Manually installing the .deb file:
|
||||||
|
|
||||||
=== "armv7/armhf"
|
=== "armv7/armhf"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_armv7.deb
|
wget https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_armv7.deb
|
||||||
sudo dpkg -i ntfy_*.deb
|
sudo dpkg -i ntfy_*.deb
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
|
@ -133,7 +134,7 @@ Manually installing the .deb file:
|
||||||
|
|
||||||
=== "arm64"
|
=== "arm64"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_arm64.deb
|
wget https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_arm64.deb
|
||||||
sudo dpkg -i ntfy_*.deb
|
sudo dpkg -i ntfy_*.deb
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
|
@ -143,28 +144,28 @@ Manually installing the .deb file:
|
||||||
|
|
||||||
=== "x86_64/amd64"
|
=== "x86_64/amd64"
|
||||||
```bash
|
```bash
|
||||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_amd64.rpm
|
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_amd64.rpm
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "armv6"
|
=== "armv6"
|
||||||
```bash
|
```bash
|
||||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_armv6.rpm
|
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_armv6.rpm
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "armv7/armhf"
|
=== "armv7/armhf"
|
||||||
```bash
|
```bash
|
||||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_armv7.rpm
|
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_armv7.rpm
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "arm64"
|
=== "arm64"
|
||||||
```bash
|
```bash
|
||||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_arm64.rpm
|
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_linux_arm64.rpm
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
```
|
```
|
||||||
|
@ -194,18 +195,18 @@ NixOS also supports [declarative setup of the ntfy server](https://search.nixos.
|
||||||
|
|
||||||
## macOS
|
## macOS
|
||||||
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on macOS as well.
|
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.6.2/ntfy_2.6.2_darwin_all.tar.gz),
|
To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_darwin_all.tar.gz),
|
||||||
extract it and place it somewhere in your `PATH` (e.g. `/usr/local/bin/ntfy`).
|
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
|
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).
|
`~/Library/Application Support/ntfy/client.yml` (sample included in the tarball).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_darwin_all.tar.gz > ntfy_2.6.2_darwin_all.tar.gz
|
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.6.2_darwin_all.tar.gz
|
tar zxvf ntfy_2.7.0_darwin_all.tar.gz
|
||||||
sudo cp -a ntfy_2.6.2_darwin_all/ntfy /usr/local/bin/ntfy
|
sudo cp -a ntfy_2.7.0_darwin_all/ntfy /usr/local/bin/ntfy
|
||||||
mkdir ~/Library/Application\ Support/ntfy
|
mkdir ~/Library/Application\ Support/ntfy
|
||||||
cp ntfy_2.6.2_darwin_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
|
cp ntfy_2.7.0_darwin_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
|
||||||
ntfy --help
|
ntfy --help
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -223,7 +224,7 @@ brew install ntfy
|
||||||
|
|
||||||
## Windows
|
## Windows
|
||||||
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on Windows as well.
|
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.6.2/ntfy_2.6.2_windows_amd64.zip),
|
To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.7.0/ntfy_2.7.0_windows_amd64.zip),
|
||||||
extract it and place the `ntfy.exe` binary somewhere in your `%Path%`.
|
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).
|
The default path for the client config file is at `%AppData%\ntfy\client.yml` (not created automatically, sample in the ZIP file).
|
||||||
|
|
|
@ -23,6 +23,8 @@ I've added a ⭐ to projects or posts that have a significant following, or had
|
||||||
- [Platypush](https://docs.platypush.tech/platypush/plugins/ntfy.html) - Automation platform aimed to run on any device that can run Python
|
- [Platypush](https://docs.platypush.tech/platypush/plugins/ntfy.html) - Automation platform aimed to run on any device that can run Python
|
||||||
- [diun](https://crazymax.dev/diun/) - Docker Image Update Notifier
|
- [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
|
- [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.
|
## Integration via HTTP/SMTP/etc.
|
||||||
|
|
||||||
|
@ -56,6 +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](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)
|
- [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)
|
- [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)
|
||||||
|
- [ntfy-java](https://github.com/MaheshBabu11/ntfy-java/) - A Java package to interact with a ntfy server (Java)
|
||||||
|
|
||||||
## CLIs + GUIs
|
## CLIs + GUIs
|
||||||
|
|
||||||
|
@ -126,9 +130,31 @@ I've added a ⭐ to projects or posts that have a significant following, or had
|
||||||
- [msgdrop](https://github.com/jbrubake/msgdrop) - Send and receive encrypted messages (Bash)
|
- [msgdrop](https://github.com/jbrubake/msgdrop) - Send and receive encrypted messages (Bash)
|
||||||
- [vigilant](https://github.com/VerifiedJoseph/vigilant) - Monitor RSS/ATOM and JSON feeds, and send push notifications on new entries (PHP)
|
- [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
|
- [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-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)
|
||||||
|
|
||||||
## Blog + forum posts
|
## 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
|
||||||
|
- [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
|
- [Basic website monitoring using cronjobs and ntfy.sh](https://burkhardt.dev/2023/website-monitoring-cron-ntfy/) - burkhardt.dev - 6/2023
|
||||||
- [Pingdom alternative in one line of curl through ntfy.sh](https://piqoni.bearblog.dev/uptime-monitoring-in-one-line-of-curl/) - bearblog.dev - 6/2023
|
- [Pingdom alternative in one line of curl through ntfy.sh](https://piqoni.bearblog.dev/uptime-monitoring-in-one-line-of-curl/) - bearblog.dev - 6/2023
|
||||||
- [#OpenSourceDiscovery 78: ntfy.sh](https://opensourcedisc.substack.com/p/opensourcediscovery-78-ntfysh) - opensourcedisc.substack.com - 6/2023
|
- [#OpenSourceDiscovery 78: ntfy.sh](https://opensourcedisc.substack.com/p/opensourcediscovery-78-ntfysh) - opensourcedisc.substack.com - 6/2023
|
||||||
|
@ -155,6 +181,7 @@ I've added a ⭐ to projects or posts that have a significant following, or had
|
||||||
- [NTFY - système de notification hyper simple et complet](https://www.youtube.com/watch?v=UieZYWVVgA4) - youtube.com - 12/2022
|
- [NTFY - système de notification hyper simple et complet](https://www.youtube.com/watch?v=UieZYWVVgA4) - youtube.com - 12/2022
|
||||||
- [ntfy.sh](https://paramdeo.com/til/ntfy-sh) - paramdeo.com - 11/2022
|
- [ntfy.sh](https://paramdeo.com/til/ntfy-sh) - paramdeo.com - 11/2022
|
||||||
- [Using ntfy to warn me when my computer is discharging](https://ulysseszh.github.io/programming/2022/11/28/ntfy-warn-discharge.html) - ulysseszh.github.io - 11/2022
|
- [Using ntfy to warn me when my computer is discharging](https://ulysseszh.github.io/programming/2022/11/28/ntfy-warn-discharge.html) - ulysseszh.github.io - 11/2022
|
||||||
|
- [Enabling SSH Login Notifications using Ntfy](https://paramdeo.com/blog/enabling-ssh-login-notifications-using-ntfy) - paramdeo.com - 11/2022
|
||||||
- [ntfy - Push Notification Service](https://dizzytech.de/posts/ntfy/) - dizzytech.de - 11/2022
|
- [ntfy - Push Notification Service](https://dizzytech.de/posts/ntfy/) - dizzytech.de - 11/2022
|
||||||
- [Console #132](https://console.substack.com/p/console-132) ⭐ - console.substack.com - 11/2022
|
- [Console #132](https://console.substack.com/p/console-132) ⭐ - console.substack.com - 11/2022
|
||||||
- [How to make my phone buzz*](https://evbogue.com/howtomakemyphonebuzz) - evbogue.com - 11/2022
|
- [How to make my phone buzz*](https://evbogue.com/howtomakemyphonebuzz) - evbogue.com - 11/2022
|
||||||
|
@ -211,6 +238,7 @@ ntfy community. Thanks to everyone running a public server. **You guys rock!**
|
||||||
| [ntfy.envs.net](https://ntfy.envs.net) | 🇩🇪 Germany |
|
| [ntfy.envs.net](https://ntfy.envs.net) | 🇩🇪 Germany |
|
||||||
| [ntfy.mzte.de](https://ntfy.mzte.de/) | 🇩🇪 Germany |
|
| [ntfy.mzte.de](https://ntfy.mzte.de/) | 🇩🇪 Germany |
|
||||||
| [ntfy.hostux.net](https://ntfy.hostux.net/) | 🇫🇷 France |
|
| [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
|
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**.
|
and uptime of third party servers, so use of each server is **at your own discretion**.
|
||||||
|
|
|
@ -27,11 +27,12 @@ Be sure that in your selfhosted server:
|
||||||
* Set `upstream-base-url: "https://ntfy.sh"` (**not your own hostname!**)
|
* Set `upstream-base-url: "https://ntfy.sh"` (**not your own hostname!**)
|
||||||
* Ensure that the URL you set in `base-url` **matches exactly** what you set the Default Server in iOS to
|
* Ensure that the URL you set in `base-url` **matches exactly** what you set the Default Server in iOS to
|
||||||
|
|
||||||
## Firefox on Android not automatically subscribing to web push (see [#789](https://github.com/binwiederhier/ntfy/issues/789))
|
## iOS app seeing "New message", but not real message content
|
||||||
ntfy defaults to web-push based subscriptions when installed as a [progressive web app](./subscribe/pwa.md). Firefox
|
If you see `New message` notifications on iOS, your iPhone can likely not talk to your self-hosted server. Be sure that
|
||||||
Android has an [open bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1796434) where it reports the PWA mode incorrectly.
|
your iOS device and your ntfy server are either on the same network, or that your phone can actually reach the server.
|
||||||
This causes ntfy to not automatically subscribe to web push, and requires you to go to the ntfy Settings page to enable
|
|
||||||
it manually.
|
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).
|
||||||
|
|
||||||
## Safari does not play sounds for web push notifications
|
## 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
|
Safari does not support playing sounds for web push notifications, and treats them all as silent. This will be fixed with
|
||||||
|
|
|
@ -457,6 +457,7 @@ You can set the priority with the header `X-Priority` (or any of its aliases: `P
|
||||||
=== "PowerShell"
|
=== "PowerShell"
|
||||||
``` powershell
|
``` powershell
|
||||||
$Request = @{
|
$Request = @{
|
||||||
|
Method = 'POST'
|
||||||
URI = "https://ntfy.sh/phil_alerts"
|
URI = "https://ntfy.sh/phil_alerts"
|
||||||
Headers = @{
|
Headers = @{
|
||||||
Priority = "5"
|
Priority = "5"
|
||||||
|
@ -1033,7 +1034,7 @@ is the only required one:
|
||||||
$Request = @{
|
$Request = @{
|
||||||
Method = "POST"
|
Method = "POST"
|
||||||
URI = "https://ntfy.sh"
|
URI = "https://ntfy.sh"
|
||||||
Body = @{
|
Body = ConvertTo-JSON @{
|
||||||
Topic = "mytopic"
|
Topic = "mytopic"
|
||||||
Title = "Low disk space alert"
|
Title = "Low disk space alert"
|
||||||
Message = "Disk space is low at 5.1 GB"
|
Message = "Disk space is low at 5.1 GB"
|
||||||
|
@ -1042,7 +1043,7 @@ is the only required one:
|
||||||
FileName = "diskspace.jpg"
|
FileName = "diskspace.jpg"
|
||||||
Tags = @("warning", "cd")
|
Tags = @("warning", "cd")
|
||||||
Click = "https://homecamera.lan/xasds1h2xsSsa/"
|
Click = "https://homecamera.lan/xasds1h2xsSsa/"
|
||||||
Actions = ConvertTo-JSON @(
|
Actions = @(
|
||||||
@{
|
@{
|
||||||
Action = "view"
|
Action = "view"
|
||||||
Label = "Admin panel"
|
Label = "Admin panel"
|
||||||
|
@ -1130,7 +1131,7 @@ As of today, the following actions are supported:
|
||||||
when the action button is tapped (only supported on Android)
|
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
|
* [`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>
|
<figure markdown>
|
||||||
{ width=500 }
|
{ width=500 }
|
||||||
|
@ -1919,10 +1920,10 @@ And the same example using [JSON publishing](#publish-as-json):
|
||||||
$Request = @{
|
$Request = @{
|
||||||
Method = "POST"
|
Method = "POST"
|
||||||
URI = "https://ntfy.sh"
|
URI = "https://ntfy.sh"
|
||||||
Body = @{
|
Body = ConvertTo-Json -Depth 3 @{
|
||||||
Topic = "wifey"
|
Topic = "wifey"
|
||||||
Message = "Your wife requested you send a picture of yourself."
|
Message = "Your wife requested you send a picture of yourself."
|
||||||
Actions = ConvertTo-Json -Depth 3 @(
|
Actions = @(
|
||||||
@{
|
@{
|
||||||
Action = "broadcast"
|
Action = "broadcast"
|
||||||
Label = "Take picture"
|
Label = "Take picture"
|
||||||
|
@ -2072,7 +2073,7 @@ Here's an example using the [`X-Actions` header](#using-a-header):
|
||||||
'method' => 'POST',
|
'method' => 'POST',
|
||||||
'header' =>
|
'header' =>
|
||||||
"Content-Type: text/plain\r\n" .
|
"Content-Type: text/plain\r\n" .
|
||||||
"Actions: http, Close door, https://api.mygarage.lan/, method=PUT, headers.Authorization=Bearer zAzsx1sk.., body={\"action\": \"close\"}",
|
'Actions: http, Close door, https://api.mygarage.lan/, method=PUT, headers.Authorization=Bearer zAzsx1sk.., body={\"action\": \"close\"}',
|
||||||
'content' => 'Garage door has been open for 15 minutes. Close it?'
|
'content' => 'Garage door has been open for 15 minutes. Close it?'
|
||||||
]
|
]
|
||||||
]));
|
]));
|
||||||
|
@ -2199,10 +2200,10 @@ And the same example using [JSON publishing](#publish-as-json):
|
||||||
$Request = @{
|
$Request = @{
|
||||||
Method = "POST"
|
Method = "POST"
|
||||||
URI = "https://ntfy.sh"
|
URI = "https://ntfy.sh"
|
||||||
Body = @{
|
Body = ConvertTo-Json -Depth 3 @{
|
||||||
Topic = "myhome"
|
Topic = "myhome"
|
||||||
Message = "Garage door has been open for 15 minutes. Close it?"
|
Message = "Garage door has been open for 15 minutes. Close it?"
|
||||||
Actions = ConvertTo-Json -Depth 3 @(
|
Actions = @(
|
||||||
@{
|
@{
|
||||||
Action = "http"
|
Action = "http"
|
||||||
Label = "Close door"
|
Label = "Close door"
|
||||||
|
@ -2287,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
|
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.
|
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
|
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.
|
by another app, the responsible app may open.
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,38 @@
|
||||||
Binaries for all releases can be found on the GitHub releases pages for the [ntfy server](https://github.com/binwiederhier/ntfy/releases)
|
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).
|
and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/releases).
|
||||||
|
|
||||||
|
## ntfy server v2.7.0
|
||||||
|
Released August 17, 2023
|
||||||
|
|
||||||
|
This release ships Markdown support for the web app (not in the Android app yet), and adds support for
|
||||||
|
right-to-left languages (RTL) in the web app. It also fixes a few issues around date/time formatting,
|
||||||
|
internationalization support, a CLI auth bug.
|
||||||
|
|
||||||
|
Furthermore, it fixes a security issue around access tokens getting erroneously deleted for other users
|
||||||
|
in a specific scenario. This was a denial-of-service-type security issue, since it **effectively allowed a
|
||||||
|
single user to deny access to all other users of a ntfy instance**. Please note that while tokens were
|
||||||
|
erroneously deleted, **nobody but the token owner ever had access to it.** Please refer to [the ticket](https://github.com/binwiederhier/ntfy/issues/838)
|
||||||
|
for details. **Please upgrade your ntfy instance if you run a multi-user system.**
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
|
||||||
|
* Add support for [Markdown formatting](publish.md#markdown-formatting) in web app ([#310](https://github.com/binwiederhier/ntfy/issues/310), thanks to [@nihalgonsalves](https://github.com/nihalgonsalves))
|
||||||
|
* Add support for right-to-left languages (RTL) in the web app ([#663](https://github.com/binwiederhier/ntfy/issues/663), thanks to [@nimbleghost](https://github.com/nimbleghost))
|
||||||
|
|
||||||
|
**Security:** ⚠️
|
||||||
|
|
||||||
|
* Fixes issue with access tokens getting deleted ([#838](https://github.com/binwiederhier/ntfy/issues/838))
|
||||||
|
|
||||||
|
**Bug fixes + maintenance:**
|
||||||
|
|
||||||
|
* Fix issues with date/time with different locales ([#700](https://github.com/binwiederhier/ntfy/issues/700), thanks to [@nimbleghost](https://github.com/nimbleghost))
|
||||||
|
* Re-init i18n on each service worker message to avoid missing translations ([#817](https://github.com/binwiederhier/ntfy/pull/817), thanks to [@nihalgonsalves](https://github.com/nihalgonsalves))
|
||||||
|
* You can now unset the default user:pass/token in `client.yml` for an individual subscription to remove the Authorization header ([#829](https://github.com/binwiederhier/ntfy/issues/829), thanks to [@tomeon](https://github.com/tomeon) for reporting and to [@wunter8](https://github.com/wunter8) for fixing)
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
|
||||||
|
* Update docs for Apache config ([#819](https://github.com/binwiederhier/ntfy/pull/819), thanks to [@nisbet-hubbard](https://github.com/nisbet-hubbard))
|
||||||
|
|
||||||
## ntfy server v2.6.2
|
## ntfy server v2.6.2
|
||||||
Released June 30, 2023
|
Released June 30, 2023
|
||||||
|
|
||||||
|
@ -78,7 +110,7 @@ if you use promo code `MYTOPIC`). ntfy will always remain open source.
|
||||||
## ntfy server v2.4.0
|
## ntfy server v2.4.0
|
||||||
Released Apr 26, 2023
|
Released Apr 26, 2023
|
||||||
|
|
||||||
This release adds a tiny `v1/stats` endpoint to expose how many messages have been published, and adds suport to encode the `X-Title`,
|
This release adds a tiny `v1/stats` endpoint to expose how many messages have been published, and adds support to encode the `X-Title`,
|
||||||
`X-Message` and `X-Tags` header as RFC 2047. It's a pretty small release, and mainly enables the release of the new ntfy.sh website.
|
`X-Message` and `X-Tags` header as RFC 2047. It's a pretty small release, and mainly enables the release of the new ntfy.sh website.
|
||||||
|
|
||||||
❤️ If you like ntfy, **please consider sponsoring me** via [GitHub Sponsors](https://github.com/sponsors/binwiederhier)
|
❤️ If you like ntfy, **please consider sponsoring me** via [GitHub Sponsors](https://github.com/sponsors/binwiederhier)
|
||||||
|
@ -1241,7 +1273,7 @@ Released Dec 28, 2021
|
||||||
|
|
||||||
**Features & bug fixes:**
|
**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
|
* Server-side work to support [unifiedpush.org](https://unifiedpush.org) #64
|
||||||
* Fixing the Santa bug #65
|
* Fixing the Santa bug #65
|
||||||
|
|
||||||
|
@ -1251,16 +1283,15 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
|
||||||
|
|
||||||
## Not released yet
|
## Not released yet
|
||||||
|
|
||||||
### ntfy server v2.7.0 (UNRELEASED)
|
### ntfy server v2.8.0 (UNRELEASED)
|
||||||
|
|
||||||
**Features:**
|
|
||||||
|
|
||||||
* Add support for [Markdown formatting](publish.md#markdown-formatting) in web app ([#310](https://github.com/binwiederhier/ntfy/issues/310), thanks to [@nihalgonsalves](https://github.com/nihalgonsalves))
|
|
||||||
* Add support for right-to-left languages (RTL) in the web app ([#663](https://github.com/binwiederhier/ntfy/issues/663), thanks to [@nimbleghost](https://github.com/nimbleghost))
|
|
||||||
|
|
||||||
**Bug fixes + maintenance:**
|
**Bug fixes + maintenance:**
|
||||||
|
|
||||||
* Fix issues with date/time with different locales ([#700](https://github.com/binwiederhier/ntfy/issues/700), thanks to [@nimbleghost](https://github.com/nimbleghost))
|
* 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))
|
||||||
|
* 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))
|
||||||
|
|
||||||
### ntfy Android app v1.16.1 (UNRELEASED)
|
### ntfy Android app v1.16.1 (UNRELEASED)
|
||||||
|
|
||||||
|
|
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 |
|
@ -10,7 +10,7 @@ to topics via the ntfy CLI. The CLI is included in the same `ntfy` binary that c
|
||||||
## Install + configure
|
## Install + configure
|
||||||
To install the ntfy CLI, simply **follow the steps outlined on the [install page](../install.md)**. The ntfy server and
|
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
|
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.
|
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**,
|
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>
|
<a href="../../static/img/pwa-badge.png"><img src="../../static/img/pwa-badge.png"/></a>
|
||||||
</div>
|
</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
|
### 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"
|
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,
|
in the menu, and then click "Install" in the popup menu. After installation, you can find the app in your app drawer,
|
||||||
|
|
81
go.mod
81
go.mod
|
@ -1,43 +1,45 @@
|
||||||
module heckel.io/ntfy
|
module git.zio.sh/astra/ntfy/v2
|
||||||
|
|
||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/firestore v1.11.0 // indirect
|
cloud.google.com/go/firestore v1.14.0 // indirect
|
||||||
cloud.google.com/go/storage v1.31.0 // indirect
|
cloud.google.com/go/storage v1.34.1 // indirect
|
||||||
github.com/BurntSushi/toml v1.3.2 // 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.17.0
|
github.com/emersion/go-smtp v0.18.0
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2
|
github.com/gabriel-vasile/mimetype v1.4.3
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.1
|
||||||
github.com/mattn/go-sqlite3 v1.14.17
|
github.com/mattn/go-sqlite3 v1.14.18
|
||||||
github.com/olebedev/when v1.0.0
|
github.com/olebedev/when v1.0.0
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.1
|
||||||
github.com/urfave/cli/v2 v2.25.7
|
github.com/urfave/cli/v2 v2.25.7
|
||||||
golang.org/x/crypto v0.11.0
|
golang.org/x/crypto v0.14.0
|
||||||
golang.org/x/oauth2 v0.10.0 // indirect
|
golang.org/x/oauth2 v0.13.0 // indirect
|
||||||
golang.org/x/sync v0.3.0
|
golang.org/x/sync v0.5.0
|
||||||
golang.org/x/term v0.10.0
|
golang.org/x/term v0.13.0
|
||||||
golang.org/x/time v0.3.0
|
golang.org/x/time v0.4.0
|
||||||
google.golang.org/api v0.130.0
|
google.golang.org/api v0.149.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace github.com/emersion/go-smtp => github.com/emersion/go-smtp v0.17.0 // Pin version due to breaking changes, see #839
|
||||||
|
|
||||||
require github.com/pkg/errors v0.9.1 // indirect
|
require github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
|
||||||
require (
|
require (
|
||||||
firebase.google.com/go/v4 v4.11.0
|
firebase.google.com/go/v4 v4.12.1
|
||||||
github.com/SherClockHolmes/webpush-go v1.2.0
|
github.com/SherClockHolmes/webpush-go v1.3.0
|
||||||
github.com/prometheus/client_golang v1.16.0
|
github.com/prometheus/client_golang v1.17.0
|
||||||
github.com/stripe/stripe-go/v74 v74.25.0
|
github.com/stripe/stripe-go/v74 v74.30.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.110.4 // indirect
|
cloud.google.com/go v0.110.10 // indirect
|
||||||
cloud.google.com/go/compute v1.20.1 // indirect
|
cloud.google.com/go/compute v1.23.3 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||||
cloud.google.com/go/iam v1.1.1 // indirect
|
cloud.google.com/go/iam v1.1.5 // indirect
|
||||||
cloud.google.com/go/longrunning v0.5.1 // indirect
|
cloud.google.com/go/longrunning v0.5.4 // indirect
|
||||||
github.com/AlekSi/pointer v1.2.0 // indirect
|
github.com/AlekSi/pointer v1.2.0 // indirect
|
||||||
github.com/MicahParks/keyfunc v1.9.0 // indirect
|
github.com/MicahParks/keyfunc v1.9.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
@ -48,31 +50,30 @@ require (
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // 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/s2a-go v0.1.4 // indirect
|
github.com/google/uuid v1.4.0 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
|
|
||||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||||
github.com/kr/text v0.2.0 // 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/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.4.0 // indirect
|
github.com/prometheus/client_model v0.5.0 // indirect
|
||||||
github.com/prometheus/common v0.44.0 // indirect
|
github.com/prometheus/common v0.45.0 // indirect
|
||||||
github.com/prometheus/procfs v0.11.0 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/stretchr/objx v0.5.0 // indirect
|
github.com/stretchr/objx v0.5.0 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
golang.org/x/net v0.12.0 // indirect
|
golang.org/x/net v0.17.0 // indirect
|
||||||
golang.org/x/sys v0.10.0 // indirect
|
golang.org/x/sys v0.14.0 // indirect
|
||||||
golang.org/x/text v0.11.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
google.golang.org/appengine/v2 v2.0.3 // indirect
|
google.golang.org/appengine/v2 v2.0.5 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect
|
google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect
|
||||||
google.golang.org/grpc v1.56.2 // indirect
|
google.golang.org/grpc v1.59.0 // indirect
|
||||||
google.golang.org/protobuf v1.31.0 // indirect
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
225
go.sum
225
go.sum
|
@ -1,23 +1,20 @@
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y=
|
||||||
cloud.google.com/go v0.110.3 h1:wwearW+L7sAPSomPIgJ3bVn6Ck00HGQnn5HMLwf0azo=
|
cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic=
|
||||||
cloud.google.com/go v0.110.3/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI=
|
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
||||||
cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk=
|
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
|
||||||
cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI=
|
|
||||||
cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg=
|
|
||||||
cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
|
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
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/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||||
cloud.google.com/go/firestore v1.11.0 h1:PPgtwcYUOXV2jFe1bV3nda3RCrOa8cvBjTOn2MQVfW8=
|
cloud.google.com/go/firestore v1.14.0 h1:8aLcKnMPoldYU3YHgu4t2exrKhLQkqaXAGqT0ljrFVw=
|
||||||
cloud.google.com/go/firestore v1.11.0/go.mod h1:b38dKhgzlmNNGTNZZwe7ZRFEuRab1Hay3/DBsIGKKy4=
|
cloud.google.com/go/firestore v1.14.0/go.mod h1:96MVaHLsEhbvkBEdZgfN+AS/GIkco1LRpH9Xp9YZfzQ=
|
||||||
cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y=
|
cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
|
||||||
cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU=
|
cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
|
||||||
cloud.google.com/go/longrunning v0.5.1 h1:Fr7TXftcqTudoyRJa113hyaqlGdiBQkp0Gq7tErFDWI=
|
cloud.google.com/go/longrunning v0.5.4 h1:w8xEcbZodnA2BbW6sVirkkoC+1gP8wS57EUUgGS0GVg=
|
||||||
cloud.google.com/go/longrunning v0.5.1/go.mod h1:spvimkwdz6SPWKEt/XBij79E9fiTkHSQl/fRUUQJYJc=
|
cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI=
|
||||||
cloud.google.com/go/storage v1.31.0 h1:+S3LjjEN2zZ+L5hOwj4+1OkGCsLVe0NzpXKQ1pSdTCI=
|
cloud.google.com/go/storage v1.34.1 h1:H2Af2dU5J0PF7A5B+ECFIce+RqxVnrVilO+cu0TS3MI=
|
||||||
cloud.google.com/go/storage v1.31.0/go.mod h1:81ams1PrhW16L4kF7qg+4mTq7SRs5HsbDTM0bWvrwJ0=
|
cloud.google.com/go/storage v1.34.1/go.mod h1:VN1ElqqvR9adg1k9xlkUJ55cMOP1/QjnNNuT5xQL6dY=
|
||||||
firebase.google.com/go/v4 v4.11.0 h1:szjBoiF33A2FavRLIDZjW1mw+OsW/XAtHoYNIqWOjRk=
|
firebase.google.com/go/v4 v4.12.1 h1:tDNvobifGsx/1HSFLnM0fmNfx/CDZSgsTO2KhZtgpcs=
|
||||||
firebase.google.com/go/v4 v4.11.0/go.mod h1:60c36dWLK4+j05Vw5XMllek3b3PCynU3BfI46OSwsUE=
|
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 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
|
||||||
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
|
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
@ -25,24 +22,17 @@ github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8
|
||||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
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 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o=
|
||||||
github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw=
|
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.3.0 h1:CAu3FvEE9QS4drc3iKNgpBWFfGqNthKlZhp5QpYnu6k=
|
||||||
github.com/SherClockHolmes/webpush-go v1.2.0/go.mod h1:w6X47YApe/B9wUz2Wh8xukxlyupaxSSEbu6yKJcHN2w=
|
github.com/SherClockHolmes/webpush-go v1.3.0/go.mod h1:AxRHmJuYwKGG1PVgYzToik1lphQvDnqFYDqimHvwhIw=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
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/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
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/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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
|
||||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
||||||
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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
@ -50,19 +40,14 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
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 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-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||||
github.com/emersion/go-smtp v0.16.0 h1:eB9CY9527WdEZSs5sWisTmilDX7gG+Q/2IdRcmubpa8=
|
|
||||||
github.com/emersion/go-smtp v0.16.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
|
||||||
github.com/emersion/go-smtp v0.17.0 h1:tq90evlrcyqRfE6DSXaWVH54oX6OuZOQECEmhWBMEtI=
|
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/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.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.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/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
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.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
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 v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||||
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
|
@ -74,16 +59,13 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
@ -96,46 +78,41 @@ 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.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.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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
|
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
|
||||||
github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
|
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||||
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
|
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.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||||
github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
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/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.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
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.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
|
||||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
||||||
github.com/olebedev/when v1.0.0 h1:T2DZCj8HxUhOVxcqaLOmzuTr+iZLtMHsZEim7mjIA2w=
|
github.com/olebedev/when v1.0.0 h1:T2DZCj8HxUhOVxcqaLOmzuTr+iZLtMHsZEim7mjIA2w=
|
||||||
github.com/olebedev/when v1.0.0/go.mod h1:T0THb4kP9D3NNqlvCwIG4GyUioTAzEhB4RNVzig/43E=
|
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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
|
||||||
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
|
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.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
|
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||||
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||||
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
|
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
|
||||||
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
|
||||||
github.com/prometheus/procfs v0.11.0 h1:5EAgkfkMl659uZPbe9AS2N68a7Cc1TJbPEuGzFuRbyk=
|
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||||
github.com/prometheus/procfs v0.11.0/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
@ -143,16 +120,13 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
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.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.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stripe/stripe-go/v74 v74.24.0 h1:h+hXEI5avC5moAh2YLtphMFTBnp11TfXTcP4suuWDLk=
|
github.com/stripe/stripe-go/v74 v74.30.0 h1:0Kf0KkeFnY7iRhOwvTerX0Ia1BRw+eV1CVJ51mGYAUY=
|
||||||
github.com/stripe/stripe-go/v74 v74.24.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaBRvGAimAO+udbBw=
|
github.com/stripe/stripe-go/v74 v74.30.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaBRvGAimAO+udbBw=
|
||||||
github.com/stripe/stripe-go/v74 v74.25.0 h1:mGJp9L1ymxjFvq5MlmG6ynv/fAGX6LLU8MyMVsiRAMY=
|
|
||||||
github.com/stripe/stripe-go/v74 v74.25.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaBRvGAimAO+udbBw=
|
|
||||||
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
|
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
|
||||||
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||||
|
@ -160,80 +134,72 @@ 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=
|
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 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
|
||||||
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-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-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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
|
||||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
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-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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
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.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-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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
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-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
|
||||||
golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs=
|
golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
|
||||||
golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw=
|
|
||||||
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
|
|
||||||
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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-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-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.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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
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-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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-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.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
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.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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
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-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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
@ -241,49 +207,35 @@ 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-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.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.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-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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
||||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
google.golang.org/api v0.149.0 h1:b2CqT6kG+zqJIVKRQ3ELJVLN1PwHZ6DJ3dW8yl82rgY=
|
||||||
google.golang.org/api v0.129.0 h1:2XbdjjNfFPXQyufzQVwPf1RRnHH8Den2pfNE2jw7L8w=
|
google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI=
|
||||||
google.golang.org/api v0.129.0/go.mod h1:dFjiXlanKwWE3612X97llhsoI36FAoIiRj3aTl5b/zE=
|
|
||||||
google.golang.org/api v0.130.0 h1:A50ujooa1h9iizvfzA4rrJr2B7uRmWexwbekQ2+5FPQ=
|
|
||||||
google.golang.org/api v0.130.0/go.mod h1:J/LCJMYSDFvAVREGCbrESb53n4++NMBDetSHGL5I5RY=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
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.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||||
google.golang.org/appengine/v2 v2.0.3 h1:AyY/mipuqiyCIAqOevfmu5fMDc5/9P/QggWfCQYdkSA=
|
google.golang.org/appengine/v2 v2.0.5 h1:4C+F3Cd3L2nWEfSmFEZDPjQvDwL8T0YCeZBysZifP3k=
|
||||||
google.golang.org/appengine/v2 v2.0.3/go.mod h1:2Z0TTdcXxnHdXzmp8drrmOExUDM2WQgyT33c6JDUlJM=
|
google.golang.org/appengine/v2 v2.0.5/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
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-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 h1:9JucMWR7sPvCxUFd6UsOUNmA5kCcWOfORaT3tpAsKQs=
|
google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 h1:I6WNifs6pF9tNdSob2W24JtyxIYjzFB9qDlpUC76q+U=
|
||||||
google.golang.org/genproto v0.0.0-20230629202037-9506855d4529/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64=
|
google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405/go.mod h1:3WDQMjmJk36UQhjQ89emUzb1mdaHcPeeAh4SCBKznB4=
|
||||||
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 h1:Au6te5hbKUV8pIYWHqOUZ1pva5qK/rwbIhoXEUB9Lu8=
|
google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405 h1:HJMDndgxest5n2y77fnErkM62iUsptE/H8p0dC2Huo4=
|
||||||
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y=
|
google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405/go.mod h1:oT32Z4o8Zv2xPQTg0pbVaPr0MPOH6f14RgXt7zfIpwg=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 h1:s5YSX+ZH5b5vS9rnpGymvIyMpLRJizowqDlOuyjXnTk=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 h1:XVeBY8d/FaK4848myy41HBqnDwvxeV3zMZhwN1TvAMU=
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:mPBs5jNgx2GuQGvFwUvVKqtn6HsUw9nP64BedgvqEsQ=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 h1:DEH99RbiLZhMxrpEJCZ0A+wdTe0EOgou/poSLx9vWf4=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 h1:2FZP5XuJY9zQyGM5N0rtovnoXjiMUEIUMvw0m9wlpLc=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:8mL13HKkDa+IuJ8yruA3ci0q+0vsUz4m//+ottjwS5o=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
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.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
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.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||||
google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ=
|
|
||||||
google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
|
||||||
google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI=
|
|
||||||
google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
@ -295,12 +247,11 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
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 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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
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.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
|
@ -3,7 +3,7 @@ package log
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"heckel.io/ntfy/util"
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
2
main.go
2
main.go
|
@ -2,8 +2,8 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/cmd"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"heckel.io/ntfy/cmd"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
)
|
)
|
||||||
|
|
|
@ -25,9 +25,9 @@ elif [[ "$1" == *.md ]]; then
|
||||||
|
|
||||||
<!-- This file was generated by scripts/emoji-convert.sh -->
|
<!-- 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
|
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>
|
<table class=\"remove-md-box emoji-table\"><tr>
|
||||||
" > "$1"
|
" > "$1"
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"heckel.io/ntfy/util"
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"heckel.io/ntfy/user"
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Defines default config settings (excluding limits, see below)
|
// Defines default config settings (excluding limits, see below)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package server_test
|
package server_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.zio.sh/astra/ntfy/v2/server"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"heckel.io/ntfy/server"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ package server
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"heckel.io/ntfy/log"
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ package server
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"heckel.io/ntfy/log"
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
"heckel.io/ntfy/util"
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
|
@ -3,8 +3,8 @@ package server
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
|
@ -2,10 +2,10 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/emersion/go-smtp"
|
"github.com/emersion/go-smtp"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"heckel.io/ntfy/log"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
|
@ -9,9 +9,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
_ "github.com/mattn/go-sqlite3" // SQLite driver
|
_ "github.com/mattn/go-sqlite3" // SQLite driver
|
||||||
"heckel.io/ntfy/log"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -26,13 +26,13 @@ import (
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/emersion/go-smtp"
|
"github.com/emersion/go-smtp"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"heckel.io/ntfy/log"
|
|
||||||
"heckel.io/ntfy/user"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server is the main server, providing the UI and API for ntfy
|
// Server is the main server, providing the UI and API for ntfy
|
||||||
|
|
|
@ -342,6 +342,10 @@
|
||||||
# - "field -> level" to match any value, e.g. "time_taken_ms -> debug"
|
# - "field -> level" to match any value, e.g. "time_taken_ms -> debug"
|
||||||
# Warning: Using log-level-overrides has a performance penalty. Only use it for temporary debugging.
|
# Warning: Using log-level-overrides has a performance penalty. Only use it for temporary debugging.
|
||||||
#
|
#
|
||||||
|
# Check your permissions:
|
||||||
|
# If you are running ntfy with systemd, make sure this log file is owned by the
|
||||||
|
# ntfy user and group by running: chown ntfy.ntfy <filename>.
|
||||||
|
#
|
||||||
# Example (good for production):
|
# Example (good for production):
|
||||||
# log-level: info
|
# log-level: info
|
||||||
# log-format: json
|
# log-format: json
|
||||||
|
|
|
@ -2,9 +2,9 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"heckel.io/ntfy/log"
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
"heckel.io/ntfy/user"
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
"heckel.io/ntfy/util"
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
|
@ -2,10 +2,10 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"heckel.io/ntfy/log"
|
|
||||||
"heckel.io/ntfy/user"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"io"
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"heckel.io/ntfy/user"
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"heckel.io/ntfy/user"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
|
@ -7,9 +7,9 @@ import (
|
||||||
firebase "firebase.google.com/go/v4"
|
firebase "firebase.google.com/go/v4"
|
||||||
"firebase.google.com/go/v4/messaging"
|
"firebase.google.com/go/v4/messaging"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"google.golang.org/api/option"
|
"google.golang.org/api/option"
|
||||||
"heckel.io/ntfy/user"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"heckel.io/ntfy/user"
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"heckel.io/ntfy/log"
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
"heckel.io/ntfy/util"
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"heckel.io/ntfy/util"
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
|
@ -3,7 +3,7 @@ package server
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"heckel.io/ntfy/util"
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type contextKey int
|
type contextKey int
|
||||||
|
|
|
@ -4,6 +4,9 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/stripe/stripe-go/v74"
|
"github.com/stripe/stripe-go/v74"
|
||||||
portalsession "github.com/stripe/stripe-go/v74/billingportal/session"
|
portalsession "github.com/stripe/stripe-go/v74/billingportal/session"
|
||||||
"github.com/stripe/stripe-go/v74/checkout/session"
|
"github.com/stripe/stripe-go/v74/checkout/session"
|
||||||
|
@ -11,9 +14,6 @@ import (
|
||||||
"github.com/stripe/stripe-go/v74/price"
|
"github.com/stripe/stripe-go/v74/price"
|
||||||
"github.com/stripe/stripe-go/v74/subscription"
|
"github.com/stripe/stripe-go/v74/subscription"
|
||||||
"github.com/stripe/stripe-go/v74/webhook"
|
"github.com/stripe/stripe-go/v74/webhook"
|
||||||
"heckel.io/ntfy/log"
|
|
||||||
"heckel.io/ntfy/user"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
|
@ -2,12 +2,12 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stripe/stripe-go/v74"
|
"github.com/stripe/stripe-go/v74"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
"heckel.io/ntfy/user"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"io"
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
|
@ -6,8 +6,8 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"heckel.io/ntfy/user"
|
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -22,10 +22,10 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/SherClockHolmes/webpush-go"
|
"github.com/SherClockHolmes/webpush-go"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"heckel.io/ntfy/log"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
|
@ -329,6 +329,27 @@ func TestServer_PublishPriority(t *testing.T) {
|
||||||
require.Equal(t, 40007, toHTTPError(t, response.Body.String()).Code)
|
require.Equal(t, 40007, toHTTPError(t, response.Body.String()).Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServer_PublishPriority_SpecialHTTPHeader(t *testing.T) {
|
||||||
|
s := newTestServer(t, newTestConfig(t))
|
||||||
|
|
||||||
|
response := request(t, s, "POST", "/mytopic", "test", map[string]string{
|
||||||
|
"Priority": "u=4",
|
||||||
|
"X-Priority": "5",
|
||||||
|
})
|
||||||
|
require.Equal(t, 5, toMessage(t, response.Body.String()).Priority)
|
||||||
|
|
||||||
|
response = request(t, s, "POST", "/mytopic?priority=4", "test", map[string]string{
|
||||||
|
"Priority": "u=9",
|
||||||
|
})
|
||||||
|
require.Equal(t, 4, toMessage(t, response.Body.String()).Priority)
|
||||||
|
|
||||||
|
response = request(t, s, "POST", "/mytopic", "test", map[string]string{
|
||||||
|
"p": "2",
|
||||||
|
"priority": "u=9, i",
|
||||||
|
})
|
||||||
|
require.Equal(t, 2, toMessage(t, response.Body.String()).Priority)
|
||||||
|
}
|
||||||
|
|
||||||
func TestServer_PublishGETOnlyOneTopic(t *testing.T) {
|
func TestServer_PublishGETOnlyOneTopic(t *testing.T) {
|
||||||
// This tests a bug that allowed publishing topics with a comma in the name (no ticket)
|
// This tests a bug that allowed publishing topics with a comma in the name (no ticket)
|
||||||
|
|
||||||
|
@ -491,6 +512,8 @@ func TestServer_PublishAtAndPrune(t *testing.T) {
|
||||||
messages := toMessages(t, response.Body.String())
|
messages := toMessages(t, response.Body.String())
|
||||||
require.Equal(t, 1, len(messages)) // Not affected by pruning
|
require.Equal(t, 1, len(messages)) // Not affected by pruning
|
||||||
require.Equal(t, "a message", messages[0].Message)
|
require.Equal(t, "a message", messages[0].Message)
|
||||||
|
|
||||||
|
time.Sleep(time.Second) // FIXME CI failing not sure why
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_PublishAndMultiPoll(t *testing.T) {
|
func TestServer_PublishAndMultiPoll(t *testing.T) {
|
||||||
|
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"heckel.io/ntfy/log"
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
"heckel.io/ntfy/user"
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
"heckel.io/ntfy/util"
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"heckel.io/ntfy/user"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
|
|
@ -7,9 +7,9 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
"github.com/SherClockHolmes/webpush-go"
|
"github.com/SherClockHolmes/webpush-go"
|
||||||
"heckel.io/ntfy/log"
|
|
||||||
"heckel.io/ntfy/user"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -3,9 +3,9 @@ package server
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"heckel.io/ntfy/user"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
|
|
@ -11,8 +11,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"heckel.io/ntfy/log"
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
"heckel.io/ntfy/util"
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mailer interface {
|
type mailer interface {
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"heckel.io/ntfy/log"
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
"heckel.io/ntfy/util"
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -5,10 +5,10 @@ import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"heckel.io/ntfy/log"
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
"heckel.io/ntfy/user"
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
|
|
||||||
"heckel.io/ntfy/util"
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// List of possible events
|
// List of possible events
|
||||||
|
|
|
@ -3,15 +3,19 @@ package server
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"heckel.io/ntfy/util"
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"io"
|
"io"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var mimeDecoder mime.WordDecoder
|
var (
|
||||||
|
mimeDecoder mime.WordDecoder
|
||||||
|
priorityHeaderIgnoreRegex = regexp.MustCompile(`^u=\d,\s*(i|\d)$|^u=\d$`)
|
||||||
|
)
|
||||||
|
|
||||||
func readBoolParam(r *http.Request, defaultValue bool, names ...string) bool {
|
func readBoolParam(r *http.Request, defaultValue bool, names ...string) bool {
|
||||||
value := strings.ToLower(readParam(r, names...))
|
value := strings.ToLower(readParam(r, names...))
|
||||||
|
@ -50,9 +54,9 @@ func readParam(r *http.Request, names ...string) string {
|
||||||
|
|
||||||
func readHeaderParam(r *http.Request, names ...string) string {
|
func readHeaderParam(r *http.Request, names ...string) string {
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
value := maybeDecodeHeader(r.Header.Get(name))
|
value := strings.TrimSpace(maybeDecodeHeader(name, r.Header.Get(name)))
|
||||||
if value != "" {
|
if value != "" {
|
||||||
return strings.TrimSpace(value)
|
return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
|
@ -126,10 +130,26 @@ func fromContext[T any](r *http.Request, key contextKey) (T, error) {
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func maybeDecodeHeader(header string) string {
|
// maybeDecodeHeader decodes the given header value if it is MIME encoded, e.g. "=?utf-8?q?Hello_World?=",
|
||||||
decoded, err := mimeDecoder.DecodeHeader(header)
|
// or returns the original header value if it is not MIME encoded. It also calls maybeIgnoreSpecialHeader
|
||||||
|
// to ignore new HTTP "Priority" header.
|
||||||
|
func maybeDecodeHeader(name, value string) string {
|
||||||
|
decoded, err := mimeDecoder.DecodeHeader(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return header
|
return maybeIgnoreSpecialHeader(name, value)
|
||||||
}
|
}
|
||||||
return decoded
|
return maybeIgnoreSpecialHeader(name, decoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
// maybeIgnoreSpecialHeader ignores new HTTP "Priority" header (see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-priority)
|
||||||
|
//
|
||||||
|
// Cloudflare (and potentially other providers) add this to requests when forwarding to the backend (ntfy),
|
||||||
|
// so we just ignore it. If the "Priority" header is set to "u=*, i" or "u=*" (by Cloudflare), the header will be ignored.
|
||||||
|
// Returning an empty string will allow the rest of the logic to continue searching for another header (x-priority, prio, p),
|
||||||
|
// or in the Query parameters.
|
||||||
|
func maybeIgnoreSpecialHeader(name, value string) string {
|
||||||
|
if strings.ToLower(name) == "priority" && priorityHeaderIgnoreRegex.MatchString(strings.TrimSpace(value)) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -75,3 +75,16 @@ Accept: */*
|
||||||
(peeked bytes not UTF-8, peek limit of 4096 bytes reached, hex: ` + fmt.Sprintf("%x", body[:4096]) + ` ...)`
|
(peeked bytes not UTF-8, peek limit of 4096 bytes reached, hex: ` + fmt.Sprintf("%x", body[:4096]) + ` ...)`
|
||||||
require.Equal(t, expected, renderHTTPRequest(r))
|
require.Equal(t, expected, renderHTTPRequest(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMaybeIgnoreSpecialHeader(t *testing.T) {
|
||||||
|
require.Empty(t, maybeIgnoreSpecialHeader("priority", "u=1"))
|
||||||
|
require.Empty(t, maybeIgnoreSpecialHeader("Priority", "u=1"))
|
||||||
|
require.Empty(t, maybeIgnoreSpecialHeader("Priority", "u=1, i"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaybeDecodeHeaders(t *testing.T) {
|
||||||
|
r, _ := http.NewRequest("GET", "http://ntfy.sh/mytopic/json?since=all", nil)
|
||||||
|
r.Header.Set("Priority", "u=1") // Cloudflare priority header
|
||||||
|
r.Header.Set("X-Priority", "5") // ntfy priority header
|
||||||
|
require.Equal(t, "5", readHeaderParam(r, "x-priority", "priority", "p"))
|
||||||
|
}
|
||||||
|
|
|
@ -2,14 +2,14 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"heckel.io/ntfy/log"
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
"heckel.io/ntfy/user"
|
"git.zio.sh/astra/ntfy/v2/user"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -3,7 +3,7 @@ package server
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"heckel.io/ntfy/util"
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"heckel.io/ntfy/server"
|
"git.zio.sh/astra/ntfy/v2/server"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
|
@ -6,11 +6,11 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/mattn/go-sqlite3"
|
"github.com/mattn/go-sqlite3"
|
||||||
"github.com/stripe/stripe-go/v74"
|
"github.com/stripe/stripe-go/v74"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"heckel.io/ntfy/log"
|
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -160,7 +160,7 @@ const (
|
||||||
SELECT read, write
|
SELECT read, write
|
||||||
FROM user_access a
|
FROM user_access a
|
||||||
JOIN user u ON u.id = a.user_id
|
JOIN user u ON u.id = a.user_id
|
||||||
WHERE (u.user = ? OR u.user = ?) AND ? LIKE a.topic
|
WHERE (u.user = ? OR u.user = ?) AND ? LIKE a.topic ESCAPE '\'
|
||||||
ORDER BY u.user DESC
|
ORDER BY u.user DESC
|
||||||
`
|
`
|
||||||
|
|
||||||
|
@ -235,7 +235,7 @@ const (
|
||||||
selectOtherAccessCountQuery = `
|
selectOtherAccessCountQuery = `
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM user_access
|
FROM user_access
|
||||||
WHERE (topic = ? OR ? LIKE topic)
|
WHERE (topic = ? OR ? LIKE topic ESCAPE '\')
|
||||||
AND (owner_user_id IS NULL OR owner_user_id != (SELECT id FROM user WHERE user = ?))
|
AND (owner_user_id IS NULL OR owner_user_id != (SELECT id FROM user WHERE user = ?))
|
||||||
`
|
`
|
||||||
deleteAllAccessQuery = `DELETE FROM user_access`
|
deleteAllAccessQuery = `DELETE FROM user_access`
|
||||||
|
@ -262,7 +262,8 @@ const (
|
||||||
deleteExpiredTokensQuery = `DELETE FROM user_token WHERE expires > 0 AND expires < ?`
|
deleteExpiredTokensQuery = `DELETE FROM user_token WHERE expires > 0 AND expires < ?`
|
||||||
deleteExcessTokensQuery = `
|
deleteExcessTokensQuery = `
|
||||||
DELETE FROM user_token
|
DELETE FROM user_token
|
||||||
WHERE (user_id, token) NOT IN (
|
WHERE user_id = ?
|
||||||
|
AND (user_id, token) NOT IN (
|
||||||
SELECT user_id, token
|
SELECT user_id, token
|
||||||
FROM user_token
|
FROM user_token
|
||||||
WHERE user_id = ?
|
WHERE user_id = ?
|
||||||
|
@ -311,7 +312,7 @@ const (
|
||||||
|
|
||||||
// Schema management queries
|
// Schema management queries
|
||||||
const (
|
const (
|
||||||
currentSchemaVersion = 4
|
currentSchemaVersion = 5
|
||||||
insertSchemaVersion = `INSERT INTO schemaVersion VALUES (1, ?)`
|
insertSchemaVersion = `INSERT INTO schemaVersion VALUES (1, ?)`
|
||||||
updateSchemaVersion = `UPDATE schemaVersion SET version = ? WHERE id = 1`
|
updateSchemaVersion = `UPDATE schemaVersion SET version = ? WHERE id = 1`
|
||||||
selectSchemaVersionQuery = `SELECT version FROM schemaVersion WHERE id = 1`
|
selectSchemaVersionQuery = `SELECT version FROM schemaVersion WHERE id = 1`
|
||||||
|
@ -421,6 +422,11 @@ const (
|
||||||
FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE
|
FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
`
|
`
|
||||||
|
|
||||||
|
// 4 -> 5
|
||||||
|
migrate4To5UpdateQueries = `
|
||||||
|
UPDATE user_access SET topic = REPLACE(topic, '_', '\_');
|
||||||
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -428,6 +434,7 @@ var (
|
||||||
1: migrateFrom1,
|
1: migrateFrom1,
|
||||||
2: migrateFrom2,
|
2: migrateFrom2,
|
||||||
3: migrateFrom3,
|
3: migrateFrom3,
|
||||||
|
4: migrateFrom4,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -534,7 +541,7 @@ func (a *Manager) CreateToken(userID, label string, expires time.Time, origin ne
|
||||||
if tokenCount >= tokenMaxCount {
|
if tokenCount >= tokenMaxCount {
|
||||||
// This pruning logic is done in two queries for efficiency. The SELECT above is a lookup
|
// This pruning logic is done in two queries for efficiency. The SELECT above is a lookup
|
||||||
// on two indices, whereas the query below is a full table scan.
|
// on two indices, whereas the query below is a full table scan.
|
||||||
if _, err := tx.Exec(deleteExcessTokensQuery, userID, tokenMaxCount); err != nil {
|
if _, err := tx.Exec(deleteExcessTokensQuery, userID, userID, tokenMaxCount); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1122,7 +1129,7 @@ func (a *Manager) Reservations(username string) ([]Reservation, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
reservations = append(reservations, Reservation{
|
reservations = append(reservations, Reservation{
|
||||||
Topic: topic,
|
Topic: unescapeUnderscore(topic),
|
||||||
Owner: NewPermission(ownerRead, ownerWrite),
|
Owner: NewPermission(ownerRead, ownerWrite),
|
||||||
Everyone: NewPermission(everyoneRead.Bool, everyoneWrite.Bool), // false if null
|
Everyone: NewPermission(everyoneRead.Bool, everyoneWrite.Bool), // false if null
|
||||||
})
|
})
|
||||||
|
@ -1132,7 +1139,7 @@ func (a *Manager) Reservations(username string) ([]Reservation, error) {
|
||||||
|
|
||||||
// HasReservation returns true if the given topic access is owned by the user
|
// HasReservation returns true if the given topic access is owned by the user
|
||||||
func (a *Manager) HasReservation(username, topic string) (bool, error) {
|
func (a *Manager) HasReservation(username, topic string) (bool, error) {
|
||||||
rows, err := a.db.Query(selectUserHasReservationQuery, username, topic)
|
rows, err := a.db.Query(selectUserHasReservationQuery, username, escapeUnderscore(topic))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -1167,7 +1174,7 @@ func (a *Manager) ReservationsCount(username string) (int64, error) {
|
||||||
// ReservationOwner returns user ID of the user that owns this topic, or an
|
// ReservationOwner returns user ID of the user that owns this topic, or an
|
||||||
// empty string if it's not owned by anyone
|
// empty string if it's not owned by anyone
|
||||||
func (a *Manager) ReservationOwner(topic string) (string, error) {
|
func (a *Manager) ReservationOwner(topic string) (string, error) {
|
||||||
rows, err := a.db.Query(selectUserReservationsOwnerQuery, topic)
|
rows, err := a.db.Query(selectUserReservationsOwnerQuery, escapeUnderscore(topic))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -1262,7 +1269,7 @@ func (a *Manager) AllowReservation(username string, topic string) error {
|
||||||
if (!AllowedUsername(username) && username != Everyone) || !AllowedTopic(topic) {
|
if (!AllowedUsername(username) && username != Everyone) || !AllowedTopic(topic) {
|
||||||
return ErrInvalidArgument
|
return ErrInvalidArgument
|
||||||
}
|
}
|
||||||
rows, err := a.db.Query(selectOtherAccessCountQuery, topic, topic, username)
|
rows, err := a.db.Query(selectOtherAccessCountQuery, escapeUnderscore(topic), escapeUnderscore(topic), username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1327,10 +1334,10 @@ func (a *Manager) AddReservation(username string, topic string, everyone Permiss
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
if _, err := tx.Exec(upsertUserAccessQuery, username, topic, true, true, username, username); err != nil {
|
if _, err := tx.Exec(upsertUserAccessQuery, username, escapeUnderscore(topic), true, true, username, username); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := tx.Exec(upsertUserAccessQuery, Everyone, topic, everyone.IsRead(), everyone.IsWrite(), username, username); err != nil {
|
if _, err := tx.Exec(upsertUserAccessQuery, Everyone, escapeUnderscore(topic), everyone.IsRead(), everyone.IsWrite(), username, username); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return tx.Commit()
|
return tx.Commit()
|
||||||
|
@ -1353,10 +1360,10 @@ func (a *Manager) RemoveReservations(username string, topics ...string) error {
|
||||||
}
|
}
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
for _, topic := range topics {
|
for _, topic := range topics {
|
||||||
if _, err := tx.Exec(deleteTopicAccessQuery, username, username, topic); err != nil {
|
if _, err := tx.Exec(deleteTopicAccessQuery, username, username, escapeUnderscore(topic)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := tx.Exec(deleteTopicAccessQuery, Everyone, Everyone, topic); err != nil {
|
if _, err := tx.Exec(deleteTopicAccessQuery, Everyone, Everyone, escapeUnderscore(topic)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1483,12 +1490,24 @@ func (a *Manager) Close() error {
|
||||||
return a.db.Close()
|
return a.db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// toSQLWildcard converts a wildcard string to a SQL wildcard string. It only allows '*' as wildcards,
|
||||||
|
// and escapes '_', assuming '\' as escape character.
|
||||||
func toSQLWildcard(s string) string {
|
func toSQLWildcard(s string) string {
|
||||||
return strings.ReplaceAll(s, "*", "%")
|
return escapeUnderscore(strings.ReplaceAll(s, "*", "%"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fromSQLWildcard converts a SQL wildcard string to a wildcard string. It converts '%' to '*',
|
||||||
|
// and removes the '\_' escape character.
|
||||||
func fromSQLWildcard(s string) string {
|
func fromSQLWildcard(s string) string {
|
||||||
return strings.ReplaceAll(s, "%", "*")
|
return strings.ReplaceAll(unescapeUnderscore(s), "%", "*")
|
||||||
|
}
|
||||||
|
|
||||||
|
func escapeUnderscore(s string) string {
|
||||||
|
return strings.ReplaceAll(s, "_", "\\_")
|
||||||
|
}
|
||||||
|
|
||||||
|
func unescapeUnderscore(s string) string {
|
||||||
|
return strings.ReplaceAll(s, "\\_", "_")
|
||||||
}
|
}
|
||||||
|
|
||||||
func runStartupQueries(db *sql.DB, startupQueries string) error {
|
func runStartupQueries(db *sql.DB, startupQueries string) error {
|
||||||
|
@ -1626,6 +1645,22 @@ func migrateFrom3(db *sql.DB) error {
|
||||||
return tx.Commit()
|
return tx.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func migrateFrom4(db *sql.DB) error {
|
||||||
|
log.Tag(tag).Info("Migrating user database schema: from 4 to 5")
|
||||||
|
tx, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
if _, err := tx.Exec(migrate4To5UpdateQueries); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := tx.Exec(updateSchemaVersion, 5); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
func nullString(s string) sql.NullString {
|
func nullString(s string) sql.NullString {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return sql.NullString{}
|
return sql.NullString{}
|
||||||
|
|
|
@ -3,10 +3,10 @@ package user
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stripe/stripe-go/v74"
|
"github.com/stripe/stripe-go/v74"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -330,7 +330,7 @@ func TestManager_Reservations(t *testing.T) {
|
||||||
a := newTestManager(t, PermissionDenyAll)
|
a := newTestManager(t, PermissionDenyAll)
|
||||||
require.Nil(t, a.AddUser("phil", "phil", RoleUser))
|
require.Nil(t, a.AddUser("phil", "phil", RoleUser))
|
||||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
|
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
|
||||||
require.Nil(t, a.AddReservation("ben", "ztopic", PermissionDenyAll))
|
require.Nil(t, a.AddReservation("ben", "ztopic_", PermissionDenyAll))
|
||||||
require.Nil(t, a.AddReservation("ben", "readme", PermissionRead))
|
require.Nil(t, a.AddReservation("ben", "readme", PermissionRead))
|
||||||
require.Nil(t, a.AllowAccess("ben", "something-else", PermissionRead))
|
require.Nil(t, a.AllowAccess("ben", "something-else", PermissionRead))
|
||||||
|
|
||||||
|
@ -343,7 +343,7 @@ func TestManager_Reservations(t *testing.T) {
|
||||||
Everyone: PermissionRead,
|
Everyone: PermissionRead,
|
||||||
}, reservations[0])
|
}, reservations[0])
|
||||||
require.Equal(t, Reservation{
|
require.Equal(t, Reservation{
|
||||||
Topic: "ztopic",
|
Topic: "ztopic_",
|
||||||
Owner: PermissionReadWrite,
|
Owner: PermissionReadWrite,
|
||||||
Everyone: PermissionDenyAll,
|
Everyone: PermissionDenyAll,
|
||||||
}, reservations[1])
|
}, reservations[1])
|
||||||
|
@ -352,6 +352,14 @@ func TestManager_Reservations(t *testing.T) {
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.True(t, b)
|
require.True(t, b)
|
||||||
|
|
||||||
|
b, err = a.HasReservation("ben", "ztopic_")
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.True(t, b)
|
||||||
|
|
||||||
|
b, err = a.HasReservation("ben", "ztopicX") // _ != X (used to be a SQL wildcard issue)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.False(t, b)
|
||||||
|
|
||||||
b, err = a.HasReservation("notben", "readme")
|
b, err = a.HasReservation("notben", "readme")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.False(t, b)
|
require.False(t, b)
|
||||||
|
@ -371,11 +379,17 @@ func TestManager_Reservations(t *testing.T) {
|
||||||
err = a.AllowReservation("phil", "readme")
|
err = a.AllowReservation("phil", "readme")
|
||||||
require.Equal(t, errTopicOwnedByOthers, err)
|
require.Equal(t, errTopicOwnedByOthers, err)
|
||||||
|
|
||||||
|
err = a.AllowReservation("phil", "ztopic_")
|
||||||
|
require.Equal(t, errTopicOwnedByOthers, err)
|
||||||
|
|
||||||
|
err = a.AllowReservation("phil", "ztopicX")
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
err = a.AllowReservation("phil", "not-reserved")
|
err = a.AllowReservation("phil", "not-reserved")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
// Now remove them again
|
// Now remove them again
|
||||||
require.Nil(t, a.RemoveReservations("ben", "ztopic", "readme"))
|
require.Nil(t, a.RemoveReservations("ben", "ztopic_", "readme"))
|
||||||
|
|
||||||
count, err = a.ReservationsCount("ben")
|
count, err = a.ReservationsCount("ben")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
@ -580,46 +594,80 @@ func TestManager_Token_Extend(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManager_Token_MaxCount_AutoDelete(t *testing.T) {
|
func TestManager_Token_MaxCount_AutoDelete(t *testing.T) {
|
||||||
|
// Tests that tokens are automatically deleted when the maximum number of tokens is reached
|
||||||
|
|
||||||
a := newTestManager(t, PermissionDenyAll)
|
a := newTestManager(t, PermissionDenyAll)
|
||||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
|
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
|
||||||
|
require.Nil(t, a.AddUser("phil", "phil", RoleUser))
|
||||||
|
|
||||||
// Try to extend token for user without token
|
ben, err := a.User("ben")
|
||||||
u, err := a.User("ben")
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
// Tokens
|
phil, err := a.User("phil")
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// Create 2 tokens for phil
|
||||||
|
philTokens := make([]string, 0)
|
||||||
|
token, err := a.CreateToken(phil.ID, "", time.Now().Add(72*time.Hour), netip.IPv4Unspecified())
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.NotEmpty(t, token.Value)
|
||||||
|
philTokens = append(philTokens, token.Value)
|
||||||
|
|
||||||
|
token, err = a.CreateToken(phil.ID, "", time.Unix(0, 0), netip.IPv4Unspecified())
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.NotEmpty(t, token.Value)
|
||||||
|
philTokens = append(philTokens, token.Value)
|
||||||
|
|
||||||
|
// Create 22 tokens for ben (only 20 allowed!)
|
||||||
baseTime := time.Now().Add(24 * time.Hour)
|
baseTime := time.Now().Add(24 * time.Hour)
|
||||||
tokens := make([]string, 0)
|
benTokens := make([]string, 0)
|
||||||
for i := 0; i < 22; i++ {
|
for i := 0; i < 22; i++ { //
|
||||||
token, err := a.CreateToken(u.ID, "", time.Now().Add(72*time.Hour), netip.IPv4Unspecified())
|
token, err := a.CreateToken(ben.ID, "", time.Now().Add(72*time.Hour), netip.IPv4Unspecified())
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.NotEmpty(t, token.Value)
|
require.NotEmpty(t, token.Value)
|
||||||
tokens = append(tokens, token.Value)
|
benTokens = append(benTokens, token.Value)
|
||||||
|
|
||||||
// Manually modify expiry date to avoid sorting issues (this is a hack)
|
// Manually modify expiry date to avoid sorting issues (this is a hack)
|
||||||
_, err = a.db.Exec(`UPDATE user_token SET expires=? WHERE token=?`, baseTime.Add(time.Duration(i)*time.Minute).Unix(), token.Value)
|
_, err = a.db.Exec(`UPDATE user_token SET expires=? WHERE token=?`, baseTime.Add(time.Duration(i)*time.Minute).Unix(), token.Value)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = a.AuthenticateToken(tokens[0])
|
// Ben: The first 2 tokens should have been wiped and should not work anymore!
|
||||||
|
_, err = a.AuthenticateToken(benTokens[0])
|
||||||
require.Equal(t, ErrUnauthenticated, err)
|
require.Equal(t, ErrUnauthenticated, err)
|
||||||
|
|
||||||
_, err = a.AuthenticateToken(tokens[1])
|
_, err = a.AuthenticateToken(benTokens[1])
|
||||||
require.Equal(t, ErrUnauthenticated, err)
|
require.Equal(t, ErrUnauthenticated, err)
|
||||||
|
|
||||||
|
// Ben: The other tokens should still work
|
||||||
for i := 2; i < 22; i++ {
|
for i := 2; i < 22; i++ {
|
||||||
userWithToken, err := a.AuthenticateToken(tokens[i])
|
userWithToken, err := a.AuthenticateToken(benTokens[i])
|
||||||
require.Nil(t, err, "token[%d]=%s failed", i, tokens[i])
|
require.Nil(t, err, "token[%d]=%s failed", i, benTokens[i])
|
||||||
require.Equal(t, "ben", userWithToken.Name)
|
require.Equal(t, "ben", userWithToken.Name)
|
||||||
require.Equal(t, tokens[i], userWithToken.Token)
|
require.Equal(t, benTokens[i], userWithToken.Token)
|
||||||
}
|
}
|
||||||
|
|
||||||
var count int
|
// Phil: All tokens should still work
|
||||||
rows, err := a.db.Query(`SELECT COUNT(*) FROM user_token`)
|
for i := 0; i < 2; i++ {
|
||||||
|
userWithToken, err := a.AuthenticateToken(philTokens[i])
|
||||||
|
require.Nil(t, err, "token[%d]=%s failed", i, philTokens[i])
|
||||||
|
require.Equal(t, "phil", userWithToken.Name)
|
||||||
|
require.Equal(t, philTokens[i], userWithToken.Token)
|
||||||
|
}
|
||||||
|
|
||||||
|
var benCount int
|
||||||
|
rows, err := a.db.Query(`SELECT COUNT(*) FROM user_token WHERE user_id=?`, ben.ID)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.True(t, rows.Next())
|
require.True(t, rows.Next())
|
||||||
require.Nil(t, rows.Scan(&count))
|
require.Nil(t, rows.Scan(&benCount))
|
||||||
require.Equal(t, 20, count)
|
require.Equal(t, 20, benCount)
|
||||||
|
|
||||||
|
var philCount int
|
||||||
|
rows, err = a.db.Query(`SELECT COUNT(*) FROM user_token WHERE user_id=?`, phil.ID)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.True(t, rows.Next())
|
||||||
|
require.Nil(t, rows.Scan(&philCount))
|
||||||
|
require.Equal(t, 2, philCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManager_EnqueueStats_ResetStats(t *testing.T) {
|
func TestManager_EnqueueStats_ResetStats(t *testing.T) {
|
||||||
|
@ -944,7 +992,44 @@ func TestUser_PhoneNumberAdd_Multiple_Users_Same_Number(t *testing.T) {
|
||||||
require.Nil(t, a.AddPhoneNumber(ben.ID, "+1234567890"))
|
require.Nil(t, a.AddPhoneNumber(ben.ID, "+1234567890"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSqliteCache_Migration_From1(t *testing.T) {
|
func TestManager_Topic_Wildcard_With_Asterisk_Underscore(t *testing.T) {
|
||||||
|
f := filepath.Join(t.TempDir(), "user.db")
|
||||||
|
a := newTestManagerFromFile(t, f, "", PermissionDenyAll, DefaultUserPasswordBcryptCost, DefaultUserStatsQueueWriterInterval)
|
||||||
|
require.Nil(t, a.AllowAccess(Everyone, "*_", PermissionRead))
|
||||||
|
require.Nil(t, a.AllowAccess(Everyone, "__*_", PermissionRead))
|
||||||
|
require.Nil(t, a.Authorize(nil, "allowed_", PermissionRead))
|
||||||
|
require.Nil(t, a.Authorize(nil, "__allowed_", PermissionRead))
|
||||||
|
require.Nil(t, a.Authorize(nil, "_allowed_", PermissionRead)) // The "%" in "%\_" matches the first "_"
|
||||||
|
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "notallowed", PermissionRead))
|
||||||
|
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "_notallowed", PermissionRead))
|
||||||
|
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "__notallowed", PermissionRead))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_Topic_Wildcard_With_Underscore(t *testing.T) {
|
||||||
|
f := filepath.Join(t.TempDir(), "user.db")
|
||||||
|
a := newTestManagerFromFile(t, f, "", PermissionDenyAll, DefaultUserPasswordBcryptCost, DefaultUserStatsQueueWriterInterval)
|
||||||
|
require.Nil(t, a.AllowAccess(Everyone, "mytopic_", PermissionReadWrite))
|
||||||
|
require.Nil(t, a.Authorize(nil, "mytopic_", PermissionRead))
|
||||||
|
require.Nil(t, a.Authorize(nil, "mytopic_", PermissionWrite))
|
||||||
|
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopicX", PermissionRead))
|
||||||
|
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopicX", PermissionWrite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToFromSQLWildcard(t *testing.T) {
|
||||||
|
require.Equal(t, "up%", toSQLWildcard("up*"))
|
||||||
|
require.Equal(t, "up\\_%", toSQLWildcard("up_*"))
|
||||||
|
require.Equal(t, "foo", toSQLWildcard("foo"))
|
||||||
|
|
||||||
|
require.Equal(t, "up*", fromSQLWildcard("up%"))
|
||||||
|
require.Equal(t, "up_*", fromSQLWildcard("up\\_%"))
|
||||||
|
require.Equal(t, "foo", fromSQLWildcard("foo"))
|
||||||
|
|
||||||
|
require.Equal(t, "up*", fromSQLWildcard(toSQLWildcard("up*")))
|
||||||
|
require.Equal(t, "up_*", fromSQLWildcard(toSQLWildcard("up_*")))
|
||||||
|
require.Equal(t, "foo", fromSQLWildcard(toSQLWildcard("foo")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMigrationFrom1(t *testing.T) {
|
||||||
filename := filepath.Join(t.TempDir(), "user.db")
|
filename := filepath.Join(t.TempDir(), "user.db")
|
||||||
db, err := sql.Open("sqlite3", filename)
|
db, err := sql.Open("sqlite3", filename)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
@ -1029,6 +1114,152 @@ func TestSqliteCache_Migration_From1(t *testing.T) {
|
||||||
require.Equal(t, PermissionRead, everyoneGrants[0].Allow)
|
require.Equal(t, PermissionRead, everyoneGrants[0].Allow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMigrationFrom4(t *testing.T) {
|
||||||
|
filename := filepath.Join(t.TempDir(), "user.db")
|
||||||
|
db, err := sql.Open("sqlite3", filename)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// Create "version 4" schema
|
||||||
|
_, err = db.Exec(`
|
||||||
|
BEGIN;
|
||||||
|
CREATE TABLE IF NOT EXISTS tier (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
code TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
messages_limit INT NOT NULL,
|
||||||
|
messages_expiry_duration INT NOT NULL,
|
||||||
|
emails_limit INT NOT NULL,
|
||||||
|
calls_limit INT NOT NULL,
|
||||||
|
reservations_limit INT NOT NULL,
|
||||||
|
attachment_file_size_limit INT NOT NULL,
|
||||||
|
attachment_total_size_limit INT NOT NULL,
|
||||||
|
attachment_expiry_duration INT NOT NULL,
|
||||||
|
attachment_bandwidth_limit INT NOT NULL,
|
||||||
|
stripe_monthly_price_id TEXT,
|
||||||
|
stripe_yearly_price_id TEXT
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX idx_tier_code ON tier (code);
|
||||||
|
CREATE UNIQUE INDEX idx_tier_stripe_monthly_price_id ON tier (stripe_monthly_price_id);
|
||||||
|
CREATE UNIQUE INDEX idx_tier_stripe_yearly_price_id ON tier (stripe_yearly_price_id);
|
||||||
|
CREATE TABLE IF NOT EXISTS user (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
tier_id TEXT,
|
||||||
|
user TEXT NOT NULL,
|
||||||
|
pass TEXT NOT NULL,
|
||||||
|
role TEXT CHECK (role IN ('anonymous', 'admin', 'user')) NOT NULL,
|
||||||
|
prefs JSON NOT NULL DEFAULT '{}',
|
||||||
|
sync_topic TEXT NOT NULL,
|
||||||
|
stats_messages INT NOT NULL DEFAULT (0),
|
||||||
|
stats_emails INT NOT NULL DEFAULT (0),
|
||||||
|
stats_calls INT NOT NULL DEFAULT (0),
|
||||||
|
stripe_customer_id TEXT,
|
||||||
|
stripe_subscription_id TEXT,
|
||||||
|
stripe_subscription_status TEXT,
|
||||||
|
stripe_subscription_interval TEXT,
|
||||||
|
stripe_subscription_paid_until INT,
|
||||||
|
stripe_subscription_cancel_at INT,
|
||||||
|
created INT NOT NULL,
|
||||||
|
deleted INT,
|
||||||
|
FOREIGN KEY (tier_id) REFERENCES tier (id)
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX idx_user ON user (user);
|
||||||
|
CREATE UNIQUE INDEX idx_user_stripe_customer_id ON user (stripe_customer_id);
|
||||||
|
CREATE UNIQUE INDEX idx_user_stripe_subscription_id ON user (stripe_subscription_id);
|
||||||
|
CREATE TABLE IF NOT EXISTS user_access (
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
topic TEXT NOT NULL,
|
||||||
|
read INT NOT NULL,
|
||||||
|
write INT NOT NULL,
|
||||||
|
owner_user_id INT,
|
||||||
|
PRIMARY KEY (user_id, topic),
|
||||||
|
FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (owner_user_id) REFERENCES user (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS user_token (
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
token TEXT NOT NULL,
|
||||||
|
label TEXT NOT NULL,
|
||||||
|
last_access INT NOT NULL,
|
||||||
|
last_origin TEXT NOT NULL,
|
||||||
|
expires INT NOT NULL,
|
||||||
|
PRIMARY KEY (user_id, token),
|
||||||
|
FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS user_phone (
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
phone_number TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (user_id, phone_number),
|
||||||
|
FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS schemaVersion (
|
||||||
|
id INT PRIMARY KEY,
|
||||||
|
version INT NOT NULL
|
||||||
|
);
|
||||||
|
INSERT INTO user (id, user, pass, role, sync_topic, created)
|
||||||
|
VALUES ('u_everyone', '*', '', 'anonymous', '', UNIXEPOCH())
|
||||||
|
ON CONFLICT (id) DO NOTHING;
|
||||||
|
INSERT INTO schemaVersion (id, version) VALUES (1, 4);
|
||||||
|
COMMIT;
|
||||||
|
`)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// Insert a few ACL entries
|
||||||
|
_, err = db.Exec(`
|
||||||
|
BEGIN;
|
||||||
|
INSERT INTO user_access (user_id, topic, read, write) values ('u_everyone', 'mytopic_', 1, 1);
|
||||||
|
INSERT INTO user_access (user_id, topic, read, write) values ('u_everyone', 'up%', 1, 1);
|
||||||
|
INSERT INTO user_access (user_id, topic, read, write) values ('u_everyone', 'down_%', 1, 1);
|
||||||
|
COMMIT;
|
||||||
|
`)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// Create manager to trigger migration
|
||||||
|
a := newTestManagerFromFile(t, filename, "", PermissionDenyAll, bcrypt.MinCost, DefaultUserStatsQueueWriterInterval)
|
||||||
|
checkSchemaVersion(t, a.db)
|
||||||
|
|
||||||
|
// Add another
|
||||||
|
require.Nil(t, a.AllowAccess(Everyone, "left_*", PermissionReadWrite))
|
||||||
|
|
||||||
|
// Check "external view" of grants
|
||||||
|
everyoneGrants, err := a.Grants(Everyone)
|
||||||
|
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, "up*", everyoneGrants[3].TopicPattern)
|
||||||
|
|
||||||
|
// Check they are stored correctly in the database
|
||||||
|
rows, err := db.Query(`SELECT topic FROM user_access WHERE user_id = 'u_everyone' ORDER BY topic`)
|
||||||
|
require.Nil(t, err)
|
||||||
|
topicPatterns := make([]string, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var topicPattern string
|
||||||
|
require.Nil(t, rows.Scan(&topicPattern))
|
||||||
|
topicPatterns = append(topicPatterns, topicPattern)
|
||||||
|
}
|
||||||
|
require.Nil(t, rows.Close())
|
||||||
|
require.Equal(t, 4, len(topicPatterns))
|
||||||
|
require.Equal(t, "down\\_%", topicPatterns[0])
|
||||||
|
require.Equal(t, "left\\_%", topicPatterns[1])
|
||||||
|
require.Equal(t, "mytopic\\_", topicPatterns[2])
|
||||||
|
require.Equal(t, "up%", topicPatterns[3])
|
||||||
|
|
||||||
|
// Check that ACL works as excepted
|
||||||
|
require.Nil(t, a.Authorize(nil, "down_123", PermissionRead))
|
||||||
|
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "downX123", PermissionRead))
|
||||||
|
|
||||||
|
require.Nil(t, a.Authorize(nil, "left_abc", PermissionRead))
|
||||||
|
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "leftX123", PermissionRead))
|
||||||
|
|
||||||
|
require.Nil(t, a.Authorize(nil, "mytopic_", PermissionRead))
|
||||||
|
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopicX", PermissionRead))
|
||||||
|
|
||||||
|
require.Nil(t, a.Authorize(nil, "up123", PermissionRead))
|
||||||
|
require.Nil(t, a.Authorize(nil, "up", PermissionRead)) // % matches 0 or more characters
|
||||||
|
}
|
||||||
|
|
||||||
func checkSchemaVersion(t *testing.T, db *sql.DB) {
|
func checkSchemaVersion(t *testing.T, db *sql.DB) {
|
||||||
rows, err := db.Query(`SELECT version FROM schemaVersion`)
|
rows, err := db.Query(`SELECT version FROM schemaVersion`)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
|
@ -2,8 +2,8 @@ package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"git.zio.sh/astra/ntfy/v2/log"
|
||||||
"github.com/stripe/stripe-go/v74"
|
"github.com/stripe/stripe-go/v74"
|
||||||
"heckel.io/ntfy/log"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package util_test
|
package util_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.zio.sh/astra/ntfy/v2/util"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"heckel.io/ntfy/util"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
|
@ -161,11 +161,6 @@ func ParsePriority(priority string) (int, error) {
|
||||||
case "5", "max", "urgent":
|
case "5", "max", "urgent":
|
||||||
return 5, nil
|
return 5, nil
|
||||||
default:
|
default:
|
||||||
// Ignore new HTTP Priority header (see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-priority)
|
|
||||||
// Cloudflare adds this to requests when forwarding to the backend (ntfy), so we just ignore it.
|
|
||||||
if strings.HasPrefix(p, "u=") {
|
|
||||||
return 3, nil
|
|
||||||
}
|
|
||||||
return 0, errInvalidPriority
|
return 0, errInvalidPriority
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,15 +87,6 @@ func TestParsePriority_Invalid(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParsePriority_HTTPSpecPriority(t *testing.T) {
|
|
||||||
priorities := []string{"u=1", "u=3", "u=7, i"} // see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-priority
|
|
||||||
for _, priority := range priorities {
|
|
||||||
actual, err := ParsePriority(priority)
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, 3, actual) // Always expect 3!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPriorityString(t *testing.T) {
|
func TestPriorityString(t *testing.T) {
|
||||||
priorities := []int{0, 1, 2, 3, 4, 5}
|
priorities := []int{0, 1, 2, 3, 4, 5}
|
||||||
expected := []string{"default", "min", "low", "default", "high", "max"}
|
expected := []string{"default", "min", "low", "default", "high", "max"}
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<!-- Previews in Google, Slack, WhatsApp, etc. -->
|
<!-- Previews in Google, Slack, WhatsApp, etc. -->
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="ntfy lets you send push notifications via scripts from any computer or phone. Made with ❤ by Philipp C. Heckel, Apache License 2.0, source at https://heckel.io/ntfy."
|
content="ntfy lets you send push notifications via scripts from any computer or phone. Made with ❤ by Philipp C. Heckel, Apache License 2.0, source at https://git.zio.sh/astra/ntfy/v2."
|
||||||
/>
|
/>
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:locale" content="en_US" />
|
<meta property="og:locale" content="en_US" />
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
<meta property="og:title" content="ntfy web" />
|
<meta property="og:title" content="ntfy web" />
|
||||||
<meta
|
<meta
|
||||||
property="og:description"
|
property="og:description"
|
||||||
content="ntfy lets you send push notifications via scripts from any computer or phone. Made with ❤ by Philipp C. Heckel, Apache License 2.0, source at https://heckel.io/ntfy."
|
content="ntfy lets you send push notifications via scripts from any computer or phone. Made with ❤ by Philipp C. Heckel, Apache License 2.0, source at https://git.zio.sh/astra/ntfy/v2."
|
||||||
/>
|
/>
|
||||||
<meta property="og:image" content="/static/images/ntfy.png" />
|
<meta property="og:image" content="/static/images/ntfy.png" />
|
||||||
<meta property="og:url" content="https://ntfy.sh" />
|
<meta property="og:url" content="https://ntfy.sh" />
|
||||||
|
|
2224
web/package-lock.json
generated
2224
web/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -152,7 +152,7 @@
|
||||||
"publish_dialog_chip_delay_label": "تأخير التسليم",
|
"publish_dialog_chip_delay_label": "تأخير التسليم",
|
||||||
"subscribe_dialog_login_description": "هذا الموضوع محمي بكلمة مرور. الرجاء إدخال اسم المستخدم وكلمة المرور للاشتراك.",
|
"subscribe_dialog_login_description": "هذا الموضوع محمي بكلمة مرور. الرجاء إدخال اسم المستخدم وكلمة المرور للاشتراك.",
|
||||||
"subscribe_dialog_subscribe_button_cancel": "إلغاء",
|
"subscribe_dialog_subscribe_button_cancel": "إلغاء",
|
||||||
"common_back": "العودة",
|
"common_back": "الرجوع",
|
||||||
"prefs_notifications_sound_play": "تشغيل الصوت المحدد",
|
"prefs_notifications_sound_play": "تشغيل الصوت المحدد",
|
||||||
"prefs_notifications_min_priority_title": "أولوية دنيا",
|
"prefs_notifications_min_priority_title": "أولوية دنيا",
|
||||||
"prefs_notifications_min_priority_max_only": "الأولوية القصوى فقط",
|
"prefs_notifications_min_priority_max_only": "الأولوية القصوى فقط",
|
||||||
|
@ -329,5 +329,6 @@
|
||||||
"publish_dialog_attachment_limits_quota_reached": "يتجاوز الحصة، {{remainingBytes}} متبقية",
|
"publish_dialog_attachment_limits_quota_reached": "يتجاوز الحصة، {{remainingBytes}} متبقية",
|
||||||
"account_basics_tier_paid_until": "تم دفع مبلغ الاشتراك إلى غاية {{date}}، وسيتم تجديده تِلْقائيًا",
|
"account_basics_tier_paid_until": "تم دفع مبلغ الاشتراك إلى غاية {{date}}، وسيتم تجديده تِلْقائيًا",
|
||||||
"account_basics_tier_canceled_subscription": "تم إلغاء اشتراكك وسيتم إعادته إلى مستوى حساب مجاني بداية مِن {{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_one": "{{emails}} ел. писмо на ден",
|
||||||
"account_upgrade_dialog_tier_features_emails_other": "{{emails}} ел. писма на ден",
|
"account_upgrade_dialog_tier_features_emails_other": "{{emails}} ел. писма на ден",
|
||||||
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} телефонни обаждания на ден",
|
"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",
|
"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_click_copy_url_button": "Copio linc",
|
||||||
"notifications_actions_open_url_title": "Ewch i {{url}}",
|
"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",
|
"notifications_click_copy_url_title": "Link-URL in Zwischenablage kopieren",
|
||||||
"publish_dialog_priority_low": "Niedrige Priorität",
|
"publish_dialog_priority_low": "Niedrige Priorität",
|
||||||
"publish_dialog_message_label": "Nachricht",
|
"publish_dialog_message_label": "Nachricht",
|
||||||
"action_bar_unsubscribe": "Von Thema abmelden",
|
"action_bar_unsubscribe": "Abmelden",
|
||||||
"notifications_copied_to_clipboard": "In Zwischenablage kopiert",
|
"notifications_copied_to_clipboard": "In Zwischenablage kopiert",
|
||||||
"notifications_loading": "Benachrichtigungen werden geladen …",
|
"notifications_loading": "Benachrichtigungen werden geladen …",
|
||||||
"notifications_attachment_open_title": "Gehe zu {{url}}",
|
"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_not_supported": "Diese Aktion wird in der Web-App nicht unterstützt",
|
||||||
"notifications_actions_http_request_title": "Sende HTTP {{method}} an {{url}}",
|
"notifications_actions_http_request_title": "Sende HTTP {{method}} an {{url}}",
|
||||||
"action_bar_show_menu": "Menü anzeigen",
|
"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_show_dialog": "Dialog zur Veröffentlichung anzeigen",
|
||||||
"message_bar_publish": "Benachrichtigung veröffentlichen",
|
"message_bar_publish": "Benachrichtigung veröffentlichen",
|
||||||
"nav_button_connecting": "verbinde",
|
"nav_button_connecting": "verbinde",
|
||||||
|
@ -310,7 +310,7 @@
|
||||||
"prefs_reservations_delete_button": "Zugriff auf Thema zurücksetzen",
|
"prefs_reservations_delete_button": "Zugriff auf Thema zurücksetzen",
|
||||||
"prefs_reservations_table": "Übersicht reservierter Themen",
|
"prefs_reservations_table": "Übersicht reservierter Themen",
|
||||||
"prefs_reservations_table_topic_header": "Thema",
|
"prefs_reservations_table_topic_header": "Thema",
|
||||||
"prefs_reservations_table_everyone_deny_all": "Nur kann veröffentlichen und lesen",
|
"prefs_reservations_table_everyone_deny_all": "Nur ich kann veröffentlichen und lesen",
|
||||||
"prefs_reservations_table_everyone_write_only": "Ich kann veröffentlichen und lesen, jeder kann veröffentlichen",
|
"prefs_reservations_table_everyone_write_only": "Ich kann veröffentlichen und lesen, jeder kann veröffentlichen",
|
||||||
"prefs_reservations_table_not_subscribed": "Nicht abonniert",
|
"prefs_reservations_table_not_subscribed": "Nicht abonniert",
|
||||||
"prefs_reservations_table_click_to_subscribe": "Klicken um zu abonnieren",
|
"prefs_reservations_table_click_to_subscribe": "Klicken um zu abonnieren",
|
||||||
|
|
1
web/public/static/langs/fi.json
Normal file
1
web/public/static/langs/fi.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -272,7 +272,7 @@
|
||||||
"account_delete_dialog_button_submit": "Supprimer définitivement le compte",
|
"account_delete_dialog_button_submit": "Supprimer définitivement le compte",
|
||||||
"account_delete_dialog_billing_warning": "Supprimer votre compte annule aussi immédiatement votre facturation. Vous n'aurez plus accès à votre tableau de bord de facturation.",
|
"account_delete_dialog_billing_warning": "Supprimer votre compte annule aussi immédiatement votre facturation. Vous n'aurez plus accès à votre tableau de bord de facturation.",
|
||||||
"account_upgrade_dialog_title": "Changer le tarif du compte",
|
"account_upgrade_dialog_title": "Changer le tarif du compte",
|
||||||
"account_upgrade_dialog_proration_info": "<strong>Facturation</strong> : Lors d'un changement entre un plan payant et un autre, la différence de prix sera créditée ou remboursée sur la prochaine facture. Vous ne recevrez pas d'autre facture avant la fin de la prochaine période de facturation.",
|
"account_upgrade_dialog_proration_info": "<strong>Facturation</strong> : Lors d'un changement vers un tiers payant, la différence de prix sera débitée <strong>immédiatement</strong>. En passant d'un tiers payant a gratuit, votre solde sera utilisé pour payer de futur factures.",
|
||||||
"account_upgrade_dialog_reservations_warning_other": "Le tarif sélectionné autorise moins de sujets réservés que votre tarif actuel. Avant de changer de tarif, <strong>veuillez supprimer au moins {{count}} sujets réservés</strong>. Vous pouvez supprimer des sujets réservés dans les <Link>Paramètres</Link>.",
|
"account_upgrade_dialog_reservations_warning_other": "Le tarif sélectionné autorise moins de sujets réservés que votre tarif actuel. Avant de changer de tarif, <strong>veuillez supprimer au moins {{count}} sujets réservés</strong>. Vous pouvez supprimer des sujets réservés dans les <Link>Paramètres</Link>.",
|
||||||
"account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} sujets réservés",
|
"account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} sujets réservés",
|
||||||
"account_upgrade_dialog_tier_features_messages_other": "{{messages}} messages journaliers",
|
"account_upgrade_dialog_tier_features_messages_other": "{{messages}} messages journaliers",
|
||||||
|
@ -368,8 +368,17 @@
|
||||||
"account_basics_phone_numbers_dialog_code_placeholder": "Ex : 123456",
|
"account_basics_phone_numbers_dialog_code_placeholder": "Ex : 123456",
|
||||||
"account_basics_phone_numbers_dialog_check_verification_button": "Code de confirmarion",
|
"account_basics_phone_numbers_dialog_check_verification_button": "Code de confirmarion",
|
||||||
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
|
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
|
||||||
"account_basics_phone_numbers_dialog_channel_call": "Appel",
|
"account_basics_phone_numbers_dialog_channel_call": "Appeler",
|
||||||
"account_usage_calls_none": "Aucun appels téléphoniques ne peut être fait avec ce compte",
|
"account_usage_calls_none": "Aucun appels téléphoniques ne peut être fait avec ce compte",
|
||||||
"publish_dialog_call_reset": "Supprimer les appels téléphoniques",
|
"publish_dialog_call_reset": "Supprimer les appels téléphoniques",
|
||||||
"publish_dialog_chip_call_label": "Appel téléphonique"
|
"publish_dialog_chip_call_label": "Appel téléphonique",
|
||||||
|
"account_upgrade_dialog_tier_features_messages_one": "{{messages}} message journalier",
|
||||||
|
"account_upgrade_dialog_tier_features_emails_one": "{{emails}} mail journalier",
|
||||||
|
"account_upgrade_dialog_tier_features_calls_other": "{{calls}} appels journaliers",
|
||||||
|
"account_upgrade_dialog_tier_features_no_calls": "Aucun appel",
|
||||||
|
"publish_dialog_call_item": "Appeler le numéro {{number}}",
|
||||||
|
"publish_dialog_chip_call_no_verified_numbers_tooltip": "Aucun numéro de téléphone vérifié",
|
||||||
|
"account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} sujet réservé",
|
||||||
|
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} appels journaliers",
|
||||||
|
"account_usage_calls_title": "Appels téléphoniques passés"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,384 @@
|
||||||
{}
|
{
|
||||||
|
"common_cancel": "Cancelar",
|
||||||
|
"common_save": "Gardar",
|
||||||
|
"common_add": "Engadir",
|
||||||
|
"signup_disabled": "O rexistro está desactivado",
|
||||||
|
"signup_error_username_taken": "O identificador {{username}} xa está collido",
|
||||||
|
"login_title": "Accede á túa conta ntfy",
|
||||||
|
"action_bar_send_test_notification": "Enviar notificación de proba",
|
||||||
|
"action_bar_clear_notifications": "Limpar todas as notificacións",
|
||||||
|
"action_bar_unsubscribe": "Retirar subscrición",
|
||||||
|
"action_bar_profile_settings": "Axustes",
|
||||||
|
"message_bar_type_message": "Escribe aquí a mensaxe",
|
||||||
|
"notifications_copied_to_clipboard": "Copiada ao portapapeis",
|
||||||
|
"notifications_attachment_image": "Imaxe anexa",
|
||||||
|
"notifications_attachment_copy_url_title": "Copiar URL do anexo ao portapapeis",
|
||||||
|
"notifications_attachment_copy_url_button": "Copiar URL",
|
||||||
|
"notifications_attachment_open_title": "Ir a {{url}}",
|
||||||
|
"notifications_attachment_file_audio": "ficheiro de audio",
|
||||||
|
"notifications_attachment_file_app": "ficheiro de app Android",
|
||||||
|
"notifications_attachment_file_document": "outro documento",
|
||||||
|
"notifications_click_copy_url_title": "Copiar URL da ligazón ao portapapeis",
|
||||||
|
"notifications_click_copy_url_button": "Copiar ligazón",
|
||||||
|
"notifications_actions_open_url_title": "Ir a {{url}}",
|
||||||
|
"notifications_none_for_topic_description": "Para enviar notificacións a este tema, simplemente usa PUT ou POST co URL do tema.",
|
||||||
|
"notifications_no_subscriptions_description": "Preme en \"{{linktext}} para crear ou subscribirte a un tema. Após, podes enviar mensaxes vía PUT ou POST e recibirás aquí as notificacións.",
|
||||||
|
"display_name_dialog_description": "Establecer un nome alternativo para o tema que será mostrado na lista de subscrición. Isto axudará a identificar os temas que teñan nomes complicados.",
|
||||||
|
"publish_dialog_tags_label": "Etiquetas",
|
||||||
|
"publish_dialog_tags_placeholder": "Lista de etiquetas separadas por vírgulas, ex. aviso, tarefa1",
|
||||||
|
"publish_dialog_priority_label": "Prioridade",
|
||||||
|
"publish_dialog_click_label": "URL a premer",
|
||||||
|
"publish_dialog_click_placeholder": "URL que se abre ao premer na notificación",
|
||||||
|
"publish_dialog_click_reset": "Desbotar o URL a premer",
|
||||||
|
"common_back": "Atrás",
|
||||||
|
"common_copy_to_clipboard": "Copiar ao portapapeis",
|
||||||
|
"signup_title": "Crear unha conta ntfy",
|
||||||
|
"signup_form_username": "Identificador",
|
||||||
|
"signup_form_password": "Contrasinal",
|
||||||
|
"signup_form_confirm_password": "Confirmar contrasinal",
|
||||||
|
"signup_form_button_submit": "Crear conta",
|
||||||
|
"login_form_button_submit": "Acceder",
|
||||||
|
"login_link_signup": "Crear conta",
|
||||||
|
"login_disabled": "O acceso está desactivado",
|
||||||
|
"action_bar_show_menu": "Mostrar menú",
|
||||||
|
"action_bar_toggle_mute": "Acalar/Reactivar as notificacións",
|
||||||
|
"message_bar_error_publishing": "Erro ao publicar a notificación",
|
||||||
|
"message_bar_publish": "Publicar mensaxe",
|
||||||
|
"nav_topics_title": "Temas subscritos",
|
||||||
|
"nav_button_documentation": "Documentación",
|
||||||
|
"nav_button_publish_message": "Publicar notificación",
|
||||||
|
"nav_button_subscribe": "Subscribirse ao tema",
|
||||||
|
"nav_button_muted": "Notificacións acaladas",
|
||||||
|
"nav_button_connecting": "conectando",
|
||||||
|
"nav_upgrade_banner_label": "Mellorar a ntfy Pro",
|
||||||
|
"alert_not_supported_description": "O teu navegador non ten soporte para notificacións.",
|
||||||
|
"notifications_priority_x": "Prioridade {{priority}}",
|
||||||
|
"notifications_attachment_link_expires": "a ligazón caduca o {{date}}",
|
||||||
|
"notifications_attachment_link_expired": "a ligazón de descarga caducou",
|
||||||
|
"notifications_attachment_file_image": "ficheiro de imaxe",
|
||||||
|
"notifications_attachment_file_video": "ficheiro de vídeo",
|
||||||
|
"notifications_actions_not_supported": "Acción non soportada na aplicación web",
|
||||||
|
"notifications_actions_http_request_title": "Enviar HTTP {{method}} a {{url}}",
|
||||||
|
"notifications_none_for_topic_title": "Aínda non recibiches ningunha notificación para este tema.",
|
||||||
|
"reserve_dialog_checkbox_label": "Reservar tema e configurar acceso",
|
||||||
|
"notifications_loading": "Cargando notificacións…",
|
||||||
|
"publish_dialog_base_url_placeholder": "URL de servizo, ex. https://exemplo.com",
|
||||||
|
"publish_dialog_topic_label": "Nome do tema",
|
||||||
|
"publish_dialog_topic_placeholder": "Nome do tema, ex. alertas_equipo",
|
||||||
|
"publish_dialog_topic_reset": "Restablecer tema",
|
||||||
|
"publish_dialog_title_label": "Título",
|
||||||
|
"publish_dialog_title_placeholder": "Título das notificacións, ex. Alerta de reunión",
|
||||||
|
"publish_dialog_message_label": "Mensaxe",
|
||||||
|
"publish_dialog_message_placeholder": "Escribe aquí a mensaxe",
|
||||||
|
"publish_dialog_email_label": "Correo electrónico",
|
||||||
|
"signup_form_toggle_password_visibility": "Cambiar visibilidade do contrasinal",
|
||||||
|
"signup_already_have_account": "Xa tes unha conta? Accede!",
|
||||||
|
"signup_error_creation_limit_reached": "Acadouse o límite de creación de contas",
|
||||||
|
"action_bar_logo_alt": "logo ntfy",
|
||||||
|
"action_bar_settings": "Axustes",
|
||||||
|
"action_bar_account": "Conta",
|
||||||
|
"action_bar_change_display_name": "Cambiar nome público",
|
||||||
|
"action_bar_reservation_add": "Reservar tema",
|
||||||
|
"action_bar_reservation_edit": "Cambiar a reserva",
|
||||||
|
"action_bar_reservation_delete": "Desbotar a reserva",
|
||||||
|
"action_bar_reservation_limit_reached": "Acadouse o límite",
|
||||||
|
"action_bar_toggle_action_menu": "Abrir/Pechar menú de accións",
|
||||||
|
"action_bar_profile_title": "Perfil",
|
||||||
|
"action_bar_profile_logout": "Pechar sesión",
|
||||||
|
"action_bar_sign_in": "Acceder",
|
||||||
|
"action_bar_sign_up": "Crear conta",
|
||||||
|
"message_bar_show_dialog": "Mostrar diálogo para publicar",
|
||||||
|
"nav_button_all_notifications": "Todas as notificacións",
|
||||||
|
"nav_button_account": "Conta",
|
||||||
|
"nav_button_settings": "Axustes",
|
||||||
|
"nav_upgrade_banner_description": "Reserva temas, máis mensaxes e correos electrónicos así como anexos máis grandes",
|
||||||
|
"alert_grant_title": "As notificacións están desactivadas",
|
||||||
|
"alert_grant_description": "Concede permiso no navegador para mostrar notificacións de escritorio.",
|
||||||
|
"alert_grant_button": "Conceder agora",
|
||||||
|
"alert_not_supported_title": "Non hai soporte para notificacións",
|
||||||
|
"alert_not_supported_context_description": "Só hai soporte para notificacións ao usar HTTPS. Esta é unha limitación da <mdnLink>API de Notificacións</mdnLink>.",
|
||||||
|
"notifications_list": "Lista de notificacións",
|
||||||
|
"notifications_list_item": "Notificación",
|
||||||
|
"notifications_mark_read": "Marcar como lida",
|
||||||
|
"notifications_delete": "Eliminar",
|
||||||
|
"notifications_tags": "Etiquetas",
|
||||||
|
"notifications_new_indicator": "Nova notificación",
|
||||||
|
"notifications_attachment_open_button": "Abrir anexo",
|
||||||
|
"notifications_click_open_button": "Abrir ligazón",
|
||||||
|
"notifications_none_for_any_title": "Non recibiches ningunha notificación.",
|
||||||
|
"notifications_none_for_any_description": "Para enviar notificacións ao tema, simplemente usa PUT ou POST ao URL do tema. Aquí tes un exemplo usando un dos teus temas.",
|
||||||
|
"notifications_no_subscriptions_title": "Semella que aínda non tes subscricións.",
|
||||||
|
"notifications_example": "Exemplo",
|
||||||
|
"display_name_dialog_title": "Cambiar nonme público",
|
||||||
|
"display_name_dialog_placeholder": "Nome público",
|
||||||
|
"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_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}}",
|
||||||
|
"publish_dialog_attachment_limits_quota_reached": "supera a cota, quedan {{remainingBytes}}",
|
||||||
|
"publish_dialog_emoji_picker_show": "Elixe emoji",
|
||||||
|
"publish_dialog_priority_min": "Prioridade Mínima",
|
||||||
|
"publish_dialog_priority_low": "Prioridade baixa",
|
||||||
|
"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 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",
|
||||||
|
"publish_dialog_delay_placeholder": "Adiar a entrega, ex. {{unixTimestamp}}, {{relativeTime}}, ou \"{{naturalLanguage}}\" (Só en inglés)",
|
||||||
|
"publish_dialog_other_features": "Outras características:",
|
||||||
|
"publish_dialog_chip_click_label": "Premer en URL",
|
||||||
|
"publish_dialog_chip_email_label": "Reenvío por correo",
|
||||||
|
"publish_dialog_chip_call_label": "Chamada de teléfono",
|
||||||
|
"publish_dialog_chip_attach_url_label": "Anexar ficheiro por URL",
|
||||||
|
"publish_dialog_button_cancel_sending": "Cancelar o envío",
|
||||||
|
"publish_dialog_button_cancel": "Cancelar",
|
||||||
|
"publish_dialog_button_send": "Enviar",
|
||||||
|
"publish_dialog_attached_file_title": "Ficheiro anexo:",
|
||||||
|
"publish_dialog_attached_file_filename_placeholder": "Nome do ficheiro anexo",
|
||||||
|
"publish_dialog_drop_file_here": "Soltar aquí o ficheiro",
|
||||||
|
"emoji_picker_search_placeholder": "Buscar emoji",
|
||||||
|
"subscribe_dialog_subscribe_title": "Subscribirse a un tema",
|
||||||
|
"publish_dialog_call_item": "Número de teléfono {{number}}",
|
||||||
|
"publish_dialog_email_placeholder": "Enderezo ao que reenviar a notificación, ex. xoana@exemplo.com",
|
||||||
|
"publish_dialog_email_reset": "Retirar reenvío ao correo",
|
||||||
|
"publish_dialog_attach_label": "URL do anexo",
|
||||||
|
"publish_dialog_attach_placeholder": "Anexa un ficheiro por URL, ex. https://f-droid.org/F-Droid.apk",
|
||||||
|
"publish_dialog_attach_reset": "Retirar URL do anexo",
|
||||||
|
"publish_dialog_filename_placeholder": "Nome do ficheiro anexo",
|
||||||
|
"publish_dialog_filename_label": "Nome do ficheiro",
|
||||||
|
"publish_dialog_delay_label": "Adiar",
|
||||||
|
"publish_dialog_delay_reset": "Retirar o adiadamento da entrega",
|
||||||
|
"publish_dialog_chip_attach_file_label": "Anexar ficheiro local",
|
||||||
|
"publish_dialog_chip_delay_label": "Entrega adiada",
|
||||||
|
"publish_dialog_chip_topic_label": "Cambiar tema",
|
||||||
|
"publish_dialog_details_examples_description": "Para ver exemplos e unha descrición polo miúdo das ferramentas de envío, le a <docsLink>documentación</docsLink>.",
|
||||||
|
"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",
|
||||||
|
"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."
|
||||||
|
}
|
||||||
|
|
|
@ -267,5 +267,42 @@
|
||||||
"publish_dialog_chip_call_label": "Chiamata telefonica",
|
"publish_dialog_chip_call_label": "Chiamata telefonica",
|
||||||
"publish_dialog_chip_call_no_verified_numbers_tooltip": "Nessun numero verificato",
|
"publish_dialog_chip_call_no_verified_numbers_tooltip": "Nessun numero verificato",
|
||||||
"account_basics_phone_numbers_title": "Numeri di telefono",
|
"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_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_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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -190,5 +190,10 @@
|
||||||
"error_boundary_unsupported_indexeddb_title": "Privat surfing støttes ikke",
|
"error_boundary_unsupported_indexeddb_title": "Privat surfing støttes ikke",
|
||||||
"action_bar_account": "Konto",
|
"action_bar_account": "Konto",
|
||||||
"action_bar_profile_settings": "Innstillinger",
|
"action_bar_profile_settings": "Innstillinger",
|
||||||
"nav_button_account": "Konto"
|
"nav_button_account": "Konto",
|
||||||
|
"signup_title": "Opprett en ntfy konto",
|
||||||
|
"signup_form_username": "Brukernavn",
|
||||||
|
"signup_form_password": "Passord",
|
||||||
|
"signup_form_button_submit": "Meld deg på",
|
||||||
|
"signup_form_confirm_password": "Bekreft passord"
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue