From ec2beee084c1f5b97553b90789f4670ced9b63e9 Mon Sep 17 00:00:00 2001 From: Jersey Date: Mon, 8 Jul 2024 16:13:49 -0400 Subject: [PATCH] uploads --- src/api/index.ts | 1 + src/api/socket.ts | 12 ++++---- src/api/uploads.ts | 68 ++++++++++++++++++++++++++++++++++++++++++ src/interfaces/post.ts | 21 ++----------- tests/uploads.ts | 64 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 143 insertions(+), 23 deletions(-) create mode 100644 src/api/uploads.ts create mode 100644 tests/uploads.ts diff --git a/src/api/index.ts b/src/api/index.ts index 4606071..3577fb0 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -4,3 +4,4 @@ export * from './client.ts'; export * from './rest.ts'; export * from './socket.ts'; +export * from './uploads.ts'; diff --git a/src/api/socket.ts b/src/api/socket.ts index 14eee06..2657ee1 100644 --- a/src/api/socket.ts +++ b/src/api/socket.ts @@ -45,7 +45,7 @@ export class socket extends EventEmitter<{ [key: `listener-${string}`]: [socket_packet]; create_message: [post]; edit_message: [post]; - delete_message: [{ id: string}]; + delete_message: [{ id: string }]; }> { private socket: WebSocket; private opts: socket_connect_opts; @@ -68,7 +68,7 @@ export class socket extends EventEmitter<{ 'val': 'js', }, }); - + this.send({ 'cmd': 'authpswd', 'val': { @@ -76,7 +76,7 @@ export class socket extends EventEmitter<{ 'pswd': this.opts.api_token, }, }); - + setInterval(() => { if (this.socket.readyState === 1) { this.send({ @@ -85,7 +85,7 @@ export class socket extends EventEmitter<{ }); } }, 30000); - } + }; this.socket.onclose = () => this.emit('socket_close'); @@ -111,7 +111,9 @@ export class socket extends EventEmitter<{ ) return; if (packet.val.p) { - const event = 'payload' in packet.val ? 'edit_message' : 'create_message'; + const event = 'payload' in packet.val + ? 'edit_message' + : 'create_message'; const api = (packet.val.payload ?? packet.val) as unknown as api_post; const p = new post({ api_token: this.opts.api_token, diff --git a/src/api/uploads.ts b/src/api/uploads.ts new file mode 100644 index 0000000..fb10fb4 --- /dev/null +++ b/src/api/uploads.ts @@ -0,0 +1,68 @@ +/** api attachment */ +export interface api_attachment { + /** filename */ + filename: string; + /** file type */ + mime: string; + /** file size */ + size: number; + /** image height */ + height?: number; + /** image width */ + width?: number; + /** file id */ + id: string; +} + +/** uploads class construction options */ +export interface uploads_opts { + /** base url for uploads */ + base_url: string; + /** an api token */ + token: string; +} + +/** upload types */ +export enum upload_types { + attachment = 'attachments', + icon = 'icons', +} + +/** access to meower uploads */ +export class uploads { + private opts: uploads_opts; + + constructor(opts: uploads_opts) { + this.opts = opts; + } + + /** upload a file */ + async upload_file( + file: Blob, + upload_type: upload_types = upload_types.attachment, + ): Promise { + const form = new FormData(); + form.append('file', file); + const res = await fetch(`${this.opts.base_url}/${upload_type}`, { + method: 'POST', + headers: { + Authorization: `Bearer ${this.opts.token}`, + }, + body: form, + }); + if (!res.ok) { + throw new Error('failed to upload file', { + cause: await res.json(), + }); + } + return await res.json(); + } + + /** get the url for an attachment */ + get_file_url( + file: api_attachment, + upload_type: upload_types = upload_types.attachment, + ): string { + return `${this.opts.base_url}/${upload_type}/${file.id}/${file.filename}`; + } +} diff --git a/src/interfaces/post.ts b/src/interfaces/post.ts index 9e779f9..b46350a 100644 --- a/src/interfaces/post.ts +++ b/src/interfaces/post.ts @@ -1,3 +1,4 @@ +import type { api_attachment } from '../api/uploads.ts'; import type { message_send_opts } from './chat.ts'; /** types of posts */ @@ -11,26 +12,10 @@ export enum post_type { /** bridge users */ export const bridge_users = ['Discord', 'boltcanary', 'bolt']; -/** api attachment */ -export interface api_attachment { - /** filename */ - filename: string; - /** file type */ - mime: string; - /** file size */ - size: number; - /** image height */ - height?: number; - /** image width */ - width?: number; - /** file id */ - id: string; -} - /** raw post data */ export interface api_post { /** attachments */ - attachments?: string[]; + attachments?: api_attachment[]; /** is the post pinned */ pinned: boolean; /** bridged post */ @@ -118,7 +103,7 @@ export class post { private api_username: string; private raw: api_post; /** attachments */ - attachments?: string[]; + attachments?: api_attachment[]; /** post id */ id!: string; /** whether the post in pinned */ diff --git a/tests/uploads.ts b/tests/uploads.ts new file mode 100644 index 0000000..6bae5a8 --- /dev/null +++ b/tests/uploads.ts @@ -0,0 +1,64 @@ +import { uploads } from '../src/api/uploads.ts'; +import { assertEquals, assertRejects, mockFetch } from './internal/deps.ts'; + +Deno.test('attachment file url', () => { + const u = new uploads({ + base_url: 'http://localhost:8080', + token: 'test', + }); + + assertEquals( + u.get_file_url({ + filename: 'test.txt', + id: '1234', + mime: 'text/plain', + size: 1234, + }), + 'http://localhost:8080/attachments/1234/test.txt', + ); +}); + +Deno.test('attachment upload', async (i) => { + const u = new uploads({ + base_url: 'http://localhost:8080', + token: 'test', + }); + + await i.step('successful', async () => { + mockFetch('http://localhost:8080/attachments', { + body: JSON.stringify({ + filename: 'string', + mime: 'string', + size: 1, + id: 'string', + }), + }); + + const file = new Blob(['test'], { type: 'text/plain' }); + + const res = await u.upload_file(file); + + assertEquals(res, { + filename: 'string', + mime: 'string', + size: 1, + id: 'string', + }); + }); + + await i.step('failure', async () => { + mockFetch('http://localhost:8080/attachment', { + status: 500, + body: JSON.stringify({ + error: true, + message: 'notFound', + }), + }); + + const file = new Blob(['test'], { type: 'text/plain' }); + + await assertRejects(async () => { + await u.upload_file(file); + }); + }); +});