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: "直近の通信" | ||||
| resolved: "解決済み" | ||||
| unresolved: "未解決" | ||||
| breakFollow: "フォロワーを解除" | ||||
| itsOn: "オンになっています" | ||||
| itsOff: "オフになっています" | ||||
| 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 { IUndo, isFollow, isBlock, isLike, isAnnounce, getApType } from '../../type'; | ||||
| import {IUndo, isFollow, isBlock, isLike, isAnnounce, getApType, isAccept} from '../../type'; | ||||
| import unfollow from './follow'; | ||||
| import unblock from './block'; | ||||
| import undoLike from './like'; | ||||
| import undoAccept from './accept'; | ||||
| import { undoAnnounce } from './announce'; | ||||
| import Resolver from '../../resolver'; | ||||
| 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 (isLike(object)) return await undoLike(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)}`; | ||||
| }; | ||||
|  |  | |||
|  | @ -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 renderFollow from '@/remote/activitypub/renderer/follow'; | ||||
| import renderUndo from '@/remote/activitypub/renderer/undo'; | ||||
| import renderReject from '@/remote/activitypub/renderer/reject'; | ||||
| import { deliver } from '@/queue/index'; | ||||
| import Logger from '../logger'; | ||||
| 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)); | ||||
| 		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']; }) { | ||||
|  |  | |||
|  | @ -109,6 +109,14 @@ export function getUserMenu(user) { | |||
| 		return !confirm.canceled; | ||||
| 	} | ||||
| 
 | ||||
| 	async function invalidateFollow() { | ||||
| 		os.apiWithDialog('following/invalidate', { | ||||
| 			userId: user.id | ||||
| 		}).then(() => { | ||||
| 			user.isFollowed = !user.isFollowed; | ||||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	let menu = [{ | ||||
| 		icon: 'fas fa-at', | ||||
| 		text: i18n.locale.copyUsername, | ||||
|  | @ -153,6 +161,14 @@ export function getUserMenu(user) { | |||
| 			action: toggleBlock | ||||
| 		}]); | ||||
| 
 | ||||
| 		if (user.isFollowed) { | ||||
| 			menu = menu.concat([{ | ||||
| 				icon: 'fas fa-unlink', | ||||
| 				text: i18n.locale.breakFollow, | ||||
| 				action: invalidateFollow | ||||
| 			}]); | ||||
| 		} | ||||
| 
 | ||||
| 		menu = menu.concat([null, { | ||||
| 			icon: 'fas fa-exclamation-circle', | ||||
| 			text: i18n.locale.reportAbuse, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue