Skip to content

Commit

Permalink
Feat: WebHookでいろいろ通知できるようにした
Browse files Browse the repository at this point in the history
  • Loading branch information
mattyatea committed Jul 20, 2024
1 parent de2be9d commit a059034
Show file tree
Hide file tree
Showing 11 changed files with 106 additions and 92 deletions.
5 changes: 5 additions & 0 deletions packages/backend/migration/1698907074200-gorillamode.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project , Type4ny-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

export class Gorillamode1698907074200 {
name = 'Gorillamode1698907074200';

Expand Down
7 changes: 6 additions & 1 deletion packages/backend/src/core/AbuseReportService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import type { AbuseUserReportsRepository, MiAbuseUserReport, MiUser, UsersRepository } from '@/models/_.js';
import type { AbuseUserReportsRepository, MiAbuseUserReport, MiNote, MiUser, UsersRepository } from '@/models/_.js';
import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
import { QueueService } from '@/core/QueueService.js';
import { InstanceActorService } from '@/core/InstanceActorService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import type { Packed } from '@/misc/json-schema.js';
import { IdService } from './IdService.js';

@Injectable()
Expand Down Expand Up @@ -47,6 +48,8 @@ export class AbuseReportService {
reporterId: MiAbuseUserReport['reporterId'],
reporterHost: MiAbuseUserReport['reporterHost'],
comment: string,
notes?: Packed<'Note'>[] | [],
noteIds?: MiNote['id'][] | [],
}[]) {
const entities = params.map(param => {
return {
Expand All @@ -56,6 +59,8 @@ export class AbuseReportService {
reporterId: param.reporterId,
reporterHost: param.reporterHost,
comment: param.comment,
notes: param.notes ?? [],
noteIds: param.noteIds ?? [],
};
});

Expand Down
32 changes: 27 additions & 5 deletions packages/backend/src/core/CustomEmojiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
import type { MiEmoji } from '@/models/Emoji.js';
import type { EmojisRepository, EmojiRequestsRepository, MiRole, MiUser } from '@/models/_.js';
import type { EmojisRepository, EmojiRequestsRepository, MiRole, MiUser, SystemWebhooksRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
import { UtilityService } from '@/core/UtilityService.js';
import type { Serialized } from '@/types.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { MiEmojiRequest } from '@/models/EmojiRequest.js';
import { SystemWebhookService } from '@/core/SystemWebhookService.js';

const parseEmojiStrRegexp = /^([-\w]+)(?:@([\w.-]+))?$/;

Expand All @@ -37,11 +38,15 @@ export class CustomEmojiService implements OnApplicationShutdown {
@Inject(DI.emojiRequestsRepository)
private emojiRequestsRepository: EmojiRequestsRepository,

@Inject(DI.systemWebhooksRepository)
private systemWebhooksRepository: SystemWebhooksRepository,

private utilityService: UtilityService,
private idService: IdService,
private emojiEntityService: EmojiEntityService,
private moderationLogService: ModerationLogService,
private globalEventService: GlobalEventService,
private systemWebhookService: SystemWebhookService,
) {
this.cache = new MemoryKVCache<MiEmoji | null>(1000 * 60 * 60 * 12);

Expand All @@ -58,7 +63,19 @@ export class CustomEmojiService implements OnApplicationShutdown {
},
});
}

private async notifyWebhooks(emoji: MiEmojiRequest | MiEmoji, eventType: 'customEmojiRequest' | 'customEmojiRequestResolved', me?: MiUser | null) {
const activeSystemWebhooksWithCustomEmojiRequest = await this.systemWebhooksRepository
.createQueryBuilder('webhook')
.where('webhook.isActive = :isActive', { isActive: true })
.andWhere('webhook.on @> :eventName', { eventName: `{${eventType}}` })
.getMany();
console.log({ emoji, user: (me ? me : null) });
activeSystemWebhooksWithCustomEmojiRequest.forEach(it => this.systemWebhookService.enqueueSystemWebhook(
it.id,
eventType,
{ emoji, user: (me ? me : null) },
));
}
@bindThis
public async request(data: {
driveFile: MiDriveFile;
Expand All @@ -85,12 +102,12 @@ export class CustomEmojiService implements OnApplicationShutdown {
}).then(x => this.emojiRequestsRepository.findOneByOrFail(x.identifiers[0]));

if (me) {
this.moderationLogService.log(me, 'addCustomEmoji', {
await this.moderationLogService.log(me, 'addCustomEmoji', {
emojiId: emoji.id,
emoji: emoji,
});
}

await this.notifyWebhooks(emoji, 'customEmojiRequest', me ? me : null);
return emoji;
}

Expand All @@ -105,7 +122,8 @@ export class CustomEmojiService implements OnApplicationShutdown {
isSensitive: boolean;
localOnly: boolean;
roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][];
}, moderator?: MiUser): Promise<MiEmoji> {
requestToAdd?: boolean;
}, moderator?: MiUser, me?: MiUser): Promise<MiEmoji> {
const emoji = await this.emojisRepository.insertOne({
id: this.idService.gen(),
updatedAt: new Date(),
Expand All @@ -122,6 +140,10 @@ export class CustomEmojiService implements OnApplicationShutdown {
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction,
});

if (data.requestToAdd) {
await this.notifyWebhooks(emoji, 'customEmojiRequestResolved', me ? me : null);
}

if (data.host == null) {
this.localEmojisCache.refresh();

Expand Down
6 changes: 6 additions & 0 deletions packages/backend/src/models/SystemWebhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ export const systemWebhookEventTypes = [
'abuseReport',
// 通報を処理したとき
'abuseReportResolved',
// 絵文字申請を受け取ったとき
'customEmojiRequest',
// 絵文字申請を処理したとき
'customEmojiRequestResolved',
// ユーザが登録したとき
'userRegistered',
] as const;
export type SystemWebhookEventType = typeof systemWebhookEventTypes[number];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ export class SystemWebhookDeliverProcessorService {
public async process(job: Bull.Job<SystemWebhookDeliverJobData>): Promise<string> {
try {
this.logger.debug(`delivering ${job.data.webhookId}`);
this.logger.debug( JSON.stringify({
server: this.config.url,
hookId: job.data.webhookId,
eventId: job.data.eventId,
createdAt: job.data.createdAt,
type: job.data.type,
body: job.data.content,
}));

const res = await this.httpRequestService.send(job.data.to, {
method: 'POST',
Expand Down
48 changes: 28 additions & 20 deletions packages/backend/src/server/api/SignupApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@ import bcrypt from 'bcryptjs';
import { IsNull } from 'typeorm';
import ProxyCheck from 'proxycheck-ts';
import { DI } from '@/di-symbols.js';
import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository, MiRegistrationTicket } from '@/models/_.js';
import type {
RegistrationTicketsRepository,
UsedUsernamesRepository,
UserPendingsRepository,
UserProfilesRepository,
UsersRepository,
MiRegistrationTicket,
SystemWebhooksRepository,
} from '@/models/_.js';
import type { Config } from '@/config.js';
import { MetaService } from '@/core/MetaService.js';
import { CaptchaService } from '@/core/CaptchaService.js';
Expand All @@ -20,6 +28,7 @@ import { MiLocalUser } from '@/models/User.js';
import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
import { bindThis } from '@/decorators.js';
import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js';
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
import { SigninService } from './SigninService.js';
import type { FastifyRequest, FastifyReply } from 'fastify';

Expand All @@ -44,13 +53,17 @@ export class SignupApiService {
@Inject(DI.registrationTicketsRepository)
private registrationTicketsRepository: RegistrationTicketsRepository,

@Inject(DI.systemWebhooksRepository)
private systemWebhooksRepository: SystemWebhooksRepository,

private userEntityService: UserEntityService,
private idService: IdService,
private metaService: MetaService,
private captchaService: CaptchaService,
private signupService: SignupService,
private signinService: SigninService,
private emailService: EmailService,
private systemWebhookService: SystemWebhookService,
) {
}

Expand Down Expand Up @@ -108,25 +121,20 @@ export class SignupApiService {
const host: string | null = process.env.NODE_ENV === 'test' ? (body['host'] ?? null) : null;
const invitationCode = body['invitationCode'];
const emailAddress = body['emailAddress'];

const { DiscordWebhookUrl } = (await this.metaService.fetch());
if (DiscordWebhookUrl) {
const data_disc = { 'username': 'ユーザー登録お知らせ',
'content':
'ユーザー名 :' + username + '\n' +
'メールアドレス : ' + emailAddress + '\n' +
'IPアドレス : ' + request.headers['x-real-ip'] ?? request.ip,
};

await fetch(DiscordWebhookUrl, {
'method': 'post',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data_disc),
});
}

const activeSystemWebhooksWithUserRegistered = await this.systemWebhooksRepository
.createQueryBuilder('webhook')
.where('webhook.isActive = :isActive', { isActive: true })
.andWhere('webhook.on @> :eventName', { eventName: '{userRegistered}' })
.getMany();
activeSystemWebhooksWithUserRegistered.forEach(it => this.systemWebhookService.enqueueSystemWebhook(
it.id,
'userRegistered',
{
username,
email: emailAddress ?? null,
host,
},
));
if (instance.emailRequiredForSignup) {
if (emailAddress == null || typeof emailAddress !== 'string') {
reply.code(400);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
throw new ApiError();
}
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
const { ApiBase, EmojiBotToken, DiscordWebhookUrl, requestEmojiAllOk } = (await this.metaService.fetch());
const { ApiBase, EmojiBotToken, requestEmojiAllOk } = (await this.metaService.fetch());
let emoji;
if (requestEmojiAllOk) {
emoji = await this.customEmojiService.add({
Expand All @@ -91,7 +91,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
isSensitive: ps.isSensitive ?? false,
localOnly: ps.localOnly ?? false,
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
});
}, undefined, me);
} else {
emoji = await this.customEmojiService.request({
driveFile,
Expand All @@ -101,7 +101,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
license: ps.license ?? null,
isSensitive: ps.isSensitive ?? false,
localOnly: ps.localOnly ?? false,
});
}, me);
}

await this.moderationLogService.log(me, 'addCustomEmoji', {
Expand Down Expand Up @@ -129,23 +129,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
});
}

if (DiscordWebhookUrl) {
const data_disc = { 'username': '絵文字追加通知ちゃん',
'content':
'絵文字名 : :' + ps.name + ':\n' +
'カテゴリ : ' + ps.category + '\n' +
'ライセンス : ' + ps.license + '\n' +
'タグ : ' + ps.aliases + '\n' +
'追加したユーザー : ' + '@' + me.username + '\n',
};
await fetch(DiscordWebhookUrl, {
'method': 'post',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data_disc),
});
}
return {
id: emoji.id,
};
Expand Down
45 changes: 2 additions & 43 deletions packages/backend/src/server/api/endpoints/users/report-abuse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,56 +94,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const notes = ps.noteIds ? await this.notesRepository.find({
where: { id: In(ps.noteIds), userId: targetUser.id },
}) : [];

const report = await this.abuseUserReportsRepository.insert({
id: this.idService.gen(),
await this.abuseReportService.report([{
targetUserId: targetUser.id,
targetUserHost: targetUser.host,
reporterId: me.id,
reporterHost: null,
comment: ps.comment,
notes: (ps.noteIds && !((await this.metaService.fetch()).enableGDPRMode)) ? await this.noteEntityService.packMany(notes) : [],
noteIds: (ps.noteIds && (await this.metaService.fetch()).enableGDPRMode) ? ps.noteIds : [],
}).then(x => this.abuseUserReportsRepository.findOneByOrFail(x.identifiers[0]));

// Publish event to moderators
setImmediate(async () => {
const moderators = await this.roleService.getModerators();

for (const moderator of moderators) {
this.globalEventService.publishAdminStream(moderator.id, 'newAbuseUserReport', {
id: report.id,
targetUserId: report.targetUserId,
reporterId: report.reporterId,
comment: report.comment,
notes: report.notes,
noteIds: report.noteIds ?? [],
});
}
const meta = await this.metaService.fetch();
if (meta.DiscordWebhookUrl) {
const data_disc = { 'username': '絵文字追加通知ちゃん',
'content':

'通報' + '\n' +
'通報' + report.comment + '\n' +
'通報したユーザー : ' + '@' + me.username + '\n' +
'通報されたユーザー : ' + report.targetUserId + '\n',
};
await fetch(meta.DiscordWebhookUrl, {
'method': 'post',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data_disc),
});
}
if (meta.email) {
this.emailService.sendEmail(meta.email, 'New abuse report',
sanitizeHtml(ps.comment),
sanitizeHtml(ps.comment));
}
});
}]);
});
}
}
4 changes: 2 additions & 2 deletions packages/frontend/src/components/MkNotes.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ SPDX-License-Identifier: AGPL-3.0-only

<template #default="{ item: note, index, items }">
<div :class="[$style.root, { [$style.noGap]: noGap },{ [$style.dateseparatedlist]: noGap}]">
<div :class="$style.notes,{ [$style.dateseparatedlistnogap]: noGap}" >
<p :style="{margin: 0, borderBottom: 'solid 1px var(--divider)'}"></p>
<div :class="$style.notes, { [$style.dateseparatedlistnogap]: noGap}">
<p v-if="index !== 0" :style="{margin: 0, borderBottom: 'solid 1px var(--divider)'}"></p>
<MkNote v-if="props.withCw && !note.cw || !props.withCw" :key="note._featuredId_ || note._prId_ || note.id" :class="$style.note" :note="note" :withHardMute="true"/>
<div v-if="index !== items.length - 1 && note?.createdAt && items[index + 1]?.createdAt && (new Date(note?.createdAt).getDate()) !== ( new Date(items[index + 1]?.createdAt).getDate())" :key="note.id" :class="$style.separator">
<p :class="$style.date">
Expand Down
Loading

0 comments on commit a059034

Please sign in to comment.