Support removing simple bots from rooms

This commit is contained in:
Travis Ralston 2018-03-25 16:20:15 -06:00
parent de766ab945
commit 87121150cc
8 changed files with 68 additions and 13 deletions

View File

@ -1,4 +1,4 @@
import { GET, Path, PathParam, QueryParam } from "typescript-rest"; import { DELETE, GET, Path, PathParam, QueryParam } from "typescript-rest";
import { ScalarService } from "../scalar/ScalarService"; import { ScalarService } from "../scalar/ScalarService";
import { Widget } from "../../integrations/Widget"; import { Widget } from "../../integrations/Widget";
import { Cache, CACHE_INTEGRATIONS } from "../../MemoryCache"; import { Cache, CACHE_INTEGRATIONS } from "../../MemoryCache";
@ -52,6 +52,18 @@ export class DimensionIntegrationsService {
return this.getEnabledIntegrations(scalarToken); return this.getEnabledIntegrations(scalarToken);
} }
@DELETE
@Path("room/:roomId/integrations/:category/:type")
public async removeIntegrationInRoom(@QueryParam("scalar_token") scalarToken: string, @PathParam("roomId") roomId: string, @PathParam("category") category: string, @PathParam("type") integrationType: string): Promise<any> {
const userId = await ScalarService.getTokenOwner(scalarToken);
if (category === "widget") throw new ApiError(400, "Widgets should be removed client-side");
else if (category === "bot") await NebStore.removeSimpleBot(integrationType, roomId, userId);
else throw new ApiError(400, "Unrecognized category");
return {}; // 200 OK
}
@GET @GET
@Path(":category/:type") @Path(":category/:type")
public async getIntegration(@PathParam("category") category: string, @PathParam("type") type: string): Promise<Integration> { public async getIntegration(@PathParam("category") category: string, @PathParam("type") type: string): Promise<Integration> {

View File

@ -90,9 +90,9 @@ export class NebStore {
}, },
}; };
public static async listSimpleBots(requestingUserId: string): Promise<SimpleBot[]> { public static async listEnabledNebSimpleBots(): Promise<{neb: NebConfig, integration: NebIntegration}[]>{
const nebConfigs = await NebStore.getAllConfigs(); const nebConfigs = await NebStore.getAllConfigs();
const integrations: { integration: NebIntegration, userId: string }[] = []; const integrations: {neb: NebConfig, integration: NebIntegration}[] = [];
const hasTypes: string[] = []; const hasTypes: string[] = [];
for (const neb of nebConfigs) { for (const neb of nebConfigs) {
@ -103,16 +103,29 @@ export class NebStore {
if (!metadata || !metadata.simple) continue; if (!metadata || !metadata.simple) continue;
if (hasTypes.indexOf(integration.type) !== -1) continue; if (hasTypes.indexOf(integration.type) !== -1) continue;
const proxy = new NebProxy(neb, requestingUserId); integrations.push({neb, integration});
integrations.push({
integration: integration,
userId: await proxy.getBotUserId(integration),
});
hasTypes.push(integration.type); hasTypes.push(integration.type);
} }
} }
return integrations.map(i => new SimpleBot(i.integration, i.userId)); return integrations;
}
public static async listSimpleBots(requestingUserId: string): Promise<SimpleBot[]> {
const rawIntegrations = await NebStore.listEnabledNebSimpleBots();
return Promise.all(rawIntegrations.map(async i => {
const proxy = new NebProxy(i.neb, requestingUserId);
return new SimpleBot(i.integration, await proxy.getBotUserId(i.integration));
}));
}
public static async removeSimpleBot(type: string, roomId: string, requestingUserId: string): Promise<any> {
const rawIntegrations = await NebStore.listEnabledNebSimpleBots();
const integration = rawIntegrations.find(i => i.integration.type === type);
if (!integration) throw new Error("Integration not found");
const proxy = new NebProxy(integration.neb, requestingUserId);
return proxy.removeBotFromRoom(integration.integration, roomId);
} }
public static async getAllConfigs(): Promise<NebConfig[]> { public static async getAllConfigs(): Promise<NebConfig[]> {

View File

@ -30,4 +30,13 @@ export class MatrixAppserviceClient {
); );
return response['user_id']; return response['user_id'];
} }
public async leaveRoom(virtualUserId: string, roomId: string): Promise<any> {
return doClientApiCall(
"POST",
"/_matrix/client/r0/rooms/" + encodeURIComponent(roomId) + "/leave",
{access_token: this.appservice.asToken, user_id: virtualUserId},
{},
);
}
} }

