"use strict";
// Copyright 2022 - 2023 Gnuxie <Gnuxie@protonmail.com>
// Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: Apache-2.0
//
// SPDX-FileAttributionText: <text>
// This modified file incorporates work from mjolnir
// https://github.com/matrix-org/mjolnir
// </text>
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Draupnir = void 0;
const matrix_protection_suite_1 = require("matrix-protection-suite");
const UnlistedUserRedactionQueue_1 = require("./queues/UnlistedUserRedactionQueue");
const ThrottlingQueue_1 = require("./queues/ThrottlingQueue");
const ManagementRoomOutput_1 = __importDefault(require("./managementroom/ManagementRoomOutput"));
const ReportPoller_1 = require("./report/ReportPoller");
const ReportManager_1 = require("./report/ReportManager");
const matrix_protection_suite_for_matrix_bot_sdk_1 = require("matrix-protection-suite-for-matrix-bot-sdk");
const matrix_bot_sdk_1 = require("@vector-im/matrix-bot-sdk");
const DraupnirRendererMessageCollector_1 = require("./capabilities/DraupnirRendererMessageCollector");
const ProtectedRoomsSetRenderers_1 = require("./protections/ProtectedRoomsSetRenderers");
const StatusCommand_1 = require("./commands/StatusCommand");
const matrix_basic_types_1 = require("@the-draupnir-project/matrix-basic-types");
const DraupnirCommandDispatcher_1 = require("./commands/DraupnirCommandDispatcher");
const ManagementRoom_1 = require("./safemode/ManagementRoom");
const interface_manager_1 = require("@the-draupnir-project/interface-manager");
const HomeserverUserPurgingDeactivate_1 = require("./protections/HomeserverUserPolicyApplication/HomeserverUserPurgingDeactivate");
const mps_interface_adaptor_1 = require("@the-draupnir-project/mps-interface-adaptor");
const TimelineRedactionQueue_1 = require("./queues/TimelineRedactionQueue");
const typescript_result_1 = require("@gnuxie/typescript-result");
const log = new matrix_protection_suite_1.Logger("Draupnir");
// webAPIS should not be included on the Draupnir class.
// That should be managed elsewhere.
// It's not actually relevant to the Draupnir instance and it only was connected
// to Draupnir because it needs to be started after Draupnir started and not before.
// And giving it to the class was a dumb easy way of doing that.
class Draupnir {
    client;
    clientUserID;
    clientDisplayName;
    clientPlatform;
    managementRoomDetail;
    clientRooms;
    config;
    protectedRoomsSet;
    roomStateManager;
    policyRoomManager;
    roomMembershipManager;
    loggableConfigTracker;
    acceptInvitesFromRoom;
    acceptInvitesFromRoomIssuer;
    safeModeToggle;
    stores;
    synapseAdminClient;
    synapseHTTPAntispam;
    /**
     * This is for users who are not listed on a watchlist,
     * but have been flagged by the automatic spam detection as suispicous
     */
    unlistedUserRedactionQueue = new UnlistedUserRedactionQueue_1.UnlistedUserRedactionQueue();
    commandDispatcher;
    taskQueue;
    /**
     * Reporting back to the management room.
     */
    managementRoomOutput;
    /*
     * Config-enabled polling of reports in Synapse, so Draupnir can react to reports
     */
    reportPoller;
    /**
     * Handle user reports from the homeserver.
     * FIXME: ReportManager should be a protection.
     */
    reportManager;
    reactionHandler;
    purgingDeactivate;
    timelineEventListener = this.handleTimelineEvent.bind(this);
    capabilityMessageRenderer;
    commandDispatcherTimelineListener;
    JSInterfaceDispatcher;
    timelineRedactionQueue;
    constructor(client, clientUserID, clientDisplayName, clientPlatform, managementRoomDetail, clientRooms, config, protectedRoomsSet, roomStateManager, policyRoomManager, roomMembershipManager, loggableConfigTracker, 
    /** Draupnir has a feature where you can choose to accept invitations from a space and not just the management room. */
    acceptInvitesFromRoom, acceptInvitesFromRoomIssuer, safeModeToggle, stores, synapseAdminClient, synapseHTTPAntispam) {
        this.client = client;
        this.clientUserID = clientUserID;
        this.clientDisplayName = clientDisplayName;
        this.clientPlatform = clientPlatform;
        this.managementRoomDetail = managementRoomDetail;
        this.clientRooms = clientRooms;
        this.config = config;
        this.protectedRoomsSet = protectedRoomsSet;
        this.roomStateManager = roomStateManager;
        this.policyRoomManager = policyRoomManager;
        this.roomMembershipManager = roomMembershipManager;
        this.loggableConfigTracker = loggableConfigTracker;
        this.acceptInvitesFromRoom = acceptInvitesFromRoom;
        this.acceptInvitesFromRoomIssuer = acceptInvitesFromRoomIssuer;
        this.safeModeToggle = safeModeToggle;
        this.stores = stores;
        this.synapseAdminClient = synapseAdminClient;
        this.synapseHTTPAntispam = synapseHTTPAntispam;
        this.commandDispatcher = (0, DraupnirCommandDispatcher_1.makeDraupnirCommandDispatcher)(this);
        this.purgingDeactivate =
            this.synapseAdminClient && this.stores.userRestrictionAuditLog
                ? new HomeserverUserPurgingDeactivate_1.HomeserverUserPurgingDeactivate(this.synapseAdminClient, this.stores.userRestrictionAuditLog)
                : undefined;
        this.commandDispatcherTimelineListener =
            (0, ManagementRoom_1.makeCommandDispatcherTimelineListener)(this.managementRoom, this.client, this.commandDispatcher);
        this.JSInterfaceDispatcher = (0, DraupnirCommandDispatcher_1.makeDraupnirJSCommandDispatcher)(this);
        this.timelineRedactionQueue = new TimelineRedactionQueue_1.TimelineRedactionQueue(this.clientPlatform.toRoomMessages(), this.clientPlatform.toRoomEventRedacter());
        this.managementRoomOutput = new ManagementRoomOutput_1.default(this.managementRoomDetail, this.clientUserID, this.client, this.config);
        this.taskQueue = new ThrottlingQueue_1.ThrottlingQueue(this.managementRoomOutput, config.backgroundDelayMS);
        this.reactionHandler = new mps_interface_adaptor_1.MatrixReactionHandler(this.managementRoom.toRoomIDOrAlias(), clientUserID, clientPlatform);
        this.reportManager = new ReportManager_1.StandardReportManager(this);
        if (config.pollReports) {
            this.reportPoller = new ReportPoller_1.ReportPoller(this, this.reportManager);
        }
        this.reactionHandler.on(mps_interface_adaptor_1.ARGUMENT_PROMPT_LISTENER, (0, mps_interface_adaptor_1.makeListenerForArgumentPrompt)(this.commandRoomID, this.commandDispatcher, this.reactionHandler));
        this.reactionHandler.on(mps_interface_adaptor_1.DEFAUILT_ARGUMENT_PROMPT_LISTENER, (0, mps_interface_adaptor_1.makeListenerForPromptDefault)(this.commandRoomID, this.commandDispatcher, this.reactionHandler));
        this.reactionHandler.on(mps_interface_adaptor_1.COMMAND_CONFIRMATION_LISTENER, (0, mps_interface_adaptor_1.makeConfirmationPromptListener)(this.commandRoomID, this.commandDispatcher, this.reactionHandler));
        this.capabilityMessageRenderer = new DraupnirRendererMessageCollector_1.DraupnirRendererMessageCollector(this.clientPlatform.toRoomMessageSender(), this.managementRoomID);
    }
    static async makeDraupnirBot(client, clientUserID, clientDisplayName, clientPlatform, managementRoomDetail, clientRooms, protectedRoomsSet, roomStateManager, policyRoomManager, roomMembershipManager, config, loggableConfigTracker, safeModeToggle, stores, synapseHTTPAntispam) {
        const acceptInvitesFromRoom = await (async () => {
            if (config.autojoinOnlyIfManager) {
                return (0, matrix_protection_suite_1.Ok)(managementRoomDetail.managementRoom);
            }
            else {
                if (config.acceptInvitesFromSpace === undefined) {
                    throw new TypeError(`You cannot leave config.acceptInvitesFromSpace undefined if you have disabled config.autojoinOnlyIfManager`);
                }
                const room = (() => {
                    if ((0, matrix_basic_types_1.isStringRoomID)(config.acceptInvitesFromSpace) ||
                        (0, matrix_basic_types_1.isStringRoomAlias)(config.acceptInvitesFromSpace)) {
                        return config.acceptInvitesFromSpace;
                    }
                    else {
                        const parseResult = matrix_basic_types_1.MatrixRoomReference.fromPermalink(config.acceptInvitesFromSpace);
                        if ((0, matrix_protection_suite_1.isError)(parseResult)) {
                            throw new TypeError(`config.acceptInvitesFromSpace: ${config.acceptInvitesFromSpace} needs to be a room id, alias or permalink`);
                        }
                        return parseResult.ok;
                    }
                })();
                return await clientPlatform.toRoomJoiner().joinRoom(room);
            }
        })();
        if ((0, matrix_protection_suite_1.isError)(acceptInvitesFromRoom)) {
            return acceptInvitesFromRoom.elaborate("Unable to join the space from config.acceptInvitesFromSpace");
        }
        const acceptInvitesFromRoomIssuer = await roomMembershipManager.getRoomMembershipRevisionIssuer(acceptInvitesFromRoom.ok);
        if ((0, matrix_protection_suite_1.isError)(acceptInvitesFromRoomIssuer)) {
            return acceptInvitesFromRoomIssuer;
        }
        const draupnir = new Draupnir(client, clientUserID, clientDisplayName, clientPlatform, managementRoomDetail, clientRooms, config, protectedRoomsSet, roomStateManager, policyRoomManager, roomMembershipManager, loggableConfigTracker, acceptInvitesFromRoom.ok, acceptInvitesFromRoomIssuer.ok, safeModeToggle, stores, new matrix_protection_suite_for_matrix_bot_sdk_1.SynapseAdminClient(client, clientUserID), synapseHTTPAntispam);
        const loadResult = await protectedRoomsSet.protections.loadProtections(protectedRoomsSet, draupnir, (error, protectionName, description) => (0, ProtectedRoomsSetRenderers_1.renderProtectionFailedToStart)(clientPlatform.toRoomMessageSender(), managementRoomDetail.managementRoomID, error, protectionName, description));
        if ((0, matrix_protection_suite_1.isError)(loadResult)) {
            return loadResult;
        }
        // we need to make sure that we are protecting the management room so we
        // have immediate access to its membership (for accepting invitations).
        const managementRoomProtectResult = await draupnir.protectedRoomsSet.protectedRoomsManager.addRoom(managementRoomDetail.managementRoom);
        if ((0, matrix_protection_suite_1.isError)(managementRoomProtectResult)) {
            return managementRoomProtectResult;
        }
        // Check that Draupnir has the ability to send state events to the management
        // room. Which is important because Draupnir users state events to persist
        // protection settings, including the draupnir news feed position.
        if (!managementRoomDetail.isDraupnirUserPowered(clientUserID)) {
            const errorText = `${clientUserID} doesn't have the power level required to send state events in the management room. Please make the Draupnir user an administrator.`;
            await (0, matrix_protection_suite_1.Task)(clientPlatform
                .toRoomMessageSender()
                .sendMessage(managementRoomDetail.managementRoomID, {
                body: errorText,
                msgtype: "m.notice",
            }));
            return typescript_result_1.ResultError.Result(errorText);
        }
        return (0, matrix_protection_suite_1.Ok)(draupnir);
    }
    get managementRoomID() {
        return this.managementRoomDetail.managementRoomID;
    }
    get managementRoom() {
        return this.managementRoomDetail.managementRoom;
    }
    /**
     * Note: This is only public due to having to first start the syncloop before sending events
     * when we use encryption.
     * This means this is only used in the index.ts.
     */
    async startupComplete() {
        try {
            await this.managementRoomOutput.logMessage(matrix_bot_sdk_1.LogLevel.INFO, "Draupnir@startup", "Startup complete. Now monitoring rooms.");
            const statusInfo = (0, StatusCommand_1.draupnirStatusInfo)(this);
            await (0, mps_interface_adaptor_1.sendMatrixEventsFromDeadDocument)(this.clientPlatform.toRoomMessageSender(), this.managementRoomID, (0, StatusCommand_1.renderStatusInfo)(statusInfo), {});
        }
        catch (ex) {
            log.error(`Caught an error when trying to show status at startup`, ex);
        }
    }
    handleTimelineEvent(roomID, event) {
        if (matrix_protection_suite_1.Value.Check(matrix_protection_suite_1.MembershipEvent, event) &&
            event.state_key === this.clientUserID &&
            // if the membership is join, make sure that we filter out protected rooms.
            (event.content.membership === matrix_protection_suite_1.Membership.Join
                ? !this.protectedRoomsSet.isProtectedRoom(roomID)
                : true)) {
            this.protectedRoomsSet.handleExternalMembership(roomID, event);
        }
        this.managementRoomMessageListener(roomID, event);
        void (0, matrix_protection_suite_1.Task)((async () => {
            await this.reactionHandler.handleEvent(roomID, event);
        })());
        if (this.protectedRoomsSet.isProtectedRoom(roomID)) {
            this.protectedRoomsSet.handleTimelineEvent(roomID, event);
        }
    }
    managementRoomMessageListener(roomID, event) {
        if (roomID !== this.managementRoomID) {
            return;
        }
        this.commandDispatcherTimelineListener(roomID, event);
        this.reportManager.handleTimelineEvent(roomID, event);
    }
    /**
     * Start responding to events.
     * This will not start the appservice from listening and responding
     * to events. Nor will it start any syncing client.
     */
    start() {
        // to avoid handlers getting out of sync on clientRooms and leaking
        // when draupnir keeps being started and restarted, we can basically
        // clear all listeners each time and add the factory listener back.
        this.clientRooms.on("timeline", this.timelineEventListener);
        if (this.reportPoller) {
            // allow this to crash draupnir if it fails, since we need to know.
            void this.reportPoller.startFromStoredSetting(this.client, this.managementRoomOutput);
        }
    }
    stop() {
        this.clientRooms.off("timeline", this.timelineEventListener);
        this.reportPoller?.stop();
        this.protectedRoomsSet.unregisterListeners();
        this.stores.dispose();
    }
    createRoomReference(roomID) {
        return new matrix_basic_types_1.MatrixRoomID(roomID, [(0, matrix_basic_types_1.userServerName)(this.clientUserID)]);
    }
    handleEventReport(report) {
        this.protectedRoomsSet.handleEventReport(report);
    }
    /**
     * This is needed to implement the MatrixInterfaceAdaptor interface.
     */
    get commandRoomID() {
        return this.managementRoomID;
    }
    /**
     * API for integration tests to be able to test commands, mostly to ensure
     * functionality of the appservice bots.
     */
    async sendPresentationCommand(sender, ...items) {
        return await this.JSInterfaceDispatcher.invokeCommandFromPresentationStream({ commandSender: sender }, new interface_manager_1.StandardPresentationArgumentStream(items));
    }
    async sendTextCommand(sender, command) {
        return await this.JSInterfaceDispatcher.invokeCommandFromBody({ commandSender: sender }, command);
    }
}
exports.Draupnir = Draupnir;
//# sourceMappingURL=Draupnir.js.map