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 0000000..3cf2573 Binary files /dev/null and b/web/public/img/avatars/echo.png differ