This repository has been archived by the owner on Apr 12, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
requests.go
169 lines (149 loc) Β· 4.31 KB
/
requests.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package main
import (
"bytes"
"encoding/json"
"fmt"
"golang.org/x/net/html"
"log"
"net/http"
"net/http/cookiejar"
"net/url"
"time"
)
const baseUrl = "https://www.strava.com"
const loginUrl = baseUrl + "/login"
const sessionUrl = baseUrl + "/session"
const activitiesUrl = baseUrl + "/athlete/training_activities"
const stravaDateFormat = "2006-01-02T15:04:05+0000"
var client *http.Client
var csrfToken string
func update(email string, password string, fromDate time.Time, toDate time.Time, update string) {
// Set up HTTP client which stores cookies and does not allow redirects
cookieJar, _ := cookiejar.New(nil)
client = &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Jar: cookieJar,
}
// Obtain CSRF token and Strava website cookie
csrfToken = getCsrfToken()
logIn(email, password)
// Fetch list of activities
activities := getActivities(fromDate, toDate)
// Update activities with the new value
updateActivities(activities, update)
}
func getCsrfToken() string {
log.Println("Logging into Strava...")
// Fetch login page
res, err := client.Get(loginUrl)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
// Extract CSRF token from login page
var csrfToken string
htmlTokens := html.NewTokenizer(res.Body)
loop:
for {
tt := htmlTokens.Next()
switch tt {
// Exit on errors or end of file if CSRF token hasn't been found yet
case html.ErrorToken:
log.Fatal("CSRF token not found on login page")
// Inspect meta tags for CSRF token
case html.SelfClosingTagToken:
t := htmlTokens.Token()
if t.Data == "meta" {
attr := t.Attr
if attr[0].Val == "csrf-token" {
csrfToken = attr[1].Val
break loop
}
}
}
}
return csrfToken
}
func logIn(email string, password string) {
// Log in to obtain Strava client cookies (stored in http.Client's cookie jar)
res, err := client.PostForm(
sessionUrl,
url.Values{"email": {email}, "password": {password}, "authenticity_token": {csrfToken}},
)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusFound {
log.Fatal("Login failed")
}
}
func getActivities(fromDate time.Time, toDate time.Time) []Activity {
log.Println("Fetching activity list...")
var activities []Activity
page := 1
for {
// Set up request
req, _ := http.NewRequest("GET", fmt.Sprintf("%s?page=%d", activitiesUrl, page), nil)
req.Header.Add("X-CSRF-Token", csrfToken)
req.Header.Add("X-Requested-With", "XMLHttpRequest")
// Send request
res, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
if res.StatusCode == http.StatusMovedPermanently {
log.Fatal("Incorrect Strava login credentials")
}
if res.StatusCode != http.StatusOK {
log.Fatalf("Request to %q returned status code %d", activitiesUrl, res.StatusCode)
}
// Extract and save activities
listResponse := new(ActivityListResponse)
json.NewDecoder(res.Body).Decode(listResponse)
for _, activity := range listResponse.Activities {
activityDate, _ := time.Parse(stravaDateFormat, activity.Date)
// Stop fetching if activity is before --from flag
if activityDate.Before(fromDate) {
break
}
// Save activity if it is between --from and --to flags
if activityDate.Before(toDate) {
activities = append(activities, activity)
}
}
// Determine whether there are more pages to fetch
if listResponse.Total-(listResponse.Page*listResponse.PerPage) <= 0 {
break
} else {
page += 1
}
res.Body.Close()
}
return activities
}
func updateActivities(activities []Activity, update string) {
// Loop over activities and update the specified attribute
for index, activity := range activities {
// Set up request
updateUrl := fmt.Sprintf("%s/%d", activitiesUrl, activity.Id)
body := []byte(update)
req, _ := http.NewRequest("PUT", updateUrl, bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Add("X-CSRF-Token", csrfToken)
req.Header.Add("X-Requested-With", "XMLHttpRequest")
// Send request
res, err := client.Do(req)
res.Body.Close()
if err != nil {
log.Fatal(err)
}
if res.StatusCode != http.StatusOK {
log.Fatalf("Request to %q returned status code %d", updateUrl, res.StatusCode)
}
log.Printf("Updated %d activities", index+1)
}
log.Println("Finished updating")
}