Skip to content

Commit

Permalink
feat: support universal upload response
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed May 21, 2024
1 parent 4ae699b commit fbfa005
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 42 deletions.
32 changes: 19 additions & 13 deletions adapters/satori/src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,25 @@ export class SatoriBot<C extends Context = Context> extends Bot<C, Universal.Log

for (const [key, method] of Object.entries(Universal.Methods)) {
SatoriBot.prototype[method.name] = async function (this: SatoriBot, ...args: any[]) {
const payload = {}
for (const [index, field] of method.fields.entries()) {
if (method.name === 'createMessage' && field.name === 'content') {
const session = this.session({
type: 'send',
channel: { id: args[0], type: 0 },
...args[3]?.session?.event,
})
session.elements = await session.transform(h.normalize(args[index]))
if (await session.app.serial(session, 'before-send', session, args[3] ?? {})) return
payload[field.name] = session.elements.join('')
} else {
payload[field.name] = transformKey(args[index], snakeCase)
let payload: any
if (method.name === 'createUpload') {
payload = new FormData()
payload.append('file', new Blob([args[0]], { type: args[1] }), args[2])
} else {
payload = {}
for (const [index, field] of method.fields.entries()) {
if (method.name === 'createMessage' && field.name === 'content') {
const session = this.session({
type: 'send',
channel: { id: args[0], type: 0 },
...args[3]?.session?.event,
})
session.elements = await session.transform(h.normalize(args[index]))
if (await session.app.serial(session, 'before-send', session, args[3] ?? {})) return
payload[field.name] = session.elements.join('')
} else {
payload[field.name] = transformKey(args[index], snakeCase)
}
}
}
this.logger.debug('[request]', key, payload)
Expand Down
13 changes: 12 additions & 1 deletion adapters/satori/src/ws.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Adapter, camelize, Context, HTTP, Logger, Schema, Time, Universal } from '@satorijs/core'
import { Adapter, camelize, Context, HTTP, Logger, pick, Schema, Time, Universal } from '@satorijs/core'
import { SatoriBot, transformKey } from './bot'

export class SatoriAdapter<C extends Context = Context> extends Adapter.WsClientBase<C, SatoriBot<C>> {
Expand Down Expand Up @@ -129,6 +129,17 @@ export class SatoriAdapter<C extends Context = Context> extends Adapter.WsClient
this.socket.addEventListener('close', () => {
clearInterval(this.timeout)
})

this.ctx.satori.upload(() => {
return this.bots
.flatMap(bot => bot.resourceUrls)
.filter(url => url.startsWith('upload://'))
.map(url => url.replace('upload://', ''))
}, async (path) => {
path = path.replace(/^\//g, '')
const response = await this.http('/v1/proxy/upload://' + path, { responseType: 'arraybuffer' })
return pick(response, ['status', 'data', 'headers', 'statusText'])
})
}

async start() {
Expand Down
9 changes: 4 additions & 5 deletions packages/core/src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import h from '@satorijs/element'
import { Adapter } from './adapter'
import { MessageEncoder } from './message'
import { defineAccessor, Session } from './session'
import { Event, List, Login, Methods, SendOptions, Status, Upload, User } from '@satorijs/protocol'
import { Universal, UploadResult } from '.'
import { Event, List, Login, Methods, Response, SendOptions, Status, Upload, User } from '@satorijs/protocol'

const eventAliases = [
['message-created', 'message'],
Expand Down Expand Up @@ -53,7 +52,7 @@ export abstract class Bot<C extends Context = Context, T = any> implements Login
}

this.resourceUrls = [`upload://temp/${ctx.satori.uid}/`]
this.features = Object.entries(Universal.Methods)
this.features = Object.entries(Methods)
.filter(([, value]) => this[value.name])
.map(([key]) => key)

Expand All @@ -73,7 +72,7 @@ export abstract class Bot<C extends Context = Context, T = any> implements Login
return self
}

registerUpload(path: string, callback: (path: string) => Promise<UploadResult>) {
registerUpload(path: string, callback: (path: string) => Promise<Response>) {
this.ctx.satori.upload(path, callback, this.resourceUrls)
}

Expand Down Expand Up @@ -193,7 +192,7 @@ export abstract class Bot<C extends Context = Context, T = any> implements Login
return this.sendMessage(id, content, null, options)
}

async createUpload(data: UploadResult['data'], type: string, name?: string): Promise<Upload> {
async createUpload(data: ArrayBuffer, type: string | null, name?: string): Promise<Upload> {
const result = { status: 200, data, type, name }
const id = Math.random().toString(36).slice(2)
this.ctx.satori._tempStore[id] = result
Expand Down
30 changes: 18 additions & 12 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Context, Logger, Service, z } from 'cordis'
import { Awaitable, defineProperty, Dict, makeArray, remove } from 'cosmokit'
import { ReadableStream } from 'node:stream/web'
import { Bot } from './bot'
import { Session } from './session'
import { HTTP } from '@cordisjs/plugin-http'
import { SendOptions } from '@satorijs/protocol'
import { Response, SendOptions } from '@satorijs/protocol'
import h from '@satorijs/element'

h.warn = new Logger('element').warn
Expand Down Expand Up @@ -125,17 +124,9 @@ class SatoriContext extends Context {

export { SatoriContext as Context }

export interface UploadResult {
status: number
data?: ArrayBuffer | ReadableStream
type?: string
name?: string
url?: string
}

export interface UploadRoute {
path: string | string[] | (() => string | string[])
callback: (path: string) => Promise<UploadResult>
callback: (path: string) => Promise<Response>
}

export class Satori<C extends Context = Context> extends Service<unknown, C> {
Expand All @@ -145,15 +136,30 @@ export class Satori<C extends Context = Context> extends Service<unknown, C> {
public uid = Math.random().toString(36).slice(2)

_uploadRoutes: UploadRoute[] = []
_tempStore: Dict<UploadResult> = Object.create(null)
_tempStore: Dict<Response> = Object.create(null)

constructor(ctx?: C) {
super(ctx)
ctx.mixin('satori', ['bots', 'component'])

this.upload(`/temp/${this.uid}/`, async (path) => {
const id = path.split('/').pop()
return this._tempStore[id] ?? { status: 404 }
})

const self = this
;(ctx as Context).on('http/file', async function (url, options) {
if (!url.startsWith('upload://')) return
const { status, data, headers } = await self.download(url.slice(9))
if (status >= 400) throw new Error(`Failed to fetch ${url}, status code: ${status}`)
if (status >= 300) {
const location = headers?.get('location')
return this.file(location, options)
}
const mime = headers?.get('content-type')
const filename = headers?.get('content-disposition')?.split('filename=')[1]
return { data, filename, mime }
})
}

public bots = new Proxy([], {
Expand Down
11 changes: 9 additions & 2 deletions packages/protocol/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ export interface SendOptions {
linkPreview?: boolean
}

export interface Response {
status: number
statusText?: string
data?: ArrayBuffer
headers?: Headers
}

export interface Field {
name: string
}
Expand Down Expand Up @@ -42,7 +49,7 @@ export const Methods: Dict<Method> = {
'reaction.clear': Method('clearReaction', ['channel_id', 'message_id', 'emoji']),
'reaction.list': Method('getReactionList', ['channel_id', 'message_id', 'emoji', 'next']),

'upload.create': Method('createUpload', ['data', 'type', 'name'], true),
'upload.create': Method('createUpload', ['data', 'type'], true),

'guild.get': Method('getGuild', ['guild_id']),
'guild.list': Method('getGuildList', ['next']),
Expand Down Expand Up @@ -104,7 +111,7 @@ export interface Methods {
getReactionIter(channelId: string, messageId: string, emoji: string): AsyncIterable<User>

// upload
createUpload(data: ArrayBuffer, type: string, name?: string): Promise<Upload>
createUpload(data: ArrayBuffer, type: string | null, name?: string): Promise<Upload>

// user
getLogin(): Promise<Login>
Expand Down
33 changes: 24 additions & 9 deletions packages/server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { camelCase, Context, sanitize, Schema, Session, snakeCase, Time, Universal } from '@satorijs/core'
import {} from '@cordisjs/plugin-server'
import WebSocket from 'ws'
import { Readable } from 'stream'
import { ReadableStream } from 'stream/web'
import { Readable } from 'node:stream'
import { ReadableStream } from 'node:stream/web'
import { readFile } from 'node:fs/promises'

export const name = 'server'
export const inject = ['server', 'http']
Expand Down Expand Up @@ -95,6 +96,18 @@ export function apply(ctx: Context, config: Config) {
return koa.status = 403
}

if (method.name === 'createUpload') {
const [file] = Object.values(koa.request.files ?? {}).flat()
if (!file) {
koa.body = 'file not provided'
return koa.status = 400
}
const data = await readFile(file.filepath)
const result = await bot.createUpload(data, file.mimetype, file.newFilename)
koa.body = result
return koa.status = 201
}

const json = koa.request.body
const args = method.fields.map(({ name }) => {
return transformKey(json[name], camelCase)
Expand Down Expand Up @@ -124,13 +137,15 @@ export function apply(ctx: Context, config: Config) {
})

ctx.server.get(path + '/v1/upload/:name(.+)', async (koa) => {
const result = await ctx.satori.download(koa.params.name)
koa.status = result.status
if (result.status >= 300 && result.status < 400) {
koa.set('Location', result.url!)
} else if (result.status >= 200 && result.status < 300) {
koa.body = result.data instanceof ReadableStream ? Readable.fromWeb(result.data) : result.data
koa.type = result.type!
const { status, statusText, data, headers } = await ctx.satori.download(koa.params.name)
koa.status = status
for (const [key, value] of headers || new Headers()) {
koa.set(key, value)
}
if (status >= 200 && status < 300) {
koa.body = data instanceof ReadableStream ? Readable.fromWeb(data) : data
} else {
koa.body = statusText
}
})

Expand Down

0 comments on commit fbfa005

Please sign in to comment.