Merge branch 'develop' into feat-14931

This commit is contained in:
かっこかり 2024-11-19 10:42:39 +09:00 committed by GitHub
commit 662bfd64bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 95 additions and 37 deletions

View File

@ -8,6 +8,7 @@
### General ### General
- Feat: コンテンツの表示にログインを必須にできるように - Feat: コンテンツの表示にログインを必須にできるように
- Feat: 過去のノートを非公開化/フォロワーのみ表示可能にできるように - Feat: 過去のノートを非公開化/フォロワーのみ表示可能にできるように
- Fix: お知らせ作成時に画像URL入力欄を空欄に変更できないのを修正 ( #14976 )
- Enhance: 依存関係の更新 - Enhance: 依存関係の更新
- Enhance: l10nの更新 - Enhance: l10nの更新
@ -28,6 +29,7 @@
- Enhance: 過去に送信したフォローリクエストを確認できるように - Enhance: 過去に送信したフォローリクエストを確認できるように
(Based on https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/663) (Based on https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/663)
- Enhance: サイドバーを簡単に展開・折りたたみできるように ( #14981 ) - Enhance: サイドバーを簡単に展開・折りたたみできるように ( #14981 )
- Enhance: リノートメニューに「リノートの詳細」を追加
- Enhance: Blueskyの投稿埋め込みプレビューに対応 - Enhance: Blueskyの投稿埋め込みプレビューに対応
- Fix: 通知の範囲指定の設定項目が必要ない通知設定でも範囲指定の設定がでている問題を修正 - Fix: 通知の範囲指定の設定項目が必要ない通知設定でも範囲指定の設定がでている問題を修正
- Fix: Turnstileが失敗・期限切れした際にも成功扱いとなってしまう問題を修正 - Fix: Turnstileが失敗・期限切れした際にも成功扱いとなってしまう問題を修正
@ -40,6 +42,8 @@
- Fix: メールアドレス登録有効化時の「完了」ダイアログボックスの表示条件を修正 - Fix: メールアドレス登録有効化時の「完了」ダイアログボックスの表示条件を修正
- Fix: 画面幅が狭い環境でデザインが崩れる問題を修正 - Fix: 画面幅が狭い環境でデザインが崩れる問題を修正
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/815) (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/815)
- Fix: TypeScriptの型チェック対象ファイルを限定してビルドを高速化するように
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/725)
### Server ### Server
- Enhance: DockerのNode.jsを22.11.0に更新 - Enhance: DockerのNode.jsを22.11.0に更新

4
locales/index.d.ts vendored
View File

