Commit 67646c15 authored by nanahira's avatar nanahira

add middleware framework

parent e6233815
Pipeline #11792 canceled with stage
// import 'source-map-support/register'; // import 'source-map-support/register';
import { DefineSchema, RegisterSchema, SchemaClass } from 'koishi-thirdeye'; import { DefineSchema, RegisterSchema, SchemaClass } from 'koishi-thirdeye';
import { Quester } from 'koishi'; import { Quester } from 'koishi';
import { PicMiddleware } from './index';
@RegisterSchema() @RegisterSchema()
export class PicsPluginConfig { export class PicsPluginConfig {
...@@ -58,3 +59,19 @@ export class PicSourceConfig implements PicSourceInfo { ...@@ -58,3 +59,19 @@ export class PicSourceConfig implements PicSourceInfo {
} }
export const PicSourceSchema = SchemaClass(PicSourceConfig); 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 'source-map-support/register';
import { Context, Assets, Awaitable, Random, Logger, Bot } from 'koishi'; import {
import { PicSourceInfo, PicsPluginConfig } from './config'; Context,
Assets,
Awaitable,
Random,
Logger,
Bot,
remove,
} from 'koishi';
import {
PicMiddlewareConfig,
PicMiddlewareInfo,
PicSourceInfo,
PicsPluginConfig,
} from './config';
import _ from 'lodash'; import _ from 'lodash';
import { segment, Quester } from 'koishi'; import { segment, Quester } from 'koishi';
import { import {
BasePlugin, BasePlugin,
Caller, Caller,
ClassType,
DefinePlugin, DefinePlugin,
Inject, Inject,
InjectLogger, InjectLogger,
LifecycleEvents, LifecycleEvents,
Provide, Provide,
} from 'koishi-thirdeye'; } 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 './config';
export * from './middleware';
declare module 'koishi' { declare module 'koishi' {
// eslint-disable-next-line @typescript-eslint/no-namespace // eslint-disable-next-line @typescript-eslint/no-namespace
...@@ -65,6 +84,7 @@ export default class PicsContainer ...@@ -65,6 +84,7 @@ export default class PicsContainer
implements LifecycleEvents implements LifecycleEvents
{ {
private sources = new Map<PicSource, () => boolean>(); private sources = new Map<PicSource, () => boolean>();
private picMiddlewares: PicMiddleware[] = [];
@Caller() @Caller()
private caller: Context; private caller: Context;
...@@ -72,9 +92,6 @@ export default class PicsContainer ...@@ -72,9 +92,6 @@ export default class PicsContainer
@InjectLogger() @InjectLogger()
private logger: Logger; private logger: Logger;
@Inject()
private assets: Assets;
@Inject(true) @Inject(true)
private http: Quester; private http: Quester;
...@@ -108,6 +125,24 @@ export default class PicsContainer ...@@ -108,6 +125,24 @@ export default class PicsContainer
return Array.from(this.sources.keys()); 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) { pickAvailableSources(sourceTags: string[] = [], includeNonDefault = false) {
let sources = this.allSources(); let sources = this.allSources();
if (sourceTags.length) { if (sourceTags.length) {
...@@ -188,32 +223,63 @@ export default class PicsContainer ...@@ -188,32 +223,63 @@ export default class PicsContainer
); );
} }
async getSegment(url: string, bot?: Bot) { async download(url: string, extraConfig: AxiosRequestConfig = {}) {
let useFileHeader = false; 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 { try {
if (this.config.useAssets && this.assets) { let result = await middlwares[0].use(url, next);
const uploadedUrl = await this.assets.upload(url, undefined); if (!result) {
url = uploadedUrl; this.logger.warn(
} else if (this.config.useBase64 && url.startsWith('http')) { `Got empty result from middleware ${middlwares[0].name || '???'}`,
const buf = await this._http.get(url, { );
responseType: 'arraybuffer', result = url;
});
url = `base64://${buf.toString('base64')}`;
useFileHeader = true;
} }
return result;
} catch (e) { } 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 = { const picData: segment.Data = {
[isOneBotBot && useFileHeader ? 'file' : 'url']: url, [url.startsWith('base64://') && this.isOneBotBot(bot) ? 'file' : 'url']:
url,
cache: true, cache: true,
}; };
return segment('image', picData); 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._http = this.http.extend(this.config.httpConfig);
this.installDefaultMiddlewares();
const ctx = this.ctx; const ctx = this.ctx;
ctx.i18n.define('zh', `commands.${this.config.commandName}`, { ctx.i18n.define('zh', `commands.${this.config.commandName}`, {
description: '获取随机图片', 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