Skip to content

Commit

Permalink
予約投稿に失敗した時に通知する (kokonect-link#523)
Browse files Browse the repository at this point in the history
  • Loading branch information
kozakura913 authored Nov 2, 2024
1 parent 0e9d8b9 commit a0e4798
Show file tree
Hide file tree
Showing 13 changed files with 107 additions and 7 deletions.
26 changes: 26 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10691,6 +10691,28 @@ export interface Locale extends ILocale {
* 通知の履歴をリセットする
*/
"flushNotification": string;
"_scheduleNote": {
/**
* 原因は不明です
*/
"unknown": string;
/**
* 引用元がありません
*/
"renoteTargetNotFound": string;
/**
* 対象のチャンネルがありません
*/
"channelTargetNotFound": string;
/**
* 返信先がありません
*/
"replyTargetNotFound": string;
/**
* 添付ファイルがありません
*/
"invalidFilesCount": string;
};
"_types": {
/**
* すべて
Expand Down Expand Up @@ -10748,6 +10770,10 @@ export interface Locale extends ILocale {
* 実績の獲得
*/
"achievementEarned": string;
/**
* 予約投稿に失敗
*/
"scheduleNote": string;
/**
* 連携アプリからの通知
*/
Expand Down
7 changes: 7 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2823,6 +2823,12 @@ _notification:
notedBySomeUsers: "{n}件の新しい投稿"
followedBySomeUsers: "{n}人にフォローされました"
flushNotification: "通知の履歴をリセットする"
_scheduleNote:
unknown: "原因は不明です"
renoteTargetNotFound: "引用元がありません"
channelTargetNotFound: "対象のチャンネルがありません"
replyTargetNotFound: "返信先がありません"
invalidFilesCount: "添付ファイルがありません"

_types:
all: "すべて"
Expand All @@ -2839,6 +2845,7 @@ _notification:
groupInvited: "グループに招待された"
roleAssigned: "ロールが付与された"
achievementEarned: "実績の獲得"
scheduleNote: "予約投稿に失敗"
app: "連携アプリからの通知"

_actions:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ export class NotificationEntityService implements OnModuleInit {
...(notification.type === 'achievementEarned' ? {
achievement: notification.achievement,
} : {}),
...(notification.type === 'scheduleNote' ? {
errorType: notification.errorType,
} : {}),
...(notification.type === 'app' ? {
body: notification.customBody,
header: notification.customHeader,
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/models/Notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ export type MiNotification = {
id: string;
createdAt: string;
achievement: string;
} | {
type: 'scheduleNote';
id: string;
createdAt: string;
errorType: string;
} | {
type: 'app';
id: string;
Expand Down
14 changes: 14 additions & 0 deletions packages/backend/src/models/json-schema/notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,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: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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');
}
Expand Down Expand Up @@ -72,6 +74,24 @@ 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, {
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const notificationTypes = [
'groupInvited',
'roleAssigned',
'achievementEarned',
'scheduleNote',
'app',
'test',
] as const;
Expand Down
2 changes: 1 addition & 1 deletion packages/cherrypick-js/etc/cherrypick-js.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2838,7 +2838,7 @@ type NotificationsCreateRequest = operations['notifications___create']['requestB
type NotificationsDeleteRequest = operations['notifications___delete']['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)
type OfficialTagsShowResponse = operations['official-tags___show']['responses']['200']['content']['application/json'];
Expand Down
16 changes: 12 additions & 4 deletions packages/cherrypick-js/src/autogen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4598,6 +4598,14 @@ export type components = {
/** @enum {string} */
type: 'achievementEarned';
achievement: string;
} | {
/** Format: id */
id: string;
/** Format: date-time */
createdAt: string;
/** @enum {string} */
type: 'scheduleNote';
errorType: string;
} | {
/** Format: id */
id: string;
Expand Down Expand Up @@ -18786,8 +18794,8 @@ export type operations = {
untilId?: string;
/** @default true */
markAsRead?: boolean;
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote')[];
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'scheduleNote' | 'app' | 'test' | 'pollVote')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'scheduleNote' | 'app' | 'test' | 'pollVote')[];
};
};
};
Expand Down Expand Up @@ -18854,8 +18862,8 @@ export type operations = {
untilId?: string;
/** @default true */
markAsRead?: boolean;
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote')[];
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'scheduleNote' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'scheduleNote' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote')[];
};
};
};
Expand Down
2 changes: 1 addition & 1 deletion packages/cherrypick-js/src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
12 changes: 11 additions & 1 deletion packages/frontend/src/components/MkNotification.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ti ti-plus" style="line-height: 1;"></i></div>
<div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div>
<div v-else-if="notification.type === 'note:grouped'" :class="[$style.icon, $style.icon_noteGroup]"><i class="ti ti-pencil" style="line-height: 1;"></i></div>
<div v-else-if="notification.type === 'scheduleNote'" :class="[$style.icon, $style.icon_scheduleNote]"><i class="ti ti-alert-triangle" style="line-height: 1;"></i></div>
<img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/>
<MkAvatar v-else-if="'user' in notification" :class="$style.icon" :user="notification.user" link preview/>
<img v-else-if="'icon' in notification" :class="[$style.icon, $style.icon_app]" :src="notification.icon" alt=""/>
Expand Down Expand Up @@ -59,6 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else-if="notification.type === 'note'">{{ i18n.ts._notification.newNote }}: <MkUserName :user="notification.note.user"/></span>
<span v-else-if="notification.type === 'roleAssigned'">{{ i18n.ts._notification.roleAssigned }}</span>
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
<span v-else-if="notification.type === 'scheduleNote'">{{ i18n.ts._notification._types.scheduleNote }}</span>
<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
<MkA v-else-if="notification.type === 'follow' || notification.type === 'mention' || notification.type === 'reply' || notification.type === 'renote' || notification.type === 'quote' || notification.type === 'reaction' || notification.type === 'receiveFollowRequest' || notification.type === 'followRequestAccepted'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
<span v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'">{{ i18n.tsx._notification.likedBySomeUsers({ n: getActualReactedUsersCount(notification) }) }}</span>
Expand Down Expand Up @@ -107,6 +109,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkA v-else-if="notification.type === 'achievementEarned'" :class="$style.text" to="/my/achievements">
{{ i18n.ts._achievements._types['_' + notification.achievement].title }}
</MkA>
<span v-else-if="notification.type === 'scheduleNote'" :class="$style.text">{{ i18n.ts._notification._scheduleNote[notification.errorType] }}</span>
<template v-else-if="notification.type === 'follow'">
<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}</span>
<div v-if="full"><MkFollowButton :user="notification.user" :full="true" :disableIfFollowing="defaultStore.reactiveState.showFollowingMessageInsteadOfButtonEnabled.value"/></div>
Expand Down Expand Up @@ -171,7 +174,7 @@ import { userPage } from '@/filters/user.js';
import { i18n } from '@/i18n.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { signinRequired } from '@/account.js';
import { infoImageUrl } from '@/instance.js';
import { infoImageUrl, serverErrorImageUrl } from '@/instance.js';
import { defaultStore } from '@/store.js';

const $i = signinRequired();
Expand Down Expand Up @@ -250,6 +253,7 @@ const rejectGroupInvitation = () => {
height: 100%;
}

.icon_scheduleNote,
.icon_noteGroup,
.icon_reactionGroup,
.icon_reactionGroupHeart,
Expand Down Expand Up @@ -280,6 +284,12 @@ const rejectGroupInvitation = () => {

background: var(--eventRenote);
}
.icon_scheduleNote {
width: 100%;
height: 100%;
color: var(--warn);
background: var(--eventOther);
}

.icon_app {
border-radius: 6px;
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export const notificationTypes = [
'groupInvited',
'roleAssigned',
'achievementEarned',
'scheduleNote',
'app',
'test',
'pollVote',
Expand Down
5 changes: 5 additions & 0 deletions packages/sw/src/scripts/create-notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
data,
tag: `achievement:${data.body.achievement}`,
}];
case 'scheduleNote':
return [t('_notification.scheduleNote'), {
body: data.body.errorType,
data,
}];

case 'pollEnded':
return [t('_notification.pollEnded'), {
Expand Down

0 comments on commit a0e4798

Please sign in to comment.