Skip to content
This repository was archived by the owner on Oct 22, 2018. It is now read-only.

Commit e8434f7

Browse files
committed
Rewrite in Go
1 parent f97ce64 commit e8434f7

File tree

9 files changed

+210
-113
lines changed

9 files changed

+210
-113
lines changed

.env.sample

Lines changed: 0 additions & 9 deletions
This file was deleted.

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
.env
1+
*.json

Gemfile

Lines changed: 0 additions & 4 deletions
This file was deleted.

Gemfile.lock

Lines changed: 0 additions & 16 deletions
This file was deleted.

LICENSE.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License
22

3-
Copyright (c) 2016 Angelos Orfanakos
3+
Copyright (c) 2016-2017 Angelos Orfanakos
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy of
66
this software and associated documentation files (the "Software"), to deal in

README.md

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,65 @@
11
# trafficjam
22

3-
A Ruby script that uses the [Google Maps API][] to notify you if your daily
4-
commute has unusual traffic.
3+
Queries the [Google Maps API][] and notifies you if a trip has unusual traffic.
4+
Useful for knowing about traffic in your daily commute before getting stuck.
55

66
[Google Maps API]: https://developers.google.com/maps/
77

88
## Quick how-to
99

1010
First, you need a free [Google Maps API key][] and access to an SMTP server.
11-
[Mailgun][] has free plans.
11+
[Mailgun][] has a free plan.
1212

1313
[Google Maps API key]: https://developers.google.com/maps/documentation/javascript/get-api-key#key
1414
[Mailgun]: https://www.mailgun.com/
1515

16-
Then, issue once:
16+
Then install [Go](https://golang.org/), if you haven't, and issue once:
1717

1818
$ git clone https://github.com/agorf/trafficjam.git
1919
$ cd trafficjam
20-
$ bundle install
21-
$ cp .env.sample .env
20+
$ go build trafficjam.go
21+
$ cp config.json.sample config.json
2222

23-
Configure the script with your `$EDITOR`:
23+
Configure the program with your `$EDITOR`:
2424

25-
$ $EDITOR .env
25+
$ $EDITOR config.json
2626

27-
Run the script:
27+
Run the program:
2828

29-
$ bundle exec ruby trafficjam.rb
29+
$ ./trafficjam config.json
3030

3131
You can use [Cron][] to run the script at predetermined intervals (e.g. right
32-
before heading out each morning).
32+
before heading out each morning). Here's what I have:
33+
34+
0,5,10,15,20,25,30 9 * * 1-5 agorf ./trafficjam config.json
35+
36+
This runs the program on working days, from 9:00 until 9:30, every five minutes.
37+
38+
To avoid getting spammed, you need to figure out the right threshold
39+
(`max_duration` config option) that you want to get notified for.
3340

3441
[Cron]: https://en.wikipedia.org/wiki/Cron
3542

3643
## Configuration
3744

38-
The following environment variables should be defined (all required unless
39-
otherwise stated):
45+
The configuration file is a plain JSON file. The following keys can be defined
46+
(all required unless otherwise stated):
4047

