Skip to content

Instantly share code, notes, and snippets.

@olimsaidov
Created March 4, 2021 14:06
Show Gist options
  • Save olimsaidov/ca2e6a140c2b8bf8ad81509484c1fe37 to your computer and use it in GitHub Desktop.
Save olimsaidov/ca2e6a140c2b8bf8ad81509484c1fe37 to your computer and use it in GitHub Desktop.
(require '[babashka.curl :as curl])
(require '[cheshire.core :as json])
(require '[clojure.java.io :as io])
(require '[clojure.string :as str])
(import 'java.time.YearMonth)
(def bearer-token
"eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIyMjhUUUQiLCJzdWIiOiI0VkNOVjYiLCJ4YWkiOiI1NTExNTU4OTU5MzIiLCJ4dmVyIjoiMCIsImlzcyI6IkZpdGJpdCIsInR5cCI6ImFjY2Vzc190b2tlbiIsInNjb3BlcyI6IndociB3bnV0IHdwcm8gd3NsZSB3d2VpIHdtZmEgd3NvYyB3YWN0IHdzZXQgd2xvYyIsImV4cCI6MTYxNTAwNjg3NCwiaWF0IjoxNjE0ODM0MDc0fQ.FdaLszvp6vS4K5kUQ06ylOF_25y_rE0AbsFuMIke4tc")
(defn request-export
[start-date end-date]
(-> (curl/post "https://web-api.fitbit.com/1/user/-/legacy/export/request-export.json"
{:headers {"Authorization" (str "Bearer " bearer-token)}
:body (str "periodType=CUSTOM&dataTypes=BODY%2CFOODS%2CACTIVITIES%2CSLEEP&dataExportFileFormat=CSV&startDate=" start-date "&endDate=" end-date)})
(:body)
(json/parse-string true)
(:fileIdentifier)))
(def ranges
(->> (for [year (range 2015 2021)]
(for [month (range 1 13)]
[year month]))
(apply concat)
(map (fn [[year month]]
[(str (.atDay (YearMonth/of year month) 1))
(str (.atEndOfMonth (YearMonth/of year month)))]))))
(def file-identifiers
(->> ranges
(pmap (partial apply request-export))))
(defn export-status
[file-identifier]
(-> (curl/get "https://web-api.fitbit.com/1/user/-/legacy/export/export-status.json"
{:headers {"Authorization" (str "Bearer " bearer-token)}
:query-params {"fileIdentifier" file-identifier}})
(:body)
(json/parse-string true)
(:fileIsReady)))
(defn wait-exports
[file-identifiers]
(loop [file-identifiers file-identifiers]
(if (empty? file-identifiers)
:ready
(do (Thread/sleep 500)
(recur (->> file-identifiers
(pmap #(vector % (export-status %)))
(keep #(when-not (second %) (first %)))))))))
(wait-exports file-identifiers)
(defn get-completed-export
[file-identifier]
(-> (curl/get "https://web-api.fitbit.com/1/user/-/legacy/export/get-completed-export.json"
{:headers {"Authorization" (str "Bearer " bearer-token)}
:query-params {"fileIdentifier" file-identifier}})
(:body)
(json/parse-string true)
(:exportUrl)))
(def urls
(->> file-identifiers
(pmap get-completed-export)))
(defn download-urls
[output names urls]
(->> (map vector names urls)
(pmap (fn [[name url]]
(io/copy
(:body (curl/get url {:as :stream}))
(io/file output name))))
(doall)))
(download-urls "output" (map (fn [[s e]] (str s "-" e ".csv")) ranges) urls)
(->> (file-seq (io/file "output"))
(filter #(str/ends-with? (str %) ".csv"))
(map slurp)
(map str/split-lines)
(map #(take-while #(not= "Foods" %) %))
(map drop-last)
(map #(drop 2 %))
(apply concat)
(sort)
(dedupe)
(str/join "\n")
(spit "unified.csv"))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment