grigori-gru/jira-to-matrix

View on GitHub
src/messengers/matrix-api.ts

Summary

Maintainability
F
6 days
Test Coverage
/* eslint-disable no-undefined */
/* eslint no-empty-function: ["error", { "allow": ["arrowFunctions"] }] */
import matrixSdk from 'matrix-js-sdk';
import { errorTracing } from '../lib/utils';
import * as R from 'ramda';
import { BaseChatApi } from './base-api';
import { MessengerApi, RoomData, ChatConfig, CommandNames } from '../types';
import { Commands } from '../bot/commands';
import { LoggerInstance } from 'winston';
import { NO_ROOM_PATTERN, END_NO_ROOM_PATTERN } from '../lib/consts';
import axios from 'axios';

const getEvent = content => ({
    getType: () => 'm.room.power_levels',
    getContent: () => content,
});

export enum Msgtype {
    text = 'm.text',
    image = 'm.image',
    audio = 'm.audio',
    emote = 'm.emote',
    notice = 'm.notice',
    file = 'm.file',
    location = 'm.location',
    video = 'm.video',
}

interface BaseContent {
    body: string;
}

interface FileContent extends BaseContent {
    filename: string;
    info: {
        mimetype: string;
        size: number;
    };
    msgtype: Msgtype.file;
    url: string;
}

interface TextContent extends BaseContent {
    msgtype: Msgtype.text;
}

interface ImageContent extends BaseContent {
    info: {
        h: number;
        mimetype: string;
        size: number;
        w: number;
    };
    msgtype: Msgtype.image;
    url: string;
}

type Content = ImageContent | TextContent | FileContent;
const removeStatus = 'shouldRemove';

interface FailParse {
    isSuccess: false;
    body?: typeof removeStatus;
}
interface SuccessParse {
    isSuccess: true;
    body: { commandName: CommandNames; url?: string; bodyText?: string };
}

export class MatrixApi extends BaseChatApi implements MessengerApi {
    userId: string;
    baseUrl: string;
    // matrix client
    client: any;
    connection = false;

    constructor(commands: Commands, config: ChatConfig, logger: LoggerInstance, sdk = matrixSdk) {
        super(commands, config, logger, sdk);
        this.userId = `@${this.config.user}:${this.config.messenger.domain}`;
        this.baseUrl = `https://${this.config.messenger.domain}`;
    }

    getRoomLink(idOrAlias: string) {
        const endpoint = idOrAlias.startsWith('!') ? idOrAlias : this._getMatrixRoomAlias(idOrAlias);

        return [this.baseUrl, '#', 'room', endpoint].join('/');
    }

    get postfix() {
        return `:${this.config.messenger.domain}`.length;
    }

    get USER_ALREADY_IN_ROOM() {
        return 'is already in the room';
    }

    get BOT_OUT_OF_ROOM_EXEPTION() {
        return `User ${this.getMyId()} not in room`;
    }

    /**
     * @returns {string} user id
     */
    getMyId(): string {
        return this.config.user;
    }

    /**
     * @returns {string} matrix user id
     */
    getBotId(): string {
        return this.getMyId();
    }

    get EVENT_EXCEPTION() {
        return 'Could not find event';
    }

    get MESSAGE_TO_LARGE() {
        return 'event too large';
    }

    /**
     * Transform ldap user name to matrix user id
     */
    getChatUserId(shortName: string): string {
        return shortName && `@${shortName.toLowerCase()}:${this.config.messenger.domain}`;
    }

    /**
     * Get name from matrix id
     */
    _getNameFromMatrixId(id: string): string | undefined {
        if (id) {
            const [name] = id.split(':').slice(0, 1);

            return name.slice(1);
        }
    }

    createCommand(commandPart?: string): string;
    createCommand(): undefined;
    createCommand(commandPart?: string): string | undefined {
        return commandPart && `!${commandPart}`;
    }

    parseTextBody(body: string): SuccessParse | FailParse {
        try {
            const trimedBody = body.trim();

            const commandNameFromUser = R.pipe(
                R.split(' '),
                R.head,
                R.match(/^!\w+$/g),
                R.head,
                R.ifElse(R.isNil, R.always(''), R.drop(1)),
            )(body);

            if (this.config.features.postEachComments) {
                if (!Object.values(CommandNames).some(el => el === commandNameFromUser)) {
                    return {
                        isSuccess: true,
                        body: { commandName: CommandNames.Comment, bodyText: trimedBody },
                    };
                }
            }

            if (!commandNameFromUser) {
                return {
                    isSuccess: false,
                };
            }
            const commandName = commandNameFromUser;

            if (this.createCommand(commandName) === trimedBody) {
                return {
                    isSuccess: true,
                    body: { commandName },
                };
            }

            const bodyText = trimedBody.replace(this.createCommand(commandNameFromUser), '').trim();

            return {
                isSuccess: true,
                body: { commandName, bodyText },
            };
        } catch (err) {
            this.logger.error(`Error in parsing comment from matrix: ${err}`);
            return {
                isSuccess: false,
            };
        }
    }

    parseFileBody(content: FileContent): SuccessParse | FailParse {
        if (!this.config.features.postEachComments) {
            return {
                isSuccess: false,
            };
        }
        if (content.info.size >= this.config.maxFileSize) {
            return {
                isSuccess: false,
                body: removeStatus,
            };
        }

        return {
            isSuccess: true,
            body: {
                bodyText: content.body,
                commandName: CommandNames.Upload,
                url: this.getDownloadLink(content.url),
            },
        };
    }

    parseImageBody(content: ImageContent): SuccessParse | FailParse {
        if (!this.config.features.postEachComments) {
            return {
                isSuccess: false,
            };
        }
        if (content.info.size >= this.config.maxFileSize) {
            return {
                isSuccess: false,
                body: removeStatus,
            };
        }

        return {
            isSuccess: true,
            body: {
                bodyText: content.body,
                commandName: CommandNames.Upload,
                url: this.getDownloadLink(content.url),
            },
        };
    }

    parseEventBody(content: Content): SuccessParse | FailParse {
        switch (content.msgtype) {
            case Msgtype.text:
                return this.parseTextBody(content.body);
            case Msgtype.image:
                return this.parseImageBody(content);
            case Msgtype.file:
                return this.parseFileBody(content);
            default:
                return {
                    isSuccess: false,
                };
        }
    }

    /**
     * Matrix events handler
     */
    async timelineHandler(event: any, room: any, toStartOfTimeline: boolean) {
        try {
            if (event.getType() !== 'm.room.message' || toStartOfTimeline) {
                return;
            }

            const sender = this._getNameFromMatrixId(event.getSender())!;

            if (sender === this.getMyId()) {
                return;
            }
            const senderDisplayName = event?.sender?.name;

            const content: Content = event.getContent();

            const parseRes = this.parseEventBody(content);
            const roomData = this.getRoomData(room);
            const roomName = roomData.alias;

            if (!parseRes.isSuccess) {
                if (parseRes.body === removeStatus) {
                    this.logger.warn('Media size is bigger than limited, start removing');
                    await this.client.redactEvent(roomData.id, event.getId());
                    const message = 'Media upload is too big, max size is 10 mb';
                    // await this.commands.run(CommandNames.Comment, {
                    //     roomData,
                    //     chatApi: this,
                    //     sender,
                    //     roomName,
                    //     roomId: room.roomId,
                    //     bodyText: message,
                    // });
                    await this.sendHtmlMessage(room.roomId, message, message);
                    return;
                }
                return;
            }
            const { commandName, ...bodyData } = parseRes.body;

            if (commandName === CommandNames.Comment && roomData.alias === this.getNotifyData()?.name) {
                return;
            }

            // TODO add class to commandsHandler
            await this.commands.run(commandName, {
                roomData,
                chatApi: this,
                sender,
                roomName,
                roomId: room.roomId,
                senderDisplayName,
                ...bodyData,
            });
        } catch (err) {
            const errMsg = errorTracing(
                `Error while handling event from Matrix room "${room.name}" ${room.roomId}`,
                err,
            );
            this.logger.error(errMsg);
        }
    }

    /**
     * Convert string with alias to matrix form
     */
    _getMatrixRoomAlias(alias: string): string {
        const upperAlias = alias.toUpperCase();

        return `#${upperAlias}:${this.config.messenger.domain}`;
    }

    /**
     * Check if err should be ignored
     *
     * @param  {object} err catching error body
     * @returns {boolean} true/false
     */
    _isEventExeptionError(err: Error): boolean {
        return (
            typeof err.message === 'string' &&
            (err.message.includes(this.EVENT_EXCEPTION) ||
                err.message.includes(this.BOT_OUT_OF_ROOM_EXEPTION) ||
                err.message.includes(this.MESSAGE_TO_LARGE) ||
                err.message.includes(this.USER_ALREADY_IN_ROOM))
        );
    }

    async _createClient(): Promise<void> {
        try {
            const client = this.sdk.createClient(this.baseUrl);
            const { access_token: accessToken } = await client.loginWithPassword(this.userId, this.config.password);
            const matrixClient = this.sdk.createClient({
                baseUrl: this.baseUrl,
                accessToken,
                userId: this.userId,
            });

            this.logger.info(`createClient OK BaseUrl: ${this.baseUrl}, userId: ${this.userId}`);
            this.logger.info('Started connect to matrixClient');
            this.client = matrixClient;
        } catch (err) {
            throw [`createClient error. BaseUrl: ${this.baseUrl}, userId: ${this.userId}`, err].join('\n');
        }
    }

    _executor(resolve: Function): void {
        const syncHandler = state => {
            if (state === 'SYNCING') {
                this.logger.info('well connected');
                this.connection = true;
                resolve(this.client);
            } else {
                this.client.once('sync', syncHandler);
            }
        };
        this.client.once('sync', syncHandler);
    }

    async _startClient(): Promise<any> {
        try {
            await this._createClient();
            this.client.startClient();

            return new Promise(this._executor.bind(this));
        } catch (err) {
            throw ['Error in Matrix connection', err].join('\n');
        }
    }

    async _inviteBot(event: any) {
        if (event.event.membership !== 'invite') {
            return;
        }

        let sender = event.getSender();
        sender = sender.slice(1, -this.postfix);

        if (
            !this.config.messenger.admins.includes(sender) &&
            sender !== this.config.user &&
            event.getStateKey() === this.getMyId()
        ) {
            await this.client.leave(event.getRoomId());
            return;
        }

        if (event.getStateKey() === this.getMyId()) {
            await this.client.joinRoom(event.getRoomId());
        }
    }

    async leaveRoom(roomId: string): Promise<string | false> {
        try {
            await this.client.leave(roomId);
            this.logger.info(`Left room with id ${roomId}`);

            return roomId;
        } catch (err) {
            this.logger.error([`leave room ${roomId}`, err].join('\n'));

            return false;
        }
    }

    _removeListener(eventName: string, listener: Function, matrixClient: any) {
        const listCount = matrixClient.listenerCount(eventName);
        if (listCount > 1) {
            matrixClient.removeListener(eventName, listener);
            this.logger.warn(`Count listener for ${eventName} ${listCount}. To remove unnecessary listener`);
        }
    }

    /**
     * Handler to add timeline handler to watch events in a room
     */
    _handler(): any {
        if (!this.client) {
            this.logger.error('matrixclient is undefined');
            return;
        }

        this.client.on('Room.timeline', this.timelineHandler.bind(this));

        this.client.on('sync', (state, prevState) => {
            this._removeListener('Room.timeline', this.timelineHandler, this.client);
            this._removeListener('event', this._inviteBot.bind(this), this.client);

            if (state !== 'SYNCING' || prevState !== 'SYNCING') {
                this.logger.warn(`state is ${state}, prevState is ${prevState} for bot with id ${this.config.user}`);
            }
        });

        this.client.on('RoomMember.membership', async (event, member) => {
            if (member.membership === 'invite' && member.userId === this.getMyId()) {
                try {
                    await this.client.joinRoom(member.roomId);
                    this.logger.info(`${this.getMyId()} joined to room with id = ${member.roomId}`);
                } catch (error) {
                    this.logger.error(`Error joining to room with id = ${member.roomId}`);
                }
            }
        });

        this.client.on('event', this._inviteBot.bind(this));

        return this.client;
    }

    getClient(): any {
        return this.client;
    }

    isConnected(): boolean {
        if (this.client) {
            return Boolean(this.client.clientRunning && this.connection);
        }

        return false;
    }

    async connect(): Promise<void> {
        try {
            await this._startClient();

            return this._handler();
        } catch (err) {
            throw ['Error in Matrix connection', err].join('\n');
        }
    }

    disconnect(): void {
        if (this.isConnected()) {
            this.client.stopClient();
            this.logger.info('Disconnected from Matrix');
        }
    }

    async setPower(roomId: string, userId: string, level = 50): Promise<boolean> {
        try {
            const content = await this.client.getStateEvent(roomId, 'm.room.power_levels', '');
            const event = getEvent(content);

            await this.client.setPowerLevel(roomId, userId, 50, event);

            this.logger.info(`Power level for room with id ${roomId} is set to ${level} for user ${userId}`);
            return true;
        } catch (err) {
            throw [`Error setting power level for user ${userId} in room ${roomId}`, err].join('\n');
        }
    }

    async getUserIdByDisplayName(searchParam: string): Promise<string | undefined> {
        try {
            const method = 'POST';
            const path = '/user_directory/search';
            const body = {
                search_term: searchParam,
                limit: 10000,
            };

            const result = await this.client._http.authedRequest(undefined, method, path, {}, body);
            const domainUsers: string[] | undefined = result?.results
                .map(el => el.user_id)
                .filter((el: string) => el.includes(this.config.messenger.domain));
            const userId: string | undefined = domainUsers && domainUsers[0];

            if (!userId) {
                this.logger.warn(`Not found user by search params ${searchParam}`);
            }

            return userId;
        } catch (error) {
            this.logger.error(error);
            throw error;
        }
    }

    getRoomData(room: any): RoomData {
        const lastCreatedAlias = R.head(room.getAliases()) || room.getCanonicalAlias();
        const alias = this._getNameFromMatrixId(lastCreatedAlias) || null;
        const joinedMembers = room.getJoinedMembers();
        const topicEvent = room.currentState.getStateEvents('m.room.topic', '');
        const topic: string | undefined = topicEvent && R.path(['topic'], topicEvent.getContent());

        return {
            id: room.roomId,
            alias,
            name: room.name,
            topic,
            members: joinedMembers.map(({ userId, powerLevel }) => ({
                userId,
                powerLevel,
            })),
        };
    }

    async getRoomDataById(roomId: string): Promise<RoomData | undefined> {
        const room = await this.client.getRoom(roomId);
        if (room) {
            return this.getRoomData(room);
        }
    }

    getRooms(): Array<any> {
        const getParsedRooms = room => {
            const joinedMembers = room.getJoinedMembers();

            return {
                name: room.name,
                id: room.roomId,
                members: joinedMembers.map(({ userId }) => userId),
            };
        };

        const rooms = this.client.getRooms();

        return rooms.map(getParsedRooms);
    }

    /**
     * Create matrix room
     */
    async createRoom({
        invite,
        avatarUrl,
        ...options
    }: {
        room_alias_name: string;
        invite: string[];
        name: string;
        topic?: string;
        purpose?: string;
        avatarUrl?: string;
    }) {
        try {
            const lowerNameList = invite.filter(Boolean).map(name => name.toLowerCase());
            const createRoomOptions = {
                ...options,
                room_alias_name: options.room_alias_name.toUpperCase(),
                visibility: 'private',
                invite: lowerNameList,
            };
            const { room_id: roomId } = await this.client.createRoom(createRoomOptions);

            if (avatarUrl) {
                await this.setRoomAvatar(roomId, avatarUrl);
            }

            this.logger.info(`Room with alias "${options.name}" is created with id ${roomId}`);
            return roomId;
        } catch (err) {
            throw ['Error while creating room', err].join('\n');
        }
    }

    /**
     * Get matrix room id by alias
     */
    async getRoomId(alias: string): Promise<string> {
        try {
            const { room_id: roomId } = await this.client.getRoomIdForAlias(this._getMatrixRoomAlias(alias));
            return roomId;
        } catch (err) {
            throw [`${NO_ROOM_PATTERN}${alias}${END_NO_ROOM_PATTERN}`, err].join('\n');
        }
    }

    /**
     * Get matrix room by alias
     */
    async getRoomMembers({
        name,
        roomId,
    }: { name: string; roomId?: string } | { name?: string; roomId: string }): Promise<string[]> {
        try {
            const id = roomId || (await this.getRoomId(name as string));
            const room = await this.client.getRoom(id);
            const joinedMembers = room.getJoinedMembers();

            return joinedMembers.map(({ userId }) => userId);
        } catch (err) {
            throw [`Error while getting matrix members from room ${name || roomId}`, err].join('\n');
        }
    }

    /**
     * Get matrix room by alias
     */
    async getRoomAdmins({
        name,
        roomId,
    }: { name?: string; roomId: string } | { name: string; roomId?: string }): Promise<
        { name: string; userId: string }[]
    > {
        try {
            const id = roomId || (await this.getRoomId(name as string));
            const room = await this.client.getRoom(id);
            const joinedMembers = room.getJoinedMembers();

            return joinedMembers
                .filter(({ powerLevel }) => powerLevel === 100)
                .map(({ name, userId }) => ({ name, userId }));
        } catch (err) {
            throw [`Error while getting matrix members from room ${name}`, err].join('\n');
        }
    }

    /**
     * Check if user is in matrix room
     */
    async isRoomMember(roomId: string, user: string): Promise<boolean> {
        const roomMembers = await this.getRoomMembers({ roomId });
        return roomMembers.includes(user);
    }

    /**
     * Invite user to matrix room
     */
    async invite(roomId: string, userId: string): Promise<boolean> {
        try {
            const user = userId.toLowerCase();
            if (await this.isRoomMember(roomId, user)) {
                this.logger.warn(`Room ${roomId} already has user ${user}`);

                return false;
            }
            await this.client.invite(roomId, user);

            return true;
        } catch (err) {
            if (this._isEventExeptionError(err)) {
                return false;
            }

            throw ['Error while inviting a new member to a room:', err].join('\n');
        }
    }

    /**
     * Send message to matrix room
     */
    async sendTextMessage(roomId: string, body: string): Promise<void> {
        try {
            await this.client.sendTextMessage(roomId, body);
        } catch (err) {
            if (this._isEventExeptionError(err)) {
                this.logger.warn(err.message);

                return;
            }

            throw ['Error in sendHtmlMessage', err].join('\n');
        }
    }

    /**
     * Send message to matrix room
     */
    async sendHtmlMessage(roomId: string, body: string, htmlBody: string): Promise<void> {
        try {
            await this.client.sendHtmlMessage(roomId, body, htmlBody);
        } catch (err) {
            if (this._isEventExeptionError(err)) {
                this.logger.warn(err.message);

                return;
            }

            throw ['Error in sendHtmlMessage', err].join('\n');
        }
    }

    /**
     * Create alias for the room
     */
    async createAlias(name: string, roomId: string): Promise<string | false> {
        const newAlias = this._getMatrixRoomAlias(name);
        try {
            await this.client.createAlias(newAlias, roomId);
            this.logger.info(`New alias ${newAlias} for room with id ${roomId} is added`);

            return newAlias;
        } catch (err) {
            if (err.message.includes(`Room alias ${newAlias} already exists`)) {
                this.logger.warn(err.message);

                return false;
            }
            this.logger.error(err);
            throw ['Error while creating alias for a room', err].join('\n');
        }
    }

    /**
     * Set new name for matrix room
     */
    async setRoomName(roomId: string, name: string): Promise<boolean> {
        try {
            await this.client.setRoomName(roomId, name);
            return true;
        } catch (err) {
            if (this._isEventExeptionError(err)) {
                this.logger.warn(err.message);

                return false;
            }

            throw ['Error while setting room name', err].join('\n');
        }
    }

    async setRoomTopic(roomId: string, topic: string): Promise<void> {
        try {
            await this.client.setRoomTopic(roomId, topic);
            this.logger.debug(`New room topic is added for room with id ${roomId}`);
        } catch (err) {
            if (this._isEventExeptionError(err)) {
                this.logger.warn(err.message);
            }

            throw [`Error while setting room's topic`, err].join('\n');
        }
    }

    /**
     * Chack if it's room name
     */
    _isRoomAlias(room: string): boolean {
        return room.includes(this.config.messenger.domain) && room[0] === '#';
    }

    async getRoomIdByName(text: string, notUpper?: boolean): Promise<string | false> {
        try {
            const alias = this._isRoomAlias(text)
                ? text
                : this._getMatrixRoomAlias(notUpper ? text : text.toUpperCase());
            const { room_id: roomId } = await this.client.getRoomIdForAlias(alias);

            return roomId;
        } catch (err) {
            // this.logger.warn(err);
            this.logger.warn('No room id by alias ', text);
            return false;
        }
    }

    composeRoomName(key: string, summary: string): string {
        return summary ? `${key} ${summary}` : key;
    }

    async updateRoomName(roomId: string, newRoomName: string): Promise<void> {
        await this.setRoomName(roomId, newRoomName);
    }

    async updateRoomData(roomId: string, topic: string, key: string): Promise<void> {
        await this.createAlias(key, roomId);
        await this.setRoomTopic(roomId, topic);
    }

    async isInRoom(roomId: string): Promise<boolean> {
        const room = await this.client.getRoom(roomId);

        return Boolean(room);
    }

    async setRoomAvatar(roomId: string, url: string): Promise<true | undefined> {
        try {
            const method = 'PUT';
            const path = `/rooms/${encodeURIComponent(roomId)}/state/m.room.avatar`;
            const body = { url };

            await this.client._http.authedRequest(undefined, method, path, {}, body);

            return true;
        } catch (error) {
            this.logger.error(`Error in avatar setting for roomId ${roomId} with avatar url ${url}`);
            this.logger.error(error);
        }
    }

    async getAllMessagesFromRoom(
        roomId: string,
    ): Promise<{ author: string; date: string; body: string; eventId: string }[] | undefined> {
        try {
            const method = 'GET';
            const path = `/rooms/${encodeURIComponent(roomId)}/messages`;
            const qweryParams = { limit: 10000, dir: 'b' };
            const body = {};

            const { chunk } = await this.client._http.authedRequest(undefined, method, path, qweryParams, body);

            const allMessages = chunk
                .filter(({ type }) => type === 'm.room.message')
                .map(event => {
                    const { user_id: author, content, origin_server_ts: timestamp, event_id: eventId } = event;
                    const body = content.msgtype === 'm.text' && content.body;
                    const date = new Date(timestamp);

                    return { author, date, body, eventId };
                });
            return allMessages;
        } catch (error) {
            this.logger.error(`Error in request to all messages for ${roomId}.`);
            this.logger.error(error);
        }
    }

    async uploadContent(data: Buffer, imageType: string): Promise<string> {
        const uploadResponse = await this.client.uploadContent(data, {
            rawResponse: false,
            type: imageType,
        });
        const matrixUrl = uploadResponse.content_uri;

        return matrixUrl;
    }

