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.
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/index.js +13 -0
- package/package.json +29 -0
- package/src/classes/Actions/Awaiter.js +203 -0
- package/src/classes/Actions/Channel.js +327 -0
- package/src/classes/Actions/Direct.js +390 -0
- package/src/classes/Actions/Inventory.js +193 -0
- package/src/classes/Actions/Music.js +243 -0
- package/src/classes/Actions/Outfit.js +175 -0
- package/src/classes/Actions/Player.js +604 -0
- package/src/classes/Actions/Public.js +143 -0
- package/src/classes/Actions/Room.js +495 -0
- package/src/classes/Actions/Utils.js +77 -0
- package/src/classes/Actions/lib/AudioStreaming.js +695 -0
- package/src/classes/Caches/MovementCache.js +364 -0
- package/src/classes/Handlers/AxiosErrorHandler.js +68 -0
- package/src/classes/Handlers/ErrorHandler.js +65 -0
- package/src/classes/Handlers/EventHandlers.js +193 -0
- package/src/classes/Handlers/WebSocketHandlers.js +126 -0
- package/src/classes/Managers/CooldownManager.js +516 -0
- package/src/classes/Managers/DanceFloorManagers.js +609 -0
- package/src/classes/Managers/Helpers/CleanupManager.js +130 -0
- package/src/classes/Managers/Helpers/HighriseError.js +107 -0
- package/src/classes/Managers/Helpers/HighriseResponse.js +33 -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 +253 -0
- package/src/classes/Managers/Networking/EventsManager.js +64 -0
- package/src/classes/Managers/Networking/KeepAliveManager.js +58 -0
- package/src/classes/Managers/Networking/MessageHandler.js +123 -0
- package/src/classes/Managers/Networking/Request.js +323 -0
- package/src/classes/Managers/RoleManager.js +322 -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/ErrorConstants.js +109 -0
- package/src/constants/TypesConstants.js +91 -0
- package/src/constants/WebSocketConstants.js +78 -0
- package/src/core/Highrise.js +192 -0
- package/src/core/HighriseWebsocket.js +242 -0
- package/src/utils/ConvertSvgToPng.js +51 -0
- package/src/utils/Job.js +130 -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 +195 -0
- package/src/validators/ConnectionValidator.js +65 -0
- 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 };
|