create-message-kit 1.0.15 → 1.0.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. package/index.js +12 -3
  2. package/package.json +2 -2
  3. package/templates/agent/.env.example +2 -0
  4. package/{examples/one-to-one → templates/agent}/package.json +2 -4
  5. package/templates/agent/src/handler/ens.ts +211 -0
  6. package/templates/agent/src/index.ts +14 -0
  7. package/{examples/group → templates/agent}/src/lib/openai.ts +42 -4
  8. package/templates/agent/src/lib/resolver.ts +126 -0
  9. package/templates/agent/src/prompt.ts +81 -0
  10. package/templates/agent/src/skills.ts +93 -0
  11. package/templates/gm/.env.example +1 -0
  12. package/{examples → templates}/gm/package.json +1 -0
  13. package/templates/group/.env.example +3 -0
  14. package/{examples → templates}/group/package.json +1 -0
  15. package/{examples → templates}/group/src/handler/agent.ts +18 -10
  16. package/{examples → templates}/group/src/handler/game.ts +2 -2
  17. package/{examples → templates}/group/src/handler/loyalty.ts +3 -11
  18. package/{examples → templates}/group/src/handler/splitpayment.ts +18 -11
  19. package/{examples → templates}/group/src/handler/tipping.ts +10 -6
  20. package/{examples → templates}/group/src/handler/transaction.ts +11 -40
  21. package/templates/group/src/index.ts +57 -0
  22. package/templates/group/src/lib/openai.ts +137 -0
  23. package/templates/group/src/lib/resolver.ts +126 -0
  24. package/{examples/group/src/commands.ts → templates/group/src/skills.ts} +24 -26
  25. package/examples/gm/.env.example +0 -1
  26. package/examples/group/.env.example +0 -3
  27. package/examples/group/src/index.ts +0 -68
  28. package/examples/one-to-one/.env.example +0 -2
  29. package/examples/one-to-one/src/index.ts +0 -72
  30. package/examples/one-to-one/src/lib/cron.ts +0 -34
  31. package/examples/one-to-one/src/lib/redis.ts +0 -15
  32. /package/{examples → templates}/gm/src/index.ts +0 -0
  33. /package/{examples → templates}/group/src/lib/stack.ts +0 -0
@@ -11,6 +11,7 @@
11
11
  "@xmtp/message-kit": "workspace:*"
12
12
  },
13
13
  "devDependencies": {
14
+ "@types/node": "^20.14.2",
14
15
  "nodemon": "^3.1.3",
15
16
  "typescript": "^5.4.5"
16
17
  },
@@ -0,0 +1,3 @@
1
+ KEY= # the private key of the bot wallet
2
+ OPEN_AI_API_KEY= # openai api key
3
+ STACK_API_KEY= # stack api key
@@ -13,6 +13,7 @@
13
13
  "openai": "^4.52.0"
14
14
  },
15
15
  "devDependencies": {
16
+ "@types/node": "^20.14.2",
16
17
  "nodemon": "^3.1.3",
17
18
  "typescript": "^5.4.5"
18
19
  },
@@ -1,14 +1,15 @@
1
- import { HandlerContext, User } from "@xmtp/message-kit";
1
+ import { HandlerContext, AbstractedMember } from "@xmtp/message-kit";
2
2
  import { textGeneration } from "../lib/openai.js";
3
3
 
