Merge pull request #35 from lqvp/master

フォロー解除時にも通知が来るように
This commit is contained in:
ひたりん 2024-10-01 05:46:05 +09:00 committed by hijiki
parent 10be4267b1
commit 796492e3bf
14 changed files with 122 additions and 2 deletions

View File

@ -1,4 +1,5 @@
# DIFFRENCE
<<<<<<< HEAD
## 2024.9.0-yami-1.3.1
## Client
- フォロー/フォロワー/アナウンス/みつける/Play/ギャラリー/チャンネル/TL/ユーザー/ノートのページをログイン必須に
@ -28,6 +29,8 @@
### Server
- `notes/show`, `users/notes`の認証を不要に(revert?)
=======
>>>>>>> 12828a4aa2 (Merge pull request #35 from lqvp/master)
## 2024.9.0-yami-1.2.5
### Feat
- フォロー解除時にも通知するように

View File

@ -68,6 +68,7 @@ loadMore: "Load more"
showMore: "Show more"
showLess: "Close"
youGotNewFollower: "followed you"
youGotUnFollower: "unfollowed you"
receiveFollowRequest: "Follow request received"
followRequestAccepted: "Follow request accepted"
mention: "Mention"
@ -1884,6 +1885,8 @@ _gallery:
_email:
_follow:
title: "You've got a new follower"
_unfollow:
title: "You have been unfollowed"
_receiveFollowRequest:
title: "You've received a follow request"
_plugin:
@ -2440,6 +2443,7 @@ _notification:
youGotQuote: "{name} quoted you"
youRenoted: "Boost from {name}"
youWereFollowed: "followed you"
youWereUnFollower: "unfollowed you"
youReceivedFollowRequest: "You've received a follow request"
yourFollowRequestAccepted: "Your follow request was accepted"
pollEnded: "Poll results have become available"
@ -2456,12 +2460,14 @@ _notification:
likedBySomeUsers: "{n} users liked your note"
renotedBySomeUsers: "Boosted by {n} users"
followedBySomeUsers: "Followed by {n} users"
unfollowerBySomeUsers: "UnFollowed by {n} users"
flushNotification: "Clear notifications"
edited: "Note got edited"
_types:
all: "All"
note: "New notes"
follow: "New followers"
unfollow: "Unfollowers"
mention: "Mentions"
reply: "Replies"
renote: "Boosts"

22
locales/index.d.ts vendored
View File

@ -288,6 +288,10 @@ export interface Locale extends ILocale {
*
*/
"youGotNewFollower": string;
/**
*
*/
"youGotUnFollower": string;
/**
*
*/
@ -7340,6 +7344,12 @@ export interface Locale extends ILocale {
*/
"title": string;
};
"_unfollow" : {
/**
*
*/
"title": string;
}
"_receiveFollowRequest": {
/**
*
@ -9480,6 +9490,10 @@ export interface Locale extends ILocale {
*
*/
"youWereFollowed": string;
/**
*
*/
"youWereUnFollower": string;
/**
*
*/
@ -9548,6 +9562,10 @@ export interface Locale extends ILocale {
* {n}
*/
"followedBySomeUsers": ParameterizedString<"n">;
/**
* {n}
*/
"unfollowerBySomeUser": ParameterizedString<"n">;
/**
*
*/
@ -9565,6 +9583,10 @@ export interface Locale extends ILocale {
*
*/
"follow": string;
/**
*
*/
"unfollow": string;
/**
*
*/

View File

@ -68,6 +68,7 @@ loadMore: "もっと見る"
showMore: "もっと見る"
showLess: "閉じる"
youGotNewFollower: "フォローされました"
youGotUnFollower: "フォロー解除されました"
receiveFollowRequest: "フォローリクエストされました"
followRequestAccepted: "フォローが承認されました"
mention: "メンション"
@ -1907,6 +1908,8 @@ _gallery:
_email:
_follow:
title: "フォローされました"
_unfollow:
title: "フォロー解除されました"
_receiveFollowRequest:
title: "フォローリクエストを受け取りました"
@ -2501,6 +2504,7 @@ _notification:
youGotQuote: "{name}による引用"
youRenoted: "{name}がBoostしました"
youWereFollowed: "フォローされました"
youWereUnFollower: "フォロー解除されました"
youReceivedFollowRequest: "フォローリクエストが来ました"
yourFollowRequestAccepted: "フォローリクエストが承認されました"
pollEnded: "アンケートの結果が出ました"
@ -2518,12 +2522,14 @@ _notification:
likedBySomeUsers: "{n}人がいいねしました"
renotedBySomeUsers: "{n}人がリノートしました"
followedBySomeUsers: "{n}人にフォローされました"
unfollowerBySomeUsers: "{n}人にフォロー解除されました"
flushNotification: "通知の履歴をリセットする"
_types:
all: "すべて"
note: "ユーザーの新規投稿"
follow: "フォロー"
unfollow: "フォロー解除"
mention: "メンション"
reply: "リプライ"
renote: "Boost"

View File

@ -66,6 +66,7 @@ loadMore: "まだまだあるで!"
showMore: "まだまだあるで!"
showLess: "さいなら"
youGotNewFollower: "フォローされたで"
youGotUnFollower: "フォロー解除されたで"
receiveFollowRequest: "フォローリクエストされたで"
followRequestAccepted: "フォローが承認されたで"
mention: "メンション"
@ -1806,6 +1807,8 @@ _gallery:
_email:
_follow:
title: "フォローされたで"
_unfollow:
title: "フォロー解除されたで"
_receiveFollowRequest:
title: "フォローリクエストを受け取ったで"
_plugin:
@ -2349,6 +2352,7 @@ _notification:
youGotQuote: "{name}による引用"
youRenoted: "{name}がブーストしたみたいやで"
youWereFollowed: "フォローされたで"
youWereUnFollower: "フォロー解除されたで"
youReceivedFollowRequest: "フォロー許可してほしいみたいやな"
yourFollowRequestAccepted: "フォローさせてもろたで"
pollEnded: "アンケートの結果が出たみたいや"
@ -2365,11 +2369,13 @@ _notification:
likedBySomeUsers: "{n}人がいいねしたで"
renotedBySomeUsers: "{n}人がブーストしたで"
followedBySomeUsers: "{n}人にフォローされたで"
unfollowerBySomeUser: "{n}人にフォロー解除されたで"
flushNotification: "通知の履歴をリセットする"
_types:
all: "すべて"
note: "あんたらの新規投稿"
follow: "フォロー"
unfollow: "フォロー解除"
mention: "メンション"
reply: "リプライ"
renote: "Renote"

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

View File

@ -179,6 +179,7 @@ export class NotificationService implements OnApplicationShutdown {
this.pushNotificationService.pushNotification(notifieeId, 'notification', packed);
if (type === 'follow') this.emailNotificationFollow(notifieeId, await this.usersRepository.findOneByOrFail({ id: notifierId! }));
if (type === 'unfollow') this.emailNotificationUnFollow(notifieeId, await this.usersRepository.findOneByOrFail({ id: notifierId! }));
if (type === 'receiveFollowRequest') this.emailNotificationReceiveFollowRequest(notifieeId, await this.usersRepository.findOneByOrFail({ id: notifierId! }));
}, () => { /* aborted, ignore it */ });
@ -202,6 +203,18 @@ export class NotificationService implements OnApplicationShutdown {
*/
}
@bindThis
private async emailNotificationUnFollow(userId: MiUser['id'], follower: MiUser) {
/*
const userProfile = await UserProfiles.findOneByOrFail({ userId: userId });
if (!userProfile.email || !userProfile.emailNotificationTypes.includes('follow')) return;
const locale = locales[userProfile.lang ?? 'ja-JP'];
const i18n = new I18n(locale);
// TODO: render user information html
sendEmail(userProfile.email, i18n.t('_email._unfollow.title'), `${follower.name} (@${Acct.toString(follower)})`, `${follower.name} (@${Acct.toString(follower)})`);
*/
}
@bindThis
private async emailNotificationReceiveFollowRequest(userId: MiUser['id'], follower: MiUser) {
/*

View File

@ -431,6 +431,24 @@ export class UserFollowingService implements OnModuleInit {
const content = this.apRendererService.addContext(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower as MiPartialRemoteUser, followee as MiPartialLocalUser), followee));
this.queueService.deliver(followee, content, follower.inbox, false);
}
// UnFollow
if (this.userEntityService.isLocalUser(followee)) {
this.userEntityService.pack(follower.id, followee).then(async packed => {
this.globalEventService.publishMainStream(followee.id, 'unfollow', packed);
const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === followee.id && x.on.includes('unfollow'));
for (const webhook of webhooks) {
this.queueService.userWebhookDeliver(webhook, 'unfollow', {
user: packed,
});
}
});
// 通知を作成
this.notificationService.createNotification(followee.id, 'unfollow', {
}, follower.id);
}
}
@bindThis

View File

@ -19,6 +19,11 @@ export type MiNotification = {
id: string;
createdAt: string;
notifierId: MiUser['id'];
} | {
type: 'unfollow';
id: string;
createdAt: string;
notifierId: MiUser['id'];
} | {
type: 'mention';
id: string;

View File

@ -6,6 +6,7 @@
/**
* note - 稿
* follow -
* unfollow -
* mention - 稿
* reply - 稿
* renote - 稿Renoteされた
@ -22,6 +23,7 @@
export const notificationTypes = [
'note',
'follow',
'unfollow',
'mention',
'reply',
'renote',

View File

@ -17,6 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div
:class="[$style.subIcon, {
[$style.t_follow]: notification.type === 'follow',
[$style.t_unfollow]: notification.type === 'unfollow',
[$style.t_followRequestAccepted]: notification.type === 'followRequestAccepted',
[$style.t_receiveFollowRequest]: notification.type === 'receiveFollowRequest',
[$style.t_renote]: notification.type === 'renote',
@ -30,6 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
}]"
> <!-- we re-use t_pollEnded for "edited" instead of making an identical style -->
<i v-if="notification.type === 'follow'" class="ti ti-plus"></i>
<i v-else-if="notification.type === 'unfollow'" class="ti ti-minus"></i>
<i v-else-if="notification.type === 'receiveFollowRequest'" class="ti ti-clock"></i>
<i v-else-if="notification.type === 'followRequestAccepted'" class="ti ti-check"></i>
<i v-else-if="notification.type === 'renote'" class="ti ti-repeat"></i>
@ -60,7 +62,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<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 === '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 === 'exportCompleted'">{{ i18n.tsx._notification.exportOfXCompleted({ x: exportEntityName[notification.exportedEntity] }) }}</span>
<MkA v-else-if="notification.type === 'follow' || notification.type === 'unfollow' || 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>
<span v-else-if="notification.type === 'reaction:grouped'">{{ i18n.tsx._notification.reactedBySomeUsers({ n: getActualReactedUsersCount(notification) }) }}</span>
<span v-else-if="notification.type === 'renote:grouped'">{{ i18n.tsx._notification.renotedBySomeUsers({ n: notification.users.length }) }}</span>
@ -106,6 +109,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}</span>
</template>
<span v-else-if="notification.type === 'followRequestAccepted'" :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</span>
<template v-else-if="notification.type === 'unfollow'">
<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotUnFollower }}</span>
<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div>
</template>
<template v-else-if="notification.type === 'receiveFollowRequest'">
<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.receiveFollowRequest }}</span>
<div v-if="full && !followRequestDone" :class="$style.followRequestCommands">
@ -200,6 +207,7 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
overflow-wrap: break-word;
display: flex;
contain: content;
--eventUnFollow: #f08080;
}
.head {
@ -272,6 +280,12 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
pointer-events: none;
}
.t_unfollow {
padding: 3px;
background: var(--eventUnFollow);
pointer-events: none;
}
.t_renote {
padding: 3px;
background: var(--eventRenote);

View File

@ -108,6 +108,7 @@ https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers
export const notificationTypes = [
'note',
'follow',
'unfollow',
'mention',
'reply',
'renote',

View File

@ -36,6 +36,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="emailNotification_follow">
{{ i18n.ts._notification._types.follow }}
</MkSwitch>
<MkSwitch v-model="emailNotification_unfollow">
{{ i18n.ts._notification._types.unfollow }}
</MkSwitch>
<MkSwitch v-model="emailNotification_receiveFollowRequest">
{{ i18n.ts._notification._types.receiveFollowRequest }}
</MkSwitch>
@ -85,6 +88,7 @@ const emailNotification_mention = ref($i.emailNotificationTypes.includes('mentio
const emailNotification_reply = ref($i.emailNotificationTypes.includes('reply'));
const emailNotification_quote = ref($i.emailNotificationTypes.includes('quote'));
const emailNotification_follow = ref($i.emailNotificationTypes.includes('follow'));
const emailNotification_unfollow = ref($i.emailNotificationTypes.includes('unfollow'))
const emailNotification_receiveFollowRequest = ref($i.emailNotificationTypes.includes('receiveFollowRequest'));
const saveNotificationSettings = () => {
@ -94,12 +98,13 @@ const saveNotificationSettings = () => {
...[emailNotification_reply.value ? 'reply' : null],
...[emailNotification_quote.value ? 'quote' : null],
...[emailNotification_follow.value ? 'follow' : null],
...[emailNotification_unfollow.value ? 'unfollow' : null],
...[emailNotification_receiveFollowRequest.value ? 'receiveFollowRequest' : null],
].filter(x => x != null),
});
};
watch([emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest], () => {
watch([emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_unfollow,emailNotification_receiveFollowRequest], () => {
saveNotificationSettings();
});

View File

@ -72,6 +72,25 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
}];
}
case 'unfollow': {
// フォローが外されたときの処理
const account = await getAccountFromId(data.userId);
if (!account) return null;
const userDetail = await cli.request('users/show', { userId: data.body.userId }, account.token);
return [i18n.ts._notification.youWereUnFollower, {
body: getUserName(data.body.user),
icon: data.body.user.avatarUrl ?? undefined,
badge: iconUrl('user-minus'),
data,
actions: userDetail.isFollowing ? [] : [
{
action: 'follow',
title: i18n.ts._notification._actions.followBack,
},
],
}];
}
case 'mention':
return [t('_notification.youGotMention', { name: getUserName(data.body.user) }), {
body: data.body.note.text ?? '',