package gfit import ( "context" "fmt" "net/http" "time" "github.com/octo/gfitsync/app" "golang.org/x/oauth2" oauth2google "golang.org/x/oauth2/google" fitness "google.golang.org/api/fitness/v1" "google.golang.org/appengine" "google.golang.org/appengine/log" ) var oauthConfig = &oauth2.Config{ ClientID: "@GOOGLE_CLIENT_ID@", ClientSecret: "@GOOGLE_CLIENT_SECRET@", Endpoint: oauth2google.Endpoint, RedirectURL: "https://fitbit-gfit-sync.appspot.com/google/grant", Scopes: []string{ fitness.FitnessActivityWriteScope, fitness.FitnessBodyWriteScope, }, } const csrfToken = "@CSRFTOKEN@" func Application(ctx context.Context) *fitness.Application { return &fitness.Application{ Name: "Fitbit to Google Fit sync", Version: appengine.VersionID(ctx), DetailsUrl: "", // optional } } func AuthURL() string { return oauthConfig.AuthCodeURL(csrfToken, oauth2.AccessTypeOffline) } func ParseToken(ctx context.Context, r *http.Request, u *app.User) error { if state := r.FormValue("state"); state != csrfToken { return fmt.Errorf("invalid state parameter: %q", state) } tok, err := oauthConfig.Exchange(ctx, r.FormValue("code")) if err != nil { return err } return u.SetToken(ctx, "Google", tok) } type Client struct { *fitness.Service } func NewClient(ctx context.Context, u *app.User) (*Client, error) { c, err := u.OAuthClient(ctx, "Google", oauthConfig) if err != nil { return nil, err } service, err := fitness.New(c) if err != nil { return nil, err } return &Client{ Service: service, }, nil } func (c *Client) SetSteps(ctx context.Context, steps int, date time.Time) error { const userID = "me" const dataTypeName = "com.google.step_count.delta" dataSource := &fitness.DataSource{ Application: Application(ctx), DataStreamName: "", // "daily summary"? DataType: &fitness.DataType{ Field: []*fitness.DataTypeField{ &fitness.DataTypeField{ Format: "integer", Name: "steps", }, }, Name: dataTypeName, }, Name: "Step Count", Type: "raw", } dataSource, err := c.Service.Users.DataSources.Create(userID, dataSource).Context(ctx).Do() if err != nil { log.Errorf(ctx, "c.Service.Users.DataSources.Create() = (%+v, %v)", dataSource, err) return err } dataSourceID := dataSource.DataStreamId startTimeNanos := date.UnixNano() endTimeNanos := date.Add(86399999999999 * time.Nanosecond).UnixNano() datasetID := fmt.Sprintf("%d-%d", startTimeNanos, endTimeNanos) dataset := &fitness.Dataset{ MinStartTimeNs: startTimeNanos, MaxEndTimeNs: endTimeNanos, Point: []*fitness.DataPoint{ &fitness.DataPoint{ ComputationTimeMillis: time.Now().UnixNano() / 1000000, DataTypeName: dataTypeName, StartTimeNanos: startTimeNanos, EndTimeNanos: endTimeNanos, Value: []*fitness.Value{ &fitness.Value{ IntVal: int64(steps), }, }, }, }, } dataset, err = c.Service.Users.DataSources.Datasets.Patch(userID, dataSourceID, datasetID, dataset).Context(ctx).Do() if err != nil { log.Errorf(ctx, "c.Service.Users.DataSources.Datasets.Patch() = (%+v, %v)", dataset, err) return err } return nil }