4
4
  export async function handler(context: HandlerContext) {
5
5
  if (!process?.env?.OPEN_AI_API_KEY) {
6
- console.log("No OPEN_AI_API_KEY found in .env");
6
+ console.warn("No OPEN_AI_API_KEY found in .env");
7
7
  return;
8
8
  }
9
9
 
10
10
  const {
11
11
  message: {
12
+ sender,
12
13
  content: { content, params },
13
14
  },
14
15
  } = context;
@@ -16,11 +17,13 @@ export async function handler(context: HandlerContext) {
16
17
  const systemPrompt = generateSystemPrompt(context);
17
18
  try {
18
19
  let userPrompt = params?.prompt ?? content;
19
- console.log("userPrompt", userPrompt);
20
20
 
21
- const { reply } = await textGeneration(userPrompt, systemPrompt);
22
- console.log("intent:", reply);
23
- context.intent(reply);
21
+ const { reply } = await textGeneration(
22
+ sender.address,
23
+ userPrompt,
24
+ systemPrompt,
25
+ );
26
+ context.skill(reply);
24
27
  } catch (error) {
25
28
  console.error("Error during OpenAI call:", error);
26
29
  await context.reply("An error occurred while processing your request.");
@@ -30,7 +33,7 @@ export async function handler(context: HandlerContext) {
30
33
  function generateSystemPrompt(context: HandlerContext) {
31
34
  const {
32
35
  members,
33
- commands,
36
+ skills,
34
37
  message: { sender },
35
38
  } = context;
36
39
 
@@ -39,10 +42,15 @@ function generateSystemPrompt(context: HandlerContext) {
39
42
 
40
43
  You are a helpful bot agent that lives inside a web3 messaging group that helps interpret user requests and execute commands.
41
44
  #### Users
42
- ${JSON.stringify(members?.map((member: User) => ({ ...member, username: `@${member.username}` })))}\n
45
+ ${JSON.stringify(
46
+ members?.map((member: AbstractedMember) => ({
47
+ ...member,
48
+ username: `@${member.accountAddresses[0]}`,
49
+ })),
50
+ )}\n
43
51
  #### Commands
44
- ${JSON.stringify(commands)}\n
45
- The message was sent by @${sender?.username}
52
+ ${JSON.stringify(skills)}\n
53
+ The message was sent by @${sender?.address}
46
54
 
47
55
  ### Examples
48
56
  prompt /agent tip alix and bo
@@ -17,8 +17,8 @@ export async function handler(context: HandlerContext) {
17
17
  }
18
18
  // URLs for each game type
19
19
  const gameUrls: { [key: string]: string } = {
20
- wordle: "https://framedl.xyz/",
21
- slot: "https://slot-machine-frame.vercel.app/",
20
+ wordle: "https://framedl.xyz",
21
+ slot: "https://slot-machine-frame.vercel.app",
22
22
  };
23
23
  // Respond with the appropriate game URL or an error message
24
24
  switch (params.game) {
@@ -1,4 +1,4 @@
1
- import { HandlerContext, User } from "@xmtp/message-kit";
1
+ import { HandlerContext, AbstractedMember } from "@xmtp/message-kit";
2
2
  import { getStackClient } from "../lib/stack.js";
3
3
 
4
4
  export async function handler(context: HandlerContext, fake?: boolean) {
@@ -6,15 +6,8 @@ export async function handler(context: HandlerContext, fake?: boolean) {
6
6
  const {
7
7
  members,
8
8
  group,
9
- getMessageById,
10
- message: {
11
- content,
12
- content: { command },
13
- sender,
14
- typeId,
15
- },
9
+ message: { sender, typeId, content },
16
10
  } = context;
17
- console.log(command);
18
11
  if (typeId === "text" && group) {
19
12
  const { command } = content;
20
13
  if (command === "points") {
@@ -40,14 +33,13 @@ export async function handler(context: HandlerContext, fake?: boolean) {
40
33
  } else if (typeId === "group_updated" && group) {
41
34
  const { initiatedByInboxId, addedInboxes } = content;
42
35
  const adminAddress = members?.find(
43
- (member: User) => member.inboxId === initiatedByInboxId,
36
+ (member: AbstractedMember) => member.inboxId === initiatedByInboxId,
44
37
  );
45
38
  if (addedInboxes && addedInboxes.length > 0) {
46
39
  //if add someone to the group
47
40
  await stack?.track("referral", {
48
41
  points: 10,
49
42
  account: adminAddress?.address ?? "",
50
- uniqueId: adminAddress?.username ?? "",
51
43
  });
52
44
  }
53
45
  }
@@ -1,14 +1,15 @@
1
1
  import { HandlerContext } from "@xmtp/message-kit";
2
2
  import { vision, textGeneration } from "../lib/openai.js";
3
+ import { getUserInfo } from "../lib/resolver.js";
3
4
 
4
5
  export async function handler(context: HandlerContext) {
5
6
  if (!process?.env?.OPEN_AI_API_KEY) {
6
- console.log("No OPEN_AI_API_KEY found in .env");
7
+ console.warn("No OPEN_AI_API_KEY found in .env");
7
8
  return;
8
9
  }
9
10
  const {
10
11
  members,
11
- commands,
12
+ skills,
12
13
  message: {
13
14
  typeId,
14
15
  content: { attachment },
@@ -16,8 +17,12 @@ export async function handler(context: HandlerContext) {
16
17
  },
17
18
  } = context;
18
19
 
20
+ if (!members) {
21
+ return;
22
+ }
23
+ let senderInfo = await getUserInfo(sender.address);
19
24
  if (attachment && typeId === "remoteStaticAttachment") {
20
- const { data, filename, mimeType } = attachment;
25
+ const { data } = attachment;
21
26
  const response = await vision(
22
27
  data,
23
28
  "This image is the bill of a restaurant dinner. Return the total. If you can't find the total, return 'undefined'.",
@@ -31,9 +36,6 @@ export async function handler(context: HandlerContext) {
31
36
  }
32
37
  if (response) {
33
38
  const prompt = `You a split wise agent that splits the bill between the members of this group except for the sender and bot.\n
34
- These are the users of the group: ${JSON.stringify(members?.map((member) => ({ ...member, username: `@${member.username}` })))}\n
35
- This group app has many commands available: ${JSON.stringify(commands)}\n
36
-
37
39
 
38
40
  ## Instructions:
39
41
  When you receive the totals you should split the bill between the members of the group and send to each one a transaction frame
@@ -44,18 +46,23 @@ export async function handler(context: HandlerContext) {
44
46
  Example:
45
47
  [
46
48
  "This are the details: Total: $49.52. Tip (20%): $9.90",
47
- "All users owe X USDC to @${sender?.username}. Pay here:",
48
- "/send @${sender?.username} $9.90"
49
+ "All users owe X USDC to @${senderInfo?.converseUsername}. Pay here:",
50
+ "/send @${senderInfo?.converseUsername} $9.90"
49
51
  ]
50
52
  `;
51
53
 
52
54
  //I want the reply to be an array of messages so the bot feels like is sending multuple ones
53
- const { reply } = await textGeneration(response, prompt);
55
+ const { reply } = await textGeneration(
56
+ sender.address,
57
+ response,
58
+ prompt,
59
+ true,
60
+ );
54
61
  let splitMessages = JSON.parse(reply);
55
62
  for (const message of splitMessages) {
56
63
  let msg = message as string;
57
- if (msg.startsWith("/")) await context.intent(msg);
58
- else await context.reply(msg);
64
+ if (msg.startsWith("/")) await context.skill(msg);
65
+ else await context.send(msg);
59
66
  }
60
67
  }
61
68
  }
@@ -1,4 +1,5 @@
1
- import { HandlerContext, User } from "@xmtp/message-kit";
1
+ import { HandlerContext, AbstractedMember } from "@xmtp/message-kit";
2
+ import { getUserInfo } from "../lib/resolver.js";
2
3
 
3
4
  export async function handler(context: HandlerContext) {
4
5
  const {
@@ -6,16 +7,15 @@ export async function handler(context: HandlerContext) {
6
7
  getMessageById,
7
8
  message: { content, sender, typeId },
8
9
  } = context;
10
+ console.log(sender);
9
11
  const msg = await getMessageById(content.reference);
10
12
  const replyReceiver = members?.find(
11
13
  (member) => member.inboxId === msg?.senderInboxId,
12
14
  );
13
15
  let amount: number = 0,
14
- receivers: User[] = [];
16
+ receivers: AbstractedMember[] = [];
15
17
  // Handle different types of messages
16
18
  if (typeId === "reply" && replyReceiver) {
17
- // Process reply messages/
18
- //ha
19
19
  const { content: reply } = content;
20
20
 
21
21
  if (reply.includes("degen")) {
@@ -31,18 +31,22 @@ export async function handler(context: HandlerContext) {
31
31
  // Process text commands starting with "/tip"
32
32
  const {
33
33
  params: { amount: extractedAmount, username },
34
- content: text,
35
34
  } = content;
36
35
  amount = extractedAmount || 10; // Default amount if not specified
37
- receivers = username; // Extract receiver from parameters
36
+
37
+ receivers = await Promise.all(
38
+ username.map((username: string) => getUserInfo(username)),
39
+ );
38
40
  }
39
41
  }
40
42
  if (!sender || receivers.length === 0 || amount === 0) {
41
43
  context.reply("Sender or receiver or amount not found.");
42
44
  return;
43
45
  }
46
+ console.log(receivers);
44
47
  const receiverAddresses = receivers.map((receiver) => receiver.address);
45
48
  // Process sending tokens to each receiver
49
+
46
50
  context.sendTo(
47
51
  `You received ${amount} tokens from ${sender.address}.`,
48
52
  receiverAddresses,
@@ -1,4 +1,5 @@
1
1
  import { HandlerContext } from "@xmtp/message-kit";
2
+ import { getUserInfo } from "../lib/resolver.js";
2
3
 
3
4
  // Main handler function for processing commands
4
5
  export async function handler(context: HandlerContext) {
@@ -7,26 +8,23 @@ export async function handler(context: HandlerContext) {
7
8
  content: { command, params },
8
9
  },
9
10
  } = context;
10
- const baseUrl = "https://base-frame-lyart.vercel.app/transaction";
11
+ const baseUrl = "https://base-tx-frame.vercel.app/transaction";
11
12
 
12
13
  switch (command) {
13
14
  case "send":
14
15
  // Destructure and validate parameters for the send command
15
16
  const { amount: amountSend, token: tokenSend, username } = params; // [!code hl] // [!code focus]
16
-
17
- if (!amountSend || !tokenSend || !username) {
17
+ let senderInfo = await getUserInfo(username);
18
+ if (!amountSend || !tokenSend || !senderInfo) {
18
19
  context.reply(
19
20
  "Missing required parameters. Please provide amount, token, and username.",
20
21
  );
21
22
  return;
22
23
  }
23
- // Generate URL for the send transaction
24
- let url_send = generateFrameURL(baseUrl, "send", {
25
- amount: amountSend,
26
- token: tokenSend,
27
- receiver: username[0]?.address,
28
- });
29
- context.reply(`${url_send}`);
24
+ let name = senderInfo.converseUsername || senderInfo.address;
25
+
26
+ let sendUrl = `${baseUrl}/?transaction_type=send&amount=${amountSend}&token=${tokenSend}&receiver=${senderInfo.address}`;
27
+ context.send(`${sendUrl}`);
30
28
  break;
31
29
  case "swap":
32
30
  // Destructure and validate parameters for the swap command
@@ -38,13 +36,9 @@ export async function handler(context: HandlerContext) {
38
36
  );
39
37
  return;
40
38
  }
41
- // Generate URL for the swap transaction
42
- let url_swap = generateFrameURL(baseUrl, "swap", {
43
- amount,
44
- token_from,
45
- token_to,
46
- });
47
- context.reply(`${url_swap}`);
39
+
40
+ let swapUrl = `${baseUrl}/?transaction_type=swap&token_from=${token_from}&token_to=${token_to}&amount=${amount}`;
41
+ context.send(`${swapUrl}`);
48
42
  break;
49
43
  case "show": // [!code hl] // [!code focus]
50
44
  // Show the base URL without the transaction path
@@ -55,26 +49,3 @@ export async function handler(context: HandlerContext) {
55
49
  context.reply("Unknown command. Use help to see all available commands.");
56
50
  }
57
51
  }
58
-
59
- // Function to generate a URL with query parameters for transactions
60
- function generateFrameURL(
61
- baseUrl: string,
62
- transaction_type: string,
63
- params: { [key: string]: string | number | string[] | undefined },
64
- ) {
65
- // Filter out undefined parameters
66
- let filteredParams: {
67
- [key: string]: string | number | string[] | undefined;
68
- } = {};
69
-
70
- for (const key in params) {
71
- if (params[key] !== undefined) {
72
- filteredParams[key] = params[key];
73
- }
74
- }
75
- let queryParams = new URLSearchParams({
76
- transaction_type,
77
- ...filteredParams,
78
- }).toString();
79
- return `${baseUrl}?${queryParams}`;
80
- }
@@ -0,0 +1,57 @@
1
+ import { run, HandlerContext } from "@xmtp/message-kit";
2
+ import { handler as splitpayment } from "./handler/splitpayment.js";
3
+
4
+ // Main function to run the app
5
+ run(
6
+ async (context: HandlerContext) => {
7
+ const {
8
+ message: { typeId },
9
+ } = context;
10
+ switch (typeId) {
11
+ case "reply":
12
+ handleReply(context);
13
+ break;
14
+ case "remoteStaticAttachment":
15
+ handleAttachment(context);
16
+ break;
17
+ }
18
+ if (!context.group) {
19
+ context.send("This is a group bot, add this address to a group");
20
+ }
21
+ },
22
+ { attachments: true },
23
+ );
24
+ async function handleReply(context: HandlerContext) {
25
+ const {
26
+ v2client,
27
+ getReplyChain,
28
+ version,
29
+ message: {
30
+ content: { reference },
31
+ },
32
+ } = context;
33
+
34
+ const { chain, isSenderInChain } = await getReplyChain(
35
+ reference,
36
+ version,
37
+ v2client.address,
38
+ );
39
+ //await context.skill(chain);
40
+ }
41
+
42
+ // Handle attachment messages
43
+ async function handleAttachment(context: HandlerContext) {
44
+ await splitpayment(context);
45
+ }
46
+
47
+ export async function helpHandler(context: HandlerContext) {
48
+ const { skills } = context;
49
+ const intro =
50
+ "Available experiences:\n" +
51
+ skills
52
+ ?.flatMap((app) => app.skills)
53
+ .map((skill) => `${skill.command} - ${skill.description}`)
54
+ .join("\n") +
55
+ "\nUse these commands to interact with specific apps.";
56
+ context.send(intro);
57
+ }
@@ -0,0 +1,137 @@
1
+ import dotenv from "dotenv";
2
+ dotenv.config();
3
+
4
+ import OpenAI from "openai";
5
+ const openai = new OpenAI({
6
+ apiKey: process.env.OPEN_AI_API_KEY,
7
+ });
8
+
9
+ export type ChatHistoryEntry = { role: string; content: string };
10
+ export type ChatHistories = Record<string, ChatHistoryEntry[]>;
11
+
12
+ let chatHistories: ChatHistories = {};
13
+ export async function textGeneration(
14
+ address: string,
15
+ userPrompt: string,
16
+ systemPrompt: string,
17
+ isGroup: boolean = false,
18
+ ) {
19
+ let messages = chatHistories[address] || [];
20
+ if (messages.length === 0) {
21
+ messages.push({
22
+ role: "system",
23
+ content: systemPrompt,
24
+ });
25
+ }
26
+ messages.push({
27
+ role: "user",
28
+ content: userPrompt,
29
+ });
30
+ try {
31
+ const response = await openai.chat.completions.create({
32
+ model: "gpt-4o",
33
+ messages: messages as any,
34
+ });
35
+ const reply = response.choices[0].message.content;
36
+ messages.push({
37
+ role: "assistant",
38
+ content: reply || "No response from OpenAI.",
39
+ });
40
+ const cleanedReply = responseParser(reply as string);
41
+ if (!isGroup) chatHistories[address] = messages;
42
+ return { reply: cleanedReply, history: messages };
43
+ } catch (error) {
44
+ console.error("Failed to fetch from OpenAI:", error);
45
+ throw error;
46
+ }
47
+ }
48
+
49
+ // New method to interpret an image
50
+ export async function vision(imageData: Uint8Array, systemPrompt: string) {
51
+ const base64Image = Buffer.from(imageData).toString("base64");
52
+ const dataUrl = `data:image/jpeg;base64,${base64Image}`;
53
+
54
+ // Create a new thread for each vision request
55
+ const visionMessages = [
56
+ {
57
+ role: "system",
58
+ content: systemPrompt,
59
+ },
60
+ {
61
+ role: "user",
62
+ content: [
63
+ { type: "text", text: systemPrompt },
64
+ {
65
+ type: "image_url",
66
+ image_url: {
67
+ url: dataUrl,
68
+ },
69
+ },
70
+ ],
71
+ },
72
+ ];
73
+
74
+ try {
75
+ const response = await openai.chat.completions.create({
76
+ model: "gpt-4o",
77
+ messages: visionMessages as any,
78
+ });
79
+ return response.choices[0].message.content;
80
+ } catch (error) {
81
+ console.error("Failed to interpret image with OpenAI:", error);
82
+ throw error;
83
+ }
84
+ }
85
+
86
+ export async function processResponseWithskill(
87
+ address: string,
88
+ reply: string,
89
+ context: any,
90
+ ) {
91
+ let messages = reply
92
+ .split("\n")
93
+ .map((message: string) => responseParser(message))
94
+ .filter((message): message is string => message.length > 0);
95
+
96
+ console.log(messages);
97
+ for (const message of messages) {
98
+ if (message.startsWith("/")) {
99
+ const response = await context.skill(message);
100
+ if (response && response.message) {
101
+ let msg = responseParser(response.message);
102
+
103
+ chatHistories[address].push({
104
+ role: "system",
105
+ content: msg,
106
+ });
107
+
108
+ await context.send(response.message);
109
+ }
110
+ } else {
111
+ await context.send(message);
112
+ }
113
+ }
114
+ }
115
+ export function responseParser(message: string) {
116
+ let trimmedMessage = message;
117
+ // Remove bold and underline markdown
118
+ trimmedMessage = trimmedMessage?.replace(/(\*\*|__)(.*?)\1/g, "$2");
119
+ // Remove markdown links, keeping only the URL
120
+ trimmedMessage = trimmedMessage?.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$2");
121
+ // Remove markdown headers
122
+ trimmedMessage = trimmedMessage?.replace(/^#+\s*(.*)$/gm, "$1");
123
+ // Remove inline code formatting
124
+ trimmedMessage = trimmedMessage?.replace(/`([^`]+)`/g, "$1");
125
+ // Remove single backticks at the start or end of the message
126
+ trimmedMessage = trimmedMessage?.replace(/^`|`$/g, "");
127
+ // Remove leading and trailing whitespace
128
+ trimmedMessage = trimmedMessage?.replace(/^\s+|\s+$/g, "");
129
+ // Remove any remaining leading or trailing whitespace
130
+ trimmedMessage = trimmedMessage.trim();
131
+
132
+ return trimmedMessage;
133
+ }
134
+
135
+ export const clearChatHistories = () => {
136
+ chatHistories = {};
137
+ };
@@ -0,0 +1,126 @@
1
+ import { Client } from "@xmtp/xmtp-js";
2
+ import { isAddress } from "viem";
3
+
4
+ export const converseEndpointURL =
5
+ "https://converse-website-git-endpoit-ephemerahq.vercel.app";
6
+ //export const converseEndpointURL = "http://localhost:3000";
7
+
8
+ export type InfoCache = Map<string, UserInfo>;
9
+ export type ConverseProfile = {
10
+ address: string | null;
11
+ onXmtp: boolean;
12
+ avatar: string | null;
13
+ formattedName: string | null;
14
+ name: string | null;
15
+ };
16
+ export type UserInfo = {
17
+ ensDomain?: string | undefined;
18
+ address?: string | undefined;
19
+ converseUsername?: string | undefined;
20
+ ensInfo?: EnsData | undefined;
21
+ avatar?: string | undefined;
22
+ };
23
+ export interface EnsData {
24
+ address?: string;
25
+ avatar?: string;
26
+ avatar_small?: string;
27
+ converse?: string;
28
+ avatar_url?: string;
29
+ contentHash?: string;
30
+ description?: string;
31
+ ens?: string;
32
+ ens_primary?: string;
33
+ github?: string;
34
+ resolverAddress?: string;
35
+ twitter?: string;
36
+ url?: string;
37
+ wallets?: {
38
+ eth?: string;
39
+ };
40
+ }
41
+
42
+ let infoCache: InfoCache = new Map();
43
+
44
+ export const clearInfoCache = () => {
45
+ infoCache.clear();
46
+ };
47
+ export const getUserInfo = async (
48
+ key: string,
49
+ clientAddress?: string,
50
+ ): Promise<UserInfo | null> => {
51
+ let data: UserInfo = infoCache.get(key) || {
52
+ ensDomain: undefined,
53
+ address: undefined,
54
+ converseUsername: undefined,
55
+ ensInfo: undefined,
56
+ };
57
+ //console.log("Getting user info", key, clientAddress);
58
+ if (isAddress(clientAddress || "")) {
59
+ data.address = clientAddress;
60
+ } else if (isAddress(key || "")) {
61
+ data.address = key;
62
+ } else if (key.includes(".eth")) {
63
+ data.ensDomain = key;
64
+ } else if (key == "@user" || key == "@me" || key == "@bot") {
65
+ data.address = clientAddress;
66
+ data.ensDomain = key.replace("@", "") + ".eth";
67
+ data.converseUsername = key.replace("@", "");
68
+ } else if (key == "@alix") {
69
+ data.address = "0x3a044b218BaE80E5b9E16609443A192129A67BeA";
70
+ data.converseUsername = "alix";
71
+ } else if (key == "@bo") {
72
+ data.address = "0xbc3246461ab5e1682baE48fa95172CDf0689201a";
73
+ data.converseUsername = "bo";
74
+ } else {
75
+ data.converseUsername = key;
76
+ }
77
+
78
+ let keyToUse = data.address || data.ensDomain || data.converseUsername;
79
+ let cacheData = keyToUse && infoCache.get(keyToUse);
80
+ if (cacheData) {
81
+ //console.log("Getting user info", keyToUse, cacheData);
82
+ return cacheData;
83
+ } else {
84
+ //console.log("Getting user info", keyToUse, data);
85
+ }
86
+
87
+ if (keyToUse?.includes(".eth")) {
88
+ const response = await fetch(`https://ensdata.net/${keyToUse}`);
89
+ const ensData: EnsData = (await response.json()) as EnsData;
90
+ //console.log("Ens data", ensData);
91
+ if (ensData) {
92
+ data.ensInfo = ensData;
93
+ data.ensDomain = ensData?.ens;
94
+ data.address = ensData?.address;
95
+ }
96
+ } else if (keyToUse) {
97
+ keyToUse = keyToUse.replace("@", "");
98
+ const response = await fetch(`${converseEndpointURL}/profile/${keyToUse}`, {
99
+ method: "POST",
100
+ headers: {
101
+ "Content-Type": "application/json",
102
+ Accept: "application/json",
103
+ },
104
+ body: JSON.stringify({
105
+ peer: keyToUse,
106
+ }),
107
+ });
108
+ const converseData = (await response.json()) as ConverseProfile;
109
+ if (process.env.MSG_LOG)
110
+ console.log("Converse data", keyToUse, converseData);
111
+ data.converseUsername =
112
+ converseData?.formattedName || converseData?.name || undefined;
113
+ data.address = converseData?.address || undefined;
114
+ data.avatar = converseData?.avatar || undefined;
115
+ }
116
+ if (data.address) infoCache.set(data.address, data);
117
+ return data;
118
+ };
119
+ export const isOnXMTP = async (
120
+ client: Client,
121
+ domain: string | undefined,
122
+ address: string | undefined,
123
+ ) => {
124
+ if (domain == "fabri.eth") return false;
125
+ if (address) return (await client.canMessage([address])).length > 0;
126
+ };