View File

@ -7,6 +7,8 @@ import Upstream from "../db/models/Upstream";
import UserScalarToken from "../db/models/UserScalarToken"; import UserScalarToken from "../db/models/UserScalarToken";
import { NebClient } from "./NebClient"; import { NebClient } from "./NebClient";
import { ModularIntegrationInfoResponse } from "../models/ModularResponses"; import { ModularIntegrationInfoResponse } from "../models/ModularResponses";
import { AppserviceStore } from "../db/AppserviceStore";
import { MatrixAppserviceClient } from "../matrix/MatrixAppserviceClient";
export class NebProxy { export class NebProxy {
constructor(private neb: NebConfig, private requestingUserId: string) { constructor(private neb: NebConfig, private requestingUserId: string) {
@ -29,6 +31,18 @@ export class NebProxy {
} }
} }
public async removeBotFromRoom(integration: NebIntegration, roomId: string) {
if (integration.nebId !== this.neb.id) throw new Error("Integration is not for this NEB proxy");
if (this.neb.upstreamId) {
await this.doUpstreamRequest("/removeIntegration", {type: integration.type, room_id: roomId});
} else {
const appservice = await AppserviceStore.getAppservice(this.neb.appserviceId);
const client = new MatrixAppserviceClient(appservice);
await client.leaveRoom(await this.getBotUserId(integration), roomId);
}
}
private async doUpstreamRequest<T>(endpoint: string, body?: any): Promise<T> { private async doUpstreamRequest<T>(endpoint: string, body?: any): Promise<T> {
const upstream = await Upstream.findByPrimary(this.neb.upstreamId); const upstream = await Upstream.findByPrimary(this.neb.upstreamId);
const token = await UserScalarToken.findOne({ const token = await UserScalarToken.findOne({

View File

@ -1,5 +1,4 @@
Release checklist: Release checklist:
* Remove bots from rooms
* Manage custom bots (not sure if anyone actually uses this?) * Manage custom bots (not sure if anyone actually uses this?)
* RSS bot * RSS bot
* Travis CI * Travis CI

View File

@ -135,9 +135,7 @@ export class RiotHomeComponent {
let promise: Promise<any> = Promise.resolve(); let promise: Promise<any> = Promise.resolve();
if (!integration._inRoom) { if (!integration._inRoom) {
promise = this.scalar.inviteUser(this.roomId, bot.userId); promise = this.scalar.inviteUser(this.roomId, bot.userId);
} } else promise = this.integrationsApi.removeIntegration(integration.category, integration.type, this.roomId);
// 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;

View File

@ -17,4 +17,10 @@ export class AuthedApi {
const qs = {scalar_token: SessionStorage.scalarToken}; const qs = {scalar_token: SessionStorage.scalarToken};
return this.http.post(url, body, {params: qs}); return this.http.post(url, body, {params: qs});
} }
protected authedDelete(url: string, qs?: any): Observable<Response> {
if (!qs) qs = {};
qs["scalar_token"] = SessionStorage.scalarToken;
return this.http.delete(url, {params: qs});
}
} }

View File

@ -26,4 +26,8 @@ export class IntegrationsApiService extends AuthedApi {
return this.http.get("/api/v1/dimension/widgets/embeddable", {params: {url: url}}) return this.http.get("/api/v1/dimension/widgets/embeddable", {params: {url: url}})
.map(r => r.json()).toPromise(); .map(r => r.json()).toPromise();
} }
public removeIntegration(category: string, type: string, roomId: string): Promise<any> {
return this.authedDelete("/api/v1/dimension/integrations/room/" + roomId + "/integrations/" + category + "/" + type).map(r => r.json()).toPromise();
}
} }