streamify-audio 2.1.8 → 2.1.10
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/index.js +0 -10
- package/package.json +2 -3
- package/src/discord/Manager.js +0 -253
- package/src/discord/Player.js +0 -65
- package/src/filters/presets.js +0 -227
- package/src/persistence/index.js +0 -166
package/index.js
CHANGED
|
@@ -249,14 +249,4 @@ Streamify.Manager = Manager;
|
|
|
249
249
|
Streamify.Player = Manager ? require('./src/discord/Player') : null;
|
|
250
250
|
Streamify.Queue = Manager ? require('./src/discord/Queue') : null;
|
|
251
251
|
|
|
252
|
-
const persistence = require('./src/persistence');
|
|
253
|
-
Streamify.PersistenceAdapter = persistence.PersistenceAdapter;
|
|
254
|
-
Streamify.MemoryAdapter = persistence.MemoryAdapter;
|
|
255
|
-
Streamify.MongoAdapter = persistence.MongoAdapter;
|
|
256
|
-
|
|
257
|
-
const presets = require('./src/filters/presets');
|
|
258
|
-
Streamify.EffectPresets = presets;
|
|
259
|
-
Streamify.getEffectPresetNames = presets.getPresetNames;
|
|
260
|
-
Streamify.getEffectPresetsInfo = presets.getAllPresetsInfo;
|
|
261
|
-
|
|
262
252
|
module.exports = Streamify;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "streamify-audio",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.10",
|
|
4
4
|
"description": "Dual-mode audio library: HTTP streaming proxy + Discord player (Lavalink alternative). Supports YouTube, Spotify, SoundCloud with audio filters.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -29,8 +29,7 @@
|
|
|
29
29
|
"author": "Lucas",
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"express": "^4.18.2"
|
|
33
|
-
"streamify-audio": "^2.1.6"
|
|
32
|
+
"express": "^4.18.2"
|
|
34
33
|
},
|
|
35
34
|
"optionalDependencies": {
|
|
36
35
|
"@discordjs/opus": "^0.9.0",
|
package/src/discord/Manager.js
CHANGED
|
@@ -5,7 +5,6 @@ const spotify = require('../providers/spotify');
|
|
|
5
5
|
const soundcloud = require('../providers/soundcloud');
|
|
6
6
|
const log = require('../utils/logger');
|
|
7
7
|
const { loadConfig } = require('../config');
|
|
8
|
-
const { MemoryAdapter, MongoAdapter, serializeTrack, deserializeTrack } = require('../persistence');
|
|
9
8
|
|
|
10
9
|
function checkDependencies() {
|
|
11
10
|
const missing = [];
|
|
@@ -74,186 +73,11 @@ class Manager extends EventEmitter {
|
|
|
74
73
|
maxTracks: options.autoplay?.maxTracks ?? 5
|
|
75
74
|
};
|
|
76
75
|
|
|
77
|
-
this.fallback = {
|
|
78
|
-
enabled: options.fallback?.enabled ?? false,
|
|
79
|
-
order: options.fallback?.order || ['youtube', 'soundcloud']
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
this.persistence = null;
|
|
83
|
-
this._persistenceInterval = null;
|
|
84
|
-
this._persistenceIntervalMs = options.persistence?.interval || 30000;
|
|
85
|
-
this._restoringGuilds = new Set();
|
|
86
|
-
|
|
87
76
|
this._setupVoiceStateListener();
|
|
88
77
|
|
|
89
78
|
log.info('MANAGER', 'Streamify Manager initialized');
|
|
90
79
|
}
|
|
91
80
|
|
|
92
|
-
async enablePersistence(options = {}) {
|
|
93
|
-
if (options.adapter) {
|
|
94
|
-
this.persistence = options.adapter;
|
|
95
|
-
} else if (options.type === 'mongodb') {
|
|
96
|
-
this.persistence = new MongoAdapter({
|
|
97
|
-
client: options.client,
|
|
98
|
-
uri: options.uri,
|
|
99
|
-
db: options.db,
|
|
100
|
-
collection: options.collection
|
|
101
|
-
});
|
|
102
|
-
} else {
|
|
103
|
-
this.persistence = new MemoryAdapter();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (options.autoSave !== false) {
|
|
107
|
-
this._persistenceInterval = setInterval(() => {
|
|
108
|
-
this._saveAllSessions();
|
|
109
|
-
}, this._persistenceIntervalMs);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
log.info('MANAGER', `Persistence enabled (type: ${options.type || 'memory'})`);
|
|
113
|
-
return this;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async _saveAllSessions() {
|
|
117
|
-
if (!this.persistence) return;
|
|
118
|
-
|
|
119
|
-
for (const [guildId, player] of this.players) {
|
|
120
|
-
if (player.connected) {
|
|
121
|
-
await this.saveSession(guildId);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async saveSession(guildId) {
|
|
127
|
-
if (!this.persistence) return false;
|
|
128
|
-
if (this._restoringGuilds.has(guildId)) return false;
|
|
129
|
-
|
|
130
|
-
const player = this.players.get(guildId);
|
|
131
|
-
if (!player || !player.connected) return false;
|
|
132
|
-
|
|
133
|
-
const current = player.queue.current;
|
|
134
|
-
if (!current && player.queue.tracks.length === 0) {
|
|
135
|
-
await this.persistence.delete(guildId);
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const sessionData = {
|
|
140
|
-
guildId,
|
|
141
|
-
voiceChannelId: player.voiceChannelId,
|
|
142
|
-
textChannelId: player.textChannelId,
|
|
143
|
-
currentTrack: serializeTrack(current),
|
|
144
|
-
positionMs: player.position || 0,
|
|
145
|
-
queue: player.queue.tracks.map(serializeTrack),
|
|
146
|
-
volume: player.volume,
|
|
147
|
-
loop: player.queue.repeatMode || 'off',
|
|
148
|
-
filters: player.filters || {}
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
await this.persistence.save(guildId, sessionData);
|
|
152
|
-
log.info('PERSISTENCE', `Session saved: ${guildId} (track: ${sessionData.currentTrack?.title || 'none'}, queue: ${sessionData.queue?.length || 0})`);
|
|
153
|
-
return true;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async restoreSession(guildId, options = {}) {
|
|
157
|
-
if (!this.persistence) return null;
|
|
158
|
-
|
|
159
|
-
const session = await this.persistence.load(guildId);
|
|
160
|
-
if (!session) return null;
|
|
161
|
-
|
|
162
|
-
const guild = this.client.guilds.cache.get(guildId);
|
|
163
|
-
if (!guild) {
|
|
164
|
-
log.debug('MANAGER', `Guild ${guildId} not found, skipping restore`);
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const voiceChannel = this.client.channels.cache.get(session.voiceChannelId);
|
|
169
|
-
if (!voiceChannel) {
|
|
170
|
-
log.debug('MANAGER', `Voice channel ${session.voiceChannelId} not found, skipping restore`);
|
|
171
|
-
await this.persistence.delete(guildId);
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
log.info('MANAGER', `Restoring session for guild ${guildId}`);
|
|
176
|
-
this._restoringGuilds.add(guildId);
|
|
177
|
-
|
|
178
|
-
const player = await this.create(guildId, session.voiceChannelId, session.textChannelId);
|
|
179
|
-
|
|
180
|
-
if (session.volume !== undefined) player.setVolume(session.volume);
|
|
181
|
-
if (session.loop && session.loop !== 'off') player.setLoop(session.loop);
|
|
182
|
-
|
|
183
|
-
if (session.currentTrack) {
|
|
184
|
-
const track = deserializeTrack(session.currentTrack);
|
|
185
|
-
log.info('MANAGER', `[RESTORE] Playing track: ${track.title}`);
|
|
186
|
-
try {
|
|
187
|
-
await player.play(track);
|
|
188
|
-
} catch (playErr) {
|
|
189
|
-
log.error('MANAGER', `[RESTORE] Play error: ${playErr.message}`);
|
|
190
|
-
}
|
|
191
|
-
log.info('MANAGER', `[RESTORE] Track play() returned`);
|
|
192
|
-
|
|
193
|
-
if (session.positionMs > 0 && !track.isLive && options.seekToPosition !== false) {
|
|
194
|
-
try {
|
|
195
|
-
await player.seek(session.positionMs);
|
|
196
|
-
} catch (e) {
|
|
197
|
-
log.debug('MANAGER', `Could not seek to position: ${e.message}`);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (session.queue && session.queue.length > 0) {
|
|
203
|
-
const tracks = session.queue.map(deserializeTrack);
|
|
204
|
-
player.queue.addMany(tracks);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
log.info('MANAGER', `[RESTORE] Checking voice channel members`);
|
|
208
|
-
let memberCount = 0;
|
|
209
|
-
try {
|
|
210
|
-
const members = voiceChannel.members;
|
|
211
|
-
if (members) {
|
|
212
|
-
memberCount = members.filter(m => !m.user.bot).size;
|
|
213
|
-
}
|
|
214
|
-
} catch (e) {
|
|
215
|
-
log.info('MANAGER', `[RESTORE] Error getting members: ${e.message}`);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (memberCount === 0 && player.autoPause.enabled) {
|
|
219
|
-
player.pause();
|
|
220
|
-
player._autoPaused = true;
|
|
221
|
-
log.info('MANAGER', `Session restored but auto-paused (empty channel)`);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
log.info('MANAGER', `[RESTORE] Emitting sessionRestored event`);
|
|
225
|
-
this._restoringGuilds.delete(guildId);
|
|
226
|
-
this.emit('sessionRestored', { guildId, voiceChannelId: player.voiceChannelId });
|
|
227
|
-
log.info('MANAGER', `[RESTORE] Returning true`);
|
|
228
|
-
return true;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
async restoreAllSessions(options = {}) {
|
|
232
|
-
if (!this.persistence) return [];
|
|
233
|
-
|
|
234
|
-
const sessions = await this.persistence.loadAll();
|
|
235
|
-
const restored = [];
|
|
236
|
-
|
|
237
|
-
for (const session of sessions) {
|
|
238
|
-
try {
|
|
239
|
-
const success = await this.restoreSession(session.guildId, options);
|
|
240
|
-
if (success) {
|
|
241
|
-
restored.push({ guildId: session.guildId });
|
|
242
|
-
}
|
|
243
|
-
} catch (error) {
|
|
244
|
-
log.error('MANAGER', `Failed to restore session ${session.guildId}: ${error.message}`);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
log.info('MANAGER', `Restored ${restored.length}/${sessions.length} sessions`);
|
|
249
|
-
return restored;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
async deleteSession(guildId) {
|
|
253
|
-
if (!this.persistence) return false;
|
|
254
|
-
return this.persistence.delete(guildId);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
81
|
_setupVoiceStateListener() {
|
|
258
82
|
this.client.on('voiceStateUpdate', (oldState, newState) => {
|
|
259
83
|
const player = this.players.get(oldState.guild.id) || this.players.get(newState.guild.id);
|
|
@@ -650,84 +474,7 @@ class Manager extends EventEmitter {
|
|
|
650
474
|
}
|
|
651
475
|
}
|
|
652
476
|
|
|
653
|
-
async searchWithFallback(query, options = {}) {
|
|
654
|
-
if (!this.fallback.enabled) {
|
|
655
|
-
return this.search(query, options);
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
const sources = options.sources || this.fallback.order;
|
|
659
|
-
let lastError = null;
|
|
660
|
-
|
|
661
|
-
for (const source of sources) {
|
|
662
|
-
try {
|
|
663
|
-
const result = await this.search(query, { ...options, source });
|
|
664
|
-
if (result.loadType !== 'error' && result.tracks.length > 0) {
|
|
665
|
-
return result;
|
|
666
|
-
}
|
|
667
|
-
} catch (error) {
|
|
668
|
-
lastError = error;
|
|
669
|
-
log.debug('MANAGER', `Fallback: ${source} failed, trying next...`);
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
return {
|
|
674
|
-
loadType: 'error',
|
|
675
|
-
tracks: [],
|
|
676
|
-
error: lastError?.message || 'All sources failed'
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
async resolveWithFallback(query) {
|
|
681
|
-
if (!this.fallback.enabled) {
|
|
682
|
-
return this.resolve(query);
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
const result = await this.resolve(query);
|
|
686
|
-
if (result.loadType !== 'error' && result.tracks.length > 0) {
|
|
687
|
-
return result;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
const detected = this._detectSource(query);
|
|
691
|
-
if (detected === 'spotify') {
|
|
692
|
-
const match = this._extractId(query, detected);
|
|
693
|
-
if (match) {
|
|
694
|
-
try {
|
|
695
|
-
const track = await this.getInfo(match.id, 'spotify');
|
|
696
|
-
const searchQuery = `${track.author} - ${track.title}`;
|
|
697
|
-
|
|
698
|
-
for (const source of this.fallback.order) {
|
|
699
|
-
if (source === 'spotify') continue;
|
|
700
|
-
try {
|
|
701
|
-
const fallbackResult = await this.search(searchQuery, { source, limit: 1 });
|
|
702
|
-
if (fallbackResult.tracks.length > 0) {
|
|
703
|
-
const fallbackTrack = fallbackResult.tracks[0];
|
|
704
|
-
fallbackTrack._originalSpotifyTrack = track;
|
|
705
|
-
log.info('MANAGER', `Spotify fallback: resolved via ${source}`);
|
|
706
|
-
return {
|
|
707
|
-
loadType: 'track',
|
|
708
|
-
tracks: [fallbackTrack],
|
|
709
|
-
fallbackUsed: source
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
} catch (e) {
|
|
713
|
-
continue;
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
} catch (e) {
|
|
717
|
-
log.debug('MANAGER', `Spotify info fetch failed: ${e.message}`);
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
return result;
|
|
723
|
-
}
|
|
724
|
-
|
|
725
477
|
destroyAll() {
|
|
726
|
-
if (this._persistenceInterval) {
|
|
727
|
-
clearInterval(this._persistenceInterval);
|
|
728
|
-
this._persistenceInterval = null;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
478
|
for (const [guildId, player] of this.players) {
|
|
732
479
|
player.destroy();
|
|
733
480
|
}
|
package/src/discord/Player.js
CHANGED
|
@@ -2,7 +2,6 @@ const { EventEmitter } = require('events');
|
|
|
2
2
|
const Queue = require('./Queue');
|
|
3
3
|
const { createStream } = require('./Stream');
|
|
4
4
|
const log = require('../utils/logger');
|
|
5
|
-
const { buildPresetFilters, getPresetNames, getAllPresetsInfo } = require('../filters/presets');
|
|
6
5
|
|
|
7
6
|
let voiceModule;
|
|
8
7
|
try {
|
|
@@ -588,70 +587,6 @@ class Player extends EventEmitter {
|
|
|
588
587
|
return this.setFilter('preset', presetName);
|
|
589
588
|
}
|
|
590
589
|
|
|
591
|
-
async setEffectPreset(presetName, intensity = 0.5) {
|
|
592
|
-
const filters = buildPresetFilters(presetName, intensity);
|
|
593
|
-
|
|
594
|
-
for (const [key, value] of Object.entries(filters)) {
|
|
595
|
-
if (key === '_intensityFactor') continue;
|
|
596
|
-
this._filters[key] = value;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
this._activePresets = this._activePresets || new Map();
|
|
600
|
-
this._activePresets.set(presetName, intensity);
|
|
601
|
-
|
|
602
|
-
if (this._playing && this.queue.current) {
|
|
603
|
-
return this.setFilter('_trigger', null);
|
|
604
|
-
}
|
|
605
|
-
return true;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
async setEffectPresets(presets) {
|
|
609
|
-
this._activePresets = new Map();
|
|
610
|
-
|
|
611
|
-
for (const { name, intensity = 0.5 } of presets) {
|
|
612
|
-
const filters = buildPresetFilters(name, intensity);
|
|
613
|
-
for (const [key, value] of Object.entries(filters)) {
|
|
614
|
-
if (key === '_intensityFactor') continue;
|
|
615
|
-
this._filters[key] = value;
|
|
616
|
-
}
|
|
617
|
-
this._activePresets.set(name, intensity);
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
if (this._playing && this.queue.current) {
|
|
621
|
-
return this.setFilter('_trigger', null);
|
|
622
|
-
}
|
|
623
|
-
return true;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
async clearEffectPresets() {
|
|
627
|
-
const presetsToRemove = ['bass', 'treble', 'speed', 'pitch', 'rotation', 'karaoke',
|
|
628
|
-
'lowpass', 'highpass', 'bandpass', 'vibrato', 'tremolo', 'chorus',
|
|
629
|
-
'compressor', 'normalizer', 'nightcore', 'vaporwave', 'bassboost', '8d'];
|
|
630
|
-
|
|
631
|
-
for (const key of presetsToRemove) {
|
|
632
|
-
delete this._filters[key];
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
this._activePresets = new Map();
|
|
636
|
-
|
|
637
|
-
if (this._playing && this.queue.current) {
|
|
638
|
-
return this.setFilter('_trigger', null);
|
|
639
|
-
}
|
|
640
|
-
return true;
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
getActiveEffectPresets() {
|
|
644
|
-
return this._activePresets ? Array.from(this._activePresets.entries()).map(([name, intensity]) => ({ name, intensity })) : [];
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
getAvailableEffectPresets() {
|
|
648
|
-
return getAllPresetsInfo();
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
static getEffectPresetNames() {
|
|
652
|
-
return getPresetNames();
|
|
653
|
-
}
|
|
654
|
-
|
|
655
590
|
async clearEQ() {
|
|
656
591
|
delete this._filters.equalizer;
|
|
657
592
|
delete this._filters.preset;
|
package/src/filters/presets.js
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
const EFFECT_PRESETS = {
|
|
2
|
-
bassboost: {
|
|
3
|
-
description: 'Boost bass frequencies',
|
|
4
|
-
build: (intensity = 0.5) => {
|
|
5
|
-
const gain = 5 + (intensity * 15);
|
|
6
|
-
return { bass: gain };
|
|
7
|
-
}
|
|
8
|
-
},
|
|
9
|
-
|
|
10
|
-
nightcore: {
|
|
11
|
-
description: 'Speed up with higher pitch (anime style)',
|
|
12
|
-
build: (intensity = 0.5) => {
|
|
13
|
-
const speedBoost = 0.15 + (intensity * 0.2);
|
|
14
|
-
return {
|
|
15
|
-
speed: 1 + speedBoost,
|
|
16
|
-
pitch: 1 + speedBoost
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
|
|
21
|
-
vaporwave: {
|
|
22
|
-
description: 'Slow down with lower pitch (aesthetic)',
|
|
23
|
-
build: (intensity = 0.5) => {
|
|
24
|
-
const slowdown = 0.1 + (intensity * 0.2);
|
|
25
|
-
return {
|
|
26
|
-
speed: 1 - slowdown,
|
|
27
|
-
pitch: 1 - slowdown
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
|
|
32
|
-
'8d': {
|
|
33
|
-
description: 'Rotating 8D audio effect',
|
|
34
|
-
build: (intensity = 0.5) => {
|
|
35
|
-
const speed = 0.05 + (intensity * 0.2);
|
|
36
|
-
return { rotation: { speed } };
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
karaoke: {
|
|
41
|
-
description: 'Reduce vocals (center channel removal)',
|
|
42
|
-
build: () => ({ karaoke: true })
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
trebleboost: {
|
|
46
|
-
description: 'Boost high frequencies',
|
|
47
|
-
build: (intensity = 0.5) => {
|
|
48
|
-
const gain = 5 + (intensity * 15);
|
|
49
|
-
return { treble: gain };
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
|
|
53
|
-
deep: {
|
|
54
|
-
description: 'Deep bass with reduced highs',
|
|
55
|
-
build: (intensity = 0.5) => {
|
|
56
|
-
const bassGain = 8 + (intensity * 12);
|
|
57
|
-
const trebleCut = -3 - (intensity * 7);
|
|
58
|
-
return { bass: bassGain, treble: trebleCut };
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
|
|
62
|
-
pop: {
|
|
63
|
-
description: 'Enhanced clarity for pop music',
|
|
64
|
-
build: (intensity = 0.5) => {
|
|
65
|
-
const factor = 0.5 + (intensity * 0.5);
|
|
66
|
-
return {
|
|
67
|
-
preset: 'pop',
|
|
68
|
-
_intensityFactor: factor
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
rock: {
|
|
74
|
-
description: 'Punchy mids and lows for rock',
|
|
75
|
-
build: (intensity = 0.5) => {
|
|
76
|
-
const factor = 0.5 + (intensity * 0.5);
|
|
77
|
-
return {
|
|
78
|
-
preset: 'rock',
|
|
79
|
-
_intensityFactor: factor
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
electronic: {
|
|
85
|
-
description: 'Enhanced bass and highs for EDM',
|
|
86
|
-
build: (intensity = 0.5) => {
|
|
87
|
-
const factor = 0.5 + (intensity * 0.5);
|
|
88
|
-
return {
|
|
89
|
-
preset: 'electronic',
|
|
90
|
-
_intensityFactor: factor
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
lofi: {
|
|
96
|
-
description: 'Lo-fi aesthetic with warmth',
|
|
97
|
-
build: (intensity = 0.5) => ({
|
|
98
|
-
lowpass: 8000 - (intensity * 4000),
|
|
99
|
-
bass: 3 + (intensity * 5)
|
|
100
|
-
})
|
|
101
|
-
},
|
|
102
|
-
|
|
103
|
-
radio: {
|
|
104
|
-
description: 'AM radio effect',
|
|
105
|
-
build: (intensity = 0.5) => ({
|
|
106
|
-
lowpass: 5000 - (intensity * 2000),
|
|
107
|
-
highpass: 300 + (intensity * 200)
|
|
108
|
-
})
|
|
109
|
-
},
|
|
110
|
-
|
|
111
|
-
telephone: {
|
|
112
|
-
description: 'Telephone/walkie-talkie effect',
|
|
113
|
-
build: () => ({
|
|
114
|
-
bandpass: { frequency: 1500, width: 1000 }
|
|
115
|
-
})
|
|
116
|
-
},
|
|
117
|
-
|
|
118
|
-
soft: {
|
|
119
|
-
description: 'Soft, gentle sound',
|
|
120
|
-
build: (intensity = 0.5) => ({
|
|
121
|
-
lowpass: 12000 - (intensity * 4000),
|
|
122
|
-
compressor: true
|
|
123
|
-
})
|
|
124
|
-
},
|
|
125
|
-
|
|
126
|
-
loud: {
|
|
127
|
-
description: 'Louder, more compressed',
|
|
128
|
-
build: (intensity = 0.5) => ({
|
|
129
|
-
preset: 'loudness',
|
|
130
|
-
compressor: true,
|
|
131
|
-
normalizer: true
|
|
132
|
-
})
|
|
133
|
-
},
|
|
134
|
-
|
|
135
|
-
chipmunk: {
|
|
136
|
-
description: 'High-pitched chipmunk voice',
|
|
137
|
-
build: (intensity = 0.5) => ({
|
|
138
|
-
pitch: 1.3 + (intensity * 0.5),
|
|
139
|
-
speed: 1.2 + (intensity * 0.3)
|
|
140
|
-
})
|
|
141
|
-
},
|
|
142
|
-
|
|
143
|
-
darth: {
|
|
144
|
-
description: 'Deep Darth Vader-like voice',
|
|
145
|
-
build: (intensity = 0.5) => ({
|
|
146
|
-
pitch: 0.7 - (intensity * 0.2),
|
|
147
|
-
speed: 0.9 - (intensity * 0.1)
|
|
148
|
-
})
|
|
149
|
-
},
|
|
150
|
-
|
|
151
|
-
echo: {
|
|
152
|
-
description: 'Echo/reverb effect',
|
|
153
|
-
build: () => ({
|
|
154
|
-
chorus: true
|
|
155
|
-
})
|
|
156
|
-
},
|
|
157
|
-
|
|
158
|
-
vibrato: {
|
|
159
|
-
description: 'Vibrating pitch effect',
|
|
160
|
-
build: (intensity = 0.5) => ({
|
|
161
|
-
vibrato: {
|
|
162
|
-
frequency: 4 + (intensity * 6),
|
|
163
|
-
depth: 0.3 + (intensity * 0.4)
|
|
164
|
-
}
|
|
165
|
-
})
|
|
166
|
-
},
|
|
167
|
-
|
|
168
|
-
tremolo: {
|
|
169
|
-
description: 'Trembling volume effect',
|
|
170
|
-
build: (intensity = 0.5) => ({
|
|
171
|
-
tremolo: {
|
|
172
|
-
frequency: 4 + (intensity * 8),
|
|
173
|
-
depth: 0.4 + (intensity * 0.4)
|
|
174
|
-
}
|
|
175
|
-
})
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
function getPresetNames() {
|
|
180
|
-
return Object.keys(EFFECT_PRESETS);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function getPresetInfo(name) {
|
|
184
|
-
const preset = EFFECT_PRESETS[name];
|
|
185
|
-
if (!preset) return null;
|
|
186
|
-
return {
|
|
187
|
-
name,
|
|
188
|
-
description: preset.description
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function getAllPresetsInfo() {
|
|
193
|
-
return Object.entries(EFFECT_PRESETS).map(([name, preset]) => ({
|
|
194
|
-
name,
|
|
195
|
-
description: preset.description
|
|
196
|
-
}));
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function buildPresetFilters(name, intensity = 0.5) {
|
|
200
|
-
const preset = EFFECT_PRESETS[name];
|
|
201
|
-
if (!preset) {
|
|
202
|
-
throw new Error(`Unknown preset: ${name}. Available: ${getPresetNames().join(', ')}`);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const clampedIntensity = Math.max(0, Math.min(1, intensity));
|
|
206
|
-
return preset.build(clampedIntensity);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function combinePresets(presetNames, intensity = 0.5) {
|
|
210
|
-
const combined = {};
|
|
211
|
-
|
|
212
|
-
for (const name of presetNames) {
|
|
213
|
-
const filters = buildPresetFilters(name, intensity);
|
|
214
|
-
Object.assign(combined, filters);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return combined;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
module.exports = {
|
|
221
|
-
EFFECT_PRESETS,
|
|
222
|
-
getPresetNames,
|
|
223
|
-
getPresetInfo,
|
|
224
|
-
getAllPresetsInfo,
|
|
225
|
-
buildPresetFilters,
|
|
226
|
-
combinePresets
|
|
227
|
-
};
|
package/src/persistence/index.js
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
const log = require('../utils/logger');
|
|
2
|
-
|
|
3
|
-
class PersistenceAdapter {
|
|
4
|
-
async save(guildId, sessionData) {
|
|
5
|
-
throw new Error('save() must be implemented by adapter');
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
async load(guildId) {
|
|
9
|
-
throw new Error('load() must be implemented by adapter');
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
async delete(guildId) {
|
|
13
|
-
throw new Error('delete() must be implemented by adapter');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async loadAll() {
|
|
17
|
-
throw new Error('loadAll() must be implemented by adapter');
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
class MemoryAdapter extends PersistenceAdapter {
|
|
22
|
-
constructor() {
|
|
23
|
-
super();
|
|
24
|
-
this.sessions = new Map();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async save(guildId, sessionData) {
|
|
28
|
-
this.sessions.set(guildId, {
|
|
29
|
-
...sessionData,
|
|
30
|
-
updatedAt: Date.now()
|
|
31
|
-
});
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async load(guildId) {
|
|
36
|
-
return this.sessions.get(guildId) || null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async delete(guildId) {
|
|
40
|
-
return this.sessions.delete(guildId);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async loadAll() {
|
|
44
|
-
return Array.from(this.sessions.entries()).map(([guildId, data]) => ({
|
|
45
|
-
guildId,
|
|
46
|
-
...data
|
|
47
|
-
}));
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
class MongoAdapter extends PersistenceAdapter {
|
|
52
|
-
constructor(options = {}) {
|
|
53
|
-
super();
|
|
54
|
-
this.collection = null;
|
|
55
|
-
this.collectionName = options.collection || 'streamify_sessions';
|
|
56
|
-
this.mongoClient = options.client || null;
|
|
57
|
-
this.mongoUri = options.uri || null;
|
|
58
|
-
this.db = options.db || null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async connect() {
|
|
62
|
-
if (this.collection) return;
|
|
63
|
-
|
|
64
|
-
if (this.mongoClient) {
|
|
65
|
-
const dbName = this.db || 'streamify';
|
|
66
|
-
this.collection = this.mongoClient.db(dbName).collection(this.collectionName);
|
|
67
|
-
} else if (this.mongoUri) {
|
|
68
|
-
const { MongoClient } = require('mongodb');
|
|
69
|
-
const client = new MongoClient(this.mongoUri);
|
|
70
|
-
await client.connect();
|
|
71
|
-
const dbName = this.db || 'streamify';
|
|
72
|
-
this.collection = client.db(dbName).collection(this.collectionName);
|
|
73
|
-
} else {
|
|
74
|
-
throw new Error('MongoAdapter requires either client or uri option');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
log.info('PERSISTENCE', `Connected to MongoDB collection: ${this.collectionName}`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async save(guildId, sessionData) {
|
|
81
|
-
await this.connect();
|
|
82
|
-
await this.collection.updateOne(
|
|
83
|
-
{ _id: guildId },
|
|
84
|
-
{ $set: { ...sessionData, updatedAt: new Date() } },
|
|
85
|
-
{ upsert: true }
|
|
86
|
-
);
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async load(guildId) {
|
|
91
|
-
await this.connect();
|
|
92
|
-
const doc = await this.collection.findOne({ _id: guildId });
|
|
93
|
-
return doc || null;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async delete(guildId) {
|
|
97
|
-
await this.connect();
|
|
98
|
-
const result = await this.collection.deleteOne({ _id: guildId });
|
|
99
|
-
return result.deletedCount > 0;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async loadAll() {
|
|
103
|
-
await this.connect();
|
|
104
|
-
const docs = await this.collection.find({}).toArray();
|
|
105
|
-
return docs.map(doc => ({
|
|
106
|
-
guildId: doc._id,
|
|
107
|
-
...doc
|
|
108
|
-
}));
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function serializeTrack(track) {
|
|
113
|
-
if (!track) return null;
|
|
114
|
-
|
|
115
|
-
let requestedBy = null;
|
|
116
|
-
if (track.requestedBy) {
|
|
117
|
-
if (typeof track.requestedBy === 'string') {
|
|
118
|
-
requestedBy = { username: track.requestedBy };
|
|
119
|
-
} else if (typeof track.requestedBy === 'object') {
|
|
120
|
-
requestedBy = {
|
|
121
|
-
id: track.requestedBy.id || null,
|
|
122
|
-
username: track.requestedBy.username || track.requestedBy.tag || String(track.requestedBy),
|
|
123
|
-
avatarUrl: track.requestedBy.avatarUrl || null
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return {
|
|
129
|
-
id: track.id,
|
|
130
|
-
title: track.title,
|
|
131
|
-
author: track.author,
|
|
132
|
-
duration: track.duration,
|
|
133
|
-
thumbnail: track.thumbnail,
|
|
134
|
-
uri: track.uri,
|
|
135
|
-
source: track.source,
|
|
136
|
-
isLive: track.isLive || false,
|
|
137
|
-
_resolvedId: track._resolvedId,
|
|
138
|
-
requestedBy,
|
|
139
|
-
artistUrl: track.artistUrl || null
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function deserializeTrack(data) {
|
|
144
|
-
if (!data) return null;
|
|
145
|
-
return {
|
|
146
|
-
id: data.id,
|
|
147
|
-
title: data.title,
|
|
148
|
-
author: data.author,
|
|
149
|
-
duration: data.duration,
|
|
150
|
-
thumbnail: data.thumbnail,
|
|
151
|
-
uri: data.uri,
|
|
152
|
-
source: data.source,
|
|
153
|
-
isLive: data.isLive || false,
|
|
154
|
-
_resolvedId: data._resolvedId,
|
|
155
|
-
requestedBy: data.requestedBy,
|
|
156
|
-
artistUrl: data.artistUrl
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
module.exports = {
|
|
161
|
-
PersistenceAdapter,
|
|
162
|
-
MemoryAdapter,
|
|
163
|
-
MongoAdapter,
|
|
164
|
-
serializeTrack,
|
|
165
|
-
deserializeTrack
|
|
166
|
-
};
|