Last active
November 30, 2018 19:54
-
-
Save patmigliaccio/a24c2cd10a3a088c473f6fe2ea8c2e30 to your computer and use it in GitHub Desktop.
A Go script that pulls Uber ride history and fare details
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * A Go script that pulls Uber ride history and fare details | |
| * | |
| * Example: | |
| * $ go run concurrent_http.go | |
| * | |
| * New York | Base: $1.16 -- Duration Cost: $2.32 -- Distance Cost (mile): $4.53 -- Service Fees: $2.35 -- Duration: 13.67min | |
| * New Jersey | Base: $1.16 -- Duration Cost: $0.66 -- Distance Cost (mile): $1.83 -- Service Fees: $2.35 -- Duration: 3.88min | |
| * New Jersey | Base: $1.10 -- Duration Cost: $0.86 -- Distance Cost (mile): $1.94 -- Service Fees: $2.20 -- Duration: 5.40min | |
| * New Jersey | Base: $1.10 -- Duration Cost: $2.38 -- Distance Cost (mile): $1.47 -- Service Fees: $2.20 -- Duration: 14.87min | |
| * | |
| * Author: Pat Migliaccio <[email protected]> | |
| * License: MIT | |
| */ | |
| package main | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "io/ioutil" | |
| "log" | |
| "net/http" | |
| "strconv" | |
| "time" | |
| ) | |
| // AuthorizationCode is the Personal Access Token used to make requests to the Uber API | |
| const AuthorizationCode = "" | |
| func main() { | |
| if AuthorizationCode == "" { | |
| log.Fatalln("'AuthorizationCode' is empty. Please edit the script and provide a Personal Access Token (https://developer.uber.com/dashboard/app/)") | |
| } | |
| historyItems := GetHistory(15) | |
| ch := make(chan ProductDetail) | |
| for _, historyItem := range historyItems[1:] { | |
| go func(item HistoryItem) { | |
| ch <- GetProductDetails(item.ProductID) | |
| }(historyItem) | |
| } | |
| for _, historyItem := range historyItems[1:] { | |
| pd := <-ch | |
| if pd.ProductGroup != "ubereats" { | |
| fmt.Printf("%s | ", historyItem.StartCity.DisplayName) | |
| totalServiceFee := CalculateServiceFees(pd.PriceDetails) | |
| duration := CalculateTripTime(historyItem) | |
| fmt.Printf("Base: $%.2f -- Duration Cost: $%.2f -- Distance Cost (%s): $%.2f -- Service Fees: $%.2f", pd.PriceDetails.Base, pd.PriceDetails.CostPerMinute*duration, pd.PriceDetails.DistanceUnit, pd.PriceDetails.CostPerDistance*historyItem.Distance, totalServiceFee) | |
| fmt.Printf(" -- Duration: %.2fmin \n", duration) | |
| } | |
| } | |
| } | |
| type HistoryItem struct { | |
| Status string `json:"status"` | |
| Distance float64 `json:"distance"` | |
| ProductID string `json:"product_id"` | |
| StartTime int64 `json:"start_time"` | |
| StartCity struct { | |
| Latitude float64 `json:"latitude"` | |
| DisplayName string `json:"display_name"` | |
| Longitude float64 `json:"longitude"` | |
| } `json:"start_city"` | |
| EndTime int64 `json:"end_time"` | |
| RequestID string `json:"request_id"` | |
| RequestTime int64 `json:"request_time"` | |
| } | |
| type RideReceipt struct { | |
| RequestID string `json:"request_id"` | |
| Subtotal string `json:"subtotal"` | |
| TotalCharged string `json:"total_charged"` | |
| TotalOwed string `json:"total_owed"` | |
| TotalFare string `json:"total_fare"` | |
| CurrencyCode string `json:"currency_code"` | |
| ChargeAdjustments []struct{} `json:"charge_adjustments"` | |
| Duration string `json:"duration"` | |
| Distance string `json:"distance"` | |
| DistanceLabel string `json:"distance_label"` | |
| } | |
| type ProductDetail struct { | |
| UpfrontFareEnabled bool `json:"upfront_fare_enabled"` | |
| Capacity int `json:"capacity"` | |
| ProductID string `json:"product_id"` | |
| PriceDetails PriceDetail `json:"price_details"` | |
| Image string `json:"image"` | |
| CashEnabled bool `json:"cash_enabled"` | |
| Shared bool `json:"shared"` | |
| ShortDescription string `json:"short_description"` | |
| DisplayName string `json:"display_name"` | |
| ProductGroup string `json:"product_group"` | |
| Description string `json:"description"` | |
| } | |
| type PriceDetail struct { | |
| ServiceFees []struct { | |
| Fee float64 `json:"fee"` | |
| Name string `json:"name"` | |
| } `json:"service_fees"` | |
| CostPerMinute float64 `json:"cost_per_minute"` | |
| DistanceUnit string `json:"distance_unit"` | |
| Minimum float64 `json:"minimum"` | |
| CostPerDistance float64 `json:"cost_per_distance"` | |
| Base float64 `json:"base"` | |
| CancellationFee float64 `json:"cancellation_fee"` | |
| CurrencyCode string `json:"currency_code"` | |
| } | |
| // GetHistory retrieves a user's ride history | |
| func GetHistory(limit int) []HistoryItem { | |
| client := &http.Client{} | |
| if limit > 50 { | |
| limit = 50 | |
| } | |
| req, err := http.NewRequest("GET", "https://api.uber.com/v1.2/history?limit="+strconv.Itoa(limit), nil) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| req.Header.Add("Authorization", "Bearer "+AuthorizationCode) | |
| resp, err := client.Do(req) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| body, _ := ioutil.ReadAll(resp.Body) | |
| handleHTTPError(resp, body) | |
| var historyResponse struct { | |
| Count int `json:"count"` | |
| History []HistoryItem `json:"history"` | |
| } | |
| if err := json.Unmarshal(body, &historyResponse); err != nil { | |
| panic(err) | |
| } | |
| return historyResponse.History | |
| } | |
| /* | |
| GetRideReceipt retrieves receipt information. Requires privileged scope access. | |
| https://developer.uber.com/docs/riders/guides/scopes#privileged-scopes | |
| */ | |
| func GetRideReceipt(requestID string) RideReceipt { | |
| client := &http.Client{} | |
| req, err := http.NewRequest("GET", "https://api.uber.com/v1.2/requests/"+requestID+"/receipt", nil) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| req.Header.Add("Authorization", "Bearer "+AuthorizationCode) | |
| resp, err := client.Do(req) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| body, _ := ioutil.ReadAll(resp.Body) | |
| handleHTTPError(resp, body) | |
| var rideReceipt RideReceipt | |
| if err := json.Unmarshal(body, &rideReceipt); err != nil { | |
| panic(err) | |
| } | |
| return rideReceipt | |
| } | |
| // GetProductDetails retrieves the product details for a ride | |
| func GetProductDetails(productID string) ProductDetail { | |
| client := &http.Client{} | |
| req, err := http.NewRequest("GET", "https://api.uber.com/v1.2/products/"+productID, nil) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| req.Header.Add("Authorization", "Bearer "+AuthorizationCode) | |
| resp, err := client.Do(req) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| body, _ := ioutil.ReadAll(resp.Body) | |
| handleHTTPError(resp, body) | |
| var productDetail ProductDetail | |
| if err := json.Unmarshal(body, &productDetail); err != nil { | |
| panic(err) | |
| } | |
| return productDetail | |
| } | |
| // CalculateServiceFees determines the total cost of the trip service fees | |
| func CalculateServiceFees(priceDetail PriceDetail) float64 { | |
| var totalServiceFee float64 | |
| if len(priceDetail.ServiceFees) > 0 { | |
| for _, fee := range priceDetail.ServiceFees { | |
| totalServiceFee += fee.Fee | |
| } | |
| } | |
| return totalServiceFee | |
| } | |
| // CalculateTripTime determines the duration of the trip from the history | |
| func CalculateTripTime(historyItem HistoryItem) float64 { | |
| return time.Unix(historyItem.EndTime, 0).Sub(time.Unix(historyItem.StartTime, 0)).Minutes() | |
| } | |
| func handleHTTPError(resp *http.Response, body []byte) { | |
| if resp.StatusCode < 200 || resp.StatusCode > 299 { | |
| log.Fatal(struct { | |
| StatusCode int | |
| Message string | |
| }{ | |
| resp.StatusCode, | |
| string(body), | |
| }) | |
| } | |
| } | |
| func printJSON(v interface{}) { | |
| vj, err := json.Marshal(v) | |
| if err == nil { | |
| fmt.Println(string(vj)) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment