1
0
Fork 0
Browse Source

Decouple layers 🚀

main
Daniele Tricoli 4 months ago
parent
commit
a1b10758cd
  1. 52
      cfg/environ.go
  2. 30
      cfg/environ_test.go
  3. 104
      cmd/run.go
  4. 23
      cmd/run_test.go
  5. 6
      go.mod
  6. 10
      go.sum
  7. 59
      mastodon/post.go

52
cfg/environ.go

@ -0,0 +1,52 @@
package cfg
import (
"os"
"sort"
"strconv"
"strings"
)
const (
MASTODON_ACCESS_TOKEN = "MASTODON_ACCESS_TOKEN"
MASTODON_SERVER_ADDRESS = "MASTODON_SERVER_ADDRESS"
MASTODON_TOOT_FOOTER = "MASTODON_TOOT_FOOTER"
MASTODON_TOOT_MAX_CHARACTERS = "MASTODON_TOOT_MAX_CHARACTERS"
MASTODON_TOOT_VISIBILITY = "MASTODON_TOOT_VISIBILITY"
TELEGRAM_BOT_TOKEN = "TELEGRAM_BOT_TOKEN"
TELEGRAM_CHAT_ID = "TELEGRAM_CHAT_ID"
TELEGRAM_DEBUG = "TELEGRAM_DEBUG"
)
// Check the specified Mastodon visibility and return it if valid or return
// unlisted if it's not valid.
// The specified string will be cheched case unsensitive.
func parseMastodonVisibility(s string) string {
s = strings.ToLower(s)
// Keep sorted since we search inside.
visibilities := []string{"direct", "private", "public", "unlisted"}
r := sort.SearchStrings(visibilities, s)
if r < len(visibilities) && visibilities[r] == s {
return s
}
return "unlisted"
}
// Return configured Mastodon visibility for toot.
func GetMastodonVisibility() string {
return parseMastodonVisibility(os.Getenv(MASTODON_TOOT_VISIBILITY))
}
// Parse Mastodon max characters and return 500 as default in case of errors.
func parseMastodonMaxCharacters(s string) int {
if n, err := strconv.ParseUint(s, 10, 32); err == nil {
return int(n)
}
return 500
}
func GetMastodonMaxCharacters() int {
return parseMastodonMaxCharacters(os.Getenv(MASTODON_TOOT_MAX_CHARACTERS))
}

30
cfg/environ_test.go

@ -0,0 +1,30 @@
package cfg
import (
"testing"
"github.com/alecthomas/assert"
)
func TestParseMastodonVisibility(t *testing.T) {
assert.Equal(t, parseMastodonVisibility("public"), "public")
assert.Equal(t, parseMastodonVisibility("direct"), "direct")
assert.Equal(t, parseMastodonVisibility("unlisted"), "unlisted")
assert.Equal(t, parseMastodonVisibility("private"), "private")
assert.Equal(t, parseMastodonVisibility("Public"), "public")
assert.Equal(t, parseMastodonVisibility("diRect"), "direct")
assert.Equal(t, parseMastodonVisibility("unlisTED"), "unlisted")
assert.Equal(t, parseMastodonVisibility("PRIVATE"), "private")
assert.Equal(t, parseMastodonVisibility("True"), "unlisted")
assert.Equal(t, parseMastodonVisibility("eriol"), "unlisted")
assert.Equal(t, parseMastodonVisibility(""), "unlisted")
assert.Equal(t, parseMastodonVisibility(" "), "unlisted")
}
func TestParseMastodonMaxCharacters(t *testing.T) {
assert.Equal(t, parseMastodonMaxCharacters("42"), 42)
assert.Equal(t, parseMastodonMaxCharacters("-42"), 500)
assert.Equal(t, parseMastodonMaxCharacters("hello"), 500)
}

104
cmd/run.go

