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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +864 -0
  3. package/index.js +13 -0
  4. package/package.json +28 -0
  5. package/src/classes/Actions/Awaiter.js +202 -0
  6. package/src/classes/Actions/Channel.js +73 -0
  7. package/src/classes/Actions/Direct.js +263 -0
  8. package/src/classes/Actions/Inventory.js +156 -0
  9. package/src/classes/Actions/Music.js +278 -0
  10. package/src/classes/Actions/Player.js +377 -0
  11. package/src/classes/Actions/Public.js +66 -0
  12. package/src/classes/Actions/Room.js +333 -0
  13. package/src/classes/Actions/Utils.js +29 -0
  14. package/src/classes/Actions/lib/AudioStreaming.js +447 -0
  15. package/src/classes/Caches/MovementCache.js +357 -0
  16. package/src/classes/Handlers/AxiosErrorHandler.js +68 -0
  17. package/src/classes/Handlers/ErrorHandler.js +65 -0
  18. package/src/classes/Handlers/EventHandlers.js +259 -0
  19. package/src/classes/Handlers/WebSocketHandlers.js +54 -0
  20. package/src/classes/Managers/ChannelManager.js +303 -0
  21. package/src/classes/Managers/DanceFloorManagers.js +509 -0
  22. package/src/classes/Managers/Helpers/CleanupManager.js +130 -0
  23. package/src/classes/Managers/Helpers/LoggerManager.js +171 -0
  24. package/src/classes/Managers/Helpers/MetricsManager.js +83 -0
  25. package/src/classes/Managers/Networking/ConnectionManager.js +259 -0
  26. package/src/classes/Managers/Networking/CooldownManager.js +516 -0
  27. package/src/classes/Managers/Networking/EventsManager.js +64 -0
  28. package/src/classes/Managers/Networking/KeepAliveManager.js +109 -0
  29. package/src/classes/Managers/Networking/MessageHandler.js +110 -0
  30. package/src/classes/Managers/Networking/Request.js +329 -0
  31. package/src/classes/Managers/PermissionManager.js +288 -0
  32. package/src/classes/WebApi/Category/Grab.js +98 -0
  33. package/src/classes/WebApi/Category/Item.js +347 -0
  34. package/src/classes/WebApi/Category/Post.js +154 -0
  35. package/src/classes/WebApi/Category/Room.js +137 -0
  36. package/src/classes/WebApi/Category/User.js +88 -0
  37. package/src/classes/WebApi/webapi.js +52 -0
  38. package/src/constants/TypesConstants.js +89 -0
  39. package/src/constants/WebSocketConstants.js +80 -0
  40. package/src/core/Highrise.js +123 -0
  41. package/src/core/HighriseWebsocket.js +228 -0
  42. package/src/utils/ConvertSvgToPng.js +51 -0
  43. package/src/utils/ModelPool.js +160 -0
  44. package/src/utils/Models.js +128 -0
  45. package/src/utils/versionCheck.js +27 -0
  46. package/src/validators/ConfigValidator.js +205 -0
  47. package/src/validators/ConnectionValidator.js +65 -0
  48. package/typings/index.d.ts +3820 -0
