diff --git a/cmd/db640bot/main.go b/cmd/db640bot/main.go new file mode 100644 index 0000000..2c6526a --- /dev/null +++ b/cmd/db640bot/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "flag" + "log" + + "git.1750studios.com/ToddShepard/DB640/internal/config" + "git.1750studios.com/ToddShepard/DB640/internal/twitter" +) + +func main() { + cfg := flag.String("c", "", "Config file") + flag.Parse() + if *cfg == "" { + log.Fatalf("Config file must not be empty!") + } + + log.Println("Hello World!") + + config.LoadConfig(*cfg) + twitter.Init() + stream, err := twitter.GetStreamForTag("#NationalPuppyDay") + if err != nil { + log.Fatalf("Could not establish twitter stream: %+v", err) + } + + twitter.StreamDemux(stream, showTweet) +} + +func showTweet(tweet *twitter.Tweet) { + log.Printf("%s\n", tweet.Text) +} diff --git a/go.mod b/go.mod index 246824d..9c5aa2b 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,9 @@ module git.1750studios.com/ToddShepard/DB640 go 1.14 -require github.com/jinzhu/gorm v1.9.12 +require ( + github.com/BurntSushi/toml v0.3.1 + github.com/dghubble/go-twitter v0.0.0-20190719072343-39e5462e111f + github.com/dghubble/oauth1 v0.6.0 + github.com/jinzhu/gorm v1.9.12 +) diff --git a/go.sum b/go.sum index d6a0c30..fc63fdc 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,17 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY= +github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dghubble/go-twitter v0.0.0-20190719072343-39e5462e111f h1:M2wB039zeS1/LZtN/3A7tWyfctiOBL4ty5PURBmDdWU= +github.com/dghubble/go-twitter v0.0.0-20190719072343-39e5462e111f/go.mod h1:xfg4uS5LEzOj8PgZV7SQYRHbG7jPUnelEiaAVJxmhJE= +github.com/dghubble/oauth1 v0.6.0 h1:m1yC01Ohc/eF38jwZ8JUjL1a+XHHXtGQgK+MxQbmSx0= +github.com/dghubble/oauth1 v0.6.0/go.mod h1:8pFdfPkv/jr8mkChVbNVuJ0suiHe278BtWI4Tk1ujxk= +github.com/dghubble/sling v1.3.0 h1:pZHjCJq4zJvc6qVQ5wN1jo5oNZlNE0+8T/h0XeXBUKU= +github.com/dghubble/sling v1.3.0/go.mod h1:XXShWaBWKzNLhu2OxikSNFrlsvowtz4kyRuXUG7oQKY= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= @@ -7,6 +19,8 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q= github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -17,6 +31,11 @@ github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw= github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd h1:GGJVjV8waZKRHrgwvtH66z9ZGVurTD1MT0n1Bb+q4aM= diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..1bb9c86 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,58 @@ +package config + +import ( + "bytes" + "errors" + "io/ioutil" + "log" + "os" + + "github.com/BurntSushi/toml" +) + +// Config main type +type Config struct { + Twitter Twitter +} + +// Twitter related config +type Twitter struct { + ConsumerKey string + ConsumerSecret string + AccessKey string + AccessSecret string +} + +// C holds the loaded configuration +var C Config + +// LoadDefaults puts default values into C +func LoadDefaults() { + C.Twitter.AccessKey = "ACCESSKEY" + C.Twitter.AccessSecret = "ACCESSSECRET" + C.Twitter.ConsumerKey = "CONSUMERKEY" + C.Twitter.ConsumerSecret = "CONSUMERSECRET" +} + +// LoadConfig loads the configuration from given path +func LoadConfig(path string) error { + _, err := toml.DecodeFile(path, &C) + if errors.Is(err, os.ErrNotExist) { + log.Printf("Could not find file \"%s\", using defaults!", path) + LoadDefaults() + err = WriteConfig(path) + return err + } + return err +} + +// WriteConfig writes the configuration to the given path +func WriteConfig(path string) error { + buf := new(bytes.Buffer) + err := toml.NewEncoder(buf).Encode(C) + if err != nil { + return err + } + err = ioutil.WriteFile(path, buf.Bytes(), 0644) + return err +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000..81f36bc --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,41 @@ +package config + +import ( + "os" + "path/filepath" + "testing" +) + +func TestLoadConfigNonExistent(t *testing.T) { + err := LoadConfig(filepath.Join("..", "..", "test", "data", "doesnotexist.toml")) + if err != nil { + t.Errorf("Cannot write defaults, error: %+v", err) + } +} + +func TestWriteConfig(t *testing.T) { + C.Twitter.AccessKey = "§OneNiceKey" + C.Twitter.AccessSecret = "SuperNiceSecret" + C.Twitter.ConsumerKey = "ConsumeMe" + C.Twitter.ConsumerSecret = "EatASecret" + err := WriteConfig(filepath.Join("..", "..", "test", "data", "doesnotexist.toml")) + if err != nil { + t.Errorf("Could not write config, got error: %+v", err) + } +} + +func TestLoadConfig(t *testing.T) { + C = Config{} + err := LoadConfig(filepath.Join("..", "..", "test", "data", "doesnotexist.toml")) + if err != nil { + t.Errorf("Cannot read config, error: %+v", err) + } + if C.Twitter.AccessKey != "§OneNiceKey" || C.Twitter.AccessSecret != "SuperNiceSecret" || C.Twitter.ConsumerKey != "ConsumeMe" || C.Twitter.ConsumerSecret != "EatASecret" { + t.Errorf("Could not read config, entries wrong") + } + t.Cleanup(CleanupConfigTest) +} + +func CleanupConfigTest() { + os.Remove(filepath.Join("..", "..", "test", "data", "doesnotexist.toml")) +} diff --git a/internal/twitter/twitter.go b/internal/twitter/twitter.go new file mode 100644 index 0000000..b51bc5e --- /dev/null +++ b/internal/twitter/twitter.go @@ -0,0 +1,38 @@ +package twitter + +import ( + "git.1750studios.com/ToddShepard/DB640/internal/config" + "github.com/dghubble/go-twitter/twitter" + "github.com/dghubble/oauth1" +) + +// Client holds the authenticated twitter client +var Client *twitter.Client + +// Tweet as in twitter.Tweet +type Tweet = twitter.Tweet + +// Init initzializes the twitter client +func Init() { + conf := oauth1.NewConfig(config.C.Twitter.ConsumerKey, config.C.Twitter.ConsumerSecret) + token := oauth1.NewToken(config.C.Twitter.AccessKey, config.C.Twitter.AccessSecret) + httpClient := conf.Client(oauth1.NoContext, token) + + Client = twitter.NewClient(httpClient) +} + +// GetStreamForTag returns a stream object for a specified hashtag +func GetStreamForTag(hashtag string) (*twitter.Stream, error) { + params := &twitter.StreamFilterParams{ + Track: []string{hashtag}, + StallWarnings: twitter.Bool(true), + } + return Client.Streams.Filter(params) +} + +// StreamDemux sets callback for incoming messages +func StreamDemux(stream *twitter.Stream, cb func(*Tweet)) { + demux := twitter.NewSwitchDemux() + demux.Tweet = cb + demux.HandleChan(stream.Messages) +}