mirror of
https://github.com/turt2live/matrix-dimension.git
synced 2024-10-01 05:05:53 +00:00
Support simple custom bots
Fixes https://github.com/turt2live/matrix-dimension/issues/165
This commit is contained in:
parent
c1a55ade7c
commit
82343da942
@ -52,3 +52,4 @@ export const CACHE_STICKERS = "stickers";
|
|||||||
export const CACHE_TELEGRAM_BRIDGE = "telegram-bridge";
|
export const CACHE_TELEGRAM_BRIDGE = "telegram-bridge";
|
||||||
export const CACHE_WEBHOOKS_BRIDGE = "webhooks-bridge";
|
export const CACHE_WEBHOOKS_BRIDGE = "webhooks-bridge";
|
||||||
export const CACHE_GITTER_BRIDGE = "gitter-bridge";
|
export const CACHE_GITTER_BRIDGE = "gitter-bridge";
|
||||||
|
export const CACHE_SIMPLE_BOTS = "simple-bots";
|
92
src/api/admin/AdminCustomSimpleBotService.ts
Normal file
92
src/api/admin/AdminCustomSimpleBotService.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { DELETE, GET, Path, PathParam, POST, QueryParam } from "typescript-rest";
|
||||||
|
import { AdminService } from "./AdminService";
|
||||||
|
import { ApiError } from "../ApiError";
|
||||||
|
import { LogService } from "matrix-js-snippets";
|
||||||
|
import { BotStore } from "../../db/BotStore";
|
||||||
|
import { Cache, CACHE_INTEGRATIONS } from "../../MemoryCache";
|
||||||
|
|
||||||
|
interface BotResponse extends BotRequest {
|
||||||
|
id: number;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BotRequest {
|
||||||
|
name: string;
|
||||||
|
avatarUrl: string;
|
||||||
|
description: string;
|
||||||
|
isEnabled: boolean;
|
||||||
|
isPublic: boolean;
|
||||||
|
userId: string;
|
||||||
|
accessToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BotProfile {
|
||||||
|
name: string;
|
||||||
|
avatarUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Administrative API for managing custom simple bots hosted by Dimension
|
||||||
|
*/
|
||||||
|
@Path("/api/v1/dimension/admin/bots/simple/custom")
|
||||||
|
export class AdminCustomSimpleBotService {
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("all")
|
||||||
|
public async getBots(@QueryParam("scalar_token") scalarToken: string): Promise<BotResponse[]> {
|
||||||
|
await AdminService.validateAndGetAdminTokenOwner(scalarToken);
|
||||||
|
return BotStore.getCustomBots();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path(":botId")
|
||||||
|
public async getBot(@QueryParam("scalar_token") scalarToken: string, @PathParam("botId") botId: number): Promise<BotResponse> {
|
||||||
|
await AdminService.validateAndGetAdminTokenOwner(scalarToken);
|
||||||
|
|
||||||
|
const bot = await BotStore.getCustomBot(botId);
|
||||||
|
if (!bot) throw new ApiError(404, "Bot not found");
|
||||||
|
return bot;
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("new")
|
||||||
|
public async createBot(@QueryParam("scalar_token") scalarToken: string, request: BotRequest): Promise<BotResponse> {
|
||||||
|
const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken);
|
||||||
|
|
||||||
|
const bot = await BotStore.createCustom(request);
|
||||||
|
LogService.info("AdminCustomSimpleBotService", userId + " created a simple bot");
|
||||||
|
Cache.for(CACHE_INTEGRATIONS).clear();
|
||||||
|
return bot;
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path(":botId")
|
||||||
|
public async updateBot(@QueryParam("scalar_token") scalarToken: string, @PathParam("botId") botId: number, request: BotRequest): Promise<BotResponse> {
|
||||||
|
const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken);
|
||||||
|
|
||||||
|
const bot = await BotStore.updateCustom(botId, request);
|
||||||
|
LogService.info("AdminCustomSimpleBotService", userId + " updated a simple bot");
|
||||||
|
Cache.for(CACHE_INTEGRATIONS).clear();
|
||||||
|
return bot;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DELETE
|
||||||
|
@Path(":botId")
|
||||||
|
public async deleteBot(@QueryParam("scalar_token") scalarToken: string, @PathParam("botId") botId: number): Promise<any> {
|
||||||
|
const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken);
|
||||||
|
|
||||||
|
await BotStore.deleteCustom(botId);
|
||||||
|
LogService.info("AdminCustomSimpleBotService", userId + " deleted a simple bot");
|
||||||
|
Cache.for(CACHE_INTEGRATIONS).clear();
|
||||||
|
return {}; // 200 OK
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("profile/:userId")
|
||||||
|
public async getProfile(@QueryParam("scalar_token") scalarToken: string, @PathParam("userId") userId: string): Promise<BotProfile> {
|
||||||
|
await AdminService.validateAndGetAdminTokenOwner(scalarToken);
|
||||||
|
|
||||||
|
const profile = await BotStore.getProfile(userId);
|
||||||
|
return {name: profile.displayName, avatarUrl: profile.avatarMxc};
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ import { NebStore } from "../../db/NebStore";
|
|||||||
import { ComplexBot } from "../../integrations/ComplexBot";
|
import { ComplexBot } from "../../integrations/ComplexBot";
|
||||||
import { Bridge } from "../../integrations/Bridge";
|
import { Bridge } from "../../integrations/Bridge";
|
||||||
import { BridgeStore } from "../../db/BridgeStore";
|
import { BridgeStore } from "../../db/BridgeStore";
|
||||||
|
import { BotStore } from "../../db/BotStore";
|
||||||
|
|
||||||
export interface IntegrationsResponse {
|
export interface IntegrationsResponse {
|
||||||
widgets: Widget[],
|
widgets: Widget[],
|
||||||
@ -58,7 +59,11 @@ export class DimensionIntegrationsService {
|
|||||||
const cached = Cache.for(CACHE_INTEGRATIONS).get("simple_bots");
|
const cached = Cache.for(CACHE_INTEGRATIONS).get("simple_bots");
|
||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
|
|
||||||
const bots = await NebStore.listSimpleBots(userId);
|
const nebs = await NebStore.listSimpleBots(userId);
|
||||||
|
const custom = (await BotStore.getCustomBots())
|
||||||
|
.filter(b => b.isEnabled)
|
||||||
|
.map(b => SimpleBot.fromCached(b));
|
||||||
|
const bots = [...nebs, ...custom];
|
||||||
Cache.for(CACHE_INTEGRATIONS).put("simple_bots", bots);
|
Cache.for(CACHE_INTEGRATIONS).put("simple_bots", bots);
|
||||||
return bots;
|
return bots;
|
||||||
}
|
}
|
||||||
@ -121,7 +126,11 @@ export class DimensionIntegrationsService {
|
|||||||
const userId = await ScalarService.getTokenOwner(scalarToken);
|
const userId = await ScalarService.getTokenOwner(scalarToken);
|
||||||
|
|
||||||
if (category === "widget") throw new ApiError(400, "Widgets should be removed client-side");
|
if (category === "widget") throw new ApiError(400, "Widgets should be removed client-side");
|
||||||
else if (category === "bot") await NebStore.removeSimpleBot(integrationType, roomId, userId);
|
else if (category === "bot") {
|
||||||
|
if (integrationType.startsWith(BotStore.TYPE_PREFIX)) {
|
||||||
|
await BotStore.removeCustomByTypeFromRoom(integrationType, roomId);
|
||||||
|
} else await NebStore.removeSimpleBot(integrationType, roomId, userId);
|
||||||
|
}
|
||||||
else if (category === "complex-bot") throw new ApiError(400, "Complex bots should be removed automatically");
|
else if (category === "complex-bot") throw new ApiError(400, "Complex bots should be removed automatically");
|
||||||
else if (category === "bridge") throw new ApiError(400, "Bridges should be removed automatically");
|
else if (category === "bridge") throw new ApiError(400, "Bridges should be removed automatically");
|
||||||
else throw new ApiError(400, "Unrecognized category");
|
else throw new ApiError(400, "Unrecognized category");
|
||||||
|
141
src/db/BotStore.ts
Normal file
141
src/db/BotStore.ts
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import CustomSimpleBotRecord from "./models/CustomSimpleBotRecord";
|
||||||
|
import { Cache, CACHE_SIMPLE_BOTS } from "../MemoryCache";
|
||||||
|
import { MatrixLiteClient } from "../matrix/MatrixLiteClient";
|
||||||
|
import config from "../config";
|
||||||
|
import * as randomString from "random-string";
|
||||||
|
import { LogService } from "matrix-js-snippets";
|
||||||
|
|
||||||
|
export interface CachedSimpleBot extends SimpleBotTemplate {
|
||||||
|
id: number;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SimpleBotTemplate {
|
||||||
|
name: string;
|
||||||
|
avatarUrl: string;
|
||||||
|
description: string;
|
||||||
|
isEnabled: boolean;
|
||||||
|
isPublic: boolean;
|
||||||
|
userId: string;
|
||||||
|
accessToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CachedProfile {
|
||||||
|
userId: string;
|
||||||
|
displayName: string;
|
||||||
|
avatarMxc: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BotStore {
|
||||||
|
|
||||||
|
public static readonly TYPE_PREFIX = "custom_";
|
||||||
|
|
||||||
|
public static async createCustom(template: SimpleBotTemplate): Promise<CachedSimpleBot> {
|
||||||
|
const newType = `${BotStore.TYPE_PREFIX}${randomString({length: 32})}`;
|
||||||
|
const bot = await CustomSimpleBotRecord.create({...template, type: newType});
|
||||||
|
return BotStore.mapAndCacheCustom(bot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async updateCustom(id: number, template: SimpleBotTemplate): Promise<CachedSimpleBot> {
|
||||||
|
const record = await CustomSimpleBotRecord.findByPrimary(id);
|
||||||
|
if (!record) return null;
|
||||||
|
|
||||||
|
record.name = template.name;
|
||||||
|
record.avatarUrl = template.avatarUrl;
|
||||||
|
record.description = template.description;
|
||||||
|
record.isEnabled = template.isEnabled;
|
||||||
|
record.isPublic = template.isPublic;
|
||||||
|
record.userId = template.userId;
|
||||||
|
record.accessToken = template.accessToken;
|
||||||
|
await record.save();
|
||||||
|
|
||||||
|
return BotStore.mapAndCacheCustom(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async deleteCustom(id: number): Promise<any> {
|
||||||
|
const record = await CustomSimpleBotRecord.findByPrimary(id);
|
||||||
|
if (!record) return null;
|
||||||
|
|
||||||
|
await record.destroy();
|
||||||
|
Cache.for(CACHE_SIMPLE_BOTS).del("bots");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async removeCustomByTypeFromRoom(type: string, roomId: string): Promise<any> {
|
||||||
|
const record = await CustomSimpleBotRecord.findOne({where: {type: type}});
|
||||||
|
if (!record) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = new MatrixLiteClient(record.accessToken);
|
||||||
|
await client.leaveRoom(roomId);
|
||||||
|
} catch (e) {
|
||||||
|
LogService.error("BotStore", "Error leaving room " + roomId + " for integration type " + type);
|
||||||
|
LogService.error("BotStore", e);
|
||||||
|
throw new Error("Error leaving room");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getCustomBot(id: number): Promise<CachedSimpleBot> {
|
||||||
|
const bots = await BotStore.getCustomBots();
|
||||||
|
return bots.find(b => b.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getCustomBots(): Promise<CachedSimpleBot[]> {
|
||||||
|
if (!Cache.for(CACHE_SIMPLE_BOTS).get("bots")) {
|
||||||
|
const bots = await CustomSimpleBotRecord.findAll();
|
||||||
|
const cached = [];
|
||||||
|
await Promise.all(bots.map(async (b) => {
|
||||||
|
cached.push(await BotStore.mapAndCacheCustom(b));
|
||||||
|
return Promise.resolve();
|
||||||
|
}));
|
||||||
|
Cache.for(CACHE_SIMPLE_BOTS).put("bots", cached);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Cache.for(CACHE_SIMPLE_BOTS).get("bots").map(b => BotStore.cloneBot(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getProfile(userId: string): Promise<CachedProfile> {
|
||||||
|
const key = "profile_" + userId;
|
||||||
|
if (!Cache.for(CACHE_SIMPLE_BOTS).get(key)) {
|
||||||
|
const client = new MatrixLiteClient(config.homeserver.accessToken);
|
||||||
|
const profile = await client.getProfile(userId);
|
||||||
|
const cached = {userId, displayName: profile.displayname, avatarMxc: profile.avatar_url};
|
||||||
|
Cache.for(CACHE_SIMPLE_BOTS).put(key, cached);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Cache.for(CACHE_SIMPLE_BOTS).get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async mapAndCacheCustom(bot: CustomSimpleBotRecord): Promise<CachedSimpleBot> {
|
||||||
|
Cache.for(CACHE_SIMPLE_BOTS).del("bots");
|
||||||
|
return {
|
||||||
|
id: bot.id,
|
||||||
|
type: bot.type,
|
||||||
|
name: bot.name,
|
||||||
|
avatarUrl: bot.avatarUrl,
|
||||||
|
description: bot.description,
|
||||||
|
isEnabled: bot.isEnabled,
|
||||||
|
isPublic: bot.isPublic,
|
||||||
|
userId: bot.userId,
|
||||||
|
accessToken: bot.accessToken,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static cloneBot(bot: CachedSimpleBot): CachedSimpleBot {
|
||||||
|
return {
|
||||||
|
id: bot.id,
|
||||||
|
type: bot.type,
|
||||||
|
name: bot.name,
|
||||||
|
avatarUrl: bot.avatarUrl,
|
||||||
|
description: bot.description,
|
||||||
|
isEnabled: bot.isEnabled,
|
||||||
|
isPublic: bot.isPublic,
|
||||||
|
userId: bot.userId,
|
||||||
|
accessToken: bot.accessToken,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,7 @@ import UserStickerPack from "./models/UserStickerPack";
|
|||||||
import TelegramBridgeRecord from "./models/TelegramBridgeRecord";
|
import TelegramBridgeRecord from "./models/TelegramBridgeRecord";
|
||||||
import WebhookBridgeRecord from "./models/WebhookBridgeRecord";
|
import WebhookBridgeRecord from "./models/WebhookBridgeRecord";
|
||||||
import GitterBridgeRecord from "./models/GitterBridgeRecord";
|
import GitterBridgeRecord from "./models/GitterBridgeRecord";
|
||||||
|
import CustomSimpleBotRecord from "./models/CustomSimpleBotRecord";
|
||||||
|
|
||||||
class _DimensionStore {
|
class _DimensionStore {
|
||||||
private sequelize: Sequelize;
|
private sequelize: Sequelize;
|
||||||
@ -59,6 +60,7 @@ class _DimensionStore {
|
|||||||
TelegramBridgeRecord,
|
TelegramBridgeRecord,
|
||||||
WebhookBridgeRecord,
|
WebhookBridgeRecord,
|
||||||
GitterBridgeRecord,
|
GitterBridgeRecord,
|
||||||
|
CustomSimpleBotRecord,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ export class NebStore {
|
|||||||
const rawIntegrations = await NebStore.listEnabledNebSimpleBots();
|
const rawIntegrations = await NebStore.listEnabledNebSimpleBots();
|
||||||
return Promise.all(rawIntegrations.map(async i => {
|
return Promise.all(rawIntegrations.map(async i => {
|
||||||
const proxy = new NebProxy(i.neb, requestingUserId);
|
const proxy = new NebProxy(i.neb, requestingUserId);
|
||||||
return new SimpleBot(i.integration, await proxy.getBotUserId(i.integration));
|
return SimpleBot.fromNeb(i.integration, await proxy.getBotUserId(i.integration));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
23
src/db/migrations/20181022185045-AddCustomSimpleBots.ts
Normal file
23
src/db/migrations/20181022185045-AddCustomSimpleBots.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { QueryInterface } from "sequelize";
|
||||||
|
import { DataType } from "sequelize-typescript";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => queryInterface.createTable("dimension_custom_simple_bots", {
|
||||||
|
"id": {type: DataType.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false},
|
||||||
|
"type": {type: DataType.STRING, allowNull: false},
|
||||||
|
"name": {type: DataType.STRING, allowNull: false},
|
||||||
|
"avatarUrl": {type: DataType.STRING, allowNull: false},
|
||||||
|
"description": {type: DataType.STRING, allowNull: false},
|
||||||
|
"isEnabled": {type: DataType.BOOLEAN, allowNull: false},
|
||||||
|
"isPublic": {type: DataType.BOOLEAN, allowNull: false},
|
||||||
|
"userId": {type: DataType.STRING, allowNull: false},
|
||||||
|
"accessToken": {type: DataType.STRING, allowNull: false},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => queryInterface.dropTable("dimension_custom_simple_bots"));
|
||||||
|
}
|
||||||
|
}
|
38
src/db/models/CustomSimpleBotRecord.ts
Normal file
38
src/db/models/CustomSimpleBotRecord.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { AutoIncrement, Column, Model, PrimaryKey, Table } from "sequelize-typescript";
|
||||||
|
import { IntegrationRecord } from "./IntegrationRecord";
|
||||||
|
|
||||||
|
@Table({
|
||||||
|
tableName: "dimension_custom_simple_bots",
|
||||||
|
underscoredAll: false,
|
||||||
|
timestamps: false,
|
||||||
|
})
|
||||||
|
export default class CustomSimpleBotRecord extends Model<CustomSimpleBotRecord> implements IntegrationRecord {
|
||||||
|
@PrimaryKey
|
||||||
|
@AutoIncrement
|
||||||
|
@Column
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
avatarUrl: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
isEnabled: boolean;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
isPublic: boolean;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
accessToken: string;
|
||||||
|
}
|
@ -1,8 +1,10 @@
|
|||||||
import { Integration } from "./Integration";
|
import { Integration } from "./Integration";
|
||||||
|
import { CachedSimpleBot } from "../db/BotStore";
|
||||||
|
import { IntegrationRecord } from "../db/models/IntegrationRecord";
|
||||||
import NebIntegration from "../db/models/NebIntegration";
|
import NebIntegration from "../db/models/NebIntegration";
|
||||||
|
|
||||||
export class SimpleBot extends Integration {
|
export class SimpleBot extends Integration {
|
||||||
constructor(bot: NebIntegration, public userId: string) {
|
private constructor(bot: IntegrationRecord, public userId: string) {
|
||||||
super(bot);
|
super(bot);
|
||||||
this.category = "bot";
|
this.category = "bot";
|
||||||
this.requirements = [];
|
this.requirements = [];
|
||||||
@ -10,4 +12,13 @@ export class SimpleBot extends Integration {
|
|||||||
// We're going to go ahead and claim that none of the bots are supported in e2e rooms
|
// We're going to go ahead and claim that none of the bots are supported in e2e rooms
|
||||||
this.isEncryptionSupported = false;
|
this.isEncryptionSupported = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static fromCached(bot: CachedSimpleBot) {
|
||||||
|
delete bot.accessToken;
|
||||||
|
return new SimpleBot(bot, bot.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromNeb(bot: NebIntegration, userId: string) {
|
||||||
|
return new SimpleBot(bot, userId);
|
||||||
|
}
|
||||||
}
|
}
|
@ -6,6 +6,11 @@ export interface MatrixUrlPreview {
|
|||||||
"og:title"?: string;
|
"og:title"?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MatrixUserProfile {
|
||||||
|
displayname?: string;
|
||||||
|
avatar_url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class MatrixLiteClient {
|
export class MatrixLiteClient {
|
||||||
|
|
||||||
constructor(private accessToken: string) {
|
constructor(private accessToken: string) {
|
||||||
@ -37,6 +42,22 @@ export class MatrixLiteClient {
|
|||||||
return response['user_id'];
|
return response['user_id'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async leaveRoom(roomId: string): Promise<string> {
|
||||||
|
return doClientApiCall(
|
||||||
|
"POST",
|
||||||
|
"/_matrix/client/r0/rooms/" + roomId + "/leave",
|
||||||
|
{access_token: this.accessToken}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getProfile(userId: string): Promise<MatrixUserProfile> {
|
||||||
|
return doClientApiCall(
|
||||||
|
"GET",
|
||||||
|
"/_matrix/client/r0/profile/" + userId,
|
||||||
|
{access_token: this.accessToken},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public async getDisplayName(): Promise<string> {
|
public async getDisplayName(): Promise<string> {
|
||||||
const response = await doClientApiCall(
|
const response = await doClientApiCall(
|
||||||
"GET",
|
"GET",
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<li (click)="goto('')" [ngClass]="[isActive('', true) ? 'active' : '']">Dashboard</li>
|
<li (click)="goto('')" [ngClass]="[isActive('', true) ? 'active' : '']">Dashboard</li>
|
||||||
<li (click)="goto('widgets')" [ngClass]="[isActive('widgets') ? 'active' : '']">Widgets</li>
|
<li (click)="goto('widgets')" [ngClass]="[isActive('widgets') ? 'active' : '']">Widgets</li>
|
||||||
<li (click)="goto('neb')" [ngClass]="[isActive('neb') ? 'active' : '']">go-neb</li>
|
<li (click)="goto('neb')" [ngClass]="[isActive('neb') ? 'active' : '']">go-neb</li>
|
||||||
|
<li (click)="goto('custom-bots')" [ngClass]="[isActive('custom-bots') ? 'active' : '']">Custom Bots</li>
|
||||||
<li (click)="goto('bridges')" [ngClass]="[isActive('bridges') ? 'active' : '']">Bridges</li>
|
<li (click)="goto('bridges')" [ngClass]="[isActive('bridges') ? 'active' : '']">Bridges</li>
|
||||||
<li (click)="goto('stickerpacks')" [ngClass]="[isActive('stickerpacks') ? 'active' : '']">Sticker Packs</li>
|
<li (click)="goto('stickerpacks')" [ngClass]="[isActive('stickerpacks') ? 'active' : '']">Sticker Packs</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
54
web/app/admin/custom-bots/add/add.component.html
Normal file
54
web/app/admin/custom-bots/add/add.component.html
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<div class="dialog">
|
||||||
|
<div class="dialog-header">
|
||||||
|
<h4>{{ isAdding ? "Add a new" : "Edit" }} custom bot</h4>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-content">
|
||||||
|
<label class="label-block">
|
||||||
|
User ID
|
||||||
|
<span class="text-muted">The user ID that Dimension will invite to rooms.</span>
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
placeholder="@yourbot:example.org"
|
||||||
|
[(ngModel)]="bot.userId" [disabled]="isSaving" (blur)="loadProfile()"/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="label-block">
|
||||||
|
Description
|
||||||
|
<span class="text-muted ">A few words here will help people understand what the bot does.</span>
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
placeholder="Does awesome things"
|
||||||
|
[(ngModel)]="bot.description" [disabled]="isSaving"/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="label-block">
|
||||||
|
Display Name
|
||||||
|
<span class="text-muted ">This is the name Dimension will use to tell users which bot this is.</span>
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
placeholder="Cool Bot"
|
||||||
|
[(ngModel)]="bot.name" [disabled]="isSaving"/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="label-block">
|
||||||
|
Avatar URL
|
||||||
|
<span class="text-muted ">This can either be an MXC URI or a plain URL.</span>
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
placeholder="mxc://example.org/C00lAvat4r"
|
||||||
|
[(ngModel)]="bot.avatarUrl" [disabled]="isSaving"/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="label-block">
|
||||||
|
Access Token
|
||||||
|
<span class="text-muted ">This is used by Dimension to force the bot to leave the room when the user removes the bot. <a href="https://t2bot.io/docs/access_tokens/" target="_blank">Learn more about access tokens</a>.</span>
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
placeholder="MDaX..."
|
||||||
|
[(ngModel)]="bot.accessToken" [disabled]="isSaving"/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<button type="button" (click)="add()" title="close" class="btn btn-primary btn-sm">
|
||||||
|
<i class="far fa-save"></i> Save
|
||||||
|
</button>
|
||||||
|
<button type="button" (click)="dialog.close()" title="close" class="btn btn-secondary btn-sm">
|
||||||
|
<i class="far fa-times-circle"></i> Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
0
web/app/admin/custom-bots/add/add.component.scss
Normal file
0
web/app/admin/custom-bots/add/add.component.scss
Normal file
96
web/app/admin/custom-bots/add/add.component.ts
Normal file
96
web/app/admin/custom-bots/add/add.component.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
import { ToasterService } from "angular2-toaster";
|
||||||
|
import { DialogRef, ModalComponent } from "ngx-modialog";
|
||||||
|
import { BSModalContext } from "ngx-modialog/plugins/bootstrap";
|
||||||
|
import { FE_CustomSimpleBot, FE_UserProfile } from "../../../shared/models/admin-responses";
|
||||||
|
import { AdminCustomSimpleBotsApiService } from "../../../shared/services/admin/admin-custom-simple-bots-api.service";
|
||||||
|
|
||||||
|
export class AddCustomBotDialogContext extends BSModalContext {
|
||||||
|
bot: FE_CustomSimpleBot;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: "./add.component.html",
|
||||||
|
styleUrls: ["./add.component.scss"],
|
||||||
|
})
|
||||||
|
export class AdminAddCustomBotComponent implements ModalComponent<AddCustomBotDialogContext> {
|
||||||
|
|
||||||
|
public bot: FE_CustomSimpleBot;
|
||||||
|
public isAdding = false;
|
||||||
|
public isSaving = false;
|
||||||
|
|
||||||
|
private lastProfile: FE_UserProfile;
|
||||||
|
|
||||||
|
constructor(public dialog: DialogRef<AddCustomBotDialogContext>,
|
||||||
|
private botApi: AdminCustomSimpleBotsApiService,
|
||||||
|
private toaster: ToasterService) {
|
||||||
|
this.bot = this.dialog.context.bot || <FE_CustomSimpleBot>{};
|
||||||
|
this.isAdding = !this.dialog.context.bot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public loadProfile() {
|
||||||
|
this.botApi.getProfile(this.bot.userId).then(profile => {
|
||||||
|
if (!this.lastProfile || this.lastProfile.name === this.bot.name) {
|
||||||
|
this.bot.name = profile.name;
|
||||||
|
}
|
||||||
|
if (!this.lastProfile || this.lastProfile.avatarUrl === this.bot.avatarUrl) {
|
||||||
|
this.bot.avatarUrl = profile.avatarUrl;
|
||||||
|
}
|
||||||
|
this.lastProfile = profile;
|
||||||
|
}).catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
// We don't need to alert the user - this is non-fatal
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public add() {
|
||||||
|
if (!this.bot.name) {
|
||||||
|
this.toaster.pop("warning", "Please enter a name for the bot");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.bot.avatarUrl) {
|
||||||
|
this.toaster.pop("warning", "Please enter an avatar URL for the bot");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.bot.userId) {
|
||||||
|
this.toaster.pop("warning", "Please enter a user ID for the bot");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.bot.description) {
|
||||||
|
this.toaster.pop("warning", "Please enter a description for the bot");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.bot.accessToken) {
|
||||||
|
this.toaster.pop("warning", "Please enter an access token for the bot");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isSaving = true;
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
name: this.bot.name,
|
||||||
|
avatarUrl: this.bot.avatarUrl,
|
||||||
|
userId: this.bot.userId,
|
||||||
|
accessToken: this.bot.accessToken,
|
||||||
|
description: this.bot.description,
|
||||||
|
isEnabled: true,
|
||||||
|
isPublic: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let promise = null;
|
||||||
|
if (this.isAdding) {
|
||||||
|
promise = this.botApi.createBot(config);
|
||||||
|
} else {
|
||||||
|
promise = this.botApi.updateBot(this.bot.id, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.then(() => {
|
||||||
|
this.toaster.pop("success", "Bot updated");
|
||||||
|
this.dialog.close();
|
||||||
|
}).catch(error => {
|
||||||
|
this.isSaving = false;
|
||||||
|
console.error(error);
|
||||||
|
this.toaster.pop("error", "Error updating bot");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
45
web/app/admin/custom-bots/custom-bots.component.html
Normal file
45
web/app/admin/custom-bots/custom-bots.component.html
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<div *ngIf="isLoading">
|
||||||
|
<my-spinner></my-spinner>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="!isLoading">
|
||||||
|
<my-ibox title="Custom bots">
|
||||||
|
<div class="my-ibox-content">
|
||||||
|
<p>
|
||||||
|
Custom bots give you the ability to add your own bots to Dimension for users to add
|
||||||
|
to their rooms. These bots can't have any configuration to them and must be able to
|
||||||
|
accept room invites on their own. All Dimension will do when a user wants to add the
|
||||||
|
bot is invite it to the room.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<table class="table table-striped table-condensed table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th class="text-center" style="width: 120px;">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngIf="!bots || bots.length === 0">
|
||||||
|
<td colspan="2"><i>No custom bots.</i></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngFor="let bot of bots trackById">
|
||||||
|
<td>
|
||||||
|
{{ bot.name }}
|
||||||
|
<span class="text-muted" style="display: inline-block;">({{ bot.userId }})</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button type="button" class="btn btn-link btn-sm editButton" (click)="editBot(bot)">
|
||||||
|
<i class="fa fa-pencil-alt"></i>
|
||||||
|
</button>
|
||||||
|
<ui-switch [checked]="bot.isEnabled" size="small" [disabled]="isUpdating"
|
||||||
|
(change)="toggleBot(bot)"></ui-switch>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<button type="button" class="btn btn-success btn-sm" (click)="addBot()">
|
||||||
|
<i class="fa fa-plus"></i> Add custom bot
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</my-ibox>
|
||||||
|
</div>
|
8
web/app/admin/custom-bots/custom-bots.component.scss
Normal file
8
web/app/admin/custom-bots/custom-bots.component.scss
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
tr td:last-child {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editButton {
|
||||||
|
cursor: pointer;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
73
web/app/admin/custom-bots/custom-bots.component.ts
Normal file
73
web/app/admin/custom-bots/custom-bots.component.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
import { ToasterService } from "angular2-toaster";
|
||||||
|
import { FE_CustomSimpleBot } from "../../shared/models/admin-responses";
|
||||||
|
import { AdminCustomSimpleBotsApiService } from "../../shared/services/admin/admin-custom-simple-bots-api.service";
|
||||||
|
import { Modal, overlayConfigFactory } from "ngx-modialog";
|
||||||
|
import { AddCustomBotDialogContext, AdminAddCustomBotComponent } from "./add/add.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: "./custom-bots.component.html",
|
||||||
|
styleUrls: ["./custom-bots.component.scss"],
|
||||||
|
})
|
||||||
|
export class AdminCustomBotsComponent {
|
||||||
|
|
||||||
|
public isLoading = true;
|
||||||
|
public bots: FE_CustomSimpleBot[];
|
||||||
|
public isUpdating = false;
|
||||||
|
|
||||||
|
constructor(private botApi: AdminCustomSimpleBotsApiService,
|
||||||
|
private toaster: ToasterService,
|
||||||
|
private modal: Modal) {
|
||||||
|
|
||||||
|
this.reload().then(() => this.isLoading = false).catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
this.toaster.pop("error", "Error loading go-neb configuration");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private reload(): Promise<any> {
|
||||||
|
return this.botApi.getBots().then(bots => {
|
||||||
|
this.bots = bots;
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public addBot() {
|
||||||
|
this.modal.open(AdminAddCustomBotComponent, overlayConfigFactory({
|
||||||
|
isBlocking: true,
|
||||||
|
size: 'lg',
|
||||||
|
}, AddCustomBotDialogContext)).result.then(() => {
|
||||||
|
this.reload().catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
this.toaster.pop("error", "Failed to get an updated bot list");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public editBot(bot: FE_CustomSimpleBot) {
|
||||||
|
this.modal.open(AdminAddCustomBotComponent, overlayConfigFactory({
|
||||||
|
isBlocking: true,
|
||||||
|
size: 'lg',
|
||||||
|
|
||||||
|
bot: bot,
|
||||||
|
}, AddCustomBotDialogContext)).result.then(() => {
|
||||||
|
this.reload().catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
this.toaster.pop("error", "Failed to get an updated bot list");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleBot(bot: FE_CustomSimpleBot) {
|
||||||
|
this.isUpdating = true;
|
||||||
|
bot.isEnabled = !bot.isEnabled;
|
||||||
|
this.botApi.updateBot(bot.id, bot).then(() => {
|
||||||
|
this.isUpdating = false;
|
||||||
|
this.toaster.pop("success", "Bot " + (bot.isEnabled ? "enabled" : "disabled"));
|
||||||
|
}).catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
bot.isEnabled = !bot.isEnabled;
|
||||||
|
this.toaster.pop("error", "Error updating bot");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -101,6 +101,9 @@ import { TradingViewWidgetConfigComponent } from "./configs/widget/tradingview/t
|
|||||||
import { TradingViewWidgetWrapperComponent } from "./widget-wrappers/tradingview/tradingview.component";
|
import { TradingViewWidgetWrapperComponent } from "./widget-wrappers/tradingview/tradingview.component";
|
||||||
import { SpotifyWidgetConfigComponent } from "./configs/widget/spotify/spotify.widget.component";
|
import { SpotifyWidgetConfigComponent } from "./configs/widget/spotify/spotify.widget.component";
|
||||||
import { SpotifyWidgetWrapperComponent } from "./widget-wrappers/spotify/spotify.component";
|
import { SpotifyWidgetWrapperComponent } from "./widget-wrappers/spotify/spotify.component";
|
||||||
|
import { AdminCustomSimpleBotsApiService } from "./shared/services/admin/admin-custom-simple-bots-api.service";
|
||||||
|
import { AdminCustomBotsComponent } from "./admin/custom-bots/custom-bots.component";
|
||||||
|
import { AdminAddCustomBotComponent } from "./admin/custom-bots/add/add.component";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -185,6 +188,8 @@ import { SpotifyWidgetWrapperComponent } from "./widget-wrappers/spotify/spotify
|
|||||||
TradingViewWidgetWrapperComponent,
|
TradingViewWidgetWrapperComponent,
|
||||||
SpotifyWidgetConfigComponent,
|
SpotifyWidgetConfigComponent,
|
||||||
SpotifyWidgetWrapperComponent,
|
SpotifyWidgetWrapperComponent,
|
||||||
|
AdminCustomBotsComponent,
|
||||||
|
AdminAddCustomBotComponent,
|
||||||
|
|
||||||
// Vendor
|
// Vendor
|
||||||
],
|
],
|
||||||
@ -210,6 +215,7 @@ import { SpotifyWidgetWrapperComponent } from "./widget-wrappers/spotify/spotify
|
|||||||
WebhooksApiService,
|
WebhooksApiService,
|
||||||
AdminGitterApiService,
|
AdminGitterApiService,
|
||||||
GitterApiService,
|
GitterApiService,
|
||||||
|
AdminCustomSimpleBotsApiService,
|
||||||
{provide: Window, useValue: window},
|
{provide: Window, useValue: window},
|
||||||
|
|
||||||
// Vendor
|
// Vendor
|
||||||
@ -232,6 +238,7 @@ import { SpotifyWidgetWrapperComponent } from "./widget-wrappers/spotify/spotify
|
|||||||
TelegramCannotUnbridgeComponent,
|
TelegramCannotUnbridgeComponent,
|
||||||
AdminWebhooksBridgeManageSelfhostedComponent,
|
AdminWebhooksBridgeManageSelfhostedComponent,
|
||||||
AdminGitterBridgeManageSelfhostedComponent,
|
AdminGitterBridgeManageSelfhostedComponent,
|
||||||
|
AdminAddCustomBotComponent,
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AppModule {
|
export class AppModule {
|
||||||
|
@ -39,6 +39,7 @@ import { TradingViewWidgetConfigComponent } from "./configs/widget/tradingview/t
|
|||||||
import { TradingViewWidgetWrapperComponent } from "./widget-wrappers/tradingview/tradingview.component";
|
import { TradingViewWidgetWrapperComponent } from "./widget-wrappers/tradingview/tradingview.component";
|
||||||
import { SpotifyWidgetConfigComponent } from "./configs/widget/spotify/spotify.widget.component";
|
import { SpotifyWidgetConfigComponent } from "./configs/widget/spotify/spotify.widget.component";
|
||||||
import { SpotifyWidgetWrapperComponent } from "./widget-wrappers/spotify/spotify.component";
|
import { SpotifyWidgetWrapperComponent } from "./widget-wrappers/spotify/spotify.component";
|
||||||
|
import { AdminCustomBotsComponent } from "./admin/custom-bots/custom-bots.component";
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{path: "", component: HomeComponent},
|
{path: "", component: HomeComponent},
|
||||||
@ -86,6 +87,16 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "custom-bots",
|
||||||
|
data: {breadcrumb: "Custom bots", name: "Custom bots"},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
component: AdminCustomBotsComponent,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "bridges",
|
path: "bridges",
|
||||||
data: {breadcrumb: "Bridges", name: "Bridges"},
|
data: {breadcrumb: "Bridges", name: "Bridges"},
|
||||||
|
@ -1,18 +1,29 @@
|
|||||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChange, SimpleChanges } from "@angular/core";
|
||||||
import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser";
|
import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser";
|
||||||
import { FE_Integration } from "../shared/models/integration";
|
import { FE_Integration } from "../shared/models/integration";
|
||||||
|
import { MediaService } from "../shared/services/media.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "my-integration-bag",
|
selector: "my-integration-bag",
|
||||||
templateUrl: "./integration-bag.component.html",
|
templateUrl: "./integration-bag.component.html",
|
||||||
styleUrls: ["./integration-bag.component.scss"],
|
styleUrls: ["./integration-bag.component.scss"],
|
||||||
})
|
})
|
||||||
export class IntegrationBagComponent {
|
export class IntegrationBagComponent implements OnChanges {
|
||||||
|
|
||||||
@Input() integrations: FE_Integration[];
|
@Input() integrations: FE_Integration[];
|
||||||
@Output() integrationClicked: EventEmitter<FE_Integration> = new EventEmitter<FE_Integration>();
|
@Output() integrationClicked: EventEmitter<FE_Integration> = new EventEmitter<FE_Integration>();
|
||||||
|
|
||||||
constructor(private sanitizer: DomSanitizer) {
|
constructor(private sanitizer: DomSanitizer, private media: MediaService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnChanges(changes: SimpleChanges) {
|
||||||
|
const change: SimpleChange = changes.integrations;
|
||||||
|
|
||||||
|
(<FE_Integration[]>change.currentValue).map(async (i) => {
|
||||||
|
if (i.avatarUrl.startsWith("mxc://")) {
|
||||||
|
i.avatarUrl = await this.media.getThumbnailUrl(i.avatarUrl, 128, 128, "scale", true);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSafeUrl(url: string): SafeResourceUrl {
|
public getSafeUrl(url: string): SafeResourceUrl {
|
||||||
|
@ -37,3 +37,23 @@ export interface FE_NebConfiguration {
|
|||||||
upstreamId?: string;
|
upstreamId?: string;
|
||||||
integrations: FE_Integration[];
|
integrations: FE_Integration[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FE_CustomSimpleBot extends FE_CustomSimpleBotTemplate {
|
||||||
|
id: number;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FE_CustomSimpleBotTemplate {
|
||||||
|
name: string;
|
||||||
|
avatarUrl: string;
|
||||||
|
description: string;
|
||||||
|
isEnabled: boolean;
|
||||||
|
isPublic: boolean;
|
||||||
|
userId: string;
|
||||||
|
accessToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FE_UserProfile {
|
||||||
|
name: string;
|
||||||
|
avatarUrl: string;
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
import { Http } from "@angular/http";
|
||||||
|
import { AuthedApi } from "../authed-api";
|
||||||
|
import { FE_CustomSimpleBot, FE_CustomSimpleBotTemplate, FE_UserProfile } from "../../models/admin-responses";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AdminCustomSimpleBotsApiService extends AuthedApi {
|
||||||
|
constructor(http: Http) {
|
||||||
|
super(http);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getBots(): Promise<FE_CustomSimpleBot[]> {
|
||||||
|
return this.authedGet("/api/v1/dimension/admin/bots/simple/custom/all").map(r => r.json()).toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getBot(id: number): Promise<FE_CustomSimpleBot> {
|
||||||
|
return this.authedGet("/api/v1/dimension/admin/bots/simple/custom/" + id).map(r => r.json()).toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateBot(id: number, config: FE_CustomSimpleBotTemplate): Promise<FE_CustomSimpleBot> {
|
||||||
|
return this.authedPost("/api/v1/dimension/admin/bots/simple/custom/" + id, config).map(r => r.json()).toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
public createBot(config: FE_CustomSimpleBotTemplate): Promise<FE_CustomSimpleBot> {
|
||||||
|
return this.authedPost("/api/v1/dimension/admin/bots/simple/custom/new", config).map(r => r.json()).toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getProfile(userId: string): Promise<FE_UserProfile> {
|
||||||
|
return this.authedGet("/api/v1/dimension/admin/bots/simple/custom/profile/" + userId).map(r => r.json()).toPromise();
|
||||||
|
}
|
||||||
|
}
|
6
web/style/components/bootstrap_override.scss
vendored
6
web/style/components/bootstrap_override.scss
vendored
@ -1,7 +1,6 @@
|
|||||||
@import "../themes/themes";
|
@import "../themes/themes";
|
||||||
|
|
||||||
.main-app {
|
@include themifyRoot() {
|
||||||
@include themify(){
|
|
||||||
a {
|
a {
|
||||||
color: themed(anchorColor);
|
color: themed(anchorColor);
|
||||||
}
|
}
|
||||||
@ -14,7 +13,7 @@
|
|||||||
background-color: themed(codeBgColor);
|
background-color: themed(codeBgColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-muted {
|
*.text-muted {
|
||||||
color: themed(altMutedColor) !important;
|
color: themed(altMutedColor) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,4 +26,3 @@
|
|||||||
color: themed(formControlPlaceholderColor);
|
color: themed(formControlPlaceholderColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
@ -3,7 +3,7 @@ $theme_dark: (
|
|||||||
defaultFgColor: #eaeaea,
|
defaultFgColor: #eaeaea,
|
||||||
headerColor: #f9f9f9,
|
headerColor: #f9f9f9,
|
||||||
mutedColor: #d6d6d6,
|
mutedColor: #d6d6d6,
|
||||||
altMutedColor: #c4cdd4,
|
altMutedColor: #a6b6c1,
|
||||||
anchorColor: #82c5ff,
|
anchorColor: #82c5ff,
|
||||||
tableBorderColor: #1e1e1e,
|
tableBorderColor: #1e1e1e,
|
||||||
codeBgColor: #323233,
|
codeBgColor: #323233,
|
||||||
|
Loading…
Reference in New Issue
Block a user