Browse Source

Support sending notifications to ntfy.sh

master
Jan-Lukas Else 3 weeks ago
parent
commit
e994e5400d
  1. 6
      config.go
  2. 8
      docs/usage.md
  3. 5
      example-config.yml
  4. 10
      notifications.go
  5. 28
      ntfy.go
  6. 66
      ntfy_test.go
  7. 12
      telegram.go
  8. 2
      telegram_test.go

6
config.go

@ -258,9 +258,15 @@ type configActivityPub struct {
}
type configNotifications struct {
Ntfy *configNtfy `mapstructure:"ntfy"`
Telegram *configTelegram `mapstructure:"telegram"`
}
type configNtfy struct {
Enabled bool `mapstructure:"enabled"`
Topic string `mapstructure:"topic"`
}
type configTelegram struct {
Enabled bool `mapstructure:"enabled"`
ChatID string `mapstructure:"chatId"`

8
docs/usage.md

@ -34,4 +34,10 @@ It is possible to configure multiple compression providers. If one fails, the ne
GoBlog features a button on each post that allows you to read the post's content aloud. By default, that uses an API from the browser to generate the speech. But it's not available on all browsers and on some operating systems it sounds horrible.
There's also the possibility to configure GoBlog to use Google Cloud's Text-to-Speech API. For that take a look at the `example-config.yml` file. If configured and enabled, after publishing a post, GoBlog will automatically generate an audio file, save it to the configured media storage (local file storage by default) and safe the audio file URL to the post's `tts` parameter. After updating a post, you can manually regenerate the audio file by using the button on the post. When deleting a post or regenerating the audio, GoBlog tries to delete the old audio file as well.
There's also the possibility to configure GoBlog to use Google Cloud's Text-to-Speech API. For that take a look at the `example-config.yml` file. If configured and enabled, after publishing a post, GoBlog will automatically generate an audio file, save it to the configured media storage (local file storage by default) and safe the audio file URL to the post's `tts` parameter. After updating a post, you can manually regenerate the audio file by using the button on the post. When deleting a post or regenerating the audio, GoBlog tries to delete the old audio file as well.
## Notifications
On receiving a webmention, a new comment or a contact form submission, GoBlog will create a new notification. Notifications are displayed on `/notifications` and can be deleted by the user.
If configured, GoBlog will also send a notification using a Telegram Bot or [Ntfy.sh](https://ntfy.sh/). See the `example-config.yml` file for how to configure the notification providers.

5
example-config.yml

@ -114,8 +114,11 @@ micropub:
# Notifications
notifications:
ntfy: # Receive notifications using Ntfy.sh
enabled: true # Enable it
topic: ntfy.sh/mynotificationstopic # The topic for the notifications
telegram: # Receive notifications via Telegram
enabled: true
enabled: true # Enable it
chatId: 123456 # Telegram chat ID (usually the user id on Telegram)
botToken: BOT-TOKEN # Telegram bot token

10
notifications.go

@ -30,10 +30,12 @@ func (a *goBlog) sendNotification(text string) {
if err := a.db.saveNotification(n); err != nil {
log.Println("Failed to save notification:", err.Error())
}
if an := a.cfg.Notifications; an != nil {
_, _, err := a.send(an.Telegram, n.Text, "")
if err != nil {
log.Println("Failed to send Telegram notification:", err.Error())
if cfg := a.cfg.Notifications; cfg != nil {
if err := a.sendNtfy(cfg.Ntfy, n.Text); err != nil {
log.Println("Failed to send notification to Ntfy:", err.Error())
}
if _, _, err := a.sendTelegram(cfg.Telegram, n.Text, ""); err != nil {
log.Println("Failed to send notification to Telegram:", err.Error())
}
}
}

28
ntfy.go

@ -0,0 +1,28 @@
package main
import (
"context"
"strings"
"github.com/carlmjohnson/requests"
)
func (ntfy *configNtfy) enabled() bool {
if ntfy == nil || !ntfy.Enabled || ntfy.Topic == "" {
return false
}
return true
}
func (a *goBlog) sendNtfy(cfg *configNtfy, msg string) error {
if !cfg.enabled() {
return nil
}
return requests.
URL(cfg.Topic).
Client(a.httpClient).
UserAgent(appUserAgent).
Post().
BodyReader(strings.NewReader(msg)).
Fetch(context.Background())
}

66
ntfy_test.go

@ -0,0 +1,66 @@
package main
import (
"io"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_ntfySending(t *testing.T) {
fakeClient := newFakeHttpClient()
fakeClient.setHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {}))
app := &goBlog{
cfg: createDefaultTestConfig(t),
httpClient: fakeClient.Client,
}
app.cfg.Notifications = &configNotifications{
Ntfy: &configNtfy{
Enabled: true,
Topic: "example.com/topic",
},
}
_ = app.initConfig()
_ = app.initDatabase(false)
app.initComponents(true)
app.sendNotification("Test notification")
req := fakeClient.req
require.NotNil(t, req)
assert.Equal(t, http.MethodPost, req.Method)
assert.Equal(t, "https://example.com/topic", req.URL.String())
reqBody, _ := req.GetBody()
reqBodyByte, _ := io.ReadAll(reqBody)
assert.Equal(t, "Test notification", string(reqBodyByte))
res := fakeClient.res
require.NotNil(t, res)
assert.Equal(t, http.StatusOK, res.StatusCode)
}
func Test_ntfyConfig(t *testing.T) {
var cfg *configNtfy
assert.False(t, cfg.enabled())
cfg = &configNtfy{}
assert.False(t, cfg.enabled())
cfg.Enabled = true
assert.False(t, cfg.enabled())
cfg.Topic = "example.com/topic"
assert.True(t, cfg.enabled())
}

12
telegram.go

@ -25,7 +25,7 @@ func (a *goBlog) initTelegram() {
return
}
// Send message
chatId, msgId, err := a.send(tg, html, tgbotapi.ModeHTML)
chatId, msgId, err := a.sendTelegram(tg, html, tgbotapi.ModeHTML)
if err != nil {
log.Printf("Failed to send post to Telegram: %v", err)
return
@ -71,7 +71,7 @@ func (a *goBlog) initTelegram() {
return
}
// Send update
err = a.update(tg, chatId, messageId, html, "HTML")
err = a.updateTelegram(tg, chatId, messageId, html, "HTML")
if err != nil {
log.Printf("Failed to send update to Telegram: %v", err)
}
@ -98,7 +98,7 @@ func (a *goBlog) initTelegram() {
return
}
// Delete message
err = a.delete(tg, chatId, messageId)
err = a.deleteTelegram(tg, chatId, messageId)
if err != nil {
log.Printf("Failed to delete Telegram message: %v", err)
}
@ -134,7 +134,7 @@ func (tg *configTelegram) generateHTML(title, fullURL, shortURL string) string {
return message.String()
}
func (a *goBlog) send(tg *configTelegram, message, mode string) (int64, int, error) {
func (a *goBlog) sendTelegram(tg *configTelegram, message, mode string) (int64, int, error) {
if !tg.enabled() {
return 0, 0, nil
}
@ -156,7 +156,7 @@ func (a *goBlog) send(tg *configTelegram, message, mode string) (int64, int, err
return res.Chat.ID, res.MessageID, nil
}
func (a *goBlog) update(tg *configTelegram, chatId int64, messageId int, message, mode string) error {
func (a *goBlog) updateTelegram(tg *configTelegram, chatId int64, messageId int, message, mode string) error {
if !tg.enabled() {
return nil
}
@ -189,7 +189,7 @@ func (a *goBlog) update(tg *configTelegram, chatId int64, messageId int, message
return err
}
func (a *goBlog) delete(tg *configTelegram, chatId int64, messageId int) error {
func (a *goBlog) deleteTelegram(tg *configTelegram, chatId int64, messageId int) error {
if !tg.enabled() {
return nil
}

2
telegram_test.go

@ -67,7 +67,7 @@ func Test_configTelegram_send(t *testing.T) {
httpClient: fakeClient.Client,
}
chatId, msgId, err := app.send(tg, "Message", "HTML")
chatId, msgId, err := app.sendTelegram(tg, "Message", "HTML")
require.Nil(t, err)
assert.Equal(t, 123, msgId)

Loading…
Cancel
Save