Skip to content

brandonm/gocronometer

 
 

Repository files navigation

gocronometer

gocronometer is a GPLv2-licensed Go module that provides a client for reading your own data out of Cronometer. It speaks the same unpublished API the Cronometer single-page app uses.

This is @brandonm's fork, ahead of upstream jrmycanady/gocronometer (v1.5.1). It adds quota-free diary reading over GWT-RPC, custom-food/recipe resolution, and automatic rate limiting. The module path is kept as github.com/jrmycanady/gocronometer so it drops into existing code via a replace directive — see Using this fork.

IMPORTANT: This module uses the same API the SPA uses. For that reason it should only be used by single users wanting to read/back-up their own personal data. It should never be used for integrations that the enterprise plan would cover. The library is licensed under the GPLv2 to help discourage unacceptable usage.

IMPORTANT: Always scope your requests to a reasonable date range. Never continually re-read all data or data you already have.


What's new in this fork

Capability Why it matters
Diary reading over GWT-RPC (GetDayServings) The legacy CSV /export endpoint is capped at roughly 10 exports per day. getDayInfo is the same call the web diary uses — no daily quota — and it returns water servings, which the CSV export drops.
Custom foods & recipes (FindMyFoods, GetFood, GetAllFoods) Resolve a serving's food ID to its name, source, per-100 g nutrients, measures, and (for recipes) its ingredient list.
Automatic rate limiting The client throttles every request and, on login, fetches Cronometer's published throttle-config to honor the real gwt_rpc limit. Nothing to wire up.

Install

go get github.com/jrmycanady/gocronometer

Using this fork

The module keeps the upstream path, so point your go.mod at this fork with a replace directive and import the original path:

// go.mod
require github.com/jrmycanady/gocronometer v1.5.1
replace github.com/jrmycanady/gocronometer => github.com/brandonm/gocronometer v1.6.0
import "github.com/jrmycanady/gocronometer"

Quick start — read a day's diary (recommended)

// NewClient installs an automatic rate limiter (see Rate limiting below).
c := gocronometer.NewClient(nil)

// Login also fetches Cronometer's published throttle-config and tunes the limiter.
if err := c.Login(context.Background(), username, password); err != nil {
	log.Fatalf("login failed: %s", err)
}
defer c.Logout(context.Background())

// Read one day's servings via GWT-RPC (getDayInfo) — no daily export quota.
day := time.Date(2026, 6, 28, 0, 0, 0, 0, time.UTC)
servings, err := c.GetDayServings(context.Background(), day)
if err != nil {
	log.Fatalf("getDayInfo failed: %s", err)
}

for _, s := range servings {
	fmt.Printf("%-12s food=%d amount=%.2f\n",
		gocronometer.MealGroupName(s.MealGroup), s.FoodID, s.Amount)
}

getDayInfo is a single-day call, so to cover a range, loop it across the dates you need.

DayServing

type DayServing struct {
	FoodID    int64     // Cronometer food ID (resolve nutrients via GetAllFoods)
	Amount    float64   // quantity in the measure's units (grams when MeasureID is the gram measure)
	MeasureID int64     // which measure the amount is expressed in
	MealGroup int       // 1=Breakfast 2=Lunch 3=Dinner 4=Snacks 5=Supplements 6=Water
	Date      time.Time // the diary day this serving belongs to
}

MealGroupName(group int) string maps the MealGroup index to its display name.

A DayServing references a food by ID plus an amount; nutrient values are not embedded. Resolve them with the custom-food calls below (per-100 g × grams), reusing the food cache.


Custom foods, recipes & nutrients

func description
FindMyFoods(ctx) ([]CustomFood, error) Lists your custom foods/recipes/meals (ID, Name).
GetFood(ctx, foodID) (*FoodDetail, error) Full detail for one food: name, source, ingredients (if a recipe), per-100 g nutrients, and measures.
GetAllFoods(ctx, foodIDs) (map[int64]*FoodDetail, error) Batch GetFood — resolve many food IDs at once.
GetFoodIngredients(ctx, foodID) ([]RecipeIngredient, error) Just the ingredient list for a recipe/meal.
type FoodDetail struct {
	FoodID           int64
	Name             string
	Source           string             // e.g. "NCCDB", "CRDB", "custom"
	Ingredients      []RecipeIngredient // populated for recipes/meals
	NutrientsPer100g map[int]float64    // keyed by USDA nutrient code; per 100 g
	Measures         []FoodMeasure      // measure ID -> grams (convert amount → grams)
}

Rate limiting

NewClient installs an http.RoundTripper that paces every request to a safe default (DefaultGWTRateLimit). On Login, the client fetches Cronometer's published limits from /api/v3/throttle-config and tunes itself to a fraction of the real gwt_rpc rate, leaving headroom for any concurrent web/app sessions. This is automatic — you don't need to call anything.

You can inspect the published limits yourself:

cfg, err := c.GetThrottleConfig(context.Background())

Legacy CSV exports

The original CSV /export endpoint is still available, but is subject to Cronometer's ~10 exports/day quota and omits water servings. Prefer GetDayServings for diary data. The exports return raw CSV.

func description
ExportDailyNutrition() Daily nutrition for the date range.
ExportServings() Servings for the date range.
ExportExercises() Exercises for the date range.
ExportBiometrics() Biometrics for the date range.
ExportNotes() Notes for the date range.

Parsing CSV exports

func parses output of
ParseServingsExport() ExportDailyNutrition(), ExportServings()
ParseExerciseExport() ExportExercises()
ParseBiometricRecordsExport() ExportBiometrics()

The *Parsed helpers (ExportServingsParsed, etc.) export and parse in one call.


API Magic Values

This library mimics the GWT HTTP requests Cronometer's deployed GWT application makes. Several values can only be obtained by loading the application itself, and they change over time with application updates. The library ships the values as of its last push; new values can be provided via the ClientOptions parameter of NewClient.

Name Location Changes
GWTContentType Request header. false
GWTModuleBase Request header. false
GWTPermutation Request header. true
GWTHeader GWT request body. true

Testing

go test ./...

Offline unit tests (GWT deserializers, custom-food parsing, diary decoding) run with no setup. The live integration tests in gocronometer_test.go hit the real Cronometer API and skip unless GOCRONOMETER_TEST_USERNAME and GOCRONOMETER_TEST_PASSWORD are set — so CI stays green without secrets, and you can run the full suite against your own account by exporting both.


License

GPLv2. Forked from jrmycanady/gocronometer.

About

gocronometer is a go module that provides API access to the export methods of Cronometer.com

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages

  • Go 100.0%