import type { PersonalGateway } from "./gateway.ts"; import { MalformedRequest, inboxWindowOf, optionalLoopbackTypingURL, optionalLoopbackWithdrawalsURL, parseCredentialAnswers, parseMemberExternalIDs, parseNewChannel, parsePersonRequest, requireConversation, requireExternalID, requireLargestBytes, requireMediaRange, requireMediaSource, requireMediaURL, requireMessage, requireLoopbackArrivalsURL, requireName, requireQuery, requireReadAt, } from "./parse.ts"; // The agent's own account on the messenger, when the platform knows it. A // direct conversation with the agent is a conversation with the product, and // only the product knows which account that is. export type PersonCapability = ( gateway: PersonalGateway, requestBody: unknown, agentExternalID?: string, ) => Promise; export const personCapabilities: Record = { "person.credential.requirement": async (gateway) => gateway.credentialRequirement(), "person.credential.issue": async (gateway, body) => await gateway.issueCredential(parseCredentialAnswers(body)), "person.identity": async (gateway, body) => { const request = parsePersonRequest(body); return await gateway.identity(request.actor); }, "person.conversations.list": async (gateway, body, agentExternalID) => { const request = parsePersonRequest(body); const conversations = await gateway.listConversations(request.actor); return { agentExternalID, conversations: conversations.map((conversation) => ({ ...conversation, isWithTheAgent: Boolean( agentExternalID && (conversation.participantExternalIDs ?? []).includes(agentExternalID), ), })), }; }, "person.people.list": async (gateway, body, agentExternalID) => { const request = parsePersonRequest(body); return { agentExternalID, people: await gateway.listPeople(request.actor) }; }, "person.dm.ensure": async (gateway, body) => { const request = parsePersonRequest(body); return await gateway.ensureDirectConversation(request.actor, request.counterpartExternalIDs); }, "person.dm.hide": async (gateway, body) => { const request = parsePersonRequest(body); await gateway.hideDirectConversation(request.actor, requireConversation(request)); return {}; }, "person.dm.reveal": async (gateway, body) => { const request = parsePersonRequest(body); await gateway.revealDirectConversation(request.actor, requireConversation(request)); return {}; }, "person.channel.create": async (gateway, body) => { const request = parsePersonRequest(body); return await gateway.createChannel(request.actor, parseNewChannel(body)); }, "person.channels.open.list": async (gateway, body) => { const request = parsePersonRequest(body); return { channels: await gateway.listOpenChannels(request.actor) }; }, "person.channel.join": async (gateway, body) => { const request = parsePersonRequest(body); await gateway.joinChannel(request.actor, requireConversation(request)); return {}; }, "person.channel.members.add": async (gateway, body) => { const request = parsePersonRequest(body); const members = parseMemberExternalIDs(body); if (members.length === 0) throw new MalformedRequest("memberExternalIDs must name someone to add"); return await gateway.addChannelMembers(request.actor, requireConversation(request), members); }, "person.channel.member.remove": async (gateway, body) => { const request = parsePersonRequest(body); await gateway.removeChannelMember(request.actor, requireConversation(request), requireExternalID(request)); return {}; }, "person.channel.leave": async (gateway, body) => { const request = parsePersonRequest(body); await gateway.leaveChannel(request.actor, requireConversation(request)); return {}; }, "person.channel.owner.set": async (gateway, body) => { const request = parsePersonRequest(body); await gateway.handOverChannel(request.actor, requireConversation(request), requireExternalID(request)); return {}; }, "person.channel.owner.add": async (gateway, body) => { const request = parsePersonRequest(body); return gateway.addChannelOwner(request.actor, requireConversation(request), requireExternalID(request)); }, "person.channel.delete": async (gateway, body) => { const request = parsePersonRequest(body); await gateway.deleteChannel(request.actor, requireConversation(request)); return {}; }, "person.read_state.mark": async (gateway, body) => { const request = parsePersonRequest(body); await gateway.markConversationRead( request.actor, requireConversation(request), requireReadAt(request), request.parentID, ); return {}; }, "person.inbox.list": async (gateway, body, agentExternalID) => { const request = parsePersonRequest(body); return { agentExternalID, ...(await gateway.listInbox(request.actor, inboxWindowOf(request))) }; }, "person.messages.list": async (gateway, body) => { const request = parsePersonRequest(body); return await gateway.listMessages(request.actor, requireConversation(request), request.before); }, "person.messages.search": async (gateway, body) => { const request = parsePersonRequest(body); return { matches: await gateway.searchMessages(request.actor, requireConversation(request), requireQuery(request)), }; }, "person.message.send": async (gateway, body) => { const request = parsePersonRequest(body); return await gateway.sendMessage( request.actor, requireConversation(request), request.body ?? "", request.parentID, request.attachments, request.mentions, request.timeZone, ); }, "person.message.edit": async (gateway, body) => { const request = parsePersonRequest(body); return await gateway.editMessage( request.actor, requireConversation(request), requireMessage(request), request.body ?? "", request.mentions, ); }, "person.message.delete": async (gateway, body) => { const request = parsePersonRequest(body); await gateway.deleteMessage(request.actor, requireConversation(request), requireMessage(request)); return {}; }, "person.reaction.add": async (gateway, body) => { const request = parsePersonRequest(body); await gateway.addReaction( request.actor, requireConversation(request), requireMessage(request), requireEmoji(request.emoji), ); return {}; }, "person.reaction.remove": async (gateway, body) => { const request = parsePersonRequest(body); await gateway.removeReaction( request.actor, requireConversation(request), requireMessage(request), requireEmoji(request.emoji), ); return {}; }, "person.emoji.list": async (gateway, body) => { const request = parsePersonRequest(body); return { emoji: await gateway.listCustomEmoji(request.actor) }; }, "person.emoji.image": async (gateway, body) => { const request = parsePersonRequest(body); return { image: await gateway.readCustomEmojiImage( request.actor, requireName(request), requireLargestBytes(request), ), }; }, "person.picture": async (gateway, body) => { const request = parsePersonRequest(body); return { image: await gateway.readProfilePicture( request.actor, requireExternalID(request), requireLargestBytes(request), ), }; }, "person.arrivals.watch": async (gateway, body) => { const request = parsePersonRequest(body); await gateway.watchArrivals( request.actor, requireLoopbackArrivalsURL(request), optionalLoopbackTypingURL(request), optionalLoopbackWithdrawalsURL(request), ); return {}; }, "person.typing.send": async (gateway, body) => { const request = parsePersonRequest(body); await gateway.announceTyping(request.actor, requireConversation(request)); return {}; }, "person.media.read": async (gateway, body) => { const request = parsePersonRequest(body); return await gateway.readMedia(request.actor, requireMediaURL(request), requireMediaRange(request)); }, "person.media.upload": async (gateway, body) => { const request = parsePersonRequest(body); return { media: await gateway.uploadMedia(request.actor, requireMediaSource(request)) }; }, }; function requireEmoji(emoji: string | undefined): string { if (!emoji) throw new MalformedRequest("missing required field emoji"); return emoji; }