mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
feat(api): add moderation events and visibility actions (#3219)
This commit is contained in:
1 parent
532e828fe6
commit
05257d6439
39 files changed
+2464
-88
No files matched your search
@@ -14,6 +14,7 @@ import type {GuildEmoji} from '@app/api/models/GuildEmoji';
|
||||
import type {GuildMember} from '@app/api/models/GuildMember';
|
||||
import type {GuildRole} from '@app/api/models/GuildRole';
|
||||
import type {GuildSticker} from '@app/api/models/GuildSticker';
|
||||
import {hiddenGuildMember, isHiddenPartial} from '@app/api/user/ProfileVisibility';
|
||||
import {getCachedUserPartialResponse, getCachedUserPartialResponses} from '@app/api/user/UserCacheHelpers';
|
||||
import type {
|
||||
GuildEmojiResponse,
|
||||
@@ -132,6 +133,11 @@ export function mapGuildStickerToResponse(sticker: GuildSticker): GuildStickerRe
|
||||
}
|
||||
|
||||
function mapMemberWithUser(member: GuildMember, userPartial: UserPartialResponse): GuildMemberResponse {
|
||||
const response = mapMemberFields(member, userPartial);
|
||||
return isHiddenPartial(userPartial) ? hiddenGuildMember(response) : response;
|
||||
}
|
||||
|
||||
function mapMemberFields(member: GuildMember, userPartial: UserPartialResponse): GuildMemberResponse {
|
||||
const now = Date.now();
|
||||
const isTimedOut = member.communicationDisabledUntil != null && member.communicationDisabledUntil.getTime() > now;
|
||||
return {
|
||||
|
||||
@@ -8,10 +8,13 @@ import {mapGuildBansToResponse} from '@app/api/guild/GuildModel';
|
||||
import type {IGuildRepositoryAggregate} from '@app/api/guild/repositories/IGuildRepositoryAggregate';
|
||||
import {createGuildMfaEnforcer} from '@app/api/guild/services/GuildMfaEnforcement';
|
||||
import {GuildMemberSearchIndexService} from '@app/api/guild/services/member/GuildMemberSearchIndexService';
|
||||
import type {BanBy} from '@app/api/infrastructure/activity/Contract.generated';
|
||||
import {emitGuildMemberBanned, emitGuildMemberUnbanned} from '@app/api/infrastructure/activity/ModerationEvents';
|
||||
import type {IGatewayService} from '@app/api/infrastructure/IGatewayService';
|
||||
import type {UserCacheService} from '@app/api/infrastructure/UserCacheService';
|
||||
import {Logger} from '@app/api/Logger';
|
||||
import type {RequestCache} from '@app/api/middleware/RequestCacheMiddleware';
|
||||
import type {Guild} from '@app/api/models/Guild';
|
||||
import type {GuildBan} from '@app/api/models/GuildBan';
|
||||
import type {IUserRepository} from '@app/api/user/IUserRepository';
|
||||
import type {WorkerTaskName} from '@app/api/worker/WorkerLaneConfig';
|
||||
@@ -29,6 +32,8 @@ import type {GuildBanResponse} from '@fluxer/schema/src/domains/guild/GuildMembe
|
||||
import type {IWorkerService} from '@pkgs/worker/src/contracts/IWorkerService';
|
||||
|
||||
const SECONDS_PER_DAY = 86_400;
|
||||
const GUILD_MODERATION_PERMISSIONS =
|
||||
Permissions.ADMINISTRATOR | Permissions.BAN_MEMBERS | Permissions.KICK_MEMBERS | Permissions.MANAGE_GUILD;
|
||||
|
||||
export class GuildModerationService {
|
||||
private readonly searchIndexService: GuildMemberSearchIndexService;
|
||||
@@ -67,6 +72,7 @@ export class GuildModerationService {
|
||||
reason?: string | null;
|
||||
banDurationSeconds?: number;
|
||||
skipGuildAuditLog?: boolean;
|
||||
by?: BanBy;
|
||||
},
|
||||
auditLogReason?: string | null,
|
||||
): Promise<void> {
|
||||
@@ -79,6 +85,7 @@ export class GuildModerationService {
|
||||
reason,
|
||||
banDurationSeconds,
|
||||
skipGuildAuditLog,
|
||||
by = 'moderator',
|
||||
} = params;
|
||||
await this.checkModerationPermission({guildId, userId, permission: Permissions.BAN_MEMBERS});
|
||||
if (userId === targetId) throw new UnknownGuildMemberError();
|
||||
@@ -106,6 +113,8 @@ export class GuildModerationService {
|
||||
if (banDurationSeconds && banDurationSeconds > 0) {
|
||||
expiresAt = new Date(Date.now() + banDurationSeconds * 1000);
|
||||
}
|
||||
const guildBeforeBan = await this.guildRepository.findUnique(guildId);
|
||||
const targetModerator = await this.isGuildModerator(guildId, targetId, guildBeforeBan, targetMember !== null);
|
||||
const ban = await this.guildRepository.upsertBan({
|
||||
guild_id: guildId,
|
||||
user_id: targetId,
|
||||
@@ -131,6 +140,16 @@ export class GuildModerationService {
|
||||
changes: this.guildAuditLogService.computeChanges(null, this.serializeBanForAudit(ban)),
|
||||
});
|
||||
}
|
||||
await emitGuildMemberBanned({
|
||||
guildId,
|
||||
userId: targetId,
|
||||
moderatorId: userId,
|
||||
by,
|
||||
memberCount: guildBeforeBan?.memberCount ?? 0,
|
||||
targetModerator,
|
||||
bannedAt: ban.bannedAt,
|
||||
expiresAt: ban.expiresAt,
|
||||
});
|
||||
await this.gatewayService.dispatchGuild({
|
||||
guildId,
|
||||
event: 'GUILD_BAN_ADD',
|
||||
@@ -177,16 +196,18 @@ export class GuildModerationService {
|
||||
userId: UserID;
|
||||
targetId: UserID;
|
||||
guildId: GuildID;
|
||||
by?: BanBy;
|
||||
},
|
||||
auditLogReason?: string | null,
|
||||
): Promise<void> {
|
||||
const {userId, guildId, targetId} = params;
|
||||
const {userId, guildId, targetId, by = 'moderator'} = params;
|
||||
await this.checkModerationPermission({guildId, userId, permission: Permissions.BAN_MEMBERS});
|
||||
const ban = await this.guildRepository.getBan(guildId, targetId);
|
||||
if (!ban) {
|
||||
throw InputValidationError.fromCode('user_id', ValidationErrorCodes.USER_IS_NOT_BANNED);
|
||||
}
|
||||
await this.guildRepository.deleteBan(guildId, targetId);
|
||||
await emitGuildMemberUnbanned({guildId, userId: targetId, moderatorId: userId, by});
|
||||
await this.recordAuditLog({
|
||||
guildId,
|
||||
userId,
|
||||
@@ -225,6 +246,23 @@ export class GuildModerationService {
|
||||
}
|
||||
}
|
||||
|
||||
private async isGuildModerator(
|
||||
guildId: GuildID,
|
||||
targetId: UserID,
|
||||
guild: Guild | null,
|
||||
isMember: boolean,
|
||||
): Promise<boolean> {
|
||||
if (guild?.ownerId === targetId) return true;
|
||||
if (!isMember) return false;
|
||||
try {
|
||||
const permissions = await this.gatewayService.getUserPermissions({guildId, userId: targetId});
|
||||
return (permissions & GUILD_MODERATION_PERMISSIONS) !== 0n;
|
||||
} catch (error) {
|
||||
Logger.debug({error, guildId: guildId.toString()}, 'Could not read target permissions for a guild ban');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private serializeBanForAudit(ban: GuildBan): Record<string, unknown> {
|
||||
return {
|
||||
user_id: ban.userId.toString(),
|
||||
|
||||
Reference in new issue
Block a user