Skip to main content
Glama
deso-protocol

DeSo MCP Server

Official
conversations.service.tsx8.14 kB
import { AccessGroupEntryResponse, ChatType, checkPartyAccessGroups, DecryptedMessageEntryResponse, getAllAccessGroups, getAllMessageThreads, identity, NewMessageEntryResponse, PublicKeyToProfileEntryResponseMap, sendDMMessage, sendGroupChatMessage, waitForTransactionFound, } from "deso-protocol"; import { toast } from "react-toastify"; import { DEFAULT_KEY_MESSAGING_GROUP_NAME, USER_TO_SEND_MESSAGE_TO, } from "../utils/constants"; import { ConversationMap } from "../utils/types"; import { bytesToHex } from "@noble/hashes/utils"; export const getConversationsNewMap = async ( userPublicKeyBase58Check: string, allAccessGroups: AccessGroupEntryResponse[] ): Promise<{ conversations: ConversationMap; publicKeyToProfileEntryResponseMap: PublicKeyToProfileEntryResponseMap; updatedAllAccessGroups: AccessGroupEntryResponse[]; }> => { const { decrypted, publicKeyToProfileEntryResponseMap, updatedAllAccessGroups, } = await getConversationNew(userPublicKeyBase58Check, allAccessGroups); const conversations: ConversationMap = {}; decrypted.forEach((dmr) => { const otherInfo = dmr.ChatType === ChatType.DM ? dmr.IsSender ? dmr.RecipientInfo : dmr.SenderInfo : dmr.RecipientInfo; const key = otherInfo.OwnerPublicKeyBase58Check + (otherInfo.AccessGroupKeyName ? otherInfo.AccessGroupKeyName : DEFAULT_KEY_MESSAGING_GROUP_NAME); const currentConversation = conversations[key]; if (currentConversation) { currentConversation.messages.push(dmr); currentConversation.messages.sort( (a, b) => b.MessageInfo.TimestampNanos - a.MessageInfo.TimestampNanos ); return; } conversations[key] = { firstMessagePublicKey: otherInfo.OwnerPublicKeyBase58Check, messages: [dmr], ChatType: dmr.ChatType, }; }); return { conversations, publicKeyToProfileEntryResponseMap, updatedAllAccessGroups, }; }; export const getConversationNew = async ( userPublicKeyBase58Check: string, allAccessGroups: AccessGroupEntryResponse[] ): Promise<{ decrypted: DecryptedMessageEntryResponse[]; publicKeyToProfileEntryResponseMap: PublicKeyToProfileEntryResponseMap; updatedAllAccessGroups: AccessGroupEntryResponse[]; }> => { const messages = await getAllMessageThreads({ UserPublicKeyBase58Check: userPublicKeyBase58Check, }); const { decrypted, updatedAllAccessGroups } = await decryptAccessGroupMessagesWithRetry( userPublicKeyBase58Check, messages.MessageThreads, allAccessGroups ); return { decrypted, publicKeyToProfileEntryResponseMap: messages.PublicKeyToProfileEntryResponse, updatedAllAccessGroups, }; }; export const getConversations = async ( userPublicKeyBase58Check: string, allAccessGroups: AccessGroupEntryResponse[] ): Promise<{ conversations: ConversationMap; publicKeyToProfileEntryResponseMap: PublicKeyToProfileEntryResponseMap; updatedAllAccessGroups: AccessGroupEntryResponse[]; }> => { try { let { conversations, publicKeyToProfileEntryResponseMap, updatedAllAccessGroups, } = await getConversationsNewMap(userPublicKeyBase58Check, allAccessGroups); if (Object.keys(conversations).length === 0) { const txnHashHex = await encryptAndSendNewMessage( "Hi. This is my first test message!", userPublicKeyBase58Check, USER_TO_SEND_MESSAGE_TO ); await waitForTransactionFound(txnHashHex); const getConversationsNewMapResponse = await getConversationsNewMap( userPublicKeyBase58Check, allAccessGroups ); conversations = getConversationsNewMapResponse.conversations; publicKeyToProfileEntryResponseMap = getConversationsNewMapResponse.publicKeyToProfileEntryResponseMap; updatedAllAccessGroups = getConversationsNewMapResponse.updatedAllAccessGroups; } return { conversations, publicKeyToProfileEntryResponseMap, updatedAllAccessGroups, }; } catch (e: any) { toast.error(e.toString()); console.error(e); return { conversations: {}, publicKeyToProfileEntryResponseMap: {}, updatedAllAccessGroups: [], }; } }; export const decryptAccessGroupMessagesWithRetry = async ( publicKeyBase58Check: string, messages: NewMessageEntryResponse[], accessGroups: AccessGroupEntryResponse[] ): Promise<{ decrypted: DecryptedMessageEntryResponse[]; updatedAllAccessGroups: AccessGroupEntryResponse[]; }> => { let decryptedMessageEntries = await decryptAccessGroupMessages( messages, accessGroups ); // Naive approach to figuring out which access groups we need to fetch. const accessGroupsToFetch = decryptedMessageEntries.filter( (dmr) => dmr.error === "Error: access group key not found for group message" ); if (accessGroupsToFetch.length > 0) { const newAllAccessGroups = await getAllAccessGroups({ PublicKeyBase58Check: publicKeyBase58Check, }); accessGroups = (newAllAccessGroups.AccessGroupsOwned || []).concat( newAllAccessGroups.AccessGroupsMember || [] ); decryptedMessageEntries = await decryptAccessGroupMessages( messages, accessGroups ); } return { decrypted: decryptedMessageEntries, updatedAllAccessGroups: accessGroups, }; }; export const decryptAccessGroupMessages = ( messages: NewMessageEntryResponse[], accessGroups: AccessGroupEntryResponse[] ): Promise<DecryptedMessageEntryResponse[]> => { return Promise.all( (messages || []).map((m) => identity.decryptMessage(m, accessGroups)) ); }; export const encryptAndSendNewMessage = async ( messageToSend: string, senderPublicKeyBase58Check: string, RecipientPublicKeyBase58Check: string, RecipientMessagingKeyName = DEFAULT_KEY_MESSAGING_GROUP_NAME, SenderMessagingKeyName = DEFAULT_KEY_MESSAGING_GROUP_NAME ): Promise<string> => { if (SenderMessagingKeyName !== DEFAULT_KEY_MESSAGING_GROUP_NAME) { return Promise.reject("sender must use default key for now"); } const response = await checkPartyAccessGroups({ SenderPublicKeyBase58Check: senderPublicKeyBase58Check, SenderAccessGroupKeyName: SenderMessagingKeyName, RecipientPublicKeyBase58Check: RecipientPublicKeyBase58Check, RecipientAccessGroupKeyName: RecipientMessagingKeyName, }); if (!response.SenderAccessGroupKeyName) { return Promise.reject("SenderAccessGroupKeyName is undefined"); } let message: string; let isUnencrypted = false; const ExtraData: { [k: string]: string } = {}; if (response.RecipientAccessGroupKeyName) { message = await identity.encryptMessage( response.RecipientAccessGroupPublicKeyBase58Check, messageToSend ); } else { message = bytesToHex(new TextEncoder().encode(messageToSend)); isUnencrypted = true; ExtraData["unencrypted"] = "true"; } if (!message) { return Promise.reject("error encrypting message"); } const requestBody = { SenderAccessGroupOwnerPublicKeyBase58Check: senderPublicKeyBase58Check, SenderAccessGroupPublicKeyBase58Check: response.SenderAccessGroupPublicKeyBase58Check, SenderAccessGroupKeyName: SenderMessagingKeyName, RecipientAccessGroupOwnerPublicKeyBase58Check: RecipientPublicKeyBase58Check, RecipientAccessGroupPublicKeyBase58Check: isUnencrypted ? response.RecipientPublicKeyBase58Check : response.RecipientAccessGroupPublicKeyBase58Check, RecipientAccessGroupKeyName: response.RecipientAccessGroupKeyName, ExtraData, EncryptedMessageText: message, MinFeeRateNanosPerKB: 1000, }; const isDM = !RecipientMessagingKeyName || RecipientMessagingKeyName === DEFAULT_KEY_MESSAGING_GROUP_NAME; const { submittedTransactionResponse } = await (isDM ? sendDMMessage(requestBody) : sendGroupChatMessage(requestBody)); if (!submittedTransactionResponse) { throw new Error("Failed to submit transaction for sending message."); } return submittedTransactionResponse.TxnHashHex; };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/deso-protocol/deso-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server