"use strict";
// SPDX-FileCopyrightText: 2025 Gnuxie <Gnuxie@protonmail.com>
//
// SPDX-License-Identifier: Apache-2.0
Object.defineProperty(exports, "__esModule", { value: true });
exports.HomeserverUserPolicyApplication = void 0;
const typescript_result_1 = require("@gnuxie/typescript-result");
const matrix_basic_types_1 = require("@the-draupnir-project/matrix-basic-types");
const matrix_protection_suite_1 = require("matrix-protection-suite");
const DeactivateCommand_1 = require("../../commands/server-admin/DeactivateCommand");
const log = new matrix_protection_suite_1.Logger("HomeserverUserPolicyApplication");
class HomeserverUserPolicyApplication {
    managementRoomID;
    consequences;
    confirmationPromptSender;
    userAuditLog;
    watchedPolicyRooms;
    serverName;
    automaticallyRedactForReasons;
    constructor(managementRoomID, consequences, confirmationPromptSender, userAuditLog, watchedPolicyRooms, serverName, automaticallyRedactForReasons) {
        this.managementRoomID = managementRoomID;
        this.consequences = consequences;
        this.confirmationPromptSender = confirmationPromptSender;
        this.userAuditLog = userAuditLog;
        this.watchedPolicyRooms = watchedPolicyRooms;
        this.serverName = serverName;
        this.automaticallyRedactForReasons = automaticallyRedactForReasons;
        // nothing to do.
    }
    isPolicyElegiableForRestriction(policy) {
        if (policy.kind !== matrix_protection_suite_1.PolicyRuleType.User) {
            return false;
        }
        if (!(0, matrix_basic_types_1.isStringUserID)(policy.entity)) {
            return false;
        }
        if ((0, matrix_basic_types_1.userServerName)(policy.entity) !== this.serverName) {
            return false;
        }
        if (policy.recommendation === matrix_protection_suite_1.Recommendation.Takedown) {
            return true;
        }
        else if (policy.recommendation === matrix_protection_suite_1.Recommendation.Ban) {
            return true;
        }
        else {
            return false;
        }
    }
    /**
     * Sometimes it makes sense to skip the audit log check if e.g. we want to really
     * make sure the user is restricted like on a policy change.
     * But when starting up in bulk, we want to check the audit log first to not do duplicate
     * actions.
     */
    async isUserRestricted(userID, { checkAuditLog }) {
        const auditResult = await (async () => {
            if (!checkAuditLog) {
                return (0, typescript_result_1.Ok)(false);
            }
            return await this.userAuditLog.isUserRestricted(userID);
        })();
        if ((0, typescript_result_1.isError)(auditResult)) {
            return auditResult.elaborate("Failed to check audit log for user restriction");
        }
        if (auditResult.ok) {
            return auditResult;
        }
        else {
            return await this.consequences.isUserRestricted(userID);
        }
    }
    handlePolicyChange(changes) {
        void (0, matrix_protection_suite_1.Task)((async () => {
            for (const change of changes) {
                if (change.changeType === matrix_protection_suite_1.PolicyRuleChangeType.Removed) {
                    if (change.rule.matchType === matrix_protection_suite_1.PolicyRuleMatchType.Literal &&
                        (0, matrix_basic_types_1.isStringUserID)(change.rule.entity)) {
                        const matchingPolicies = this.watchedPolicyRooms.currentRevision
                            .allRulesMatchingEntity(change.rule.entity, {})
                            .filter((policy) => policy.recommendation === matrix_protection_suite_1.Recommendation.Ban ||
                            policy.recommendation === matrix_protection_suite_1.Recommendation.Takedown);
                        // If there are other policies targetting the user then we don't want to unrestrict them.
                        if (matchingPolicies.length > 0) {
                            continue;
                        }
                        const unrestrictResult = await this.consequences.unrestrictUser(change.rule.entity, change.event.sender);
                        if ((0, typescript_result_1.isError)(unrestrictResult)) {
                            log.error(`Failed to unrestrict the user ${change.rule.entity}`, unrestrictResult.error);
                        }
                    }
                    continue;
                    // FIXME: What about modifying from a automaticallyRedactReason to a non redact reason?
                }
                const policy = change.rule;
                if (policy.matchType !== matrix_protection_suite_1.PolicyRuleMatchType.Literal) {
                    continue;
                }
                if (!this.isPolicyElegiableForRestriction(policy)) {
                    continue;
                }
                const isUserRestricted = await this.consequences.isUserRestricted(policy.entity);
                if ((0, typescript_result_1.isError)(isUserRestricted)) {
                    log.error(`Failed to check if a user ${policy.entity} is suspended`, isUserRestricted.error);
                }
                else if (isUserRestricted.ok) {
                    continue; // user is already suspended
                }
                // FIXME: Takedown policie should really automatically deactivate
                // after a grace period or something.
                if (this.automaticallyRedactForReasons.some((glob) => glob.test(policy.reason) ||
                    policy.recommendation === matrix_protection_suite_1.Recommendation.Takedown)) {
                    // prompt to also deactivate the user if they match globs
                    (0, DeactivateCommand_1.sendPromptDeactivation)(policy.entity, policy, this.managementRoomID, this.confirmationPromptSender, log);
                }
                const restrictionResult = await this.consequences.restrictUser(policy.entity, {
                    rule: policy,
                    sender: policy.sourceEvent.sender,
                });
                if ((0, typescript_result_1.isError)(restrictionResult)) {
                    log.error(`Failed to suspend user ${policy.entity}`, restrictionResult.error);
                }
            }
        })());
    }
    handleProtectionEnable() {
        // FIXME: We probably need to synchronise unrestricted users with the audit log
        // in the background too.
        void (0, matrix_protection_suite_1.Task)((async () => {
            log.debug("Findind local users to suspend at protection enable...");
            const revision = this.watchedPolicyRooms.currentRevision;
            // FIXME: We need to also paginate through all supsended, shadowbanned, and locked
            // users to see if they have a matching policy and recommend that they either
            // be deactivated or have their restrictions lifted.
            // probably something for another component of the protection to do.
            // the users list api lets us also poll by user creation so it's pretty cool.
            // https://element-hq.github.io/synapse/latest/admin_api/user_admin_api.html#list-accounts-v2
            const literalRules = [
                ...revision.allRulesOfType(matrix_protection_suite_1.PolicyRuleType.User, matrix_protection_suite_1.Recommendation.Ban),
                ...revision.allRulesOfType(matrix_protection_suite_1.PolicyRuleType.User, matrix_protection_suite_1.Recommendation.Takedown),
            ].filter((policy) => policy.matchType === matrix_protection_suite_1.PolicyRuleMatchType.Literal &&
                (0, matrix_basic_types_1.isStringUserID)(policy.entity) &&
                (0, matrix_basic_types_1.userServerName)(policy.entity) === this.serverName);
            for (const policy of literalRules) {
                const userID = policy.entity;
                const isUserRestrictedResult = await this.isUserRestricted(userID, {
                    checkAuditLog: true,
                });
                if ((0, typescript_result_1.isError)(isUserRestrictedResult)) {
                    log.error(`Failed to check if a user ${userID} is restricted`, isUserRestrictedResult.error);
                    continue;
                }
                if (isUserRestrictedResult.ok) {
                    continue;
                }
                const suspensionResult = await this.consequences.restrictUser(userID, {
                    rule: policy,
                    sender: policy.sourceEvent.sender,
                });
                if ((0, typescript_result_1.isError)(suspensionResult)) {
                    log.error(`Failed to suspend user ${userID}`, suspensionResult.error);
                }
            }
            // NOTE: If we find out that we're polling lots of accounts whether
            // they are restricted and this is taking a significant amount of time
            // we should recommend that the policies that are relevant be removed.
            // if the account has been deactivated.
            // And the policy has been instated for awhile (give enough time for
            // other communities to redact their spam who may be using the policy)
            log.debug("Finished suspending from policies at protection enable");
        })());
    }
}
exports.HomeserverUserPolicyApplication = HomeserverUserPolicyApplication;
//# sourceMappingURL=HomeserverUserPolicyApplication.js.map