flux-dl 1.1.3 → 1.1.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flux-dl",
3
- "version": "1.1.3",
3
+ "version": "1.1.6",
4
4
  "description": "Leistungsstarke Video-Downloader Library für YouTube, Vimeo und Dailymotion - komplett in JavaScript, keine externen Binaries",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -5,6 +5,7 @@ const platforms = require('./platforms');
5
5
  const VideoEncryption = require('./utils/encryption');
6
6
  const RequestSpoofing = require('./utils/requestSpoofing');
7
7
  const BrowserEmulation = require('./utils/browserEmulation');
8
+ const QualityManager = require('./utils/qualityManager');
8
9
 
9
10
  class VideoDownloader {
10
11
  constructor(options = {}) {
@@ -13,17 +14,24 @@ class VideoDownloader {
13
14
  timeout: 30000,
14
15
  encryptionKey: options.encryptionKey || 'default-key',
15
16
  cookiesFile: options.cookiesFile,
17
+ quality: options.quality || 192, // Standard: Medium (192 kbps)
16
18
  ...options
17
19
  };
18
20
 
19
21
  this.encryption = new VideoEncryption(this.options.encryptionKey);
20
22
  this.spoofing = new RequestSpoofing();
21
23
  this.browser = new BrowserEmulation();
24
+ this.qualityManager = new QualityManager();
22
25
 
23
26
  // Load cookies if provided
24
27
  if (this.options.cookiesFile) {
25
28
  this.browser.cookieManager.loadFromFile(this.options.cookiesFile);
26
29
  }
30
+
31
+ // Set default quality if provided
32
+ if (options.quality) {
33
+ this.qualityManager.setDefaultQuality(options.quality);
34
+ }
27
35
  }
28
36
 
29
37
  async getVideoInfo(url) {
@@ -158,8 +166,17 @@ class VideoDownloader {
158
166
  // Nutze Browser Emulation mit Cookies
159
167
  const referer = `https://www.youtube.com/watch?v=${info.videoId}`;
160
168
 
169
+ // Wrap onProgress to catch errors
170
+ const safeOnProgress = onProgress ? (percent, downloaded, total) => {
171
+ try {
172
+ onProgress(percent, downloaded, total);
173
+ } catch (error) {
174
+ // Silently ignore progress callback errors
175
+ }
176
+ } : null;
177
+
161
178
  try {
162
- const rawStream = await this.browser.downloadStream(info.videoUrl, referer, onProgress);
179
+ const rawStream = await this.browser.downloadStream(info.videoUrl, referer, safeOnProgress);
163
180
 
164
181
  // Create ULTRA-RESILIENT wrapper stream
165
182
  const { PassThrough } = require('stream');
@@ -347,6 +364,169 @@ class VideoDownloader {
347
364
  signUrl(url, expiresIn = 3600) {
348
365
  return this.encryption.signUrl(url, expiresIn);
349
366
  }
367
+
368
+ // ========== QUALITY MANAGEMENT API ==========
369
+
370
+ /**
371
+ * Setzt die Qualität für einen bestimmten User
372
+ * @param {string} userId - User ID
373
+ * @param {number} quality - Bitrate (120-360 kbps)
374
+ * @returns {number} Gesetzte Qualität
375
+ */
376
+ setUserQuality(userId, quality) {
377
+ return this.qualityManager.setUserQuality(userId, quality);
378
+ }
379
+
380
+ /**
381
+ * Gibt die erlaubte Qualität für einen User zurück
382
+ * @param {string} userId - User ID
383
+ * @param {boolean} isPremium - Ist Premium User?
384
+ * @returns {number} Erlaubte Bitrate
385
+ */
386
+ getUserQuality(userId, isPremium = false) {
387
+ return this.qualityManager.getUserQuality(userId, isPremium);
388
+ }
389
+
390
+ /**
391
+ * Setzt globale Standard-Qualität
392
+ * @param {number} quality - Bitrate (120-360 kbps)
393
+ */
394
+ setDefaultQuality(quality) {
395
+ this.qualityManager.setDefaultQuality(quality);
396
+ this.options.quality = quality;
397
+ }
398
+
399
+ /**
400
+ * Setzt maximale Qualität für Free Users
401
+ * @param {number} quality - Bitrate (120-360 kbps)
402
+ */
403
+ setMaxQualityForFreeUsers(quality) {
404
+ this.qualityManager.setMaxQualityForFreeUsers(quality);
405
+ }
406
+
407
+ /**
408
+ * Setzt maximale Qualität für Premium Users
409
+ * @param {number} quality - Bitrate (120-360 kbps)
410
+ */
411
+ setMaxQualityForPremiumUsers(quality) {
412
+ this.qualityManager.setMaxQualityForPremiumUsers(quality);
413
+ }
414
+
415
+ /**
416
+ * Gibt verfügbare Qualitätsstufen für User zurück
417
+ * @param {string} userId - User ID
418
+ * @param {boolean} isPremium - Ist Premium User?
419
+ * @returns {Array} Verfügbare Stufen
420
+ */
421
+ getAvailableQualities(userId, isPremium = false) {
422
+ return this.qualityManager.getAvailableQualities(userId, isPremium);
423
+ }
424
+
425
+ /**
426
+ * Prüft ob User bestimmte Qualität nutzen darf
427
+ * @param {string} userId - User ID
428
+ * @param {number} requestedQuality - Gewünschte Qualität
429
+ * @param {boolean} isPremium - Ist Premium User?
430
+ * @returns {boolean} Erlaubt?
431
+ */
432
+ canUserUseQuality(userId, requestedQuality, isPremium = false) {
433
+ return this.qualityManager.canUserUseQuality(userId, requestedQuality, isPremium);
434
+ }
435
+
436
+ /**
437
+ * Download mit User-spezifischer Qualität
438
+ * @param {string} url - Video URL
439
+ * @param {string} outputPath - Output Pfad
440
+ * @param {string} userId - User ID
441
+ * @param {boolean} isPremium - Ist Premium User?
442
+ * @param {function} onProgress - Progress Callback
443
+ * @returns {Promise<Object>} Download Result
444
+ */
445
+ async downloadAudioWithQuality(url, outputPath, userId, isPremium = false, onProgress = null) {
446
+ const userQuality = this.getUserQuality(userId, isPremium);
447
+
448
+ console.log(`🎵 Downloading for user ${userId}`);
449
+ console.log(`📊 Quality: ${this.qualityManager.getQualityLabel(userQuality)}`);
450
+
451
+ // Temporär Qualität setzen
452
+ const originalQuality = this.options.quality;
453
+ this.options.quality = userQuality;
454
+
455
+ try {
456
+ const result = await this.downloadAudio(url, outputPath, onProgress);
457
+ return {
458
+ ...result,
459
+ quality: userQuality,
460
+ qualityLabel: this.qualityManager.getQualityLabel(userQuality)
461
+ };
462
+ } finally {
463
+ // Qualität zurücksetzen
464
+ this.options.quality = originalQuality;
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Stream mit User-spezifischer Qualität
470
+ * @param {string} url - Video URL
471
+ * @param {string} userId - User ID
472
+ * @param {boolean} isPremium - Ist Premium User?
473
+ * @param {function} onProgress - Progress Callback
474
+ * @returns {Promise<Object>} Stream Result
475
+ */
476
+ async getAudioStreamWithQuality(url, userId, isPremium = false, onProgress = null) {
477
+ const userQuality = this.getUserQuality(userId, isPremium);
478
+
479
+ console.log(`🎵 Streaming for user ${userId}`);
480
+ console.log(`📊 Quality: ${this.qualityManager.getQualityLabel(userQuality)}`);
481
+
482
+ // Temporär Qualität setzen
483
+ const originalQuality = this.options.quality;
484
+ this.options.quality = userQuality;
485
+
486
+ try {
487
+ const result = await this.getAudioStream(url, onProgress);
488
+ return {
489
+ ...result,
490
+ quality: userQuality,
491
+ qualityLabel: this.qualityManager.getQualityLabel(userQuality)
492
+ };
493
+ } finally {
494
+ // Qualität zurücksetzen
495
+ this.options.quality = originalQuality;
496
+ }
497
+ }
498
+
499
+ /**
500
+ * Entfernt User-Permission
501
+ * @param {string} userId - User ID
502
+ */
503
+ removeUserQuality(userId) {
504
+ this.qualityManager.removeUserQuality(userId);
505
+ }
506
+
507
+ /**
508
+ * Gibt alle User-Permissions zurück
509
+ * @returns {Object} User-Permissions
510
+ */
511
+ getAllUserPermissions() {
512
+ return this.qualityManager.getAllUserPermissions();
513
+ }
514
+
515
+ /**
516
+ * Exportiert Quality-Konfiguration
517
+ * @returns {Object} Konfiguration
518
+ */
519
+ exportQualityConfig() {
520
+ return this.qualityManager.exportConfig();
521
+ }
522
+
523
+ /**
524
+ * Importiert Quality-Konfiguration
525
+ * @param {Object} config - Konfiguration
526
+ */
527
+ importQualityConfig(config) {
528
+ this.qualityManager.importConfig(config);
529
+ }
350
530
  }
351
531
 
352
532
  module.exports = VideoDownloader;
package/src/index.js CHANGED
@@ -1,11 +1,13 @@
1
1
  const VideoDownloader = require('./VideoDownloader');
2
2
  const VideoEncryption = require('./utils/encryption');
3
3
  const YouTubeSearch = require('./utils/youtubeSearch');
4
+ const QualityManager = require('./utils/qualityManager');
4
5
  const { supportedPlatforms } = require('./platforms');
5
6
 
6
7
  module.exports = {
7
8
  VideoDownloader,
8
9
  VideoEncryption,
9
10
  YouTubeSearch,
11
+ QualityManager,
10
12
  supportedPlatforms
11
13
  };
@@ -18,7 +18,7 @@ module.exports = {
18
18
  throw new Error('Invalid YouTube URL');
19
19
  }
20
20
 
21
- console.log(`\n=== Extracting YouTube Video: ${videoId} ===`);
21
+ // console.log(`\n=== Extracting YouTube Video: ${videoId} ===`);
22
22
 
23
23
  // Starte Browser-Session (wie echter User)
24
24
  await this.browser.startSession();
@@ -26,9 +26,12 @@ module.exports = {
26
26
  // Methode 1: InnerTube API mit verschiedenen Clients
27
27
  const clientPriority = ['android', 'ios', 'tvEmbedded', 'web'];
28
28
 
29
+ // Get target quality from options
30
+ const targetQuality = options?.quality || null;
31
+
29
32
  for (const clientType of clientPriority) {
30
33
  try {
31
- console.log(`[InnerTube] Trying ${clientType} client...`);
34
+ // console.log(`[InnerTube] Trying ${clientType} client...`);
32
35
  const data = await this.innerTube.getVideoInfo(videoId, clientType);
33
36
 
34
37
  if (data.videoDetails && data.streamingData) {
@@ -41,7 +44,12 @@ module.exports = {
41
44
  if (formatsWithUrl.length > 0) {
42
45
  console.log(`✓ ${clientType} client success! ${formatsWithUrl.length} formats available`);
43
46
 
44
- const bestFormat = this.selectBestFormat(formatsWithUrl);
47
+ // Select format with target quality
48
+ const bestFormat = this.selectBestFormat(formatsWithUrl, targetQuality);
49
+
50
+ // Get actual bitrate for quality label
51
+ const actualBitrate = this.getBitrate(bestFormat);
52
+ const qualityLabel = `${actualBitrate}kbps`;
45
53
 
46
54
  // Get highest quality thumbnail (last one in array)
47
55
  const thumbnails = data.videoDetails.thumbnail.thumbnails;
@@ -58,7 +66,8 @@ module.exports = {
58
66
  likeCount: parseInt(data.videoDetails.likeCount || 0),
59
67
  uploadDate: data.videoDetails.uploadDate || data.videoDetails.publishDate || null,
60
68
  videoUrl: bestFormat.url,
61
- quality: bestFormat.qualityLabel || bestFormat.quality || 'unknown',
69
+ quality: qualityLabel,
70
+ bitrate: actualBitrate,
62
71
  format: bestFormat,
63
72
  allFormats: formatsWithUrl,
64
73
  platform: this.name,
@@ -128,8 +137,14 @@ module.exports = {
128
137
  throw new Error('No valid formats found');
129
138
  }
130
139
 
131
- const bestFormat = this.selectBestFormat(validFormats);
132
- console.log(`Selected: ${bestFormat.qualityLabel || 'unknown'} quality\n`);
140
+ // Select format with target quality (already declared above)
141
+ const bestFormat = this.selectBestFormat(validFormats, targetQuality);
142
+
143
+ // Get actual bitrate for quality label
144
+ const actualBitrate = this.getBitrate(bestFormat);
145
+ const qualityLabel = `${actualBitrate}kbps`;
146
+
147
+ console.log(`Selected: ${qualityLabel} quality\n`);
133
148
 
134
149
  // Get highest quality thumbnail (last one in array)
135
150
  const thumbnails = videoDetails.thumbnail.thumbnails;
@@ -146,7 +161,8 @@ module.exports = {
146
161
  likeCount: parseInt(videoDetails.likeCount || 0),
147
162
  uploadDate: videoDetails.uploadDate || videoDetails.publishDate || null,
148
163
  videoUrl: bestFormat.url,
149
- quality: bestFormat.qualityLabel || 'unknown',
164
+ quality: qualityLabel,
165
+ bitrate: actualBitrate,
150
166
  format: bestFormat,
151
167
  allFormats: validFormats,
152
168
  platform: this.name,
@@ -429,8 +445,35 @@ module.exports = {
429
445
  return res.data;
430
446
  },
431
447
 
432
- selectBestFormat(formats) {
433
- // Mit Audio + Video
448
+ selectBestFormat(formats, targetQuality = null) {
449
+ // Wenn targetQuality angegeben, nutze Audio-only Format mit passender Bitrate
450
+ if (targetQuality) {
451
+ const audioFormats = formats.filter(f =>
452
+ f.url && f.mimeType && f.mimeType.includes('audio')
453
+ );
454
+
455
+ if (audioFormats.length > 0) {
456
+ // Sortiere nach Bitrate-Nähe zur Ziel-Qualität
457
+ audioFormats.sort((a, b) => {
458
+ const bitrateA = this.getBitrate(a);
459
+ const bitrateB = this.getBitrate(b);
460
+ return Math.abs(bitrateA - targetQuality) - Math.abs(bitrateB - targetQuality);
461
+ });
462
+
463
+ // Finde Format das unter oder gleich der Ziel-Qualität ist
464
+ for (const format of audioFormats) {
465
+ const bitrate = this.getBitrate(format);
466
+ if (bitrate <= targetQuality) {
467
+ return format;
468
+ }
469
+ }
470
+
471
+ // Fallback: Nehme das kleinste Format
472
+ return audioFormats[audioFormats.length - 1];
473
+ }
474
+ }
475
+
476
+ // Standard: Mit Audio + Video
434
477
  const withBoth = formats.filter(f =>
435
478
  f.url && f.mimeType && f.mimeType.includes('video') && f.audioQuality
436
479
  );
@@ -451,6 +494,31 @@ module.exports = {
451
494
  return formats[0];
452
495
  },
453
496
 
497
+ getBitrate(format) {
498
+ if (format.bitrate) {
499
+ return Math.round(format.bitrate / 1000); // Bits zu kbps
500
+ }
501
+
502
+ if (format.audioBitrate) {
503
+ return format.audioBitrate;
504
+ }
505
+
506
+ if (format.averageBitrate) {
507
+ return Math.round(format.averageBitrate / 1000);
508
+ }
509
+
510
+ // Fallback: Schätze aus Quality-Label
511
+ if (format.qualityLabel) {
512
+ const match = format.qualityLabel.match(/(\d+)kbps/i);
513
+ if (match) {
514
+ return parseInt(match[1]);
515
+ }
516
+ }
517
+
518
+ // Default: Mittel
519
+ return 192;
520
+ },
521
+
454
522
  extractVideoId(url) {
455
523
  let m = url.match(/[?&]v=([^&]+)/);
456
524
  if (m) return m[1];
@@ -280,8 +280,7 @@ class BrowserEmulation {
280
280
  onProgress(percent, downloadedLength, totalLength);
281
281
  }
282
282
  } catch (err) {
283
- // Even progress callback errors won't crash us
284
- console.warn('⚠️ Progress callback error (handled)');
283
+ // Silently ignore progress callback errors
285
284
  }
286
285
  });
287
286
  }
@@ -0,0 +1,270 @@
1
+ /**
2
+ * QUALITY MANAGER - Audio Bitrate Control System
3
+ * Verwaltet Audio-Qualität mit User-Permissions (120-360 kbps)
4
+ */
5
+
6
+ class QualityManager {
7
+ constructor() {
8
+ // Qualitätsstufen (in kbps für Audio)
9
+ this.QUALITY_LEVELS = {
10
+ MINIMUM: 120, // Niedrigste Qualität
11
+ LOW: 128, // Niedrig
12
+ MEDIUM: 192, // Mittel (Standard)
13
+ HIGH: 256, // Hoch
14
+ MAXIMUM: 360 // Höchste Qualität
15
+ };
16
+
17
+ // User-Permissions (wer darf welche Qualität nutzen)
18
+ this.userPermissions = new Map();
19
+
20
+ // Globale Einstellungen
21
+ this.defaultQuality = this.QUALITY_LEVELS.MEDIUM;
22
+ this.maxQualityForFreeUsers = this.QUALITY_LEVELS.MEDIUM;
23
+ this.maxQualityForPremiumUsers = this.QUALITY_LEVELS.MAXIMUM;
24
+ }
25
+
26
+ /**
27
+ * Setzt die erlaubte Qualität für einen User
28
+ * @param {string} userId - User ID
29
+ * @param {number} maxQuality - Maximale Bitrate (120-360)
30
+ */
31
+ setUserQuality(userId, maxQuality) {
32
+ const quality = this.validateQuality(maxQuality);
33
+ this.userPermissions.set(userId, quality);
34
+ return quality;
35
+ }
36
+
37
+ /**
38
+ * Gibt die erlaubte Qualität für einen User zurück
39
+ * @param {string} userId - User ID
40
+ * @param {boolean} isPremium - Ist der User Premium?
41
+ * @returns {number} Erlaubte Bitrate
42
+ */
43
+ getUserQuality(userId, isPremium = false) {
44
+ // Wenn User spezifische Permission hat
45
+ if (this.userPermissions.has(userId)) {
46
+ return this.userPermissions.get(userId);
47
+ }
48
+
49
+ // Sonst nach Premium-Status
50
+ return isPremium ? this.maxQualityForPremiumUsers : this.maxQualityForFreeUsers;
51
+ }
52
+
53
+ /**
54
+ * Validiert und begrenzt Qualitätswert
55
+ * @param {number} quality - Gewünschte Bitrate
56
+ * @returns {number} Validierte Bitrate (120-360)
57
+ */
58
+ validateQuality(quality) {
59
+ const q = parseInt(quality);
60
+
61
+ if (isNaN(q)) {
62
+ return this.defaultQuality;
63
+ }
64
+
65
+ // Begrenze auf Min/Max
66
+ if (q < this.QUALITY_LEVELS.MINIMUM) {
67
+ return this.QUALITY_LEVELS.MINIMUM;
68
+ }
69
+
70
+ if (q > this.QUALITY_LEVELS.MAXIMUM) {
71
+ return this.QUALITY_LEVELS.MAXIMUM;
72
+ }
73
+
74
+ return q;
75
+ }
76
+
77
+ /**
78
+ * Wählt das beste Audio-Format basierend auf Qualitätseinstellung
79
+ * @param {Array} formats - Verfügbare Formate
80
+ * @param {number} targetQuality - Ziel-Bitrate
81
+ * @returns {Object} Bestes Format
82
+ */
83
+ selectBestAudioFormat(formats, targetQuality) {
84
+ // Nur Audio-Formate
85
+ const audioFormats = formats.filter(f =>
86
+ f.mimeType && f.mimeType.includes('audio') && f.url
87
+ );
88
+
89
+ if (audioFormats.length === 0) return null;
90
+
91
+ // Sortiere nach Bitrate
92
+ audioFormats.sort((a, b) => {
93
+ const bitrateA = this.getBitrate(a);
94
+ const bitrateB = this.getBitrate(b);
95
+ return Math.abs(bitrateA - targetQuality) - Math.abs(bitrateB - targetQuality);
96
+ });
97
+
98
+ // Finde Format das am nächsten zur Ziel-Qualität ist
99
+ let bestFormat = audioFormats[0];
100
+
101
+ for (const format of audioFormats) {
102
+ const bitrate = this.getBitrate(format);
103
+
104
+ // Wenn Format unter Ziel-Qualität liegt, nehme es
105
+ if (bitrate <= targetQuality && bitrate > this.getBitrate(bestFormat)) {
106
+ bestFormat = format;
107
+ }
108
+ }
109
+
110
+ return bestFormat;
111
+ }
112
+
113
+ /**
114
+ * Extrahiert Bitrate aus Format
115
+ * @param {Object} format - Format-Objekt
116
+ * @returns {number} Bitrate in kbps
117
+ */
118
+ getBitrate(format) {
119
+ if (format.bitrate) {
120
+ return Math.round(format.bitrate / 1000); // Bits zu kbps
121
+ }
122
+
123
+ if (format.audioBitrate) {
124
+ return format.audioBitrate;
125
+ }
126
+
127
+ // Fallback: Schätze aus Quality-Label
128
+ if (format.qualityLabel) {
129
+ const match = format.qualityLabel.match(/(\d+)kbps/i);
130
+ if (match) {
131
+ return parseInt(match[1]);
132
+ }
133
+ }
134
+
135
+ // Default: Mittel
136
+ return this.QUALITY_LEVELS.MEDIUM;
137
+ }
138
+
139
+ /**
140
+ * Gibt Qualitäts-Label zurück
141
+ * @param {number} bitrate - Bitrate in kbps
142
+ * @returns {string} Label
143
+ */
144
+ getQualityLabel(bitrate) {
145
+ if (bitrate >= this.QUALITY_LEVELS.MAXIMUM) return '🔥 Maximum (360 kbps)';
146
+ if (bitrate >= this.QUALITY_LEVELS.HIGH) return '⭐ High (256 kbps)';
147
+ if (bitrate >= this.QUALITY_LEVELS.MEDIUM) return '✅ Medium (192 kbps)';
148
+ if (bitrate >= this.QUALITY_LEVELS.LOW) return '📊 Low (128 kbps)';
149
+ return '💾 Minimum (120 kbps)';
150
+ }
151
+
152
+ /**
153
+ * Setzt globale Standard-Qualität
154
+ * @param {number} quality - Bitrate (120-360)
155
+ */
156
+ setDefaultQuality(quality) {
157
+ this.defaultQuality = this.validateQuality(quality);
158
+ }
159
+
160
+ /**
161
+ * Setzt maximale Qualität für Free Users
162
+ * @param {number} quality - Bitrate (120-360)
163
+ */
164
+ setMaxQualityForFreeUsers(quality) {
165
+ this.maxQualityForFreeUsers = this.validateQuality(quality);
166
+ }
167
+
168
+ /**
169
+ * Setzt maximale Qualität für Premium Users
170
+ * @param {number} quality - Bitrate (120-360)
171
+ */
172
+ setMaxQualityForPremiumUsers(quality) {
173
+ this.maxQualityForPremiumUsers = this.validateQuality(quality);
174
+ }
175
+
176
+ /**
177
+ * Entfernt User-Permission
178
+ * @param {string} userId - User ID
179
+ */
180
+ removeUserQuality(userId) {
181
+ this.userPermissions.delete(userId);
182
+ }
183
+
184
+ /**
185
+ * Gibt alle User-Permissions zurück
186
+ * @returns {Object} User-Permissions
187
+ */
188
+ getAllUserPermissions() {
189
+ const permissions = {};
190
+ for (const [userId, quality] of this.userPermissions.entries()) {
191
+ permissions[userId] = {
192
+ quality,
193
+ label: this.getQualityLabel(quality)
194
+ };
195
+ }
196
+ return permissions;
197
+ }
198
+
199
+ /**
200
+ * Prüft ob User bestimmte Qualität nutzen darf
201
+ * @param {string} userId - User ID
202
+ * @param {number} requestedQuality - Gewünschte Qualität
203
+ * @param {boolean} isPremium - Ist Premium User?
204
+ * @returns {boolean} Erlaubt?
205
+ */
206
+ canUserUseQuality(userId, requestedQuality, isPremium = false) {
207
+ const maxAllowed = this.getUserQuality(userId, isPremium);
208
+ return requestedQuality <= maxAllowed;
209
+ }
210
+
211
+ /**
212
+ * Gibt verfügbare Qualitätsstufen für User zurück
213
+ * @param {string} userId - User ID
214
+ * @param {boolean} isPremium - Ist Premium User?
215
+ * @returns {Array} Verfügbare Stufen
216
+ */
217
+ getAvailableQualities(userId, isPremium = false) {
218
+ const maxQuality = this.getUserQuality(userId, isPremium);
219
+ const available = [];
220
+
221
+ for (const [name, value] of Object.entries(this.QUALITY_LEVELS)) {
222
+ if (value <= maxQuality) {
223
+ available.push({
224
+ name,
225
+ value,
226
+ label: this.getQualityLabel(value)
227
+ });
228
+ }
229
+ }
230
+
231
+ return available;
232
+ }
233
+
234
+ /**
235
+ * Exportiert Konfiguration
236
+ * @returns {Object} Konfiguration
237
+ */
238
+ exportConfig() {
239
+ return {
240
+ defaultQuality: this.defaultQuality,
241
+ maxQualityForFreeUsers: this.maxQualityForFreeUsers,
242
+ maxQualityForPremiumUsers: this.maxQualityForPremiumUsers,
243
+ userPermissions: Object.fromEntries(this.userPermissions)
244
+ };
245
+ }
246
+
247
+ /**
248
+ * Importiert Konfiguration
249
+ * @param {Object} config - Konfiguration
250
+ */
251
+ importConfig(config) {
252
+ if (config.defaultQuality) {
253
+ this.defaultQuality = this.validateQuality(config.defaultQuality);
254
+ }
255
+
256
+ if (config.maxQualityForFreeUsers) {
257
+ this.maxQualityForFreeUsers = this.validateQuality(config.maxQualityForFreeUsers);
258
+ }
259
+
260
+ if (config.maxQualityForPremiumUsers) {
261
+ this.maxQualityForPremiumUsers = this.validateQuality(config.maxQualityForPremiumUsers);
262
+ }
263
+
264
+ if (config.userPermissions) {
265
+ this.userPermissions = new Map(Object.entries(config.userPermissions));
266
+ }
267
+ }
268
+ }
269
+
270
+ module.exports = QualityManager;