From 0617663b249f9ca488e5de652108b17d67fbaf45 Mon Sep 17 00:00:00 2001 From: Vika Date: Sat, 29 Jul 2023 21:59:56 +0300 Subject: Moved the entire Kittybox tree into the root --- companion-lite/src/micropub_api.ts | 125 +++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 companion-lite/src/micropub_api.ts (limited to 'companion-lite/src/micropub_api.ts') diff --git a/companion-lite/src/micropub_api.ts b/companion-lite/src/micropub_api.ts new file mode 100644 index 0000000..fa1c431 --- /dev/null +++ b/companion-lite/src/micropub_api.ts @@ -0,0 +1,125 @@ +export interface MicropubChannel { + readonly uid: string, + readonly name: string +} + +export interface MF2 { + type: string[], + properties: { [key:string]: (string | MF2 | {[key:string]: string})[] | undefined } +} + +export interface MicropubConfig { + readonly channels?: MicropubChannel[], + readonly "media-endpoint"?: string +} + +export interface MicropubErrorMessage { + readonly error: string, + readonly error_description: string | undefined +} + +export class MicropubError extends Error { + readonly status: number | null + readonly response: MicropubErrorMessage | null + + constructor(status: number | null, response: MicropubErrorMessage | null, cause: Error | null = null) { + // Needs to pass both `message` and `options` to install the "cause" property. + if (status == null) { + super("Micropub endpoint didn't respond properly", { cause }); + } else if (response == null) { + super(`Micropub endpoint returned HTTP ${status}`, { cause }); + } else { + super( + `Micropub endpoint returned ${response.error}: ${response.error_description ?? "(no description was provided)"}`, + { cause } + ) + } + + this.status = status; + this.response = response; + } +} + +export class Micropub { + readonly token: string + readonly micropub_endpoint: URL + private config_response: MicropubConfig | null + + constructor({ endpoint, token }: { endpoint: URL, token: string }) { + this.micropub_endpoint = endpoint; + this.token = token; + this.config_response = null; + } + + async config(): Promise { + if (this.config_response != null) { + return this.config_response + } + let url = this.micropub_endpoint; + let params = new URLSearchParams(); + for (const [key, val] of url.searchParams) { + params.append(key, val) + } + params.set("q", "config") + + url.search = "?" + params.toString(); + + const response = await fetch(url, { + headers: { + "Authorization": `Bearer ${this.token}` + } + }); + if (response.ok) { + const config = await response.json() as MicropubConfig; + this.config_response = config + + return config + } else { + throw new MicropubError(response.status, await response.json() as MicropubErrorMessage); + } + } + + async submit(mf2: MF2): Promise { + const response = await fetch(this.micropub_endpoint, { + method: "POST", + headers: { + "Authorization": `Bearer ${this.token}`, + "Content-Type": "application/json" + }, + body: JSON.stringify(mf2) + }) + + if (response.status != 201 && response.status != 202) { + let err = await response.json() as MicropubErrorMessage; + + throw new MicropubError(response.status, err) + } else { + return new URL(response.headers.get("Location") as string) + } + } + + async upload(file: File): Promise { + const config = await this.config(); + const media = config["media-endpoint"]; + if (media == null) { + throw new Error("Micropub endpoint doesn't support file uploads") + } + + const form = new FormData(); + form.set("file", file); + + const response = await fetch(media, { + method: "POST", + headers: { + "Authorization": `Bearer ${this.token}`, + }, + body: form + }) + + if (response.ok) { + return new URL(response.headers.get("Location") as string) + } else { + throw new MicropubError(response.status, await response.json()); + } + } +} -- cgit 1.4.1