mirror of
https://github.com/binwiederhier/ntfy.git
synced 2024-05-19 11:53:26 +12:00
Compare commits
102 commits
Author | SHA1 | Date | |
---|---|---|---|
9d3fc20e58 | |||
0be467f809 | |||
ec75ce0787 | |||
d11b1007ef | |||
c542dd8c6f | |||
37697aed27 | |||
4360d157b2 | |||
c3c4d65f99 | |||
ffd7645c0b | |||
043738a475 | |||
fb52ad6fdb | |||
29318f9d61 | |||
030f7266f7 | |||
9692de1469 | |||
eab90a0275 | |||
e6f70f8e41 | |||
499b0dd839 | |||
31d0c812ce | |||
d37f861f6b | |||
0a49a8d88b | |||
b63ef0defb | |||
f8068ef561 | |||
2608687e98 | |||
02564a40c7 | |||
bdd49f4e16 | |||
33b603def5 | |||
6eff5553b5 | |||
7cac03c1ec | |||
b33918f267 | |||
f68ad6acdf | |||
a533bf9efb | |||
66ea805cde | |||
7c3b6e4521 | |||
9cb3d056fe | |||
4111bee0c4 | |||
e4c2b938d3 | |||
fc7cf5933f | |||
e4d22ebd8b | |||
69d6e0f890 | |||
ecab7fbf65 | |||
75887e4a62 | |||
130039f5c8 | |||
bec0d4807b | |||
5ee62033b5 | |||
3e02d7b0bb | |||
290ed1124e | |||
fc62682334 | |||
28404565d2 | |||
f8548e9d46 | |||
d90b290cd2 | |||
21c6776269 | |||
7fed392e0c | |||
913b59b5e3 | |||
4692ca7b7f | |||
af16542d02 | |||
5511812e30 | |||
547b09a7e5 | |||
b9c176ddba | |||
f971377cbb | |||
a04f2f9c9a | |||
763eafd5dd | |||
9247dac50d | |||
de65d07518 | |||
1966f80855 | |||
4b2e38320d | |||
83356f565e | |||
7fd5f0b29d | |||
03737dbf5c | |||
867cf28080 | |||
b2eb5b94bd | |||
df7d6baec5 | |||
a4f5c8dee7 | |||
4c0ec3f75b | |||
12bbe9a1ae | |||
0a589f6242 | |||
ab2dd6136e | |||
4d64515e45 | |||
411597ecc2 | |||
1a426da913 | |||
7936c38feb | |||
d0beaa900f | |||
f4bf8fd9bb | |||
d866cb2fd9 | |||
0ab6171962 | |||
b7c2898b9c | |||
d155ebb3a4 | |||
3d5bb92774 | |||
a2f1a45097 | |||
b43fff7c7e | |||
b186c7a324 | |||
e50b6f4075 | |||
e5342d5eca | |||
a25fc1daaa | |||
6e75108aa9 | |||
0735a5cb48 | |||
088aede833 | |||
2119954f67 | |||
220b012ae3 | |||
e1278a5e92 | |||
0ae62d36d2 | |||
70729edb2b | |||
de2f7d3e9b |
37
README.md
37
README.md
|
@ -9,7 +9,6 @@
|
|||
[![Discord](https://img.shields.io/discord/874398661709295626?label=Discord)](https://discord.gg/cT7ECsZj9w)
|
||||
[![Matrix](https://img.shields.io/matrix/ntfy:matrix.org?label=Matrix)](https://matrix.to/#/#ntfy:matrix.org)
|
||||
[![Matrix space](https://img.shields.io/matrix/ntfy-space:matrix.org?label=Matrix+space)](https://matrix.to/#/#ntfy-space:matrix.org)
|
||||
[![Lemmy](https://img.shields.io/badge/Lemmy-discuss-green)](https://discuss.ntfy.sh/c/ntfy)
|
||||
[![Healthcheck](https://healthchecks.io/badge/68b65976-b3b0-4102-aec9-980921/kcoEgrLY.svg)](https://ntfy.statuspage.io/)
|
||||
[![Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod)](https://gitpod.io/#https://github.com/binwiederhier/ntfy)
|
||||
|
||||
|
@ -50,7 +49,6 @@ 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
|
||||
|
@ -71,7 +69,7 @@ for the server and the Android app. Or, if you'd like to help translate 🇩🇪
|
|||
## 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:
|
||||
account costs. Even small donations are very much appreciated. A big fat **Thank You** to the folks who have sponsored ntfy in the past, or are still 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>
|
||||
|
@ -168,6 +166,39 @@ account costs. Even small donations are very much appreciated. A big fat **Thank
|
|||
<a href="https://github.com/ubipo"><img src="https://github.com/ubipo.png" width="40px" /></a>
|
||||
<a href="https://github.com/tka85"><img src="https://github.com/tka85.png" width="40px" /></a>
|
||||
<a href="https://github.com/beekeeb"><img src="https://github.com/beekeeb.png" width="40px" /></a>
|
||||
<a href="https://github.com/Emiliaaah"><img src="https://github.com/Emiliaaah.png" width="40px" /></a>
|
||||
<a href="https://github.com/zark0s"><img src="https://github.com/zark0s.png" width="40px" /></a>
|
||||
<a href="https://github.com/tomershvueli"><img src="https://github.com/tomershvueli.png" width="40px" /></a>
|
||||
<a href="https://github.com/CataIana"><img src="https://github.com/CataIana.png" width="40px" /></a>
|
||||
<a href="https://github.com/ajay-actuary"><img src="https://github.com/ajay-actuary.png" width="40px" /></a>
|
||||
<a href="https://github.com/mursec"><img src="https://github.com/mursec.png" width="40px" /></a>
|
||||
<a href="https://github.com/FrameXX"><img src="https://github.com/FrameXX.png" width="40px" /></a>
|
||||
<a href="https://github.com/vovayartsev"><img src="https://github.com/vovayartsev.png" width="40px" /></a>
|
||||
<a href="https://github.com/dwain-lab"><img src="https://github.com/dwain-lab.png" width="40px" /></a>
|
||||
<a href="https://github.com/brookmg"><img src="https://github.com/brookmg.png" width="40px" /></a>
|
||||
<a href="https://github.com/siebej"><img src="https://github.com/siebej.png" width="40px" /></a>
|
||||
<a href="https://github.com/rxsantos"><img src="https://github.com/rxsantos.png" width="40px" /></a>
|
||||
<a href="https://github.com/hermannx5"><img src="https://github.com/hermannx5.png" width="40px" /></a>
|
||||
<a href="https://github.com/rwxd"><img src="https://github.com/rwxd.png" width="40px" /></a>
|
||||
<a href="https://github.com/Integral-Tech"><img src="https://github.com/Integral-Tech.png" width="40px" /></a>
|
||||
<a href="https://github.com/TheTomik1"><img src="https://github.com/TheTomik1.png" width="40px" /></a>
|
||||
<a href="https://github.com/dav23r"><img src="https://github.com/dav23r.png" width="40px" /></a>
|
||||
<a href="https://github.com/stannynuytkens"><img src="https://github.com/stannynuytkens.png" width="40px" /></a>
|
||||
<a href="https://github.com/danbartram"><img src="https://github.com/danbartram.png" width="40px" /></a>
|
||||
<a href="https://github.com/arthurgleckler"><img src="https://github.com/arthurgleckler.png" width="40px" /></a>
|
||||
<a href="https://github.com/tomroth04"><img src="https://github.com/tomroth04.png" width="40px" /></a>
|
||||
<a href="https://github.com/Circenn5130"><img src="https://github.com/Circenn5130.png" width="40px" /></a>
|
||||
<a href="https://github.com/jceloria"><img src="https://github.com/jceloria.png" width="40px" /></a>
|
||||
<a href="https://github.com/afunworm"><img src="https://github.com/afunworm.png" width="40px" /></a>
|
||||
<a href="https://github.com/PTR-inc"><img src="https://github.com/PTR-inc.png" width="40px" /></a>
|
||||
<a href="https://github.com/spudooli"><img src="https://github.com/spudooli.png" width="40px" /></a>
|
||||
<a href="https://github.com/IMarkoMC"><img src="https://github.com/IMarkoMC.png" width="40px" /></a>
|
||||
<a href="https://github.com/rubund"><img src="https://github.com/rubund.png" width="40px" /></a>
|
||||
<a href="https://github.com/Riolku"><img src="https://github.com/Riolku.png" width="40px" /></a>
|
||||
<a href="https://github.com/arnbrhm"><img src="https://github.com/arnbrhm.png" width="40px" /></a>
|
||||
<a href="https://github.com/herzkerl"><img src="https://github.com/herzkerl.png" width="40px" /></a>
|
||||
<a href="https://github.com/0x45796164"><img src="https://github.com/0x45796164.png" width="40px" /></a>
|
||||
<a href="https://github.com/madchr1st"><img src="https://github.com/madchr1st.png" width="40px" /></a>
|
||||
|
||||
I'd also like to thank JetBrains for their awesome [IntelliJ IDEA](https://www.jetbrains.com/idea/),
|
||||
and [DigitalOcean](https://m.do.co/c/442b929528db) (*referral link*) for supporting the project:
|
||||
|
|
|
@ -607,6 +607,8 @@ This will only work on selfhosted [traccar](https://www.traccar.org/) ([Github](
|
|||
|
||||
The easiest way to integrate traccar with ntfy, is to configure ntfy as the SMS provider for your instance. You then can set your ntfy topic as your account's phone number in traccar. Sending the email notifications to ntfy will not work, as ntfy does not support HTML emails.
|
||||
|
||||
**Info:** Add a phone number to your traccar account not in device, as otherwise it will not try to send SMS.
|
||||
|
||||
**Caution:** JSON publishing is only possible, when POST-ing to the root URL of the ntfy instance. (see [documentation](publish.md#publish-as-json))
|
||||
```xml
|
||||
<entry key='sms.http.url'>https://ntfy.sh</entry>
|
||||
|
|
|
@ -30,37 +30,37 @@ deb/rpm packages.
|
|||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.9.0/ntfy_2.9.0_linux_amd64.tar.gz
|
||||
tar zxvf ntfy_2.9.0_linux_amd64.tar.gz
|
||||
sudo cp -a ntfy_2.9.0_linux_amd64/ntfy /usr/local/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.9.0_linux_amd64/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.11.0/ntfy_2.11.0_linux_amd64.tar.gz
|
||||
tar zxvf ntfy_2.11.0_linux_amd64.tar.gz
|
||||
sudo cp -a ntfy_2.11.0_linux_amd64/ntfy /usr/local/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.11.0_linux_amd64/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
=== "armv6"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.9.0/ntfy_2.9.0_linux_armv6.tar.gz
|
||||
tar zxvf ntfy_2.9.0_linux_armv6.tar.gz
|
||||
sudo cp -a ntfy_2.9.0_linux_armv6/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.9.0_linux_armv6/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.11.0/ntfy_2.11.0_linux_armv6.tar.gz
|
||||
tar zxvf ntfy_2.11.0_linux_armv6.tar.gz
|
||||
sudo cp -a ntfy_2.11.0_linux_armv6/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.11.0_linux_armv6/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.9.0/ntfy_2.9.0_linux_armv7.tar.gz
|
||||
tar zxvf ntfy_2.9.0_linux_armv7.tar.gz
|
||||
sudo cp -a ntfy_2.9.0_linux_armv7/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.9.0_linux_armv7/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.11.0/ntfy_2.11.0_linux_armv7.tar.gz
|
||||
tar zxvf ntfy_2.11.0_linux_armv7.tar.gz
|
||||
sudo cp -a ntfy_2.11.0_linux_armv7/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.11.0_linux_armv7/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
=== "arm64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.9.0/ntfy_2.9.0_linux_arm64.tar.gz
|
||||
tar zxvf ntfy_2.9.0_linux_arm64.tar.gz
|
||||
sudo cp -a ntfy_2.9.0_linux_arm64/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.9.0_linux_arm64/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.11.0/ntfy_2.11.0_linux_arm64.tar.gz
|
||||
tar zxvf ntfy_2.11.0_linux_arm64.tar.gz
|
||||
sudo cp -a ntfy_2.11.0_linux_arm64/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.11.0_linux_arm64/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
|
@ -110,7 +110,7 @@ Manually installing the .deb file:
|
|||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.9.0/ntfy_2.9.0_linux_amd64.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.11.0/ntfy_2.11.0_linux_amd64.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
|
@ -118,7 +118,7 @@ Manually installing the .deb file:
|
|||
|
||||
=== "armv6"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.9.0/ntfy_2.9.0_linux_armv6.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.11.0/ntfy_2.11.0_linux_armv6.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
|
@ -126,7 +126,7 @@ Manually installing the .deb file:
|
|||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.9.0/ntfy_2.9.0_linux_armv7.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.11.0/ntfy_2.11.0_linux_armv7.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
|
@ -134,7 +134,7 @@ Manually installing the .deb file:
|
|||
|
||||
=== "arm64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.9.0/ntfy_2.9.0_linux_arm64.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.11.0/ntfy_2.11.0_linux_arm64.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
|
@ -144,28 +144,28 @@ Manually installing the .deb file:
|
|||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.9.0/ntfy_2.9.0_linux_amd64.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.11.0/ntfy_2.11.0_linux_amd64.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
||||
=== "armv6"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.9.0/ntfy_2.9.0_linux_armv6.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.11.0/ntfy_2.11.0_linux_armv6.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.9.0/ntfy_2.9.0_linux_armv7.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.11.0/ntfy_2.11.0_linux_armv7.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
||||
=== "arm64"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.9.0/ntfy_2.9.0_linux_arm64.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.11.0/ntfy_2.11.0_linux_arm64.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
@ -195,18 +195,18 @@ NixOS also supports [declarative setup of the ntfy server](https://search.nixos.
|
|||
|
||||
## macOS
|
||||
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on macOS as well.
|
||||
To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.9.0/ntfy_2.9.0_darwin_all.tar.gz),
|
||||
To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.11.0/ntfy_2.11.0_darwin_all.tar.gz),
|
||||
extract it and place it somewhere in your `PATH` (e.g. `/usr/local/bin/ntfy`).
|
||||
|
||||
If run as `root`, ntfy will look for its config at `/etc/ntfy/client.yml`. For all other users, it'll look for it at
|
||||
`~/Library/Application Support/ntfy/client.yml` (sample included in the tarball).
|
||||
|
||||
```bash
|
||||
curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.9.0/ntfy_2.9.0_darwin_all.tar.gz > ntfy_2.9.0_darwin_all.tar.gz
|
||||
tar zxvf ntfy_2.9.0_darwin_all.tar.gz
|
||||
sudo cp -a ntfy_2.9.0_darwin_all/ntfy /usr/local/bin/ntfy
|
||||
curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.11.0/ntfy_2.11.0_darwin_all.tar.gz > ntfy_2.11.0_darwin_all.tar.gz
|
||||
tar zxvf ntfy_2.11.0_darwin_all.tar.gz
|
||||
sudo cp -a ntfy_2.11.0_darwin_all/ntfy /usr/local/bin/ntfy
|
||||
mkdir ~/Library/Application\ Support/ntfy
|
||||
cp ntfy_2.9.0_darwin_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
|
||||
cp ntfy_2.11.0_darwin_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
|
||||
ntfy --help
|
||||
```
|
||||
|
||||
|
@ -224,7 +224,7 @@ brew install ntfy
|
|||
|
||||
## Windows
|
||||
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on Windows as well.
|
||||
To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.9.0/ntfy_2.9.0_windows_amd64.zip),
|
||||
To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.11.0/ntfy_2.11.0_windows_amd64.zip),
|
||||
extract it and place the `ntfy.exe` binary somewhere in your `%Path%`.
|
||||
|
||||
The default path for the client config file is at `%AppData%\ntfy\client.yml` (not created automatically, sample in the ZIP file).
|
||||
|
|
|
@ -34,6 +34,7 @@ I've added a ⭐ to projects or posts that have a significant following, or had
|
|||
- [Overseer](https://docs.overseerr.dev/using-overseerr/notifications/webhooks) ⭐ - a request management and media discovery tool for Plex (see [integration example](examples.md#jellyseerroverseerr-webhook))
|
||||
- [Tautulli](https://github.com/Tautulli/Tautulli) ⭐ - Monitoring and tracking tool for Plex (integration [via webhook](https://github.com/Tautulli/Tautulli/wiki/Notification-Agents-Guide#webhook))
|
||||
- [Mailrise](https://github.com/YoRyan/mailrise) - An SMTP gateway (integration via [Apprise](https://github.com/caronc/apprise/wiki/Notify_ntfy))
|
||||
- [Proxmox-Ntfy](https://github.com/qtsone/proxmox-ntfy) - Python script that monitors Proxmox tasks and sends notifications using the Ntfy service.
|
||||
|
||||
## [UnifiedPush](https://unifiedpush.org/users/apps/) integrations
|
||||
|
||||
|
@ -144,9 +145,18 @@ I've added a ⭐ to projects or posts that have a significant following, or had
|
|||
|
||||
## Blog + forum posts
|
||||
|
||||
- [ntfy / Emacs Lisp](https://speechcode.com/blog/ntfy/) - speechcode.com - 3/2024
|
||||
- [Boost Your Productivity with ntfy.sh: The Ultimate Notification Tool for Command-Line Users](https://dev.to/archetypal/boost-your-productivity-with-ntfysh-the-ultimate-notification-tool-for-command-line-users-iil) - dev.to - 3/2024
|
||||
- [Nextcloud Talk (F-Droid version) notifications using ntfy (ntfy.sh)](https://www.youtube.com/watch?v=0a6PpfN5PD8) - youtube.com - 2/2024
|
||||
- [ZFS and SMART Warnings via Ntfy](https://rair.dev/zfs-smart-ntfy/) - rair.dev - 2/2024
|
||||
- [Automating Security Camera Notifications With Home Assistant and Ntfy](https://runtimeterror.dev/automating-camera-notifications-home-assistant-ntfy/) ⭐ - runtimeterror.dev - 2/2024
|
||||
- [Ntfy: self-hosted notification service](https://medium.com/@williamdonze/ntfy-self-hosted-notification-service-0f3eada6e657) ⭐ - williamdonze.medium.com - 1/2024
|
||||
- [Let’s Supercharge Snowflake Alerts with Cool ntfy Open-source Notifications!](https://sarathi-data-ml-cloud.medium.com/lets-supercharge-snowflake-alerts-with-cool-ntfy-open-source-notifications-296da442c331) - sarathi-data-ml-cloud.medium.com - 1/2024
|
||||
- [Setting up NTFY with Ngnix-Proxy-Manager, authentication and Ansible notifications](https://random-it-blog.de/rocky-linux/setting-up-ntfy-with-ngnix-proxy-manager-authentication-and-ansible-notifications/) - random-it-blog.de - 12/2023
|
||||
- [Introducing the Monitoring Ntfy.sh Integration Module: Real-time Notifications for Drupal Monitoring](https://cyberschorsch.dev/drupal/introducing-monitoring-ntfysh-integration-module-real-time-notifications-drupal-monitoring) - cyberschorsch.dev - 11/2023
|
||||
- [How to install Ntfy.sh on CasaOS using BigBearCasaOS](https://www.youtube.com/watch?v=wSWhtSNwTd8) - youtube.com - 10/2023
|
||||
- [Podman Update Notifications via Ntfy](https://rair.dev/podman-update-notifications-ntfy/) - rair.dev - 9/2023
|
||||
- [Easy Push Notifications With ntfy.sh](https://runtimeterror.dev/easy-push-notifications-with-ntfy/) ⭐ - runtimeterror.dev - 9/2023
|
||||
- [Ntfy: Your Ultimate Push Notification Powerhouse!](https://kkamalesh117.medium.com/ntfy-your-ultimate-push-notification-powerhouse-1968c070f1d1) - kkamalesh117.medium.com - 9/2023
|
||||
- [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
|
||||
|
|
144
docs/publish.md
144
docs/publish.md
File diff suppressed because one or more lines are too long
|
@ -2,13 +2,43 @@
|
|||
Binaries for all releases can be found on the GitHub releases pages for the [ntfy server](https://github.com/binwiederhier/ntfy/releases)
|
||||
and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/releases).
|
||||
|
||||
### ntfy server v2.11.0
|
||||
Released May 13, 2024
|
||||
|
||||
This is a tiny release that fixes a database index issue that caused performance issues on ntfy.sh. It also fixes a bug
|
||||
in the rate visitor logic that caused rate visitors to be assigned to seemingly random topics. Nothing major this time.
|
||||
|
||||
❤️ Quick reminder that if you like ntfy, **please consider sponsoring us** via [GitHub Sponsors](https://github.com/sponsors/binwiederhier)
|
||||
and [Liberapay](https://en.liberapay.com/ntfy/), or buying a [paid plan via the web app](https://ntfy.sh/app). ntfy will always remain open source.
|
||||
|
||||
**Bug fixes + maintenance:**
|
||||
|
||||
* Re-add database index `idx_topic` to the `messages` table to fix performance issues on ntfy.sh (no ticket, big thanks to [@tcaputi](https://github.com/tcaputi) for finding this issue)
|
||||
* Do not set rate visitor for non-eligible topics (no ticket)
|
||||
* Do not cache `config.js` ([#1098](https://github.com/binwiederhier/ntfy/pull/1098), thanks to [@wunter8](https://github.com/wunter8))
|
||||
|
||||
### ntfy server v2.10.0
|
||||
Released Mar 27, 2024
|
||||
|
||||
This release adds support for **message templating** in the ntfy server, which allows you to include a message and/or
|
||||
title template that will be filled with values from a JSON body (e.g. `curl -gd '{"alert":"Disk space low"}' "ntfy.sh/mytopic?tpl=1&m={{.alert}}"`).
|
||||
This is great for services that let you specify a webhook URL but do not let you change the webhook body (such as GitHub, or Grafana).
|
||||
|
||||
**Features:**
|
||||
|
||||
* [Message templating](publish.md#message-templating): You can now include a message and/or title template that will be filled with values from a JSON body ([#724](https://github.com/binwiederhier/ntfy/issues/724), thanks to [@wunter8](https://github.com/wunter8) for implementing)
|
||||
|
||||
### ntfy server v2.9.0
|
||||
Released Mar 7, 2024
|
||||
|
||||
A small release after a long pause (lots of day job work). This release adds for **larger messages** and **longer message delays** in scheduled delivery messages. The web app also now supports pasting images from the clipboard. Other than that, only a few bug fixes and documentation updates, and a teeny tiny breaking change 😬.
|
||||
A small release after a long pause (lots of day job work). This release adds for **larger messages** and **longer
|
||||
message delays** in scheduled delivery messages. The web app also now supports pasting images from the clipboard. Other
|
||||
than that, only a few bug fixes and documentation updates, and a teeny tiny breaking change 😬.
|
||||
|
||||
!!! info
|
||||
⚠️ **Breaking change**: The `Rate-Topics` header was removed due to a [DoS issue](https://github.com/binwiederhier/ntfy/issues/1048). This only affects installations with `visitor-subscriber-rate-limiting: true`, which is not the default and likely very rarely used. Normally I'd never remove a feature, but this is a security issue, and likely affects almost nobody.
|
||||
⚠️ **Breaking change**: The `Rate-Topics` header was removed due to a [DoS issue](https://github.com/binwiederhier/ntfy/issues/1048). This only affects
|
||||
installations with `visitor-subscriber-rate-limiting: true`, which is not the default and likely very rarely used.
|
||||
Normally I'd never remove a feature, but this is a security issue, and likely affects almost nobody.
|
||||
|
||||
**Features:**
|
||||
|
||||
|
@ -30,7 +60,9 @@ A small release after a long pause (lots of day job work). This release adds for
|
|||
## ntfy iOS app v1.3
|
||||
Released Nov 26, 2023
|
||||
|
||||
This release (hopefully) fixes the issues with the iOS UI not updating properly when new notifications arrive, as well as notifications not being received (anymore) after previously working. Both issues have been annoying and known bugs for a long time, and I hope that they are finally fixed.
|
||||
This release (hopefully) fixes the issues with the iOS UI not updating properly when new notifications arrive, as well
|
||||
as notifications not being received (anymore) after previously working. Both issues have been annoying and known bugs
|
||||
for a long time, and I hope that they are finally fixed.
|
||||
|
||||
Many thanks to [@tcaputi](https://github.com/tcaputi) for fixing the issues, and to the anonymous donor for sponsoring these fixes.
|
||||
|
||||
|
@ -41,7 +73,10 @@ Many thanks to [@tcaputi](https://github.com/tcaputi) for fixing the issues, and
|
|||
## ntfy server v2.8.0
|
||||
Released November 19, 2023
|
||||
|
||||
This release brings a handful of random bug fixes: two unrelated access control list fixes, a fix around web app crashes for languages with underscores in the language code (e.g. `zh_Hant`, `zh_Hans`, `pt_BR`, ...), a workaround for the `Priority` header (often used in Cloudflare setups), and support among others support for HTML-only emails (finally), web app crash fixes
|
||||
This release brings a handful of random bug fixes: two unrelated access control list fixes, a fix around web app crashes
|
||||
for languages with underscores in the language code (e.g. `zh_Hant`, `zh_Hans`, `pt_BR`, ...), a workaround for the
|
||||
`Priority` header (often used in Cloudflare setups), and support among others support for HTML-only emails (finally),
|
||||
web app crash fixes
|
||||
|
||||
**Bug fixes + maintenance:**
|
||||
|
||||
|
|
BIN
docs/static/img/android-screenshot-template.jpg
vendored
Normal file
BIN
docs/static/img/android-screenshot-template.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 122 KiB |
130
docs/static/js/extra.js
vendored
130
docs/static/js/extra.js
vendored
|
@ -1,99 +1,103 @@
|
|||
// Link tabs, as per https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/#linked-tabs
|
||||
|
||||
const savedCodeTab = localStorage.getItem('savedTab')
|
||||
const codeTabs = document.querySelectorAll(".tabbed-set > input")
|
||||
const savedCodeTab = localStorage.getItem("savedTab");
|
||||
const codeTabs = document.querySelectorAll(".tabbed-set > input");
|
||||
for (const tab of codeTabs) {
|
||||
tab.addEventListener("click", () => {
|
||||
const current = document.querySelector(`label[for=${tab.id}]`)
|
||||
const pos = current.getBoundingClientRect().top
|
||||
const labelContent = current.innerHTML
|
||||
const labels = document.querySelectorAll('.tabbed-set > label, .tabbed-alternate > .tabbed-labels > label')
|
||||
for (const label of labels) {
|
||||
if (label.innerHTML === labelContent) {
|
||||
document.querySelector(`input[id=${label.getAttribute('for')}]`).checked = true
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve scroll position
|
||||
const delta = (current.getBoundingClientRect().top) - pos
|
||||
window.scrollBy(0, delta)
|
||||
|
||||
// Save
|
||||
localStorage.setItem('savedTab', labelContent)
|
||||
})
|
||||
|
||||
// Select saved tab
|
||||
const current = document.querySelector(`label[for=${tab.id}]`)
|
||||
const labelContent = current.innerHTML
|
||||
if (savedCodeTab === labelContent) {
|
||||
tab.checked = true
|
||||
tab.addEventListener("click", () => {
|
||||
const current = document.querySelector(`label[for=${tab.id}]`);
|
||||
const pos = current.getBoundingClientRect().top;
|
||||
const labelContent = current.innerHTML;
|
||||
const labels = document.querySelectorAll(".tabbed-set > label, .tabbed-alternate > .tabbed-labels > label");
|
||||
for (const label of labels) {
|
||||
if (label.innerHTML === labelContent) {
|
||||
document.querySelector(`input[id=${label.getAttribute("for")}]`).checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve scroll position
|
||||
const delta = (current.getBoundingClientRect().top) - pos;
|
||||
window.scrollBy(0, delta);
|
||||
|
||||
// Save
|
||||
localStorage.setItem("savedTab", labelContent);
|
||||
});
|
||||
|
||||
// Select saved tab
|
||||
const current = document.querySelector(`label[for=${tab.id}]`);
|
||||
const labelContent = current.innerHTML;
|
||||
if (savedCodeTab === labelContent) {
|
||||
tab.checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Lightbox for screenshot
|
||||
|
||||
const lightbox = document.createElement('div');
|
||||
lightbox.classList.add('lightbox');
|
||||
const lightbox = document.createElement("div");
|
||||
lightbox.classList.add("lightbox");
|
||||
document.body.appendChild(lightbox);
|
||||
|
||||
const showScreenshotOverlay = (e, el, group, index) => {
|
||||
lightbox.classList.add('show');
|
||||
document.addEventListener('keydown', nextScreenshotKeyboardListener);
|
||||
return showScreenshot(e, group, index);
|
||||
lightbox.classList.add("show");
|
||||
document.addEventListener("keydown", nextScreenshotKeyboardListener);
|
||||
return showScreenshot(e, group, index);
|
||||
};
|
||||
|
||||
const showScreenshot = (e, group, index) => {
|
||||
const actualIndex = resolveScreenshotIndex(group, index);
|
||||
lightbox.innerHTML = '<div class="close-lightbox"></div>' + screenshots[group][actualIndex].innerHTML;
|
||||
lightbox.querySelector('img').onclick = (e) => { return showScreenshot(e, group, actualIndex+1); };
|
||||
currentScreenshotGroup = group;
|
||||
currentScreenshotIndex = actualIndex;
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
const actualIndex = resolveScreenshotIndex(group, index);
|
||||
lightbox.innerHTML = "<div class=\"close-lightbox\"></div>" + screenshots[group][actualIndex].innerHTML;
|
||||
lightbox.querySelector("img").onclick = (e) => {
|
||||
return showScreenshot(e, group, actualIndex + 1);
|
||||
};
|
||||
currentScreenshotGroup = group;
|
||||
currentScreenshotIndex = actualIndex;
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
};
|
||||
|
||||
const nextScreenshot = (e) => {
|
||||
return showScreenshot(e, currentScreenshotGroup, currentScreenshotIndex+1);
|
||||
return showScreenshot(e, currentScreenshotGroup, currentScreenshotIndex + 1);
|
||||
};
|
||||
|
||||
const previousScreenshot = (e) => {
|
||||
return showScreenshot(e, currentScreenshotGroup, currentScreenshotIndex-1);
|
||||
return showScreenshot(e, currentScreenshotGroup, currentScreenshotIndex - 1);
|
||||
};
|
||||
|
||||
const resolveScreenshotIndex = (group, index) => {
|
||||
if (index < 0) {
|
||||
return screenshots[group].length - 1;
|
||||
} else if (index > screenshots[group].length - 1) {
|
||||
return 0;
|
||||
}
|
||||
return index;
|
||||
if (index < 0) {
|
||||
return screenshots[group].length - 1;
|
||||
} else if (index > screenshots[group].length - 1) {
|
||||
return 0;
|
||||
}
|
||||
return index;
|
||||
};
|
||||
|
||||
const hideScreenshotOverlay = (e) => {
|
||||
lightbox.classList.remove('show');
|
||||
document.removeEventListener('keydown', nextScreenshotKeyboardListener);
|
||||
lightbox.classList.remove("show");
|
||||
document.removeEventListener("keydown", nextScreenshotKeyboardListener);
|
||||
};
|
||||
|
||||
const nextScreenshotKeyboardListener = (e) => {
|
||||
switch (e.keyCode) {
|
||||
case 37:
|
||||
previousScreenshot(e);
|
||||
break;
|
||||
case 39:
|
||||
nextScreenshot(e);
|
||||
break;
|
||||
}
|
||||
switch (e.keyCode) {
|
||||
case 37:
|
||||
previousScreenshot(e);
|
||||
break;
|
||||
case 39:
|
||||
nextScreenshot(e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
let currentScreenshotGroup = '';
|
||||
let currentScreenshotGroup = "";
|
||||
let currentScreenshotIndex = 0;
|
||||
let screenshots = {};
|
||||
Array.from(document.getElementsByClassName('screenshots')).forEach((sg) => {
|
||||
const group = sg.id;
|
||||
screenshots[group] = [...sg.querySelectorAll('a')];
|
||||
screenshots[group].forEach((el, index) => {
|
||||
el.onclick = (e) => { return showScreenshotOverlay(e, el, group, index); };
|
||||
});
|
||||
Array.from(document.getElementsByClassName("screenshots")).forEach((sg) => {
|
||||
const group = sg.id;
|
||||
screenshots[group] = [...sg.querySelectorAll("a")];
|
||||
screenshots[group].forEach((el, index) => {
|
||||
el.onclick = (e) => {
|
||||
return showScreenshotOverlay(e, el, group, index);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
lightbox.onclick = hideScreenshotOverlay;
|
||||
|
|
76
go.mod
76
go.mod
|
@ -6,22 +6,22 @@ toolchain go1.21.3
|
|||
|
||||
require (
|
||||
cloud.google.com/go/firestore v1.15.0 // indirect
|
||||
cloud.google.com/go/storage v1.39.0 // indirect
|
||||
cloud.google.com/go/storage v1.41.0 // indirect
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/emersion/go-smtp v0.18.0
|
||||
github.com/gabriel-vasile/mimetype v1.4.3
|
||||
github.com/gorilla/websocket v1.5.1
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/olebedev/when v1.0.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/urfave/cli/v2 v2.27.1
|
||||
golang.org/x/crypto v0.21.0
|
||||
golang.org/x/oauth2 v0.18.0 // indirect
|
||||
golang.org/x/sync v0.6.0
|
||||
golang.org/x/term v0.18.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/urfave/cli/v2 v2.27.2
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/oauth2 v0.20.0 // indirect
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/term v0.20.0
|
||||
golang.org/x/time v0.5.0
|
||||
google.golang.org/api v0.168.0
|
||||
google.golang.org/api v0.180.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
|
@ -30,24 +30,25 @@ replace github.com/emersion/go-smtp => github.com/emersion/go-smtp v0.17.0 // Pi
|
|||
require github.com/pkg/errors v0.9.1 // indirect
|
||||
|
||||
require (
|
||||
firebase.google.com/go/v4 v4.13.0
|
||||
firebase.google.com/go/v4 v4.14.0
|
||||
github.com/SherClockHolmes/webpush-go v1.3.0
|
||||
github.com/microcosm-cc/bluemonday v1.0.26
|
||||
github.com/prometheus/client_golang v1.19.0
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/stripe/stripe-go/v74 v74.30.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.112.1 // indirect
|
||||
cloud.google.com/go/compute v1.25.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v1.1.6 // indirect
|
||||
cloud.google.com/go/longrunning v0.5.5 // indirect
|
||||
cloud.google.com/go v0.113.0 // indirect
|
||||
cloud.google.com/go/auth v0.4.1 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.8 // indirect
|
||||
cloud.google.com/go/longrunning v0.5.7 // indirect
|
||||
github.com/AlekSi/pointer v1.2.0 // indirect
|
||||
github.com/MicahParks/keyfunc v1.9.0 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
|
@ -60,31 +61,30 @@ require (
|
|||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.4 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.0 // indirect
|
||||
github.com/prometheus/common v0.50.0 // indirect
|
||||
github.com/prometheus/procfs v0.13.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.53.0 // indirect
|
||||
github.com/prometheus/procfs v0.14.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
golang.org/x/net v0.22.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/appengine/v2 v2.0.5 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240304212257-790db918fca8 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240304212257-790db918fca8 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 // indirect
|
||||
google.golang.org/grpc v1.62.1 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
|
||||
go.opentelemetry.io/otel v1.26.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.26.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.26.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
google.golang.org/appengine/v2 v2.0.6 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240513163218-0867130af1f8 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect
|
||||
google.golang.org/grpc v1.63.2 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
188
go.sum
188
go.sum
|
@ -1,20 +1,36 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
|
||||
cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
|
||||
cloud.google.com/go/compute v1.25.0 h1:H1/4SqSUhjPFE7L5ddzHOfY2bCAvjwNRZPNl6Ni5oYU=
|
||||
cloud.google.com/go/compute v1.25.0/go.mod h1:GR7F0ZPZH8EhChlMo9FkLd7eUTwEymjqQagxzilIxIE=
|
||||
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 v0.112.2 h1:ZaGT6LiG7dBzi6zNOvVZwacaXlmf3lRqnC4DQzqyRQw=
|
||||
cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms=
|
||||
cloud.google.com/go v0.113.0 h1:g3C70mn3lWfckKBiCVsAshabrDg01pQ0pnX1MNtnMkA=
|
||||
cloud.google.com/go v0.113.0/go.mod h1:glEqlogERKYeePz6ZdkcLJ28Q2I6aERgDDErBg9GzO8=
|
||||
cloud.google.com/go/auth v0.3.0 h1:PRyzEpGfx/Z9e8+lHsbkoUVXD0gnu4MNmm7Gp8TQNIs=
|
||||
cloud.google.com/go/auth v0.3.0/go.mod h1:lBv6NKTWp8E3LPzmO1TbiiRKc4drLOfHsgmlH9ogv5w=
|
||||
cloud.google.com/go/auth v0.4.0 h1:vcJWEguhY8KuiHoSs/udg1JtIRYm3YAWPBE1moF1m3U=
|
||||
cloud.google.com/go/auth v0.4.0/go.mod h1:tO/chJN3obc5AbRYFQDsuFbL4wW5y8LfbPtDCfgwOVE=
|
||||
cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg=
|
||||
cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
|
||||
cloud.google.com/go/compute v1.26.0 h1:uHf0NN2nvxl1Gh4QO83yRCOdMK4zivtMS5gv0dEX0hg=
|
||||
cloud.google.com/go/compute v1.26.0/go.mod h1:T9RIRap4pVHCGUkVFRJ9hygT3KCXjip41X1GgWtBBII=
|
||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/firestore v1.15.0 h1:/k8ppuWOtNuDHt2tsRV42yI21uaGnKDEQnRFeBpbFF8=
|
||||
cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk=
|
||||
cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc=
|
||||
cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI=
|
||||
cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg=
|
||||
cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s=
|
||||
cloud.google.com/go/storage v1.39.0 h1:brbjUa4hbDHhpQf48tjqMaXEV+f1OGoaTmQau9tmCsA=
|
||||
cloud.google.com/go/storage v1.39.0/go.mod h1:OAEj/WZwUYjA3YHQ10/YcN9ttGuEpLwvaoyBXIPikEk=
|
||||
firebase.google.com/go/v4 v4.13.0 h1:meFz9nvDNh/FDyrEykoAzSfComcQbmnQSjoHrePRqeI=
|
||||
firebase.google.com/go/v4 v4.13.0/go.mod h1:e1/gaR6EnbQfsmTnAMx1hnz+ninJIrrr/RAh59Tpfn8=
|
||||
cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM=
|
||||
cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA=
|
||||
cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
|
||||
cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE=
|
||||
cloud.google.com/go/longrunning v0.5.6 h1:xAe8+0YaWoCKr9t1+aWe+OeQgN/iJK1fEgZSXmjuEaE=
|
||||
cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA=
|
||||
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
|
||||
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
|
||||
cloud.google.com/go/storage v1.40.0 h1:VEpDQV5CJxFmJ6ueWNsKxcr1QAYOXEgxDa+sBbJahPw=
|
||||
cloud.google.com/go/storage v1.40.0/go.mod h1:Rrj7/hKlG87BLqDJYtwR0fbPld8uJPbQ2ucUMY7Ir0g=
|
||||
cloud.google.com/go/storage v1.41.0 h1:RusiwatSu6lHeEXe3kglxakAmAbfV+rhtPqA6i8RBx0=
|
||||
cloud.google.com/go/storage v1.41.0/go.mod h1:J1WCa/Z2FcgdEDuPUY8DxT5I+d9mFKsCepp5vR6Sq80=
|
||||
firebase.google.com/go/v4 v4.14.0 h1:Tc9jWzMUApUFUA5UUx/HcBeZ+LPjlhG2vNRfWJrcMwU=
|
||||
firebase.google.com/go/v4 v4.14.0/go.mod h1:pLATyL6xH2o9AMe7rqHdmmOUE/Ph7wcwepIs+uiEKPg=
|
||||
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
|
||||
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
|
@ -29,12 +45,12 @@ github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd3
|
|||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
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.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
|
@ -77,7 +93,6 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
|
|||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
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.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
|
@ -91,6 +106,7 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
|
||||
github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
|
||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
|
@ -98,8 +114,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUhuHF+DA=
|
||||
github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc=
|
||||
github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA=
|
||||
github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4=
|
||||
github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
|
||||
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
|
@ -120,54 +138,62 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
|
||||
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
|
||||
github.com/prometheus/common v0.50.0 h1:YSZE6aa9+luNa2da6/Tik0q0A5AbR+U003TItK57CPQ=
|
||||
github.com/prometheus/common v0.50.0/go.mod h1:wHFBCEVWVmHMUpg7pYcOm2QUR/ocQdYSJVQJKnHc3xQ=
|
||||
github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o=
|
||||
github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE=
|
||||
github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=
|
||||
github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s=
|
||||
github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stripe/stripe-go/v74 v74.30.0 h1:0Kf0KkeFnY7iRhOwvTerX0Ia1BRw+eV1CVJ51mGYAUY=
|
||||
github.com/stripe/stripe-go/v74 v74.30.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaBRvGAimAO+udbBw=
|
||||
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
|
||||
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI=
|
||||
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
|
||||
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
|
||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
|
||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc=
|
||||
go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
|
||||
go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
|
||||
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
|
||||
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
|
||||
go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw=
|
||||
go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc=
|
||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
|
||||
go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
|
||||
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
|
@ -186,18 +212,22 @@ golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
|
||||
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
|
||||
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
|
||||
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
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-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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -209,14 +239,18 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
|
@ -226,6 +260,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
@ -240,30 +276,45 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/api v0.168.0 h1:MBRe+Ki4mMN93jhDDbpuRLjRddooArz4FeSObvUMmjY=
|
||||
google.golang.org/api v0.168.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg=
|
||||
google.golang.org/api v0.176.1 h1:DJSXnV6An+NhJ1J+GWtoF2nHEuqB1VNoTfnIbjNvwD4=
|
||||
google.golang.org/api v0.176.1/go.mod h1:j2MaSDYcvYV1lkZ1+SMW4IeF90SrEyFA+tluDYWRrFg=
|
||||
google.golang.org/api v0.178.0 h1:yoW/QMI4bRVCHF+NWOTa4cL8MoWL3Jnuc7FlcFF91Ok=
|
||||
google.golang.org/api v0.178.0/go.mod h1:84/k2v8DFpDRebpGcooklv/lais3MEfqpaBLA12gl2U=
|
||||
google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4=
|
||||
google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/appengine/v2 v2.0.5 h1:4C+F3Cd3L2nWEfSmFEZDPjQvDwL8T0YCeZBysZifP3k=
|
||||
google.golang.org/appengine/v2 v2.0.5/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI=
|
||||
google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw=
|
||||
google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20240304212257-790db918fca8 h1:Fe8QycXyEd9mJgnwB9kmw00WgB43eQ/xYO5C6gceybQ=
|
||||
google.golang.org/genproto v0.0.0-20240304212257-790db918fca8/go.mod h1:yA7a1bW1kwl459Ol0m0lV4hLTfrL/7Bkk4Mj2Ir1mWI=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240304212257-790db918fca8 h1:8eadJkXbwDEMNwcB5O0s5Y5eCfyuCLdvaiOIaGTrWmQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240304212257-790db918fca8/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 h1:IR+hp6ypxjH24bkMfEJ0yHR21+gwPWdV+/IBrPQyn3k=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs=
|
||||
google.golang.org/genproto v0.0.0-20240415180920-8c6c420018be h1:g4aX8SUFA8V5F4LrSY5EclyGYw1OZN4HS1jTyjB9ZDc=
|
||||
google.golang.org/genproto v0.0.0-20240415180920-8c6c420018be/go.mod h1:FeSdT5fk+lkxatqJP38MsUicGqHax5cLtmy/6TAuxO4=
|
||||
google.golang.org/genproto v0.0.0-20240506185236-b8a5c65736ae h1:HjgkYCl6cWQEKSHkpUp4Q8VB74swzyBwTz1wtTzahm0=
|
||||
google.golang.org/genproto v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:i4np6Wrjp8EujFAUn0CM0SH+iZhY1EbrfzEIJbFkHFM=
|
||||
google.golang.org/genproto v0.0.0-20240513163218-0867130af1f8 h1:XpH03M6PDRKTo1oGfZBXu2SzwcbfxUokgobVinuUZoU=
|
||||
google.golang.org/genproto v0.0.0-20240513163218-0867130af1f8/go.mod h1:OLh2Ylz+WlYAJaSBRpJIJLP8iQP+8da+fpxbwNEAV/o=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be h1:Zz7rLWqp0ApfsR/l7+zSHhY3PMiH2xqgxlfYfAfNpoU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be/go.mod h1:dvdCTIoAGbkWbcIKBniID56/7XHTt6WfxXNMxuziJ+w=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae h1:AH34z6WAGVNkllnKs5raNq3yRq93VnjBG6rpfub/jYk=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 h1:W5Xj/70xIA4x60O/IFyXivR5MGqblAb8R3w26pnD6No=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240506185236-b8a5c65736ae h1:c55+MER4zkBS14uJhSZMGGmya0yJx5iHV4x/fpOSNRk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 h1:mxSlqyb8ZAHsYDCfiXN1EDdNTdvjUJSLY+OnAUtYNYA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
|
||||
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
@ -274,10 +325,11 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
|||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
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/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
|
|
@ -89,7 +89,7 @@ var (
|
|||
errHTTPBadRequestSinceInvalid = &errHTTP{40008, http.StatusBadRequest, "invalid since parameter", "https://ntfy.sh/docs/subscribe/api/#fetch-cached-messages", nil}
|
||||
errHTTPBadRequestTopicInvalid = &errHTTP{40009, http.StatusBadRequest, "invalid request: topic invalid", "", nil}
|
||||
errHTTPBadRequestTopicDisallowed = &errHTTP{40010, http.StatusBadRequest, "invalid request: topic name is not allowed", "", nil}
|
||||
errHTTPBadRequestMessageNotUTF8 = &errHTTP{40011, http.StatusBadRequest, "invalid message: message must be UTF-8 encoded", "", nil}
|
||||
errHTTPBadRequestMessageNotUTF8 = &errHTTP{40011, http.StatusBadRequest, "invalid request: message must be UTF-8 encoded", "", nil}
|
||||
errHTTPBadRequestAttachmentURLInvalid = &errHTTP{40013, http.StatusBadRequest, "invalid request: attachment URL is invalid", "https://ntfy.sh/docs/publish/#attachments", nil}
|
||||
errHTTPBadRequestAttachmentsDisallowed = &errHTTP{40014, http.StatusBadRequest, "invalid request: attachments not allowed", "https://ntfy.sh/docs/config/#attachments", nil}
|
||||
errHTTPBadRequestAttachmentsExpiryBeforeDelivery = &errHTTP{40015, http.StatusBadRequest, "invalid request: attachment expiry before delayed delivery date", "https://ntfy.sh/docs/publish/#scheduled-delivery", nil}
|
||||
|
@ -113,10 +113,16 @@ var (
|
|||
errHTTPBadRequestPhoneNumberNotVerified = &errHTTP{40034, http.StatusBadRequest, "invalid request: phone number not verified, or no matching verified numbers found", "https://ntfy.sh/docs/publish/#phone-calls", nil}
|
||||
errHTTPBadRequestAnonymousCallsNotAllowed = &errHTTP{40035, http.StatusBadRequest, "invalid request: anonymous phone calls are not allowed", "https://ntfy.sh/docs/publish/#phone-calls", nil}
|
||||
errHTTPBadRequestPhoneNumberVerifyChannelInvalid = &errHTTP{40036, http.StatusBadRequest, "invalid request: verification channel must be 'sms' or 'call'", "https://ntfy.sh/docs/publish/#phone-calls", nil}
|
||||
errHTTPBadRequestDelayNoCall = &errHTTP{40037, http.StatusBadRequest, "delayed call notifications are not supported", "", nil}
|
||||
errHTTPBadRequestDelayNoCall = &errHTTP{40037, http.StatusBadRequest, "invalid request: delayed call notifications are not supported", "", nil}
|
||||
errHTTPBadRequestWebPushSubscriptionInvalid = &errHTTP{40038, http.StatusBadRequest, "invalid request: web push payload malformed", "", nil}
|
||||
errHTTPBadRequestWebPushEndpointUnknown = &errHTTP{40039, http.StatusBadRequest, "invalid request: web push endpoint unknown", "", nil}
|
||||
errHTTPBadRequestWebPushTopicCountTooHigh = &errHTTP{40040, http.StatusBadRequest, "invalid request: too many web push topic subscriptions", "", nil}
|
||||
errHTTPBadRequestTemplateMessageTooLarge = &errHTTP{40041, http.StatusBadRequest, "invalid request: message or title is too large after replacing template", "https://ntfy.sh/docs/publish/#message-templating", nil}
|
||||
errHTTPBadRequestTemplateMessageNotJSON = &errHTTP{40042, http.StatusBadRequest, "invalid request: message body must be JSON if templating is enabled", "https://ntfy.sh/docs/publish/#message-templating", nil}
|
||||
errHTTPBadRequestTemplateInvalid = &errHTTP{40043, http.StatusBadRequest, "invalid request: could not parse template", "https://ntfy.sh/docs/publish/#message-templating", nil}
|
||||
errHTTPBadRequestTemplateDisallowedFunctionCalls = &errHTTP{40044, http.StatusBadRequest, "invalid request: template contains disallowed function calls, e.g. template, call, or define", "https://ntfy.sh/docs/publish/#message-templating", nil}
|
||||
errHTTPBadRequestTemplateExecuteFailed = &errHTTP{40045, http.StatusBadRequest, "invalid request: template execution failed", "https://ntfy.sh/docs/publish/#message-templating", nil}
|
||||
errHTTPBadRequestInvalidUsername = &errHTTP{40046, http.StatusBadRequest, "invalid request: invalid username", "", nil}
|
||||
errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", "", nil}
|
||||
errHTTPUnauthorized = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication", nil}
|
||||
errHTTPForbidden = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication", nil}
|
||||
|
|
|
@ -122,7 +122,7 @@ const (
|
|||
|
||||
// Schema management queries
|
||||
const (
|
||||
currentSchemaVersion = 12
|
||||
currentSchemaVersion = 13
|
||||
createSchemaVersionTableQuery = `
|
||||
CREATE TABLE IF NOT EXISTS schemaVersion (
|
||||
id INT PRIMARY KEY,
|
||||
|
@ -246,6 +246,11 @@ const (
|
|||
migrate11To12AlterMessagesTableQuery = `
|
||||
ALTER TABLE messages ADD COLUMN content_type TEXT NOT NULL DEFAULT('');
|
||||
`
|
||||
|
||||
// 12 -> 13
|
||||
migrate12To13AlterMessagesTableQuery = `
|
||||
CREATE INDEX IF NOT EXISTS idx_topic ON messages (topic);
|
||||
`
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -262,6 +267,7 @@ var (
|
|||
9: migrateFrom9,
|
||||
10: migrateFrom10,
|
||||
11: migrateFrom11,
|
||||
12: migrateFrom12,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -970,3 +976,19 @@ func migrateFrom11(db *sql.DB, _ time.Duration) error {
|
|||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func migrateFrom12(db *sql.DB, _ time.Duration) error {
|
||||
log.Tag(tagMessageCache).Info("Migrating cache database schema: from 12 to 13")
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
if _, err := tx.Exec(migrate12To13AlterMessagesTableQuery); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(updateSchemaVersion, 13); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -509,6 +508,14 @@ func TestSqliteCache_Migration_From1(t *testing.T) {
|
|||
messages, err = c.Messages("mytopic", sinceAllMessages, true)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 11, len(messages))
|
||||
|
||||
// Check that index "idx_topic" exists
|
||||
rows, err := c.db.Query(`SELECT name FROM sqlite_master WHERE type='index' AND name='idx_topic'`)
|
||||
require.Nil(t, err)
|
||||
require.True(t, rows.Next())
|
||||
var indexName string
|
||||
require.Nil(t, rows.Scan(&indexName))
|
||||
require.Equal(t, "idx_topic", indexName)
|
||||
}
|
||||
|
||||
func TestSqliteCache_Migration_From9(t *testing.T) {
|
||||
|
@ -675,15 +682,15 @@ func checkSchemaVersion(t *testing.T, db *sql.DB) {
|
|||
|
||||
func TestMemCache_NopCache(t *testing.T) {
|
||||
c, _ := newNopCache()
|
||||
assert.Nil(t, c.AddMessage(newDefaultMessage("mytopic", "my message")))
|
||||
require.Nil(t, c.AddMessage(newDefaultMessage("mytopic", "my message")))
|
||||
|
||||
messages, err := c.Messages("mytopic", sinceAllMessages, false)
|
||||
assert.Nil(t, err)
|
||||
assert.Empty(t, messages)
|
||||
require.Nil(t, err)
|
||||
require.Empty(t, messages)
|
||||
|
||||
topics, err := c.Topics()
|
||||
assert.Nil(t, err)
|
||||
assert.Empty(t, topics)
|
||||
require.Nil(t, err)
|
||||
require.Empty(t, topics)
|
||||
}
|
||||
|
||||
func newSqliteTestCache(t *testing.T) *messageCache {
|
||||
|
@ -700,16 +707,12 @@ func newSqliteTestCacheFile(t *testing.T) string {
|
|||
|
||||
func newSqliteTestCacheFromFile(t *testing.T, filename, startupQueries string) *messageCache {
|
||||
c, err := newSqliteCache(filename, startupQueries, time.Hour, 0, 0, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Nil(t, err)
|
||||
return c
|
||||
}
|
||||
|
||||
func newMemTestCache(t *testing.T) *messageCache {
|
||||
c, err := newMemCache()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Nil(t, err)
|
||||
return c
|
||||
}
|
||||
|
|
114
server/server.go
114
server/server.go
|
@ -23,6 +23,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
|
@ -123,15 +124,22 @@ var (
|
|||
|
||||
const (
|
||||
firebaseControlTopic = "~control" // See Android if changed
|
||||
firebasePollTopic = "~poll" // See iOS if changed
|
||||
firebasePollTopic = "~poll" // See iOS if changed (DISABLED for now)
|
||||
emptyMessageBody = "triggered" // Used if message body is empty
|
||||
newMessageBody = "New message" // Used in poll requests as generic message
|
||||
defaultAttachmentMessage = "You received a file: %s" // Used if message body is empty, and there is an attachment
|
||||
encodingBase64 = "base64" // Used mainly for binary UnifiedPush messages
|
||||
jsonBodyBytesLimit = 16384 // Max number of bytes for a JSON request body
|
||||
jsonBodyBytesLimit = 32768 // Max number of bytes for a request bodys (unless MessageLimit is higher)
|
||||
unifiedPushTopicPrefix = "up" // Temporarily, we rate limit all "up*" topics based on the subscriber
|
||||
unifiedPushTopicLength = 14 // Length of UnifiedPush topics, including the "up" part
|
||||
messagesHistoryMax = 10 // Number of message count values to keep in memory
|
||||
templateMaxExecutionTime = 100 * time.Millisecond
|
||||
)
|
||||
|
||||
var (
|
||||
// templateDisallowedRegex tests a template for disallowed expressions. While not really dangerous, they
|
||||
// are not useful, and seem potentially troublesome.
|
||||
templateDisallowedRegex = regexp.MustCompile(`(?m)\{\{-?\s*(call|template|define)\b`)
|
||||
)
|
||||
|
||||
// WebSocket constants
|
||||
|
@ -585,6 +593,7 @@ func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visi
|
|||
return err
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/javascript")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
_, err = io.WriteString(w, fmt.Sprintf("// Generated server configuration\nvar config = %s;\n", string(b)))
|
||||
return err
|
||||
}
|
||||
|
@ -673,7 +682,7 @@ func (s *Server) handleFile(w http.ResponseWriter, r *http.Request, v *visitor)
|
|||
// - avoid abuse (e.g. 1 uploader, 1k downloaders)
|
||||
// - and also uses the higher bandwidth limits of a paying user
|
||||
m, err := s.messageCache.Message(messageID)
|
||||
if err == errMessageNotFound {
|
||||
if errors.Is(err, errMessageNotFound) {
|
||||
if s.config.CacheBatchTimeout > 0 {
|
||||
// Strange edge case: If we immediately after upload request the file (the web app does this for images),
|
||||
// and messages are persisted asynchronously, retry fetching from the database
|
||||
|
@ -738,7 +747,7 @@ func (s *Server) handlePublishInternal(r *http.Request, v *visitor) (*message, e
|
|||
return nil, err
|
||||
}
|
||||
m := newDefaultMessage(t.ID, "")
|
||||
cache, firebase, email, call, unifiedpush, e := s.parsePublishParams(r, m)
|
||||
cache, firebase, email, call, template, unifiedpush, e := s.parsePublishParams(r, m)
|
||||
if e != nil {
|
||||
return nil, e.With(t)
|
||||
}
|
||||
|
@ -769,7 +778,7 @@ func (s *Server) handlePublishInternal(r *http.Request, v *visitor) (*message, e
|
|||
if cache {
|
||||
m.Expires = time.Unix(m.Time, 0).Add(v.Limits().MessageExpiryDuration).Unix()
|
||||
}
|
||||
if err := s.handlePublishBody(r, v, m, body, unifiedpush); err != nil {
|
||||
if err := s.handlePublishBody(r, v, m, body, template, unifiedpush); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if m.Message == "" {
|
||||
|
@ -872,7 +881,7 @@ func (s *Server) sendToFirebase(v *visitor, m *message) {
|
|||
logvm(v, m).Tag(tagFirebase).Debug("Publishing to Firebase")
|
||||
if err := s.firebaseClient.Send(v, m); err != nil {
|
||||
minc(metricFirebasePublishedFailure)
|
||||
if err == errFirebaseTemporarilyBanned {
|
||||
if errors.Is(err, errFirebaseTemporarilyBanned) {
|
||||
logvm(v, m).Tag(tagFirebase).Err(err).Debug("Unable to publish to Firebase: %v", err.Error())
|
||||
} else {
|
||||
logvm(v, m).Tag(tagFirebase).Err(err).Warn("Unable to publish to Firebase: %v", err.Error())
|
||||
|
@ -924,7 +933,7 @@ func (s *Server) forwardPollRequest(v *visitor, m *message) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, firebase bool, email, call string, unifiedpush bool, err *errHTTP) {
|
||||
func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, firebase bool, email, call string, template bool, unifiedpush bool, err *errHTTP) {
|
||||
cache = readBoolParam(r, true, "x-cache", "cache")
|
||||
firebase = readBoolParam(r, true, "x-firebase", "firebase")
|
||||
m.Title = readParam(r, "x-title", "title", "t")
|
||||
|
@ -940,7 +949,7 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
|
|||
}
|
||||
if attach != "" {
|
||||
if !urlRegex.MatchString(attach) {
|
||||
return false, false, "", "", false, errHTTPBadRequestAttachmentURLInvalid
|
||||
return false, false, "", "", false, false, errHTTPBadRequestAttachmentURLInvalid
|
||||
}
|
||||
m.Attachment.URL = attach
|
||||
if m.Attachment.Name == "" {
|
||||
|
@ -958,19 +967,19 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
|
|||
}
|
||||
if icon != "" {
|
||||
if !urlRegex.MatchString(icon) {
|
||||
return false, false, "", "", false, errHTTPBadRequestIconURLInvalid
|
||||
return false, false, "", "", false, false, errHTTPBadRequestIconURLInvalid
|
||||
}
|
||||
m.Icon = icon
|
||||
}
|
||||
email = readParam(r, "x-email", "x-e-mail", "email", "e-mail", "mail", "e")
|
||||
if s.smtpSender == nil && email != "" {
|
||||
return false, false, "", "", false, errHTTPBadRequestEmailDisabled
|
||||
return false, false, "", "", false, false, errHTTPBadRequestEmailDisabled
|
||||
}
|
||||
call = readParam(r, "x-call", "call")
|
||||
if call != "" && (s.config.TwilioAccount == "" || s.userManager == nil) {
|
||||
return false, false, "", "", false, errHTTPBadRequestPhoneCallsDisabled
|
||||
return false, false, "", "", false, false, errHTTPBadRequestPhoneCallsDisabled
|
||||
} else if call != "" && !isBoolValue(call) && !phoneNumberRegex.MatchString(call) {
|
||||
return false, false, "", "", false, errHTTPBadRequestPhoneNumberInvalid
|
||||
return false, false, "", "", false, false, errHTTPBadRequestPhoneNumberInvalid
|
||||
}
|
||||
messageStr := strings.ReplaceAll(readParam(r, "x-message", "message", "m"), "\\n", "\n")
|
||||
if messageStr != "" {
|
||||
|
@ -979,27 +988,27 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
|
|||
var e error
|
||||
m.Priority, e = util.ParsePriority(readParam(r, "x-priority", "priority", "prio", "p"))
|
||||
if e != nil {
|
||||
return false, false, "", "", false, errHTTPBadRequestPriorityInvalid
|
||||
return false, false, "", "", false, false, errHTTPBadRequestPriorityInvalid
|
||||
}
|
||||
m.Tags = readCommaSeparatedParam(r, "x-tags", "tags", "tag", "ta")
|
||||
delayStr := readParam(r, "x-delay", "delay", "x-at", "at", "x-in", "in")
|
||||
if delayStr != "" {
|
||||
if !cache {
|
||||
return false, false, "", "", false, errHTTPBadRequestDelayNoCache
|
||||
return false, false, "", "", false, false, errHTTPBadRequestDelayNoCache
|
||||
}
|
||||
if email != "" {
|
||||
return false, false, "", "", false, errHTTPBadRequestDelayNoEmail // we cannot store the email address (yet)
|
||||
return false, false, "", "", false, false, errHTTPBadRequestDelayNoEmail // we cannot store the email address (yet)
|
||||
}
|
||||
if call != "" {
|
||||
return false, false, "", "", false, errHTTPBadRequestDelayNoCall // we cannot store the phone number (yet)
|
||||
return false, false, "", "", false, false, errHTTPBadRequestDelayNoCall // we cannot store the phone number (yet)
|
||||
}
|
||||
delay, err := util.ParseFutureTime(delayStr, time.Now())
|
||||
if err != nil {
|
||||
return false, false, "", "", false, errHTTPBadRequestDelayCannotParse
|
||||
return false, false, "", "", false, false, errHTTPBadRequestDelayCannotParse
|
||||
} else if delay.Unix() < time.Now().Add(s.config.MessageDelayMin).Unix() {
|
||||
return false, false, "", "", false, errHTTPBadRequestDelayTooSmall
|
||||
return false, false, "", "", false, false, errHTTPBadRequestDelayTooSmall
|
||||
} else if delay.Unix() > time.Now().Add(s.config.MessageDelayMax).Unix() {
|
||||
return false, false, "", "", false, errHTTPBadRequestDelayTooLarge
|
||||
return false, false, "", "", false, false, errHTTPBadRequestDelayTooLarge
|
||||
}
|
||||
m.Time = delay.Unix()
|
||||
}
|
||||
|
@ -1007,13 +1016,14 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
|
|||
if actionsStr != "" {
|
||||
m.Actions, e = parseActions(actionsStr)
|
||||
if e != nil {
|
||||
return false, false, "", "", false, errHTTPBadRequestActionsInvalid.Wrap(e.Error())
|
||||
return false, false, "", "", false, false, errHTTPBadRequestActionsInvalid.Wrap(e.Error())
|
||||
}
|
||||
}
|
||||
contentType, markdown := readParam(r, "content-type", "content_type"), readBoolParam(r, false, "x-markdown", "markdown", "md")
|
||||
if markdown || strings.ToLower(contentType) == "text/markdown" {
|
||||
m.ContentType = "text/markdown"
|
||||
}
|
||||
template = readBoolParam(r, false, "x-template", "template", "tpl")
|
||||
unifiedpush = readBoolParam(r, false, "x-unifiedpush", "unifiedpush", "up") // see GET too!
|
||||
if unifiedpush {
|
||||
firebase = false
|
||||
|
@ -1025,7 +1035,7 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
|
|||
cache = false
|
||||
email = ""
|
||||
}
|
||||
return cache, firebase, email, call, unifiedpush, nil
|
||||
return cache, firebase, email, call, template, unifiedpush, nil
|
||||
}
|
||||
|
||||
// handlePublishBody consumes the PUT/POST body and decides whether the body is an attachment or the message.
|
||||
|
@ -1033,16 +1043,18 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
|
|||
// 1. curl -X POST -H "Poll: 1234" ntfy.sh/...
|
||||
// If a message is flagged as poll request, the body does not matter and is discarded
|
||||
// 2. curl -T somebinarydata.bin "ntfy.sh/mytopic?up=1"
|
||||
// If body is binary, encode as base64, if not do not encode
|
||||
// If UnifiedPush is enabled, encode as base64 if body is binary, and do not trim
|
||||
// 3. curl -H "Attach: http://example.com/file.jpg" ntfy.sh/mytopic
|
||||
// Body must be a message, because we attached an external URL
|
||||
// 4. curl -T short.txt -H "Filename: short.txt" ntfy.sh/mytopic
|
||||
// Body must be attachment, because we passed a filename
|
||||
// 5. curl -T file.txt ntfy.sh/mytopic
|
||||
// If file.txt is <= 4096 (message limit) and valid UTF-8, treat it as a message
|
||||
// 5. curl -H "Template: yes" -T file.txt ntfy.sh/mytopic
|
||||
// If templating is enabled, read up to 32k and treat message body as JSON
|
||||
// 6. curl -T file.txt ntfy.sh/mytopic
|
||||
// If file.txt is > message limit, treat it as an attachment
|
||||
func (s *Server) handlePublishBody(r *http.Request, v *visitor, m *message, body *util.PeekedReadCloser, unifiedpush bool) error {
|
||||
// If file.txt is <= 4096 (message limit) and valid UTF-8, treat it as a message
|
||||
// 7. curl -T file.txt ntfy.sh/mytopic
|
||||
// In all other cases, mostly if file.txt is > message limit, treat it as an attachment
|
||||
func (s *Server) handlePublishBody(r *http.Request, v *visitor, m *message, body *util.PeekedReadCloser, template, unifiedpush bool) error {
|
||||
if m.Event == pollRequestEvent { // Case 1
|
||||
return s.handleBodyDiscard(body)
|
||||
} else if unifiedpush {
|
||||
|
@ -1051,10 +1063,12 @@ func (s *Server) handlePublishBody(r *http.Request, v *visitor, m *message, body
|
|||
return s.handleBodyAsTextMessage(m, body) // Case 3
|
||||
} else if m.Attachment != nil && m.Attachment.Name != "" {
|
||||
return s.handleBodyAsAttachment(r, v, m, body) // Case 4
|
||||
} else if template {
|
||||
return s.handleBodyAsTemplatedTextMessage(m, body) // Case 5
|
||||
} else if !body.LimitReached && utf8.Valid(body.PeekedBytes) {
|
||||
return s.handleBodyAsTextMessage(m, body) // Case 5
|
||||
return s.handleBodyAsTextMessage(m, body) // Case 6
|
||||
}
|
||||
return s.handleBodyAsAttachment(r, v, m, body) // Case 6
|
||||
return s.handleBodyAsAttachment(r, v, m, body) // Case 7
|
||||
}
|
||||
|
||||
func (s *Server) handleBodyDiscard(body *util.PeekedReadCloser) error {
|
||||
|
@ -1086,6 +1100,45 @@ func (s *Server) handleBodyAsTextMessage(m *message, body *util.PeekedReadCloser
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) handleBodyAsTemplatedTextMessage(m *message, body *util.PeekedReadCloser) error {
|
||||
body, err := util.Peek(body, max(s.config.MessageSizeLimit, jsonBodyBytesLimit))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if body.LimitReached {
|
||||
return errHTTPEntityTooLargeJSONBody
|
||||
}
|
||||
peekedBody := strings.TrimSpace(string(body.PeekedBytes))
|
||||
if m.Message, err = replaceTemplate(m.Message, peekedBody); err != nil {
|
||||
return err
|
||||
}
|
||||
if m.Title, err = replaceTemplate(m.Title, peekedBody); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(m.Message) > s.config.MessageSizeLimit {
|
||||
return errHTTPBadRequestTemplateMessageTooLarge
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func replaceTemplate(tpl string, source string) (string, error) {
|
||||
if templateDisallowedRegex.MatchString(tpl) {
|
||||
return "", errHTTPBadRequestTemplateDisallowedFunctionCalls
|
||||
}
|
||||
var data any
|
||||
if err := json.Unmarshal([]byte(source), &data); err != nil {
|
||||
return "", errHTTPBadRequestTemplateMessageNotJSON
|
||||
}
|
||||
t, err := template.New("").Parse(tpl)
|
||||
if err != nil {
|
||||
return "", errHTTPBadRequestTemplateInvalid
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := t.Execute(util.NewTimeoutWriter(&buf, templateMaxExecutionTime), data); err != nil {
|
||||
return "", errHTTPBadRequestTemplateExecuteFailed
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func (s *Server) handleBodyAsAttachment(r *http.Request, v *visitor, m *message, body *util.PeekedReadCloser) error {
|
||||
if s.fileCache == nil || s.config.BaseURL == "" || s.config.AttachmentCacheDir == "" {
|
||||
return errHTTPBadRequestAttachmentsDisallowed.With(m)
|
||||
|
@ -1128,7 +1181,7 @@ func (s *Server) handleBodyAsAttachment(r *http.Request, v *visitor, m *message,
|
|||
util.NewFixedLimiter(vinfo.Stats.AttachmentTotalSizeRemaining),
|
||||
}
|
||||
m.Attachment.Size, err = s.fileCache.Write(m.ID, body, limiters...)
|
||||
if err == util.ErrLimitReached {
|
||||
if errors.Is(err, util.ErrLimitReached) {
|
||||
return errHTTPEntityTooLargeAttachment.With(m)
|
||||
} else if err != nil {
|
||||
return err
|
||||
|
@ -1447,6 +1500,9 @@ func (s *Server) maybeSetRateVisitors(r *http.Request, v *visitor, topics []*top
|
|||
// - topic is not reserved, and v.user has write access
|
||||
writableRateTopics := make([]*topic, 0)
|
||||
for _, t := range topics {
|
||||
if !util.Contains(eligibleRateTopics, t) {
|
||||
continue
|
||||
}
|
||||
ownerUserID, err := s.userManager.ReservationOwner(t.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -2,6 +2,7 @@ package server
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"heckel.io/ntfy/v2/log"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
|
@ -37,6 +38,9 @@ func (s *Server) handleAccountCreate(w http.ResponseWriter, r *http.Request, v *
|
|||
}
|
||||
logvr(v, r).Tag(tagAccount).Field("user_name", newAccount.Username).Info("Creating user %s", newAccount.Username)
|
||||
if err := s.userManager.AddUser(newAccount.Username, newAccount.Password, user.RoleUser); err != nil {
|
||||
if errors.Is(err, user.ErrInvalidArgument) {
|
||||
return errHTTPBadRequestInvalidUsername
|
||||
}
|
||||
return err
|
||||
}
|
||||
v.AccountCreated()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"heckel.io/ntfy/v2/user"
|
||||
"net/http"
|
||||
)
|
||||
|
@ -45,7 +46,7 @@ func (s *Server) handleUsersAdd(w http.ResponseWriter, r *http.Request, v *visit
|
|||
return errHTTPBadRequest.Wrap("username invalid, or password missing")
|
||||
}
|
||||
u, err := s.userManager.User(req.Username)
|
||||
if err != nil && err != user.ErrUserNotFound {
|
||||
if err != nil && !errors.Is(err, user.ErrUserNotFound) {
|
||||
return err
|
||||
} else if u != nil {
|
||||
return errHTTPConflictUserExists
|
||||
|
@ -53,7 +54,7 @@ func (s *Server) handleUsersAdd(w http.ResponseWriter, r *http.Request, v *visit
|
|||
var tier *user.Tier
|
||||
if req.Tier != "" {
|
||||
tier, err = s.userManager.Tier(req.Tier)
|
||||
if err == user.ErrTierNotFound {
|
||||
if errors.Is(err, user.ErrTierNotFound) {
|
||||
return errHTTPBadRequestTierInvalid
|
||||
} else if err != nil {
|
||||
return err
|
||||
|
@ -76,7 +77,7 @@ func (s *Server) handleUsersDelete(w http.ResponseWriter, r *http.Request, v *vi
|
|||
return err
|
||||
}
|
||||
u, err := s.userManager.User(req.Username)
|
||||
if err == user.ErrUserNotFound {
|
||||
if errors.Is(err, user.ErrUserNotFound) {
|
||||
return errHTTPBadRequestUserNotFound
|
||||
} else if err != nil {
|
||||
return err
|
||||
|
@ -98,7 +99,7 @@ func (s *Server) handleAccessAllow(w http.ResponseWriter, r *http.Request, v *vi
|
|||
return err
|
||||
}
|
||||
_, err = s.userManager.User(req.Username)
|
||||
if err == user.ErrUserNotFound {
|
||||
if errors.Is(err, user.ErrUserNotFound) {
|
||||
return errHTTPBadRequestUserNotFound
|
||||
} else if err != nil {
|
||||
return err
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -2,6 +2,7 @@ package server
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"heckel.io/ntfy/v2/util"
|
||||
"io"
|
||||
|
@ -104,9 +105,9 @@ func extractIPAddress(r *http.Request, behindProxy bool) netip.Addr {
|
|||
|
||||
func readJSONWithLimit[T any](r io.ReadCloser, limit int, allowEmpty bool) (*T, error) {
|
||||
obj, err := util.UnmarshalJSONWithLimit[T](r, limit, allowEmpty)
|
||||
if err == util.ErrUnmarshalJSON {
|
||||
if errors.Is(err, util.ErrUnmarshalJSON) {
|
||||
return nil, errHTTPBadRequestJSONInvalid
|
||||
} else if err == util.ErrTooLargeJSON {
|
||||
} else if errors.Is(err, util.ErrTooLargeJSON) {
|
||||
return nil, errHTTPEntityTooLargeJSONBody
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -16,7 +16,7 @@ func StartServer(t *testing.T) (*server.Server, int) {
|
|||
|
||||
// StartServerWithConfig starts a server.Server with a random port and waits for the server to be up
|
||||
func StartServerWithConfig(t *testing.T, conf *server.Config) (*server.Server, int) {
|
||||
port := 10000 + rand.Intn(20000)
|
||||
port := 10000 + rand.Intn(30000)
|
||||
conf.ListenHTTP = fmt.Sprintf(":%d", port)
|
||||
conf.AttachmentCacheDir = t.TempDir()
|
||||
conf.CacheFile = filepath.Join(t.TempDir(), "cache.db")
|
||||
|
|
|
@ -241,7 +241,7 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
allowedUsernameRegex = regexp.MustCompile(`^[-_.@a-zA-Z0-9]+$`) // Does not include Everyone (*)
|
||||
allowedUsernameRegex = regexp.MustCompile(`^[-_.+@a-zA-Z0-9]+$`) // Does not include Everyone (*)
|
||||
allowedTopicRegex = regexp.MustCompile(`^[-_A-Za-z0-9]{1,64}$`) // No '*'
|
||||
allowedTopicPatternRegex = regexp.MustCompile(`^[-_*A-Za-z0-9]{1,64}$`) // Adds '*' for wildcards!
|
||||
allowedTierRegex = regexp.MustCompile(`^[-_A-Za-z0-9]{1,64}$`)
|
||||
|
|
|
@ -61,3 +61,15 @@ func TestTierContext(t *testing.T) {
|
|||
require.Equal(t, "price_456", context["stripe_yearly_price_id"])
|
||||
|
||||
}
|
||||
|
||||
func TestUsernameRegex(t *testing.T) {
|
||||
username := "phil"
|
||||
usernameEmail := "phil@ntfy.sh"
|
||||
usernameEmailAlias := "phil+alias@ntfy.sh"
|
||||
usernameInvalid := "phil\rocks"
|
||||
|
||||
require.True(t, AllowedUsername(username))
|
||||
require.True(t, AllowedUsername(usernameEmail))
|
||||
require.True(t, AllowedUsername(usernameEmailAlias))
|
||||
require.False(t, AllowedUsername(usernameInvalid))
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package util
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
@ -26,7 +27,7 @@ func Peek(underlying io.ReadCloser, limit int) (*PeekedReadCloser, error) {
|
|||
}
|
||||
peeked := make([]byte, limit)
|
||||
read, err := io.ReadFull(underlying, peeked)
|
||||
if err != nil && err != io.ErrUnexpectedEOF && err != io.EOF {
|
||||
if err != nil && !errors.Is(err, io.ErrUnexpectedEOF) && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
return &PeekedReadCloser{
|
||||
|
@ -44,7 +45,7 @@ func (r *PeekedReadCloser) Read(p []byte) (n int, err error) {
|
|||
return 0, io.EOF
|
||||
}
|
||||
n, err = r.peeked.Read(p)
|
||||
if err == io.EOF {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return r.underlying.Read(p)
|
||||
} else if err != nil {
|
||||
return 0, err
|
||||
|
|
34
util/timeout_writer.go
Normal file
34
util/timeout_writer.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrWriteTimeout is returned when a write timed out
|
||||
var ErrWriteTimeout = errors.New("write operation failed due to timeout since creation")
|
||||
|
||||
// TimeoutWriter wraps an io.Writer that will time out after the given timeout
|
||||
type TimeoutWriter struct {
|
||||
writer io.Writer
|
||||
timeout time.Duration
|
||||
start time.Time
|
||||
}
|
||||
|
||||
// NewTimeoutWriter creates a new TimeoutWriter
|
||||
func NewTimeoutWriter(w io.Writer, timeout time.Duration) *TimeoutWriter {
|
||||
return &TimeoutWriter{
|
||||
writer: w,
|
||||
timeout: timeout,
|
||||
start: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// Write implements the io.Writer interface, failing if called after the timeout period from creation.
|
||||
func (tw *TimeoutWriter) Write(p []byte) (n int, err error) {
|
||||
if time.Since(tw.start) > tw.timeout {
|
||||
return 0, errors.New("write operation failed due to timeout since creation")
|
||||
}
|
||||
return tw.writer.Write(p)
|
||||
}
|
2844
web/package-lock.json
generated
2844
web/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -330,5 +330,34 @@
|
|||
"account_basics_tier_paid_until": "تم دفع مبلغ الاشتراك إلى غاية {{date}}، وسيتم تجديده تِلْقائيًا",
|
||||
"account_basics_tier_canceled_subscription": "تم إلغاء اشتراكك وسيتم إعادته إلى مستوى حساب مجاني بداية مِن {{date}}.",
|
||||
"account_delete_dialog_billing_warning": "إلغاء حسابك أيضاً يلغي اشتراكك في الفوترة فوراً ولن تتمكن من الوصول إلى لوح الفوترة بعد الآن.",
|
||||
"nav_upgrade_banner_description": "حجز المواضيع والمزيد من الرسائل ورسائل البريد الإلكتروني والمرفقات الأكبر حجمًا"
|
||||
"nav_upgrade_banner_description": "حجز المواضيع والمزيد من الرسائل ورسائل البريد الإلكتروني والمرفقات الأكبر حجمًا",
|
||||
"prefs_appearance_theme_dark": "الوضع الليلي",
|
||||
"prefs_appearance_theme_light": "الوضع النهاري",
|
||||
"publish_dialog_checkbox_markdown": "تنسيق على هيئة ماركداون",
|
||||
"alert_not_supported_context_description": "الإشعارات مسموحة فقط على بروتوكول HTTPS المأمن, هذه القيود <mdnLink>خصائص الإشعارات</mdnLink>",
|
||||
"publish_dialog_call_reset": "حذف اتصال بالهاتف",
|
||||
"publish_dialog_call_label": "اتصال هاتفي",
|
||||
"publish_dialog_chip_call_label": "اتصال هاتفي",
|
||||
"publish_dialog_delay_placeholder": "تأخير التوصيل, مثال {{unixTimestamp}}, {{relativeTime}}, او \"{{naturalLanguage}}\" (اللغة الإنجليزية فقط)",
|
||||
"publish_dialog_attachment_limits_file_and_quota_reached": "تجاوز حجم {{fileSizeLimit}} الملف, {{remainingBytes}} متبقي",
|
||||
"prefs_reservations_dialog_title_delete": "حذف حجز موضوع",
|
||||
"publish_dialog_call_item": "اتصل برقم الهاتف {{number}}",
|
||||
"publish_dialog_chip_call_no_verified_numbers_tooltip": "لا يوجد ارقام هواتف معرفة",
|
||||
"action_bar_mute_notifications": "كتم الإشعارات",
|
||||
"action_bar_unmute_notifications": "إلغاء كتم الإشعارات",
|
||||
"alert_notification_ios_install_required_description": "اضغط على زر المشاركة ثم إضافة إلى الصفحة الرئيسية لتستقبل الإشعارات على أجهزة أبل",
|
||||
"alert_notification_ios_install_required_title": "يجب تثبيت الصفحة",
|
||||
"alert_notification_permission_denied_description": "الرجاء اعادة منح الصلاحيات في المتصفح",
|
||||
"alert_notification_permission_denied_title": "الإشعارات مغلقة",
|
||||
"notifications_actions_failed_notification": "حدث غير منفذ",
|
||||
"prefs_notifications_web_push_disabled": "ملغي",
|
||||
"account_basics_phone_numbers_dialog_channel_call": "اتصل",
|
||||
"account_basics_phone_numbers_title": "أرقام الهواتف",
|
||||
"account_basics_phone_numbers_dialog_channel_sms": "رسالة نصية قصيرة",
|
||||
"account_basics_phone_numbers_dialog_check_verification_button": "رمز التأكيد",
|
||||
"account_basics_phone_numbers_dialog_number_label": "رقم الهاتف",
|
||||
"account_basics_phone_numbers_dialog_verify_button_call": "اتصل بي",
|
||||
"account_basics_phone_numbers_dialog_code_label": "رمز التحقّق",
|
||||
"account_upgrade_dialog_tier_price_per_month": "شهر",
|
||||
"prefs_appearance_theme_title": "الحُلّة"
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"action_bar_clear_notifications": "Премахване на известия",
|
||||
"alert_notification_permission_required_description": "Разрешете на мрежовия четец да показва известия.",
|
||||
"alert_notification_permission_required_description": "Разрешете на мрежовия четец да показва известия",
|
||||
"notifications_attachment_copy_url_title": "Копиране на адреса на прикачения файл",
|
||||
"notifications_example": "Пример",
|
||||
"notifications_no_subscriptions_title": "Липсват абонаменти.",
|
||||
"notifications_no_subscriptions_title": "Липсват абонаменти",
|
||||
"nav_topics_title": "Абонаменти",
|
||||
"action_bar_send_test_notification": "Пробно известие",
|
||||
"action_bar_unsubscribe": "Отписване",
|
||||
|
@ -22,7 +22,7 @@
|
|||
"publish_dialog_chip_email_label": "Препращане към ел. поща",
|
||||
"publish_dialog_chip_attach_url_label": "Прикачване на файл от адрес",
|
||||
"publish_dialog_chip_attach_file_label": "Прикачване местен файл",
|
||||
"publish_dialog_chip_delay_label": "Забавяне на изпращането",
|
||||
"publish_dialog_chip_delay_label": "Отлагане на изпращането",
|
||||
"publish_dialog_chip_topic_label": "Промяна на темата",
|
||||
"publish_dialog_button_cancel_sending": "Отменяне на изпращането",
|
||||
"publish_dialog_button_cancel": "Отказ",
|
||||
|
@ -39,7 +39,7 @@
|
|||
"prefs_notifications_delete_after_never": "Никога",
|
||||
"prefs_users_add_button": "Добавяне",
|
||||
"prefs_users_dialog_password_label": "Парола",
|
||||
"alert_not_supported_description": "Мрежовият четец не поддържа известия.",
|
||||
"alert_not_supported_description": "Мрежовият четец не поддържа известия",
|
||||
"message_bar_type_message": "Въведете съобщение",
|
||||
"message_bar_error_publishing": "Грешка при изпращане на известието",
|
||||
"notifications_copied_to_clipboard": "Копирано в междинната памет",
|
||||
|
@ -61,10 +61,10 @@
|
|||
"notifications_click_open_button": "Отваряне",
|
||||
"notifications_click_copy_url_title": "Копиране на препратката в междинната памет",
|
||||
"notifications_none_for_topic_title": "Темата е все още празна",
|
||||
"notifications_none_for_any_title": "Липсват известия.",
|
||||
"notifications_none_for_any_title": "Липсват известия",
|
||||
"notifications_none_for_topic_description": "За да изпратите известия в тази тема направете заявка чрез методите PUT или POST към адреса ѝ.",
|
||||
"notifications_none_for_any_description": "За да изпратите известия в тема направете заявка чрез методите PUT или POST към адреса ѝ. Ето пример с една от вашите теми.",
|
||||
"notifications_no_subscriptions_description": "Щракнете върху „{{linktext}}“, за да създадете тема или да се абонирате. След това като направите заявка чрез методите PUT или POST ще ги получите тук.",
|
||||
"notifications_no_subscriptions_description": "Щракнете върху „{{linktext}}“, за да създадете или да се абонирате за тема. След това като изпратите съобщение с методите PUT или POST ще го получите тук.",
|
||||
"notifications_more_details": "За допълнителна информация посетете <websiteLink>страницата</websiteLink> или <docsLink>документацията</docsLink>.",
|
||||
"publish_dialog_priority_min": "Най-нисък приоритет",
|
||||
"publish_dialog_attachment_limits_file_reached": "надвишава ограничението от {{fileSizeLimit}} за размер на файл",
|
||||
|
@ -84,14 +84,14 @@
|
|||
"publish_dialog_topic_label": "Име на темата",
|
||||
"publish_dialog_title_label": "Заглавие",
|
||||
"publish_dialog_priority_label": "Приоритет",
|
||||
"publish_dialog_click_placeholder": "Адрес, който се отваря при щракване върху известието",
|
||||
"publish_dialog_click_placeholder": "Адрес, който се отваря при докосване на известието",
|
||||
"publish_dialog_email_placeholder": "Адрес, към който да бъдат препращани известия, напр. phil@example.com",
|
||||
"publish_dialog_attach_label": "Адрес на прикачения файл",
|
||||
"publish_dialog_filename_placeholder": "Име на прикачения файл",
|
||||
"publish_dialog_attach_placeholder": "Прикачете файл от адрес, напр. https://f-droid.org/F-Droid.apk",
|
||||
"prefs_notifications_delete_after_three_hours": "След три часа",
|
||||
"publish_dialog_filename_label": "Име на файла",
|
||||
"publish_dialog_delay_label": "Забавяне",
|
||||
"publish_dialog_delay_label": "Отлагане",
|
||||
"publish_dialog_details_examples_description": "За примери и подробно описание на всички възможности при изпращане, вижте <docsLink>документацията</docsLink>.",
|
||||
"publish_dialog_button_send": "Изпращане",
|
||||
"publish_dialog_checkbox_publish_another": "Изпращане на повече",
|
||||
|
@ -121,7 +121,7 @@
|
|||
"subscribe_dialog_login_button_login": "Вход",
|
||||
"subscribe_dialog_error_user_not_authorized": "Потребителят {{username}} няма достъп",
|
||||
"prefs_appearance_title": "Външен вид",
|
||||
"publish_dialog_delay_placeholder": "Забавяне на изпращането, {{unixTimestamp}}, {{relativeTime}} или „{{naturalLanguage}}“ (на английски)",
|
||||
"publish_dialog_delay_placeholder": "Отлагане на изпращането, {{unixTimestamp}}, {{relativeTime}} или „{{naturalLanguage}}“ (на английски)",
|
||||
"prefs_notifications_delete_after_one_week": "След една седмица",
|
||||
"prefs_users_title": "Управление на потребители",
|
||||
"prefs_users_table_base_url_header": "Адрес на услугата",
|
||||
|
@ -177,7 +177,7 @@
|
|||
"publish_dialog_topic_reset": "Нулиране на тема",
|
||||
"publish_dialog_click_reset": "Премахване на адрес",
|
||||
"publish_dialog_email_reset": "Премахване на препращането към ел. поща",
|
||||
"publish_dialog_delay_reset": "Премахва забавянето на изпращането",
|
||||
"publish_dialog_delay_reset": "Премахва отлагането на изпращането",
|
||||
"publish_dialog_attached_file_remove": "Премахване на прикачения файл",
|
||||
"emoji_picker_search_clear": "Изчистване на търсенето",
|
||||
"subscribe_dialog_subscribe_base_url_label": "Адрес на услугата",
|
||||
|
@ -220,7 +220,7 @@
|
|||
"alert_not_supported_context_description": "Известията се поддържат само през HTTPS. Това е ограничение на <mdnLink>Notifications API</mdnLink>.",
|
||||
"display_name_dialog_description": "Изберете друго име за темата, което да се показва в списъка с абонаменти. Помага за по-лесното разпознаване на теми със сложни имена.",
|
||||
"subscribe_dialog_error_topic_already_reserved": "Темата вече е резервирана",
|
||||
"nav_upgrade_banner_description": "Резервиране на теми, повече съобщения и имейли и по-големи прикачени файлове",
|
||||
"nav_upgrade_banner_description": "Резервиране на теми, повече съобщения и писма, по-големи прикачени файлове",
|
||||
"display_name_dialog_placeholder": "Наименование",
|
||||
"reserve_dialog_checkbox_label": "Резервиране на тема и настройки за достъп",
|
||||
"subscribe_dialog_subscribe_button_generate_topic_name": "Произволно име",
|
||||
|
@ -380,5 +380,28 @@
|
|||
"reservation_delete_dialog_action_delete_title": "Премахване на съобщения и прикачени файлове",
|
||||
"reservation_delete_dialog_action_delete_description": "Съобщенията и прикачените файлове, които са във временната памет ще бъдат премахнати. Действието е необратимо.",
|
||||
"prefs_reservations_description": "Тук можете да резервирате тема за собствено ползване. Резервирането ви осигурява собственост върху темата и ви дава възможност да определяте права за достъп от други потребители.",
|
||||
"reservation_delete_dialog_description": "С премахването на резервирането вие се отказвате от собствеността върху темата и давате възможност друг потребител да я резервира. Можете да оставите или да премахнете съществуващите съобщения и прикачени файлове."
|
||||
"reservation_delete_dialog_description": "С премахването на резервирането вие се отказвате от собствеността върху темата и давате възможност друг потребител да я резервира. Можете да оставите или да премахнете съществуващите съобщения и прикачени файлове.",
|
||||
"alert_notification_permission_denied_description": "Включете ги от мрежовия четец",
|
||||
"alert_notification_permission_denied_title": "Известията са изключени",
|
||||
"notifications_actions_failed_notification": "Действието е неуспешно",
|
||||
"publish_dialog_checkbox_markdown": "Съобщението е Markdown",
|
||||
"prefs_notifications_web_push_disabled_description": "Известията ще бъдат получавани докато приложението за уеб работи (чрез WebSocket)",
|
||||
"prefs_notifications_web_push_enabled": "Включено за {{server}}",
|
||||
"prefs_notifications_web_push_disabled": "Изключено",
|
||||
"prefs_appearance_theme_dark": "Тъмна",
|
||||
"prefs_appearance_theme_light": "Светла",
|
||||
"error_boundary_button_reload_ntfy": "Презареждне на ntfy",
|
||||
"web_push_unknown_notification_title": "Получено е неочаквано известие",
|
||||
"web_push_unknown_notification_body": "Вероятно ще трябва да обновите ntfy като отворите приложението за уеб",
|
||||
"alert_notification_ios_install_required_title": "Необходимо е инсталиране за iOS",
|
||||
"alert_notification_ios_install_required_description": "Докоснете бутона Споделяне и Добавяне към началния екран, за да включите известията под iOS",
|
||||
"subscribe_dialog_subscribe_use_another_background_info": "Известията от други сървъри няма да бъдат получавани ако приложението за уеб не е отворено",
|
||||
"action_bar_mute_notifications": "Заглушаване на известия",
|
||||
"prefs_notifications_web_push_title": "Известия във фонов режим",
|
||||
"prefs_notifications_web_push_enabled_description": "Известията ще бъдат получавани даже и ако приложението за уеб не работи (чрез Web Push)",
|
||||
"prefs_appearance_theme_title": "Цветова тема",
|
||||
"prefs_appearance_theme_system": "Системна (подразбирана)",
|
||||
"web_push_subscription_expiring_title": "Известията временно ще бъдат спрени",
|
||||
"web_push_subscription_expiring_body": "За да продължите да получавате известия, отворете ntfy",
|
||||
"action_bar_unmute_notifications": "Включване звука на известията"
|
||||
}
|
||||
|
|
|
@ -380,5 +380,8 @@
|
|||
"account_basics_phone_numbers_dialog_check_verification_button": "Code bestätigen",
|
||||
"account_usage_calls_title": "Getätigte Anrufe",
|
||||
"account_usage_calls_none": "Noch keine Anrufe mit diesem Account getätigt",
|
||||
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} Telefonanrufe pro Tag"
|
||||
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} Telefonanrufe pro Tag",
|
||||
"action_bar_mute_notifications": "Benachrichtigungen stummschalten",
|
||||
"action_bar_unmute_notifications": "Benachrichtigungen lautschalten",
|
||||
"alert_notification_permission_denied_title": "Benachrichtigungen sind blockiert"
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
"message_bar_type_message": "Escriba un mensaje aquí",
|
||||
"message_bar_error_publishing": "Error al publicar la notificación",
|
||||
"alert_notification_permission_required_title": "Las notificaciones están deshabilitadas",
|
||||
"alert_notification_permission_required_description": "Concede a tu navegador permiso para mostrar notificaciones en el escritorio.",
|
||||
"alert_notification_permission_required_description": "Concede a tu navegador permiso para mostrar notificaciones de escritorio",
|
||||
"nav_button_all_notifications": "Todas las notificaciones",
|
||||
"nav_button_settings": "Ajustes",
|
||||
"nav_button_subscribe": "Suscribirse al tópico",
|
||||
|
@ -16,7 +16,7 @@
|
|||
"nav_button_publish_message": "Publicar notificación",
|
||||
"notifications_copied_to_clipboard": "Copiado al portapapeles",
|
||||
"alert_not_supported_title": "Notificaciones no soportadas",
|
||||
"alert_not_supported_description": "Las notificaciones no están soportadas por tu navegador.",
|
||||
"alert_not_supported_description": "Su navegador no admite notificaciones",
|
||||
"notifications_tags": "Etiquetas",
|
||||
"notifications_attachment_copy_url_title": "Copiar la URL del archivo adjunto en el portapapeles",
|
||||
"notifications_attachment_copy_url_button": "Copiar URL",
|
||||
|
@ -381,5 +381,28 @@
|
|||
"account_basics_phone_numbers_dialog_title": "Agregar número de teléfono",
|
||||
"account_basics_phone_numbers_dialog_code_placeholder": "p.ej. 123456",
|
||||
"publish_dialog_call_item": "Llamar al número de teléfono {{number}}",
|
||||
"publish_dialog_chip_call_no_verified_numbers_tooltip": "No hay números de teléfono verificados"
|
||||
"publish_dialog_chip_call_no_verified_numbers_tooltip": "No hay números de teléfono verificados",
|
||||
"action_bar_mute_notifications": "Silenciar Notificaciones",
|
||||
"action_bar_unmute_notifications": "Reactivar notificaciones",
|
||||
"alert_notification_permission_denied_title": "Notificaciones bloqueadas",
|
||||
"alert_notification_permission_denied_description": "Porfavor, reactivelas en su navegador",
|
||||
"alert_notification_ios_install_required_title": "Requiere instalacion de iOS",
|
||||
"alert_notification_ios_install_required_description": "Haz click en el icono de compartir y Añadir a pantalla de inicio para activar las notificaciones de iOS",
|
||||
"notifications_actions_failed_notification": "Acción fallida",
|
||||
"publish_dialog_checkbox_markdown": "Formatear como Markdown",
|
||||
"subscribe_dialog_subscribe_use_another_background_info": "Las notificaciones de otros servidores no se recibirán cuando la aplicación web no esté abierta",
|
||||
"prefs_notifications_web_push_title": "Notificaciones en segundo plano",
|
||||
"prefs_notifications_web_push_enabled_description": "Las notificaciones se reciben incluso cuando la aplicación web no se está ejecutando (a través de Web Push)",
|
||||
"prefs_notifications_web_push_disabled": "Desactivado",
|
||||
"prefs_appearance_theme_title": "Tema",
|
||||
"prefs_appearance_theme_system": "Sistema (por defecto)",
|
||||
"error_boundary_button_reload_ntfy": "Volver a cargar ntfy",
|
||||
"web_push_subscription_expiring_title": "Las notificaciones se pausarán",
|
||||
"prefs_notifications_web_push_disabled_description": "Las notificaciones se reciben cuando la aplicación web se está ejecutando (a través de WebSocket)",
|
||||
"prefs_notifications_web_push_enabled": "Activado para {{server}}",
|
||||
"prefs_appearance_theme_light": "Claro",
|
||||
"prefs_appearance_theme_dark": "Oscuro",
|
||||
"web_push_subscription_expiring_body": "Abrir ntfy para seguir recibiendo notificaciones",
|
||||
"web_push_unknown_notification_title": "Notificación desconocida recibida del servidor",
|
||||
"web_push_unknown_notification_body": "Puede que necesites actualizar ntfy abriendo la aplicación web"
|
||||
}
|
||||
|
|
36
web/public/static/langs/fa.json
Normal file
36
web/public/static/langs/fa.json
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"signup_title": "ایجاد اکانت ntfy",
|
||||
"signup_form_button_submit": "ثبت نام",
|
||||
"signup_already_have_account": "قبلا اکانت دارید؟ وارد بشود",
|
||||
"signup_disabled": "ثبت نام غیرفعال است",
|
||||
"login_title": "ورود به اکانت ntfy",
|
||||
"login_link_signup": "ثبت نام",
|
||||
"login_disabled": "ورود غیرفعال است",
|
||||
"action_bar_show_menu": "نمایش منو",
|
||||
"action_bar_account": "اکانت",
|
||||
"action_bar_reservation_limit_reached": "دسترسی محدود",
|
||||
"action_bar_send_test_notification": "ارسال تستی اعلان",
|
||||
"action_bar_unmute_notifications": "لغو ساکت کردن اعلان ها",
|
||||
"action_bar_unsubscribe": "لغو اشتراک",
|
||||
"action_bar_toggle_mute": "بی صدا/لغو اعلان ها",
|
||||
"common_cancel": "لغو",
|
||||
"common_save": "ذخیره",
|
||||
"common_add": "اضافه کردن",
|
||||
"common_back": "عقب",
|
||||
"common_copy_to_clipboard": "کپی به کلیپ بورد",
|
||||
"signup_form_username": "نام کاربری",
|
||||
"signup_form_password": "کلمه عبور",
|
||||
"signup_form_confirm_password": "تایید پسورد",
|
||||
"signup_form_toggle_password_visibility": "تغییر وضعیت نمایش کلمه عبور",
|
||||
"signup_error_username_taken": "نام کاربری {{username}} قبلا استفاده شده است",
|
||||
"signup_error_creation_limit_reached": "به حد مجاز ایجاد حساب رسیده است",
|
||||
"login_form_button_submit": "ورود",
|
||||
"action_bar_logo_alt": "لوگوی ntfy",
|
||||
"action_bar_settings": "تنظیمات",
|
||||
"action_bar_change_display_name": "تغییر نام نمایشی",
|
||||
"action_bar_reservation_add": "رزرو موضوع",
|
||||
"action_bar_reservation_edit": "تغییر رزرو",
|
||||
"action_bar_reservation_delete": "حذف رزرو",
|
||||
"action_bar_mute_notifications": "ساکت کردن اعلان ها",
|
||||
"action_bar_clear_notifications": "پاک کردن تمام اعلان ها"
|
||||
}
|
|
@ -11,7 +11,7 @@
|
|||
"nav_button_all_notifications": "Toutes les notifications",
|
||||
"nav_button_settings": "Paramètres",
|
||||
"nav_button_documentation": "Documentation",
|
||||
"alert_not_supported_description": "Les notifications ne sont pas prises en charge par votre navigateur.",
|
||||
"alert_not_supported_description": "Les notifications ne sont pas prises en charge par votre navigateur",
|
||||
"notifications_attachment_copy_url_title": "Copier l'URL de la pièce jointe dans le presse-papiers",
|
||||
"notifications_attachment_open_title": "Aller à {{url}}",
|
||||
"notifications_attachment_link_expired": "lien de téléchargement expiré",
|
||||
|
@ -51,7 +51,7 @@
|
|||
"nav_button_subscribe": "S'abonner au sujet",
|
||||
"notifications_no_subscriptions_description": "Cliquez sur le lien « {{linktext}} » pour créer ou vous abonner à un sujet. Après cela, vous pouvez envoyer des messages via PUT ou POST et vous recevrez des notifications ici.",
|
||||
"alert_notification_permission_required_title": "Les notifications sont désactivées",
|
||||
"alert_notification_permission_required_description": "Autorisez votre navigateur à afficher les notifications du bureau.",
|
||||
"alert_notification_permission_required_description": "Autorisez votre navigateur à afficher les notifications du bureau",
|
||||
"alert_notification_permission_required_button": "Accorder maintenant",
|
||||
"notifications_none_for_any_title": "Vous n'avez reçu aucune notification.",
|
||||
"publish_dialog_title_topic": "Publier vers {{topic}}",
|
||||
|
@ -98,7 +98,7 @@
|
|||
"subscribe_dialog_subscribe_button_subscribe": "S'abonner",
|
||||
"subscribe_dialog_login_description": "Ce sujet est protégé par un mot de passe. Veuillez entrer le nom d'utilisateur et le mot de passe pour vous abonner.",
|
||||
"subscribe_dialog_login_username_label": "Nom d'utilisateur, par ex. phil",
|
||||
"subscribe_dialog_login_button_login": "Connexion",
|
||||
"subscribe_dialog_login_button_login": "Se connecter",
|
||||
"prefs_notifications_sound_title": "Son de notification",
|
||||
"prefs_notifications_delete_after_never": "Jamais",
|
||||
"prefs_users_table_base_url_header": "URL de service",
|
||||
|
@ -194,13 +194,13 @@
|
|||
"signup_error_username_taken": "L'identifiant {{username}} est déjà utilisé",
|
||||
"signup_error_creation_limit_reached": "Limite de création de comptes atteinte",
|
||||
"login_title": "Se connecter à son compte Ntfy",
|
||||
"login_form_button_submit": "Connexion",
|
||||
"login_form_button_submit": "Se connecter",
|
||||
"login_link_signup": "S'inscrire",
|
||||
"login_disabled": "La connection est désactivée",
|
||||
"action_bar_account": "Compte",
|
||||
"action_bar_profile_title": "Profil",
|
||||
"action_bar_profile_settings": "Paramètres",
|
||||
"action_bar_sign_in": "Connexion",
|
||||
"action_bar_sign_in": "Se connecter",
|
||||
"action_bar_sign_up": "Inscription",
|
||||
"nav_button_account": "Compte",
|
||||
"signup_title": "Créer un compte Ntfy",
|
||||
|
@ -380,5 +380,28 @@
|
|||
"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"
|
||||
"account_usage_calls_title": "Appels téléphoniques passés",
|
||||
"action_bar_mute_notifications": "Désactiver les notifications",
|
||||
"action_bar_unmute_notifications": "Réactiver les notifications",
|
||||
"alert_notification_permission_denied_title": "Les notifications sont bloquées",
|
||||
"alert_notification_permission_denied_description": "Veuillez les réactiver dans votre navigateur",
|
||||
"alert_notification_ios_install_required_description": "Cliquez sur l'icône Partager, puis Sur l'écran d'accueil pour activer les notifications sur iOS",
|
||||
"alert_notification_ios_install_required_title": "Installation iOS nécessaire",
|
||||
"notifications_actions_failed_notification": "Échec de l'action",
|
||||
"publish_dialog_checkbox_markdown": "Formater en Markdown",
|
||||
"subscribe_dialog_subscribe_use_another_background_info": "Les notifications provenant d'autres serveurs ne seront pas reçues tant que l'application web n'est pas ouverte",
|
||||
"prefs_notifications_web_push_title": "Notifications en arrière-plan",
|
||||
"prefs_notifications_web_push_enabled_description": "Les notifications sont reçues même quand l'application web n'est pas en cours d'exécution (via Web Push)",
|
||||
"prefs_notifications_web_push_disabled_description": "Les notifications sont reçues quand l'application web est en cours d'exécution (via WebSocket)",
|
||||
"prefs_notifications_web_push_enabled": "Activé pour {{server}}",
|
||||
"prefs_notifications_web_push_disabled": "Désactivé",
|
||||
"prefs_appearance_theme_title": "Thème",
|
||||
"prefs_appearance_theme_system": "Système (défaut)",
|
||||
"prefs_appearance_theme_dark": "Mode sombre",
|
||||
"prefs_appearance_theme_light": "Mode clair",
|
||||
"error_boundary_button_reload_ntfy": "Recharger ntfy",
|
||||
"web_push_subscription_expiring_title": "Les notifications seront suspendues",
|
||||
"web_push_subscription_expiring_body": "Ouvrez ntfy pour continuer à recevoir les notifications",
|
||||
"web_push_unknown_notification_title": "Notification inconnue reçue du serveur",
|
||||
"web_push_unknown_notification_body": "Il est possible que vous deviez mettre à jour ntfy en ouvrant l'application web"
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"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.",
|
||||
"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",
|
||||
|
@ -380,5 +380,31 @@
|
|||
"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."
|
||||
"subscribe_dialog_login_description": "Este tema está protexido por contrasinal. Por favor, introduza o usuario e contrasinal para subscribirse.",
|
||||
"action_bar_mute_notifications": "Acalar notificacións",
|
||||
"action_bar_unmute_notifications": "Reactivar notificacións",
|
||||
"alert_notification_permission_required_title": "Notificacións desactivadas",
|
||||
"alert_notification_permission_required_description": "Concederlle permisos ao navegador para mostrar notificacións de escritorio",
|
||||
"alert_notification_permission_required_button": "Conceder",
|
||||
"alert_notification_permission_denied_title": "Notificacións bloqueadas",
|
||||
"alert_notification_permission_denied_description": "Por favor reactívaas no navegador",
|
||||
"alert_notification_ios_install_required_title": "Require instalación iOS",
|
||||
"alert_notification_ios_install_required_description": "Preme na icona Compartir e Engadir a Pantalla de Inicio para activar as notificacións en iOS",
|
||||
"notifications_actions_failed_notification": "Non se puido realizar a acción",
|
||||
"publish_dialog_checkbox_markdown": "Dar formato Markdow",
|
||||
"prefs_notifications_web_push_title": "Notificacións en segundo plano",
|
||||
"prefs_notifications_web_push_enabled_description": "Recíbense notificacións incluso se a app web non está en execución (vía Web Push)",
|
||||
"prefs_notifications_web_push_disabled_description": "Recíbense as notificacións cando a app web está en execución (vía WebSocket)",
|
||||
"prefs_notifications_web_push_enabled": "Activadas para {{server}}",
|
||||
"prefs_notifications_web_push_disabled": "Desactivadas",
|
||||
"prefs_appearance_theme_title": "Decorado",
|
||||
"prefs_appearance_theme_system": "Sistema (por defecto)",
|
||||
"prefs_appearance_theme_dark": "Modo escuro",
|
||||
"prefs_appearance_theme_light": "Modo claro",
|
||||
"error_boundary_button_reload_ntfy": "Recargar ntfy",
|
||||
"web_push_subscription_expiring_title": "Vanse pausar as notificacións",
|
||||
"web_push_subscription_expiring_body": "Abrir ntfy para seguir recibindo notificacións",
|
||||
"web_push_unknown_notification_title": "Recibida unha notificación descoñecida desde o servidor",
|
||||
"web_push_unknown_notification_body": "Poderías ter que actualizar ntfy abrindo a app web",
|
||||
"subscribe_dialog_subscribe_use_another_background_info": "As notificacións procedentes doutros servidores non se van recibir cando a app web estea pechada"
|
||||
}
|
||||
|
|
|
@ -381,5 +381,28 @@
|
|||
"account_upgrade_dialog_tier_features_no_calls": "Tidak ada panggilan telepon",
|
||||
"account_basics_phone_numbers_dialog_code_label": "Kode verifikasi",
|
||||
"publish_dialog_call_item": "Panggil nomor telepon {{number}}",
|
||||
"publish_dialog_chip_call_no_verified_numbers_tooltip": "Tidak ada nomor telepon terverifikasi"
|
||||
"publish_dialog_chip_call_no_verified_numbers_tooltip": "Tidak ada nomor telepon terverifikasi",
|
||||
"action_bar_unmute_notifications": "Nyalakan notifikasi",
|
||||
"alert_notification_permission_denied_title": "Notifikasi sedang diblokir",
|
||||
"alert_notification_permission_denied_description": "Silakan aktifkan lagi dalam peramban Anda",
|
||||
"alert_notification_ios_install_required_title": "Pemasangan iOS diperlukan",
|
||||
"alert_notification_ios_install_required_description": "Klik ikon Bagikan dan Tambahkan ke Layar Beranda untuk mengaktifkan notifikasi di iOS",
|
||||
"notifications_actions_failed_notification": "Tindakan tidak berhasil",
|
||||
"publish_dialog_checkbox_markdown": "Format sebagai Markdown",
|
||||
"prefs_notifications_web_push_title": "Notifikasi latar belakang",
|
||||
"prefs_notifications_web_push_enabled_description": "Notifikasi diterima bahkan ketika aplikasi web tidak berjalan (melalui Web Push)",
|
||||
"prefs_notifications_web_push_disabled_description": "Notifikasi diterima ketika aplikasi web berjalan (melalui WebSocket)",
|
||||
"prefs_appearance_theme_title": "Tema",
|
||||
"error_boundary_button_reload_ntfy": "Muat ulang ntfy",
|
||||
"action_bar_mute_notifications": "Matikan notifikasi",
|
||||
"subscribe_dialog_subscribe_use_another_background_info": "Notifikasi dari server lain tidak akan diterima ketika aplikasi web tidak buka",
|
||||
"prefs_notifications_web_push_enabled": "Diaktifkan untuk {{server}}",
|
||||
"prefs_notifications_web_push_disabled": "Dinonaktifkan",
|
||||
"prefs_appearance_theme_dark": "Mode gelap",
|
||||
"prefs_appearance_theme_system": "Sistem (bawaan)",
|
||||
"prefs_appearance_theme_light": "Mode terang",
|
||||
"web_push_subscription_expiring_title": "Notifikasi akan dijeda",
|
||||
"web_push_subscription_expiring_body": "Buka ntfy untuk terus menerima notifikasi",
|
||||
"web_push_unknown_notification_title": "Notifikasi yang tidak diketahui diterima dari server",
|
||||
"web_push_unknown_notification_body": "Anda mungkin harus memperbarui ntfy dengan membuka aplikasi web"
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
"message_bar_type_message": "Typ hier een bericht",
|
||||
"action_bar_unsubscribe": "Afmelden",
|
||||
"message_bar_error_publishing": "Fout bij publiceren notificatie",
|
||||
"nav_topics_title": "Geabonneerde onderwerpen",
|
||||
"nav_topics_title": "Geabonneerde topics",
|
||||
"nav_button_settings": "Instellingen",
|
||||
"alert_not_supported_description": "Notificaties worden niet ondersteund door je browser.",
|
||||
"alert_not_supported_description": "Notificaties worden niet ondersteund door je browser",
|
||||
"notifications_none_for_any_title": "Je hebt nog geen notificaties ontvangen.",
|
||||
"publish_dialog_tags_label": "Tags",
|
||||
"publish_dialog_chip_attach_file_label": "Lokaal bestand bijvoegen",
|
||||
|
@ -36,7 +36,7 @@
|
|||
"nav_button_muted": "Notificaties gedempt",
|
||||
"nav_button_connecting": "verbinden",
|
||||
"alert_notification_permission_required_title": "Notificaties zijn uitgeschakeld",
|
||||
"alert_notification_permission_required_description": "Verleen je browser toestemming voor het weergeven van notificaties.",
|
||||
"alert_notification_permission_required_description": "Verleen je browser toestemming voor het weergeven van notificaties op desktop",
|
||||
"alert_notification_permission_required_button": "Nu toestaan",
|
||||
"alert_not_supported_title": "Notificaties zijn niet ondersteund",
|
||||
"notifications_list": "Notificatielijst",
|
||||
|
@ -195,14 +195,14 @@
|
|||
"signup_disabled": "Registreren is uitgeschakeld",
|
||||
"signup_error_username_taken": "Gebruikersnaam {{username}} is al bezet",
|
||||
"signup_error_creation_limit_reached": "Limiet voor aanmaken account bereikt",
|
||||
"login_title": "Aanmelden bij uw ntfy account",
|
||||
"login_title": "Inloggen met uw ntfy account",
|
||||
"login_form_button_submit": "Inloggen",
|
||||
"login_link_signup": "Registreer",
|
||||
"login_link_signup": "Registreren",
|
||||
"login_disabled": "Inloggen is uitgeschakeld",
|
||||
"action_bar_account": "Account",
|
||||
"action_bar_reservation_add": "Onderwerp reserveren",
|
||||
"action_bar_reservation_edit": "Reservatie wijzigen",
|
||||
"action_bar_reservation_delete": "Verwijder reservatie",
|
||||
"action_bar_reservation_add": "Topic reserveren",
|
||||
"action_bar_reservation_edit": "Reservering wijzigen",
|
||||
"action_bar_reservation_delete": "Verwijder reservering",
|
||||
"action_bar_reservation_limit_reached": "Limiet bereikt",
|
||||
"action_bar_profile_title": "Profiel",
|
||||
"nav_upgrade_banner_label": "Upgrade naar ntfy Pro",
|
||||
|
@ -380,5 +380,28 @@
|
|||
"account_basics_phone_numbers_dialog_verify_button_sms": "Stuur SMS",
|
||||
"account_basics_phone_numbers_dialog_code_label": "Verificatiecode",
|
||||
"account_usage_calls_title": "Aantal telefoontjes",
|
||||
"account_usage_calls_none": "Met dit account kan niet worden gebeld"
|
||||
"account_usage_calls_none": "Met dit account kan niet worden gebeld",
|
||||
"action_bar_mute_notifications": "Notificaties dempen",
|
||||
"prefs_notifications_web_push_disabled_description": "Notificaties worden ontvangen als de webapplicatie geopend is (via WebSocket)",
|
||||
"web_push_unknown_notification_body": "Het is mogelijk dat je ntfy moet updaten door de webapplicatie opnieuw te openen",
|
||||
"action_bar_unmute_notifications": "Dempen notificaties opheffen",
|
||||
"alert_notification_permission_denied_title": "Notificaties zijn geblokkeerd",
|
||||
"alert_notification_permission_denied_description": "Activeer ze in de browser",
|
||||
"alert_notification_ios_install_required_title": "iOS installatie vereist",
|
||||
"alert_notification_ios_install_required_description": "Klik op het Deel icoon, daarna op \"Add to Home Screen\" om notificaties op iOS toe te staan",
|
||||
"notifications_actions_failed_notification": "Actie onsuccesvol",
|
||||
"publish_dialog_checkbox_markdown": "Opmaken met Markdown",
|
||||
"subscribe_dialog_subscribe_use_another_background_info": "Notificaties van andere servers worden niet ontvangen als de webapplicatie niet geopend is",
|
||||
"prefs_notifications_web_push_title": "Achtergrond notificaties",
|
||||
"prefs_notifications_web_push_enabled": "Aan voor {{server}}",
|
||||
"prefs_notifications_web_push_disabled": "Uitgezet",
|
||||
"prefs_notifications_web_push_enabled_description": "Notificaties worden ontvangen, ook als de webapplicatie niet geopend is (via Web Push)",
|
||||
"prefs_appearance_theme_title": "Thema",
|
||||
"prefs_appearance_theme_system": "Systeem (standaard)",
|
||||
"prefs_appearance_theme_dark": "Donkere modus",
|
||||
"prefs_appearance_theme_light": "Lichte modus",
|
||||
"error_boundary_button_reload_ntfy": "Herlaad ntfy",
|
||||
"web_push_subscription_expiring_title": "Notificaties worden gepauzeerd",
|
||||
"web_push_subscription_expiring_body": "Open ntfy om weer notificaties te ontvangen",
|
||||
"web_push_unknown_notification_title": "Onbekende notificatie ontvangen van de server"
|
||||
}
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
"nav_button_documentation": "Dokumentacja",
|
||||
"nav_button_muted": "Powiadomienia wyciszone",
|
||||
"alert_notification_permission_required_title": "Powiadomienia są wyłączone",
|
||||
"alert_notification_permission_required_description": "Udziel przeglądarce pozwolenia na wyświetlanie powiadomień na pulpicie.",
|
||||
"alert_notification_permission_required_description": "Udziel przeglądarce pozwolenia na wyświetlanie powiadomień na pulpicie",
|
||||
"alert_notification_permission_required_button": "Pozwól teraz",
|
||||
"alert_not_supported_title": "Powiadomienia nie są obsługiwane",
|
||||
"alert_not_supported_description": "Powiadomienia nie są obsługiwane przez Twoją przeglądarkę.",
|
||||
"alert_not_supported_description": "Powiadomienia nie są obsługiwane przez Twoją przeglądarkę",
|
||||
"notifications_list": "Lista powiadomień",
|
||||
"notifications_list_item": "Powiadomienie",
|
||||
"notifications_mark_read": "Oznacz jako przeczytane",
|
||||
|
@ -355,5 +355,54 @@
|
|||
"publish_dialog_call_item": "Zadzwoń pod numer {{number}}",
|
||||
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
|
||||
"account_upgrade_dialog_tier_selected_label": "Wybrane",
|
||||
"account_upgrade_dialog_reservations_warning_other": "Wybrany plan zezwala na mniejszą liczbę zarezerwowanych tematów niż obecny. Przed zmianą planu, <strong>usuń co najmniej tyle rezerwacji: {{count}}</strong>. Rezerwacje możesz usunąć w <Link>Ustawieniach</Link>."
|
||||
"account_upgrade_dialog_reservations_warning_other": "Wybrany plan zezwala na mniejszą liczbę zarezerwowanych tematów niż obecny. Przed zmianą planu, <strong>usuń co najmniej tyle rezerwacji: {{count}}</strong>. Rezerwacje możesz usunąć w <Link>Ustawieniach</Link>.",
|
||||
"prefs_reservations_title": "Zarezerwowane tematy",
|
||||
"prefs_reservations_table_everyone_read_only": "Ja mogę publikować i subskrybować, każdy może subskrybować",
|
||||
"prefs_reservations_table_not_subscribed": "Nie jesteś zasubskrybowany",
|
||||
"prefs_reservations_dialog_title_delete": "Usuń rezerwacje tematu",
|
||||
"prefs_reservations_dialog_topic_label": "Temat",
|
||||
"reservation_delete_dialog_action_delete_title": "Usuń wiadomości i załączniki zapisane w pamięci cache",
|
||||
"prefs_reservations_description": "Możesz tutaj zarezerwować nazwy tematów do własnego użytku. Rezerwacja tematu daje ci go na własność i pozwala definiować permisje dla innych użytkowników.",
|
||||
"prefs_reservations_limit_reached": "Zużyłeś swój limit zarezerwowanych tematów.",
|
||||
"prefs_reservations_add_button": "Dodaj zarezerwowany temat",
|
||||
"account_tokens_delete_dialog_description": "Przed usuwaniem tokenu dostępu upewnij się, że nie jest on aktywnie używany przez inną aplikację lub skrypt. <strong>Ta akcja nie może być wycofana</strong>.",
|
||||
"prefs_reservations_table_everyone_read_write": "Każdy może publikować i subskrybować",
|
||||
"prefs_reservations_table_click_to_subscribe": "Kliknij aby subskrybować",
|
||||
"prefs_reservations_dialog_title_edit": "Modyfikuj zarezerwowany temat",
|
||||
"prefs_reservations_table_everyone_write_only": "Ja mogę publikować i subskrybować, każdy może publikować",
|
||||
"action_bar_mute_notifications": "Wycisz powiadomienia",
|
||||
"alert_notification_permission_denied_title": "Powiadomienia są blokowane",
|
||||
"alert_notification_ios_install_required_description": "Wciśnij ikonę Udostępniania i dodaj do Strony Głównej aby zezwolić na otrzymywanie powiadomień na IOS",
|
||||
"notifications_actions_failed_notification": "Akcja zakończona niepowodzeniem",
|
||||
"prefs_notifications_web_push_disabled_description": "Powiadomienia są dostarczane kiedy aplikacja jest aktywna (poprzez WebSocket)",
|
||||
"prefs_notifications_web_push_enabled": "Włączone dla {{server}}",
|
||||
"prefs_notifications_web_push_disabled": "Wyłączone",
|
||||
"prefs_appearance_theme_system": "Systemowy (domyślny)",
|
||||
"prefs_appearance_theme_dark": "Tryb ciemny",
|
||||
"prefs_appearance_theme_light": "Tryb jasny",
|
||||
"prefs_reservations_edit_button": "Modyfikuj ustawienia dostępu dla tematu",
|
||||
"prefs_reservations_table": "Tabela zarezerwowanych tematów",
|
||||
"prefs_reservations_table_topic_header": "Temat",
|
||||
"prefs_reservations_table_access_header": "Dostęp",
|
||||
"prefs_reservations_table_everyone_deny_all": "Tylko ja mogę publikować i subskrybować",
|
||||
"prefs_reservations_dialog_access_label": "Dostęp",
|
||||
"reservation_delete_dialog_action_delete_description": "Wiadomości i załączniki zapisane w pamięci cache zostaną pernamentie usunięte. <strong>Ta akcja nie może być wycofana</strong>.",
|
||||
"reservation_delete_dialog_submit_button": "Usuń rezerwację",
|
||||
"error_boundary_button_reload_ntfy": "Przeładuj ntfy",
|
||||
"web_push_subscription_expiring_title": "Powiadomienia będą wstrzymane",
|
||||
"web_push_subscription_expiring_body": "Otwórz ntfy aby nadal dostawać powiadomienia",
|
||||
"alert_notification_permission_denied_description": "Prosze ponownie pozwolić na otrzymywanie powiadomień w twojej przeglądarce",
|
||||
"subscribe_dialog_subscribe_use_another_background_info": "Powiadomienia z innych serwerów nie zostaną odebrane jeśli aplikacja nie jest otwarta",
|
||||
"alert_notification_ios_install_required_title": "Instalacja IOS wymagana",
|
||||
"publish_dialog_checkbox_markdown": "Formatuj jako Markdown",
|
||||
"account_tokens_delete_dialog_submit_button": "Pernamentnie usuń token dostępu",
|
||||
"prefs_notifications_web_push_title": "Powiadomienia w tle",
|
||||
"prefs_notifications_web_push_enabled_description": "Powiadomienia są dostarczane nawet kiedy aplikacja nie jest aktywna (poprzez Web Push)",
|
||||
"prefs_users_description_no_sync": "Nazwy użytkownika i hasła nie są synchronizowane z kontem.",
|
||||
"prefs_users_table_cannot_delete_or_edit": "Nie można usunąć lub modyfikować zalogowanego użytkownika",
|
||||
"prefs_reservations_delete_button": "Zresetuj ustawienia dostępu dla tematu",
|
||||
"prefs_reservations_dialog_title_add": "Zarezerwuj temat",
|
||||
"reservation_delete_dialog_action_keep_title": "Zachowaj wiadomości i załącznik w pamięci cache",
|
||||
"reservation_delete_dialog_action_keep_description": "Wiadomości i załączniki które są zapisane w pamięci cache będą dostępne publicznie dla każdego znającego nazwę powiązanego z nimi tematu.",
|
||||
"web_push_unknown_notification_title": "Nieznane powiadomienie otrzymane od serwera"
|
||||
}
|
||||
|
|
|
@ -283,5 +283,39 @@
|
|||
"account_basics_phone_numbers_dialog_verify_button_call": "Ligar pra mim",
|
||||
"publish_dialog_call_item": "Ligue para o número de telefone {{number}}",
|
||||
"account_usage_emails_title": "Emails enviados",
|
||||
"account_basics_phone_numbers_dialog_channel_sms": "SMS"
|
||||
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
|
||||
"account_delete_title": "Deletar conta",
|
||||
"account_delete_dialog_label": "Senha",
|
||||
"account_upgrade_dialog_interval_yearly": "Anual",
|
||||
"account_upgrade_dialog_title": "Alterar nível da conta",
|
||||
"alert_notification_ios_install_required_description": "Clique no ícone Compartilhar e adicione a tela inicial para ativar notificações no iOS",
|
||||
"account_delete_dialog_billing_warning": "Excluir sua conta também cancela imediatamente sua assinatura de cobrança. Você não terá mais acesso ao painel de faturamento.",
|
||||
"account_delete_dialog_description": "Isso excluirá permanentemente sua conta, incluindo todos os dados armazenados no servidor. Após a exclusão, seu nome de usuário ficará indisponível por 7 dias. Se você realmente deseja prosseguir, confirme sua senha na caixa abaixo.",
|
||||
"account_upgrade_dialog_proration_info": "<strong>Prorrogação</strong>: Ao atualizar entre planos pagos, a diferença de preço será <strong>cobrada imediatamente</strong>. Ao fazer downgrade para um nível inferior, o saldo será usado para pagar futuras cobranças.",
|
||||
"action_bar_mute_notifications": "Mutar notificações",
|
||||
"action_bar_unmute_notifications": "Desmutar notificações",
|
||||
"alert_notification_permission_denied_title": "Notificações estão bloqueadas",
|
||||
"alert_notification_permission_denied_description": "Por favor, reative elas no seu navegador",
|
||||
"alert_notification_ios_install_required_title": "Requer instalação no iOS",
|
||||
"notifications_actions_failed_notification": "Ação mal sucedida",
|
||||
"publish_dialog_checkbox_markdown": "Formatar como Markdown",
|
||||
"subscribe_dialog_subscribe_use_another_background_info": "Notificações de outros servidores não serão recebidas quando o web app não estiver aberto",
|
||||
"account_usage_basis_ip_description": "As estatísticas e limites de uso desta conta são baseados no seu endereço IP, portanto, podem ser compartilhados com outros usuários. Os limites mostrados acima são aproximados com base nos limites de taxa existentes.",
|
||||
"account_usage_cannot_create_portal_session": "Não foi possível abrir o portal de cobrança",
|
||||
"account_delete_description": "Deletar conta permanentemente",
|
||||
"account_delete_dialog_button_cancel": "Cancelar",
|
||||
"account_delete_dialog_button_submit": "Deletar conta permanentemente",
|
||||
"account_upgrade_dialog_interval_monthly": "Mensal",
|
||||
"account_upgrade_dialog_interval_yearly_discount_save": "desconto de {{discount}}%",
|
||||
"account_upgrade_dialog_interval_yearly_discount_save_up_to": "desconto de até {{discount}}%",
|
||||
"account_upgrade_dialog_cancel_warning": "Isso <strong>cancelará sua assinatura</strong> e fará downgrade de sua conta em {{date}}. Nessa data, as reservas de tópicos, bem como as mensagens armazenadas em cache no servidor <strong>serão excluídas</strong>.",
|
||||
"account_upgrade_dialog_reservations_warning_one": "O nível selecionada permite menos tópicos reservados do que a camada atual. Antes de alterar seu nível, <strong>exclua pelo menos uma reserva</strong>. Você pode remover reservas nas <Link>Configurações</Link>",
|
||||
"account_upgrade_dialog_reservations_warning_other": "O plano selecionado permite menos tópicos reservados do que o seu plano atual. Antes de mudar seu plano, exclua por favor ao menos {{count}} reservas. Você pode remover reservas em Configurações.",
|
||||
"account_upgrade_dialog_tier_features_no_reservations": "Sem tópicos reservados",
|
||||
"account_upgrade_dialog_tier_features_messages_one": "{{messages}} mensagen diária",
|
||||
"account_upgrade_dialog_tier_features_emails_one": "{{emails}} email diário",
|
||||
"account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} tópico reservado",
|
||||
"account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} tópicos reservados",
|
||||
"account_upgrade_dialog_tier_features_emails_other": "{{emails}} emails diários",
|
||||
"account_upgrade_dialog_tier_features_messages_other": "{{messages}} mensagens diárias"
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
"notifications_none_for_topic_description": "Чтобы отправить уведомление на данную тему, просто сделаете PUT или POST-запрос на URL-адрес этой темы.",
|
||||
"notifications_none_for_any_description": "Чтобы отправить уведомление на тему, просто сделаете PUT или POST-запрос на её URL-адрес. Вот пример с использованием одной из ваших тем.",
|
||||
"notifications_no_subscriptions_title": "Похоже, что у вас ещё нет подписок.",
|
||||
"alert_notification_permission_required_description": "Разрешите браузеру показывать уведомления.",
|
||||
"alert_notification_permission_required_description": "Предоставьте браузеру разрешение на отображение уведомлений на рабочем столе",
|
||||
"notifications_no_subscriptions_description": "Нажмите на ссылку \"{{linktext}}\", чтобы создать или подписаться на тему. После этого Вы сможете отправлять сообщения используя PUT или POST-запросы и получать уведомления здесь.",
|
||||
"notifications_example": "Пример",
|
||||
"notifications_more_details": "Для более подробной информации, посетите <websiteLink>наш сайт</websiteLink> или <docsLink>документацию</docsLink>.",
|
||||
|
@ -41,7 +41,7 @@
|
|||
"publish_dialog_email_label": "Электронная почта",
|
||||
"message_bar_error_publishing": "Ошибка публикации уведомления",
|
||||
"alert_not_supported_title": "Уведомления не поддерживаются",
|
||||
"alert_not_supported_description": "Уведомления не поддерживаются вашим браузером.",
|
||||
"alert_not_supported_description": "Уведомления не поддерживаются в вашем браузере",
|
||||
"notifications_copied_to_clipboard": "Скопировано в буфер обмена",
|
||||
"notifications_attachment_open_button": "Открыть вложение",
|
||||
"notifications_none_for_topic_title": "Вы ещё не получали уведомления для этой темы.",
|
||||
|
@ -254,17 +254,17 @@
|
|||
"action_bar_reservation_limit_reached": "Лимит исчерпан",
|
||||
"action_bar_toggle_mute": "Заглушить/разрешить уведомления",
|
||||
"nav_button_account": "Учетная запись",
|
||||
"nav_upgrade_banner_label": "Подпишитесь на ntfy Pro",
|
||||
"nav_upgrade_banner_label": "Купить подписку ntfy Pro",
|
||||
"message_bar_show_dialog": "Открыть диалог публикации",
|
||||
"notifications_list": "Список уведомлений",
|
||||
"notifications_list_item": "Уведомление",
|
||||
"notifications_mark_read": "Пометить как прочтенное",
|
||||
"notifications_mark_read": "Пометить как прочитанное",
|
||||
"notifications_priority_x": "Приоритет {{priority}}",
|
||||
"notifications_attachment_image": "Приложенное изображение",
|
||||
"notifications_attachment_file_audio": "звуковой файл",
|
||||
"notifications_attachment_file_video": "видео файл",
|
||||
"notifications_attachment_file_image": "графический файл",
|
||||
"notifications_attachment_file_app": "исполняемый файл Android",
|
||||
"notifications_attachment_file_app": "Исполняемый файл Android",
|
||||
"notifications_attachment_file_document": "другой тип файла",
|
||||
"notifications_actions_not_supported": "Действие не поддерживается в веб-приложении",
|
||||
"display_name_dialog_title": "Изменить псевдоним",
|
||||
|
@ -334,7 +334,7 @@
|
|||
"alert_not_supported_context_description": "Уведомления поддерживаются только по протоколу HTTPS. Это ограничение <mdnLink>Notifications API</mdnLink>.",
|
||||
"notifications_delete": "Удалить",
|
||||
"notifications_new_indicator": "Новое уведомление",
|
||||
"notifications_actions_http_request_title": "Сделать HTTP {{method}}-запрос на {{url}}",
|
||||
"notifications_actions_http_request_title": "Отправить HTTP {{method}}-запрос на {{url}}",
|
||||
"display_name_dialog_placeholder": "Псевдоним",
|
||||
"account_basics_password_dialog_new_password_label": "Новый пароль",
|
||||
"account_basics_password_dialog_confirm_password_label": "Подтвердите пароль",
|
||||
|
@ -380,5 +380,28 @@
|
|||
"account_basics_phone_numbers_dialog_code_label": "Проверочный код",
|
||||
"account_basics_phone_numbers_dialog_verify_button_call": "Позвонить мне",
|
||||
"publish_dialog_call_item": "Вызов телефонного номера {{number}}",
|
||||
"account_basics_phone_numbers_dialog_channel_sms": "SMS"
|
||||
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
|
||||
"action_bar_mute_notifications": "Заглушить уведомления",
|
||||
"action_bar_unmute_notifications": "Разрешить уведомления",
|
||||
"alert_notification_permission_denied_title": "Уведомления заблокированы",
|
||||
"alert_notification_permission_denied_description": "Пожалуйста, разрешите их в своём браузере",
|
||||
"alert_notification_ios_install_required_title": "iOS требует установку",
|
||||
"alert_notification_ios_install_required_description": "Нажмите на значок \"Поделиться\" и \"Добавить на главный экран\", чтобы включить уведомления на iOS",
|
||||
"error_boundary_button_reload_ntfy": "Перезагрузить ntfy",
|
||||
"web_push_subscription_expiring_title": "Уведомления будут приостановлены",
|
||||
"web_push_subscription_expiring_body": "Откройте ntfy, чтобы продолжать получать уведомления",
|
||||
"web_push_unknown_notification_title": "Получено неизвестное уведомление от сервера",
|
||||
"web_push_unknown_notification_body": "Вам может потребоваться обновить ntfy, для этого откройте веб-приложение",
|
||||
"prefs_notifications_web_push_title": "Фоновые уведомления",
|
||||
"prefs_notifications_web_push_enabled_description": "Уведомления приходят даже когда веб-приложение не запущено (через Web Push)",
|
||||
"prefs_notifications_web_push_disabled_description": "Уведомления приходят, когда веб-приложение запущено (через WebSocket)",
|
||||
"prefs_appearance_theme_title": "Тема",
|
||||
"prefs_notifications_web_push_enabled": "Включено для {{server}}",
|
||||
"prefs_notifications_web_push_disabled": "Выключено",
|
||||
"notifications_actions_failed_notification": "Неудачное действие",
|
||||
"publish_dialog_checkbox_markdown": "Форматировать как Markdown",
|
||||
"subscribe_dialog_subscribe_use_another_background_info": "Уведомления с других серверов не будут получены, когда веб-приложение не открыто",
|
||||
"prefs_appearance_theme_system": "Системный (по умолчанию)",
|
||||
"prefs_appearance_theme_dark": "Ночной режим",
|
||||
"prefs_appearance_theme_light": "Дневной режим"
|
||||
}
|
||||
|
|
|
@ -155,7 +155,7 @@
|
|||
"alert_grant_title": "Oznámenia sú vypnuté",
|
||||
"alert_grant_button": "Prideliť teraz",
|
||||
"alert_not_supported_title": "Oznámenia nie sú podporované",
|
||||
"alert_not_supported_description": "Oznámenia nie sú vo vašom prehliadači podporované.",
|
||||
"alert_not_supported_description": "Oznámenia nie sú vo vašom prehliadači podporované",
|
||||
"notifications_attachment_copy_url_title": "Kopírovať URL adresu prílohy do schránky",
|
||||
"notifications_attachment_copy_url_button": "Kopírovať adresu URL",
|
||||
"notifications_attachment_open_title": "Prejsť na {{url}}",
|
||||
|
@ -380,5 +380,31 @@
|
|||
"account_upgrade_dialog_reservations_warning_other": "Vybraná úroveň umožňuje menej rezervovaných tém ako vaša aktuálna úroveň. Pred zmenou úrovne <strong>vymažte aspoň {{count}} rezervácií</strong>. Rezervácie môžete odstrániť v <Link>Nastaveniach</Link>.",
|
||||
"prefs_users_dialog_title_add": "Pridať používateľa",
|
||||
"account_tokens_dialog_button_create": "Vytvoriť token",
|
||||
"account_tokens_table_create_token_button": "Vytvoriť prístupový token"
|
||||
"account_tokens_table_create_token_button": "Vytvoriť prístupový token",
|
||||
"action_bar_mute_notifications": "Stlmiť oznámenia",
|
||||
"action_bar_unmute_notifications": "Zrušiť stlmenie oznámení",
|
||||
"alert_notification_permission_required_description": "Udeliť povolenie prehliadaču na zobrazovanie oznámení na ploche",
|
||||
"alert_notification_permission_required_button": "Udeliť teraz",
|
||||
"alert_notification_permission_denied_title": "Oznámenia sú zablokované",
|
||||
"alert_notification_permission_denied_description": "Opätovne ich povoľte vo svojom prehliadači",
|
||||
"alert_notification_ios_install_required_title": "Vyžaduje sa inštalácia iOS",
|
||||
"notifications_actions_failed_notification": "Neúspešná akcia",
|
||||
"publish_dialog_checkbox_markdown": "Formátovať ako Markdown",
|
||||
"subscribe_dialog_subscribe_use_another_background_info": "Oznámenia z iných serverov sa nebudú prijímať, keď webová aplikácia nie je otvorená",
|
||||
"prefs_notifications_web_push_title": "Oznámenia na pozadí",
|
||||
"prefs_notifications_web_push_enabled_description": "Oznámenia sa prijímajú, aj keď webová aplikácia nie je spustená (prostredníctvom Web Push)",
|
||||
"prefs_notifications_web_push_disabled_description": "Oznámenia sa prijímajú, keď je webová aplikácia spustená (cez WebSocket)",
|
||||
"prefs_notifications_web_push_enabled": "Povolené pre {{server}}",
|
||||
"prefs_notifications_web_push_disabled": "Zakázané",
|
||||
"prefs_appearance_theme_title": "Téma",
|
||||
"prefs_appearance_theme_system": "Systémové (predvolené)",
|
||||
"prefs_appearance_theme_dark": "Tmavý režim",
|
||||
"prefs_appearance_theme_light": "Svetlý režim",
|
||||
"error_boundary_button_reload_ntfy": "Obnoviť ntfy",
|
||||
"web_push_subscription_expiring_title": "Oznámenia budú pozastavené",
|
||||
"web_push_subscription_expiring_body": "Ak chcete pokračovať v prijímaní upozornení, otvorte ntfy",
|
||||
"web_push_unknown_notification_title": "Neznáme oznámenie prijaté zo servera",
|
||||
"web_push_unknown_notification_body": "Možno budete musieť aktualizovať ntfy otvorením webovej aplikácie",
|
||||
"alert_notification_permission_required_title": "Oznámenia sú vypnuté",
|
||||
"alert_notification_ios_install_required_description": "Kliknutím na Zdieľať a Pridať na domovskú obrazovku povolíte oznámenia v systéme iOS"
|
||||
}
|
||||
|
|
|
@ -380,5 +380,12 @@
|
|||
"account_basics_phone_numbers_dialog_channel_sms": "SMS",
|
||||
"account_upgrade_dialog_tier_features_calls_other": "{{calls}} dagliga telefonsamtal",
|
||||
"account_upgrade_dialog_tier_features_no_calls": "Inga telefonsamtal",
|
||||
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} dagliga telefonsamtal"
|
||||
"account_upgrade_dialog_tier_features_calls_one": "{{calls}} dagliga telefonsamtal",
|
||||
"action_bar_mute_notifications": "Stäng av aviseringar",
|
||||
"action_bar_unmute_notifications": "Slå på aviseringar",
|
||||
"alert_notification_permission_denied_description": "Vänligen aktivera dem i din weblsäare",
|
||||
"alert_notification_ios_install_required_title": "iOS installation krävs",
|
||||
"notifications_actions_failed_notification": "Misslyckad åtgärd",
|
||||
"alert_notification_permission_denied_title": "Notifieringar är blockerade",
|
||||
"alert_notification_ios_install_required_description": "Klicka på delaikonen och Lägg till på hemskärmen för att aktivera notifieringarna i iOS"
|
||||
}
|
||||
|
|
|
@ -60,8 +60,8 @@
|
|||
"nav_button_documentation": "Belgelendirme",
|
||||
"nav_button_publish_message": "Bildirim yayınla",
|
||||
"alert_notification_permission_required_title": "Bildirimler devre dışı",
|
||||
"alert_notification_permission_required_description": "Tarayıcınıza masaüstü bildirimlerini görüntüleme izni verin.",
|
||||
"alert_not_supported_description": "Tarayıcınızda bildirimler desteklenmiyor.",
|
||||
"alert_notification_permission_required_description": "Tarayıcınıza masaüstü bildirimlerini görüntüleme izni verin",
|
||||
"alert_not_supported_description": "Tarayıcınızda bildirimler desteklenmiyor",
|
||||
"notifications_copied_to_clipboard": "Panoya kopyalandı",
|
||||
"notifications_tags": "Etiketler",
|
||||
"notifications_attachment_copy_url_title": "Ek URL'sini panoya kopyala",
|
||||
|
@ -380,5 +380,28 @@
|
|||
"account_basics_phone_numbers_dialog_code_label": "Doğrulama kodu",
|
||||
"account_basics_phone_numbers_dialog_code_placeholder": "örn. 123456",
|
||||
"account_usage_calls_title": "Yapılan telefon aramaları",
|
||||
"account_upgrade_dialog_tier_features_no_calls": "Telefon araması yok"
|
||||
"account_upgrade_dialog_tier_features_no_calls": "Telefon araması yok",
|
||||
"action_bar_mute_notifications": "Bildirimleri sessize al",
|
||||
"action_bar_unmute_notifications": "Bildirimlerin sesini aç",
|
||||
"alert_notification_permission_denied_title": "Bildirimler engellendi",
|
||||
"alert_notification_permission_denied_description": "Lütfen tarayıcınızda yeniden etkinleştirin",
|
||||
"alert_notification_ios_install_required_title": "iOS kurulumu gerekli",
|
||||
"alert_notification_ios_install_required_description": "iOS'ta bildirimleri etkinleştirmek için Paylaş simgesine ve Ana Ekrana Ekle'ye tıklayın",
|
||||
"notifications_actions_failed_notification": "Başarısız eylem",
|
||||
"publish_dialog_checkbox_markdown": "Markdown olarak biçimlendir",
|
||||
"prefs_notifications_web_push_title": "Arka plan bildirimleri",
|
||||
"prefs_notifications_web_push_enabled_description": "Web uygulaması çalışmadığında bile bildirimler alınır (Web Push aracılığıyla)",
|
||||
"prefs_notifications_web_push_disabled_description": "Web uygulaması çalışırken bildirim alınır (WebSocket aracılığıyla)",
|
||||
"prefs_notifications_web_push_enabled": "{{server}} için etkinleştirildi",
|
||||
"prefs_notifications_web_push_disabled": "Devre dışı",
|
||||
"prefs_appearance_theme_title": "Tema",
|
||||
"prefs_appearance_theme_system": "Sistem (öntanımlı)",
|
||||
"prefs_appearance_theme_dark": "Koyu mod",
|
||||
"prefs_appearance_theme_light": "Açık mod",
|
||||
"error_boundary_button_reload_ntfy": "ntfy'yi yeniden yükle",
|
||||
"web_push_subscription_expiring_title": "Bildirimler duraklatılacak",
|
||||
"web_push_subscription_expiring_body": "Bildirimleri almaya devam etmek için ntfy'yi açın",
|
||||
"web_push_unknown_notification_title": "Sunucudan bilinmeyen bildirim alındı",
|
||||
"web_push_unknown_notification_body": "Web uygulamasını açarak ntfy'yi güncellemeniz gerekebilir",
|
||||
"subscribe_dialog_subscribe_use_another_background_info": "Web uygulaması açık değilken diğer sunuculardan gelen bildirimler alınmayacaktır"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue