Commit 67646c15 authored by nanahira's avatar nanahira

add middleware framework

parent e6233815
Pipeline #11792 canceled with stage
// import 'source-map-support/register';
import { DefineSchema, RegisterSchema, SchemaClass } from 'koishi-thirdeye';
import { Quester } from 'koishi';
import { PicMiddleware } from './index';
@RegisterSchema()
export class PicsPluginConfig {
......@@ -58,3 +59,19 @@ export class PicSourceConfig implements PicSourceInfo {
}
export const PicSourceSchema = SchemaClass(PicSourceConfig);
export interface PicMiddlewareInfo {
name?: string;
prepend?: boolean;
}
export class PicMiddlewareConfig {
constructor(config: PicMiddlewareInfo) {}
name: string;
prepend: boolean;
applyTo(target: PicMiddleware) {
target.name = this.name;
target.prepend = this.prepend;
}
}
// import 'source-map-support/register';
import { Context, Assets, Awaitable, Random, Logger, Bot } from 'koishi';
import { PicSourceInfo, PicsPluginConfig } from './config';
import {
Context,
Assets,
Awaitable,
Random,
Logger,
Bot,
remove,
} from 'koishi';
import {
PicMiddlewareConfig,
PicMiddlewareInfo,
PicSourceInfo,
PicsPluginConfig,
} from './config';
import _ from 'lodash';
import { segment, Quester } from 'koishi';
import {
BasePlugin,
Caller,
ClassType,
DefinePlugin,
Inject,
InjectLogger,
LifecycleEvents,
Provide,
} from 'koishi-thirdeye';
import { AxiosRequestConfig } from 'axios';
import { PicAssetsTransformMiddleware } from './middlewares/assets';
import { PicDownloaderMiddleware } from './middlewares/download';
import { PicMiddleware, PicNext } from './middleware';
export * from './config';
export * from './middleware';
declare module 'koishi' {
// eslint-disable-next-line @typescript-eslint/no-namespace
......@@ -65,6 +84,7 @@ export default class PicsContainer
implements LifecycleEvents
{
private sources = new Map<PicSource, () => boolean>();
private picMiddlewares: PicMiddleware[] = [];
@Caller()
private caller: Context;
......@@ -72,9 +92,6 @@ export default class PicsContainer
@InjectLogger()
private logger: Logger;
@Inject()
private assets: Assets;
@Inject(true)
private http: Quester;
......@@ -108,6 +125,24 @@ export default class PicsContainer
return Array.from(this.sources.keys());
}
middleware(mid: PicMiddleware, targetCtx?: Context) {
const processingCtx: Context = targetCtx || this.caller;
mid.name ||= processingCtx.state?.plugin?.name;
const disposable = processingCtx.on('dispose', () => {
disposable();
this.removeMiddlware(mid);
});
if (mid.prepend) {
this.picMiddlewares.unshift(mid);
} else {
this.picMiddlewares.push(mid);
}
}
removeMiddlware(mid: PicMiddleware) {
remove(this.picMiddlewares, mid);
}
pickAvailableSources(sourceTags: string[] = [], includeNonDefault = false) {
let sources = this.allSources();
if (sourceTags.length) {
......@@ -188,32 +223,63 @@ export default class PicsContainer
);
}
async getSegment(url: string, bot?: Bot) {
let useFileHeader = false;
async download(url: string, extraConfig: AxiosRequestConfig = {}) {
if (url.startsWith('base64://')) {
return url;
}
const buf = await this._http.get(url, {
responseType: 'arraybuffer',
...extraConfig,
});
return `base64://${buf.toString('base64')}`;
}
async resolveUrl(url: string, middlwares = this.picMiddlewares) {
if (!middlwares.length) {
return url;
}
const next: PicNext = async (nextUrl) => {
nextUrl ||= url;
const nextResult = await this.resolveUrl(nextUrl, middlwares.slice(1));
return nextResult || nextUrl;
};
try {
if (this.config.useAssets && this.assets) {
const uploadedUrl = await this.assets.upload(url, undefined);
url = uploadedUrl;
} else if (this.config.useBase64 && url.startsWith('http')) {
const buf = await this._http.get(url, {
responseType: 'arraybuffer',
});
url = `base64://${buf.toString('base64')}`;
useFileHeader = true;
let result = await middlwares[0].use(url, next);
if (!result) {
this.logger.warn(
`Got empty result from middleware ${middlwares[0].name || '???'}`,
);
result = url;
}
return result;
} catch (e) {
this.logger.warn(`Download image ${url} failed: ${e.toString()}`);
this.logger.warn(`Resolve url ${url} failed: ${e.toString()}`);
return url;
}
const isOneBotBot = this.isOneBotBot(bot);
}
async getSegment(url: string, bot?: Bot) {
url = await this.resolveUrl(url);
const picData: segment.Data = {
[isOneBotBot && useFileHeader ? 'file' : 'url']: url,
[url.startsWith('base64://') && this.isOneBotBot(bot) ? 'file' : 'url']:
url,
cache: true,
};
return segment('image', picData);
}
async onApply() {
private installDefaultMiddlewares() {
if (this.config.useAssets) {
this.ctx.plugin(PicAssetsTransformMiddleware);
}
if (this.config.useBase64) {
this.ctx.plugin(PicDownloaderMiddleware);
}
}
onApply() {
this._http = this.http.extend(this.config.httpConfig);
this.installDefaultMiddlewares();
const ctx = this.ctx;
ctx.i18n.define('zh', `commands.${this.config.commandName}`, {
description: '获取随机图片',
......
import { Awaitable, Logger } from 'koishi';
import { PicMiddlewareConfig, PicMiddlewareInfo } from './config';
import {
BasePlugin,
Inject,
InjectLogger,
LifecycleEvents,
} from 'koishi-thirdeye';
import PicsContainer from './index';
export type PicNext = (url?: string) => Awaitable<string>;
export interface PicMiddleware extends PicMiddlewareInfo {
use(url: string, next: PicNext): Awaitable<string>;
}
export class PicMiddlewareBase<
C extends PicMiddlewareConfig = PicMiddlewareConfig,
>
extends BasePlugin<C>
implements PicMiddleware, LifecycleEvents
{
@Inject(true)
protected pics: PicsContainer;
@InjectLogger()
protected logger: Logger;
onApply() {
this.config.applyTo(this);
this.pics.middleware(this);
}
use(url: string, next: PicNext): Awaitable<string> {
return next(url);
}
}
import { DefinePlugin, Inject } from 'koishi-thirdeye';
import { Assets } from 'koishi';
import { PicMiddlewareBase, PicNext } from '../middleware';
import { PicMiddlewareConfig } from '../config';
@DefinePlugin({ schema: PicMiddlewareConfig })
export class PicAssetsTransformMiddleware extends PicMiddlewareBase {
@Inject()
private assets: Assets;
override async use(url: string, next: PicNext) {
if (!this.assets) {
return next();
}
const transformed = await this.assets.upload(url, undefined);
return next(transformed);
}
}
import { DefinePlugin } from 'koishi-thirdeye';
import { PicMiddlewareBase, PicNext } from '../middleware';
import { PicMiddlewareConfig } from '../config';
@DefinePlugin({ schema: PicMiddlewareConfig })
export class PicDownloaderMiddleware extends PicMiddlewareBase {
override async use(url: string, next: PicNext) {
if (url.startsWith('base64://')) {
return next();
}
const downloadedUrl = await this.pics.download(url);
return next(downloadedUrl);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment