Skip to content

Commit

Permalink
Merge branch 'develop' into flash-update
Browse files Browse the repository at this point in the history
  • Loading branch information
syuilo authored Feb 22, 2024
2 parents 9d158f9 + 4d6fab0 commit 77b9539
Show file tree
Hide file tree
Showing 23 changed files with 146 additions and 51 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
## 202x.x.x (unreleased)

### General
- Enhance: サーバーごとにモデレーションノートを残せるように

### Client
- Enhance: ノート作成画面のファイル添付メニューの区切り線の位置を調整
Expand All @@ -25,6 +26,7 @@
### Server
- Fix: nodeinfoにenableMcaptchaとenableTurnstileが無いのを修正
- エンドポイント`flash/update``flashId`以外のパラメータは必須ではなくなりました
- Fix: 禁止キーワードを含むノートがDelayed Queueに追加されて再処理される問題を修正

## 2024.2.0

Expand Down Expand Up @@ -97,6 +99,7 @@
- Fix: エラー画像URLを設定した後解除すると,デフォルトの画像が表示されない問題の修正
- Fix: MkCodeEditorで行がずれていってしまう問題の修正
- Fix: Summaly proxy利用時にプレイヤーが動作しないことがあるのを修正 #13196
- Fix: ユーザの情報のポップアップが消えなくなることがある問題を修正

### Server
- Enhance: 連合先のレートリミットを超過した際にリトライするようになりました
Expand Down
6 changes: 5 additions & 1 deletion locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9172,7 +9172,7 @@ export interface Locale extends ILocale {
*/
"updateServerSettings": string;
/**
* モデレーションノート更新
* ユーザーのモデレーションノート更新
*/
"updateUserNote": string;
/**
Expand Down Expand Up @@ -9219,6 +9219,10 @@ export interface Locale extends ILocale {
* リモートサーバーを再開
*/
"unsuspendRemoteInstance": string;
/**
* リモートサーバーのモデレーションノート更新
*/
"updateRemoteInstanceNote": string;
/**
* ファイルをセンシティブ付与
*/
Expand Down
3 changes: 2 additions & 1 deletion locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2434,7 +2434,7 @@ _moderationLogTypes:
updateCustomEmoji: "カスタム絵文字更新"
deleteCustomEmoji: "カスタム絵文字削除"
updateServerSettings: "サーバー設定更新"
updateUserNote: "モデレーションノート更新"
updateUserNote: "ユーザーのモデレーションノート更新"
deleteDriveFile: "ファイルを削除"
deleteNote: "ノートを削除"
createGlobalAnnouncement: "全体のお知らせを作成"
Expand All @@ -2446,6 +2446,7 @@ _moderationLogTypes:
resetPassword: "パスワードをリセット"
suspendRemoteInstance: "リモートサーバーを停止"
unsuspendRemoteInstance: "リモートサーバーを再開"
updateRemoteInstanceNote: "リモートサーバーのモデレーションノート更新"
markSensitiveDriveFile: "ファイルをセンシティブ付与"
unmarkSensitiveDriveFile: "ファイルをセンシティブ解除"
resolveAbuseReport: "通報を解決"
Expand Down
16 changes: 16 additions & 0 deletions packages/backend/migration/1708399372194-per-instance-mod-note.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

export class PerInstanceModNote1708399372194 {
name = 'PerInstanceModNote1708399372194'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "instance" ADD "moderationNote" character varying(16384) NOT NULL DEFAULT ''`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "moderationNote"`);
}
}
5 changes: 2 additions & 3 deletions packages/backend/src/core/NoteCreateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import { UtilityService } from '@/core/UtilityService.js';
import { UserBlockingService } from '@/core/UserBlockingService.js';
import { isReply } from '@/misc/is-reply.js';
import { trackPromise } from '@/misc/promise-tracker.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';

type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';

Expand Down Expand Up @@ -151,8 +152,6 @@ type Option = {
export class NoteCreateService implements OnApplicationShutdown {
#shutdownController = new AbortController();

public static ContainsProhibitedWordsError = class extends Error {};

constructor(
@Inject(DI.config)
private config: Config,
Expand Down Expand Up @@ -264,7 +263,7 @@ export class NoteCreateService implements OnApplicationShutdown {
}

if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', meta.prohibitedWords)) {
throw new NoteCreateService.ContainsProhibitedWordsError();
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
}

const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host);
Expand Down
63 changes: 32 additions & 31 deletions packages/backend/src/core/NoteReadService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,46 +88,47 @@ export class NoteReadService implements OnApplicationShutdown {
userId: MiUser['id'],
notes: (MiNote | Packed<'Note'>)[],
): Promise<void> {
const readMentions: (MiNote | Packed<'Note'>)[] = [];
const readSpecifiedNotes: (MiNote | Packed<'Note'>)[] = [];
if (notes.length === 0) return;

const noteIds = new Set<MiNote['id']>();

for (const note of notes) {
if (note.mentions && note.mentions.includes(userId)) {
readMentions.push(note);
noteIds.add(note.id);
} else if (note.visibleUserIds && note.visibleUserIds.includes(userId)) {
readSpecifiedNotes.push(note);
noteIds.add(note.id);
}
}

if ((readMentions.length > 0) || (readSpecifiedNotes.length > 0)) {
// Remove the record
await this.noteUnreadsRepository.delete({
userId: userId,
noteId: In([...readMentions.map(n => n.id), ...readSpecifiedNotes.map(n => n.id)]),
});
if (noteIds.size === 0) return;

// TODO: ↓まとめてクエリしたい
// Remove the record
await this.noteUnreadsRepository.delete({
userId: userId,
noteId: In(Array.from(noteIds)),
});

trackPromise(this.noteUnreadsRepository.countBy({
userId: userId,
isMentioned: true,
}).then(mentionsCount => {
if (mentionsCount === 0) {
// 全て既読になったイベントを発行
this.globalEventService.publishMainStream(userId, 'readAllUnreadMentions');
}
}));

trackPromise(this.noteUnreadsRepository.countBy({
userId: userId,
isSpecified: true,
}).then(specifiedCount => {
if (specifiedCount === 0) {
// 全て既読になったイベントを発行
this.globalEventService.publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
}
}));
}
// TODO: ↓まとめてクエリしたい

trackPromise(this.noteUnreadsRepository.countBy({
userId: userId,
isMentioned: true,
}).then(mentionsCount => {
if (mentionsCount === 0) {
// 全て既読になったイベントを発行
this.globalEventService.publishMainStream(userId, 'readAllUnreadMentions');
}
}));

trackPromise(this.noteUnreadsRepository.countBy({
userId: userId,
isSpecified: true,
}).then(specifiedCount => {
if (specifiedCount === 0) {
// 全て既読になったイベントを発行
this.globalEventService.publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
}
}));
}

@bindThis
Expand Down
9 changes: 8 additions & 1 deletion packages/backend/src/core/entities/InstanceEntityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import type { Packed } from '@/misc/json-schema.js';
import type { MiInstance } from '@/models/Instance.js';
import { MetaService } from '@/core/MetaService.js';
import { bindThis } from '@/decorators.js';
import { UtilityService } from '../UtilityService.js';
import { UtilityService } from '@/core/UtilityService.js';
import { RoleService } from '@/core/RoleService.js';
import { MiUser } from '@/models/User.js';

@Injectable()
export class InstanceEntityService {
constructor(
private metaService: MetaService,
private roleService: RoleService,

private utilityService: UtilityService,
) {
Expand All @@ -22,8 +25,11 @@ export class InstanceEntityService {
@bindThis
public async pack(
instance: MiInstance,
me?: { id: MiUser['id']; } | null | undefined,
): Promise<Packed<'FederationInstance'>> {
const meta = await this.metaService.fetch();
const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false;

return {
id: instance.id,
firstRetrievedAt: instance.firstRetrievedAt.toISOString(),
Expand All @@ -48,6 +54,7 @@ export class InstanceEntityService {
themeColor: instance.themeColor,
infoUpdatedAt: instance.infoUpdatedAt ? instance.infoUpdatedAt.toISOString() : null,
latestRequestReceivedAt: instance.latestRequestReceivedAt ? instance.latestRequestReceivedAt.toISOString() : null,
moderationNote: iAmModerator ? instance.moderationNote : null,
};
}

Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/models/Instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,9 @@ export class MiInstance {
nullable: true,
})
public infoUpdatedAt: Date | null;

@Column('varchar', {
length: 16384, default: '',
})
public moderationNote: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,9 @@ export const packedFederationInstanceSchema = {
optional: false, nullable: true,
format: 'date-time',
},
moderationNote: {
type: 'string',
optional: true, nullable: true,
},
},
} as const;
10 changes: 9 additions & 1 deletion packages/backend/src/queue/processors/InboxProcessorService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
import { LdSignatureService } from '@/core/activitypub/LdSignatureService.js';
import { ApInboxService } from '@/core/activitypub/ApInboxService.js';
import { bindThis } from '@/decorators.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type { InboxJobData } from '../types.js';

Expand Down Expand Up @@ -180,7 +181,14 @@ export class InboxProcessorService {
});

// アクティビティを処理
await this.apInboxService.performActivity(authUser.user, activity);
try {
await this.apInboxService.performActivity(authUser.user, activity);
} catch (e) {
if (e instanceof IdentifiableError) {
if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') return 'blocked notes with prohibited words';
}
throw e;
}
return 'ok';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ export const paramDef = {
properties: {
host: { type: 'string' },
isSuspended: { type: 'boolean' },
moderationNote: { type: 'string' },
},
required: ['host', 'isSuspended'],
required: ['host'],
} as const;

@Injectable()
Expand All @@ -47,9 +48,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-

await this.federatedInstanceService.update(instance.id, {
isSuspended: ps.isSuspended,
moderationNote: ps.moderationNote,
});

if (instance.isSuspended !== ps.isSuspended) {
if (ps.isSuspended != null && instance.isSuspended !== ps.isSuspended) {
if (ps.isSuspended) {
this.moderationLogService.log(me, 'suspendRemoteInstance', {
id: instance.id,
Expand All @@ -62,6 +64,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
});
}
}

if (ps.moderationNote != null && instance.moderationNote !== ps.moderationNote) {
this.moderationLogService.log(me, 'updateRemoteInstanceNote', {
id: instance.id,
host: instance.host,
before: instance.moderationNote,
after: ps.moderationNote,
});
}
});
}
}
4 changes: 1 addition & 3 deletions packages/backend/src/server/api/endpoints/antennas/notes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
notes.sort((a, b) => a.id > b.id ? -1 : 1);
}

if (notes.length > 0) {
this.noteReadService.read(me.id, notes);
}
this.noteReadService.read(me.id, notes);

return await this.noteEntityService.packMany(notes, me);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const instance = await this.instancesRepository
.findOneBy({ host: this.utilityService.toPuny(ps.host) });

return instance ? await this.instanceEntityService.pack(instance) : null;
return instance ? await this.instanceEntityService.pack(instance, me) : null;
});
}
}
5 changes: 3 additions & 2 deletions packages/backend/src/server/api/endpoints/notes/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { DI } from '@/di-symbols.js';
import { isPureRenote } from '@/misc/is-pure-renote.js';
import { MetaService } from '@/core/MetaService.js';
import { UtilityService } from '@/core/UtilityService.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { ApiError } from '../../error.js';

export const meta = {
Expand Down Expand Up @@ -376,8 +377,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
};
} catch (e) {
// TODO: 他のErrorもここでキャッチしてエラーメッセージを当てるようにしたい
if (e instanceof NoteCreateService.ContainsProhibitedWordsError) {
throw new ApiError(meta.errors.containsProhibitedWords);
if (e instanceof IdentifiableError) {
if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') throw new ApiError(meta.errors.containsProhibitedWords);
}

throw e;
Expand Down
7 changes: 7 additions & 0 deletions packages/backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const moderationLogTypes = [
'resetPassword',
'suspendRemoteInstance',
'unsuspendRemoteInstance',
'updateRemoteInstanceNote',
'markSensitiveDriveFile',
'unmarkSensitiveDriveFile',
'resolveAbuseReport',
Expand Down Expand Up @@ -209,6 +210,12 @@ export type ModerationLogPayloads = {
id: string;
host: string;
};
updateRemoteInstanceNote: {
id: string;
host: string;
before: string | null;
after: string | null;
};
markSensitiveDriveFile: {
fileId: string;
fileUserId: string | null;
Expand Down
1 change: 0 additions & 1 deletion packages/frontend/src/directives/user-preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ export class UserPreview {
this.el.removeEventListener('mouseover', this.onMouseover);
this.el.removeEventListener('mouseleave', this.onMouseleave);
this.el.removeEventListener('click', this.onClick);
window.clearInterval(this.checkTimer);
}
}

Expand Down
6 changes: 6 additions & 0 deletions packages/frontend/src/pages/admin/modlog.ModLog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
</div>
</template>
<template v-else-if="log.type === 'updateRemoteInstanceNote'">
<div>{{ i18n.ts.user }}: {{ log.info.userId }}</div>
<div :class="$style.diff">
<CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/>
</div>
</template>

<details>
<summary>raw</summary>
Expand Down
Loading

0 comments on commit 77b9539

Please sign in to comment.