@soyaxell09/zenbot-scraper 1.0.10 → 1.0.12

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.
@@ -0,0 +1,147 @@
1
+ /*
2
+ * © Created by AxelDev09 🔥
3
+ * GitHub: https://github.com/AxelDev09
4
+ * Instagram: @axeldev09
5
+ * Deja los créditos we 🗣️
6
+ */
7
+
8
+ import axios from 'axios';
9
+ import * as cheerio from 'cheerio';
10
+ import { exec } from 'child_process';
11
+ import { promisify } from 'util';
12
+ import { mkdtemp, rm } from 'fs/promises';
13
+ import { join } from 'path';
14
+ import { tmpdir } from 'os';
15
+
16
+ const execAsync = promisify(exec);
17
+ const UA = 'Mozilla/5.0 (Linux; Android 11; Redmi Note 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36';
18
+
19
+ function parseDuration(iso) {
20
+ if (!iso) return null;
21
+ const match = iso.match(/PT(\d+)H(\d+)M(\d+)S/);
22
+ if (!match) return iso;
23
+ const h = parseInt(match[1]);
24
+ const m = parseInt(match[2]);
25
+ const s = parseInt(match[3]);
26
+ if (h > 0) return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
27
+ return `${m}:${String(s).padStart(2, '0')}`;
28
+ }
29
+
30
+ function extractMediaDefinitions(s) {
31
+ const start = s.indexOf('mediaDefinitions');
32
+ if (start === -1) return null;
33
+ const arrStart = s.indexOf('[', start);
34
+ if (arrStart === -1) return null;
35
+ let depth = 0, end = -1;
36
+ for (let i = arrStart; i < s.length; i++) {
37
+ if (s[i] === '[') depth++;
38
+ else if (s[i] === ']') { depth--; if (depth === 0) { end = i; break; } }
39
+ }
40
+ try { return JSON.parse(s.slice(arrStart, end + 1).replace(/\\\//g, '/')); }
41
+ catch { return null; }
42
+ }
43
+
44
+ export async function phDownload(url) {
45
+ const { data } = await axios.get(url, {
46
+ headers: { 'User-Agent': UA, 'Accept-Language': 'en-US,en;q=0.9' },
47
+ timeout: 12000
48
+ });
49
+
50
+ const $ = cheerio.load(data);
51
+ const scripts = $('script').map((_, el) => $(el).html()).get();
52
+
53
+ let mediaDefinitions = null;
54
+ for (const s of scripts) {
55
+ if (!s || !s.includes('mediaDefinitions')) continue;
56
+ mediaDefinitions = extractMediaDefinitions(s);
57
+ if (mediaDefinitions) break;
58
+ }
59
+
60
+ if (!mediaDefinitions) throw new Error('No se encontraron calidades de video.');
61
+
62
+ const hlss = mediaDefinitions
63
+ .filter(d => d.format === 'hls' && d.videoUrl && d.quality)
64
+ .sort((a, b) => parseInt(b.quality) - parseInt(a.quality));
65
+
66
+ if (!hlss.length) throw new Error('No se encontró stream HLS.');
67
+
68
+ const jsonLd = $('script[type="application/ld+json"]').first().html();
69
+ let title = null, thumb = null, duration = null, uploadDate = null;
70
+ if (jsonLd) {
71
+ try {
72
+ const parsed = JSON.parse(jsonLd);
73
+ title = parsed.name || null;
74
+ thumb = parsed.thumbnailUrl || null;
75
+ duration = parseDuration(parsed.duration);
76
+ uploadDate = parsed.uploadDate?.slice(0, 10) || null;
77
+ } catch {}
78
+ }
79
+
80
+ if (!title) title = $('h1.title span').text().trim() || $('h1').first().text().trim() || null;
81
+
82
+ const hls = {};
83
+ for (const d of hlss) {
84
+ hls[`${d.quality}p`] = { quality: `${d.quality}p`, format: 'hls', url: d.videoUrl };
85
+ }
86
+
87
+ return { title, thumb, duration, uploadDate, hls };
88
+ }
89
+
90
+ export async function phDownloadBuffer(url, quality = '720') {
91
+ const { data } = await axios.get(url, {
92
+ headers: { 'User-Agent': UA, 'Accept-Language': 'en-US,en;q=0.9' },
93
+ timeout: 12000
94
+ });
95
+
96
+ const $ = cheerio.load(data);
97
+ const scripts = $('script').map((_, el) => $(el).html()).get();
98
+
99
+ let mediaDefinitions = null;
100
+ for (const s of scripts) {
101
+ if (!s || !s.includes('mediaDefinitions')) continue;
102
+ mediaDefinitions = extractMediaDefinitions(s);
103
+ if (mediaDefinitions) break;
104
+ }
105
+
106
+ if (!mediaDefinitions) throw new Error('No se encontraron calidades de video.');
107
+
108
+ const hlss = mediaDefinitions
109
+ .filter(d => d.format === 'hls' && d.videoUrl && d.quality)
110
+ .sort((a, b) => parseInt(b.quality) - parseInt(a.quality));
111
+
112
+ if (!hlss.length) throw new Error('No se encontró stream HLS.');
113
+
114
+ const hlsItem = hlss.find(d => d.quality === quality)
115
+ || hlss.find(d => d.quality === '480')
116
+ || hlss.find(d => d.quality === '360')
117
+ || hlss[0];
118
+
119
+ const jsonLd = $('script[type="application/ld+json"]').first().html();
120
+ let title = null, thumb = null, duration = null, uploadDate = null;
121
+ if (jsonLd) {
122
+ try {
123
+ const parsed = JSON.parse(jsonLd);
124
+ title = parsed.name || null;
125
+ thumb = parsed.thumbnailUrl || null;
126
+ duration = parseDuration(parsed.duration);
127
+ uploadDate = parsed.uploadDate?.slice(0, 10) || null;
128
+ } catch {}
129
+ }
130
+
131
+ if (!title) title = $('h1.title span').text().trim() || $('h1').first().text().trim() || null;
132
+
133
+ const tmpDir = await mkdtemp(join(tmpdir(), 'phdl-'));
134
+ const outPath = join(tmpDir, 'video.mp4');
135
+
136
+ try {
137
+ await execAsync(
138
+ `ffmpeg -v quiet -y -user_agent "${UA}" -headers "Referer: https://www.pornhub.com/\r\n" -i "${hlsItem.videoUrl}" -t 300 -c copy -bsf:a aac_adtstoasc "${outPath}"`,
139
+ { timeout: 120000 }
140
+ );
141
+ const { readFile } = await import('fs/promises');
142
+ const buffer = await readFile(outPath);
143
+ return { title, thumb, duration, uploadDate, buffer, quality: `${hlsItem.quality}p` };
144
+ } finally {
145
+ await rm(tmpDir, { recursive: true, force: true });
146
+ }
147
+ }
@@ -0,0 +1,48 @@
1
+ /*
2
+ * © Created by AxelDev09 🔥
3
+ * GitHub: https://github.com/AxelDev09
4
+ * Instagram: @axeldev09
5
+ * Deja los créditos we 🗣️
6
+ */
7
+
8
+ import axios from 'axios';
9
+ import * as cheerio from 'cheerio';
10
+
11
+ const UA = 'Mozilla/5.0 (Linux; Android 11; Redmi Note 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36';
12
+
13
+ export async function phSearch(query, limit = 10) {
14
+ const { data } = await axios.get(`https://www.pornhub.com/video/search?search=${encodeURIComponent(query)}`, {
15
+ headers: { 'User-Agent': UA, 'Accept-Language': 'en-US,en;q=0.9' },
16
+ timeout: 12000
17
+ });
18
+
19
+ const $ = cheerio.load(data);
20
+ const results = [];
21
+
22
+ $('li[data-video-vkey]').each((_, el) => {
23
+ if (results.length >= limit) return false;
24
+
25
+ const anchor = $(el).find('a.imageLink').first();
26
+ const img = $(el).find('img.videoThumb').first();
27
+
28
+ const href = anchor.attr('href') || '';
29
+ const title = $(el).find('.title a').first().text().trim();
30
+ const thumb = img.attr('src') || '';
31
+ const preview = anchor.attr('data-webm') || '';
32
+ const duration = $(el).find('.duration').first().text().trim();
33
+ const vkey = $(el).attr('data-video-vkey') || '';
34
+
35
+ if (!title || !href) return;
36
+
37
+ results.push({
38
+ title,
39
+ url: href.startsWith('http') ? href : `https://www.pornhub.com${href}`,
40
+ thumb,
41
+ preview,
42
+ duration,
43
+ vkey
44
+ });
45
+ });
46
+
47
+ return results;
48
+ }
@@ -0,0 +1,86 @@
1
+ /*
2
+ * © Created by AxelDev09 🔥
3
+ * GitHub: https://github.com/AxelDev09
4
+ * Instagram: @axeldev09
5
+ * Deja los créditos we 🗣️
6
+ */
7
+
8
+ import axios from 'axios';
9
+ import * as cheerio from 'cheerio';
10
+
11
+ const UA = 'Mozilla/5.0 (Linux; Android 11; Redmi Note 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36';
12
+
13
+ function mapYande(p) {
14
+ return {
15
+ id: String(p.id),
16
+ url: p.sample_url || p.file_url || '',
17
+ full: p.file_url || '',
18
+ preview: p.preview_url || '',
19
+ tags: p.tags || '',
20
+ score: p.score || 0,
21
+ width: p.width || 0,
22
+ height: p.height || 0
23
+ };
24
+ }
25
+
26
+ async function yandereSearch(tags, limit = 10) {
27
+ const { data } = await axios.get('https://yande.re/post.json', {
28
+ params: { tags, limit },
29
+ headers: { 'User-Agent': UA },
30
+ timeout: 12000
31
+ });
32
+ if (!Array.isArray(data) || !data.length) return [];
33
+ return data.map(mapYande);
34
+ }
35
+
36
+ function parsePaheal(xml) {
37
+ const $ = cheerio.load(xml, { xmlMode: true });
38
+ const items = [];
39
+ $('tag').each((_, el) => {
40
+ const file_url = $(el).attr('file_url') || '';
41
+ if (!file_url) return;
42
+ items.push({
43
+ id: $(el).attr('id'),
44
+ url: file_url,
45
+ full: file_url,
46
+ preview: $(el).attr('preview_url') || '',
47
+ tags: $(el).attr('tags') || '',
48
+ score: parseInt($(el).attr('score')) || 0,
49
+ width: parseInt($(el).attr('width')) || 0,
50
+ height: parseInt($(el).attr('height')) || 0
51
+ });
52
+ });
53
+ return items;
54
+ }
55
+
56
+ async function pahealSearch(tags, limit = 10) {
57
+ const { data } = await axios.get('https://rule34.paheal.net/api/danbooru/find_posts', {
58
+ params: { tags, limit },
59
+ headers: { 'User-Agent': UA },
60
+ timeout: 12000
61
+ });
62
+ return parsePaheal(data);
63
+ }
64
+
65
+ export async function rule34Search(tags, limit = 10) {
66
+ let items = await yandereSearch(tags, limit);
67
+ if (!items.length) items = await pahealSearch(tags, limit);
68
+ if (!items.length) throw new Error('Sin resultados para esos tags.');
69
+ return items;
70
+ }
71
+
72
+ export async function rule34Random(tags = null) {
73
+ for (let intento = 0; intento < 3; intento++) {
74
+ const pid = Math.floor(Math.random() * 5);
75
+ const params = { limit: 20, pid };
76
+ if (tags) params.tags = tags;
77
+ const { data } = await axios.get('https://rule34.paheal.net/api/danbooru/find_posts', {
78
+ params,
79
+ headers: { 'User-Agent': UA },
80
+ timeout: 12000
81
+ });
82
+ const items = parsePaheal(data);
83
+ if (items.length) return items[Math.floor(Math.random() * items.length)];
84
+ }
85
+ throw new Error('No se encontraron resultados.');
86
+ }
@@ -0,0 +1,162 @@
1
+ /*
2
+ * © Created by AxelDev09 🔥
3
+ * GitHub: https://github.com/AxelDev09
4
+ * Instagram: @axeldev09
5
+ * Deja los créditos we 🗣️
6
+ */
7
+
8
+ import axios from 'axios';
9
+ import * as cheerio from 'cheerio';
10
+ import { execFile } from 'child_process';
11
+ import { promisify } from 'util';
12
+ import { mkdtemp, readFile, rm } from 'fs/promises';
13
+ import { join } from 'path';
14
+ import { tmpdir } from 'os';
15
+
16
+ const execFileAsync = promisify(execFile);
17
+
18
+ const UA = 'Mozilla/5.0 (Linux; Android 11; Redmi Note 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36';
19
+
20
+ function secsToTime(s) {
21
+ if (!s) return null;
22
+ const h = Math.floor(s / 3600);
23
+ const m = Math.floor((s % 3600) / 60);
24
+ const sec = s % 60;
25
+ if (h > 0) return `${h}:${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}`;
26
+ return `${m}:${String(sec).padStart(2, '0')}`;
27
+ }
28
+
29
+ const XH_UA = 'Mozilla/5.0 (Linux; Android 11; Redmi Note 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36';
30
+
31
+ export async function xhamsterDownloadBuffer(url, quality = '720p') {
32
+ const { data } = await axios.get(url, {
33
+ headers: { 'User-Agent': XH_UA },
34
+ timeout: 12000
35
+ });
36
+
37
+ const $ = cheerio.load(data);
38
+ const scripts = $('script').map((_, el) => $(el).html()).get();
39
+
40
+ let title = null, thumb = null, duration = null, views = null;
41
+ for (const s of scripts) {
42
+ if (!s || !s.includes('"title"') || !s.includes('"duration"')) continue;
43
+ try {
44
+ const match = s.match(/"title":"([^"]+)","thumbUrl":"([^"]+)","duration":(\d+),"views":(\d+)/);
45
+ if (match) {
46
+ title = match[1].replace(/\\u([0-9a-fA-F]{4})/g, (_, h) => String.fromCharCode(parseInt(h, 16)));
47
+ thumb = match[2].replace(/\\\//g, '/');
48
+ duration = secsToTime(parseInt(match[3]));
49
+ views = parseInt(match[4]);
50
+ break;
51
+ }
52
+ } catch {}
53
+ }
54
+
55
+ if (!title) title = $('meta[property="og:title"]').attr('content') || null;
56
+ if (!thumb) thumb = $('meta[property="og:image"]').attr('content') || null;
57
+
58
+ const mp4Matches = [...new Set(data.match(/https?:\/\/[^\s"'\\]+\.mp4[^\s"'\\]*/g) || [])];
59
+ const masterUrl = mp4Matches.find(u => u.includes('480p') || u.includes('hls4'));
60
+ if (!masterUrl) throw new Error('No se encontró stream de video.');
61
+
62
+ const baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1);
63
+ const { data: m3u8 } = await axios.get(masterUrl, {
64
+ headers: { 'User-Agent': XH_UA, 'Referer': 'https://xhamster.com/' },
65
+ timeout: 10000
66
+ });
67
+
68
+ const lines = m3u8.split('\n');
69
+ let streamUrl = null;
70
+ for (let i = 0; i < lines.length; i++) {
71
+ if (lines[i].startsWith('#EXT-X-STREAM-INF')) {
72
+ const qualMatch = lines[i + 1]?.trim().match(/(\d+p)/);
73
+ const q = qualMatch ? qualMatch[1] : null;
74
+ if (q === quality || (!streamUrl && q)) {
75
+ const next = lines[i + 1]?.trim();
76
+ streamUrl = next.startsWith('http') ? next : baseUrl + next;
77
+ if (q === quality) break;
78
+ }
79
+ }
80
+ }
81
+
82
+ if (!streamUrl) throw new Error('No se encontró la calidad solicitada.');
83
+
84
+ const tmpDir = await mkdtemp(join(tmpdir(), 'xhdl-'));
85
+ const outPath = join(tmpDir, 'video.mp4');
86
+
87
+ try {
88
+ await execFileAsync('ffmpeg', [
89
+ '-v', 'quiet',
90
+ '-y',
91
+ '-user_agent', XH_UA,
92
+ '-headers', 'Referer: https://xhamster.com/\r\n',
93
+ '-i', streamUrl,
94
+ '-c', 'copy',
95
+ outPath
96
+ ], { timeout: 120000 });
97
+ const buffer = await readFile(outPath);
98
+ return { title, thumb, duration, views, buffer, quality };
99
+ } finally {
100
+ await rm(tmpDir, { recursive: true, force: true });
101
+ }
102
+ }
103
+
104
+ export async function xhamsterDownload(url) {
105
+ const { data } = await axios.get(url, {
106
+ headers: { 'User-Agent': UA },
107
+ timeout: 12000
108
+ });
109
+
110
+ const $ = cheerio.load(data);
111
+ const scripts = $('script').map((_, el) => $(el).html()).get();
112
+
113
+ let title = null, thumb = null, duration = null, views = null;
114
+
115
+ for (const s of scripts) {
116
+ if (!s || !s.includes('"title"') || !s.includes('"duration"')) continue;
117
+ try {
118
+ const match = s.match(/"title":"([^"]+)","thumbUrl":"([^"]+)","duration":(\d+),"views":(\d+)/);
119
+ if (match) {
120
+ title = match[1].replace(/\\u([0-9a-fA-F]{4})/g, (_, h) => String.fromCharCode(parseInt(h, 16)));
121
+ thumb = match[2].replace(/\\\//g, '/');
122
+ duration = secsToTime(parseInt(match[3]));
123
+ views = parseInt(match[4]);
124
+ break;
125
+ }
126
+ } catch {}
127
+ }
128
+
129
+ if (!title) title = $('meta[property="og:title"]').attr('content') || null;
130
+ if (!thumb) thumb = $('meta[property="og:image"]').attr('content') || null;
131
+
132
+ const mp4Matches = [...new Set(data.match(/https?:\/\/[^\s"'\\]+\.mp4[^\s"'\\]*/g) || [])];
133
+ const masterUrl = mp4Matches.find(u => u.includes('480p') || u.includes('hls4'));
134
+
135
+ if (!masterUrl) throw new Error('No se encontró stream de video.');
136
+
137
+ const baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1);
138
+
139
+ const { data: m3u8 } = await axios.get(masterUrl, {
140
+ headers: { 'User-Agent': UA, 'Referer': 'https://xhamster.com/' },
141
+ timeout: 10000
142
+ });
143
+
144
+ const download = {};
145
+ const lines = m3u8.split('\n');
146
+ for (let i = 0; i < lines.length; i++) {
147
+ if (lines[i].startsWith('#EXT-X-STREAM-INF')) {
148
+ const resMatch = lines[i].match(/RESOLUTION=(\d+x\d+)/);
149
+ const nextLine = lines[i + 1]?.trim();
150
+ if (nextLine && resMatch) {
151
+ const qualMatch = nextLine.match(/(\d+p)/);
152
+ const quality = qualMatch ? qualMatch[1] : resMatch[1];
153
+ const streamUrl = nextLine.startsWith('http') ? nextLine : baseUrl + nextLine;
154
+ download[quality] = { quality, url: streamUrl, format: 'hls' };
155
+ }
156
+ }
157
+ }
158
+
159
+ if (!Object.keys(download).length) throw new Error('No se encontraron calidades.');
160
+
161
+ return { title, thumb, duration, views, download };
162
+ }
@@ -0,0 +1,41 @@
1
+ /*
2
+ * © Created by AxelDev09 🔥
3
+ * GitHub: https://github.com/AxelDev09
4
+ * Instagram: @axeldev09
5
+ * Deja los créditos we 🗣️
6
+ */
7
+
8
+ import axios from 'axios';
9
+ import * as cheerio from 'cheerio';
10
+
11
+ const UA = 'Mozilla/5.0 (Linux; Android 11; Redmi Note 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36';
12
+
13
+ export async function xhamsterSearch(query, limit = 10) {
14
+ const { data } = await axios.get(`https://xhamster.com/search/${encodeURIComponent(query)}`, {
15
+ headers: { 'User-Agent': UA },
16
+ timeout: 12000
17
+ });
18
+
19
+ const $ = cheerio.load(data);
20
+ const results = [];
21
+
22
+ $('[class*="video-thumb"]').each((_, el) => {
23
+ if (results.length >= limit) return false;
24
+
25
+ const anchor = $(el).find('a.thumb-image-container').first();
26
+ const img = $(el).find('img').first();
27
+
28
+ const title = anchor.attr('aria-label') || $(el).find('[class*="name"]').first().text().trim();
29
+ const href = anchor.attr('href') || '';
30
+ const thumb = img.attr('src') || img.attr('srcset')?.split(' ')[0] || '';
31
+ const preview = anchor.attr('data-previewvideo') || '';
32
+ const duration = $(el).find('time').first().attr('datetime') || '';
33
+ const views = $(el).find('[class*="views"]').first().text().trim();
34
+
35
+ if (!title || !href) return;
36
+
37
+ results.push({ title, url: href, thumb, preview, duration, views });
38
+ });
39
+
40
+ return results;
41
+ }
@@ -0,0 +1,81 @@
1
+ /*
2
+ * © Created by AxelDev09 🔥
3
+ * GitHub: https://github.com/AxelDev09
4
+ * Instagram: @axeldev09
5
+ * Deja los créditos we 🗣️
6
+ */
7
+
8
+ import axios from 'axios';
9
+ import * as cheerio from 'cheerio';
10
+
11
+ const UA = 'Mozilla/5.0 (Linux; Android 11; Redmi Note 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36';
12
+
13
+ function parseDuration(iso) {
14
+ if (!iso) return null;
15
+ const match = iso.match(/PT(\d+)H(\d+)M(\d+)S/);
16
+ if (!match) return iso;
17
+ const h = parseInt(match[1]);
18
+ const m = parseInt(match[2]);
19
+ const s = parseInt(match[3]);
20
+ if (h > 0) return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
21
+ return `${m}:${String(s).padStart(2, '0')}`;
22
+ }
23
+
24
+ export async function xnxxDownload(url) {
25
+ const { data } = await axios.get(url, {
26
+ headers: { 'User-Agent': UA },
27
+ timeout: 12000
28
+ });
29
+
30
+ const $ = cheerio.load(data);
31
+ const scripts = $('script').map((_, el) => $(el).html()).get();
32
+
33
+ let playerScript = null;
34
+ for (const s of scripts) {
35
+ if (s && s.includes('html5player') && s.includes('setVideoUrlLow')) {
36
+ playerScript = s;
37
+ break;
38
+ }
39
+ }
40
+
41
+ if (!playerScript) throw new Error('No se encontró el player del video.');
42
+
43
+ const extract = (key) => {
44
+ const match = playerScript.match(new RegExp(`${key}\\('([^']+)'\\)`));
45
+ return match ? match[1] : null;
46
+ };
47
+
48
+ const title = extract('setVideoTitle');
49
+ const low = extract('setVideoUrlLow');
50
+ const high = extract('setVideoUrlHigh');
51
+ const hls = extract('setVideoHLS');
52
+ const thumb = extract('setThumbUrl169') || extract('setThumbUrl');
53
+ const uploader = extract('setUploaderName');
54
+
55
+ const jsonLd = $('script[type="application/ld+json"]').first().html();
56
+ let duration = null, views = null, uploadDate = null;
57
+ if (jsonLd) {
58
+ try {
59
+ const parsed = JSON.parse(jsonLd);
60
+ duration = parseDuration(parsed.duration);
61
+ views = parsed.interactionStatistic?.userInteractionCount || null;
62
+ uploadDate = parsed.uploadDate?.slice(0, 10) || null;
63
+ } catch {}
64
+ }
65
+
66
+ if (!low && !high) throw new Error('No se encontraron URLs de descarga.');
67
+
68
+ return {
69
+ title,
70
+ thumb,
71
+ uploader,
72
+ duration,
73
+ views,
74
+ uploadDate,
75
+ download: {
76
+ low: low ? { quality: '240p', url: low } : null,
77
+ high: high ? { quality: '360p', url: high } : null,
78
+ hls: hls ? { quality: 'HLS', url: hls } : null
79
+ }
80
+ };
81
+ }
@@ -0,0 +1,59 @@
1
+ /*
2
+ * © Created by AxelDev09 🔥
3
+ * GitHub: https://github.com/AxelDev09
4
+ * Instagram: @axeldev09
5
+ * Deja los créditos we 🗣️
6
+ */
7
+
8
+ import axios from 'axios';
9
+ import * as cheerio from 'cheerio';
10
+
11
+ const UA = 'Mozilla/5.0 (Linux; Android 11; Redmi Note 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36';
12
+
13
+ export async function xnxxSearch(query, limit = 10) {
14
+ const { data } = await axios.get(`https://www.xnxx.com/search/${encodeURIComponent(query)}`, {
15
+ headers: { 'User-Agent': UA },
16
+ timeout: 12000
17
+ });
18
+
19
+ const $ = cheerio.load(data);
20
+ const results = [];
21
+
22
+ $('.mozaique .thumb-block').each((_, el) => {
23
+ if (results.length >= limit) return false;
24
+
25
+ const img = $(el).find('img');
26
+ const anchor = $(el).find('.thumb-inside a').first();
27
+ const titleEl = $(el).find('a[title]').first();
28
+ const metadata = $(el).find('.metadata').text().replace(/\s+/g, ' ').trim();
29
+ const rightEl = $(el).find('.metadata .right').text().replace(/\s+/g, ' ').trim();
30
+
31
+ const href = anchor.attr('href') || titleEl.attr('href') || '';
32
+ const title = titleEl.attr('title') || '';
33
+ const thumb = img.attr('data-src') || '';
34
+ const preview = img.attr('data-pvv') || '';
35
+ const uploader = $(el).find('.uploader .name').text().trim();
36
+ const quality = $(el).find('.video-hd').text().replace(/\s+/g, ' ').trim();
37
+
38
+ const durMatch = metadata.match(/(\d+min|\d+:\d+)/);
39
+ const duration = durMatch ? durMatch[1] : '';
40
+
41
+ const viewsMatch = rightEl.match(/([\d.]+[KMB]?)/);
42
+ const views = viewsMatch ? viewsMatch[1] : '';
43
+
44
+ if (!title || !href) return;
45
+
46
+ results.push({
47
+ title,
48
+ url: `https://www.xnxx.com${href}`,
49
+ thumb,
50
+ preview,
51
+ duration,
52
+ views,
53
+ quality: quality.replace(/-/g, '').trim(),
54
+ uploader
55
+ });
56
+ });
57
+
58
+ return results;
59
+ }