├── .dockerignore ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── crosscompile.bash ├── glide.lock ├── glide.yaml ├── go.mod ├── go.sum ├── guffer.json ├── main.go ├── main_test.go └── testfiles └── auth.toml /.dockerignore: -------------------------------------------------------------------------------- 1 | vendor 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | guffer 2 | vendor 3 | target 4 | builds/** 5 | 6 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 7 | *.o 8 | *.a 9 | *.so 10 | 11 | # Folders 12 | _obj 13 | _test 14 | 15 | # Architecture specific extensions/prefixes 16 | *.[568vq] 17 | [568vq].out 18 | 19 | *.cgo1.go 20 | *.cgo2.c 21 | _cgo_defun.c 22 | _cgo_gotypes.go 23 | _cgo_export.* 24 | 25 | _testmain.go 26 | 27 | ### OSX ### 28 | .DS_Store 29 | .AppleDouble 30 | .LSOverride 31 | Icon 32 | 33 | # Thumbnails 34 | ._* 35 | 36 | # Files that might appear on external disk 37 | .Spotlight-V100 38 | .Trashes 39 | 40 | ### Windows ### 41 | # Windows image file caches 42 | Thumbs.db 43 | ehthumbs.db 44 | 45 | # Folder config file 46 | Desktop.ini 47 | 48 | # Recycle Bin used on file shares 49 | $RECYCLE.BIN/ 50 | 51 | ### SublimeText ### 52 | # SublimeText project files 53 | *.sublime-workspace 54 | *.sublime-project 55 | 56 | ### Node ### 57 | lib-cov 58 | *.seed 59 | *.log 60 | *.csv 61 | *.dat 62 | *.out 63 | *.pid 64 | *.gz 65 | 66 | pids 67 | logs 68 | results 69 | 70 | npm-debug.log 71 | node_modules 72 | 73 | ## VS Code 74 | 75 | .vscode/* 76 | !.vscode/settings.json 77 | !.vscode/tasks.json 78 | !.vscode/launch.json 79 | 80 | ## IntelliJ 81 | .idea/* 82 | *.iml 83 | 84 | *.exe 85 | *.test 86 | *.prof 87 | *.swp -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - '1.9' 4 | - '1.10' 5 | - '1.11' 6 | - tip 7 | 8 | sudo: required 9 | 10 | services: 11 | - docker 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # This is a multi-stage build. 2 | 3 | # build stage 4 | FROM golang:1.11 AS builder 5 | WORKDIR /go/src/github.com/mrichman/guffer 6 | COPY main.go . 7 | RUN go get -d -v 8 | RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . 9 | 10 | # final stage 11 | FROM scratch 12 | WORKDIR /root/ 13 | COPY --from=builder /go/src/github.com/mrichman/guffer/app . 14 | 15 | # Metadata params 16 | ARG VERSION 17 | ARG BUILD_DATE 18 | ARG VCS_URL 19 | ARG VCS_REF 20 | ARG NAME 21 | ARG VENDOR 22 | 23 | # Metadata 24 | LABEL org.label-schema.build-date=$BUILD_DATE \ 25 | org.label-schema.name=$NAME \ 26 | org.label-schema.description="Guffer" \ 27 | org.label-schema.url="https://markrichman.com" \ 28 | org.label-schema.vcs-url=https://github.com/mrichman/$VCS_URL \ 29 | org.label-schema.vcs-ref=$VCS_REF \ 30 | org.label-schema.vendor=$VENDOR \ 31 | org.label-schema.version=$VERSION \ 32 | org.label-schema.docker.schema-version="1.0" \ 33 | org.label-schema.docker.cmd="docker run --rm guffer" 34 | 35 | CMD ["./app"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mark Richman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | IMAGE := guffer 2 | VERSION := $(shell git rev-parse HEAD) 3 | BUILD_DATE := $(shell date -R) 4 | VCS_URL := $(shell basename `git rev-parse --show-toplevel`) 5 | VCS_REF := $(shell git log -1 --pretty=%h) 6 | 7 | build: 8 | docker build --rm -t ${IMAGE} --build-arg VERSION="${VERSION}" \ 9 | --build-arg BUILD_DATE="${BUILD_DATE}" \ 10 | --build-arg VCS_URL="${VCS_URL}" \ 11 | --build-arg VCS_REF="${VCS_REF}" \ 12 | --build-arg NAME="${NAME}" \ 13 | --build-arg VENDOR="${VENDOR}" . 14 | 15 | run: 16 | docker run --rm ${IMAGE} 17 | 18 | clean: 19 | docker images -q -f "dangling=true" | xargs -I {} docker rmi {} 20 | docker volume ls -q -f "dangling=true" | xargs -I {} docker volume rm {} 21 | 22 | inspect: 23 | docker inspect --format='{{range $$k, $$v := .Config.Labels}}{{$$k}}={{$$v}}{{println}}{{end}}' ${IMAGE} 24 | 25 | print: 26 | @echo VERSION=${VERSION} 27 | @echo BUILD_DATE=${BUILD_DATE} 28 | @echo VCS_URL=${VCS_URL} 29 | @echo VCS_REF=${VCS_REF} 30 | @echo IMAGE=${IMAGE}% -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Guffer 2 | 3 | [![guffer Build Status](https://travis-ci.org/mrichman/guffer.svg?branch=master)](https://travis-ci.org/mrichman/guffer) [![GoDoc](https://godoc.org/github.com/mrichman/guffer?status.svg)](https://godoc.org/github.com/mrichman/guffer) [![Go Report Card](https://goreportcard.com/badge/github.com/mrichman/guffer)](https://goreportcard.com/report/github.com/mrichman/guffer) [![Join the chat at https://gitter.im/mrichman-guffer](https://badges.gitter.im/mrichman/guffer.svg)](https://gitter.im/mrichman-guffer) [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/mrichman/guffer/blob/master/LICENSE) [![Issues](http://img.shields.io/github/issues/mrichman/guffer.svg)]( https://github.com/mrichman/guffer/issues ) 4 | 5 | 6 | Guffer tweets based on a daily schedule. If you've used tools like [Buffer](https://buffer.com) or [HootSuite](https://hootsuite.com), this will be familiar to you. 7 | 8 | ## Creating Twitter auth keys 9 | 10 | Visit [https://apps.twitter.com/app/new](https://apps.twitter.com/app/new) 11 | 12 | Set the following environment variables in either your user profile or at the command line: 13 | 14 | * CONSUMER_KEY 15 | * CONSUMER_SECRET 16 | * ACCESS_TOKEN 17 | * ACCESS_TOKEN_SECRET 18 | 19 | You can also save these auth keys in `auth.toml` file (see below). 20 | ## Defining guffer.json 21 | 22 | Guffer looks for a config file, for example `guffer.json`, which defines the schedule and status message (the tweet). Here's an example: 23 | 24 | ``` 25 | [ 26 | { 27 | "time": "15:22", 28 | "status": "Runs at 15:22 every day" 29 | }, 30 | { 31 | "time": "15:23", 32 | "status": "Runs at 15:23 every day" 33 | }, 34 | { 35 | "time": "15:26", 36 | "status": "Runs at 15:26 every day" 37 | } 38 | ] 39 | ``` 40 | ## Defining auth.toml 41 | If you prefer to save twitter auth keys in a file, you can create a `.toml` file with this data 42 | ``` 43 | ConsumerKey = "YOUR_CONSUMER_KEY" 44 | ConsumerSecret = "YOUR_CONSUMER_SECRET" 45 | AccessToken = "YOUR_ACCES_TOKEN" 46 | AccessTokenSecret = "YOUR_TOKEN_SECRET" 47 | ``` 48 | ## Running guffer 49 | 50 | From source: 51 | 52 | ``` 53 | go get github.com/mrichman/guffer 54 | cd $GOPATH/src/github.com/mrichman/guffer 55 | CONSUMER_KEY=xxxxx CONSUMER_SECRET=xxxxx ACCESS_TOKEN=xxxxx ACCESS_TOKEN_SECRET=xxxxx go run main.go guffer.json 56 | # With auth.toml 57 | go run main.go guffer.json auth.toml 58 | ``` 59 | 60 | Binary: 61 | 62 | ``` 63 | CONSUMER_KEY=xxxxx CONSUMER_SECRET=xxxxx ACCESS_TOKEN=xxxxx ACCESS_TOKEN_SECRET=xxxxx guffer guffer.json 64 | ``` 65 | or if you're using the auth.toml file 66 | ``` 67 | guffer guffer.json auth.toml 68 | ``` 69 | Guffer will print out a summary of the queued tweets, and log each tweet to the console. Quit with `Ctrl+C`. 70 | 71 | # Contributing 72 | 73 | If you find any bugs, please report them! I am also happy to accept pull requests from anyone. 74 | 75 | You can use the [GitHub issue tracker](https://github.com/mrichman/guffer/issues) to report bugs, ask questions, or suggest new features. 76 | 77 | For a more informal setting to discuss this project, you can join the [Gitter chat](https://gitter.im/mrichman/guffer). 78 | -------------------------------------------------------------------------------- /crosscompile.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Usage: ./crosscompile.bash main.go guffer 4 | 5 | # argument handling 6 | test "$1" && target="$1" # .go file to build 7 | 8 | if ! test "$target" 9 | then 10 | echo "target file required" 11 | exit 1 12 | fi 13 | 14 | binary="" # default to default 15 | test "$2" && binary="$2" # binary output 16 | 17 | platforms="darwin/386 darwin/amd64 freebsd/386 freebsd/amd64 freebsd/arm linux/386 linux/amd64 linux/arm windows/386 windows/amd64" 18 | 19 | for platform in ${platforms} 20 | do 21 | split=(${platform//\// }) 22 | goos=${split[0]} 23 | goarch=${split[1]} 24 | 25 | # ensure output file name 26 | output="$binary" 27 | test "$output" || output="$(basename $target | sed 's/\.go//')" 28 | 29 | # add exe to windows output 30 | [[ "windows" == "$goos" ]] && output="$output.exe" 31 | 32 | # set destination path for binary 33 | destination="$(dirname $target)/builds/$goos/$goarch/$output" 34 | 35 | echo "GOOS=$goos GOARCH=$goarch go build -x -o $destination $target" 36 | GOOS=$goos GOARCH=$goarch go build -x -o $destination $target 37 | done 38 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: d86eef466c6a206132f42243347d37551c30f3c96f12ccb46a878110f9b9f545 2 | updated: 2016-11-08T18:03:46.234312995-05:00 3 | imports: 4 | - name: github.com/azr/backoff 5 | version: 53511d3c733003985b0b76f733df1f4d0095ee6a 6 | - name: github.com/ChimeraCoder/anaconda 7 | version: 00ecb7b4510497dc28cd46a598007ab8fe01f889 8 | - name: github.com/ChimeraCoder/tokenbucket 9 | version: c5a927568de7aad8a58127d80bcd36ca4e71e454 10 | - name: github.com/dustin/go-jsonpointer 11 | version: ba0abeacc3dcca5b9b20f31509c46794edbc9965 12 | - name: github.com/dustin/gojson 13 | version: 2e71ec9dd5adce3b168cd0dbde03b5cc04951c30 14 | - name: github.com/garyburd/go-oauth 15 | version: da8aea9e20eda8fcb5711f929539ba9a9ecf4fb9 16 | subpackages: 17 | - oauth 18 | - name: github.com/jasonlvhit/gocron 19 | version: 42a5804d37aa0b9239b265e894a16b8edbf52d54 20 | - name: github.com/robfig/cron 21 | version: b024fc5ea0e34bc3f83d9941c8d60b0622bfaca4 22 | testImports: [] 23 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/mrichman/guffer 2 | import: 3 | - package: github.com/ChimeraCoder/anaconda 4 | - package: github.com/robfig/cron 5 | version: ^1.0.0 6 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mrichman/guffer 2 | 3 | require ( 4 | github.com/BurntSushi/toml v0.3.0 5 | github.com/ChimeraCoder/anaconda v2.0.0+incompatible 6 | github.com/ChimeraCoder/tokenbucket v0.0.0-20131201223612-c5a927568de7 7 | github.com/azr/backoff v0.0.0-20160115115103-53511d3c7330 8 | github.com/dustin/go-jsonpointer v0.0.0-20160814072949-ba0abeacc3dc 9 | github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad 10 | github.com/garyburd/go-oauth v0.0.0-20161102235315-da8aea9e20ed 11 | github.com/jasonlvhit/gocron v0.0.0-20160629181017-42a5804d37aa 12 | github.com/robfig/cron v0.0.0-20140119015047-b024fc5ea0e3 13 | ) 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY= 2 | github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/ChimeraCoder/anaconda v0.0.0-20160604233844-00ecb7b45104 h1:3wWN1vMyVltiEjkkJM1JTleRgGh3QdsX2CG3S7BacAI= 4 | github.com/ChimeraCoder/anaconda v0.0.0-20160604233844-00ecb7b45104/go.mod h1:TCt3MijIq3Qqo9SBtuW/rrM4x7rDfWqYWHj8T7hLcLg= 5 | github.com/ChimeraCoder/anaconda v2.0.0+incompatible h1:slAmCJMpjEztM42qRfhFkh3gBLHnIUUmlMK5zFd60Pc= 6 | github.com/ChimeraCoder/anaconda v2.0.0+incompatible/go.mod h1:TCt3MijIq3Qqo9SBtuW/rrM4x7rDfWqYWHj8T7hLcLg= 7 | github.com/ChimeraCoder/tokenbucket v0.0.0-20131201223612-c5a927568de7 h1:VhNF/1LOAMtJOPjNt+f785lF3H37n1b/kiinFgqL6hM= 8 | github.com/ChimeraCoder/tokenbucket v0.0.0-20131201223612-c5a927568de7/go.mod h1:b2EuEMLSG9q3bZ95ql1+8oVqzzrTNSiOQqSXWFBzxeI= 9 | github.com/azr/backoff v0.0.0-20160115115103-53511d3c7330 h1:ekDALXAVvY/Ub1UtNta3inKQwZ/jMB/zpOtD8rAYh78= 10 | github.com/azr/backoff v0.0.0-20160115115103-53511d3c7330/go.mod h1:nH+k0SvAt3HeiYyOlJpLLv1HG1p7KWP7qU9QPp2/pCo= 11 | github.com/dustin/go-jsonpointer v0.0.0-20160814072949-ba0abeacc3dc h1:tP7tkU+vIsEOKiK+l/NSLN4uUtkyuxc6hgYpQeCWAeI= 12 | github.com/dustin/go-jsonpointer v0.0.0-20160814072949-ba0abeacc3dc/go.mod h1:ORH5Qp2bskd9NzSfKqAF7tKfONsEkCarTE5ESr/RVBw= 13 | github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad h1:Qk76DOWdOp+GlyDKBAG3Klr9cn7N+LcYc82AZ2S7+cA= 14 | github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad/go.mod h1:mPKfmRa823oBIgl2r20LeMSpTAteW5j7FLkc0vjmzyQ= 15 | github.com/garyburd/go-oauth v0.0.0-20161102235315-da8aea9e20ed h1:eY7W9zmdm7kKhfHALayD8Mefyw6iV0r4xPISpV0tRtE= 16 | github.com/garyburd/go-oauth v0.0.0-20161102235315-da8aea9e20ed/go.mod h1:HfkOCN6fkKKaPSAeNq/er3xObxTW4VLeY6UUK895gLQ= 17 | github.com/jasonlvhit/gocron v0.0.0-20160629181017-42a5804d37aa h1:egYWBLOOyJ69YjvQBRB9UhXDewb4tZ+e/+5QD1WtppU= 18 | github.com/jasonlvhit/gocron v0.0.0-20160629181017-42a5804d37aa/go.mod h1:rwi/esz/h+4oWLhbWWK7f6dtmgLzxeZhnwGr7MCsTNk= 19 | github.com/robfig/cron v0.0.0-20140119015047-b024fc5ea0e3/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= 20 | -------------------------------------------------------------------------------- /guffer.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "time": "01:10", 4 | "status": "Are you Elastic Enough for Cyber Monday? http://buff.ly/2fWT7QC #saas #cloud #cybermonday" 5 | }, 6 | { 7 | "time": "01:18", 8 | "status": "Scalability: A Million Dollar Question http://buff.ly/2fWU6Qv #saas #cloud #webperf" 9 | }, 10 | { 11 | "time": "02:04", 12 | "status": "If the Customer Ain’t Happy, Ain’t Nobody Happy http://buff.ly/2ezS04a" 13 | }, 14 | { 15 | "time": "02:06", 16 | "status": "Web Application Performance and the Support Organization #saas #cloud #webperf http://buff.ly/2ezWvM1" 17 | }, 18 | { 19 | "time": "07:10", 20 | "status": "Reducing Infrastructure Costs through Application Performance Optimization http://buff.ly/2exvGbh #saas #cloud #webperf" 21 | }, 22 | { 23 | "time": "08:31", 24 | "status": "Hargo: Load Testing using .har files in Go https://t.co/bvlXEgjxVN #golang" 25 | }, 26 | { 27 | "time": "09:14", 28 | "status": "Are you Elastic Enough for Cyber Monday? http://buff.ly/2fWT7QC #saas #cloud #cybermonday" 29 | }, 30 | { 31 | "time": "10:54", 32 | "status": "Scalability: A Million Dollar Question http://buff.ly/2fWU6Qv #saas #cloud #webperf" 33 | }, 34 | { 35 | "time": "12:19", 36 | "status": "If the Customer Ain’t Happy, Ain’t Nobody Happy http://buff.ly/2ezS04a" 37 | }, 38 | { 39 | "time": "14:08", 40 | "status": "Web Application Performance and the Support Organization #saas #cloud #webperf http://buff.ly/2ezWvM1" 41 | }, 42 | { 43 | "time": "15:32", 44 | "status": "Reducing Infrastructure Costs through Application Performance Optimization http://buff.ly/2exvGbh #saas #cloud #webperf" 45 | }, 46 | { 47 | "time": "17:30", 48 | "status": "Hargo: Load Testing using .har files in Go https://t.co/bvlXEgjxVN #golang" 49 | }, 50 | { 51 | "time": "18:21", 52 | "status": "Are you Elastic Enough for Cyber Monday? http://buff.ly/2fWT7QC #saas #cloud #cybermonday" 53 | }, 54 | { 55 | "time": "20:26", 56 | "status": "Scalability: A Million Dollar Question http://buff.ly/2fWU6Qv #saas #cloud #webperf" 57 | }, 58 | { 59 | "time": "21:39", 60 | "status": "If the Customer Ain’t Happy, Ain’t Nobody Happy http://buff.ly/2ezS04a" 61 | }, 62 | { 63 | "time": "22:25", 64 | "status": "Web Application Performance and the Support Organization #saas #cloud #webperf http://buff.ly/2ezWvM1" 65 | } 66 | ] -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | 9 | "fmt" 10 | 11 | "github.com/BurntSushi/toml" 12 | "github.com/ChimeraCoder/anaconda" 13 | "github.com/jasonlvhit/gocron" 14 | ) 15 | 16 | // JSONData holds the array of Tweets 17 | type JSONData struct { 18 | Tweets []Tweet 19 | } 20 | 21 | // Tweet is a scheduled Twitter status 22 | type Tweet struct { 23 | Time string 24 | Status string 25 | } 26 | 27 | // Twitter Auth Keys 28 | type TwitterAuthKeys struct { 29 | ConsumerKey string 30 | ConsumerSecret string 31 | AccessToken string 32 | AccessTokenSecret string 33 | } 34 | 35 | var api *anaconda.TwitterApi 36 | var auth TwitterAuthKeys 37 | 38 | func main() { 39 | 40 | if len(os.Args) == 1 { 41 | fmt.Println("Usage: guffer guffer.json ") 42 | os.Exit(1) 43 | } 44 | 45 | // Load the Twitter API keys 46 | 47 | // Check if the second argument with toml auth file was given, if not load auth keys from env variables 48 | if len(os.Args) == 2 { 49 | auth.loadFromEnvVariables() 50 | } else { 51 | auth.loadFromTomlFile(os.Args[2]) 52 | } 53 | 54 | // Read the config file 55 | 56 | configFile := os.Args[1] 57 | 58 | if _, err := os.Stat(configFile); os.IsNotExist(err) { 59 | log.Fatalf("File does not exist: %s", configFile) 60 | return 61 | } 62 | 63 | file, err := ioutil.ReadFile(configFile) 64 | if err != nil { 65 | log.Fatalf("Eror opening file %s: %v", configFile, err) 66 | return 67 | } 68 | 69 | // Deserialize the JSON data 70 | 71 | var tweets []Tweet 72 | 73 | err = json.Unmarshal(file, &tweets) 74 | if err != nil { 75 | log.Fatalf("Error while parsing file: %v", err) 76 | return 77 | } 78 | 79 | log.Println("Guffer is queueing the following tweets: ") 80 | 81 | // Init the Twitter API 82 | anaconda.SetConsumerKey(auth.ConsumerKey) 83 | anaconda.SetConsumerSecret(auth.ConsumerSecret) 84 | api = anaconda.NewTwitterApi(auth.AccessToken, auth.AccessTokenSecret) 85 | 86 | // Queue up the Tweets 87 | 88 | for _, tweet := range tweets { 89 | log.Printf("[%v] %s", tweet.Time, tweet.Status) 90 | gocron.Every(1).Day().At(tweet.Time).Do(postTweet, tweet.Status) 91 | } 92 | 93 | <-gocron.Start() 94 | } 95 | 96 | func postTweet(status string) { 97 | log.Println("Tweeting: ", status) 98 | api.PostTweet(status, nil) 99 | } 100 | 101 | func (t *TwitterAuthKeys) loadFromEnvVariables() { 102 | t.ConsumerKey = os.Getenv("CONSUMER_KEY") 103 | 104 | if t.ConsumerKey == "" { 105 | log.Fatal("Environment variable CONSUMER_KEY not set. See https://apps.twitter.com/app/new for more info.") 106 | } 107 | 108 | t.ConsumerSecret = os.Getenv("CONSUMER_SECRET") 109 | 110 | if t.ConsumerSecret == "" { 111 | log.Fatal("Environment variable CONSUMER_SECRET not set. See https://apps.twitter.com/app/new for more info.") 112 | } 113 | 114 | t.AccessToken = os.Getenv("ACCESS_TOKEN") 115 | 116 | if t.AccessToken == "" { 117 | log.Fatal("Environment variable ACCESS_TOKEN not set. See https://apps.twitter.com/app/new for more info.") 118 | } 119 | 120 | t.AccessTokenSecret = os.Getenv("ACCESS_TOKEN_SECRET") 121 | 122 | if t.AccessTokenSecret == "" { 123 | log.Fatal("Environment variable ACCESS_TOKEN_SECRET not set. See https://apps.twitter.com/app/new for more info.") 124 | } 125 | } 126 | 127 | func (t *TwitterAuthKeys) loadFromTomlFile(filename string) { 128 | // Load file to []byte 129 | data, err := ioutil.ReadFile(filename) 130 | // Check for error during loading 131 | if err != nil { 132 | log.Fatal(fmt.Sprintf("The %s file does not exists.", filename)) 133 | } 134 | // Decode file contents 135 | if _, err := toml.Decode(string(data), t); err != nil { 136 | log.Fatal(fmt.Sprintf("Failed to decode %s file:%s", filename, err.Error())) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | // Test loading twitter auth keys from file 6 | func TestTwitterAuthKeysToml(t *testing.T) { 7 | // Load twitter auth keys from file 8 | var auth TwitterAuthKeys 9 | auth.loadFromTomlFile("./testfiles/auth.toml") 10 | // Compare loaded auth data with expected auth data 11 | expectedAuth := TwitterAuthKeys{ 12 | ConsumerKey: "a", 13 | ConsumerSecret: "b", 14 | AccessToken: "c", 15 | AccessTokenSecret: "d", 16 | } 17 | if auth != expectedAuth { 18 | t.Fail() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /testfiles/auth.toml: -------------------------------------------------------------------------------- 1 | ConsumerKey = "a" 2 | ConsumerSecret = "b" 3 | AccessToken = "c" 4 | AccessTokenSecret = "d" --------------------------------------------------------------------------------