mirror of
https://github.com/turt2live/matrix-dimension.git
synced 2024-07-18 08:51:51 +00:00
Self-service requests to bridge IRC channels
This commit is contained in:
parent
1e437a2f8b
commit
f33f7e5716
|
@ -46,14 +46,7 @@ export class DimensionIntegrationsService {
|
|||
* @returns {Promise<Bridge[]>} Resolves to the bridge list
|
||||
*/
|
||||
public static async getBridges(enabledOnly: boolean, forUserId: string, inRoomId?: string): Promise<Bridge[]> {
|
||||
const cacheKey = inRoomId ? "bridges_" + inRoomId : "bridges";
|
||||
|
||||
const cached = Cache.for(CACHE_INTEGRATIONS).get(cacheKey);
|
||||
if (cached) return cached;
|
||||
|
||||
const bridges = await BridgeStore.listAll(forUserId, enabledOnly ? true : null, inRoomId);
|
||||
Cache.for(CACHE_INTEGRATIONS).put(cacheKey, bridges);
|
||||
return bridges;
|
||||
return BridgeStore.listAll(forUserId, enabledOnly ? true : null, inRoomId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
49
src/api/dimension/DimensionIrcService.ts
Normal file
49
src/api/dimension/DimensionIrcService.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { GET, Path, PathParam, POST, QueryParam } from "typescript-rest";
|
||||
import { LogService } from "matrix-js-snippets";
|
||||
import { ScalarService } from "../scalar/ScalarService";
|
||||
import { IrcBridge } from "../../bridges/IrcBridge";
|
||||
import IrcBridgeRecord from "../../db/models/IrcBridgeRecord";
|
||||
import { ApiError } from "../ApiError";
|
||||
|
||||
interface RequestLinkRequest {
|
||||
op: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* API for interacting with the IRC bridge
|
||||
*/
|
||||
@Path("/api/v1/dimension/irc")
|
||||
export class DimensionIrcService {
|
||||
|
||||
@GET
|
||||
@Path(":networkId/channel/:channel/ops")
|
||||
public async getOps(@QueryParam("scalar_token") scalarToken: string, @PathParam("networkId") networkId: string, @PathParam("channel") channelNoHash: string): Promise<string[]> {
|
||||
const userId = await ScalarService.getTokenOwner(scalarToken);
|
||||
|
||||
const parsed = IrcBridge.parseNetworkId(networkId);
|
||||
const bridge = await IrcBridgeRecord.findByPrimary(parsed.bridgeId);
|
||||
if (!bridge) throw new ApiError(404, "Bridge not found");
|
||||
|
||||
const client = new IrcBridge(userId);
|
||||
const operators = await client.getOperators(bridge, parsed.bridgeNetworkId, "#" + channelNoHash);
|
||||
|
||||
LogService.info("DimensionIrcService", userId + " listed the operators for #" + channelNoHash + " on " + networkId);
|
||||
return operators;
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path(":networkId/channel/:channel/link/:roomId")
|
||||
public async requestLink(@QueryParam("scalar_token") scalarToken: string, @PathParam("networkId") networkId: string, @PathParam("channel") channelNoHash: string, @PathParam("roomId") roomId: string, request: RequestLinkRequest): Promise<any> {
|
||||
const userId = await ScalarService.getTokenOwner(scalarToken);
|
||||
|
||||
const parsed = IrcBridge.parseNetworkId(networkId);
|
||||
const bridge = await IrcBridgeRecord.findByPrimary(parsed.bridgeId);
|
||||
if (!bridge) throw new ApiError(404, "Bridge not found");
|
||||
|
||||
const client = new IrcBridge(userId);
|
||||
await client.requestLink(bridge, parsed.bridgeNetworkId, "#" + channelNoHash, request.op, roomId);
|
||||
|
||||
LogService.info("DimensionIrcService", userId + " requested #" + channelNoHash + " on " + networkId + " to be linked to " + roomId);
|
||||
return {}; // 200 OK
|
||||
}
|
||||
}
|
|
@ -5,9 +5,9 @@ import Upstream from "../db/models/Upstream";
|
|||
import UserScalarToken from "../db/models/UserScalarToken";
|
||||
import { LogService } from "matrix-js-snippets";
|
||||
import * as request from "request";
|
||||
import { QueryNetworksResponse } from "./models/provision_responses";
|
||||
import { ModularIrcQueryNetworksResponse } from "../models/ModularResponses";
|
||||
import { ListLinksResponseItem, ListOpsResponse, QueryNetworksResponse } from "./models/irc";
|
||||
import IrcBridgeNetwork from "../db/models/IrcBridgeNetwork";
|
||||
import { ModularIrcResponse } from "../models/ModularResponses";
|
||||
|
||||
interface CachedNetwork {
|
||||
ircBridgeId: number;
|
||||
|
@ -27,6 +27,12 @@ export interface AvailableNetworks {
|
|||
};
|
||||
}
|
||||
|
||||
export interface LinkedChannels {
|
||||
[networkId: string]: {
|
||||
channelName: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export class IrcBridge {
|
||||
private static getNetworkId(network: CachedNetwork): string {
|
||||
return network.ircBridgeId + "-" + network.bridgeNetworkId;
|
||||
|
@ -47,9 +53,10 @@ export class IrcBridge {
|
|||
return allNetworks.length > 0;
|
||||
}
|
||||
|
||||
public async getNetworks(bridge?: IrcBridgeRecord): Promise<AvailableNetworks> {
|
||||
public async getNetworks(bridge?: IrcBridgeRecord, enabledOnly?: boolean): Promise<AvailableNetworks> {
|
||||
let networks = await this.getAllNetworks();
|
||||
if (bridge) networks = networks.filter(n => n.ircBridgeId === bridge.id);
|
||||
if (enabledOnly) networks = networks.filter(n => n.isEnabled);
|
||||
|
||||
const available: AvailableNetworks = {};
|
||||
networks.forEach(n => available[IrcBridge.getNetworkId(n)] = {
|
||||
|
@ -61,12 +68,63 @@ export class IrcBridge {
|
|||
return available;
|
||||
}
|
||||
|
||||
public async getRoomConfiguration(requestingUserId: string, inRoomId: string): Promise<IrcBridgeConfiguration> {
|
||||
return <any>{requestingUserId, inRoomId};
|
||||
public async getRoomConfiguration(inRoomId: string): Promise<IrcBridgeConfiguration> {
|
||||
const availableNetworks = await this.getNetworks(null, true);
|
||||
const bridges = await IrcBridgeRecord.findAll({where: {isEnabled: true}});
|
||||
const linkedChannels: LinkedChannels = {};
|
||||
|
||||
for (const bridge of bridges) {
|
||||
const links = await this.fetchLinks(bridge, inRoomId);
|
||||
for (const key of Object.keys(links)) {
|
||||
linkedChannels[key] = links[key];
|
||||
}
|
||||
}
|
||||
|
||||
return {availableNetworks: availableNetworks, links: linkedChannels};
|
||||
}
|
||||
|
||||
public async setRoomConfiguration(requestingUserId: string, inRoomId: string, newConfig: IrcBridgeConfiguration): Promise<any> {
|
||||
return <any>{requestingUserId, inRoomId, newConfig};
|
||||
public async getOperators(bridge: IrcBridgeRecord, networkId: string, channel: string): Promise<string[]> {
|
||||
const network = (await this.getAllNetworks()).find(n => n.isEnabled && n.ircBridgeId === bridge.id && n.bridgeNetworkId === networkId);
|
||||
if (!network) throw new Error("Network not found");
|
||||
|
||||
const requestBody = {remote_room_server: network.domain, remote_room_channel: channel};
|
||||
|
||||
let responses: ListOpsResponse[] = [];
|
||||
if (bridge.upstreamId) {
|
||||
const result = await this.doUpstreamRequest<ModularIrcResponse<ListOpsResponse>>(bridge, "POST", "/bridges/irc/_matrix/provision/querylink", null, requestBody);
|
||||
if (result && result.replies) responses = result.replies.map(r => r.response);
|
||||
} else {
|
||||
const result = await this.doProvisionRequest<ListOpsResponse>(bridge, "POST", "/_matrix/provision/querylink", null, requestBody);
|
||||
if (result) responses = [result];
|
||||
}
|
||||
|
||||
const ops: string[] = [];
|
||||
for (const response of responses) {
|
||||
if (!response || !response.operators) continue;
|
||||
response.operators.forEach(i => ops.push(i));
|
||||
}
|
||||
|
||||
return ops;
|
||||
}
|
||||
|
||||
public async requestLink(bridge: IrcBridgeRecord, networkId: string, channel: string, op: string, inRoomId: string): Promise<any> {
|
||||
const network = (await this.getAllNetworks()).find(n => n.isEnabled && n.ircBridgeId === bridge.id && n.bridgeNetworkId === networkId);
|
||||
if (!network) throw new Error("Network not found");
|
||||
|
||||
const requestBody = {
|
||||
remote_room_server: network.domain,
|
||||
remote_room_channel: channel,
|
||||
matrix_room_id: inRoomId,
|
||||
op_nick: op,
|
||||
user_id: this.requestingUserId,
|
||||
};
|
||||
|
||||
if (bridge.upstreamId) {
|
||||
delete requestBody["user_id"];
|
||||
await this.doUpstreamRequest(bridge, "POST", "/bridges/irc/_matrix/provision/link", null, requestBody);
|
||||
} else {
|
||||
await this.doProvisionRequest(bridge, "POST", "/_matrix/provision/link", null, requestBody);
|
||||
}
|
||||
}
|
||||
|
||||
private async getAllNetworks(): Promise<CachedNetwork[]> {
|
||||
|
@ -86,10 +144,50 @@ export class IrcBridge {
|
|||
return networks;
|
||||
}
|
||||
|
||||
private async fetchLinks(bridge: IrcBridgeRecord, inRoomId: string): Promise<LinkedChannels> {
|
||||
const availableNetworks = await this.getNetworks(bridge, true);
|
||||
const networksByDomain: { [domain: string]: { id: string, name: string, bridgeUserId: string } } = {};
|
||||
for (const key of Object.keys(availableNetworks)) {
|
||||
const network = availableNetworks[key];
|
||||
networksByDomain[network.domain] = {
|
||||
id: key,
|
||||
name: network.name,
|
||||
bridgeUserId: network.bridgeUserId,
|
||||
};
|
||||
}
|
||||
|
||||
let responses: ListLinksResponseItem[] = [];
|
||||
if (bridge.upstreamId) {
|
||||
const result = await this.doUpstreamRequest<ModularIrcResponse<ListLinksResponseItem[]>>(bridge, "GET", "/bridges/irc/_matrix/provision/listlinks/" + inRoomId);
|
||||
if (result && result.replies) {
|
||||
const replies = result.replies.map(r => r.response);
|
||||
for (const reply of replies) reply.forEach(r => responses.push(r));
|
||||
}
|
||||
} else {
|
||||
const result = await this.doProvisionRequest<ListLinksResponseItem[]>(bridge, "GET", "/_matrix/provision/listlinks/" + inRoomId);
|
||||
if (result) responses = result;
|
||||
}
|
||||
|
||||
const linked: LinkedChannels = {};
|
||||
for (const response of responses) {
|
||||
if (!response || !response.remote_room_server) continue;
|
||||
|
||||
const network = networksByDomain[response.remote_room_server];
|
||||
if (!network) continue;
|
||||
|
||||
if (!linked[network.id]) linked[network.id] = [];
|
||||
linked[network.id].push({
|
||||
channelName: response.remote_room_channel,
|
||||
});
|
||||
}
|
||||
|
||||
return linked;
|
||||
}
|
||||
|
||||
private async fetchNetworks(bridge: IrcBridgeRecord): Promise<CachedNetwork[]> {
|
||||
let responses: QueryNetworksResponse[] = [];
|
||||
if (bridge.upstreamId) {
|
||||
const result = await this.doUpstreamRequest<ModularIrcQueryNetworksResponse>(bridge, "GET", "/bridges/irc/_matrix/provision/querynetworks");
|
||||
const result = await this.doUpstreamRequest<ModularIrcResponse<QueryNetworksResponse>>(bridge, "GET", "/bridges/irc/_matrix/provision/querynetworks");
|
||||
if (result && result.replies) responses = result.replies.map(r => r.response);
|
||||
} else {
|
||||
const result = await this.doProvisionRequest<QueryNetworksResponse>(bridge, "GET", "/_matrix/provision/querynetworks");
|
||||
|
|
|
@ -7,4 +7,14 @@ export interface QueryNetworksResponse {
|
|||
domain: string;
|
||||
};
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface ListLinksResponseItem {
|
||||
matrix_room_id: string;
|
||||
remote_room_channel: string;
|
||||
remote_room_server: string;
|
||||
}
|
||||
|
||||
export interface ListOpsResponse {
|
||||
operators: string[];
|
||||
}
|
|
@ -32,13 +32,12 @@ export class BridgeStore {
|
|||
return bridge.save();
|
||||
}
|
||||
|
||||
public static async setBridgeRoomConfig(requestingUserId: string, integrationType: string, inRoomId: string, newConfig: any): Promise<any> {
|
||||
public static async setBridgeRoomConfig(_requestingUserId: string, integrationType: string, _inRoomId: string, _newConfig: any): Promise<any> {
|
||||
const record = await BridgeRecord.findOne({where: {type: integrationType}});
|
||||
if (!record) throw new Error("Bridge not found");
|
||||
|
||||
if (integrationType === "irc") {
|
||||
const irc = new IrcBridge(requestingUserId);
|
||||
return irc.setRoomConfiguration(requestingUserId, inRoomId, newConfig);
|
||||
throw new Error("IRC Bridges should be modified with the dedicated API");
|
||||
} else throw new Error("Unsupported bridge");
|
||||
}
|
||||
|
||||
|
@ -53,7 +52,7 @@ export class BridgeStore {
|
|||
if (record.type === "irc") {
|
||||
if (!inRoomId) return {}; // The bridge's admin config is handled by other APIs
|
||||
const irc = new IrcBridge(requestingUserId);
|
||||
return irc.getRoomConfiguration(requestingUserId, inRoomId);
|
||||
return irc.getRoomConfiguration(inRoomId);
|
||||
} else return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Integration } from "./Integration";
|
||||
import BridgeRecord from "../db/models/BridgeRecord";
|
||||
import { AvailableNetworks } from "../bridges/IrcBridge";
|
||||
import { AvailableNetworks, LinkedChannels } from "../bridges/IrcBridge";
|
||||
|
||||
export class Bridge extends Integration {
|
||||
constructor(bridge: BridgeRecord, public config: any) {
|
||||
|
@ -12,17 +12,12 @@ export class Bridge extends Integration {
|
|||
argument: null, // not used
|
||||
}];
|
||||
|
||||
// We'll just say we aren't
|
||||
// We'll just say we don't support encryption
|
||||
this.isEncryptionSupported = false;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IrcBridgeConfiguration {
|
||||
availableNetworks: AvailableNetworks;
|
||||
links: {
|
||||
[networkId: string]: {
|
||||
channelName: string;
|
||||
addedByUserId: string;
|
||||
}[];
|
||||
};
|
||||
links: LinkedChannels;
|
||||
}
|
|
@ -1,13 +1,11 @@
|
|||
import { QueryNetworksResponse } from "../bridges/models/provision_responses";
|
||||
|
||||
export interface ModularIntegrationInfoResponse {
|
||||
bot_user_id: string;
|
||||
integrations?: any[];
|
||||
}
|
||||
|
||||
export interface ModularIrcQueryNetworksResponse {
|
||||
export interface ModularIrcResponse<T> {
|
||||
replies: {
|
||||
rid: string;
|
||||
response: QueryNetworksResponse;
|
||||
response: T;
|
||||
}[];
|
||||
}
|
|
@ -68,6 +68,8 @@ import { AdminIrcBridgeComponent } from "./admin/bridges/irc/irc.component";
|
|||
import { AdminIrcApiService } from "./shared/services/admin/admin-irc-api.service";
|
||||
import { AdminIrcBridgeNetworksComponent } from "./admin/bridges/irc/networks/networks.component";
|
||||
import { AdminIrcBridgeAddSelfhostedComponent } from "./admin/bridges/irc/add-selfhosted/add-selfhosted.component";
|
||||
import { IrcBridgeConfigComponent } from "./configs/bridge/irc/irc.bridge.component";
|
||||
import { IrcApiService } from "./shared/services/integrations/irc-api.service";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -129,6 +131,7 @@ import { AdminIrcBridgeAddSelfhostedComponent } from "./admin/bridges/irc/add-se
|
|||
AdminIrcBridgeComponent,
|
||||
AdminIrcBridgeNetworksComponent,
|
||||
AdminIrcBridgeAddSelfhostedComponent,
|
||||
IrcBridgeConfigComponent,
|
||||
|
||||
// Vendor
|
||||
],
|
||||
|
@ -144,6 +147,7 @@ import { AdminIrcBridgeAddSelfhostedComponent } from "./admin/bridges/irc/add-se
|
|||
AdminNebApiService,
|
||||
AdminUpstreamApiService,
|
||||
AdminIrcApiService,
|
||||
IrcApiService,
|
||||
{provide: Window, useValue: window},
|
||||
|
||||
// Vendor
|
||||
|
|
|
@ -23,6 +23,7 @@ import { RssComplexBotConfigComponent } from "./configs/complex-bot/rss/rss.comp
|
|||
import { TravisCiComplexBotConfigComponent } from "./configs/complex-bot/travisci/travisci.complex-bot.component";
|
||||
import { AdminBridgesComponent } from "./admin/bridges/bridges.component";
|
||||
import { AdminIrcBridgeComponent } from "./admin/bridges/irc/irc.component";
|
||||
import { IrcBridgeConfigComponent } from "./configs/bridge/irc/irc.bridge.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{path: "", component: HomeComponent},
|
||||
|
@ -142,15 +143,16 @@ const routes: Routes = [
|
|||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// path: "bridge",
|
||||
// children: [
|
||||
// {
|
||||
// path: "irc",
|
||||
//
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
{
|
||||
path: "bridge",
|
||||
children: [
|
||||
{
|
||||
path: "irc",
|
||||
component: IrcBridgeConfigComponent,
|
||||
data: {breadcrumb: "IRC Bridge Configuration", name: "IRC Bridge Configuration"},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
89
web/app/configs/bridge/irc/irc.bridge.component.html
Normal file
89
web/app/configs/bridge/irc/irc.bridge.component.html
Normal file
|
@ -0,0 +1,89 @@
|
|||
<my-bridge-config [bridgeComponent]="this">
|
||||
<ng-template #bridgeParamsTemplate>
|
||||
<my-ibox [isCollapsible]="true">
|
||||
<h5 class="my-ibox-title">
|
||||
Add an IRC channel
|
||||
</h5>
|
||||
<div class="my-ibox-content">
|
||||
<div class="alert alert-info">
|
||||
Bridging a channel requires authorization from a channel operator. When entering a channel below, a
|
||||
bot will
|
||||
join the channel to ensure it exists and has operators available.
|
||||
</div>
|
||||
|
||||
<div *ngIf="channelStep === 1">
|
||||
<label class="label-block">
|
||||
Network
|
||||
<select class="form-control form-control-sm" [(ngModel)]="networkId" [disabled]="loadingOps">
|
||||
<option *ngFor="let network of getNetworks()" [ngValue]="network.id">
|
||||
{{ network.name }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="label-block">
|
||||
Channel Name
|
||||
</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-addon">#</div>
|
||||
<input title="channel" type="text" class="form-control form-control-sm" [(ngModel)]="channel" [disabled]="loadingOps">
|
||||
</div>
|
||||
<div style="margin-top: 25px">
|
||||
<button type="button" class="btn btn-sm btn-primary" [disabled]="loadingOps" (click)="loadOps()">
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="channelStep === 2">
|
||||
<label class="label-block">
|
||||
Operator
|
||||
<span class="text-muted ">The person selected here will be asked to approve or deny the bridge request.</span>
|
||||
<select class="form-control form-control-sm" [(ngModel)]="op" [disabled]="requestingBridge">
|
||||
<option *ngFor="let op of ops" [ngValue]="op">{{ op }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<div style="margin-top: 25px">
|
||||
<button type="button" class="btn btn-sm btn-primary" [disabled]="requestingBridge" (click)="requestBridge()">
|
||||
Request Bridge
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</my-ibox>
|
||||
<my-ibox [isCollapsible]="true">
|
||||
<h5 class="my-ibox-title">
|
||||
IRC Networks
|
||||
</h5>
|
||||
<div class="my-ibox-content">
|
||||
<table class="table table-striped table-condensed table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Channel</th>
|
||||
<th>Network</th>
|
||||
<th class="actions-col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngIf="getChannels().length === 0">
|
||||
<td colspan="3">No bridged channels</td>
|
||||
</tr>
|
||||
<tr *ngFor="let channel of getChannels()">
|
||||
<td>
|
||||
{{ channel.name }}
|
||||
<span *ngIf="channel.pending" class="text-muted">(pending)</span>
|
||||
</td>
|
||||
<td>{{ channel.networkName }}</td>
|
||||
<td class="actions-col">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger"
|
||||
[disabled]="isUpdating || channel.pending"
|
||||
(click)="removeChannel(channel)">
|
||||
<i class="far fa-trash-alt"></i> Remove
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</my-ibox>
|
||||
</ng-template>
|
||||
</my-bridge-config>
|
4
web/app/configs/bridge/irc/irc.bridge.component.scss
Normal file
4
web/app/configs/bridge/irc/irc.bridge.component.scss
Normal file
|
@ -0,0 +1,4 @@
|
|||
.actions-col {
|
||||
width: 120px;
|
||||
text-align: center;
|
||||
}
|
133
web/app/configs/bridge/irc/irc.bridge.component.ts
Normal file
133
web/app/configs/bridge/irc/irc.bridge.component.ts
Normal file
|
@ -0,0 +1,133 @@
|
|||
import { Component } from "@angular/core";
|
||||
import { BridgeComponent } from "../bridge.component";
|
||||
import { FE_IrcBridgeAvailableNetworks } from "../../../shared/models/irc";
|
||||
import { IrcApiService } from "../../../shared/services/integrations/irc-api.service";
|
||||
|
||||
interface IrcConfig {
|
||||
availableNetworks: FE_IrcBridgeAvailableNetworks;
|
||||
links: {
|
||||
[networkId: string]: {
|
||||
channelName: string;
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
interface LocalChannel {
|
||||
name: string;
|
||||
networkId: string;
|
||||
networkName: string;
|
||||
pending: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: "irc.bridge.component.html",
|
||||
styleUrls: ["irc.bridge.component.scss"],
|
||||
})
|
||||
export class IrcBridgeConfigComponent extends BridgeComponent<IrcConfig> {
|
||||
|
||||
public loadingOps = false;
|
||||
public requestingBridge = false;
|
||||
public channelStep = 1;
|
||||
public networkId: string;
|
||||
public channel = "";
|
||||
public ops: string[];
|
||||
public op: string;
|
||||
public pending: LocalChannel[] = [];
|
||||
|
||||
constructor(private irc: IrcApiService) {
|
||||
super("irc");
|
||||
}
|
||||
|
||||
private resetForm() {
|
||||
this.networkId = this.getNetworks()[0].id;
|
||||
this.channel = "";
|
||||
this.ops = [];
|
||||
this.channelStep = 1;
|
||||
}
|
||||
|
||||
public getNetworks(): { id: string, name: string }[] {
|
||||
const ids = Object.keys(this.bridge.config.availableNetworks);
|
||||
if (!this.networkId) setTimeout(() => this.networkId = ids[0], 0);
|
||||
return ids.map(i => {
|
||||
return {id: i, name: this.bridge.config.availableNetworks[i].name};
|
||||
});
|
||||
}
|
||||
|
||||
public loadOps() {
|
||||
if (!this.channel.trim()) {
|
||||
this.toaster.pop("warning", "Please enter a channel name");
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadingOps = true;
|
||||
this.irc.getOperators(this.networkId, this.channel).then(ops => {
|
||||
this.ops = ops;
|
||||
this.op = ops[0];
|
||||
this.loadingOps = false;
|
||||
this.channelStep = 2;
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
this.loadingOps = false;
|
||||
this.toaster.pop("error", "Error loading channel operators");
|
||||
});
|
||||
}
|
||||
|
||||
public async requestBridge() {
|
||||
this.requestingBridge = true;
|
||||
const bridgeUserId = this.bridge.config.availableNetworks[this.networkId].bridgeUserId;
|
||||
|
||||
const memberEvent = await this.scalarClientApi.getMembershipState(this.roomId, bridgeUserId);
|
||||
const isJoined = memberEvent && memberEvent.response && ["join", "invite"].indexOf(memberEvent.response.membership) !== -1;
|
||||
|
||||
if (!isJoined) await this.scalarClientApi.inviteUser(this.roomId, bridgeUserId);
|
||||
|
||||
try {
|
||||
await this.scalarClientApi.setUserPowerLevel(this.roomId, bridgeUserId, 100);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this.requestingBridge = false;
|
||||
this.toaster.pop("error", "Failed to make the bridge an administrator", "Please ensure you are an 'Admin' for the room");
|
||||
return;
|
||||
}
|
||||
|
||||
this.irc.requestLink(this.roomId, this.networkId, this.channel, this.op).then(() => {
|
||||
this.requestingBridge = false;
|
||||
this.pending.push({
|
||||
name: this.channel,
|
||||
networkId: this.networkId,
|
||||
networkName: this.bridge.config.availableNetworks[this.networkId].name,
|
||||
pending: true,
|
||||
});
|
||||
this.resetForm();
|
||||
this.toaster.pop("success", "Link requested!", "The operator selected will have to approve the bridge for it to work");
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
this.requestingBridge = false;
|
||||
this.toaster.pop("error", "Failed to request a link");
|
||||
});
|
||||
}
|
||||
|
||||
public getChannels(): LocalChannel[] {
|
||||
const channels: LocalChannel[] = [];
|
||||
this.pending.forEach(p => channels.push(p));
|
||||
|
||||
for (const networkId of Object.keys(this.bridge.config.links)) {
|
||||
const network = this.bridge.config.availableNetworks[networkId];
|
||||
|
||||
for (const channel of this.bridge.config.links[networkId]) {
|
||||
channels.push({
|
||||
networkId: networkId,
|
||||
networkName: network.name,
|
||||
name: channel.channelName,
|
||||
pending: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return channels;
|
||||
}
|
||||
|
||||
public removeChannel(channel: any) {
|
||||
console.log(channel);
|
||||
}
|
||||
}
|
|
@ -27,7 +27,6 @@ export interface FE_ComplexBot<T> extends FE_Integration {
|
|||
}
|
||||
|
||||
export interface FE_Bridge<T> extends FE_Integration {
|
||||
bridgeUserId: string;
|
||||
config: T;
|
||||
}
|
||||
|
||||
|
|
|
@ -55,4 +55,10 @@ export interface CanSendEventResponse extends ScalarRoomResponse {
|
|||
|
||||
export interface RoomEncryptionStatusResponse extends ScalarRoomResponse {
|
||||
response: boolean;
|
||||
}
|
||||
|
||||
export interface SetPowerLevelResponse extends ScalarRoomResponse {
|
||||
response: {
|
||||
success: boolean;
|
||||
};
|
||||
}
|
18
web/app/shared/services/integrations/irc-api.service.ts
Normal file
18
web/app/shared/services/integrations/irc-api.service.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { Injectable } from "@angular/core";
|
||||
import { Http } from "@angular/http";
|
||||
import { AuthedApi } from "../authed-api";
|
||||
|
||||
@Injectable()
|
||||
export class IrcApiService extends AuthedApi {
|
||||
constructor(http: Http) {
|
||||
super(http);
|
||||
}
|
||||
|
||||
public getOperators(networkId: string, channelNoHash: string): Promise<string[]> {
|
||||
return this.authedGet("/api/v1/dimension/irc/" + networkId + "/channel/" + channelNoHash + "/ops").map(r => r.json()).toPromise();
|
||||
}
|
||||
|
||||
public requestLink(roomId: string, networkId: string, channelNoHash: string, op: string): Promise<any> {
|
||||
return this.authedPost("/api/v1/dimension/irc/" + networkId + "/channel/" + channelNoHash + "/link/" + roomId, {op: op}).map(r => r.json()).toPromise();
|
||||
}
|
||||
}
|
|
@ -3,8 +3,10 @@ import * as randomString from "random-string";
|
|||
import {
|
||||
CanSendEventResponse,
|
||||
JoinRuleStateResponse,
|
||||
MembershipStateResponse, RoomEncryptionStatusResponse,
|
||||
MembershipStateResponse,
|
||||
RoomEncryptionStatusResponse,
|
||||
ScalarSuccessResponse,
|
||||
SetPowerLevelResponse,
|
||||
WidgetsResponse
|
||||
} from "../../models/server-client-responses";
|
||||
import { EditableWidget } from "../../models/widget";
|
||||
|
@ -87,6 +89,14 @@ export class ScalarClientApiService {
|
|||
});
|
||||
}
|
||||
|
||||
public setUserPowerLevel(roomId: string, userId: string, powerLevel: number): Promise<SetPowerLevelResponse> {
|
||||
return this.callAction("set_bot_power", {
|
||||
room_id: roomId,
|
||||
user_id: userId,
|
||||
level: powerLevel,
|
||||
});
|
||||
}
|
||||
|
||||
private callAction(action, payload): Promise<any> {
|
||||
let requestKey = randomString({length: 20});
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
Loading…
Reference in New Issue
Block a user