Early support for managing bots in the room

Known drawbacks:
* Cannot remove bots
* Cannot use upstream neb instances
This commit is contained in:
Travis Ralston 2018-03-25 00:45:57 -06:00
parent dcda2fa46e
commit 947ecd43cd
8 changed files with 76 additions and 14 deletions

View File

@ -5,6 +5,7 @@ import { Cache, CACHE_INTEGRATIONS } from "../../MemoryCache";
import { Integration } from "../../integrations/Integration"; import { Integration } from "../../integrations/Integration";
import { ApiError } from "../ApiError"; import { ApiError } from "../ApiError";
import { WidgetStore } from "../../db/WidgetStore"; import { WidgetStore } from "../../db/WidgetStore";
import { NebStore } from "../../db/NebStore";
export interface IntegrationsResponse { export interface IntegrationsResponse {
widgets: Widget[], widgets: Widget[],
@ -14,14 +15,18 @@ export interface IntegrationsResponse {
export class DimensionIntegrationsService { export class DimensionIntegrationsService {
public static async getIntegrations(isEnabledCheck?: boolean): Promise<IntegrationsResponse> { public static async getIntegrations(isEnabledCheck?: boolean): Promise<IntegrationsResponse> {
const cachedWidgets = Cache.for(CACHE_INTEGRATIONS).get("integrations_" + isEnabledCheck); const cachedIntegrations = Cache.for(CACHE_INTEGRATIONS).get("integrations_" + isEnabledCheck);
if (cachedWidgets) { if (cachedIntegrations) {
return {widgets: cachedWidgets}; return cachedIntegrations;
} }
const widgets = await WidgetStore.listAll(isEnabledCheck); const integrations = {
Cache.for(CACHE_INTEGRATIONS).put("integrations_" + isEnabledCheck, widgets); widgets: await WidgetStore.listAll(isEnabledCheck),
return {widgets: widgets}; bots: await NebStore.listSimpleBots(), // No enabled check - managed internally
};
Cache.for(CACHE_INTEGRATIONS).put("integrations_" + isEnabledCheck, integrations);
return integrations;
} }
@GET @GET

View File

@ -9,6 +9,7 @@ import NebBotUser from "./models/NebBotUser";
import NebNotificationUser from "./models/NebNotificationUser"; import NebNotificationUser from "./models/NebNotificationUser";
import { AppserviceStore } from "./AppserviceStore"; import { AppserviceStore } from "./AppserviceStore";
import config from "../config"; import config from "../config";
import { SimpleBot } from "../integrations/SimpleBot";
export interface SupportedIntegration { export interface SupportedIntegration {
type: string; type: string;
@ -32,16 +33,19 @@ export class NebStore {
name: "Echo", name: "Echo",
avatarUrl: "/img/avatars/echo.png", // TODO: Make this image avatarUrl: "/img/avatars/echo.png", // TODO: Make this image
description: "Repeats text given to it from !echo", description: "Repeats text given to it from !echo",
simple: true,
}, },
"giphy": { "giphy": {
name: "Giphy", name: "Giphy",
avatarUrl: "/img/avatars/giphy.png", avatarUrl: "/img/avatars/giphy.png",
description: "Posts a GIF from Giphy using !giphy <query>", description: "Posts a GIF from Giphy using !giphy <query>",
simple: true,
}, },
"guggy": { "guggy": {
name: "Guggy", name: "Guggy",
avatarUrl: "/img/avatars/guggy.png", avatarUrl: "/img/avatars/guggy.png",
description: "Send a reaction GIF using !guggy <query>", description: "Send a reaction GIF using !guggy <query>",
simple: true,
}, },
// TODO: Support Github // TODO: Support Github
// "github": { // "github": {
@ -53,11 +57,13 @@ export class NebStore {
name: "Google", name: "Google",
avatarUrl: "/img/avatars/google.png", avatarUrl: "/img/avatars/google.png",
description: "Searches Google Images using !google image <query>", description: "Searches Google Images using !google image <query>",
simple: true,
}, },
"imgur": { "imgur": {
name: "Imgur", name: "Imgur",
avatarUrl: "/img/avatars/imgur.png", avatarUrl: "/img/avatars/imgur.png",
description: "Searches and posts images from Imgur using !imgur <query>", description: "Searches and posts images from Imgur using !imgur <query>",
simple: true,
}, },
// TODO: Support JIRA // TODO: Support JIRA
// "jira": { // "jira": {
@ -79,9 +85,34 @@ export class NebStore {
name: "Wikipedia", name: "Wikipedia",
avatarUrl: "/img/avatars/wikipedia.png", avatarUrl: "/img/avatars/wikipedia.png",
description: "Searches wikipedia using !wikipedia <query>", description: "Searches wikipedia using !wikipedia <query>",
simple: true,
}, },
}; };
public static async listSimpleBots(): Promise<SimpleBot[]> {
const configs = await NebStore.getAllConfigs();
const integrations: { integration: NebIntegration, userId: string }[] = [];
const hasTypes: string[] = [];
for (const config of configs) {
for (const integration of config.dbIntegrations) {
if (!integration.isEnabled) continue;
const metadata = NebStore.INTEGRATIONS[integration.type];
if (!metadata || !metadata.simple) continue;
if (hasTypes.indexOf(integration.type) !== -1) continue;
// TODO: Handle case of upstream bots
const user = await NebStore.getOrCreateBotUser(config.id, integration.type);
integrations.push({integration: integration, userId: user.appserviceUserId});
hasTypes.push(integration.type);
}
}
return integrations.map(i => new SimpleBot(i.integration, i.userId));
}
public static async getAllConfigs(): Promise<NebConfig[]> { public static async getAllConfigs(): Promise<NebConfig[]> {
const configs = await NebConfiguration.findAll(); const configs = await NebConfiguration.findAll();
return Promise.all((configs || []).map(c => NebStore.getConfig(c.id))); return Promise.all((configs || []).map(c => NebStore.getConfig(c.id)));

View File

@ -25,7 +25,7 @@ export class Integration {
} }
export interface IntegrationRequirement { export interface IntegrationRequirement {
condition: "publicRoom" | "canSendEventTypes"; condition: "publicRoom" | "canSendEventTypes" | "userInRoom";
argument: any; argument: any;
// For publicRoom this is true or false (boolean) // For publicRoom this is true or false (boolean)

View File

@ -0,0 +1,13 @@
import { Integration } from "./Integration";
import NebIntegration from "../db/models/NebIntegration";
export class SimpleBot extends Integration {
constructor(bot: NebIntegration, public userId: string) {
super(bot);
this.category = "bot";
this.requirements = [];
// We're going to go ahead and claim that none of the bots are supported in e2e rooms
this.isEncryptionSupported = false;
}
}

View File

@ -8,6 +8,7 @@ export class NebConfig {
public appserviceId?: string; public appserviceId?: string;
public upstreamId?: number; public upstreamId?: number;
public integrations: Integration[]; public integrations: Integration[];
public dbIntegrations: NebIntegration[];
public constructor(config: NebConfiguration, integrations: NebIntegration[]) { public constructor(config: NebConfiguration, integrations: NebIntegration[]) {
this.id = config.id; this.id = config.id;
@ -15,5 +16,6 @@ export class NebConfig {
this.appserviceId = config.appserviceId; this.appserviceId = config.appserviceId;
this.upstreamId = config.upstreamId; this.upstreamId = config.upstreamId;
this.integrations = integrations.map(i => new Integration(i)); this.integrations = integrations.map(i => new Integration(i));
this.dbIntegrations = integrations;
} }
} }

View File

@ -4,7 +4,7 @@ import { ActivatedRoute, Router } from "@angular/router";
import { ScalarClientApiService } from "../../shared/services/scalar/scalar-client-api.service"; import { ScalarClientApiService } from "../../shared/services/scalar/scalar-client-api.service";
import * as _ from "lodash"; import * as _ from "lodash";
import { ScalarServerApiService } from "../../shared/services/scalar/scalar-server-api.service"; import { ScalarServerApiService } from "../../shared/services/scalar/scalar-server-api.service";
import { FE_Integration, FE_IntegrationRequirement } from "../../shared/models/integration"; import { FE_Integration, FE_IntegrationRequirement, FE_SimpleBot } from "../../shared/models/integration";
import { IntegrationsRegistry } from "../../shared/registry/integrations.registry"; import { IntegrationsRegistry } from "../../shared/registry/integrations.registry";
import { SessionStorage } from "../../shared/SessionStorage"; import { SessionStorage } from "../../shared/SessionStorage";
import { AdminApiService } from "../../shared/services/admin/admin-api.service"; import { AdminApiService } from "../../shared/services/admin/admin-api.service";
@ -129,13 +129,15 @@ export class RiotHomeComponent {
if (integration.category === "bot") { if (integration.category === "bot") {
// It's a bot // It's a bot
const bot = <FE_SimpleBot>integration;
// TODO: "Are you sure?" dialog // TODO: "Are you sure?" dialog
// let promise = null; let promise:Promise<any> = Promise.resolve();
const promise = Promise.resolve(); if (!integration._inRoom) {
// if (!integration._inRoom) { promise = this.scalar.inviteUser(this.roomId, bot.userId);
// promise = this.scalar.inviteUser(this.roomId, integration.userId); }
// } else promise = this.api.removeIntegration(this.roomId, integration.type, integration.integrationType, this.scalarToken); // TODO: Handle removal of bots
// else promise = this.api.removeIntegration(this.roomId, integration.type, integration.integrationType, this.scalarToken);
// We set this ahead of the promise for debouncing // We set this ahead of the promise for debouncing
integration._inRoom = !integration._inRoom; integration._inRoom = !integration._inRoom;
@ -228,11 +230,16 @@ export class RiotHomeComponent {
console.log("Failed to find integration component for category=" + category + " type=" + type); console.log("Failed to find integration component for category=" + category + " type=" + type);
} }
private updateIntegrationState(integration: FE_Integration) { private async updateIntegrationState(integration: FE_Integration) {
if (!integration.requirements) return; if (!integration.requirements) return;
let promises = integration.requirements.map(r => this.checkRequirement(r)); let promises = integration.requirements.map(r => this.checkRequirement(r));
if (integration.category === "bot") {
const state = await this.scalar.getMembershipState(this.roomId, (<FE_SimpleBot>integration).userId);
integration._inRoom = ["join", "invite"].indexOf(state.response.membership) !== -1;
}
return Promise.all(promises).then(() => { return Promise.all(promises).then(() => {
integration._isSupported = true; integration._isSupported = true;
integration._notSupportedReason = null; integration._notSupportedReason = null;

View File

@ -16,6 +16,10 @@ export interface FE_Integration {
_notSupportedReason: string; _notSupportedReason: string;
} }
export interface FE_SimpleBot extends FE_Integration {
userId: string;
}
export interface FE_Widget extends FE_Integration { export interface FE_Widget extends FE_Integration {
options: any; options: any;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB