diff --git a/src/controller/BaseController.ts b/src/controller/BaseController.ts index 651f26e18..1d6b2ab9c 100644 --- a/src/controller/BaseController.ts +++ b/src/controller/BaseController.ts @@ -170,7 +170,7 @@ export abstract class BaseController extends BotSe } protected async reply(sourceCtx: Context, body: string): Promise { - const context = redirectContext(sourceCtx, this.data.redirect); + const context = redirectContext(sourceCtx, this.data.redirect, this.services); const msg = new Message({ body, context, diff --git a/src/controller/CompletionController.ts b/src/controller/CompletionController.ts index 5b2ee7f9b..cc6a7a287 100644 --- a/src/controller/CompletionController.ts +++ b/src/controller/CompletionController.ts @@ -85,7 +85,7 @@ export class CompletionController extends BaseController { diff --git a/src/controller/EchoController.ts b/src/controller/EchoController.ts index 12b44d27b..a277cb9dc 100644 --- a/src/controller/EchoController.ts +++ b/src/controller/EchoController.ts @@ -18,7 +18,7 @@ export class EchoController extends BaseController implement @Handler(NOUN_ECHO, CommandVerb.Create) @CheckRBAC() public async createEcho(cmd: Command, ctx: Context): Promise { - const targetCtx = redirectContext(ctx, this.data.redirect); + const targetCtx = redirectContext(ctx, this.data.redirect, this.services); this.logger.debug({ cmd, ctx, targetCtx }, 'echoing command'); diff --git a/src/entity/Context.ts b/src/entity/Context.ts index bd247a2ac..1129ce468 100644 --- a/src/entity/Context.ts +++ b/src/entity/Context.ts @@ -4,10 +4,12 @@ import { MissingValueError } from 'noicejs'; import { newTrie, ShiroTrie } from 'shiro-trie'; import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { NotFoundError } from '../error/NotFoundError'; import { Listener } from '../listener'; +import { ServiceModule } from '../module/ServiceModule'; import { Parser } from '../parser'; import { ServiceMetadata } from '../Service'; -import { doesExist, mustCoalesce, Optional } from '../utils'; +import { doesExist, mustCoalesce, mustExist, Optional } from '../utils'; import { Token } from './auth/Token'; import { GRAPH_OUTPUT_USER, User } from './auth/User'; import { BaseEntity, BaseEntityOptions } from './base/BaseEntity'; @@ -31,12 +33,13 @@ export interface ContextData { uid: string; } -export interface ContextOptions extends BaseEntityOptions, ContextData { - parser?: Parser; - +export interface ContextRoute { source?: Listener; - target?: Listener; +} + +export interface ContextOptions extends BaseEntityOptions, ContextData, ContextRoute { + parser?: Parser; token?: Token; @@ -47,8 +50,9 @@ export interface ContextOptions extends BaseEntityOptions, ContextData { } export interface ListenerRedirect { - source: boolean; + source?: boolean; service?: ServiceMetadata; + target?: boolean; } export interface ContextRedirectStage extends ContextData { @@ -64,7 +68,7 @@ export interface ContextRedirect { export const TABLE_CONTEXT = 'context'; @Entity(TABLE_CONTEXT) -export class Context extends BaseEntity implements ContextOptions { +export class Context extends BaseEntity implements ContextOptions, ContextRoute { @Column('simple-json') public channel: ChannelData; @@ -192,13 +196,55 @@ export function extractRedirect(stage: Optional>): }; } -export function redirectContext(original: Context, redirect: ContextRedirect): Context { +export function redirectService(original: Context, redirect: ContextRedirect, services: ServiceModule, key: 'source' | 'target'): Listener { + // check forces + const forces = redirect.forces[key]; + if (doesExist(forces)) { + if (forces.source === true) { + return mustExist(original.source); + } + + if (forces.target === true) { + return mustExist(original.target); + } + + if (doesExist(forces.service)) { + return services.getService(forces.service); + } + } + + // check original + const originalListener = original[key]; + if (doesExist(originalListener)) { + return originalListener; + } + + // check defaults + const defaults = redirect.defaults[key]; + if (doesExist(defaults)) { + if (defaults.source === true) { + return mustExist(original.source); + } + + if (defaults.target === true) { + return mustExist(original.target); + } + + if (doesExist(defaults.service)) { + return services.getService(defaults.service); + } + } + + throw new NotFoundError(); +} + +export function redirectContext(original: Context, redirect: ContextRedirect, services: ServiceModule): Context { const channel = mustCoalesce(redirect.defaults.channel, original.channel, redirect.forces.channel); const name = mustCoalesce(redirect.defaults.name, original.name, redirect.forces.name); const uid = mustCoalesce(redirect.defaults.uid, original.uid, redirect.forces.uid); // loop up source and target services, user - const source = original.source; - const target = original.target; + const source = redirectService(original, redirect, services, 'source'); + const target = redirectService(original, redirect, services, 'target'); return new Context({ channel, diff --git a/src/generator/BaseGenerator.ts b/src/generator/BaseGenerator.ts index 4e5bb79b2..40282f226 100644 --- a/src/generator/BaseGenerator.ts +++ b/src/generator/BaseGenerator.ts @@ -116,7 +116,7 @@ export abstract class BaseGenerator extends BotServ */ protected async createContext(): Promise { const base = new Context(this.data.context); - const ctx = redirectContext(base, this.data.redirect); + const ctx = redirectContext(base, this.data.redirect, this.services); return this.contextRepository.save(ctx); } } diff --git a/src/listener/LoopbackListener.ts b/src/listener/LoopbackListener.ts index 144e79042..0a72aa09c 100644 --- a/src/listener/LoopbackListener.ts +++ b/src/listener/LoopbackListener.ts @@ -22,7 +22,7 @@ export class LoopbackListener extends BaseListener impleme const outCtx = await this.createContext(ctx); const outMsg = new Message(msg); - outMsg.context = redirectContext(outCtx, this.data.redirect); + outMsg.context = redirectContext(outCtx, this.data.redirect, this.services); await this.bot.receive(outMsg); } diff --git a/src/schema/schema.yml b/src/schema/schema.yml index 5f07551e5..2b14dc8b7 100644 --- a/src/schema/schema.yml +++ b/src/schema/schema.yml @@ -102,6 +102,27 @@ definitions: target: $ref: "#/definitions/service-metadata" + entity-context-route: + oneOf: + - type: object + additionalProperties: false + required: [service] + properties: + service: + $ref: "#/definitions/service-metadata" + - type: object + additionalProperties: false + required: [source] + properties: + source: + type: boolean + - type: object + additionalProperties: false + required: [target] + properties: + target: + type: boolean + entity-context-redirect: type: object additionalProperties: false @@ -109,9 +130,17 @@ definitions: defaults: allOf: - $ref: "#/definitions/entity-context-data" + - source: + $ref: "#/definitions/entity-context-route" + - target: + $ref: "#/definitions/entity-context-route" forces: allOf: - $ref: "#/definitions/entity-context-data" + - source: + $ref: "#/definitions/entity-context-route" + - target: + $ref: "#/definitions/entity-context-route" entity-message: type: object