@lmna22/aio-downloader 2.0.2 → 2.0.3

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @lmna22/aio-downloader
2
2
 
3
- > All-in-one media downloader for YouTube, Instagram, TikTok, Pinterest, Pixiv, X/Twitter, Lahelu, and Xiaohongshu/RedNote.
3
+ > All-in-one media downloader for YouTube, Instagram, TikTok, Pinterest, Pixiv, X (Twitter), Lahelu, Xiaohongshu (RedNote), and more.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@lmna22/aio-downloader.svg)](https://www.npmjs.com/package/@lmna22/aio-downloader)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -22,6 +22,8 @@ Scrape and download videos, audio, and images from multiple platforms with a sin
22
22
  | **X / Twitter** | ✅ (Best quality) | — | ✅ | — |
23
23
  | **Lahelu** | ✅ | — | ✅ | — |
24
24
  | **Xiaohongshu/RedNote** | ✅ | — | ✅ | — |
25
+ | **Dailymotion** | ✅ | ✅ | ✅ | ✅ |
26
+ | **Spotify** | — | ✅ (MP3, Opus, WAV) | — | — |
25
27
 
26
28
  - 🔗 **Auto-detect platform** from URL — just pass any supported link
27
29
  - 📦 **Programmatic API** — designed for Node.js applications, bots, and scripts
@@ -110,14 +112,63 @@ if (result.status) {
110
112
  console.log(result.data.stats.collects);
111
113
  console.log(result.data.stats.comments);
112
114
  console.log(result.data.media.url);
113
-
115
+
114
116
  // Download the media
115
117
  const { download } = require("@lmna22/aio-downloader");
116
118
  await download(result.data.media.url, `./downloads/${result.data.fileName}`);
117
119
  }
118
120
  ```
119
121
 
120
- *(See the API Reference below for other platforms: Pinterest, Pixiv, Twitter, Lahelu)*
122
+ ### Dailymotion
123
+
124
+ ```javascript
125
+ const { lmna } = require("@lmna22/aio-downloader");
126
+
127
+ // Download video
128
+ const result = await lmna.dailymotion("https://www.dailymotion.com/video/x8z3v2y");
129
+
130
+ if (result.status) {
131
+ console.log(result.data.title);
132
+ console.log(result.data.author);
133
+ console.log(result.data.views);
134
+ console.log(result.data.url);
135
+ }
136
+
137
+ // Search videos
138
+ const searchResult = await lmna.dailymotion("https://www.dailymotion.com/search/programming", { query: "programming", limit: 10 });
139
+
140
+ if (searchResult.status) {
141
+ console.log(searchResult.data.videos);
142
+ }
143
+ ```
144
+
145
+ ### Spotify
146
+
147
+ ```javascript
148
+ const { lmna } = require("@lmna22/aio-downloader");
149
+
150
+ // Get track metadata (returns search query, does not download)
151
+ const result = await lmna.spotify("https://open.spotify.com/track/4cOdK2wGLETKBW3PvgPWqT");
152
+
153
+ if (result.status) {
154
+ console.log(result.data.title);
155
+ console.log(result.data.artist);
156
+ console.log(result.data.cover);
157
+ console.log(result.data.searchQuery); // Use this to download with yt-dlp
158
+
159
+ // Download using yt-dlp or similar tool
160
+ // Example: yt-dlp "ytsearch1:Never Gonna Give You Up audio" --extract-audio --audio-format mp3
161
+ }
162
+
163
+ // Supported formats: mp3, opus, wav
164
+ const mp3Result = await lmna.spotify("https://open.spotify.com/track/abc123", { format: 'mp3' });
165
+ const opusResult = await lmna.spotify("https://open.spotify.com/track/abc123", { format: 'opus' });
166
+ const wavResult = await lmna.spotify("https://open.spotify.com/track/abc123", { format: 'wav' });
167
+ ```
168
+
169
+ **Note:** Spotify scraper returns metadata and a search query. Use yt-dlp or similar tool to download the actual audio file using the provided searchQuery.
170
+
171
+ *(See the API Reference below for other platforms: Pinterest, Pixiv, Twitter, Lahelu, Dailymotion)*
121
172
 
122
173
  ---
123
174
 
@@ -157,6 +208,9 @@ await aioDownloader("https://lahelu.com/post/abc123");
157
208
  await aioDownloader("https://www.xiaohongshu.com/explore/abc123");
158
209
  await aioDownloader("https://www.rednote.com/explore/abc123");
159
210
  await aioDownloader("https://xhslink.com/abc123");
211
+ await aioDownloader("https://www.dailymotion.com/video/x8z3v2y");
212
+ await aioDownloader("https://www.dailymotion.com/search/programming");
213
+ await aioDownloader("https://open.spotify.com/track/4cOdK2wGLETKBW3PvgPWqT");
160
214
 
161
215
  // Force a specific platform
162
216
  await aioDownloader("https://example.com/video", { platform: "youtube", quality: 5 });
@@ -173,6 +227,9 @@ detectPlatform("https://lahelu.com/post/abc"); // "lahelu"
173
227
  detectPlatform("https://xiaohongshu.com/explore/abc"); // "xiaohongshu"
174
228
  detectPlatform("https://rednote.com/explore/abc"); // "xiaohongshu"
175
229
  detectPlatform("https://xhslink.com/abc"); // "xiaohongshu"
230
+ detectPlatform("https://dailymotion.com/video/x8z3v2y"); // "dailymotion"
231
+ detectPlatform("https://dailymotion.com/search/music"); // "dailymotion"
232
+ detectPlatform("https://open.spotify.com/track/abc123"); // "spotify"
176
233
  detectPlatform("https://unknown.com"); // null
177
234
  ```
178
235
 
@@ -196,6 +253,8 @@ await lmna.pixiv(url, options);
196
253
  await lmna.twitter(url);
197
254
  await lmna.lahelu(url);
198
255
  await lmna.xiaohongshu(url);
256
+ await lmna.dailymotion(url, options);
257
+ await lmna.spotify(url, options);
199
258
 
200
259
  // All return: { status, platform, data?, message? }
201
260
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lmna22/aio-downloader",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "All-in-one media downloader for YouTube, Instagram, TikTok, Pinterest, Pixiv, and X/Twitter. Scrape and download videos, audio, and images from multiple platforms with a single library.",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -20,7 +20,10 @@
20
20
  "image",
21
21
  "media",
22
22
  "scraper",
23
- "aio"
23
+ "aio",
24
+ "lahelu",
25
+ "dailymotion",
26
+ "xiaohongshu"
24
27
  ],
