Skip to content

Commit

Permalink
refa: introduce JsonForm utils
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Dec 30, 2024
1 parent 7cf7c58 commit efbc31b
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 77 deletions.
36 changes: 7 additions & 29 deletions adapters/satori/src/bot.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Bot, camelCase, Context, Dict, h, HTTP, snakeCase, Universal, valueMap } from '@satorijs/core'
import { Bot, camelCase, Context, h, HTTP, JsonForm, snakeCase, Universal } from '@satorijs/core'

export function transformKey(source: any, callback: (key: string) => string) {
if (!source || typeof source !== 'object') return source
Expand All @@ -9,40 +9,18 @@ export function transformKey(source: any, callback: (key: string) => string) {
}))
}

function serialize(data: any, path: string, blobs: Dict<Blob>) {
if (!data || typeof data !== 'object') return data
if (data instanceof Blob) {
blobs[path] = data
return null
}
if (Array.isArray(data)) {
return data.map((value, index) => serialize(value, `${path}.${index}`, blobs))
}
return valueMap(data, (value, key) => {
return serialize(value, `${path}.${key}`, blobs)
})
}

function createInternal(bot: SatoriBot, prefix = '') {
return new Proxy(() => {}, {
apply(target, thisArg, args) {
const key = snakeCase(prefix.slice(1))
const key = prefix.slice(1)
bot.logger.debug('[request.internal]', key, args)
const blobs: Dict<Blob> = Object.create(null)
const data = serialize(args, '$', blobs)
if (!Object.keys(blobs).length) {
return bot.http.post('/v1/internal/_api/' + key, args)
}
const form = new FormData()
form.append('$', JSON.stringify(data))
for (const [key, value] of Object.entries(blobs)) {
if (value instanceof File) {
form.append(key, value, value.name)
} else {
form.append(key, value)
}
args = JsonForm.dump(args, '$', form)
if (![...form.entries()].length) {
return bot.http.post('/v1/' + bot.getInternalUrl(`/_api/${key}`, {}, true), args)
}
return bot.http.post('/v1/internal/_api/' + key, form)
form.append('$', JSON.stringify(args))
return bot.http.post('/v1/' + bot.getInternalUrl(`/_api/${key}`, {}, true), form)
},
get(target, key, receiver) {
if (typeof key === 'symbol' || key in target) {
Expand Down
37 changes: 34 additions & 3 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Context, Logger, Service, z } from 'cordis'
import { Awaitable, defineProperty, Dict } from 'cosmokit'
import { Bot } from './bot'
import { ExtractParams, InternalRequest, InternalRouter } from './internal'
import { ExtractParams, InternalRequest, InternalRouter, JsonForm } from './internal'
import { Session } from './session'
import { HTTP } from '@cordisjs/plugin-http'
import { Response, SendOptions } from '@satorijs/protocol'
Expand Down Expand Up @@ -199,6 +199,37 @@ export class Satori<C extends Context = Context> extends Service<unknown, C> {
if (method !== 'GET') return { status: 405 }
return this._tempStore[params.id] ?? { status: 404 }
})

this.defineInternalRoute('/_api/:name', async ({ bot, headers, params, method, body }) => {
if (method !== 'POST') return { status: 405 }
const type = headers['content-type']
let args: any
if (type?.startsWith('multipart/form-data')) {
const response = new globalThis.Response(body, { headers })
const form = await response.formData()
const rawData = form.get('$') as string
try {
args = JSON.parse(rawData)
} catch {
return { status: 400 }
}
args = JsonForm.load(args, '$', form)
} else {
args = JSON.parse(new TextDecoder().decode(body))
}
try {
const result = await bot.internal[params.name](...args)
const body = new TextEncoder().encode(JSON.stringify(result))
const headers = new Headers()
if (body.byteLength) {
headers.set('content-type', 'application/json')
}
return { body, headers, status: 200 }
} catch (error) {
if (!ctx.http.isError(error) || !error.response) throw error
return error.response
}
})
}

public bots = new Proxy([], {
Expand Down Expand Up @@ -240,8 +271,8 @@ export class Satori<C extends Context = Context> extends Service<unknown, C> {
const [, platform, selfId, path] = capture
const bot = this.bots[`${platform}:${selfId}`]
if (!bot) return { status: 404 }
let response = await bot._internalRouter.handle(bot, method, path, url.searchParams, headers, body)
response ??= await this._internalRouter.handle(bot, method, path, url.searchParams, headers, body)
let response = await this._internalRouter.handle(bot, method, path, url.searchParams, headers, body)
response ??= await bot._internalRouter.handle(bot, method, path, url.searchParams, headers, body)
if (!response) return { status: 404 }
return response
}
Expand Down
30 changes: 29 additions & 1 deletion packages/core/src/internal.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Service } from 'cordis'
import { Dict, remove } from 'cosmokit'
import { Dict, remove, valueMap } from 'cosmokit'
import { HTTP } from '@cordisjs/plugin-http'
import { Response } from '@satorijs/protocol'
import { Key, pathToRegexp } from 'path-to-regexp'
Expand Down Expand Up @@ -105,3 +105,31 @@ export class InternalRouter<C extends Context> {
}
}
}

export namespace JsonForm {
export function load(data: any, path: string, form: FormData) {
const value = form.get(path)
if (value instanceof File) return value
if (!data || typeof data !== 'object') return data
if (Array.isArray(data)) {
return data.map((value, index) => load(value, `${path}.${index}`, form))
}
return valueMap(data, (value, key) => {
return load(value, `${path}.${key}`, form)
})
}

export function dump(data: any, path: string, form: FormData) {
if (!data || typeof data !== 'object') return data
if (data instanceof Blob) {
form.append(path, data)
return null
}
if (Array.isArray(data)) {
return data.map((value, index) => dump(value, `${path}.${index}`, form))
}
return valueMap(data, (value, key) => {
return dump(value, `${path}.${key}`, form)
})
}
}
2 changes: 1 addition & 1 deletion packages/core/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export class Session<C extends Context = Context> {
event.message.content = this.content
delete event.message.elements
if (event.message.quote) {
event.message.content = `<quote id="${event.message.quote.id}">${event.message.quote.content}</quote> ` + event.message.content
event.message.content = `<quote id="${event.message.quote.id}">${event.message.quote.content}</quote>` + event.message.content
}
}
return event
Expand Down
3 changes: 1 addition & 2 deletions packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
"@satorijs/core": "^4.3.3"
},
"dependencies": {
"cosmokit": "^1.6.3",
"parse-multipart-data": "^1.5.0"
"cosmokit": "^1.6.3"
}
}
42 changes: 1 addition & 41 deletions packages/server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Context, Schema, Service, Session, Universal } from '@satorijs/core'
import { Binary, camelCase, defineProperty, Dict, makeArray, sanitize, snakeCase, Time, valueMap } from 'cosmokit'
import { Binary, camelCase, defineProperty, makeArray, sanitize, snakeCase, Time } from 'cosmokit'
import {} from '@cordisjs/plugin-server'
import WebSocket from 'ws'
import { Readable } from 'node:stream'
import { readFile } from 'node:fs/promises'
import { Middleware, ParameterizedContext } from 'koa'
import { getBoundary, parse } from 'parse-multipart-data'

