highrise.bot 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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +86 -0
  3. package/index.js +13 -0
  4. package/package.json +29 -0
  5. package/src/classes/Actions/Awaiter.js +203 -0
  6. package/src/classes/Actions/Channel.js +327 -0
  7. package/src/classes/Actions/Direct.js +390 -0
  8. package/src/classes/Actions/Inventory.js +193 -0
  9. package/src/classes/Actions/Music.js +243 -0
  10. package/src/classes/Actions/Outfit.js +175 -0
  11. package/src/classes/Actions/Player.js +604 -0
  12. package/src/classes/Actions/Public.js +143 -0
  13. package/src/classes/Actions/Room.js +495 -0
  14. package/src/classes/Actions/Utils.js +77 -0
  15. package/src/classes/Actions/lib/AudioStreaming.js +695 -0
  16. package/src/classes/Caches/MovementCache.js +364 -0
  17. package/src/classes/Handlers/AxiosErrorHandler.js +68 -0
  18. package/src/classes/Handlers/ErrorHandler.js +65 -0
  19. package/src/classes/Handlers/EventHandlers.js +193 -0
  20. package/src/classes/Handlers/WebSocketHandlers.js +126 -0
  21. package/src/classes/Managers/CooldownManager.js +516 -0
  22. package/src/classes/Managers/DanceFloorManagers.js +609 -0
  23. package/src/classes/Managers/Helpers/CleanupManager.js +130 -0
  24. package/src/classes/Managers/Helpers/HighriseError.js +107 -0
  25. package/src/classes/Managers/Helpers/HighriseResponse.js +33 -0
  26. package/src/classes/Managers/Helpers/LoggerManager.js +171 -0
  27. package/src/classes/Managers/Helpers/MetricsManager.js +83 -0
  28. package/src/classes/Managers/Networking/ConnectionManager.js +253 -0
  29. package/src/classes/Managers/Networking/EventsManager.js +64 -0
  30. package/src/classes/Managers/Networking/KeepAliveManager.js +58 -0
  31. package/src/classes/Managers/Networking/MessageHandler.js +123 -0
  32. package/src/classes/Managers/Networking/Request.js +323 -0
  33. package/src/classes/Managers/RoleManager.js +322 -0
  34. package/src/classes/WebApi/Category/Grab.js +98 -0
  35. package/src/classes/WebApi/Category/Item.js +347 -0
  36. package/src/classes/WebApi/Category/Post.js +154 -0
  37. package/src/classes/WebApi/Category/Room.js +137 -0
  38. package/src/classes/WebApi/Category/User.js +88 -0
  39. package/src/classes/WebApi/webapi.js +52 -0
  40. package/src/constants/ErrorConstants.js +109 -0
  41. package/src/constants/TypesConstants.js +91 -0
  42. package/src/constants/WebSocketConstants.js +78 -0
  43. package/src/core/Highrise.js +192 -0
  44. package/src/core/HighriseWebsocket.js +242 -0
  45. package/src/utils/ConvertSvgToPng.js +51 -0
  46. package/src/utils/Job.js +130 -0
  47. package/src/utils/ModelPool.js +160 -0
  48. package/src/utils/Models.js +128 -0
  49. package/src/utils/versionCheck.js +27 -0
  50. package/src/validators/ConfigValidator.js +195 -0
  51. package/src/validators/ConnectionValidator.js +65 -0
  52. package/typings/index.d.ts +5042 -0
