Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,13 @@ require (
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-mastodon v0.0.11 // indirect
github.com/oapi-codegen/runtime v1.1.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/rs/zerolog v1.34.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e // indirect
go.mau.fi/util v0.9.5 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-mastodon v0.0.11 h1:Zcvc/8EHpf3os1mwAuUUB5es5VnfVdAeb4ed6ByJnCY=
github.com/mattn/go-mastodon v0.0.11/go.mod h1:0DcwYEkqigrvknMvjmfKXLP0vYyeYm+vBdUOvoHcczg=
github.com/mileusna/viber v1.0.1 h1:gWB6/lKoWYVxkH0Jb8jRnGIRZ/9DEM7RBZRJHRfdYWs=
github.com/mileusna/viber v1.0.1/go.mod h1:Pxu/iPMnYjnHgu+bEp3SiKWHWmlf/kDp/yOX8XUdYrQ=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
Expand Down Expand Up @@ -339,6 +341,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e h1:tD38/4xg4nuQCASJ/JxcvCHNb46w0cdAaJfkzQOO1bA=
github.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e/go.mod h1:krvJ5AY/MjdPkTeRgMYbIDhbbbVvnPQPzsIsDJO8xrY=
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 h1:5u+EJUQiosu3JFX0XS0qTf5FznsMOzTjGqavBGuCbo0=
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2/go.mod h1:4kyMkleCiLkgY6z8gK5BkI01ChBtxR0ro3I1ZDcGM3w=
github.com/ttacon/libphonenumber v1.2.1 h1:fzOfY5zUADkCkbIafAed11gL1sW+bJ26p6zWLBMElR4=
Expand Down
34 changes: 34 additions & 0 deletions service/mastodon/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
Package mastodon provides message notification integration for Mastodon.

Usage:

package main

import (
"context"
"log"

"github.com/nikoksr/notify"
"github.com/nikoksr/notify/service/mastodon"
)

func main() {
mastodonSvc := mastodon.New(
"https://mastodon.social",
"your-client-id",
"your-client-secret",
"your-access-token",
)

mastodonSvc.AddReceivers("@user@mastodon.social")

notifier := notify.New()
notifier.UseServices(mastodonSvc)

if err := notifier.Send(context.Background(), "Subject", "Message"); err != nil {
log.Fatal(err)
}
}
*/
package mastodon
69 changes: 69 additions & 0 deletions service/mastodon/mastodon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package mastodon

import (
"context"
"fmt"

"github.com/mattn/go-mastodon"
)

//go:generate mockery --name=mastodonClient --output=. --case=underscore --inpackage
type mastodonClient interface {
PostStatus(ctx context.Context, toot *mastodon.Toot) (*mastodon.Status, error)
}

// Compile-time check
var _ mastodonClient = new(mastodon.Client)

// Mastodon struct holds necessary data to communicate with the Mastodon API.
type Mastodon struct {
client mastodonClient
recipients []string
}

// New returns a new instance of a Mastodon notification service.
// serverURL is the Mastodon instance URL (e.g. "https://mastodon.social").
// clientID, clientSecret, and accessToken are OAuth2 credentials.
func New(serverURL, clientID, clientSecret, accessToken string) *Mastodon {
client := mastodon.NewClient(&mastodon.Config{
Server: serverURL,
ClientID: clientID,
ClientSecret: clientSecret,
AccessToken: accessToken,
})

return &Mastodon{
client: client,
recipients: []string{},
}
}

// AddReceivers takes Mastodon usernames (e.g. "@user@instance.social") and adds them to the
// internal recipient list. The Send method will send a direct message to all recipients.
func (m *Mastodon) AddReceivers(usernames ...string) {
m.recipients = append(m.recipients, usernames...)
}

// Send takes a message subject and a message body and sends them as a direct message
// to all previously set recipients. Each recipient is mentioned in a separate direct toot.
func (m Mastodon) Send(ctx context.Context, subject, message string) error {
fullMessage := subject + "\n" + message

for _, recipient := range m.recipients {
select {
case <-ctx.Done():
return ctx.Err()
default:
toot := &mastodon.Toot{
Status: recipient + " " + fullMessage,
Visibility: "direct",
}
_, err := m.client.PostStatus(ctx, toot)
if err != nil {
return fmt.Errorf("send message to Mastodon recipient %q: %w", recipient, err)
}
}
}

return nil
}
37 changes: 37 additions & 0 deletions service/mastodon/usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Mastodon Usage

Ensure you have a Mastodon account and have created an application
to obtain your client ID, client secret, and access token.

## Sample Code

```go
package main

import (
"context"
"log"

"github.com/nikoksr/notify"
"github.com/nikoksr/notify/service/mastodon"
)

func main() {
mastodonSvc := mastodon.New(
"https://mastodon.social", // Your instance URL
"your-client-id",
"your-client-secret",
"your-access-token",
)

mastodonSvc.AddReceivers("@user@mastodon.social")

notifier := notify.New()
notifier.UseServices(mastodonSvc)

err := notifier.Send(context.Background(), "Subject", "Message body")
if err != nil {
log.Fatal(err)
}
}
```
Loading