@ -1,20 +1,19 @@
package cmd
import (
"context"
"fmt"
"io"
"log"
"net/http"
"os"
"sort"
"strconv"
"strings"
"github.com/cking/go-mastodon"
mastodonapi "github.com/cking/go-mastodon"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/spf13/cobra"
"noa.mornie.org/eriol/telegram-group2mastodon/cfg"
"noa.mornie.org/eriol/telegram-group2mastodon/mastodon"
"noa.mornie.org/eriol/telegram-group2mastodon/utils"
)
@ -23,7 +22,6 @@ const (
MASTODON_SERVER_ADDRESS = "MASTODON_SERVER_ADDRESS"
MASTODON_TOOT_FOOTER = "MASTODON_TOOT_FOOTER"
MASTODON_TOOT_MAX_CHARACTERS = "MASTODON_TOOT_MAX_CHARACTERS"
MASTODON_TOOT_VISIBILITY = "MASTODON_TOOT_VISIBILITY"
TELEGRAM_BOT_TOKEN = "TELEGRAM_BOT_TOKEN"
TELEGRAM_CHAT_ID = "TELEGRAM_CHAT_ID"
TELEGRAM_DEBUG = "TELEGRAM_DEBUG"
@ -38,15 +36,14 @@ var runCmd = &cobra.Command{
Every messages posted in the Telegram groups the bot is in will be posted into
the specified Mastodon account.`,
Run: func(cmd *cobra.Command, args []string) {
mastodon_instance := os.Getenv(MASTODON_SERVER_ADDRESS)
c := mastodon.NewClient(&mastodon.Config{
Server: mastodon_instance,
mastodonInstance := os.Getenv(MASTODON_SERVER_ADDRESS)
c := mastodonapi.NewClient(&mastodonapi.Config{
Server: mastodonInstance,
AccessToken: os.Getenv(MASTODON_ACCESS_TOKEN),
})
log.Println("Crating a new client for mastondon istance:", mastodon_instance)
max_characters := parseMastodonMaxCharacters(os.Getenv(MASTODON_TOOT_MAX_CHARACTERS))
allowed_telegram_chat := parseTelegramChatID(os.Getenv(TELEGRAM_CHAT_ID))
log.Println("Allowed telegram chat id:", allowed_telegram_chat)
log.Println("Crating a new client for mastondon istance:", mastodonInstance)
allowedTelegramChat := parseTelegramChatID(os.Getenv(TELEGRAM_CHAT_ID))
log.Println("Allowed telegram chat id:", allowedTelegramChat)
bot, err := tgbotapi.NewBotAPI(os.Getenv(TELEGRAM_BOT_TOKEN))
if err != nil {
@ -62,39 +59,26 @@ the specified Mastodon account.`,
for update := range updates {
chatID := update.Message.Chat.ID
if chatID != allowed_telegram_chat {
if chatID != allowedTelegramChat {
log.Printf("Error: telegram chat %d is not the allowed one: %d\n",
chatID,
allowed_telegram_chat,
allowedTelegramChat,
)
continue
}
if update.Message != nil {
messageID := update.Message.MessageID
maxChars := cfg.GetMastodonMaxCharacters()
tootVisibility := cfg.GetMastodonVisibility()
tootFooter := os.Getenv(MASTODON_TOOT_FOOTER)
if update.Message.Text != "" {
log.Printf("Text message received. Message id: %d\n", messageID)
text := update.Message.Text
in_reply_to := ""
toot_footer := os.Getenv(MASTODON_TOOT_FOOTER)
messages := utils.SplitTextAtChunk(text, max_characters, toot_footer)
for _, message := range messages {
status, err := c.PostStatus(context.Background(), &mastodon.Toot{
Status: message,
Visibility: parseMastodonVisibility(os.Getenv(MASTODON_TOOT_VISIBILITY)),
InReplyToID: mastodon.ID(in_reply_to),
})
if err != nil {
log.Printf("Could not post status: %v", err)
continue
}
log.Printf("Posted status %s", status.URL)
in_reply_to = string(status.ID)
}
messages := utils.SplitTextAtChunk(text, maxChars, tootFooter)
mastodon.PostToots(c, messages, tootVisibility)
} else if update.Message.Photo != nil {
log.Printf("Photo received. Message id: %d\n", messageID)
@ -116,32 +100,14 @@ the specified Mastodon account.`,
log.Printf("Could not download file: %v", err)
continue
}
attachment, err := c.UploadMediaFromReader(
context.Background(), file)
if err != nil {
log.Printf("Could not upload media: %v", err)
continue
}
file.Close()
log.Printf("Posted attachment %s", attachment.TextURL)
mediaIds := [...]mastodon.ID{attachment.ID}
caption := update.Message.Caption
if len(caption) > max_characters {
caption = caption[:max_characters]
}
status, err := c.PostStatus(context.Background(), &mastodon.Toot{
// Write the caption in the toot because it almost probably
// doesn't describe the image.
Status: caption,
MediaIDs: mediaIds[:],
Visibility: parseMastodonVisibility(os.Getenv(MASTODON_TOOT_VISIBILITY)),
})
if err != nil {
log.Printf("Could not post status: %v", err)
continue
}
log.Printf("Posted status %s", status.URL)
mastodon.PostPhoto(
c,
file,
update.Message.Caption,
maxChars,
tootVisibility,
)
}
}
}
@ -161,21 +127,6 @@ func parseBoolOrFalse(s string) bool {
return r
}
// Check the specified Mastodon visibility and return it if valid or return
// unlisted if it's not valid.
// The specified string will be cheched case unsensitive.
func parseMastodonVisibility(s string) string {
s = strings.ToLower(s)
// Keep sorted since we search inside.
visibilities := []string{"direct", "private", "public", "unlisted"}
r := sort.SearchStrings(visibilities, s)
if r < len(visibilities) && visibilities[r] == s {
return s
}
return "unlisted"
}
func downloadFile(url string) (io.ReadCloser, error) {
response, err := http.Get(url)
if err != nil {
@ -189,15 +140,6 @@ func downloadFile(url string) (io.ReadCloser, error) {
return response.Body, nil
}
// Parse Mastodon max characters and return 500 as default in case of errors.
func parseMastodonMaxCharacters(s string) int {
if n, err := strconv.ParseUint(s, 10, 32); err == nil {
return int(n)
}
return 500
}
func parseTelegramChatID(s string) int64 {
r, err := strconv.ParseInt(s, 10, 64)
if err != nil {

23
cmd/run_test.go

@ -15,26 +15,3 @@ func TestParseBoolOrFalse(t *testing.T) {
assert.Equal(t, parseBoolOrFalse("FALSE"), false)
assert.Equal(t, parseBoolOrFalse("false"), false)
}
func TestParseMastodonVisibility(t *testing.T) {
assert.Equal(t, parseMastodonVisibility("public"), "public")
assert.Equal(t, parseMastodonVisibility("direct"), "direct")
assert.Equal(t, parseMastodonVisibility("unlisted"), "unlisted")
assert.Equal(t, parseMastodonVisibility("private"), "private")
assert.Equal(t, parseMastodonVisibility("Public"), "public")
assert.Equal(t, parseMastodonVisibility("diRect"), "direct")
assert.Equal(t, parseMastodonVisibility("unlisTED"), "unlisted")
assert.Equal(t, parseMastodonVisibility("PRIVATE"), "private")
assert.Equal(t, parseMastodonVisibility("True"), "unlisted")
assert.Equal(t, parseMastodonVisibility("eriol"), "unlisted")
assert.Equal(t, parseMastodonVisibility(""), "unlisted")
assert.Equal(t, parseMastodonVisibility(" "), "unlisted")
}
func TestParseMastodonMaxCharacters(t *testing.T) {
assert.Equal(t, parseMastodonMaxCharacters("42"), 42)
assert.Equal(t, parseMastodonMaxCharacters("-42"), 500)
assert.Equal(t, parseMastodonMaxCharacters("hello"), 500)
}

6
go.mod

@ -3,6 +3,7 @@ module noa.mornie.org/eriol/telegram-group2mastodon
go 1.17
require (
github.com/alecthomas/assert v1.0.0
github.com/cking/go-mastodon v0.0.6
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/spf13/cobra v1.3.0
@ -10,11 +11,16 @@ require (
)
require (
github.com/alecthomas/colour v0.1.0 // indirect
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gorilla/websocket v1.4.1 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

10
go.sum

@ -51,6 +51,12 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
github.com/alecthomas/assert v1.0.0 h1:3XmGh/PSuLzDbK3W2gUbRXwgW5lqPkuqvRgeQ30FI5o=
github.com/alecthomas/assert v1.0.0/go.mod h1:va/d2JC+M7F6s+80kl/R3G7FUiW6JzUO+hPhLyJ36ZY=
github.com/alecthomas/colour v0.1.0 h1:nOE9rJm6dsZ66RGWYSFrXw461ZIt9A6+nHgL7FRrDUk=
github.com/alecthomas/colour v0.1.0/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48=
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -263,6 +269,7 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
@ -310,6 +317,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@ -536,6 +545,7 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

59
mastodon/post.go

@ -0,0 +1,59 @@
package mastodon
import (
"context"
"io"
"log"
mastodonapi "github.com/cking/go-mastodon"
)
// Post one or more toots.
func PostToots(client *mastodonapi.Client, messages []string, visibility string) {
in_reply_to := ""
for _, message := range messages {
status, err := client.PostStatus(context.Background(), &mastodonapi.Toot{
Status: message,
Visibility: visibility,
InReplyToID: mastodonapi.ID(in_reply_to),
})
if err != nil {
log.Printf("Could not post status: %v", err)
continue
}
log.Printf("Posted status %s", status.URL)
in_reply_to = string(status.ID)
}
}
// Post a photo on mastodon with caption.
func PostPhoto(
client *mastodonapi.Client,
file io.ReadCloser,
caption string,
maxCharacters int,
visibility string) {
attachment, err := client.UploadMediaFromReader(
context.Background(), file)
if err != nil {
log.Printf("Could not upload media: %v", err)
}
file.Close()
log.Printf("Posted attachment %s", attachment.TextURL)
mediaIds := [...]mastodonapi.ID{attachment.ID}
if len(caption) > maxCharacters {
caption = caption[:maxCharacters]
}
status, err := client.PostStatus(context.Background(), &mastodonapi.Toot{
// Write the caption in the toot because it almost probably
// doesn't describe the image.
Status: caption,
MediaIDs: mediaIds[:],
Visibility: visibility,
})
if err != nil {
log.Printf("Could not post status: %v", err)
}
log.Printf("Posted status %s", status.URL)
}
Loading…
Cancel
Save