@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.
- package/README.md +163 -180
- package/package.json +13 -4
- package/src/index.js +12 -0
- package/src/nsfw/index.js +17 -0
- package/src/nsfw/phdl.js +147 -0
- package/src/nsfw/phsearch.js +48 -0
- package/src/nsfw/rule34.js +86 -0
- package/src/nsfw/xhammerdl.js +162 -0
- package/src/nsfw/xhamstersearch.js +41 -0
- package/src/nsfw/xnxxdl.js +81 -0
- package/src/nsfw/xnxxsearch.js +59 -0
- package/src/nsfw/xvideosdl.js +79 -0
- package/src/nsfw/xvideossearch.js +54 -0
- package/src/scrapers/index.js +3 -1
- package/src/scrapers/instagram.js +98 -0
- package/src/scrapers/spotidown.js +89 -0
- package/src/tools/index.js +1 -0
- package/src/tools/screenshot.js +34 -0
package/src/nsfw/phdl.js
ADDED
|
@@ -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
|
+
}
|