From 947ecd43cd6ae12cce726b10b90c8e62fac2ced1 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sun, 25 Mar 2018 00:45:57 -0600 Subject: [PATCH] Early support for managing bots in the room Known drawbacks: * Cannot remove bots * Cannot use upstream neb instances --- .../dimension/DimensionIntegrationsService.ts | 17 ++++++---- src/db/NebStore.ts | 31 ++++++++++++++++++ src/integrations/Integration.ts | 2 +- src/integrations/SimpleBot.ts | 13 ++++++++ src/models/neb.ts | 2 ++ web/app/riot/riot-home/home.component.ts | 21 ++++++++---- web/app/shared/models/integration.ts | 4 +++ web/public/img/avatars/echo.png | Bin 0 -> 4506 bytes 8 files changed, 76 insertions(+), 14 deletions(-) create mode 100644 src/integrations/SimpleBot.ts create mode 100644 web/public/img/avatars/echo.png diff --git a/src/api/dimension/DimensionIntegrationsService.ts b/src/api/dimension/DimensionIntegrationsService.ts index 728852a..6cb11fc 100644 --- a/src/api/dimension/DimensionIntegrationsService.ts +++ b/src/api/dimension/DimensionIntegrationsService.ts @@ -5,6 +5,7 @@ import { Cache, CACHE_INTEGRATIONS } from "../../MemoryCache"; import { Integration } from "../../integrations/Integration"; import { ApiError } from "../ApiError"; import { WidgetStore } from "../../db/WidgetStore"; +import { NebStore } from "../../db/NebStore"; export interface IntegrationsResponse { widgets: Widget[], @@ -14,14 +15,18 @@ export interface IntegrationsResponse { export class DimensionIntegrationsService { public static async getIntegrations(isEnabledCheck?: boolean): Promise { - const cachedWidgets = Cache.for(CACHE_INTEGRATIONS).get("integrations_" + isEnabledCheck); - if (cachedWidgets) { - return {widgets: cachedWidgets}; + const cachedIntegrations = Cache.for(CACHE_INTEGRATIONS).get("integrations_" + isEnabledCheck); + if (cachedIntegrations) { + return cachedIntegrations; } - const widgets = await WidgetStore.listAll(isEnabledCheck); - Cache.for(CACHE_INTEGRATIONS).put("integrations_" + isEnabledCheck, widgets); - return {widgets: widgets}; + const integrations = { + widgets: await WidgetStore.listAll(isEnabledCheck), + bots: await NebStore.listSimpleBots(), // No enabled check - managed internally + }; + + Cache.for(CACHE_INTEGRATIONS).put("integrations_" + isEnabledCheck, integrations); + return integrations; } @GET diff --git a/src/db/NebStore.ts b/src/db/NebStore.ts index 18cf81f..745a00e 100644 --- a/src/db/NebStore.ts +++ b/src/db/NebStore.ts @@ -9,6 +9,7 @@ import NebBotUser from "./models/NebBotUser"; import NebNotificationUser from "./models/NebNotificationUser"; import { AppserviceStore } from "./AppserviceStore"; import config from "../config"; +import { SimpleBot } from "../integrations/SimpleBot"; export interface SupportedIntegration { type: string; @@ -32,16 +33,19 @@ export class NebStore { name: "Echo", avatarUrl: "/img/avatars/echo.png", // TODO: Make this image description: "Repeats text given to it from !echo", + simple: true, }, "giphy": { name: "Giphy", avatarUrl: "/img/avatars/giphy.png", description: "Posts a GIF from Giphy using !giphy ", + simple: true, }, "guggy": { name: "Guggy", avatarUrl: "/img/avatars/guggy.png", description: "Send a reaction GIF using !guggy ", + simple: true, }, // TODO: Support Github // "github": { @@ -53,11 +57,13 @@ export class NebStore { name: "Google", avatarUrl: "/img/avatars/google.png", description: "Searches Google Images using !google image ", + simple: true, }, "imgur": { name: "Imgur", avatarUrl: "/img/avatars/imgur.png", description: "Searches and posts images from Imgur using !imgur ", + simple: true, }, // TODO: Support JIRA // "jira": { @@ -79,9 +85,34 @@ export class NebStore { name: "Wikipedia", avatarUrl: "/img/avatars/wikipedia.png", description: "Searches wikipedia using !wikipedia ", + simple: true, }, }; + public static async listSimpleBots(): Promise { + 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 { const configs = await NebConfiguration.findAll(); return Promise.all((configs || []).map(c => NebStore.getConfig(c.id))); diff --git a/src/integrations/Integration.ts b/src/integrations/Integration.ts index 38e74ec..c30a071 100644 --- a/src/integrations/Integration.ts +++ b/src/integrations/Integration.ts @@ -25,7 +25,7 @@ export class Integration { } export interface IntegrationRequirement { - condition: "publicRoom" | "canSendEventTypes"; + condition: "publicRoom" | "canSendEventTypes" | "userInRoom"; argument: any; // For publicRoom this is true or false (boolean) diff --git a/src/integrations/SimpleBot.ts b/src/integrations/SimpleBot.ts new file mode 100644 index 0000000..1e3b0e2 --- /dev/null +++ b/src/integrations/SimpleBot.ts @@ -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; + } +} \ No newline at end of file diff --git a/src/models/neb.ts b/src/models/neb.ts index ea94eba..e2c2f74 100644 --- a/src/models/neb.ts +++ b/src/models/neb.ts @@ -8,6 +8,7 @@ export class NebConfig { public appserviceId?: string; public upstreamId?: number; public integrations: Integration[]; + public dbIntegrations: NebIntegration[]; public constructor(config: NebConfiguration, integrations: NebIntegration[]) { this.id = config.id; @@ -15,5 +16,6 @@ export class NebConfig { this.appserviceId = config.appserviceId; this.upstreamId = config.upstreamId; this.integrations = integrations.map(i => new Integration(i)); + this.dbIntegrations = integrations; } } \ No newline at end of file diff --git a/web/app/riot/riot-home/home.component.ts b/web/app/riot/riot-home/home.component.ts index e45783a..ea699b2 100644 --- a/web/app/riot/riot-home/home.component.ts +++ b/web/app/riot/riot-home/home.component.ts @@ -4,7 +4,7 @@ import { ActivatedRoute, Router } from "@angular/router"; import { ScalarClientApiService } from "../../shared/services/scalar/scalar-client-api.service"; import * as _ from "lodash"; 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 { SessionStorage } from "../../shared/SessionStorage"; import { AdminApiService } from "../../shared/services/admin/admin-api.service"; @@ -129,13 +129,15 @@ export class RiotHomeComponent { if (integration.category === "bot") { // It's a bot + const bot = integration; // TODO: "Are you sure?" dialog - // let promise = null; - const promise = Promise.resolve(); - // if (!integration._inRoom) { - // promise = this.scalar.inviteUser(this.roomId, integration.userId); - // } else promise = this.api.removeIntegration(this.roomId, integration.type, integration.integrationType, this.scalarToken); + let promise:Promise = Promise.resolve(); + if (!integration._inRoom) { + promise = this.scalar.inviteUser(this.roomId, bot.userId); + } + // 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 integration._inRoom = !integration._inRoom; @@ -228,11 +230,16 @@ export class RiotHomeComponent { 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; let promises = integration.requirements.map(r => this.checkRequirement(r)); + if (integration.category === "bot") { + const state = await this.scalar.getMembershipState(this.roomId, (integration).userId); + integration._inRoom = ["join", "invite"].indexOf(state.response.membership) !== -1; + } + return Promise.all(promises).then(() => { integration._isSupported = true; integration._notSupportedReason = null; diff --git a/web/app/shared/models/integration.ts b/web/app/shared/models/integration.ts index 4bab96d..f32cd54 100644 --- a/web/app/shared/models/integration.ts +++ b/web/app/shared/models/integration.ts @@ -16,6 +16,10 @@ export interface FE_Integration { _notSupportedReason: string; } +export interface FE_SimpleBot extends FE_Integration { + userId: string; +} + export interface FE_Widget extends FE_Integration { options: any; } diff --git a/web/public/img/avatars/echo.png b/web/public/img/avatars/echo.png new file mode 100644 index 0000000000000000000000000000000000000000..3cf2573af38dec945ed6e2f6e84a284d49c174fb GIT binary patch literal 4506 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 literal 0 HcmV?d00001