vantiv.io 1.0.0
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/LICENSE +21 -0
- package/README.md +864 -0
- package/index.js +13 -0
- package/package.json +28 -0
- package/src/classes/Actions/Awaiter.js +202 -0
- package/src/classes/Actions/Channel.js +73 -0
- package/src/classes/Actions/Direct.js +263 -0
- package/src/classes/Actions/Inventory.js +156 -0
- package/src/classes/Actions/Music.js +278 -0
- package/src/classes/Actions/Player.js +377 -0
- package/src/classes/Actions/Public.js +66 -0
- package/src/classes/Actions/Room.js +333 -0
- package/src/classes/Actions/Utils.js +29 -0
- package/src/classes/Actions/lib/AudioStreaming.js +447 -0
- package/src/classes/Caches/MovementCache.js +357 -0
- package/src/classes/Handlers/AxiosErrorHandler.js +68 -0
- package/src/classes/Handlers/ErrorHandler.js +65 -0
- package/src/classes/Handlers/EventHandlers.js +259 -0
- package/src/classes/Handlers/WebSocketHandlers.js +54 -0
- package/src/classes/Managers/ChannelManager.js +303 -0
- package/src/classes/Managers/DanceFloorManagers.js +509 -0
- package/src/classes/Managers/Helpers/CleanupManager.js +130 -0
- package/src/classes/Managers/Helpers/LoggerManager.js +171 -0
- package/src/classes/Managers/Helpers/MetricsManager.js +83 -0
- package/src/classes/Managers/Networking/ConnectionManager.js +259 -0
- package/src/classes/Managers/Networking/CooldownManager.js +516 -0
- package/src/classes/Managers/Networking/EventsManager.js +64 -0
- package/src/classes/Managers/Networking/KeepAliveManager.js +109 -0
- package/src/classes/Managers/Networking/MessageHandler.js +110 -0
- package/src/classes/Managers/Networking/Request.js +329 -0
- package/src/classes/Managers/PermissionManager.js +288 -0
- package/src/classes/WebApi/Category/Grab.js +98 -0
- package/src/classes/WebApi/Category/Item.js +347 -0
- package/src/classes/WebApi/Category/Post.js +154 -0
- package/src/classes/WebApi/Category/Room.js +137 -0
- package/src/classes/WebApi/Category/User.js +88 -0
- package/src/classes/WebApi/webapi.js +52 -0
- package/src/constants/TypesConstants.js +89 -0
- package/src/constants/WebSocketConstants.js +80 -0
- package/src/core/Highrise.js +123 -0
- package/src/core/HighriseWebsocket.js +228 -0
- package/src/utils/ConvertSvgToPng.js +51 -0
- package/src/utils/ModelPool.js +160 -0
- package/src/utils/Models.js +128 -0
- package/src/utils/versionCheck.js +27 -0
- package/src/validators/ConfigValidator.js +205 -0
- package/src/validators/ConnectionValidator.js +65 -0
- package/typings/index.d.ts +3820 -0
package/README.md
ADDED
|
@@ -0,0 +1,864 @@
|
|
|
1
|
+
# Highrise Bot SDK - Unofficial WebSocket SDK for Highrise Bot Development
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
**Highrise Bot SDK** (`highrise-core`) is a production-ready TypeScript/JavaScript SDK for creating advanced bots and automation tools for the Highrise social platform. Built with WebSocket connectivity, spatial intelligence, and memory-optimized architecture, this SDK provides enterprise-grade reliability for scalable bot development.
|
|
9
|
+
|
|
10
|
+
## 📖 Table of Contents
|
|
11
|
+
- [Features](#-features)
|
|
12
|
+
- [Installation](#-installation)
|
|
13
|
+
- [Quick Start](#-quick-start)
|
|
14
|
+
- [License](#-license)
|
|
15
|
+
|
|
16
|
+
# 🚀 Features
|
|
17
|
+
|
|
18
|
+
### Core Capabilities
|
|
19
|
+
- **Real-time WebSocket Connection** - Stable connection with auto-reconnect
|
|
20
|
+
- **Complete Bot API Coverage** - All official Highrise bot endpoints implemented
|
|
21
|
+
- **Advanced Caching System** - Efficient user position tracking and spatial queries and more upcoming
|
|
22
|
+
- **Comprehensive Event Handling** - All major Highrise events supported
|
|
23
|
+
- **Web API Integration** - Access to user, room, item, post, grab data
|
|
24
|
+
|
|
25
|
+
### Performance Highlights
|
|
26
|
+
- **86%+ Memory Reduction** in position tracking using binary encoding
|
|
27
|
+
- **Sub-millisecond** spatial queries for user location
|
|
28
|
+
- **Automatic resource cleanup** and connection management
|
|
29
|
+
- **Object pooling** for reduced garbage collection
|
|
30
|
+
- **98% Memory leak free**
|
|
31
|
+
|
|
32
|
+
# 📦 Installation
|
|
33
|
+
```bash
|
|
34
|
+
npm install highrise-core
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
# 🎯 Quick Start
|
|
38
|
+
### Basic Bot Setup
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
const { Highrise } = require('highrise-core');
|
|
42
|
+
|
|
43
|
+
// Create bot instance
|
|
44
|
+
const bot = new Highrise(
|
|
45
|
+
['ChatEvent', 'UserJoinedEvent', 'UserMovedEvent'], // Events to listen for
|
|
46
|
+
{
|
|
47
|
+
LoggerOptions: {
|
|
48
|
+
showTimestamp: true,
|
|
49
|
+
showMethodName: true,
|
|
50
|
+
colors: true
|
|
51
|
+
},
|
|
52
|
+
autoReconnect: true,
|
|
53
|
+
reconnectDelay: 5000
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Event handlers
|
|
58
|
+
bot.on('Ready', (metadata) => {
|
|
59
|
+
console.log(`Bot connected to room: ${metadata.room.room_name}`);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
bot.on('Chat', async (user, message) => {
|
|
63
|
+
if (message === '!hello') {
|
|
64
|
+
await bot.message.send(`Hello ${user.username}! 👋`);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
bot.on('UserJoined', async (user) => {
|
|
69
|
+
await bot.message.send(`Welcome to the room, ${user.username}! 🎉`);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Connect to Highrise
|
|
73
|
+
bot.login('your_64_character_bot_token', 'your_24_character_room_id');
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
# 🔧 Core Features
|
|
77
|
+
### Movement & Positioning
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
// Make the bot walk to coordinates
|
|
81
|
+
await bot.player.walk(10, 0, 5, 'FrontRight');
|
|
82
|
+
|
|
83
|
+
// Teleport users
|
|
84
|
+
await bot.player.teleport('user_id', 15, 0, 10, 'BackLeft');
|
|
85
|
+
|
|
86
|
+
// Make the bot sit on furniture
|
|
87
|
+
await bot.player.sit('entity_id', 0);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Advanced Spatial Queries
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
// Get players in a 8x8 square area
|
|
94
|
+
const playersInArea = bot.cache.position.getPlayersInSquare(
|
|
95
|
+
{ x: 10, y: 0, z: 10 },
|
|
96
|
+
8
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Find closest player
|
|
100
|
+
const closestPlayer = bot.cache.position.getClosestPlayer({ x: 0, z: 0 });
|
|
101
|
+
|
|
102
|
+
// Get players within 5 units radius (sorted by distance)
|
|
103
|
+
const nearbyPlayers = bot.cache.position.getPlayersInCircle(
|
|
104
|
+
{ x: 10, z: 10 },
|
|
105
|
+
5
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Check if specific user is in area
|
|
109
|
+
const isInVIP = bot.cache.position.isPlayerInRectangle('user_id',
|
|
110
|
+
{
|
|
111
|
+
c1: { x: 20, z: 20 }, // Top-right
|
|
112
|
+
c2: { x: 25, z: 20 }, // Top-left
|
|
113
|
+
c3: { x: 25, z: 25 }, // bottom-right
|
|
114
|
+
c4: { x: 20, z: 25 } // bottom-left
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Communication
|
|
120
|
+
|
|
121
|
+
```javascript
|
|
122
|
+
// Room messages
|
|
123
|
+
await bot.message.send("Hello everyone!");
|
|
124
|
+
|
|
125
|
+
// Private whispers
|
|
126
|
+
await bot.whisper.send("user_id", "This is a private message!");
|
|
127
|
+
|
|
128
|
+
// Direct messages
|
|
129
|
+
await bot.direct.send("conversation_id", "Direct message content");
|
|
130
|
+
|
|
131
|
+
// Send room invites via DM
|
|
132
|
+
await bot.direct.invite("conversation_id", { room_id: "target_room_id" });
|
|
133
|
+
|
|
134
|
+
// Send world invites via DM
|
|
135
|
+
await bot.direct.invite("conversation_id", { world_id: "target_world_id" });
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Moderation
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
// Basic moderation
|
|
142
|
+
await bot.player.moderation.kick("user_id");
|
|
143
|
+
await bot.player.moderation.mute("user_id", 5 * 60 * 1000); // 5 minutes (default)
|
|
144
|
+
await bot.player.moderation.ban("user_id", 60 * 60 * 1000); // 1 hour (default)
|
|
145
|
+
|
|
146
|
+
// Room privilege management
|
|
147
|
+
await bot.room.moderator.add("user_id");
|
|
148
|
+
await bot.room.designer.add("user_id");
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Economy & Items
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
// Check wallet
|
|
155
|
+
const wallet = await bot.inventory.wallet.get();
|
|
156
|
+
console.log(`Gold: ${wallet.gold}, Boosts: ${wallet.boost_tokens}`);
|
|
157
|
+
|
|
158
|
+
// Send tips
|
|
159
|
+
await bot.player.tip("user_id", 100); // 100 gold bars (default: 1)
|
|
160
|
+
|
|
161
|
+
// Buy bot items
|
|
162
|
+
await bot.inventory.item.buy("item_id");
|
|
163
|
+
|
|
164
|
+
// Set bot outfit
|
|
165
|
+
await bot.inventory.set([
|
|
166
|
+
{
|
|
167
|
+
type: 'clothing',
|
|
168
|
+
amount: 1,
|
|
169
|
+
id: 'shirt-n_cooltshirt',
|
|
170
|
+
account_bound: false,
|
|
171
|
+
active_palette: null
|
|
172
|
+
}
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
// set outfit back to default
|
|
176
|
+
await bot.inventory.set();
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## 🌐 Web API Integration
|
|
180
|
+
```javascript
|
|
181
|
+
// User information
|
|
182
|
+
const user = await bot.webapi.user.get("username_or_id");
|
|
183
|
+
console.log(`${user.username} - ${user.followers} followers`);
|
|
184
|
+
|
|
185
|
+
// Room information
|
|
186
|
+
const room = await bot.webapi.room.get("room_id");
|
|
187
|
+
console.log(`${room.name} - ${room.connectedUsers} users online`);
|
|
188
|
+
|
|
189
|
+
// Item catalog
|
|
190
|
+
const items = await bot.webapi.item.search({ query: "dragon", limit: 10 });
|
|
191
|
+
items.items.forEach(item => {
|
|
192
|
+
console.log(`${item.name} - ${item.pricing.gems} gems`);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Posts and social
|
|
196
|
+
const posts = await bot.webapi.post.getByAuthor("user_id");
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## 📊 Performance Metrics
|
|
200
|
+
The SDK is optimized for high-performance scenarios:
|
|
201
|
+
|
|
202
|
+
- **Movement Cache**: Tracks 1000+ users with ~5KB memory usage
|
|
203
|
+
- **Spatial Queries**: Process 10,000+ positions in <1ms
|
|
204
|
+
- **WebSocket**: Handles 100+ events per second efficiently
|
|
205
|
+
- **Memory**: 80-95% reduction in position storage through binary encoding
|
|
206
|
+
|
|
207
|
+
## 📊 Metrics & Monitoring
|
|
208
|
+
|
|
209
|
+
Access real-time bot performance metrics with `bot.getMetrics()`.
|
|
210
|
+
|
|
211
|
+
### **Get Metrics Object**
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
const metrics = bot.getMetrics();
|
|
215
|
+
console.log(metrics);
|
|
216
|
+
|
|
217
|
+
// Returns:
|
|
218
|
+
{
|
|
219
|
+
uptime: '2h 15m',
|
|
220
|
+
connected: true,
|
|
221
|
+
room: "Unfairly's room",
|
|
222
|
+
messages: 1500,
|
|
223
|
+
events: 300,
|
|
224
|
+
errors: 2,
|
|
225
|
+
cache: {
|
|
226
|
+
users: 45,
|
|
227
|
+
memory: '5.2 KB',
|
|
228
|
+
active: 12,
|
|
229
|
+
changes: 28
|
|
230
|
+
},
|
|
231
|
+
pendingReq: {
|
|
232
|
+
fireForget: 3,
|
|
233
|
+
reqRes: 1
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### **Display Metrics:**
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
// Simple display
|
|
242
|
+
const m = bot.getMetrics();
|
|
243
|
+
console.log(`📊 Bot Metrics:`);
|
|
244
|
+
console.log(`├─ Room: ${m.room}`);
|
|
245
|
+
console.log(`├─ Uptime: ${m.uptime}`);
|
|
246
|
+
console.log(`├─ Status: ${m.connected ? '✅ Connected' : '❌ Disconnected'}`);
|
|
247
|
+
console.log(`├─ Messages: ${m.messages}`);
|
|
248
|
+
console.log(`├─ Events: ${m.events}`);
|
|
249
|
+
console.log(`├─ Errors: ${m.errors}`);
|
|
250
|
+
console.log(`├─ Cache: ${m.cache.users} users (${m.cache.active} active)`);
|
|
251
|
+
console.log(`└─ Memory: ${m.cache.memory} used`);
|
|
252
|
+
|
|
253
|
+
// Auto-update every 30s
|
|
254
|
+
setInterval(() => {
|
|
255
|
+
const metrics = bot.getMetrics();
|
|
256
|
+
console.log(`[${new Date().toLocaleTimeString()}] ${metrics.room} - ${metrics.uptime} - ${metrics.messages} msgs`);
|
|
257
|
+
}, 30000);
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## 🔄 Await System
|
|
261
|
+
|
|
262
|
+
The await system allows your bot to wait for specific events with powerful filtering capabilities with **unique user tracking** to prevent spam and ensure fairness.
|
|
263
|
+
|
|
264
|
+
## 🎯 Basic Usage
|
|
265
|
+
|
|
266
|
+
### Wait for a Single Chat Message
|
|
267
|
+
|
|
268
|
+
```javascript
|
|
269
|
+
// Wait for any user to type !start
|
|
270
|
+
const results = await bot.await.chat(
|
|
271
|
+
(user, message) => message === '!start',
|
|
272
|
+
30000, // 30 second timeout
|
|
273
|
+
1, // Collect 1 match
|
|
274
|
+
false // Allow multiple from same user (default)
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
if (results.length > 0) {
|
|
278
|
+
const [[user, message]] = results;
|
|
279
|
+
await bot.message.send(`Game started by ${user.username}! 🎮`);
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Wait for a Whisper
|
|
284
|
+
|
|
285
|
+
```javascript
|
|
286
|
+
// Wait for first help request from each user
|
|
287
|
+
const helpRequests = await bot.await.whisper(
|
|
288
|
+
(user, message) => message.includes('help'),
|
|
289
|
+
15000,
|
|
290
|
+
10, // Collect up to 10 requests
|
|
291
|
+
true // Only one per user
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
if (helpRequests.length > 0) {
|
|
295
|
+
const [[user, message]] = helpRequests;
|
|
296
|
+
await bot.whisper.send(user.id, "I'm here to help! What do you need?");
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Wait for a Direct Message
|
|
301
|
+
|
|
302
|
+
```javascript
|
|
303
|
+
// Wait for unique confirmations
|
|
304
|
+
const confirmations = await bot.await.direct(
|
|
305
|
+
(user, message, conversation) => message === '!confirm',
|
|
306
|
+
60000,
|
|
307
|
+
5, // Up to 5 confirmations
|
|
308
|
+
true // One confirmation per user
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
if (confirmations.length > 0) {
|
|
312
|
+
const [[user, message, conversation]] = confirmations;
|
|
313
|
+
await bot.direct.send(conversation.id, "Order confirmed! ✅");
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Wait for a Tip
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
// Wait for first-time donors only
|
|
321
|
+
const tips = await bot.await.tip(
|
|
322
|
+
(sender, receiver, currency) => receiver.id === bot.info.user.id,
|
|
323
|
+
120000,
|
|
324
|
+
20, // Up to 20 donors
|
|
325
|
+
true // Only count each user's first tip
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
if (tips.length > 0) {
|
|
329
|
+
const [[sender, receiver, currency]] = tips;
|
|
330
|
+
await bot.message.send(`Thank you ${sender.username} for your first donation! 💰`);
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Wait for Player Movement
|
|
335
|
+
|
|
336
|
+
```javascript
|
|
337
|
+
// Wait for first-time visitors to area
|
|
338
|
+
const entrants = await bot.await.movement(
|
|
339
|
+
(user, position, anchor) =>
|
|
340
|
+
position.x >= 10 && position.x <= 15 &&
|
|
341
|
+
position.z >= 10 && position.z <= 15,
|
|
342
|
+
60000,
|
|
343
|
+
30, // Up to 30 unique visitors
|
|
344
|
+
true // Only track first visit per user
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
if (entrants.length > 0) {
|
|
348
|
+
const [[user, position, anchor]] = entrants;
|
|
349
|
+
await bot.message.send(`Welcome to the dance floor, ${user.username}! First time here? 💃`);
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## 🚀 Advanced Usage
|
|
354
|
+
|
|
355
|
+
### Chat - Fair Voting System
|
|
356
|
+
|
|
357
|
+
```javascript
|
|
358
|
+
// One vote per user (no cheating!)
|
|
359
|
+
await bot.message.send("Vote for your favorite: !pizza, !burger, or !sushi");
|
|
360
|
+
|
|
361
|
+
const votes = await bot.await.chat(
|
|
362
|
+
(user, message) => ['!pizza', '!burger', '!sushi'].includes(message),
|
|
363
|
+
45000, // 45 seconds
|
|
364
|
+
100, // Collect up to 100 votes
|
|
365
|
+
true // One vote per user - prevents spamming!
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
// Process results - each user counted only once
|
|
369
|
+
const voteCount = {
|
|
370
|
+
pizza: votes.filter(([user, message]) => message === '!pizza').length,
|
|
371
|
+
burger: votes.filter(([user, message]) => message === '!burger').length,
|
|
372
|
+
sushi: votes.filter(([user, message]) => message === '!sushi').length
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const winner = Object.keys(voteCount).reduce((a, b) => voteCount[a] > voteCount[b] ? a : b);
|
|
376
|
+
await bot.message.send(`The winner is ${winner} with ${voteCount[winner]} unique votes! 🏆`);
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Whisper - Fair Moderation Applications
|
|
380
|
+
|
|
381
|
+
```javascript
|
|
382
|
+
// One application per user
|
|
383
|
+
await bot.message.send("Whisper me '!mod' to apply for moderator");
|
|
384
|
+
|
|
385
|
+
const modApplications = await bot.await.whisper(
|
|
386
|
+
(user, message) => message === '!mod',
|
|
387
|
+
120000, // 2 minutes
|
|
388
|
+
50, // Up to 50 applications
|
|
389
|
+
true // Prevent users from applying multiple times
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
// Process each unique applicant
|
|
393
|
+
for (const [user, message] of modApplications) {
|
|
394
|
+
const isEligible = await checkUserEligibility(user.id);
|
|
395
|
+
|
|
396
|
+
if (isEligible) {
|
|
397
|
+
await bot.whisper.send(user.id, "You've been approved as moderator! 🛡️");
|
|
398
|
+
await bot.room.moderator.add(user.id);
|
|
399
|
+
} else {
|
|
400
|
+
await bot.whisper.send(user.id, "Sorry, you don't meet the requirements yet.");
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Direct - Unique Support Tickets
|
|
406
|
+
|
|
407
|
+
```javascript
|
|
408
|
+
// One support ticket per user
|
|
409
|
+
const supportTickets = await bot.await.direct(
|
|
410
|
+
(user, message, conversation) =>
|
|
411
|
+
message.includes('help') || message.includes('support') || message.includes('issue'),
|
|
412
|
+
300000, // 5 minutes
|
|
413
|
+
20, // Handle 20 unique tickets
|
|
414
|
+
true // One ticket per user - prevents spam
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
// Create tickets for each unique user
|
|
418
|
+
for (const [user, message, conversation] of supportTickets) {
|
|
419
|
+
const ticketId = createSupportTicket(user.id, message);
|
|
420
|
+
|
|
421
|
+
await bot.direct.send(conversation.id,
|
|
422
|
+
`Support ticket #${ticketId} created! We'll assist you shortly. ⏱️`
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
// Assign to available support agent
|
|
426
|
+
assignTicketToAgent(ticketId, conversation.id);
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Tip - Unique Donor Fundraiser
|
|
431
|
+
|
|
432
|
+
```javascript
|
|
433
|
+
// Track unique donors only
|
|
434
|
+
await bot.message.send("🎗️ Fundraiser started! First donation from each user counts!");
|
|
435
|
+
|
|
436
|
+
const uniqueDonors = await bot.await.tip(
|
|
437
|
+
(sender, receiver, currency) =>
|
|
438
|
+
receiver.id === bot.info.user.id && currency.amount >= 50,
|
|
439
|
+
600000, // 10 minutes
|
|
440
|
+
200, // Track up to 200 unique donors
|
|
441
|
+
true // Only count first donation from each user
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
// Calculate and announce totals
|
|
445
|
+
let totalRaised = 0;
|
|
446
|
+
uniqueDonors.forEach(([sender, receiver, currency]) => {
|
|
447
|
+
totalRaised += currency.amount;
|
|
448
|
+
|
|
449
|
+
// Acknowledge unique donors
|
|
450
|
+
if (currency.amount >= 500) {
|
|
451
|
+
bot.message.send(`🎉 HUGE thanks to ${sender.username} for first-time donation!`);
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
await bot.message.send(
|
|
456
|
+
`🏁 Fundraiser complete! ${uniqueDonors.length} unique donors raised ${totalRaised} gold! Thank you all!`
|
|
457
|
+
);
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Movement - First-Time Achievement System
|
|
461
|
+
|
|
462
|
+
```javascript
|
|
463
|
+
// Award achievements for first-time discoveries
|
|
464
|
+
const treasureSpots = [
|
|
465
|
+
{ x: 5, z: 5, name: "Ancient Ruins", reward: 100 },
|
|
466
|
+
{ x: 15, z: 20, name: "Crystal Cave", reward: 150 },
|
|
467
|
+
{ x: 25, z: 10, name: "Dragon's Lair", reward: 200 }
|
|
468
|
+
];
|
|
469
|
+
|
|
470
|
+
await bot.message.send("🔍 Treasure hunt! Find hidden spots for one-time rewards!");
|
|
471
|
+
|
|
472
|
+
const firstDiscoveries = await bot.await.movement(
|
|
473
|
+
(user, position, anchor) => {
|
|
474
|
+
return treasureSpots.some(spot =>
|
|
475
|
+
Math.abs(position.x - spot.x) < 2 &&
|
|
476
|
+
Math.abs(position.z - spot.z) < 2
|
|
477
|
+
);
|
|
478
|
+
},
|
|
479
|
+
300000, // 5 minutes
|
|
480
|
+
50, // Up to 50 unique discoverers
|
|
481
|
+
true // Only first discovery per user per spot
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
// Award first-time discoverers
|
|
485
|
+
for (const [user, position, anchor] of firstDiscoveries) {
|
|
486
|
+
const treasure = treasureSpots.find(spot =>
|
|
487
|
+
Math.abs(position.x - spot.x) < 2 &&
|
|
488
|
+
Math.abs(position.z - spot.z) < 2
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
await bot.message.send(
|
|
492
|
+
`🏆 ${user.username} discovered ${treasure.name} for the first time! +${treasure.reward} gold!`
|
|
493
|
+
);
|
|
494
|
+
await bot.player.tip(user.id, treasure.reward);
|
|
495
|
+
|
|
496
|
+
// Keep spot available for other users
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
## 🎮 Complex Scenarios
|
|
501
|
+
|
|
502
|
+
### Multi-Stage Registration (Unique Users)
|
|
503
|
+
|
|
504
|
+
```javascript
|
|
505
|
+
// Complete registration - one per user
|
|
506
|
+
await bot.message.send("Type !register to start (one registration per user)");
|
|
507
|
+
|
|
508
|
+
const starters = await bot.await.chat(
|
|
509
|
+
(user, message) => message === '!register',
|
|
510
|
+
30000,
|
|
511
|
+
100, // Up to 100 registrations
|
|
512
|
+
true // One registration per user
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
if (starters.length > 0) {
|
|
516
|
+
for (const [user, message] of starters) {
|
|
517
|
+
// Each unique user gets personalized registration
|
|
518
|
+
await bot.whisper.send(user.id, "What username should we use for your account?");
|
|
519
|
+
|
|
520
|
+
const usernameResponse = await bot.await.whisper(
|
|
521
|
+
(u, msg) => u.id === user.id && msg.length >= 3,
|
|
522
|
+
60000,
|
|
523
|
+
1,
|
|
524
|
+
false // Allow multiple responses from same user
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
if (usernameResponse.length > 0) {
|
|
528
|
+
const [[u, preferredUsername]] = usernameResponse;
|
|
529
|
+
|
|
530
|
+
// Complete registration for this unique user
|
|
531
|
+
await completeRegistration(user.id, preferredUsername);
|
|
532
|
+
await bot.whisper.send(user.id, "Registration complete! Welcome! 🎉");
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### Real-time Event Coordination (Unique Participants)
|
|
539
|
+
|
|
540
|
+
```javascript
|
|
541
|
+
// Coordinate unique users for an event
|
|
542
|
+
await bot.message.send("🎪 Event starting! Stand in circle AND type !ready");
|
|
543
|
+
|
|
544
|
+
const [circleUsers, readyUsers] = await Promise.all([
|
|
545
|
+
// Wait for unique users in circle
|
|
546
|
+
bot.await.movement(
|
|
547
|
+
(user, position, anchor) =>
|
|
548
|
+
position.x >= 8 && position.x <= 12 &&
|
|
549
|
+
position.z >= 8 && position.z <= 12,
|
|
550
|
+
60000,
|
|
551
|
+
50,
|
|
552
|
+
true // Unique users only
|
|
553
|
+
),
|
|
554
|
+
|
|
555
|
+
// Wait for unique users typing ready
|
|
556
|
+
bot.await.chat(
|
|
557
|
+
(user, message) => message === '!ready',
|
|
558
|
+
60000,
|
|
559
|
+
50,
|
|
560
|
+
true // One ready per user
|
|
561
|
+
)
|
|
562
|
+
]);
|
|
563
|
+
|
|
564
|
+
// Find unique users who did both
|
|
565
|
+
const uniqueParticipants = circleUsers.filter(([user1]) =>
|
|
566
|
+
readyUsers.some(([user2]) => user2.id === user1.id)
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
await bot.message.send(
|
|
570
|
+
`${uniqueParticipants.length} unique players are ready! Starting event... 🚀`
|
|
571
|
+
);
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### Advanced Movement Tracking (Unique Patterns)
|
|
575
|
+
|
|
576
|
+
```javascript
|
|
577
|
+
// Track unique users with movement patterns
|
|
578
|
+
const movementPatterns = await bot.await.movement(
|
|
579
|
+
(user, position, anchor) => {
|
|
580
|
+
const previousData = bot.cache.position.get(user.id);
|
|
581
|
+
if (!previousData) return false;
|
|
582
|
+
|
|
583
|
+
const prevPos = previousData.position;
|
|
584
|
+
const distance = Math.sqrt(
|
|
585
|
+
Math.pow(position.x - prevPos.x, 2) +
|
|
586
|
+
Math.pow(position.z - prevPos.z, 2)
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
// Detect first teleport from each user
|
|
590
|
+
const isFirstTeleport = distance > 20;
|
|
591
|
+
|
|
592
|
+
// Detect first sitting/standing transition per user
|
|
593
|
+
const wasSitting = previousData.anchor !== null;
|
|
594
|
+
const isSitting = anchor !== null;
|
|
595
|
+
const firstPostureChange = wasSitting !== isSitting;
|
|
596
|
+
|
|
597
|
+
return isFirstTeleport || firstPostureChange;
|
|
598
|
+
},
|
|
599
|
+
120000,
|
|
600
|
+
30, // Track 30 unique users
|
|
601
|
+
true // Only track first occurrence per user
|
|
602
|
+
);
|
|
603
|
+
|
|
604
|
+
// Analyze unique movement patterns
|
|
605
|
+
movementPatterns.forEach(([user, position, anchor]) => {
|
|
606
|
+
if (anchor) {
|
|
607
|
+
bot.utils.logger.info('Movement', `${user.username} sat down for first time`);
|
|
608
|
+
} else {
|
|
609
|
+
bot.utils.logger.info('Movement', `${user.username} stood up for first time`);
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
## 💡 Pro Tips
|
|
615
|
+
|
|
616
|
+
### 1. Always Use Unique Users for Fairness
|
|
617
|
+
```javascript
|
|
618
|
+
// Fair system - one entry per user
|
|
619
|
+
const entries = await bot.await.chat(
|
|
620
|
+
filter,
|
|
621
|
+
timeout,
|
|
622
|
+
max,
|
|
623
|
+
true // Critical for contests, voting, rewards
|
|
624
|
+
);
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
### 2. Combine Unique & Non-Unique Await
|
|
628
|
+
```javascript
|
|
629
|
+
// Collect first response from each user, then allow follow-ups
|
|
630
|
+
const firstResponses = await bot.await.chat(filter1, 30000, 10, true);
|
|
631
|
+
const followUpResponses = await bot.await.chat(filter2, 30000, 20, false);
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### 3. Smart Timeouts for Unique Collections
|
|
635
|
+
```javascript
|
|
636
|
+
// Longer timeout when waiting for many unique users
|
|
637
|
+
const responses = await bot.await.direct(
|
|
638
|
+
filter,
|
|
639
|
+
120000, // 2 minutes for 20 unique users
|
|
640
|
+
20,
|
|
641
|
+
true
|
|
642
|
+
);
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
### 4. Handle Unique Collections Gracefully
|
|
646
|
+
```javascript
|
|
647
|
+
try {
|
|
648
|
+
const uniqueResponses = await bot.await.whisper(
|
|
649
|
+
(user, message) => message === '!join',
|
|
650
|
+
60000,
|
|
651
|
+
50,
|
|
652
|
+
true // Unique users only
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
if (uniqueResponses.length > 0) {
|
|
656
|
+
await bot.message.send(`${uniqueResponses.length} unique users joined!`);
|
|
657
|
+
} else {
|
|
658
|
+
await bot.message.send("No new users joined. 😔");
|
|
659
|
+
}
|
|
660
|
+
} catch (error) {
|
|
661
|
+
bot.utils.logger.error('Await', 'Unique await failed', error);
|
|
662
|
+
}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
### 5. Track User Participation
|
|
666
|
+
```javascript
|
|
667
|
+
// Keep track of which users have participated
|
|
668
|
+
const participatedUsers = new Set();
|
|
669
|
+
|
|
670
|
+
bot.on('Chat', async (user, message) => {
|
|
671
|
+
if (message === '!play' && !participatedUsers.has(user.id)) {
|
|
672
|
+
participatedUsers.add(user.id);
|
|
673
|
+
await bot.message.send(`${user.username} joined for the first time!`);
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
The enhanced await system with `uniqueUsers` parameter transforms your bot from simple event collection into a **fair, spam-resistant** system perfect for games, contests, voting, and any scenario where you need to track user participation uniquely!
|
|
679
|
+
|
|
680
|
+
## 🔌 Event System
|
|
681
|
+
|
|
682
|
+
### Available Events
|
|
683
|
+
|
|
684
|
+
```javascript
|
|
685
|
+
bot.on('Ready', (metadata) => {
|
|
686
|
+
// Bot connected and ready
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
bot.on('Chat', (user, message) => {
|
|
690
|
+
// Room message received
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
bot.on('Whisper', (user, message) => {
|
|
694
|
+
// Private whisper received
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
bot.on('Movement', (user, position, anchor) => {
|
|
698
|
+
// User moved
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
bot.on('UserJoined', (user, position) => {
|
|
702
|
+
// User entered the room
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
bot.on('UserLeft', (user) => {
|
|
706
|
+
// User left the room
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
bot.on('Direct', (user, message, conversation) => {
|
|
710
|
+
// Direct message received
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
bot.on('Tip', (sender, receiver, currency) => {
|
|
714
|
+
// Tip received or sent
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
bot.on('Moderation', (moderator, target, action) => {
|
|
718
|
+
// Moderation action occurred
|
|
719
|
+
});
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
## ⚙️ Configuration
|
|
723
|
+
|
|
724
|
+
### Bot Options
|
|
725
|
+
|
|
726
|
+
```javascript
|
|
727
|
+
const bot = new Highrise(events, {
|
|
728
|
+
LoggerOptions: {
|
|
729
|
+
showTimestamp: true, // Show timestamps in logs
|
|
730
|
+
showMethodName: true, // Show method names in logs
|
|
731
|
+
colors: true // Color-coded log output
|
|
732
|
+
},
|
|
733
|
+
autoReconnect: true, // Auto-reconnect on disconnect
|
|
734
|
+
reconnectDelay: 5000 // Delay between reconnect attempts (ms)
|
|
735
|
+
});
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
### Sender Configuration
|
|
739
|
+
|
|
740
|
+
```javascript
|
|
741
|
+
// Configure request timeouts and retries
|
|
742
|
+
bot.configureSenders({
|
|
743
|
+
defaultTimeout: 10000, // 10 second timeout
|
|
744
|
+
maxRetries: 3, // Maximum retry attempts
|
|
745
|
+
retryDelay: 100 // Delay between retries (ms)
|
|
746
|
+
});
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
## 🛠️ Advanced Usage
|
|
750
|
+
|
|
751
|
+
### Custom Event Handlers
|
|
752
|
+
|
|
753
|
+
```javascript
|
|
754
|
+
// Register custom event processing
|
|
755
|
+
bot.on('Movement', async (user, position) => {
|
|
756
|
+
const isInVIP = bot.cache.position.isPlayerInRectangle(user.id,
|
|
757
|
+
{
|
|
758
|
+
c1: { x: 20, z: 20 }, // Top-right
|
|
759
|
+
c2: { x: 25, z: 20 }, // Top-left
|
|
760
|
+
c3: { x: 25, z: 25 }, // bottom-right
|
|
761
|
+
c4: { x: 20, z: 25 } // bottom-left
|
|
762
|
+
}
|
|
763
|
+
);
|
|
764
|
+
|
|
765
|
+
// Custom movement logic
|
|
766
|
+
if (isInVIP) {
|
|
767
|
+
await bot.whisper.send(user.id, "You're in the VIP area! 🎉");
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
### Rate Limiting
|
|
773
|
+
|
|
774
|
+
```javascript
|
|
775
|
+
// Implement custom rate limiting
|
|
776
|
+
class RateLimitedBot {
|
|
777
|
+
constructor(bot) {
|
|
778
|
+
this.bot = bot;
|
|
779
|
+
this.lastMessage = 0;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
async sendMessage(message) {
|
|
783
|
+
const now = Date.now();
|
|
784
|
+
if (now - this.lastMessage < 1000) {
|
|
785
|
+
await bot.utils.sleep(1000 - (now - this.lastMessage));
|
|
786
|
+
}
|
|
787
|
+
this.lastMessage = Date.now();
|
|
788
|
+
return this.bot.message.send(message);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
## 📈 Real-World Example
|
|
794
|
+
|
|
795
|
+
### Welcome Bot
|
|
796
|
+
|
|
797
|
+
```javascript
|
|
798
|
+
const { Highrise } = require('highrise-core');
|
|
799
|
+
|
|
800
|
+
const bot = new Highrise(['ChatEvent', 'UserJoinedEvent', 'UserMovedEvent']);
|
|
801
|
+
|
|
802
|
+
// Track new users
|
|
803
|
+
const newUsers = new Set();
|
|
804
|
+
|
|
805
|
+
bot.on('Ready', () => {
|
|
806
|
+
console.log('Welcome bot is online!');
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
bot.on('UserJoined', async (user) => {
|
|
810
|
+
newUsers.add(user.id);
|
|
811
|
+
|
|
812
|
+
if (newUsers.has(user.id)) {
|
|
813
|
+
await bot.message.send(`Welcome ${user.username}! Enjoy your stay! 🏠`);
|
|
814
|
+
newUsers.delete(user.id);
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
bot.on('UserLeft', (user) => {
|
|
819
|
+
newUsers.delete(user.id);
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
bot.on('Chat', async (user, message) => {
|
|
823
|
+
// Remove from new users if they chat
|
|
824
|
+
newUsers.delete(user.id);
|
|
825
|
+
|
|
826
|
+
// Simple commands
|
|
827
|
+
if (message === '!help') {
|
|
828
|
+
await bot.message.send('Available commands: !help, !info, !position');
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
if (message === '!position') {
|
|
832
|
+
const position = await bot.room.users.position(user.id);
|
|
833
|
+
await bot.whisper.send(user.id, `Your position: ${position.x}, ${position.y}, ${position.z}`);
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
bot.login('your_bot_token', 'your_room_id');
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
## 🔍 Key Benefits
|
|
841
|
+
|
|
842
|
+
- **Reliable**: Automatic reconnection and error recovery
|
|
843
|
+
- **Fast**: Optimized caching and efficient data structures and event emition
|
|
844
|
+
- **Comprehensive**: Full Highrise API coverage
|
|
845
|
+
- **Developer-Friendly**: Intuitive API with full TypeScript support
|
|
846
|
+
- **Production-Ready**: Built-in monitoring and performance tracking
|
|
847
|
+
|
|
848
|
+
## 📚 Documentation
|
|
849
|
+
|
|
850
|
+
For complete API documentation, check the TypeScript definitions in `index.d.ts` or visit our documentation site (will be available soon).
|
|
851
|
+
|
|
852
|
+
## 🐛 Issues and Support
|
|
853
|
+
|
|
854
|
+
Found a bug or need help? Please send a message to me in discord @oqs0_ with:
|
|
855
|
+
- sdk version
|
|
856
|
+
- Error logs
|
|
857
|
+
- Steps to reproduce
|
|
858
|
+
|
|
859
|
+
## 📄 License
|
|
860
|
+
|
|
861
|
+
MIT License - Copyright (c) 2025 Yahya Ahmed
|
|
862
|
+
|
|
863
|
+
|
|
864
|
+
**Ready to build?** Start with the [Quick Start](#-quick-start) section and check the examples for common use cases!
|