towns-agent 2.0.4 → 2.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -8
- package/dist/index.js +214 -207
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +205 -199
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -8
- package/templates/quickstart/.env.sample +4 -0
- package/templates/quickstart/.turbo/turbo-build.log +2 -0
- package/templates/quickstart/AGENTS.md +267 -0
- package/templates/quickstart/README.md +95 -0
- package/templates/quickstart/_gitignore +33 -0
- package/templates/quickstart/package.json +35 -0
- package/templates/quickstart/src/commands.ts +12 -0
- package/templates/quickstart/src/index.ts +56 -0
- package/templates/quickstart/tsconfig.json +25 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to AI agents for building Towns Protocol bots.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
**Requirements:**
|
|
8
|
+
1. **APP_PRIVATE_DATA** - Bot credentials
|
|
9
|
+
2. **JWT_SECRET** - Webhook security token
|
|
10
|
+
3. **Event handlers** - Functions responding to Towns events
|
|
11
|
+
|
|
12
|
+
**CRITICAL:** Stateless architecture - no message history, thread context, or conversation memory. Store context externally if needed.
|
|
13
|
+
|
|
14
|
+
## Base Payload
|
|
15
|
+
|
|
16
|
+
All event handlers receive a `BasePayload`:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
type BasePayload = {
|
|
20
|
+
userId: string // Hex address (0x...)
|
|
21
|
+
channelId: string
|
|
22
|
+
eventId: string // Unique event ID (use as threadId/replyId when responding)
|
|
23
|
+
createdAt: Date
|
|
24
|
+
event: StreamEvent
|
|
25
|
+
isDm: boolean // true if the event is from a DM
|
|
26
|
+
isGdm: boolean // true if the event is from a GDM
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Example:**
|
|
31
|
+
```typescript
|
|
32
|
+
bot.onMessage(async (handler, event) => {
|
|
33
|
+
if (event.isDm) {
|
|
34
|
+
console.log('DM')
|
|
35
|
+
} else if (event.isGdm) {
|
|
36
|
+
console.log('GDM')
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Event Handlers
|
|
42
|
+
|
|
43
|
+
### onMessage
|
|
44
|
+
**When:** Any non-slash-command message
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
{
|
|
48
|
+
...basePayload,
|
|
49
|
+
message: string,
|
|
50
|
+
replyId?: string, // EventId of replied message
|
|
51
|
+
threadId?: string, // EventId of thread start
|
|
52
|
+
isMentioned: boolean,
|
|
53
|
+
mentions: Array<{ userId: string, displayName: string }>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
bot.onMessage(async (handler, event) => {
|
|
57
|
+
if (event.isMentioned) {
|
|
58
|
+
await handler.sendMessage(event.channelId, "You mentioned me!")
|
|
59
|
+
}
|
|
60
|
+
if (event.threadId) {
|
|
61
|
+
await handler.sendMessage(event.channelId, "Reply", { threadId: event.threadId })
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### onSlashCommand
|
|
67
|
+
**When:** User types `/command args` (does NOT trigger onMessage)
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
{
|
|
71
|
+
...basePayload,
|
|
72
|
+
command: string,
|
|
73
|
+
args: string[],
|
|
74
|
+
mentions: Array<{ userId: string, displayName: string }>,
|
|
75
|
+
replyId?: string,
|
|
76
|
+
threadId?: string
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Setup:**
|
|
81
|
+
```typescript
|
|
82
|
+
// 1. src/commands.ts
|
|
83
|
+
import type { AgentCommand } from '@towns-labs/agent'
|
|
84
|
+
|
|
85
|
+
export const commands = [
|
|
86
|
+
{ name: "help", description: "Show help" }
|
|
87
|
+
] as const satisfies AgentCommand[]
|
|
88
|
+
|
|
89
|
+
// 2. Initialize
|
|
90
|
+
const bot = await makeTownsAgent(privateData, jwtSecret, { commands })
|
|
91
|
+
|
|
92
|
+
// 3. Register
|
|
93
|
+
bot.onSlashCommand("help", async (handler, event) => {
|
|
94
|
+
await handler.sendMessage(event.channelId, "Commands: /help")
|
|
95
|
+
})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### Paid Commands
|
|
99
|
+
Add a `paid` property to your command definition with a price in USDC:
|
|
100
|
+
```typescript
|
|
101
|
+
{ name: "generate", description: "Generate AI content", paid: { price: '$0.20' } }
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### onReaction
|
|
105
|
+
**When:** User adds emoji reaction
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
{
|
|
109
|
+
...basePayload,
|
|
110
|
+
reaction: string, // "thumbsup", "heart"
|
|
111
|
+
messageId: string // EventId
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
**Note:** No access to original message content.
|
|
115
|
+
|
|
116
|
+
### onInteractionResponse
|
|
117
|
+
**When:** Button click, form submit, transaction/signature response
|
|
118
|
+
**Pattern:** Set ID in request -> Match ID in response
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
{ ...basePayload, response: DecryptedInteractionResponse }
|
|
122
|
+
|
|
123
|
+
// 1. Send request with ID
|
|
124
|
+
await handler.sendInteractionRequest(channelId, {
|
|
125
|
+
type: 'form',
|
|
126
|
+
id: "confirm-action",
|
|
127
|
+
title: "Confirm?",
|
|
128
|
+
components: [{ id: "yes", component: { case: "button", value: { label: "Yes" } } }]
|
|
129
|
+
}, hexToBytes(userId as `0x${string}`))
|
|
130
|
+
|
|
131
|
+
// 2. Match ID in response
|
|
132
|
+
bot.onInteractionResponse(async (handler, event) => {
|
|
133
|
+
if (event.response.payload.content?.case === "form") {
|
|
134
|
+
const form = event.response.payload.content.value
|
|
135
|
+
if (form.requestId === "confirm-action") {
|
|
136
|
+
for (const c of form.components) {
|
|
137
|
+
if (c.component.case === "button" && c.id === "yes") {
|
|
138
|
+
await handler.sendMessage(event.channelId, "Confirmed!")
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Other Request Types:**
|
|
147
|
+
```typescript
|
|
148
|
+
import { hexToBytes } from 'viem'
|
|
149
|
+
|
|
150
|
+
// Transaction
|
|
151
|
+
await handler.sendInteractionRequest(channelId, {
|
|
152
|
+
type: 'transaction',
|
|
153
|
+
id: "tx-id",
|
|
154
|
+
title: "Send USDC",
|
|
155
|
+
content: {
|
|
156
|
+
case: "evm",
|
|
157
|
+
value: { chainId: "8453", to, value: "0", data, signerWallet: undefined }
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// Signature
|
|
162
|
+
await handler.sendInteractionRequest(channelId, {
|
|
163
|
+
type: 'signature',
|
|
164
|
+
id: "sig-id",
|
|
165
|
+
title: "Sign",
|
|
166
|
+
chainId: "8453",
|
|
167
|
+
data: JSON.stringify(typedData),
|
|
168
|
+
signatureType: InteractionRequestPayload_Signature_SignatureType.TYPED_DATA,
|
|
169
|
+
signerWallet: undefined
|
|
170
|
+
})
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Other Events
|
|
174
|
+
|
|
175
|
+
**onMessageEdit:** `{ ...basePayload, refEventId: string, message: string, ... }`
|
|
176
|
+
|
|
177
|
+
**onRedaction / onEventRevoke:** `{ ...basePayload, refEventId: string }`
|
|
178
|
+
|
|
179
|
+
**onChannelJoin / onChannelLeave:** Base payload only
|
|
180
|
+
|
|
181
|
+
**onStreamEvent:** `{ ...basePayload, event: ParsedEvent }` (advanced)
|
|
182
|
+
|
|
183
|
+
## Handler API
|
|
184
|
+
|
|
185
|
+
**Types:** `AgentHandler`, `BasePayload`, `AgentCommand`
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
// Send (use @userId to mention a user in message text, and add mentions in sendMessage options)
|
|
189
|
+
await handler.sendMessage(channelId, "Hello @0x123...", {
|
|
190
|
+
threadId?, replyId?, mentions: [{ userId: "0x123...", displayName: "name" }], attachments? })
|
|
191
|
+
|
|
192
|
+
await handler.editMessage(channelId, messageId, newMessage) // Bot's own only
|
|
193
|
+
await handler.sendReaction(channelId, messageId, reaction)
|
|
194
|
+
await handler.sendGM(channelId, typeUrl, data) // Send typed generic message
|
|
195
|
+
await handler.sendRawGM(channelId, typeUrl, rawBytes) // Send raw generic message bytes
|
|
196
|
+
await handler.pinMessage(channelId, eventId, streamEvent)
|
|
197
|
+
await handler.unpinMessage(channelId, eventId)
|
|
198
|
+
await handler.removeEvent(channelId, eventId) // Bot's own
|
|
199
|
+
await handler.sendKeySolicitation(channelId)
|
|
200
|
+
await handler.uploadDeviceKeys()
|
|
201
|
+
await handler.sendBlockchainTransaction(channelId, params)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Attachments
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
// Image
|
|
208
|
+
attachments: [{ type: 'image', url: 'https://...jpg', alt: 'Desc' }]
|
|
209
|
+
|
|
210
|
+
// Link
|
|
211
|
+
attachments: [{ type: 'link', url: 'https://...' }]
|
|
212
|
+
|
|
213
|
+
// Miniapp
|
|
214
|
+
attachments: [{ type: 'miniapp', url: 'https://...' }]
|
|
215
|
+
|
|
216
|
+
// Chunked (videos, screenshots)
|
|
217
|
+
import { readFileSync } from 'node:fs'
|
|
218
|
+
attachments: [{
|
|
219
|
+
type: 'chunked',
|
|
220
|
+
data: readFileSync('./video.mp4'), // Uint8Array
|
|
221
|
+
filename: 'video.mp4',
|
|
222
|
+
mimetype: 'video/mp4', // Required
|
|
223
|
+
width: 1920, // Optional
|
|
224
|
+
height: 1080
|
|
225
|
+
}]
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Web3 / Contract Interactions
|
|
229
|
+
|
|
230
|
+
### Agent Wallet Architecture
|
|
231
|
+
**Single wallet** using ERC-7702 delegation. The agent has one address (`bot.appAddress`) that acts as both the signer (EOA) and the smart account. The agent owner (the account that registered the app) is a **super admin** of this wallet, retaining full control.
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
bot.viem, bot.appAddress // Access points
|
|
235
|
+
|
|
236
|
+
// Reading
|
|
237
|
+
import { readContract } from 'viem/actions'
|
|
238
|
+
const result = await readContract(bot.viem, { address, abi, functionName: 'balanceOf', args: [user] })
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Writing
|
|
242
|
+
```typescript
|
|
243
|
+
import { writeContract } from 'viem/actions'
|
|
244
|
+
const hash = await writeContract(bot.viem, { address: targetContract, abi,
|
|
245
|
+
functionName: 'transfer', args: [recipient, amount] })
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## External Interactions (Unprompted Messages)
|
|
249
|
+
|
|
250
|
+
`bot.start()` returns a **Hono app**. To extend with additional routes, create a new Hono app and use `.route('/', app)` per https://hono.dev/docs/guides/best-practices#building-a-larger-application
|
|
251
|
+
|
|
252
|
+
**All handler methods available on bot** (webhooks, timers, tasks):
|
|
253
|
+
You need data prior (channelId, etc):
|
|
254
|
+
```typescript
|
|
255
|
+
bot.sendMessage(channelId, msg, opts?) | bot.editMessage(...) | bot.sendReaction(...) | bot.removeEvent(...)
|
|
256
|
+
bot.adminRemoveEvent(...) | bot.pinMessage(...) | bot.unpinMessage(...)
|
|
257
|
+
// Properties: bot.viem, bot.appAddress
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Patterns:** Store channel IDs | Webhooks/timers | Call bot.* directly | Handle errors
|
|
261
|
+
|
|
262
|
+
## Critical Notes
|
|
263
|
+
|
|
264
|
+
1. **User IDs are addresses** - `0x...`, not usernames
|
|
265
|
+
2. **Use `@userId` for mentions** and add mentions in sendMessage options
|
|
266
|
+
3. **Slash commands exclusive** - Never trigger `onMessage`
|
|
267
|
+
4. **Stateless** - Store context externally
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Towns Agent
|
|
2
|
+
|
|
3
|
+
A [Towns Protocol](https://towns.com) agent built with [`@towns-labs/agent`](https://www.npmjs.com/package/@towns-labs/agent).
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
### 1. Install dependencies
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### 2. Create an agent account
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bunx towns-agent create --env prod
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This will prompt for authentication and bot metadata, then output your credentials. Pipe them straight into `.env`:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bunx towns-agent create --env prod >> .env
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or copy `.env.sample` and fill in the values manually:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cp .env.sample .env
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| Variable | Description |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `APP_ADDRESS` | Agent app address |
|
|
34
|
+
| `APP_PRIVATE_DATA` | Agent credentials |
|
|
35
|
+
| `JWT_SECRET` | Webhook security token |
|
|
36
|
+
| `PORT` | Server port (default: 5123) |
|
|
37
|
+
|
|
38
|
+
### 3. Start the agent
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
bun run dev
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 4. Register your webhook
|
|
45
|
+
|
|
46
|
+
Once the server is running, register its URL so Towns can deliver events:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
bunx towns-agent setup
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This reads the app address from your `.env` and prompts for the webhook URL and notification settings.
|
|
53
|
+
|
|
54
|
+
### 5. View or update metadata
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# View current metadata
|
|
58
|
+
bunx towns-agent metadata view
|
|
59
|
+
|
|
60
|
+
# Update metadata interactively
|
|
61
|
+
bunx towns-agent metadata update
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Project Structure
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
src/
|
|
68
|
+
index.ts # Agent initialization and event handlers
|
|
69
|
+
commands.ts # Slash command definitions
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- **`commands.ts`** — Define slash commands that appear in autocomplete.
|
|
73
|
+
- **`index.ts`** — Initialize the agent with `makeTownsAgent` and register event handlers (`onMessage`, `onSlashCommand`, etc.).
|
|
74
|
+
|
|
75
|
+
## Scripts
|
|
76
|
+
|
|
77
|
+
| Script | Description |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `bun run dev` | Start with hot-reload |
|
|
80
|
+
| `bun run start` | Start without hot-reload |
|
|
81
|
+
| `bun run build` | Type-check |
|
|
82
|
+
| `bun run lint` | Lint with oxlint |
|
|
83
|
+
| `bun run fmt` | Format with oxfmt |
|
|
84
|
+
| `bun run fmt:check` | Check formatting |
|
|
85
|
+
|
|
86
|
+
## Updating
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
bunx towns-agent update
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Resources
|
|
93
|
+
|
|
94
|
+
- [AGENTS.md](./AGENTS.md) — API reference for event handlers, payloads, and handler methods
|
|
95
|
+
- [@towns-labs/agent on npm](https://www.npmjs.com/package/@towns-labs/agent)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
|
|
4
|
+
# Build output
|
|
5
|
+
dist/
|
|
6
|
+
|
|
7
|
+
# Environment variables
|
|
8
|
+
.env
|
|
9
|
+
.env.local
|
|
10
|
+
.env.production
|
|
11
|
+
|
|
12
|
+
# Logs
|
|
13
|
+
*.log
|
|
14
|
+
npm-debug.log*
|
|
15
|
+
yarn-debug.log*
|
|
16
|
+
yarn-error.log*
|
|
17
|
+
|
|
18
|
+
# Runtime data
|
|
19
|
+
pids
|
|
20
|
+
*.pid
|
|
21
|
+
*.seed
|
|
22
|
+
*.pid.lock
|
|
23
|
+
|
|
24
|
+
# Coverage directory used by tools like istanbul
|
|
25
|
+
coverage/
|
|
26
|
+
|
|
27
|
+
# IDE
|
|
28
|
+
.vscode/
|
|
29
|
+
.idea/
|
|
30
|
+
|
|
31
|
+
# OS
|
|
32
|
+
.DS_Store
|
|
33
|
+
Thumbs.db
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "towns-agent-quickstart",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc --noEmit",
|
|
9
|
+
"dev": "bun run --watch src/index.ts",
|
|
10
|
+
"fmt": "oxfmt --write ./src",
|
|
11
|
+
"fmt:check": "oxfmt --check ./src",
|
|
12
|
+
"lint": "oxlint ./src",
|
|
13
|
+
"lint:fix": "oxlint ./src --fix",
|
|
14
|
+
"start": "bun run src/index.ts"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@connectrpc/connect-node": "^2.1.0",
|
|
18
|
+
"@hono/node-server": "^1.19.9",
|
|
19
|
+
"@towns-labs/agent": "workspace:^",
|
|
20
|
+
"@towns-labs/proto": "workspace:^",
|
|
21
|
+
"dotenv": "^16.4.5",
|
|
22
|
+
"hono": "^4.11.7",
|
|
23
|
+
"viem": "2.45.1"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/bun": "^1.3.1",
|
|
27
|
+
"@types/node": "^20.14.8",
|
|
28
|
+
"oxfmt": "^0.1.0",
|
|
29
|
+
"oxlint": "^1.41.0",
|
|
30
|
+
"typescript": "~5.8.3"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"/dist"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AgentCommand } from "@towns-labs/agent"
|
|
2
|
+
|
|
3
|
+
// Those commands will be registered to the bot as soon as the bot is initialized
|
|
4
|
+
// and will be available in the slash command autocomplete.
|
|
5
|
+
const commands = [
|
|
6
|
+
{
|
|
7
|
+
name: "help",
|
|
8
|
+
description: "Get help with bot commands",
|
|
9
|
+
},
|
|
10
|
+
] as const satisfies AgentCommand[]
|
|
11
|
+
|
|
12
|
+
export default commands
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import "dotenv/config"
|
|
2
|
+
import { serve } from "@hono/node-server"
|
|
3
|
+
import { makeTownsAgent } from "@towns-labs/agent"
|
|
4
|
+
import { createServer } from "node:http2"
|
|
5
|
+
import commands from "./commands"
|
|
6
|
+
|
|
7
|
+
const bot = await makeTownsAgent(
|
|
8
|
+
process.env.APP_PRIVATE_DATA!,
|
|
9
|
+
process.env.JWT_SECRET!,
|
|
10
|
+
{ commands },
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
bot.onSlashCommand("help", async (handler, { channelId }) => {
|
|
14
|
+
await handler.sendMessage(
|
|
15
|
+
channelId,
|
|
16
|
+
"**Available Commands:**\n\n" +
|
|
17
|
+
"• `/help` - Show this help message\n" +
|
|
18
|
+
"**Message Triggers:**\n\n" +
|
|
19
|
+
"• React with 👋 - I'll wave back\n" +
|
|
20
|
+
'• Say "hello" or "hey" - I\'ll greet you back\n' +
|
|
21
|
+
'• Say "ping" - I\'ll show latency\n' +
|
|
22
|
+
'• Say "react" - I\'ll add a reaction\n',
|
|
23
|
+
)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
bot.onMessage(async (handler, { message, channelId, eventId, createdAt }) => {
|
|
27
|
+
if (message.includes("hello") || message.includes("hey")) {
|
|
28
|
+
await handler.sendMessage(channelId, "Hello there! 👋")
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
if (message.includes("ping")) {
|
|
32
|
+
const now = new Date()
|
|
33
|
+
await handler.sendMessage(
|
|
34
|
+
channelId,
|
|
35
|
+
`Pong! 🏓 ${now.getTime() - createdAt.getTime()}ms`,
|
|
36
|
+
)
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
if (message.includes("react")) {
|
|
40
|
+
await handler.sendReaction(channelId, eventId, "👍")
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
bot.onReaction(async (handler, { reaction, channelId }) => {
|
|
46
|
+
if (reaction === "👋") {
|
|
47
|
+
await handler.sendMessage(channelId, "I saw your wave! 👋")
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const app = bot.start()
|
|
52
|
+
serve({
|
|
53
|
+
fetch: app.fetch,
|
|
54
|
+
port: Number(process.env.PORT) || 5123,
|
|
55
|
+
createServer,
|
|
56
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "esnext",
|
|
4
|
+
"module": "esnext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"resolveJsonModule": true,
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"allowSyntheticDefaultImports": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true,
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noImplicitAny": true,
|
|
16
|
+
"strictNullChecks": true,
|
|
17
|
+
"alwaysStrict": true,
|
|
18
|
+
"noImplicitReturns": true,
|
|
19
|
+
"noFallthroughCasesInSwitch": true,
|
|
20
|
+
"skipLibCheck": true,
|
|
21
|
+
"outDir": "./dist",
|
|
22
|
+
"types": ["node"]
|
|
23
|
+
},
|
|
24
|
+
"include": ["src/**/*"]
|
|
25
|
+
}
|