25
28
  "author": "",
26
29
  "license": "MIT",
@@ -37,7 +40,7 @@
37
40
  "axios-cookiejar-support": "^5.0.0",
38
41
  "cheerio": "^1.0.0",
39
42
  "ffmpeg-static": "^5.0.0",
40
- "puppeteer": "^24.0.0",
43
+ "puppeteer": "^24.40.0",
41
44
  "qs": "^6.13.0",
42
45
  "tough-cookie": "^5.1.0",
43
46
  "youtube-dl-exec": "^3.1.4"
package/src/index.js CHANGED
@@ -6,6 +6,8 @@ const pixivDownloader = require("./lib/pixiv");
6
6
  const twitterDownloader = require("./lib/twitter");
7
7
  const laheluDownloader = require("./lib/lahelu");
8
8
  const xiaohongshuDownloader = require("./lib/xiaohongshu");
9
+ const dailymotionDownloader = require("./lib/dailymotion");
10
+ const spotifyDownloader = require("./lib/spotify");
9
11
  const download = require("./download");
10
12
 
11
13
  const PLATFORM_PATTERNS = [
@@ -17,6 +19,8 @@ const PLATFORM_PATTERNS = [
17
19
  { name: "twitter", test: (url) => /(?:twitter\.com\/|x\.com\/)/i.test(url) },
18
20
  { name: "lahelu", test: (url) => /lahelu\.com\/post\//i.test(url) },
19
21
  { name: "xiaohongshu", test: (url) => /xiaohongshu\.com|rednote\.com|xhslink\.com/i.test(url) },
22
+ { name: "dailymotion", test: (url) => /dailymotion\.com\//i.test(url) },
23
+ { name: "spotify", test: (url) => /open\.spotify\.com\//i.test(url) },
20
24
  ];
21
25
 
22
26
  function detectPlatform(url) {
@@ -35,6 +39,8 @@ const scrapers = {
35
39
  twitter: (url) => twitterDownloader(url),
36
40
  lahelu: (url) => laheluDownloader(url),
37
41
  xiaohongshu: (url) => xiaohongshuDownloader(url),
42
+ dailymotion: (url, options) => dailymotionDownloader(url, options),
43
+ spotify: (url, options) => spotifyDownloader(url, options),
38
44
  };
39
45
 
40
46
  async function aioDownloader(url, options = {}) {
@@ -42,7 +48,7 @@ async function aioDownloader(url, options = {}) {
42
48
 
43
49
  if (!platform) {
44
50
  throw new Error(
45
- `Unsupported or unrecognized URL: ${url}. Supported platforms: YouTube, Instagram, TikTok, Pinterest, Pixiv, X/Twitter.`
51
+ `Unsupported or unrecognized URL: ${url}. Supported platforms: YouTube, Instagram, TikTok, Pinterest, Pixiv, X/Twitter, Dailymotion, Spotify.`
46
52
  );
47
53
  }
48
54
 
@@ -64,6 +70,8 @@ const lmna = {
64
70
  twitter: (url) => twitterDownloader(url),
65
71
  lahelu: (url) => laheluDownloader(url),
66
72
  xiaohongshu: (url) => xiaohongshuDownloader(url),
73
+ dailymotion: (url, options) => dailymotionDownloader(url, options),
74
+ spotify: (url, options) => spotifyDownloader(url, options),
67
75
  };
68
76
 
69
77
  module.exports = {
@@ -0,0 +1,278 @@
1
+ const puppeteer = require('puppeteer');
2
+
3
+ function extractVideoId(url) {
4
+ const patterns = [
5
+ /dailymotion\.com\/video\/([a-zA-Z0-9]+)/,
6
+ /dai\.ly\/([a-zA-Z0-9]+)/,
7
+ /dailymotion\.com\/embed\/video\/([a-zA-Z0-9]+)/,
8
+ ];
9
+
10
+ for (const pattern of patterns) {
11
+ const match = url.match(pattern);
12
+ if (match) return match[1];
13
+ }
14
+ return null;
15
+ }
16
+
17
+ async function fetchDailymotionData(url) {
18
+ const videoId = extractVideoId(url);
19
+ if (!videoId) {
20
+ throw new Error('Invalid Dailymotion URL');
21
+ }
22
+
23
+ let browser;
24
+ try {
25
+ browser = await puppeteer.launch({
26
+ headless: 'new',
27
+ args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu']
28
+ });
29
+
30
+ const page = await browser.newPage();
31
+ await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
32
+
33
+ let videoData = null;
34
+ let masterM3U8Url = null;
35
+ let masterM3U8Content = null;
36
+
37
+ page.on('response', async (response) => {
38
+ const resUrl = response.url();
39
+
40
+ if (resUrl.includes('geo.dailymotion.com') && resUrl.includes('/video/') && resUrl.includes('.json') && !resUrl.includes('fields=')) {
41
+ try {
42
+ const data = await response.json();
43
+ if (data && data.id === videoId) {
44
+ videoData = data;
45
+ }
46
+ } catch (e) { }
47
+ }
48
+
49
+ if (resUrl.includes('cdndirector.dailymotion.com') && resUrl.includes('.m3u8') && !resUrl.includes('cookie_sync') && !resUrl.includes('af=')) {
50
+ try {
51
+ const text = await response.text();
52
+ if (text.includes('#EXTM3U') && text.includes('#EXT-X-STREAM-INF')) {
53
+ masterM3U8Url = resUrl;
54
+ masterM3U8Content = text;
55
+ }
56
+ } catch (e) { }
57
+ }
58
+
59
+ if (resUrl.includes('graphql-eu-west-1.api.dailymotion.com') && resUrl.includes('video')) {
60
+ try {
61
+ const data = await response.json();
62
+ if (data && data.data && data.data.video && data.data.video.xid === videoId) {
63
+ const v = data.data.video;
64
+ videoData = {
65
+ id: v.xid || v.id,
66
+ title: v.title,
67
+ description: v.description,
68
+ duration: v.duration,
69
+ author: v.author,
70
+ thumbnails: {
71
+ '360': v.thumbnailx360,
72
+ '480': v.thumbnailx480,
73
+ '720': v.thumbnailx720
74
+ },
75
+ created_time: v.createdAt ? Math.floor(new Date(v.createdAt).getTime() / 1000) : null,
76
+ views_total: v.views,
77
+ likes_total: v.likes
78
+ };
79
+ }
80
+ } catch (e) { }
81
+ }
82
+ });
83
+
84
+ await page.goto(url, {
85
+ waitUntil: 'networkidle0',
86
+ timeout: 30000
87
+ });
88
+
89
+ await new Promise(r => setTimeout(r, 5000));
90
+
91
+ if (!videoData) {
92
+ videoData = await page.evaluate((id) => {
93
+ const content = document.documentElement.outerHTML;
94
+
95
+ const jsonPattern = new RegExp('{"id":"[^"]*' + id + '[^}]*"title":"[^"]+"[^}]*}', 'g');
96
+ const matches = content.match(jsonPattern);
97
+
98
+ if (matches && matches.length > 0) {
99
+ for (const match of matches) {
100
+ try {
101
+ const data = JSON.parse(match);
102
+ if (data.id === id && data.title) {
103
+ return {
104
+ id: data.id,
105
+ title: data.title,
106
+ description: data.description || '',
107
+ duration: data.duration || 0,
108
+ author: data.author || '',
109
+ thumbnails: data.thumbnails || {},
110
+ created_time: data.created_time,
111
+ views_total: data.views_total,
112
+ likes_total: data.likes_total
113
+ };
114
+ }
115
+ } catch (e) { }
116
+ }
117
+ }
118
+ return null;
119
+ }, videoId);
120
+ }
121
+
122
+ if (!videoData) {
123
+ throw new Error('Failed to fetch video metadata');
124
+ }
125
+
126
+ if (!masterM3U8Content || !masterM3U8Content.includes('#EXT-X-STREAM-INF')) {
127
+ throw new Error('No video stream found');
128
+ }
129
+
130
+ await browser.close();
131
+
132
+ const lines = masterM3U8Content.split('\n');
133
+ const streams = [];
134
+ let currentBandwidth = 0;
135
+ let currentResolution = '';
136
+ let currentName = '';
137
+
138
+ for (let i = 0; i < lines.length; i++) {
139
+ const line = lines[i].trim();
140
+ if (line.startsWith('#EXT-X-STREAM-INF:')) {
141
+ const bwMatch = line.match(/BANDWIDTH=(\d+)/);
142
+ const resMatch = line.match(/RESOLUTION=(\d+x\d+)/);
143
+ const nameMatch = line.match(/NAME="([^"]+)"/);
144
+
145
+ currentBandwidth = bwMatch ? parseInt(bwMatch[1]) : 0;
146
+ currentResolution = resMatch ? resMatch[1] : '';
147
+ currentName = nameMatch ? nameMatch[1] : '';
148
+ } else if (line && !line.startsWith('#') && (line.includes('.m3u8') || line.includes('dmcdn'))) {
149
+ if (currentResolution) {
150
+ const [width, height] = currentResolution.split('x').map(Number);
151
+ const fullUrl = line.startsWith('http') ? line : masterM3U8Url.substring(0, masterM3U8Url.lastIndexOf('/') + 1) + line;
152
+ streams.push({
153
+ quality: currentName ? `${currentName}p` : `${height}p`,
154
+ height: height,
155
+ width: width,
156
+ bandwidth: currentBandwidth,
157
+ name: currentName,
158
+ fps: null,
159
+ formatId: currentResolution,
160
+ type: 'MP4',
161
+ url: fullUrl,
162
+ });
163
+ }
164
+ currentResolution = '';
165
+ }
166
+ }
167
+
168
+ if (streams.length === 0) {
169
+ throw new Error('No video qualities found in manifest');
170
+ }
171
+
172
+ streams.sort((a, b) => a.height - b.height);
173
+
174
+ streams.push({
175
+ quality: 'Audio Only',
176
+ height: 0,
177
+ width: 0,
178
+ bandwidth: streams[0].bandwidth,
179
+ name: 'Audio',
180
+ fps: null,
181
+ formatId: 'audio',
182
+ type: 'MP3',
183
+ url: streams[0].url,
184
+ });
185
+
186
+ const metadata = {
187
+ id: videoData.id || videoId,
188
+ title: videoData.title || 'Unknown',
189
+ description: videoData.description || '',
190
+ duration: videoData.duration || 0,
191
+ views: parseInt(videoData.views_total) || 0,
192
+ likes: parseInt(videoData.likes_total) || 0,
193
+ owner: {
194
+ screenname: videoData.owner?.screenname || videoData.author || '',
195
+ username: videoData.owner?.username || '',
196
+ id: videoData.owner?.id || '',
197
+ },
198
+ thumbnail: videoData.thumbnails?.['720'] || videoData.thumbnails?.['480'] || videoData.thumbnails?.['360'] || '',
199
+ uploadDate: videoData.created_time ? new Date(videoData.created_time * 1000).toISOString() : '',
200
+ };
201
+
202
+ return {
203
+ metadata,
204
+ streams,
205
+ info: videoData,
206
+ };
207
+ } catch (err) {
208
+ if (browser) await browser.close().catch(() => { });
209
+ throw err;
210
+ }
211
+ }
212
+
213
+ async function dailymotionDownloader(url) {
214
+ if (typeof url !== 'string' || (!url.includes('dailymotion.com') && !url.includes('dai.ly'))) {
215
+ return {
216
+ status: false,
217
+ platform: "dailymotion",
218
+ message: 'Invalid Dailymotion URL'
219
+ };
220
+ }
221
+
222
+ try {
223
+ const data = await fetchDailymotionData(url);
224
+
225
+ if (!data || !data.metadata || !data.streams || data.streams.length === 0) {
226
+ return {
227
+ status: false,
228
+ platform: "dailymotion",
229
+ message: 'Failed to get video data'
230
+ };
231
+ }
232
+
233
+ const { metadata, streams } = data;
234
+
235
+ const videoUrls = streams
236
+ .filter(s => s.quality !== 'Audio Only')
237
+ .map(s => s.url);
238
+
239
+ const audioUrl = streams.find(s => s.quality === 'Audio Only')?.url;
240
+
241
+ const qualities = streams
242
+ .filter(s => s.quality !== 'Audio Only')
243
+ .map(s => ({
244
+ quality: s.quality,
245
+ resolution: `${s.width}x${s.height}`,
246
+ bandwidth: s.bandwidth,
247
+ url: s.url
248
+ }));
249
+
250
+ return {
251
+ status: true,
252
+ platform: "dailymotion",
253
+ data: {
254
+ id: metadata.id,
255
+ title: metadata.title,
256
+ description: metadata.description,
257
+ duration: metadata.duration,
258
+ views: metadata.views,
259
+ likes: metadata.likes,
260
+ author: metadata.owner?.screenname || metadata.owner?.username,
261
+ thumbnail: metadata.thumbnail,
262
+ uploadDate: metadata.uploadDate,
263
+ url: videoUrls.length > 0 ? videoUrls : [audioUrl],
264
+ audio: audioUrl || null,
265
+ qualities: qualities,
266
+ isVideo: true
267
+ }
268
+ };
269
+ } catch (error) {
270
+ return {
271
+ status: false,
272
+ platform: "dailymotion",
273
+ message: error.message
274
+ };
275
+ }
276
+ }
277
+
278
+ module.exports = dailymotionDownloader;
@@ -0,0 +1,89 @@
1
+ const axios = require('axios');
2
+ const youtubedl = require('youtube-dl-exec');
3
+
4
+ async function getSpotifyTrackInfo(url) {
5
+ try {
6
+ const trackId = url.split('track/')[1]?.split('?')[0] || url.split('track/')[1]?.split('/')[0];
7
+
8
+ if (!trackId) {
9
+ throw new Error('Invalid Spotify track URL');
10
+ }
11
+
12
+ const { data } = await axios.get(`https://open.spotify.com/oembed?url=${encodeURIComponent(url)}`, {
13
+ headers: {
14
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
15
+ }
16
+ });
17
+
18
+ let title = data.title || 'Unknown Title';
19
+ let cover = data.thumbnail_url || '';
20
+
21
+ const searchQuery = title;
22
+
23
+ return {
24
+ id: trackId,
25
+ title: title.replace(/\s*-\s*(Original Audio|Spotify|Audio|Official Video|Lyrics)$/i, '').trim(),
26
+ artist: 'Spotify Track',
27
+ cover: cover,
28
+ url: url,
29
+ searchQuery: searchQuery
30
+ };
31
+ } catch (e) {
32
+ throw new Error('Failed to fetch Spotify metadata: ' + e.message);
33
+ }
34
+ }
35
+
36
+ async function spotifyDownloader(url, options = {}) {
37
+ const { format = 'mp3' } = options;
38
+
39
+ if (typeof url !== 'string' || !url.includes('spotify.com/')) {
40
+ return {
41
+ status: false,
42
+ platform: 'spotify',
43
+ message: 'Invalid Spotify URL. Only track links are supported.'
44
+ };
45
+ }
46
+
47
+ try {
48
+ const trackMetadata = await getSpotifyTrackInfo(url);
49
+
50
+ // search on youtube for the track
51
+ let videoUrl = null;
52
+ try {
53
+ const searchResult = await youtubedl(`ytsearch1:${trackMetadata.searchQuery}`, {
54
+ dumpSingleJson: true,
55
+ noCheckCertificates: true,
56
+ noWarnings: true,
57
+ });
58
+ videoUrl = searchResult.entries?.[0]?.webpage_url || null;
59
+ } catch (err) {
60
+ console.error('Spotify YouTube Search Error:', err.message);
61
+ }
62
+
63
+ return {
64
+ status: true,
65
+ platform: 'spotify',
66
+ data: {
67
+ id: trackMetadata.id,
68
+ title: trackMetadata.title,
69
+ artist: trackMetadata.artist,
70
+ cover: trackMetadata.cover,
71
+ url: trackMetadata.url,
72
+ videoUrl: videoUrl, // This will be used by test.js to auto-download
73
+ searchQuery: trackMetadata.searchQuery,
74
+ format: format,
75
+ isAudio: true,
76
+ message: videoUrl ? 'Successfully found a matching video on YouTube' : 'Could not find a matching video on YouTube'
77
+ }
78
+ };
79
+
80
+ } catch (err) {
81
+ return {
82
+ status: false,
83
+ platform: 'spotify',
84
+ message: err.message
85
+ };
86
+ }
87
+ }
88
+
89
+ module.exports = spotifyDownloader;