From 4ceca3845e6072c06b889a83bf43d2523bec10ad Mon Sep 17 00:00:00 2001 From: Seth Etter Date: Sat, 30 May 2026 14:44:09 +0000 Subject: [PATCH] Starting point: pulls group events and refreshes tokens --- .env.example | 3 +++ .gitignore | 2 ++ main.ts | 20 ++++++++++++++++++++ openmeet.ts | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 30 ++++++++++++++++++++++++++++++ package.json | 11 +++++++++++ tsconfig.json | 10 ++++++++++ 7 file(s) changed, 144 insertion(s)(+), 0 deletion(s)(-) diff --git a/.env.example b/.env.example new file mode 100644 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +# Get these from an active browser session in local storage +OPENMEET_AUTH_TOKEN= +OPENMEET_REFRESH_TOKEN= diff --git a/.gitignore b/.gitignore new file mode 100644 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +.env diff --git a/main.ts b/main.ts new file mode 100644 --- /dev/null +++ b/main.ts @@ -0,0 +1,20 @@ +import { OpenMeetClient } from "./openmeet.ts"; + +const authToken = process.env.OPENMEET_AUTH_TOKEN; +const refreshToken = process.env.OPENMEET_REFRESH_TOKEN; + +(async () => { + const today = Temporal.Now.plainDateTimeISO().round("day"); + const todayPlus1Week = today.add(Temporal.Duration.from({ days: 7 })); + + const openmeet = new OpenMeetClient({ + token: authToken, + refreshToken, + }); + + const events = await openmeet.getEvents({ + start: today, + end: todayPlus1Week, + }) + console.log(JSON.stringify(events, null, 2)); +})(); diff --git a/openmeet.ts b/openmeet.ts new file mode 100644 --- /dev/null +++ b/openmeet.ts @@ -0,0 +1,68 @@ +export type Event = { + name: string; + slug: string; + startDate: string; + endDate: string; +}; + +type OpenMeetTokens = { + token: string; + refreshToken: string; + tokenExpires: number; +}; + +export class OpenMeetClient { + private API_BASE = "https://api.openmeet.net"; + private GROUP_SLUG = "devict"; + private TENANT_ID = "lsdfaopkljdfs"; + + private tokens: OpenMeetTokens; + + constructor(tokens: Omit) { + this.tokens = { ...tokens, tokenExpires: 0 }; + } + + async getEvents(opts: { + start: Temporal.PlainDateTime; + end: Temporal.PlainDateTime; + }, attempt = 1): Promise { + const url = new URL(`${this.API_BASE}/api/groups/${this.GROUP_SLUG}/events`); + + url.searchParams.set("startDate", opts.start.toString()); + url.searchParams.set("endDate", opts.end.toString()); + + const resp = await fetch(url, { + headers: { + "X-Tenant-Id": this.TENANT_ID, + Authorization: `Bearer ${this.tokens.token}` + }, + }); + + if (!resp.ok) { + if (resp.status === 401 && attempt < 3) { + await this.refreshTokens(); + return this.getEvents(opts, attempt +1); + } + throw new Error(`request failed: ${resp.status}`, { cause: await resp.json() }); + } + + return await resp.json() as Event[]; + } + + private async refreshTokens() { + const resp = await fetch(`${this.API_BASE}/api/v1/auth/refresh`, { + headers: { + Authorization: `Bearer ${this.tokens.refreshToken}`, + "X-Tenant-Id": this.TENANT_ID, + }, + method: "POST", + body: JSON.stringify({ ...this.tokens }), + }); + + if (!resp.ok) { + throw new Error(`request failed: ${resp.status}`, { cause: await resp.json() }); + } + + this.tokens = await resp.json() as OpenMeetTokens; + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,30 @@ +{ + "name": "devict-events", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "devict-events", + "devDependencies": { + "@types/node": "^25.9.1" + } + }, + "node_modules/@types/node": { + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "private": true, + "name": "devict-events", + "type": "module", + "scripts": { + "start": "node --env-file=.env main.ts" + }, + "devDependencies": { + "@types/node": "^25.9.1" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "noEmit": true, // Optional - see note below + "target": "esnext", + "module": "nodenext", + "rewriteRelativeImportExtensions": true, + "erasableSyntaxOnly": true, + "verbatimModuleSyntax": true + } +} -- tangled.sh