    async upload(roomId: string, url: string): Promise<string | undefined> {
        try {
            const mimeTypes = [
                'image/apng',
                'image/bmp',
                'image/gif',
                'image/x-icon',
                'image/jpeg',
                'image/png',
                'image/svg+xml',
                'image/tiff',
                'image/webp',
            ];

            const response = await axios.get(url, { responseType: 'arraybuffer' });
            const imageType = response.headers['content-type'];
            if (mimeTypes.includes(imageType)) {
                const uploadResponse = await this.client.uploadContent(response.data, {
                    rawResponse: false,
                    type: imageType,
                });
                const matrixUrl = uploadResponse.content_uri;
                await this.client.sendImageMessage(roomId, matrixUrl, {}, '');
                return matrixUrl;
            }
        } catch (error) {
            this.logger.error(error);
            throw error;
        }
    }

    async getAllEventsFromRoom(roomId: string, limit = 10000): Promise<any[] | undefined> {
        try {
            const method = 'GET';
            const path = `/rooms/${encodeURIComponent(roomId)}/messages`;
            const qweryParams = { limit, dir: 'b' };
            const body = {};

            const { chunk } = await this.client._http.authedRequest(undefined, method, path, qweryParams, body);

            return chunk;
        } catch (error) {
            this.logger.error(`Error in request to all events for ${roomId}.`);
            this.logger.error(error);
        }
    }

    getDownloadLink(mxcUrl: string): string {
        return this.client.mxcUrlToHttp(mxcUrl);
    }

    async kickUserByRoom({ roomId, userId }: { roomId: string; userId: string }): Promise<string | undefined> {
        try {
            const method = 'PUT';
            const path = `/rooms/${encodeURIComponent(roomId)}/state/m.room.member/${encodeURIComponent(userId)}`;
            const qweryParams = {};
            const body = { membership: 'leave', reason: 'kick by bot' };

            await this.client._http.authedRequest(undefined, method, path, qweryParams, body);
            this.logger.info(`Member ${userId} kicked from ${roomId}`);

            return userId;
        } catch (error) {
            const msg = errorTracing(`Error in request for kick ${userId} from ${roomId}.`, JSON.stringify(error));
            this.logger.error(msg);
        }
    }

    async setRoomJoinedByUrl(roomId: string): Promise<true | undefined> {
        try {
            const method = 'PUT';
            const path = `/rooms/${encodeURIComponent(roomId)}/state/m.room.join_rules`;
            // eslint-disable-next-line @typescript-eslint/camelcase
            const body = { join_rule: 'public' };

            await this.client._http.authedRequest(undefined, method, path, {}, body);

            return true;
        } catch (error) {
            this.logger.error(`Error in setting public acceess for roomId ${roomId}`);
            this.logger.error(error);
        }
    }

    async getUser(userId: string): Promise<{ displayName: string; avatarUrl: string } | undefined> {
        try {
            const user = await this.client.getProfileInfo(userId);

            return {
                displayName: user.displayname as string,
                avatarUrl: user.avatar_url as string,
            };
        } catch (err) {
            this.logger.error(`User profile ${userId} is not found. \nError: ${JSON.stringify(err)}`);
        }
    }

    async joinRoom({
        roomId,
        aliasPart,
    }: { roomId?: string; aliasPart: string } | { roomId: string; aliasPart?: string }) {
        try {
            if (aliasPart) {
                const alias = this._getMatrixRoomAlias(aliasPart);

                await this.client.joinRoom(alias);

                return;
            }

            await this.client.joinRoom(roomId);
        } catch (err) {
            this.logger.error('Error with joining to room');
            this.logger.error(err);
        }
    }

    async deleteRoomAlias(aliasPart: string): Promise<string | void> {
        const alias = this._getMatrixRoomAlias(aliasPart);
        try {
            const roomId = await this.getRoomIdByName(alias, true);
            if (!roomId) {
                this.logger.warn(`Alias ${alias} is not found!!!`);

                return;
            }
            await this.client.deleteAlias(alias);
            this.logger.debug(`Alias ${alias} is successfully deleted in room with id ${roomId}`);

            return alias;
        } catch (err) {
            const msg = errorTracing(`deleteRoomAlias "${alias}"`, JSON.stringify(err));
            this.logger.error(msg);
        }
    }
}