diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index 9f844dde20..0f3bc4889e 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -47,6 +47,7 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024110](CHANG - 기간은 최소 `1`일부터 최대 `30`일까지 설정할 수 있습니다. - `0`일로 설정하면 최소 기간인 `1`일로 설정되며, `30`일을 초과하는 값을 입력하면 최대 기간인 `30`일로 설정됩니다. - 여기서 설정된 기간은 `초대제로 전환` 옵션과 `공개 노트 허용` 옵션 모두에 적용됩니다. +- Enhance: 예약된 노트 게시에 실패할 경우 사용자에게 알림 ([penginn-net/kokonect@a0e47980](https://github.com/penginn-net/kokonect/commit/a0e47980470b49e79e84ff3b7ccaf2b4502928c8)) ### Client - Enhance: 미디어 그리드 레이아옷 조정 diff --git a/locales/en-US.yml b/locales/en-US.yml index 62cd96f7d3..ba3e03c9f1 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -2820,12 +2820,19 @@ _notification: achievementEarned: "Achievement unlocked" exportCompleted: "The export has been completed" login: "Sign In" + scheduleNote: "Scheduled note posting failed" test: "Notification test" app: "Notifications from linked apps" _actions: followBack: "followed you back" reply: "Reply" renote: "Renote" + _scheduleNote: + unknown: "The cause is unknown" + renoteTargetNotFound: "Renote target note not found" + channelTargetNotFound: "Channel not found" + replyTargetNotFound: "Reply target note not found" + invalidFilesCount: "No attachments" _deck: alwaysShowMainColumn: "Always show main column" columnAlign: "Align columns" diff --git a/locales/index.d.ts b/locales/index.d.ts index d40d9ce00a..f51e33c99e 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -10989,6 +10989,10 @@ export interface Locale extends ILocale { * ログイン */ "login": string; + /** + * 予約投稿に失敗 + */ + "scheduleNote": string; /** * 通知のテスト */ @@ -11012,6 +11016,28 @@ export interface Locale extends ILocale { */ "renote": string; }; + "_scheduleNote": { + /** + * 原因は不明です + */ + "unknown": string; + /** + * 引用元がありません + */ + "renoteTargetNotFound": string; + /** + * 対象のチャンネルがありません + */ + "channelTargetNotFound": string; + /** + * 返信先がありません + */ + "replyTargetNotFound": string; + /** + * 添付ファイルがありません + */ + "invalidFilesCount": string; + }; }; "_deck": { /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 65f25c70c5..de88b1326f 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2895,6 +2895,7 @@ _notification: achievementEarned: "実績の獲得" exportCompleted: "エクスポートが完了した" login: "ログイン" + scheduleNote: "予約投稿に失敗" test: "通知のテスト" app: "連携アプリからの通知" @@ -2903,6 +2904,13 @@ _notification: reply: "返信" renote: "リノート" + _scheduleNote: + unknown: "原因は不明です" + renoteTargetNotFound: "引用元がありません" + channelTargetNotFound: "対象のチャンネルがありません" + replyTargetNotFound: "返信先がありません" + invalidFilesCount: "添付ファイルがありません" + _deck: alwaysShowMainColumn: "常にメインカラムを表示" columnAlign: "カラムの寄せ" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 2b879ff1cd..f850aabf4e 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -2823,12 +2823,19 @@ _notification: achievementEarned: "도전 과제 획득" exportCompleted: "내보내기를 완료함" login: "로그인" + scheduleNote: "게시가 예약된 노트의 게시가 실패함" test: "알림 테스트" app: "연동된 앱을 통한 알림" _actions: followBack: "팔로우" reply: "답글" renote: "리노트" + _scheduleNote: + unknown: "알 수 없는 오류가 발생했어요" + renoteTargetNotFound: "인용할 대상이 없어요" + channelTargetNotFound: "해당 채널이 존재하지 않아요" + replyTargetNotFound: "답장할 대상이 없어요" + invalidFilesCount: "첨부 파일이 없어요" _deck: alwaysShowMainColumn: "메인 칼럼 항상 표시" columnAlign: "칼럼 정렬" diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index 93c3ccee25..3c41db0cdb 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -202,6 +202,9 @@ export class NotificationEntityService implements OnModuleInit { ...(notification.type === 'login' ? { ip: notification.userIp, } : {}), + ...(notification.type === 'scheduleNote' ? { + errorType: notification.errorType, + } : {}), ...(notification.type === 'app' ? { body: notification.customBody, header: notification.customHeader, diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts index 3bca987892..4df6c19908 100644 --- a/packages/backend/src/models/Notification.ts +++ b/packages/backend/src/models/Notification.ts @@ -98,6 +98,11 @@ export type MiNotification = { id: string; createdAt: string; userIp: string; +} | { + type: 'scheduleNote'; + id: string; + createdAt: string; + errorType: string; } | { type: 'app'; id: string; diff --git a/packages/backend/src/models/json-schema/notification.ts b/packages/backend/src/models/json-schema/notification.ts index c7ea635589..53e9d179ad 100644 --- a/packages/backend/src/models/json-schema/notification.ts +++ b/packages/backend/src/models/json-schema/notification.ts @@ -336,6 +336,20 @@ export const packedNotificationSchema = { optional: false, nullable: false, }, }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['scheduleNote'], + }, + errorType: { + type: 'string', + optional: false, nullable: false, + }, + }, }, { type: 'object', properties: { diff --git a/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts b/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts index 6b5b9db71b..055ca409c7 100644 --- a/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts +++ b/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts @@ -9,6 +9,7 @@ import { bindThis } from '@/decorators.js'; import { NoteCreateService } from '@/core/NoteCreateService.js'; import type { ChannelsRepository, DriveFilesRepository, MiDriveFile, NoteScheduleRepository, NotesRepository, UsersRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { ScheduleNotePostJobData } from '../types.js'; @@ -32,6 +33,7 @@ export class ScheduleNotePostProcessorService { private noteCreateService: NoteCreateService, private queueLoggerService: QueueLoggerService, + private notificationService: NotificationService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('schedule-note-post'); } @@ -72,6 +74,25 @@ export class ScheduleNotePostProcessorService { //キューに積んだときは有った物が消滅してたら予約投稿をキャンセルする this.logger.warn('cancel schedule note'); await this.noteScheduleRepository.remove(data); + + if (data.userId && me) { //ユーザーが特定できる場合に失敗を通知 + let errorType = 'unknown'; + if (note.renote && !renote) { + errorType = 'renoteTargetNotFound'; + } + if (note.reply && !reply) { + errorType = 'replyTargetNotFound'; + } + if (note.channel && !channel) { + errorType = 'channelTargetNotFound'; + } + if (note.files.length !== files.length) { + errorType = 'invalidFilesCount'; + } + this.notificationService.createNotification(data.userId, 'scheduleNote', { + errorType, + }); + } return; } await this.noteCreateService.create(me, { diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 65d50bcf6d..70ac83ffe8 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -38,6 +38,7 @@ export const notificationTypes = [ 'achievementEarned', 'exportCompleted', 'login', + 'scheduleNote', 'app', 'test', ] as const; diff --git a/packages/cherrypick-js/etc/cherrypick-js.api.md b/packages/cherrypick-js/etc/cherrypick-js.api.md index cca82bfd38..7605d0956f 100644 --- a/packages/cherrypick-js/etc/cherrypick-js.api.md +++ b/packages/cherrypick-js/etc/cherrypick-js.api.md @@ -3004,7 +3004,7 @@ type Notification_2 = components['schemas']['Notification']; type NotificationsCreateRequest = operations['notifications___create']['requestBody']['content']['application/json']; // @public (undocumented) -export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "achievementEarned"]; +export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "achievementEarned", "scheduleNote"]; // @public (undocumented) export function nyaize(text: string): string; diff --git a/packages/cherrypick-js/src/autogen/types.ts b/packages/cherrypick-js/src/autogen/types.ts index 67211794a6..a573b6afa8 100644 --- a/packages/cherrypick-js/src/autogen/types.ts +++ b/packages/cherrypick-js/src/autogen/types.ts @@ -4715,6 +4715,14 @@ export type components = { /** @enum {string} */ type: 'login'; ip: string; + } | { + /** Format: id */ + id: string; + /** Format: date-time */ + createdAt: string; + /** @enum {string} */ + type: 'scheduleNote'; + errorType: string; } | ({ /** Format: id */ id: string; @@ -19667,8 +19675,8 @@ export type operations = { untilId?: string; /** @default true */ markAsRead?: boolean; - includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'pollVote')[]; - excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'pollVote')[]; + includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'scheduleNote' | 'app' | 'test' | 'pollVote')[]; + excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'scheduleNote' | 'app' | 'test' | 'pollVote')[]; }; }; }; @@ -19735,8 +19743,8 @@ export type operations = { untilId?: string; /** @default true */ markAsRead?: boolean; - includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote')[]; - excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote')[]; + includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'scheduleNote' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote')[]; + excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'scheduleNote' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote')[]; }; }; }; diff --git a/packages/cherrypick-js/src/consts.ts b/packages/cherrypick-js/src/consts.ts index 90847da702..04daacd1d6 100644 --- a/packages/cherrypick-js/src/consts.ts +++ b/packages/cherrypick-js/src/consts.ts @@ -16,7 +16,7 @@ import type { UserLite, } from './autogen/models.js'; -export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'roleAssigned', 'achievementEarned'] as const; +export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'roleAssigned', 'achievementEarned', 'scheduleNote'] as const; export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const; diff --git a/packages/frontend-shared/js/const.ts b/packages/frontend-shared/js/const.ts index af3d886727..6ae8cd7a79 100644 --- a/packages/frontend-shared/js/const.ts +++ b/packages/frontend-shared/js/const.ts @@ -70,6 +70,7 @@ export const notificationTypes = [ 'achievementEarned', 'exportCompleted', 'login', + 'scheduleNote', 'test', 'app', ] as const; diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index 0787d2085e..61ca9aa67a 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -12,6 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only