@@ -0,0 +1,447 @@
1
+ const express = require('express');
2
+ const { spawn } = require('child_process');
3
+ const EventEmitter = require('events');
4
+
5
+ class IcecastStreamer extends EventEmitter {
6
+ constructor(config = {}) {
7
+ super();
8
+ this.config = {
9
+ server: config.server || 'localhost',
10
+ port: config.port || 8000,
11
+ mount: config.mount || '/stream',
12
+ sourcePassword: config.sourcePassword || 'hackme',
13
+ audioFormat: config.audioFormat || 'mp3',
14
+ audioBitrate: config.audioBitrate || '192k',
15
+ audioSampleRate: config.audioSampleRate || 48000,
16
+ audioChannels: config.audioChannels || 2,
17
+ contentType: config.contentType || 'audio/mpeg'
18
+ };
19
+
20
+ this.ffmpegProcess = null;
21
+ this.isStreaming = false;
22
+ this.currentTrack = null;
23
+
24
+ this.icecastUrl = `icecast://source:${this.config.sourcePassword}@${this.config.server}:${this.config.port}${this.config.mount}`;
25
+ this.publicStreamUrl = `http://${this.config.server}:${this.config.port}${this.config.mount}`;
26
+
27
+ }
28
+
29
+ getFFmpegArgs(url) {
30
+ return [
31
+ '-re',
32
+ '-i', url,
33
+ '-f', this.config.audioFormat,
34
+ '-content_type', this.config.contentType,
35
+ '-vn',
36
+ '-ar', this.config.audioSampleRate.toString(),
37
+ '-ac', this.config.audioChannels.toString(),
38
+ '-b:a', this.config.audioBitrate,
39
+ this.icecastUrl
40
+ ];
41
+ }
42
+
43
+ async streamToIcecast(url, metadata = {}) {
44
+ if (this.ffmpegProcess) {
45
+ this.stop();
46
+ await this.delay(200);
47
+ }
48
+
49
+ return new Promise((resolve, reject) => {
50
+ console.log(`Starting Icecast stream for: ${metadata.title || 'Unknown track'}`);
51
+
52
+ this.currentTrack = {
53
+ url,
54
+ title: metadata.title || 'Unknown Track',
55
+ duration: metadata.duration || 0,
56
+ requester: metadata.requester || 'System',
57
+ requesterId: metadata.requesterId || 'system',
58
+ thumbnail: metadata.thumbnail,
59
+ source: metadata.source || 'stream',
60
+ startedAt: Date.now()
61
+ };
62
+
63
+ this.isStreaming = true;
64
+
65
+ const args = this.getFFmpegArgs(url);
66
+ this.ffmpegProcess = spawn('ffmpeg', args);
67
+
68
+ this.ffmpegProcess.stderr.on('data', (data) => {
69
+ const message = data.toString();
70
+
71
+ const durationMatch = message.match(/Duration: (\d+):(\d+):(\d+\.\d+)/);
72
+ if (durationMatch) {
73
+ const hours = parseInt(durationMatch[1]);
74
+ const minutes = parseInt(durationMatch[2]);
75
+ const seconds = parseFloat(durationMatch[3]);
76
+ this.currentTrack.duration = hours * 3600 + minutes * 60 + seconds;
77
+ }
78
+
79
+ const timeMatch = message.match(/time=(\d+):(\d+):(\d+\.\d+)/);
80
+ if (timeMatch) {
81
+ const hours = parseInt(timeMatch[1]);
82
+ const minutes = parseInt(timeMatch[2]);
83
+ const seconds = parseFloat(timeMatch[3]);
84
+ this.currentTrack.position = hours * 3600 + minutes * 60 + seconds;
85
+
86
+ this.emit('progress', {
87
+ position: this.currentTrack.position,
88
+ duration: this.currentTrack.duration,
89
+ progress: this.currentTrack.duration > 0 ?
90
+ (this.currentTrack.position / this.currentTrack.duration) * 100 : 0
91
+ });
92
+ }
93
+
94
+ if (message.includes('Stream mapping') || message.includes('Output')) {
95
+ console.log('Icecast stream started successfully');
96
+ this.emit('playbackStart', this.currentTrack);
97
+ resolve();
98
+ }
99
+
100
+ if (message.includes('Error') || message.includes('Failed')) {
101
+ console.error('FFmpeg error:', message.trim());
102
+ this.emit('error', new Error(message.trim()));
103
+ }
104
+ });
105
+
106
+ this.ffmpegProcess.on('close', (code) => {
107
+ console.log(`Icecast stream ended with code ${code}`);
108
+ const endedTrack = this.currentTrack;
109
+
110
+ this.isStreaming = false;
111
+ this.currentTrack = null;
112
+ this.ffmpegProcess = null;
113
+
114
+ this.emit('playbackEnd', endedTrack);
115
+ this.emit('streamEnd', { code, track: endedTrack });
116
+ });
117
+
118
+ this.ffmpegProcess.on('error', (err) => {
119
+ console.error('FFmpeg process error:', err);
120
+ this.isStreaming = false;
121
+ this.currentTrack = null;
122
+ reject(err);
123
+ this.emit('error', err);
124
+ });
125
+ });
126
+ }
127
+
128
+ stop() {
129
+ if (this.ffmpegProcess) {
130
+ console.log('Stopping Icecast stream...');
131
+ this.ffmpegProcess.kill('SIGKILL');
132
+ this.ffmpegProcess = null;
133
+ this.isStreaming = false;
134
+ this.emit('streamStopped');
135
+ }
136
+ }
137
+
138
+ getStatus() {
139
+ return {
140
+ isStreaming: this.isStreaming,
141
+ currentTrack: this.currentTrack,
142
+ config: this.config,
143
+ publicStreamUrl: this.publicStreamUrl
144
+ };
145
+ }
146
+
147
+ getNowPlaying() {
148
+ if (!this.currentTrack) return null;
149
+
150
+ return {
151
+ track: this.currentTrack,
152
+ position: this.currentTrack.position || 0,
153
+ duration: this.currentTrack.duration || 0,
154
+ progress: this.currentTrack.duration > 0 ?
155
+ ((this.currentTrack.position || 0) / this.currentTrack.duration) * 100 : 0,
156
+ remaining: this.currentTrack.duration > 0 ?
157
+ this.currentTrack.duration - (this.currentTrack.position || 0) : 0,
158
+ elapsed: this.currentTrack.startedAt ?
159
+ Math.floor((Date.now() - this.currentTrack.startedAt) / 1000) : 0
160
+ };
161
+ }
162
+
163
+ updateConfig(newConfig) {
164
+ this.config = { ...this.config, ...newConfig };
165
+ this.icecastUrl = `icecast://source:${this.config.sourcePassword}@${this.config.server}:${this.config.port}${this.config.mount}`;
166
+ this.publicStreamUrl = `http://${this.config.server}:${this.config.port}${this.config.mount}`;
167
+ console.log('Icecast config updated');
168
+ return this.config;
169
+ }
170
+
171
+ async delay(ms) {
172
+ return new Promise(resolve => setTimeout(resolve, ms));
173
+ }
174
+ }
175
+
176
+ class Track {
177
+ constructor(data) {
178
+ this.id = data.id || Date.now();
179
+ this.url = data.url;
180
+ this.title = data.title;
181
+ this.duration = data.duration;
182
+ this.requester = data.requester;
183
+ this.requesterId = data.requesterId;
184
+ this.addedAt = Date.now();
185
+ this.thumbnail = data.thumbnail;
186
+ this.source = data.source;
187
+ }
188
+
189
+ getFormattedDuration() {
190
+ if (!this.duration) return 'Unknown';
191
+
192
+ const hours = Math.floor(this.duration / 3600);
193
+ const minutes = Math.floor((this.duration % 3600) / 60);
194
+ const seconds = Math.floor(this.duration % 60);
195
+
196
+ if (hours > 0) {
197
+ return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
198
+ }
199
+ return `${minutes}:${seconds.toString().padStart(2, '0')}`;
200
+ }
201
+ }
202
+
203
+ class IcecastQueue {
204
+ constructor(streamer, config = {}) {
205
+ this.queue = [];
206
+ this.streamer = streamer;
207
+ this.isProcessing = false;
208
+ this.currentTrack = null;
209
+ this.loopMode = 'off';
210
+ this.history = [];
211
+ this.maxHistory = config.maxHistory || 50;
212
+ this.skipRequested = false;
213
+
214
+ this.setupEventListeners();
215
+ }
216
+
217
+ setupEventListeners() {
218
+ this.streamer.on('playbackEnd', (track) => {
219
+ if (track) {
220
+ this.addToHistory(track, true);
221
+ }
222
+
223
+ if (this.skipRequested) {
224
+ this.skipRequested = false;
225
+ return;
226
+ }
227
+
228
+ if (this.loopMode === 'track' && track) {
229
+ this.queue.unshift(new Track({ ...track }));
230
+ } else if (this.loopMode === 'queue' && track) {
231
+ this.queue.push(new Track({ ...track }));
232
+ }
233
+
234
+ setTimeout(() => this.playNext(), 300);
235
+ });
236
+
237
+ this.streamer.on('playbackStart', (track) => {
238
+ this.currentTrack = track;
239
+ });
240
+ }
241
+
242
+ async add(track) {
243
+ const isNewTrack = !this.queue.some(q => q.id === track.id);
244
+ if (!isNewTrack) {
245
+ return { position: -1, added: false, reason: 'duplicate' };
246
+ }
247
+
248
+ this.queue.push(track);
249
+ const position = this.queue.length;
250
+
251
+ if (!this.streamer.isStreaming && this.queue.length === 1) {
252
+ await this.playNext();
253
+ return { position: 1, added: true, isNowPlaying: true };
254
+ }
255
+
256
+ return { position, added: true, isNowPlaying: false };
257
+ }
258
+
259
+ async addImmediate(track) {
260
+ if (this.currentTrack) {
261
+ this.queue.unshift(new Track({ ...this.currentTrack }));
262
+ }
263
+
264
+ this.queue.unshift(track);
265
+ await this.skip();
266
+ return { position: 1, added: true, isNowPlaying: true };
267
+ }
268
+
269
+ async playNext() {
270
+ if (this.isProcessing || this.queue.length === 0) {
271
+ return false;
272
+ }
273
+
274
+ this.isProcessing = true;
275
+
276
+ try {
277
+ const nextTrack = this.queue.shift();
278
+
279
+ if (!nextTrack?.url) {
280
+ setTimeout(() => this.playNext(), 1000);
281
+ return false;
282
+ }
283
+
284
+ console.log(`🎵 Playing on Icecast: ${nextTrack.title}`);
285
+
286
+ await this.streamer.streamToIcecast(nextTrack.url, {
287
+ title: nextTrack.title,
288
+ duration: nextTrack.duration,
289
+ requester: nextTrack.requester,
290
+ requesterId: nextTrack.requesterId,
291
+ thumbnail: nextTrack.thumbnail,
292
+ source: nextTrack.source
293
+ });
294
+
295
+ return true;
296
+ } catch (error) {
297
+ console.error('Error playing next track:', error);
298
+ // Retry with next track
299
+ setTimeout(() => this.playNext(), 1000);
300
+ return false;
301
+ } finally {
302
+ this.isProcessing = false;
303
+ }
304
+ }
305
+
306
+ async skip() {
307
+ if (!this.streamer.isStreaming) {
308
+ return false;
309
+ }
310
+
311
+ this.skipRequested = true;
312
+ this.streamer.stop();
313
+
314
+ await this.delay(100);
315
+ await this.playNext();
316
+
317
+ return true;
318
+ }
319
+
320
+ remove(position) {
321
+ if (position < 1 || position > this.queue.length) {
322
+ return null;
323
+ }
324
+
325
+ return this.queue.splice(position - 1, 1)[0];
326
+ }
327
+
328
+ clear() {
329
+ const cleared = [...this.queue];
330
+ this.queue = [];
331
+ return cleared;
332
+ }
333
+
334
+ addToHistory(track, success = true) {
335
+ const historyEntry = {
336
+ ...track,
337
+ playedAt: Date.now(),
338
+ success,
339
+ endedAt: Date.now()
340
+ };
341
+
342
+ this.history.unshift(historyEntry);
343
+
344
+ if (this.history.length > this.maxHistory) {
345
+ this.history.pop();
346
+ }
347
+ }
348
+
349
+ getQueue() {
350
+ return [...this.queue];
351
+ }
352
+
353
+ getStatus() {
354
+ return {
355
+ isPlaying: this.streamer.isStreaming,
356
+ currentTrack: this.currentTrack,
357
+ queueLength: this.queue.length,
358
+ loopMode: this.loopMode,
359
+ historyLength: this.history.length,
360
+ upcoming: this.queue.slice(0, 10)
361
+ };
362
+ }
363
+
364
+ async delay(ms) {
365
+ return new Promise(resolve => setTimeout(resolve, ms));
366
+ }
367
+ }
368
+
369
+ class YouTubeExtractor {
370
+ static async getStreamUrl(youtubeUrl) {
371
+ return new Promise((resolve) => {
372
+ const ytdlp = spawn('yt-dlp', [
373
+ youtubeUrl,
374
+ '--print', '%(url)s',
375
+ '--print', '%(title)s',
376
+ '--print', '%(duration)s',
377
+ '--print', '%(thumbnail)s',
378
+ '-f', 'bestaudio[ext=m4a]/bestaudio',
379
+ '--no-playlist',
380
+ '--no-check-certificates',
381
+ '--quiet'
382
+ ]);
383
+
384
+ let data = '';
385
+ ytdlp.stdout.on('data', (chunk) => data += chunk.toString());
386
+ ytdlp.stderr.on('data', () => { });
387
+
388
+ ytdlp.on('close', (code) => {
389
+ if (code !== 0) {
390
+ resolve(null);
391
+ return;
392
+ }
393
+
394
+ const lines = data.trim().split('\n');
395
+
396
+ if (lines.length >= 4 && lines[0].startsWith('http')) {
397
+ resolve({
398
+ url: lines[0],
399
+ title: lines[1],
400
+ duration: parseInt(lines[2]) || 0,
401
+ thumbnail: lines[3] || null,
402
+ source: 'youtube'
403
+ });
404
+ } else {
405
+ resolve(null);
406
+ }
407
+ });
408
+ });
409
+ }
410
+
411
+ static async searchTrack(query) {
412
+ return this.getStreamUrl(`ytsearch1:${query}`);
413
+ }
414
+ }
415
+
416
+ class IcecastServer {
417
+ constructor(config = {}) {
418
+ this.app = express();
419
+ this.port = config.port || 3000;
420
+ this.config = config
421
+ this.logger = config.logger
422
+ this.setupExpress();
423
+ }
424
+
425
+ setupExpress() {
426
+ this.app.use(express.json());
427
+
428
+ this.app.get('/stream', (req, res) => {
429
+ res.redirect(this.config.publicStreamUrl);
430
+ });
431
+ }
432
+
433
+ start() {
434
+ this.app.listen(this.port, () => {
435
+ this.logger.success(`IcecastServer.start`, `running: http://localhost:${this.port}`);
436
+ this.logger.success(`IcecastServer.start`, `Icecast Stream: ${this.config.publicStreamUrl}`);
437
+ });
438
+ }
439
+ }
440
+
441
+ module.exports = {
442
+ IcecastStreamer,
443
+ IcecastQueue,
444
+ Track,
445
+ YouTubeExtractor,
446
+ IcecastServer
447
+ };