Skip to content

Commit

Permalink
Merge pull request #103 from till/mastodon
Browse files Browse the repository at this point in the history
Add support for mastodon
  • Loading branch information
ezeoleaf authored Dec 22, 2022
2 parents a80c92e + 78625ac commit 5b87bb3
Show file tree
Hide file tree
Showing 9 changed files with 302 additions and 6 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ GobotTweet
bin/
coverage.out
.env
.envrc
larry-build
21 changes: 21 additions & 0 deletions PublishersAndProviders.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Providers and Publishers that are currently supported

- Twitter
- Github
- Mastodon


Before running the bot, you must configure it so that it can connect to the current supported platforms
Expand All @@ -31,6 +32,15 @@ Twitter
- TWITTER_ACCESS_TOKEN
- TWITTER_ACCESS_SECRET

Mastodon
----------------------------------------------------------------------
- MASTODON_SERVER (the instance for your (bot) account)
- MASTODON_CLIENT_ID (the application's client id)
- MASTODON_CLIENT_SECRET (the application's secret)
- MASTODON_ACCESS_TOKEN (the access token)
- MASTODON_ACCOUNT (optional, without access token)
- MASTODON_PASSWIRD (optional, without access token)

How to setup the environment variables for the platforms
----------------------------------------------------------------------

Expand Down Expand Up @@ -63,3 +73,14 @@ For further information click [here](https://docs.github.com/en/authentication/k
Twitter
----------------------------------------------------------------------
For getting Twitter keys and secrets click [here](https://developer.twitter.com/en/docs/twitter-api/getting-started/guide)


Mastodon
----------------------------------------------------------------------

Register an application on your instance:

- Redirect URI: urn:ietf:wg:oauth:2.0:oob
- Scopes: read:statuses, write:statuses

Capture: ID, Client ID, Client Secret and Access Token.
33 changes: 28 additions & 5 deletions cmd/larry/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"fmt"
"log"
"os"
"strings"
Expand All @@ -16,8 +17,10 @@ import (
"github.com/ezeoleaf/larry/provider/github"
"github.com/ezeoleaf/larry/publisher"
githubPub "github.com/ezeoleaf/larry/publisher/github"
mastodonPub "github.com/ezeoleaf/larry/publisher/mastodon"
"github.com/ezeoleaf/larry/publisher/twitter"
"github.com/go-redis/redis/v8"
"github.com/mattn/go-mastodon"
"github.com/urfave/cli/v2"
)

Expand All @@ -33,6 +36,13 @@ var (
twitterConsumerSecret = envString("TWITTER_CONSUMER_SECRET", "")
twitterAccessToken = envString("TWITTER_ACCESS_TOKEN", "")
twitterAccessSecret = envString("TWITTER_ACCESS_SECRET", "")

mastodonClientId = envString("MASTODON_CLIENT_ID", "")
mastodonClientSecret = envString("MASTODON_CLIENT_SECRET", "")
mastodonServer = envString("MASTODON_SERVER", "")
mastodonAccessToken = envString("MASTODON_ACCESS_TOKEN", "")
mastodonAccount = envString("MASTODON_ACCOUNT", "")
mastodonPassword = envString("MASTODON_PASSWORD", "")
)

func main() {
Expand All @@ -49,24 +59,25 @@ func main() {
{Name: "@kannav02"},
{Name: "@siddhant-k-code", Email: "siddhantkhare2694@gmail.com"},
{Name: "@savagedev"},
{Name: "@till"},
},
Action: func(c *cli.Context) error {
prov, err := getProvider(cfg)
if err != nil {
log.Fatal(err)
return err
}

if prov == nil {
log.Fatalf("could not initialize provider for %v", cfg.Provider)
return fmt.Errorf("could not initialize provider for %v", cfg.Provider)
}

pubs, err := getPublishers(cfg)
if err != nil {
log.Fatal(err)
return fmt.Errorf("error initializing publishers: %w", err)
}

if len(pubs) == 0 {
log.Fatalln("no publishers initialized")
return fmt.Errorf("no publishers initialized")
}

s := larry.Service{Provider: prov, Publishers: pubs, Logger: log.Default()}
Expand Down Expand Up @@ -114,6 +125,7 @@ func getProvider(cfg config.Config) (larry.Provider, error) {

func getPublishers(cfg config.Config) (map[string]larry.Publisher, error) {
pubs := make(map[string]larry.Publisher)
var err error

ps := strings.Split(cfg.Publishers, ",")

Expand All @@ -134,10 +146,21 @@ func getPublishers(cfg config.Config) (map[string]larry.Publisher, error) {
pubs[v] = twitter.NewPublisher(accessKeys, cfg)
} else if v == publisher.Github {
pubs[v] = githubPub.NewPublisher(githubAccessToken, cfg, githubPublishRepoOwner, githubPublishRepoName, githubPublishRepoFile)
} else if v == publisher.Mastodon {
pubs[v], err = mastodonPub.NewPublisher(mastodonPub.PublisherConfig{
ClientCfg: &mastodon.Config{
Server: mastodonServer,
ClientID: mastodonClientId,
ClientSecret: mastodonClientSecret,
AccessToken: mastodonAccessToken,
},
Username: mastodonAccount,
Password: mastodonPassword,
}, cfg)
}
}

return pubs, nil
return pubs, err
}

func envString(key string, fallback string) string {
Expand Down
6 changes: 5 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ services:
- TWITTER_CONSUMER_SECRET=${TWITTER_CONSUMER_SECRET}
- TWITTER_ACCESS_TOKEN=${TWITTER_ACCESS_TOKEN}
- TWITTER_ACCESS_SECRET=${TWITTER_ACCESS_SECRET}
entrypoint: sh -c "/larry --topic golang -x 1 --safe-mode"
- MASTODON_CLIENT_ID=${MASTODON_CLIENT_ID}
- MASTODON_CLIENT_SECRET=${MASTODON_CLIENT_SECRET}
- MASTODON_SERVER=${MASTODON_SERVER}
- MASTODON_ACCESS_TOKEN=${MASTODON_ACCESS_TOKEN}
entrypoint: sh -c "/larry --publisher=mastodon --topic golang -x 1 --safe-mode"
depends_on:
- redis
# ----
Expand Down
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,19 @@ require (
github.com/dghubble/oauth1 v0.7.1
github.com/go-redis/redis/v8 v8.11.5
github.com/google/go-github/v39 v39.2.0
github.com/stretchr/testify v1.7.2
github.com/urfave/cli/v2 v2.23.6
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb
)

require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

require (
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
Expand All @@ -22,6 +31,7 @@ require (
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.7 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/mattn/go-mastodon v0.0.6
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 // indirect
Expand Down
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,21 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-mastodon v0.0.6 h1:lqU1sOeeIapaDsDUL6udDZIzMb2Wqapo347VZlaOzf0=
github.com/mattn/go-mastodon v0.0.6/go.mod h1:cg7RFk2pcUfHZw/IvKe1FUzmlq5KnLFqs7eV2PHplV8=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
Expand All @@ -148,6 +154,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
github.com/urfave/cli/v2 v2.23.6 h1:iWmtKD+prGo1nKUtLO0Wg4z9esfBM4rAV4QRLQiEmJ4=
github.com/urfave/cli/v2 v2.23.6/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
Expand Down Expand Up @@ -412,13 +421,15 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
Expand Down
2 changes: 2 additions & 0 deletions publisher/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ const (
Twitter = "twitter"
// Github is the value of the valid publisher
Github = "github"
// Mastodon is the value of the valid publisher
Mastodon = "mastodon"
)
110 changes: 110 additions & 0 deletions publisher/mastodon/publisher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package mastodon

import (
"context"
"fmt"
"log"
"strings"

"github.com/ezeoleaf/larry/config"
"github.com/ezeoleaf/larry/domain"
"github.com/mattn/go-mastodon"
)

// Publisher represents the publisher client
type Publisher struct {
Client *mastodon.Client
Config config.Config
capabilities map[string]int
ctx context.Context
}

// AccessKeys represents the keys and tokens needed for comunication with the client
type PublisherConfig struct {
ClientCfg *mastodon.Config
Username string
Password string
}

// NewPublisher returns a new publisher
func NewPublisher(pCfg PublisherConfig, cfg config.Config) (Publisher, error) {
log.Print("New Mastodon Publisher")

var err error

ctx := context.Background()

client := mastodon.NewClient(pCfg.ClientCfg)

// authenticate with username and password when necessary
if len(pCfg.ClientCfg.AccessToken) == 0 {
err = client.Authenticate(ctx, pCfg.Username, pCfg.Password)
}

p := Publisher{
Config: cfg,
Client: client,
ctx: ctx,
capabilities: map[string]int{
// this seems to be a default, but it would be nice to discover it later
// see: https://github.com/mattn/go-mastodon/pull/167
"max_characters": 500,
},
}

return p, err
}

func (p Publisher) prepareToot(content *domain.Content) *mastodon.Toot {
var status, spoilerText string

if len(*content.Title) > 0 {
if len(*content.URL) > 0 {
status = fmt.Sprintf("%s\n%s\n\n", *content.Title, *content.URL)
} else {
status = *content.Title
}
}

if len(*content.Subtitle) > 0 {
spoilerText = *content.Subtitle
}

if len(content.ExtraData) > 0 {
status = status + strings.Join(content.ExtraData, "\n")
}

// using the '3' for ...
if len(status) > (p.capabilities["max_characters"]) {
status = status[0:(p.capabilities["max_characters"]-3)] + "..."
}

return &mastodon.Toot{
Status: status,
Visibility: mastodon.VisibilityPublic,
Sensitive: false,
Language: p.Config.Language,
SpoilerText: spoilerText,
}
}

func (p Publisher) PublishContent(content *domain.Content) (bool, error) {

status := p.prepareToot(content)

if p.Config.SafeMode {
log.Print("Running in Safe Mode")
log.Print(content)
log.Print(status)
return true, nil
}

_, err := p.Client.PostStatus(p.ctx, status)
if err != nil {
log.Print(err)
return false, err
}

log.Println("Content Published")
return true, nil
}
Loading

0 comments on commit 5b87bb3

Please sign in to comment.