Skip to content

Commit

Permalink
feat(assets): basic asset provider
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Mar 26, 2021
1 parent fa9331f commit 990748e
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 0 deletions.
37 changes: 37 additions & 0 deletions packages/plugin-assets/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "koishi-plugin-assets",
"description": "GitHub webhook plugin for Koishi",
"version": "3.0.0",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"files": [
"dist"
],
"author": "Shigma <1700011071@pku.edu.cn>",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/koishijs/plugin-assets.git"
},
"bugs": {
"url": "https://github.com/koishijs/plugin-assets/issues"
},
"homepage": "https://koishi.js.org/plugins/other/assets",
"keywords": [
"bot",
"qqbot",
"cqhttp",
"coolq",
"chatbot",
"koishi",
"plugin",
"assets",
"server"
],
"peerDependencies": {
"koishi-core": "^3.4.0"
},
"dependencies": {
"axios": "^0.21.1"
}
}
137 changes: 137 additions & 0 deletions packages/plugin-assets/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { Assets, Context, Random, sanitize, trimSlash } from 'koishi-core'
import axios, { AxiosRequestConfig } from 'axios'
import { promises as fs, createReadStream, existsSync } from 'fs'
import { extname, resolve } from 'path'
import { createHmac } from 'crypto'

interface ServerConfig {
path?: string
root?: string
selfUrl?: string
secret?: string
axiosConfig?: AxiosRequestConfig
}

class AssetServer implements Assets {
types: ['video', 'audio', 'image']

private _promise: Promise<void>
private _stats: Assets.Stats = {
assetCount: 0,
assetSize: 0,
}

constructor(public ctx: Context, public config: ServerConfig) {
const path = sanitize(config.path || '/assets')
config.root ||= resolve(__dirname, '../public')

if (config.selfUrl) {
config.selfUrl = trimSlash(config.selfUrl)
} else if (!(config.selfUrl = ctx.app.options.selfUrl)) {
throw new Error(`missing configuration "selfUrl" or "server"`)
}

ctx.router.get(path, async (ctx) => {
return ctx.body = await this.stats()
})

ctx.router.get(path + '/:name', (ctx) => {
const filename = resolve(config.root, ctx.params.name)
ctx.type = extname(filename)
return ctx.body = createReadStream(filename)
})

ctx.router.post(path, async (ctx) => {
const { salt, sign, url, file } = ctx.query
if (Array.isArray(file) || Array.isArray(url)) {
return ctx.status = 400
}

if (config.secret) {
if (!salt || !sign) return ctx.status = 400
const hash = createHmac('sha1', config.secret).update(file + salt).digest('hex')
if (hash !== sign) return ctx.status = 403
}

await this.upload(url, file)
return ctx.status = 200
})

this._promise = this.init()
}

async init() {
const root = this.config.root
await fs.mkdir(root, { recursive: true })
const filenames = await fs.readdir(root)
this._stats.assetCount = filenames.length
await Promise.all(filenames.map(async (file) => {
const { size } = await fs.stat(resolve(root, file))
this._stats.assetSize += size
}))
}

async upload(url: string, file: string) {
await this._promise
const { selfUrl, path, root, axiosConfig } = this.config
const filename = resolve(root, file)
if (!existsSync(filename)) {
const { data } = await axios.get<ArrayBuffer>(url, {
...axiosConfig,
responseType: 'arraybuffer',
})
await fs.writeFile(filename, Buffer.from(data))
this._stats.assetCount += 1
this._stats.assetSize += data.byteLength
}
return `${selfUrl}${path}/${file}`
}

async stats() {
await this._promise
return this._stats
}
}

interface ClientConfig {
server: string
secret?: string
axiosConfig?: AxiosRequestConfig
}

class AssetClient implements Assets {
types: ['video', 'audio', 'image']

constructor(public ctx: Context, public config: ClientConfig) {}

async upload(url: string, file: string) {
const { server, secret, axiosConfig } = this.config
const params = { url, file } as any
if (secret) {
params.salt = Random.uuid()
params.sign = createHmac('sha1', secret).update(file + params.salt).digest('hex')
}
const { data } = await axios.post(server, { ...axiosConfig, params })
return data
}

async stats() {
const { data } = await axios.get(this.config.server, this.config.axiosConfig)
return data
}
}

export type Config = ServerConfig | ClientConfig

export const name = 'assets'

export function apply(ctx: Context, config: Config = {}) {
config.axiosConfig = {
...ctx.app.options.axiosConfig,
...config.axiosConfig,
}

ctx.assets = 'server' in config
? new AssetClient(ctx, config)
: new AssetServer(ctx, config)
}
10 changes: 10 additions & 0 deletions packages/plugin-assets/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.base",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
},
"include": [
"src",
],
}

0 comments on commit 990748e

Please sign in to comment.