@@ -0,0 +1,243 @@
1
+ const { IcecastStreamer, IcecastQueue } = require('highrise.js/src/classes/Actions/lib/AudioStreaming.js');
2
+ const EventEmitter = require('events');
3
+
4
+ class MusicClass extends EventEmitter {
5
+ constructor(bot, config = {}) {
6
+ super();
7
+ this.bot = bot;
8
+ this.config = {
9
+ enabled: config.enabled || false,
10
+ icecast: config.icecast || {},
11
+ queue: config.queue || {},
12
+ serverPort: config.serverPort || 3000,
13
+ maxQueueSize: config.maxQueueSize || 100,
14
+ defaultVolume: config.defaultVolume || 80,
15
+ allowedFormats: config.allowedFormats || ['youtube', 'direct']
16
+ };
17
+
18
+ if (!this.config.enabled) {
19
+ console.log('Music module is disabled. Enable it in config to use music features.');
20
+ return;
21
+ }
22
+
23
+ this.initializeStreamer();
24
+ this.setupEventHandlers();
25
+ }
26
+
27
+ initializeStreamer() {
28
+ try {
29
+ this.streamer = new IcecastStreamer(this.config.icecast);
30
+ this.queue = new IcecastQueue(this.streamer, { ...this.config.queue, ...this.config.icecast });
31
+
32
+ this.streamer.on('playbackStart', (track) => {
33
+ this.emit('MusicStart', track);
34
+ });
35
+
36
+ this.streamer.on('playbackEnd', (track) => {
37
+ this.emit('MusicEnd', track);
38
+ });
39
+
40
+ this.streamer.on('progress', (data) => {
41
+ this.emit('Progress', data);
42
+ });
43
+
44
+ this.streamer.on('error', (error) => {
45
+ this.emit('Error', error);
46
+ });
47
+
48
+ this.streamer.on('streamStopped', () => {
49
+ this.emit('StreamEnded');
50
+ });
51
+ } catch (error) {
52
+ console.error('Failed to initialize Icecast streamer:', error);
53
+ throw error;
54
+ }
55
+ }
56
+
57
+ setupEventHandlers() {
58
+ this.bot.on('Ready', () => {
59
+ this.emit('Ready');
60
+ });
61
+
62
+ this.bot.on('error', (error) => {
63
+ this.emit('error', error);
64
+ });
65
+ }
66
+
67
+ async play(input, metadata = {}) {
68
+ try {
69
+ if (!this.streamer) {
70
+ throw new Error('Streamer not initialized');
71
+ }
72
+
73
+ let result;
74
+ if (this.isValidUrl(input)) {
75
+ if (input.includes('youtube.com') || input.includes('youtu.be')) {
76
+ result = await this.queue.addFromYouTube(input, metadata);
77
+ }
78
+ } else {
79
+ result = await this.queue.addFromYouTube(input, metadata);
80
+ }
81
+
82
+ if (!result?.added) {
83
+ return {
84
+ success: false,
85
+ error: result.reason === 'duplicate' ? 'Track already in queue' : 'Failed to add track'
86
+ };
87
+ }
88
+
89
+ const track = this.queue.getQueue()[result.position - 1] ||
90
+ (result.isNowPlaying ? this.streamer.currentTrack : null);
91
+
92
+ return {
93
+ success: true,
94
+ track: track ? {
95
+ title: track.title,
96
+ duration: track.duration,
97
+ formattedDuration: track.getFormattedDuration(),
98
+ requester: track.requester,
99
+ thumbnail: track.thumbnail
100
+ } : null,
101
+ position: result.position,
102
+ isNowPlaying: result.isNowPlaying || false
103
+ };
104
+ } catch (error) {
105
+ console.error('Error playing track:', error);
106
+ return {
107
+ success: false,
108
+ error: error.message
109
+ };
110
+ }
111
+ }
112
+
113
+ async skip() {
114
+ try {
115
+ const success = await this.queue.skip();
116
+ let upcoming = null;
117
+ if (this.queue.queue.length > 0) {
118
+ const nextTrack = this.queue.queue[0];
119
+ upcoming = {
120
+ title: nextTrack.title,
121
+ duration: nextTrack.duration,
122
+ formattedDuration: nextTrack.getFormattedDuration()
123
+ };
124
+ }
125
+ return { success, upcoming };
126
+ } catch (error) {
127
+ console.error('Error skipping track:', error);
128
+ return { success: false, error: error.message };
129
+ }
130
+ }
131
+
132
+ getNowPlaying() {
133
+ const np = this.streamer.getNowPlaying();
134
+ if (!np || !np.track) return null;
135
+
136
+ return {
137
+ track: np.track,
138
+ position: np.position || 0,
139
+ duration: np.duration || 0,
140
+ progress: np.progress || 0,
141
+ remaining: np.remaining || 0,
142
+ formattedPosition: this.formatDuration(np.position || 0),
143
+ formattedDuration: np.track.getFormattedDuration(),
144
+ formattedRemaining: this.formatDuration(np.remaining || 0)
145
+ };
146
+ }
147
+
148
+ getQueue() {
149
+ const queue = this.queue?.getQueue() || [];
150
+ return {
151
+ queue,
152
+ upcoming: queue.slice(0, 10),
153
+ loopMode: this.queue?.loopMode || 'off',
154
+ length: queue.length
155
+ };
156
+ }
157
+
158
+ toggleLoop() {
159
+ const modes = ['off', 'track', 'queue'];
160
+ const currentIndex = modes.indexOf(this.queue.loopMode);
161
+ const nextIndex = (currentIndex + 1) % modes.length;
162
+ this.queue.loopMode = modes[nextIndex];
163
+ return { success: true, newMode: this.queue.loopMode };
164
+ }
165
+
166
+ shuffle() {
167
+ if (!this.queue || this.queue.queue.length === 0) {
168
+ return { success: false, error: 'Queue is empty' };
169
+ }
170
+ for (let i = this.queue.queue.length - 1; i > 0; i--) {
171
+ const j = Math.floor(Math.random() * (i + 1));
172
+ [this.queue.queue[i], this.queue.queue[j]] = [this.queue.queue[j], this.queue.queue[i]];
173
+ }
174
+ return { success: true, queue: this.getQueue() };
175
+ }
176
+
177
+ removeFromQueue(index) {
178
+ return this.queue?.remove(index) || null;
179
+ }
180
+
181
+ moveInQueue(from, to) {
182
+ return this.queue?.move(from, to) || false;
183
+ }
184
+
185
+ clearQueue() {
186
+ return this.queue?.clear() || [];
187
+ }
188
+
189
+ isValidUrl(string) {
190
+ try {
191
+ new URL(string);
192
+ return true;
193
+ } catch (_) {
194
+ return false;
195
+ }
196
+ }
197
+
198
+ formatDuration(seconds) {
199
+ if (!seconds) return '0:00';
200
+ const hours = Math.floor(seconds / 3600);
201
+ const minutes = Math.floor((seconds % 3600) / 60);
202
+ const secondsRemaining = Math.floor(seconds % 60);
203
+ if (hours > 0) {
204
+ return `${hours}:${minutes.toString().padStart(2, '0')}:${secondsRemaining.toString().padStart(2, '0')}`;
205
+ }
206
+ return `${minutes}:${secondsRemaining.toString().padStart(2, '0')}`;
207
+ }
208
+
209
+ getHealth() {
210
+ return {
211
+ streamer: this.streamer?.getHealth(),
212
+ queue: this.queue?.getStatus(),
213
+ webServer: !!this.webServer,
214
+ uptime: process.uptime()
215
+ };
216
+ }
217
+
218
+ startFallback() {
219
+ if (!this.queue) return { success: false, error: 'Queue not initialized' };
220
+ const started = this.queue.startFallback();
221
+ return { success: started };
222
+ }
223
+
224
+ stopFallback() {
225
+ if (!this.queue) return { success: false, error: 'Queue not initialized' };
226
+ const stopped = this.queue.stopFallback();
227
+ return { success: stopped };
228
+ }
229
+
230
+ destroy() {
231
+ try {
232
+ if (this.streamer) {
233
+ this.streamer.stop();
234
+ }
235
+ return true;
236
+ } catch (error) {
237
+ console.error('Error destroying music class:', error);
238
+ return false;
239
+ }
240
+ }
241
+ }
242
+
243
+ module.exports = MusicClass;
@@ -0,0 +1,175 @@
1
+ const { default_skin } = require("highrise.js/src/constants/TypesConstants");
2
+ const ErrorConstants = require('highrise.js/src/constants/ErrorConstants');
3
+
4
+ class OutfitClass {
5
+ constructor(bot) {
6
+ this.bot = bot;
7
+
8
+ this.HighriseError = bot.HighriseError;
9
+ this.HighriseResponse = bot.HighriseResponse;
10
+ }
11
+
12
+ async set(outfit = default_skin) {
13
+ try {
14
+ if (!Array.isArray(outfit)) {
15
+ return new this.HighriseError(
16
+ ErrorConstants.VALIDATION.INVALID_PARAMETER,
17
+ 'Outfit must be an array',
18
+ { outfit, type: typeof outfit }
19
+ ).toResult();
20
+ }
21
+
22
+ const payload = {
23
+ _type: 'SetOutfitRequest',
24
+ outfit: outfit
25
+ };
26
+
27
+ const response = await this.bot._fireAndForget.send(payload);
28
+
29
+ if (response.success) {
30
+ return this.HighriseResponse.success('outfit', {
31
+ items: outfit,
32
+ count: outfit.length,
33
+ timestamp: Date.now()
34
+ });
35
+ }
36
+ } catch (error) {
37
+ return new this.HighriseError(
38
+ ErrorConstants.HIGHRISE_API.API_SERVER_ERROR,
39
+ `Failed to set outfit: ${error.message}`,
40
+ { outfit }
41
+ ).toResult();
42
+ }
43
+ }
44
+
45
+ async color(bodyPart, colorIndex) {
46
+ try {
47
+ const BodyParts = [
48
+ 'hair', 'hair_front', 'hair_back',
49
+ 'eyebrow', 'eye',
50
+ 'mouth',
51
+ 'body'
52
+ ];
53
+
54
+ const colorsRanges = {
55
+ "hair": { min: 0, max: 81 },
56
+ "hair_front": { min: 0, max: 81 },
57
+ "hair_back": { min: 0, max: 81 },
58
+ "eyebrow": { min: 0, max: 81 },
59
+ "eye": { min: 0, max: 49 },
60
+ "mouth": { min: -1, max: 57 },
61
+ "body": { min: 0, max: 86 }
62
+ };
63
+
64
+ if (!bodyPart || !BodyParts.includes(bodyPart)) {
65
+ return new this.HighriseError(
66
+ ErrorConstants.VALIDATION.INVALID_BODY_PART,
67
+ `Body part must be a valid body part: (${BodyParts.join(', ')})`,
68
+ { bodyPart, type: typeof bodyPart }
69
+ ).toResult();
70
+ }
71
+
72
+ const range = colorsRanges[bodyPart];
73
+ const min = range.min;
74
+ const max = range.max;
75
+
76
+ const num = Number(colorIndex);
77
+ if (colorIndex == null ||
78
+ isNaN(num) ||
79
+ num < min ||
80
+ num > max ||
81
+ !Number.isInteger(num)) {
82
+
83
+ return new this.HighriseError(
84
+ ErrorConstants.VALIDATION.INVALID_COLOR_INDEX,
85
+ `Color index for ${bodyPart} must be an integer between ${min} and ${max} (inclusive). Received: ${colorIndex}`,
86
+ {
87
+ bodyPart,
88
+ colorIndex,
89
+ min,
90
+ max,
91
+ IndexType: typeof colorIndex,
92
+ parsedValue: num
93
+ }
94
+ ).toResult();
95
+ }
96
+
97
+ if (this.bot.info?.outfit === null) {
98
+ await this.get();
99
+ }
100
+
101
+ const outfit = this.bot.info?.outfit || [];
102
+
103
+ let bodyPartsToUpdate = [];
104
+
105
+ if (bodyPart === 'hair') {
106
+ bodyPartsToUpdate.push('hair_front', 'hair_back')
107
+ } else {
108
+ bodyPartsToUpdate.push(bodyPart)
109
+ }
110
+
111
+ let newOutfit = outfit.map(item => {
112
+ const shouldUpdate = bodyPartsToUpdate.some(part =>
113
+ item.id.startsWith(`${part}-`)
114
+ );
115
+
116
+ if (shouldUpdate) {
117
+ item.active_palette = colorIndex;
118
+ }
119
+
120
+ return item;
121
+ });
122
+
123
+ const setResult = await this.set(newOutfit);
124
+ if (setResult.success) {
125
+
126
+ this.bot.setOutfit = newOutfit;
127
+ return setResult;
128
+ }
129
+
130
+ return setResult;
131
+ } catch (error) {
132
+ return new this.HighriseError(
133
+ ErrorConstants.INTERNAL.UNEXPECTED_ERROR,
134
+ `An unexpected error occurred: ${error.message}`
135
+ ).toResult();
136
+ }
137
+ }
138
+
139
+ async get() {
140
+ try {
141
+ const botId = this.bot.info.user?.id;
142
+ const outfit = this.bot.info?.outfit
143
+ if (outfit) {
144
+ return this.HighriseResponse.success('outfit', {
145
+ items: outfit || [],
146
+ count: (outfit || []).length
147
+ });
148
+ }
149
+
150
+ if (!botId) {
151
+ return new this.HighriseError(
152
+ ErrorConstants.INTERNAL.STATE_ERROR,
153
+ 'Bot user ID not found',
154
+ { botInfo: this.bot.info }
155
+ ).toResult();
156
+ }
157
+
158
+ const response = await this.bot.player.outfit.get(botId);
159
+ if (response.success) {
160
+ this.bot.setOutfit = response.outfit.items
161
+ return this.HighriseResponse.success('outfit', {
162
+ items: response.outfit.items || [],
163
+ count: (response.outfit.items || []).length
164
+ });
165
+ }
166
+ } catch (error) {
167
+ return new this.HighriseError(
168
+ ErrorConstants.HIGHRISE_API.API_SERVER_ERROR,
169
+ `Failed to get outfit: ${error.message}`
170
+ ).toResult();
171
+ }
172
+ }
173
+ }
174
+
175
+ module.exports = { OutfitClass };