diff --git a/lib/hooks/thread-hooks.js b/lib/hooks/thread-hooks.js --- a/lib/hooks/thread-hooks.js +++ b/lib/hooks/thread-hooks.js @@ -23,7 +23,10 @@ useUpdateFarcasterGroupNameAndDescription, useModifyFarcasterMembershipInput, } from '../shared/farcaster/farcaster-api.js'; -import { useFetchConversation } from '../shared/farcaster/farcaster-hooks.js'; +import { + useFetchConversation, + useRefreshFarcasterConversation, +} from '../shared/farcaster/farcaster-hooks.js'; import { threadSpecs } from '../shared/threads/thread-specs.js'; import { permissionsAndAuthRelatedRequestTimeout } from '../shared/timeouts.js'; import type { @@ -314,6 +317,9 @@ ) => Promise { const keyserverCall = useKeyserverCall(removeUsersFromThread); const dispatchActionPromise = useDispatchActionPromise(); + const modifyFarcasterMembership = useModifyFarcasterMembershipInput(); + const fetchConversation = useRefreshFarcasterConversation(); + const auxUserStore = useSelector(state => state.auxUserStore); return React.useCallback( async (input: RemoveUsersFromThreadInput) => { const { threadInfo, memberIDs } = input; @@ -338,10 +344,19 @@ { keyserverCall, dispatchActionPromise, + modifyFarcasterMembership, + fetchConversation, + auxUserStore, }, ); }, - [dispatchActionPromise, keyserverCall], + [ + dispatchActionPromise, + keyserverCall, + modifyFarcasterMembership, + fetchConversation, + auxUserStore, + ], ); } diff --git a/lib/shared/threads/protocols/farcaster-thread-protocol.js b/lib/shared/threads/protocols/farcaster-thread-protocol.js --- a/lib/shared/threads/protocols/farcaster-thread-protocol.js +++ b/lib/shared/threads/protocols/farcaster-thread-protocol.js @@ -3,7 +3,10 @@ import invariant from 'invariant'; import { fetchMessagesBeforeCursorActionTypes } from '../../../actions/message-actions.js'; -import { changeThreadMemberRolesActionTypes } from '../../../actions/thread-action-types.js'; +import { + changeThreadMemberRolesActionTypes, + removeUsersFromThreadActionTypes, +} from '../../../actions/thread-action-types.js'; import { getFarcasterRolePermissionsBlobs } from '../../../permissions/farcaster-permissions.js'; import type { RolePermissionBlobs } from '../../../permissions/thread-permissions.js'; import type { SetThreadUnreadStatusPayload } from '../../../types/activity-types.js'; @@ -80,6 +83,8 @@ CreateRealThreadParameters, ProtocolChangeThreadMemberRolesInput, ChangeThreadMemberRolesUtils, + ProtocolRemoveUsersFromThreadInput, + RemoveUsersFromThreadUtils, } from '../thread-spec.js'; const farcasterThreadProtocol: ThreadProtocol = { @@ -343,6 +348,60 @@ return await promise; }, + removeUsersFromThread: async ( + input: ProtocolRemoveUsersFromThreadInput, + utils: RemoveUsersFromThreadUtils, + ): Promise => { + const { threadInfo, memberIDs, customKeyName } = input; + const { + modifyFarcasterMembership, + fetchConversation, + dispatchActionPromise, + auxUserStore, + } = utils; + + const conversationId = conversationIDFromFarcasterThreadID(threadInfo.id); + + const promise = (async () => { + const membershipPromises = memberIDs + .map(memberID => { + const targetFid = + auxUserStore.auxUserInfos[memberID]?.fid ?? + extractFIDFromUserID(memberID); + if (targetFid) { + const modifyFarcasterMembershipInput: ModifyFarcasterMembershipInput = + { + conversationId, + action: 'remove', + targetFid: parseInt(targetFid, 10), + }; + + return modifyFarcasterMembership(modifyFarcasterMembershipInput); + } + return null; + }) + .filter(Boolean); + + await Promise.all(membershipPromises); + + await fetchConversation(conversationId); + + return { + threadID: threadInfo.id, + updatesResult: { newUpdates: [] }, + newMessageInfos: [], + }; + })(); + + void dispatchActionPromise( + removeUsersFromThreadActionTypes, + promise, + customKeyName ? { customKeyName } : undefined, + ); + + return await promise; + }, + updateSubscription: async ( protocolInput: ProtocolUpdateSubscriptionInput, utils: UpdateSubscriptionUtils, diff --git a/lib/shared/threads/thread-spec.js b/lib/shared/threads/thread-spec.js --- a/lib/shared/threads/thread-spec.js +++ b/lib/shared/threads/thread-spec.js @@ -263,6 +263,11 @@ export type RemoveUsersFromThreadUtils = { +keyserverCall: RemoveUsersFromThreadInput => Promise, +dispatchActionPromise: DispatchActionPromise, + +modifyFarcasterMembership: ( + input: ModifyFarcasterMembershipInput, + ) => Promise, + +fetchConversation: (conversationId: string) => Promise, + +auxUserStore: AuxUserStore, }; export type ProtocolUpdateSubscriptionInput = { diff --git a/lib/utils/convert-farcaster-message-to-comm-messages.js b/lib/utils/convert-farcaster-message-to-comm-messages.js --- a/lib/utils/convert-farcaster-message-to-comm-messages.js +++ b/lib/utils/convert-farcaster-message-to-comm-messages.js @@ -98,6 +98,27 @@ ]; } + if ( + farcasterMessage.type === 'group_membership_removal' && + farcasterMessage.actionTargetUserContext?.fid + ) { + const removedUserFID = + farcasterMessage.actionTargetUserContext.fid.toString(); + const removedUserID = + fcUserInfos?.get(removedUserFID)?.userID ?? userIDFromFID(removedUserFID); + + return [ + { + type: messageTypes.REMOVE_MEMBERS, + id: farcasterMessage.messageId, + threadID, + creatorID, + time: parseInt(farcasterMessage.serverTimestamp, 10), + removedUserIDs: [removedUserID], + }, + ]; + } + console.log( 'UNSUPPORTED FARCASTER MESSAGE', JSON.stringify(farcasterMessage, null, 2),