41-
* `ORIGINS` – Your home address
42-
* `DESTINATIONS` – Your work address
43-
* `KEY` – Your Google Maps API key
44-
* `MODE` (optional) – See [here](https://developers.google.com/maps/documentation/distance-matrix/intro#travel_modes)
45-
* `AVOID` (optional) – See [here](https://developers.google.com/maps/documentation/distance-matrix/intro#Restrictions)
46-
* `TRAFFIC_MODEL` (optional) – See [here](https://developers.google.com/maps/documentation/distance-matrix/intro#traffic-model)
47-
* `MAX_DURATION_MINUTES` – If the estimated duration of your commute exceeds
48+
* `origins` – Your home address
49+
* `destinations` – Your work address
50+
* `api_key` – Your Google Maps API key
51+
* `mode` (optional) – See [here](https://developers.google.com/maps/documentation/distance-matrix/intro#travel_modes)
52+
* `avoid` (optional) – See [here](https://developers.google.com/maps/documentation/distance-matrix/intro#Restrictions)
53+
* `traffic_model` (optional) – See [here](https://developers.google.com/maps/documentation/distance-matrix/intro#traffic-model)
54+
* `max_duration` (minutes) – If the estimated duration of your commute exceeds
4855
this value in minutes, you will get notified
49-
* `SMTP_HOST` – SMTP server hostname
50-
* `SMTP_PORT` – SMTP server port
51-
* `SMTP_USER` – SMTP server username
52-
* `SMTP_PASS` – SMTP server password
53-
* `RECIPIENT` – Email address to get notified at
56+
* `host` (under `smtp`) – SMTP server hostname
57+
* `port` (under `smtp`) – SMTP server port
58+
* `user` (under `smtp`) – SMTP server username
59+
* `pass` (under `smtp`) – SMTP server password
60+
* `recipient` – Email address to get notified at
61+
62+
See `config.json.sample` for a sample configuration file.
5463

5564
## License
5665

config.json.sample

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"origins": "",
3+
"destinations": "",
4+
"api_key": "",
5+
"mode": "",
6+
"avoid": "",
7+
"traffic_model": "",
8+
"max_duration": 0,
9+
"smtp": {
10+
"host": "",
11+
"port": 587,
12+
"user": "",
13+
"pass": ""
14+
},
15+
"recipient": ""
16+
}

trafficjam.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"io/ioutil"
8+
"net/http"
9+
"net/smtp"
10+
"net/url"
11+
"os"
12+
"os/user"
13+
)
14+
15+
const (
16+
name = "trafficjam"
17+
apiURL = "https://maps.googleapis.com/maps/api/distancematrix/json"
18+
)
19+
20+
type config struct {
21+
Origins string `json:"origins"`
22+
Destinations string `json:"destinations"`
23+
APIKey string `json:"api_key"`
24+
Mode string `json:"mode"`
25+
Avoid string `json:"avoid"`
26+
TrafficModel string `json:"traffic_model"`
27+
MaxDuration int `json:"max_duration"`
28+
SMTP struct {
29+
Host string `json:"host"`
30+
Port int `json:"port"`
31+
User string `json:"user"`
32+
Pass string `json:"pass"`
33+
} `json:"smtp"`
34+
Recipient string `json:"recipient"`
35+
}
36+
37+
type apiResponse struct {
38+
Rows []struct {
39+
Elements []struct {
40+
DurationInTraffic struct {
41+
Value int `json:"value"`
42+
} `json:"duration_in_traffic"`
43+
Status string `json:"status"`
44+
} `json:"elements"`
45+
} `json:"rows"`
46+
Status string `json:"status"`
47+
}
48+
49+
func main() {
50+
conf, err := readConfig(os.Args[1])
51+
if err != nil {
52+
fmt.Fprintf(os.Stderr, "%s: %v\n", name, err)
53+
os.Exit(1)
54+
}
55+
56+
params := map[string]string{
57+
"origins": conf.Origins,
58+
"destinations": conf.Destinations,
59+
"key": conf.APIKey,
60+
"mode": conf.Mode,
61+
"avoid": conf.Avoid,
62+
"departure_time": "now",
63+
"traffic_model": conf.TrafficModel,
64+
}
65+
66+
apiResp, err := queryMapsAPI(params)
67+
if err != nil {
68+
fmt.Fprintf(os.Stderr, "%s: %v\n", name, err)
69+
os.Exit(1)
70+
}
71+
72+
duration := apiResp.Rows[0].Elements[0].DurationInTraffic.Value
73+
durationMins := int(float64(duration)/60.0 + 0.5) // round
74+
75+
if durationMins > conf.MaxDuration {
76+
sendMail(conf, fmt.Sprintf("%d minutes", durationMins))
77+
}
78+
}
79+
80+
func readConfig(filename string) (*config, error) {
81+
var conf config
82+
83+
confData, err := ioutil.ReadFile(filename)
84+
if err != nil {
85+
return nil, err
86+
}
87+
88+
if err := json.Unmarshal(confData, &conf); err != nil {
89+
return nil, err
90+
}
91+
92+
return &conf, nil
93+
}
94+
95+
func queryMapsAPI(params map[string]string) (*apiResponse, error) {
96+
urlBuf := bytes.NewBufferString(apiURL)
97+
urlBuf.WriteByte('?')
98+
99+
for key, val := range params {
100+
urlBuf.WriteString(url.QueryEscape(key))
101+
urlBuf.WriteByte('=')
102+
urlBuf.WriteString(url.QueryEscape(val))
103+
urlBuf.WriteByte('&')
104+
}
105+
106+
resp, err := http.Get(urlBuf.String())
107+
if err != nil {
108+
return nil, err
109+
}
110+
defer resp.Body.Close()
111+
112+
body, err := ioutil.ReadAll(resp.Body)
113+
if err != nil {
114+
return nil, err
115+
}
116+
117+
var apiResp apiResponse
118+
if err := json.Unmarshal(body, &apiResp); err != nil {
119+
return nil, err
120+
}
121+
122+
if apiResp.Status != "OK" {
123+
return nil, fmt.Errorf("%s: bad response status: %s\n", name, apiResp.Status)
124+
}
125+
if len(apiResp.Rows) != 1 {
126+
return nil, fmt.Errorf("%s: response row count is not 1\n", name)
127+
}
128+
if len(apiResp.Rows[0].Elements) != 1 {
129+
return nil, fmt.Errorf("%s: response first row element count is not 1\n", name)
130+
}
131+
if apiResp.Rows[0].Elements[0].Status != "OK" {
132+
return nil, fmt.Errorf("%s: bad response row element status: %s\n", name, apiResp.Rows[0].Elements[0].Status)
133+
}
134+
135+
return &apiResp, nil
136+
}
137+
138+
func sendMail(conf *config, body string) error {
139+
user, err := user.Current()
140+
if err != nil {
141+
return err
142+
}
143+
144+
hostname, err := os.Hostname()
145+
if err != nil {
146+
return err
147+
}
148+
149+
auth := smtp.PlainAuth("", conf.SMTP.User, conf.SMTP.Pass, conf.SMTP.Host)
150+
sender := user.Username + "@" + hostname
151+
to := []string{conf.Recipient}
152+
msg := []byte("To: " + conf.Recipient + "\r\n" +
153+
"Subject: " + name + " alert\r\n" +
154+
"\r\n" +
155+
body + "\r\n")
156+
157+
return smtp.SendMail(fmt.Sprintf("%s:%d", conf.SMTP.Host, conf.SMTP.Port), auth, sender, to, msg)
158+
}

trafficjam.rb

Lines changed: 0 additions & 57 deletions
This file was deleted.

0 commit comments

Comments
 (0)