diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9605567 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + + +twitterConfig.json +matrixConfig.json +temp diff --git a/README.md b/README.md new file mode 100644 index 0000000..d16c745 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# twitterDM-to-matrix [![Go Report Card](https://goreportcard.com/badge/github.com/arnaucode/twitterDM-to-matrix)](https://goreportcard.com/report/github.com/arnaucode/twitterDM-to-matrix) + +bridge to send the received twitter Direct Messages (https://twitter.com) to a Matrix room (https://matrix.org), written in Go + + +needs a twitterConfig.json file on the /build folder with the content: +``` +{ + "consumer_key": "xxxxxxxxxxxxxxxx", + "consumer_secret": "xxxxxxxxxxxxxxxx", + "access_token_key": "xxxxxxxxxxxxxxxx", + "access_token_secret": "xxxxxxxxxxxxxxxx" +} + +``` +and a matrixConfig.json file on the /build folder with the content: +``` +{ + "room_id": "xxxxx", + "user": "xxxxx", + "password": "xxxxx", + "server": "https://xxxxx.xxxxx" +} + +``` + +to run it: +- go to the /build folder +- open terminal +- execute the script with: +``` +./twitterDM-to-matrix +``` diff --git a/build/matrixConfigDEMO.json b/build/matrixConfigDEMO.json new file mode 100644 index 0000000..7407d35 --- /dev/null +++ b/build/matrixConfigDEMO.json @@ -0,0 +1,6 @@ +{ + "room_id": "xxxxx", + "user": "xxxxx", + "password": "xxxxx", + "server": "https://xxxxx.xxxxx" +} diff --git a/build/twitterConfigDEMO.json b/build/twitterConfigDEMO.json new file mode 100644 index 0000000..a6ffeab --- /dev/null +++ b/build/twitterConfigDEMO.json @@ -0,0 +1,7 @@ +{ + "screenName": "xxxxx", + "consumer_key": "xxxxx", + "consumer_secret": "xxxxx", + "access_token": "xxxxx", + "access_token_secret": "xxxxx" +} diff --git a/build/twitterDM-to-matrix b/build/twitterDM-to-matrix new file mode 100755 index 0000000..e3d555a Binary files /dev/null and b/build/twitterDM-to-matrix differ diff --git a/color.go b/color.go new file mode 100644 index 0000000..e945ac5 --- /dev/null +++ b/color.go @@ -0,0 +1,57 @@ +package main + +import "fmt" + +//Color struct, defines the color +type Color struct{} + +var c Color + +//DarkGray color +func (c Color) DarkGray(t string) { + fmt.Print("\x1b[30;1m") //dark gray + fmt.Println(t) + fmt.Print("\x1b[0m") //defaultColor +} + +//Red color +func (c Color) Red(t string) { + fmt.Print("\x1b[31;1m") //red + fmt.Println(t) + fmt.Print("\x1b[0m") //defaultColor +} + +//Green color +func (c Color) Green(t string) { + fmt.Print("\x1b[32;1m") //green + fmt.Println(t) + fmt.Print("\x1b[0m") //defaultColor +} + +//Yellow color +func (c Color) Yellow(t string) { + fmt.Print("\x1b[33;1m") //yellow + fmt.Println(t) + fmt.Print("\x1b[0m") //defaultColor +} + +//Blue color +func (c Color) Blue(t string) { + fmt.Print("\x1b[34;1m") //blue + fmt.Println(t) + fmt.Print("\x1b[0m") //defaultColor +} + +//Purple color +func (c Color) Purple(t string) { + fmt.Print("\x1b[35;1m") //purple + fmt.Println(t) + fmt.Print("\x1b[0m") //defaultColor +} + +//Cyan color +func (c Color) Cyan(t string) { + fmt.Print("\x1b[36;1m") //cyan + fmt.Println(t) + fmt.Print("\x1b[0m") //defaultColor +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..ea9ee02 --- /dev/null +++ b/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" +) + +const version = "0.1-dev" + +func main() { + c.Green("twitterDM-to-matrix") + c.Yellow("version: " + version) + c.Green("bridge twitter DM to Matrix") + fmt.Println("configuring") + + c.Purple("------matrix------") + readMatrixConfig() + loginMatrix() + fmt.Print("matrix token: ") + c.Cyan(matrixToken.AccessToken) + fmt.Println("") + + c.Purple("------twitter------") + client := readTwitterConfigTokensAndConnect() + user := getUserData(client) + printTwitterUserData(user) + fmt.Println("") + + c.Purple("------starting to listen------") + + stream(client) +} diff --git a/matrix.go b/matrix.go new file mode 100644 index 0000000..47a01f5 --- /dev/null +++ b/matrix.go @@ -0,0 +1,86 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "math/rand" + "net/http" + "strconv" + "strings" +) + +//MatrixConfig stores the data from json matrixConfig.json file +type MatrixConfig struct { + RoomId string `json:"room_id"` + User string `json:"user"` + Password string `json:"password"` + Server string `json:"server"` +} + +//MatrixToken stores the token data from matrix +type MatrixToken struct { + AccessToken string `json:"access_token"` + Server string `json:"server"` + UserId string `json:"user_id"` + DeviceId string `json:"device_id"` +} + +var matrixConfig MatrixConfig +var matrixToken MatrixToken + +func readMatrixConfig() { + file, e := ioutil.ReadFile("matrixConfig.json") + if e != nil { + fmt.Println("error:", e) + } + content := string(file) + json.Unmarshal([]byte(content), &matrixConfig) +} + +func loginMatrix() { + url := matrixConfig.Server + "/_matrix/client/r0/login" + jsonStr := `{ + "type":"m.login.password", + "user":"` + matrixConfig.User + `", + "password":"` + matrixConfig.Password + `" + }` + b := strings.NewReader(jsonStr) + req, _ := http.NewRequest("POST", url, b) + req.Header.Set("Content-Type", "application/json") + res, err := http.DefaultClient.Do(req) + if err != nil { + log.Println(err) + } + defer res.Body.Close() + body, _ := ioutil.ReadAll(res.Body) + + json.Unmarshal([]byte(body), &matrixToken) + +} +func matrixSendMsg(senderScreenName string, message string, createdAt string) { + txnId := strconv.Itoa(rand.Int()) + c.Green(txnId) + url := matrixConfig.Server + "/_matrix/client/r0/rooms/" + matrixConfig.RoomId + "/send/m.room.message/" + txnId + "?access_token=" + matrixToken.AccessToken + jsonStr := `{ + "body":"[NEW DM] - at ` + createdAt + `\n@` + senderScreenName + `: ` + message + `", + "msgtype":"m.text" + }` + b := strings.NewReader(jsonStr) + req, _ := http.NewRequest("PUT", url, b) + req.Header.Set("Content-Type", "application/json") + res, err := http.DefaultClient.Do(req) + if err != nil { + log.Println(err) + } + defer res.Body.Close() + body, _ := ioutil.ReadAll(res.Body) + + fmt.Println(string(body)) + + fmt.Println(createdAt) + fmt.Print("received DM sent to Matrix: ") + c.Green(message) + +} diff --git a/twitterConfigClient.go b/twitterConfigClient.go new file mode 100644 index 0000000..45f28fd --- /dev/null +++ b/twitterConfigClient.go @@ -0,0 +1,43 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + + "github.com/dghubble/go-twitter/twitter" + "github.com/dghubble/oauth1" +) + +//TwitterConfig stores the data from json twitterConfig.json file +type TwitterConfig struct { + ScreenName string `json:"screenName"` + ConsumerKey string `json:"consumer_key"` + ConsumerSecret string `json:"consumer_secret"` + AccessToken string `json:"access_token"` + AccessTokenSecret string `json:"access_token_secret"` +} + +var twitterConfig TwitterConfig + +func readTwitterConfigTokensAndConnect() (client *twitter.Client) { + + file, e := ioutil.ReadFile("twitterConfig.json") + if e != nil { + fmt.Println("error:", e) + } + content := string(file) + json.Unmarshal([]byte(content), &twitterConfig) + fmt.Println("twitterConfig.json read comlete") + + fmt.Print("connecting to twitter api --> ") + configu := oauth1.NewConfig(twitterConfig.ConsumerKey, twitterConfig.ConsumerSecret) + token := oauth1.NewToken(twitterConfig.AccessToken, twitterConfig.AccessTokenSecret) + httpClient := configu.Client(oauth1.NoContext, token) + // twitter client + client = twitter.NewClient(httpClient) + + fmt.Println("connection successful") + + return client +} diff --git a/twitterStreamDM.go b/twitterStreamDM.go new file mode 100644 index 0000000..af4a95e --- /dev/null +++ b/twitterStreamDM.go @@ -0,0 +1,46 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/signal" + "syscall" + + "github.com/dghubble/go-twitter/twitter" +) + +func stream(client *twitter.Client) { + // Convenience Demux demultiplexed stream messages + demux := twitter.NewSwitchDemux() + + demux.All = func(message interface{}) { + //fmt.Println(message) + } + demux.DM = func(dm *twitter.DirectMessage) { + if dm.SenderScreenName != twitterConfig.ScreenName { + matrixSendMsg(dm.SenderScreenName, dm.Text, dm.CreatedAt) + } + } + demux.Event = func(event *twitter.Event) { + //fmt.Printf("%#v\n", event) + } + + fmt.Println("Starting Stream...") + + streamUserParams := &twitter.StreamUserParams{} + stream, err := client.Streams.User(streamUserParams) + if err != nil { + log.Fatal(err) + } + + // Receive messages until stopped or stream quits + demux.HandleChan(stream.Messages) + + ch := make(chan os.Signal) + signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) + log.Println(<-ch) + + fmt.Println("Stopping Stream...") + stream.Stop() +} diff --git a/twitterUserOperations.go b/twitterUserOperations.go new file mode 100644 index 0000000..1641712 --- /dev/null +++ b/twitterUserOperations.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + + "github.com/dghubble/go-twitter/twitter" +) + +func getUserData(client *twitter.Client) *twitter.User { + // Verify Credentials + verifyParams := &twitter.AccountVerifyParams{ + SkipStatus: twitter.Bool(true), + IncludeEmail: twitter.Bool(true), + } + user, _, _ := client.Accounts.VerifyCredentials(verifyParams) + return user +} +func printTwitterUserData(user *twitter.User) { + fmt.Print("username: ") + c.Cyan(user.Name + " @" + user.ScreenName) + if user.Email != "" { + fmt.Print("Email ") + c.Red(user.Email) + } + if user.Location != "" { + fmt.Print("Location: ") + c.Red(user.Location) + } + fmt.Print("user created on: ") + c.Cyan(user.CreatedAt) +}