feat: Undo Accept (#7980)
* allow breaking of follow * send undo * delete by using reject follow
This commit is contained in:
parent
a82ff360c6
commit
f33ded3107
6 changed files with 136 additions and 1 deletions
|
@ -792,6 +792,7 @@ pubSub: "Pub/Subのアカウント"
|
||||||
lastCommunication: "直近の通信"
|
lastCommunication: "直近の通信"
|
||||||
resolved: "解決済み"
|
resolved: "解決済み"
|
||||||
unresolved: "未解決"
|
unresolved: "未解決"
|
||||||
|
breakFollow: "フォロワーを解除"
|
||||||
itsOn: "オンになっています"
|
itsOn: "オンになっています"
|
||||||
itsOff: "オフになっています"
|
itsOff: "オフになっています"
|
||||||
emailRequiredForSignup: "アカウント登録にメールアドレスを必須にする"
|
emailRequiredForSignup: "アカウント登録にメールアドレスを必須にする"
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import unfollow from '@/services/following/delete';
|
||||||
|
import cancelRequest from '@/services/following/requests/cancel';
|
||||||
|
import {IAccept} from '../../type';
|
||||||
|
import { IRemoteUser } from '@/models/entities/user';
|
||||||
|
import { Followings } from '@/models/index';
|
||||||
|
import DbResolver from '../../db-resolver';
|
||||||
|
|
||||||
|
export default async (actor: IRemoteUser, activity: IAccept): Promise<string> => {
|
||||||
|
const dbResolver = new DbResolver();
|
||||||
|
|
||||||
|
const follower = await dbResolver.getUserFromApId(activity.object);
|
||||||
|
if (follower == null) {
|
||||||
|
return `skip: follower not found`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const following = await Followings.findOne({
|
||||||
|
followerId: follower.id,
|
||||||
|
followeeId: actor.id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (following) {
|
||||||
|
await unfollow(follower, actor);
|
||||||
|
return `ok: unfollowed`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `skip: フォローされていない`;
|
||||||
|
};
|
|
@ -1,8 +1,9 @@
|
||||||
import { IRemoteUser } from '@/models/entities/user';
|
import { IRemoteUser } from '@/models/entities/user';
|
||||||
import { IUndo, isFollow, isBlock, isLike, isAnnounce, getApType } from '../../type';
|
import {IUndo, isFollow, isBlock, isLike, isAnnounce, getApType, isAccept} from '../../type';
|
||||||
import unfollow from './follow';
|
import unfollow from './follow';
|
||||||
import unblock from './block';
|
import unblock from './block';
|
||||||
import undoLike from './like';
|
import undoLike from './like';
|
||||||
|
import undoAccept from './accept';
|
||||||
import { undoAnnounce } from './announce';
|
import { undoAnnounce } from './announce';
|
||||||
import Resolver from '../../resolver';
|
import Resolver from '../../resolver';
|
||||||
import { apLogger } from '../../logger';
|
import { apLogger } from '../../logger';
|
||||||
|
@ -29,6 +30,7 @@ export default async (actor: IRemoteUser, activity: IUndo): Promise<string> => {
|
||||||
if (isBlock(object)) return await unblock(actor, object);
|
if (isBlock(object)) return await unblock(actor, object);
|
||||||
if (isLike(object)) return await undoLike(actor, object);
|
if (isLike(object)) return await undoLike(actor, object);
|
||||||
if (isAnnounce(object)) return await undoAnnounce(actor, object);
|
if (isAnnounce(object)) return await undoAnnounce(actor, object);
|
||||||
|
if (isAccept(object)) return await undoAccept(actor, object);
|
||||||
|
|
||||||
return `skip: unknown object type ${getApType(object)}`;
|
return `skip: unknown object type ${getApType(object)}`;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
import $ from 'cafy';
|
||||||
|
import { ID } from '@/misc/cafy-id';
|
||||||
|
import * as ms from 'ms';
|
||||||
|
import deleteFollowing from '@/services/following/delete';
|
||||||
|
import define from '../../define';
|
||||||
|
import { ApiError } from '../../error';
|
||||||
|
import { getUser } from '../../common/getters';
|
||||||
|
import { Followings, Users } from '@/models/index';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['following', 'users'],
|
||||||
|
|
||||||
|
limit: {
|
||||||
|
duration: ms('1hour'),
|
||||||
|
max: 100
|
||||||
|
},
|
||||||
|
|
||||||
|
requireCredential: true as const,
|
||||||
|
|
||||||
|
kind: 'write:following',
|
||||||
|
|
||||||
|
params: {
|
||||||
|
userId: {
|
||||||
|
validator: $.type(ID),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
noSuchUser: {
|
||||||
|
message: 'No such user.',
|
||||||
|
code: 'NO_SUCH_USER',
|
||||||
|
id: '5b12c78d-2b28-4dca-99d2-f56139b42ff8'
|
||||||
|
},
|
||||||
|
|
||||||
|
followerIsYourself: {
|
||||||
|
message: 'Follower is yourself.',
|
||||||
|
code: 'FOLLOWER_IS_YOURSELF',
|
||||||
|
id: '07dc03b9-03da-422d-885b-438313707662'
|
||||||
|
},
|
||||||
|
|
||||||
|
notFollowing: {
|
||||||
|
message: 'The other use is not following you.',
|
||||||
|
code: 'NOT_FOLLOWING',
|
||||||
|
id: '5dbf82f5-c92b-40b1-87d1-6c8c0741fd09'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
res: {
|
||||||
|
type: 'object' as const,
|
||||||
|
optional: false as const, nullable: false as const,
|
||||||
|
ref: 'User'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default define(meta, async (ps, user) => {
|
||||||
|
const followee = user;
|
||||||
|
|
||||||
|
// Check if the follower is yourself
|
||||||
|
if (user.id === ps.userId) {
|
||||||
|
throw new ApiError(meta.errors.followerIsYourself);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get follower
|
||||||
|
const follower = await getUser(ps.userId).catch(e => {
|
||||||
|
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check not following
|
||||||
|
const exist = await Followings.findOne({
|
||||||
|
followerId: follower.id,
|
||||||
|
followeeId: followee.id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exist == null) {
|
||||||
|
throw new ApiError(meta.errors.notFollowing);
|
||||||
|
}
|
||||||
|
|
||||||
|
await deleteFollowing(follower, followee);
|
||||||
|
|
||||||
|
return await Users.pack(followee.id, user);
|
||||||
|
});
|
|
@ -2,6 +2,7 @@ import { publishMainStream, publishUserEvent } from '@/services/stream';
|
||||||
import { renderActivity } from '@/remote/activitypub/renderer/index';
|
import { renderActivity } from '@/remote/activitypub/renderer/index';
|
||||||
import renderFollow from '@/remote/activitypub/renderer/follow';
|
import renderFollow from '@/remote/activitypub/renderer/follow';
|
||||||
import renderUndo from '@/remote/activitypub/renderer/undo';
|
import renderUndo from '@/remote/activitypub/renderer/undo';
|
||||||
|
import renderReject from '@/remote/activitypub/renderer/reject';
|
||||||
import { deliver } from '@/queue/index';
|
import { deliver } from '@/queue/index';
|
||||||
import Logger from '../logger';
|
import Logger from '../logger';
|
||||||
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
|
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
|
||||||
|
@ -40,6 +41,12 @@ export default async function(follower: { id: User['id']; host: User['host']; ur
|
||||||
const content = renderActivity(renderUndo(renderFollow(follower, followee), follower));
|
const content = renderActivity(renderUndo(renderFollow(follower, followee), follower));
|
||||||
deliver(follower, content, followee.inbox);
|
deliver(follower, content, followee.inbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Users.isLocalUser(followee) && Users.isRemoteUser(follower)) {
|
||||||
|
// local user has null host
|
||||||
|
const content = renderActivity(renderReject(renderFollow(follower, followee), followee));
|
||||||
|
deliver(followee, content, follower.inbox);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function decrementFollowing(follower: { id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }) {
|
export async function decrementFollowing(follower: { id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }) {
|
||||||
|
|
|
@ -109,6 +109,14 @@ export function getUserMenu(user) {
|
||||||
return !confirm.canceled;
|
return !confirm.canceled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function invalidateFollow() {
|
||||||
|
os.apiWithDialog('following/invalidate', {
|
||||||
|
userId: user.id
|
||||||
|
}).then(() => {
|
||||||
|
user.isFollowed = !user.isFollowed;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let menu = [{
|
let menu = [{
|
||||||
icon: 'fas fa-at',
|
icon: 'fas fa-at',
|
||||||
text: i18n.locale.copyUsername,
|
text: i18n.locale.copyUsername,
|
||||||
|
@ -153,6 +161,14 @@ export function getUserMenu(user) {
|
||||||
action: toggleBlock
|
action: toggleBlock
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
if (user.isFollowed) {
|
||||||
|
menu = menu.concat([{
|
||||||
|
icon: 'fas fa-unlink',
|
||||||
|
text: i18n.locale.breakFollow,
|
||||||
|
action: invalidateFollow
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
|
||||||
menu = menu.concat([null, {
|
menu = menu.concat([null, {
|
||||||
icon: 'fas fa-exclamation-circle',
|
icon: 'fas fa-exclamation-circle',
|
||||||
text: i18n.locale.reportAbuse,
|
text: i18n.locale.reportAbuse,
|
||||||
|
|
Loading…
Reference in a new issue