declare module '@satorijs/core' {
interface Satori {
Expand All @@ -28,17 +27,6 @@ function transformKey(source: any, callback: (key: string) => string) {
}))
}

function deserialize(data: any, path: string, blobs: Dict<Blob>) {
if (path in blobs) return blobs[path]
if (!data || typeof data !== 'object') return data
if (Array.isArray(data)) {
return data.map((value, index) => deserialize(value, `${path}.${index}`, blobs))
}
return valueMap(data, (value, key) => {
return deserialize(value, `${path}.${key}`, blobs)
})
}

const FILTER_HEADERS = [
'host',
'authorization',
Expand All @@ -56,34 +44,6 @@ class SatoriServer extends Service<SatoriServer.Config> {
const logger = ctx.logger('server')
const path = sanitize(config.path)

ctx.satori.defineInternalRoute('/_api/:name', async ({ bot, headers, params, method, body }) => {
if (method !== 'POST') return { status: 405 }
const type = headers['content-type']
const boundary = getBoundary(type)
let args: any
if (boundary) {
const blobs: Dict<Blob> = {}
const fields: Dict<string> = {}
for (const { name, type, data, filename } of parse(Buffer.from(body), boundary)) {
if (type) {
blobs[name!] = new File([data], filename!, { type })
} else {
fields[name!] = data.toString()
}
}
args = deserialize(JSON.parse(fields.$), '$', blobs)
} else {
args = JSON.parse(new TextDecoder().decode(body))
}
try {
const result = await bot.internal[camelCase(params.name)](...args)
return { body: result, status: 200 }
} catch (error) {
if (!ctx.http.isError(error) || !error.response) throw error
return error.response
}
})

function checkAuth(koa: ParameterizedContext) {
if (!config.token) return
if (koa.request.headers.authorization !== `Bearer ${config.token}`) {
Expand Down

0 comments on commit efbc31b

Please sign in to comment.