@ -2362,6 +2362,10 @@ export interface Locale extends ILocale {
* *
*/ */
"details": string; "details": string;
/**
*
*/
"renoteDetails": string;
/** /**
* *
*/ */

View File

@ -586,6 +586,7 @@ masterVolume: "マスター音量"
notUseSound: "サウンドを出力しない" notUseSound: "サウンドを出力しない"
useSoundOnlyWhenActive: "Misskeyがアクティブな時のみサウンドを出力する" useSoundOnlyWhenActive: "Misskeyがアクティブな時のみサウンドを出力する"
details: "詳細" details: "詳細"
renoteDetails: "リノートの詳細"
chooseEmoji: "絵文字を選択" chooseEmoji: "絵文字を選択"
unableToProcess: "操作を完了できません" unableToProcess: "操作を完了できません"
recentUsed: "最近使用" recentUsed: "最近使用"

View File

@ -154,9 +154,9 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
const convertedReports = abuseReports.map(it => { const convertedReports = abuseReports.map(it => {
return { return {
...it, ...it,
reporter: usersMap.get(it.reporterId), reporter: usersMap.get(it.reporterId) ?? null,
targetUser: usersMap.get(it.targetUserId), targetUser: usersMap.get(it.targetUserId) ?? null,
assignee: it.assigneeId ? usersMap.get(it.assigneeId) : null, assignee: it.assigneeId ? (usersMap.get(it.assigneeId) ?? null) : null,
}; };
}); });

View File

@ -72,7 +72,7 @@ export class AnnouncementService {
updatedAt: null, updatedAt: null,
title: values.title, title: values.title,
text: values.text, text: values.text,
imageUrl: values.imageUrl, imageUrl: values.imageUrl || null,
icon: values.icon, icon: values.icon,
display: values.display, display: values.display,
forExistingUsers: values.forExistingUsers, forExistingUsers: values.forExistingUsers,

View File

@ -7,13 +7,15 @@ import { randomUUID } from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import type { IActivity } from '@/core/activitypub/type.js'; import type { IActivity } from '@/core/activitypub/type.js';
import type { MiDriveFile } from '@/models/DriveFile.js'; import type { MiDriveFile } from '@/models/DriveFile.js';
import type { MiWebhook, WebhookEventTypes, webhookEventTypes } from '@/models/Webhook.js'; import type { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js';
import type { MiSystemWebhook, SystemWebhookEventType } from '@/models/SystemWebhook.js'; import type { MiSystemWebhook, SystemWebhookEventType } from '@/models/SystemWebhook.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js'; import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js';
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js'; import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
import { type SystemWebhookPayload } from '@/core/SystemWebhookService.js';
import { type UserWebhookPayload } from './UserWebhookService.js';
import type { import type {
DbJobData, DbJobData,
DeliverJobData, DeliverJobData,
@ -30,12 +32,11 @@ import type {
ObjectStorageQueue, ObjectStorageQueue,
RelationshipQueue, RelationshipQueue,
SystemQueue, SystemQueue,
UserWebhookDeliverQueue,
SystemWebhookDeliverQueue, SystemWebhookDeliverQueue,
UserWebhookDeliverQueue,
} from './QueueModule.js'; } from './QueueModule.js';
import type httpSignature from '@peertube/http-signature'; import type httpSignature from '@peertube/http-signature';
import type * as Bull from 'bullmq'; import type * as Bull from 'bullmq';
import { type UserWebhookPayload } from './UserWebhookService.js';
@Injectable() @Injectable()
export class QueueService { export class QueueService {
@ -501,10 +502,10 @@ export class QueueService {
* @see SystemWebhookDeliverProcessorService * @see SystemWebhookDeliverProcessorService
*/ */
@bindThis @bindThis
public systemWebhookDeliver( public systemWebhookDeliver<T extends SystemWebhookEventType>(
webhook: MiSystemWebhook, webhook: MiSystemWebhook,
type: SystemWebhookEventType, type: T,
content: unknown, content: SystemWebhookPayload<T>,
opts?: { attempts?: number }, opts?: { attempts?: number },
) { ) {
const data: SystemWebhookDeliverJobData = { const data: SystemWebhookDeliverJobData = {

View File

@ -15,8 +15,39 @@ import { QueueService } from '@/core/QueueService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js';
import { LoggerService } from '@/core/LoggerService.js'; import { LoggerService } from '@/core/LoggerService.js';
import Logger from '@/logger.js'; import Logger from '@/logger.js';
import { Packed } from '@/misc/json-schema.js';
import { AbuseReportResolveType } from '@/models/AbuseUserReport.js';
import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
import type { OnApplicationShutdown } from '@nestjs/common'; import type { OnApplicationShutdown } from '@nestjs/common';
export type AbuseReportPayload = {
id: string;
targetUserId: string;
targetUser: Packed<'UserLite'> | null;
targetUserHost: string | null;
reporterId: string;
reporter: Packed<'UserLite'> | null;
reporterHost: string | null;
assigneeId: string | null;
assignee: Packed<'UserLite'> | null;
resolved: boolean;
forwarded: boolean;
comment: string;
moderationNote: string;
resolvedAs: AbuseReportResolveType | null;
};
export type InactiveModeratorsWarningPayload = {
remainingTime: ModeratorInactivityRemainingTime;
};
export type SystemWebhookPayload<T extends SystemWebhookEventType> =
T extends 'abuseReport' | 'abuseReportResolved' ? AbuseReportPayload :
T extends 'userCreated' ? Packed<'UserLite'> :
T extends 'inactiveModeratorsWarning' ? InactiveModeratorsWarningPayload :
T extends 'inactiveModeratorsInvitationOnlyChanged' ? Record<string, never> :
never;
@Injectable() @Injectable()
export class SystemWebhookService implements OnApplicationShutdown { export class SystemWebhookService implements OnApplicationShutdown {
private logger: Logger; private logger: Logger;
@ -168,7 +199,7 @@ export class SystemWebhookService implements OnApplicationShutdown {
public async enqueueSystemWebhook<T extends SystemWebhookEventType>( public async enqueueSystemWebhook<T extends SystemWebhookEventType>(
webhook: MiSystemWebhook | MiSystemWebhook['id'], webhook: MiSystemWebhook | MiSystemWebhook['id'],
type: T, type: T,
content: unknown, content: SystemWebhookPayload<T>,
) { ) {
const webhookEntity = typeof webhook === 'string' const webhookEntity = typeof webhook === 'string'
? (await this.fetchActiveSystemWebhooks()).find(a => a.id === webhook) ? (await this.fetchActiveSystemWebhooks()).find(a => a.id === webhook)

View File

@ -7,7 +7,7 @@ import { Injectable } from '@nestjs/common';
import { MiAbuseUserReport, MiNote, MiUser, MiWebhook } from '@/models/_.js'; import { MiAbuseUserReport, MiNote, MiUser, MiWebhook } from '@/models/_.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { MiSystemWebhook, type SystemWebhookEventType } from '@/models/SystemWebhook.js'; import { MiSystemWebhook, type SystemWebhookEventType } from '@/models/SystemWebhook.js';
import { SystemWebhookService } from '@/core/SystemWebhookService.js'; import { AbuseReportPayload, SystemWebhookPayload, SystemWebhookService } from '@/core/SystemWebhookService.js';
import { Packed } from '@/misc/json-schema.js'; import { Packed } from '@/misc/json-schema.js';
import { type WebhookEventTypes } from '@/models/Webhook.js'; import { type WebhookEventTypes } from '@/models/Webhook.js';
import { type UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js'; import { type UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js';
@ -16,13 +16,7 @@ import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModera
const oneDayMillis = 24 * 60 * 60 * 1000; const oneDayMillis = 24 * 60 * 60 * 1000;
type AbuseUserReportDto = Omit<MiAbuseUserReport, 'targetUser' | 'reporter' | 'assignee'> & { function generateAbuseReport(override?: Partial<MiAbuseUserReport>): AbuseReportPayload {
targetUser: Packed<'UserLite'> | null,
reporter: Packed<'UserLite'> | null,
assignee: Packed<'UserLite'> | null,
};
function generateAbuseReport(override?: Partial<MiAbuseUserReport>): AbuseUserReportDto {
const result: MiAbuseUserReport = { const result: MiAbuseUserReport = {
id: 'dummy-abuse-report1', id: 'dummy-abuse-report1',
targetUserId: 'dummy-target-user', targetUserId: 'dummy-target-user',
@ -389,7 +383,8 @@ export class WebhookTestService {
break; break;
} }
// まだ実装されていない (#9485) // まだ実装されていない (#9485)
case 'reaction': return; case 'reaction':
return;
default: { default: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const _exhaustiveAssertion: never = params.type; const _exhaustiveAssertion: never = params.type;
@ -407,10 +402,10 @@ export class WebhookTestService {
* - on * - on
*/ */
@bindThis @bindThis
public async testSystemWebhook( public async testSystemWebhook<T extends SystemWebhookEventType>(
params: { params: {
webhookId: MiSystemWebhook['id'], webhookId: MiSystemWebhook['id'],
type: SystemWebhookEventType, type: T,
override?: Partial<Omit<MiSystemWebhook, 'id'>>, override?: Partial<Omit<MiSystemWebhook, 'id'>>,
}, },
) { ) {
@ -420,7 +415,7 @@ export class WebhookTestService {
} }
const webhook = webhooks[0]; const webhook = webhooks[0];
const send = (contents: unknown) => { const send = <U extends SystemWebhookEventType>(type: U, contents: SystemWebhookPayload<U>) => {
const merged = { const merged = {
...webhook, ...webhook,
...params.override, ...params.override,
@ -428,12 +423,12 @@ export class WebhookTestService {
// テスト目的なのでSystemWebhookServiceの機能を経由せず直接キューに追加するチェック処理などをスキップする意図. // テスト目的なのでSystemWebhookServiceの機能を経由せず直接キューに追加するチェック処理などをスキップする意図.
// また、Jobの試行回数も1回だけ. // また、Jobの試行回数も1回だけ.
this.queueService.systemWebhookDeliver(merged, params.type, contents, { attempts: 1 }); this.queueService.systemWebhookDeliver(merged, type, contents, { attempts: 1 });
}; };
switch (params.type) { switch (params.type) {
case 'abuseReport': { case 'abuseReport': {
send(generateAbuseReport({ send('abuseReport', generateAbuseReport({
targetUserId: dummyUser1.id, targetUserId: dummyUser1.id,
targetUser: dummyUser1, targetUser: dummyUser1,
reporterId: dummyUser2.id, reporterId: dummyUser2.id,
@ -442,7 +437,7 @@ export class WebhookTestService {
break; break;
} }
case 'abuseReportResolved': { case 'abuseReportResolved': {
send(generateAbuseReport({ send('abuseReportResolved', generateAbuseReport({
targetUserId: dummyUser1.id, targetUserId: dummyUser1.id,
targetUser: dummyUser1, targetUser: dummyUser1,
reporterId: dummyUser2.id, reporterId: dummyUser2.id,
@ -454,7 +449,7 @@ export class WebhookTestService {
break; break;
} }
case 'userCreated': { case 'userCreated': {
send(toPackedUserLite(dummyUser1)); send('userCreated', toPackedUserLite(dummyUser1));
break; break;
} }
case 'inactiveModeratorsWarning': { case 'inactiveModeratorsWarning': {
@ -464,15 +459,20 @@ export class WebhookTestService {
asHours: 24, asHours: 24,
}; };
send({ send('inactiveModeratorsWarning', {
remainingTime: dummyTime, remainingTime: dummyTime,
}); });
break; break;
} }
case 'inactiveModeratorsInvitationOnlyChanged': { case 'inactiveModeratorsInvitationOnlyChanged': {
send({}); send('inactiveModeratorsInvitationOnlyChanged', {});
break; break;
} }
default: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _exhaustiveAssertion: never = params.type;
return;
}
} }
} }
} }

View File

@ -7,6 +7,8 @@ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typ
import { id } from './util/id.js'; import { id } from './util/id.js';
import { MiUser } from './User.js'; import { MiUser } from './User.js';
export type AbuseReportResolveType = 'accept' | 'reject';
@Entity('abuse_user_report') @Entity('abuse_user_report')
export class MiAbuseUserReport { export class MiAbuseUserReport {
@PrimaryColumn(id()) @PrimaryColumn(id())
@ -76,7 +78,7 @@ export class MiAbuseUserReport {
@Column('varchar', { @Column('varchar', {
length: 128, nullable: true, length: 128, nullable: true,
}) })
public resolvedAs: 'accept' | 'reject' | null; public resolvedAs: AbuseReportResolveType | null;
//#region Denormalized fields //#region Denormalized fields
@Index() @Index()

View File

@ -55,7 +55,7 @@ export const paramDef = {
properties: { properties: {
title: { type: 'string', minLength: 1 }, title: { type: 'string', minLength: 1 },
text: { type: 'string', minLength: 1 }, text: { type: 'string', minLength: 1 },
imageUrl: { type: 'string', nullable: true, minLength: 1 }, imageUrl: { type: 'string', nullable: true, minLength: 0 },
icon: { type: 'string', enum: ['info', 'warning', 'error', 'success'], default: 'info' }, icon: { type: 'string', enum: ['info', 'warning', 'error', 'success'], default: 'info' },
display: { type: 'string', enum: ['normal', 'banner', 'dialog'], default: 'normal' }, display: { type: 'string', enum: ['normal', 'banner', 'dialog'], default: 'normal' },
forExistingUsers: { type: 'boolean', default: false }, forExistingUsers: { type: 'boolean', default: false },
@ -76,7 +76,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
updatedAt: null, updatedAt: null,
title: ps.title, title: ps.title,
text: ps.text, text: ps.text,
imageUrl: ps.imageUrl, /* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 空の文字列の場合、nullを渡すようにするため */
imageUrl: ps.imageUrl || null,
icon: ps.icon, icon: ps.icon,
display: ps.display, display: ps.display,
forExistingUsers: ps.forExistingUsers, forExistingUsers: ps.forExistingUsers,

View File

@ -33,7 +33,7 @@
"./node_modules" "./node_modules"
], ],
"types": [ "types": [
"vite/client", "vite/client"
], ],
"lib": [ "lib": [
"esnext", "esnext",
@ -44,8 +44,9 @@
}, },
"compileOnSave": false, "compileOnSave": false,
"include": [ "include": [
"./**/*.ts", "./src/**/*.ts",
"./**/*.vue" "./src/**/*.vue",
"./@types/**/*.ts"
], ],
"exclude": [ "exclude": [
".storybook/**/*" ".storybook/**/*"

View File

@ -187,6 +187,7 @@ import MkUrlPreview from '@/components/MkUrlPreview.vue';
import MkInstanceTicker from '@/components/MkInstanceTicker.vue'; import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
import { pleaseLogin, type OpenOnRemoteOptions } from '@/scripts/please-login.js'; import { pleaseLogin, type OpenOnRemoteOptions } from '@/scripts/please-login.js';
import { checkWordMute } from '@/scripts/check-word-mute.js'; import { checkWordMute } from '@/scripts/check-word-mute.js';
import { notePage } from '@/filters/note.js';
import { userPage } from '@/filters/user.js'; import { userPage } from '@/filters/user.js';
import number from '@/filters/number.js'; import number from '@/filters/number.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
@ -566,15 +567,24 @@ function showRenoteMenu(): void {
}; };
} }
const renoteDetailsMenu: MenuItem = {
type: 'link',
text: i18n.ts.renoteDetails,
icon: 'ti ti-info-circle',
to: notePage(note.value),
};
if (isMyRenote) { if (isMyRenote) {
pleaseLogin({ openOnRemote: pleaseLoginContext.value }); pleaseLogin({ openOnRemote: pleaseLoginContext.value });
os.popupMenu([ os.popupMenu([
renoteDetailsMenu,
getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote), getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote),
{ type: 'divider' }, { type: 'divider' },
getUnrenote(), getUnrenote(),
], renoteTime.value); ], renoteTime.value);
} else { } else {
os.popupMenu([ os.popupMenu([
renoteDetailsMenu,
getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote), getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote),
{ type: 'divider' }, { type: 'divider' },
getAbuseNoteMenu(note.value, i18n.ts.reportAbuseRenote), getAbuseNoteMenu(note.value, i18n.ts.reportAbuseRenote),

View File

@ -45,8 +45,11 @@
}, },
"compileOnSave": false, "compileOnSave": false,
"include": [ "include": [
"./**/*.ts", "./src/**/*.ts",
"./**/*.vue" "./src/**/*.vue",
"./test/**/*.ts",
"./test/**/*.vue",
"./@types/**/*.ts"
], ],
"exclude": [ "exclude": [
".storybook/**/*" ".storybook/**/*"