// Copyright 2023 Datakami // // SPDX-License-Identifier: MIT export namespace API { export type CreateDatapoint = { value: number; timestamp?: number; daystamp?: string; comment?: string; requestid?: string; } export type Datapoint = Required & { id: string updated_at: number } export interface GetDatapoints { sort?: string; count?: number; page?: number; per?: number; } export type GoalType = 'hustler' | 'biker' | 'fatloser' | 'gainer' | 'inboxer' | 'drinker' | 'custom'; export interface Goal { slug: string; updated_at: number; title: string; fineprint: string; yaxis: string; goaldate: number; goalval: number; rate: number; runits: string; svg_url: string; graph_url: string; thumb_url: string; autodata: string | null; goal_type: GoalType losedate: number | null; queued: boolean; secret: boolean; datapublic: boolean; datapoints: Datapoint[]; numpts: number; pledge: number; initday: number; initval: number; curday: number; curval: number; lastday: number; yaw: -1 | 1; dir: -1 | 1; lane: number; mathishard: [number, number, number]; headsum: string; limsum: string; kyoom: boolean; odom: boolean; aggday: 'min' | 'max' | 'mean'; steppy: boolean; rosy: boolean; movingav: boolean; aura: boolean; frozen: boolean; won: boolean; lost: boolean; maxflux: number; contract: { amount: number; stepdown_at: number | null; }; road: Array<[number | null, number | null, number | null]>; roadall: Array<[number, number, number | null]>; fullroad: Array<[number, number, number]>; rah: number; delta: number; delta_text: string; safebuf: number; safebump: number; id: string; callback_url: string; description: string; graphsum: string; lanewidth: number; deadline: number; leadtime: number; alertstart: number; plotall: boolean; last_datapoint: Datapoint; integery: boolean; gunits: string; hhmmformat: boolean; todayta: boolean; weekends_off: boolean; tmin: string; tmax: string; tags: string[]; } export interface NewGoal { slug: string; title: string; goal_type: GoalType gunits: string; goaldate?: number | null; goalval?: number | null; rate?: number | null; initval?: number; secret?: boolean; datapublic?: boolean; datasource?: "api" | "ifttt" | "zapier" | string; dryrun?: boolean; tags?: string[]; }; export interface UpdateGoal { title?: string; yaxis?: string; tmin?: string; // date format "yyyy-mm-dd" tmax?: string; // date format "yyyy-mm-dd" secret?: boolean; datapublic?: boolean; roadall?: Array<[number | null, number, number | null]>; // array of arrays like [date::int, value::float, rate::float] each with exactly one field null datasource?: string; // one of {"api", "ifttt", "zapier", or clientname}. Default: none. tags?: Array; // a list of tags for the goal } } export class Beeminder { constructor(private readonly token: string) {} async post_request(url: string, post_data: any, method: "POST" | "PUT" = "POST") { const response = await fetch("https://www.beeminder.com/api/v1/" + url + ".json", { method, body: JSON.stringify(Object.assign({}, post_data, { auth_token: this.token })), headers: { 'Content-Type': 'application/json' } }) if (response.ok || response.status == 422) { return response.json() } else { throw [response.status, await response.text()] } } async get_request(url: string, params = {}) { const qs = new URLSearchParams(Object.assign({}, params, { auth_token: this.token })) const response = await fetch("https://www.beeminder.com/api/v1/" + url + ".json?" + qs.toString()) if (response.ok || response.status == 422) { return response.json() } else { throw [response.status, await response.text()] } } user(user = "me") { return new User(this, user) } charge(amount: number, note: string, dryrun = true) { return this.post_request(`charges`, { amount, note, dryrun }) } } export class User { constructor(public readonly b: Beeminder, public readonly user: string) {} info() { return this.b.get_request(`users/${this.user}`) } goals() { return this.b.get_request(`users/${this.user}/goals`) } goal(goal: string) { return new Goal(this.b, this.user, goal) } create_goal(params: API.NewGoal) { return this.b.post_request(`users/${this.user}/goals`, params, 'POST') } } export class Goal { readonly prefix: string constructor(public readonly b: Beeminder, public readonly user: string, public readonly goal: string) { this.prefix = `users/${this.user}/goals/${this.goal}` } info(): Promise { return this.b.get_request(`${this.prefix}`) } update(params: API.UpdateGoal): Promise { return this.b.post_request(`${this.prefix}`, params, 'PUT') } refresh_graph(): Promise { return this.b.get_request(`${this.prefix}/refresh_graph`) } create_datapoint(params: API.CreateDatapoint): Promise { return this.b.post_request(`${this.prefix}/datapoints`, params) } create_datapoints(params: API.CreateDatapoint[]): Promise { return this.b.post_request(`${this.prefix}/datapoints/create_all`, params) } datapoints(params: API.GetDatapoints = {}): Promise { return this.b.get_request(`${this.prefix}/datapoints`, params) } // todo: put, delete datapoint } module.exports = Beeminder