@soyaxell09/zenbot-scraper 1.0.0
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 +344 -0
- package/package.json +54 -0
- package/src/index.js +27 -0
- package/src/scrapers/apk.js +112 -0
- package/src/scrapers/facebook.js +201 -0
- package/src/scrapers/github.js +156 -0
- package/src/scrapers/index.js +15 -0
- package/src/scrapers/mediafire.js +77 -0
- package/src/scrapers/tiktok.js +58 -0
- package/src/scrapers/twitter.js +106 -0
- package/src/scrapers/youtube.js +190 -0
- package/src/scrapers/youtubev2.js +131 -0
- package/src/search/giphy.js +36 -0
- package/src/search/google.js +55 -0
- package/src/search/index.js +12 -0
- package/src/search/pinterest.js +162 -0
- package/src/search/spotify.js +56 -0
- package/src/tools/index.js +15 -0
- package/src/tools/lyrics.js +88 -0
- package/src/tools/news.js +54 -0
- package/src/tools/qr.js +40 -0
- package/src/tools/tiktokstalk.js +68 -0
- package/src/tools/translator.js +82 -0
- package/src/tools/upload.js +70 -0
- package/src/tools/urlshortener.js +54 -0
- package/src/tools/weather.js +72 -0
- package/src/tools/youtube.js +249 -0
|
@@ -0,0 +1,190 @@
|
|
|
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 ytdl from '@distube/ytdl-core'
|
|
10
|
+
|
|
11
|
+
const INNERTUBE_URL = 'https://www.youtube.com/youtubei/v1'
|
|
12
|
+
let _config = null
|
|
13
|
+
|
|
14
|
+
async function getConfig() {
|
|
15
|
+
if (_config) return _config
|
|
16
|
+
const res = await axios.get('https://www.youtube.com/', {
|
|
17
|
+
headers: {
|
|
18
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36',
|
|
19
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
20
|
+
'Accept': 'text/html,application/xhtml+xml',
|
|
21
|
+
},
|
|
22
|
+
timeout: 15000,
|
|
23
|
+
})
|
|
24
|
+
const html = res.data
|
|
25
|
+
const key = html.match(/"INNERTUBE_API_KEY"\s*:\s*"([^"]+)"/)?.[1] || 'AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM394'
|
|
26
|
+
const visitorData = html.match(/"visitorData"\s*:\s*"([^"]+)"/)?.[1] || ''
|
|
27
|
+
const clientVersion = html.match(/"clientVersion"\s*:\s*"([^"]+)"/)?.[1] || '2.20240101.00.00'
|
|
28
|
+
_config = { key, visitorData, clientVersion }
|
|
29
|
+
return _config
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseVideoId(url) {
|
|
33
|
+
const patterns = [
|
|
34
|
+
/(?:v=|youtu\.be\/|shorts\/)([a-zA-Z0-9_-]{11})/,
|
|
35
|
+
/^([a-zA-Z0-9_-]{11})$/,
|
|
36
|
+
]
|
|
37
|
+
for (const p of patterns) {
|
|
38
|
+
const m = url.match(p)
|
|
39
|
+
if (m) return m[1]
|
|
40
|
+
}
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function ytInfo(url) {
|
|
45
|
+
const id = parseVideoId(url)
|
|
46
|
+
if (!id) throw new Error('URL/ID de YouTube inválido')
|
|
47
|
+
|
|
48
|
+
const info = await ytdl.getInfo(`https://www.youtube.com/watch?v=${id}`)
|
|
49
|
+
const vd = info.videoDetails
|
|
50
|
+
const formats = info.formats
|
|
51
|
+
.filter(f => f.url)
|
|
52
|
+
.map(f => ({
|
|
53
|
+
itag: f.itag,
|
|
54
|
+
url: f.url,
|
|
55
|
+
mimeType: f.mimeType || '',
|
|
56
|
+
quality: f.qualityLabel || f.quality || '',
|
|
57
|
+
bitrate: f.bitrate || f.averageBitrate || 0,
|
|
58
|
+
width: f.width || 0,
|
|
59
|
+
height: f.height || 0,
|
|
60
|
+
fps: f.fps || 0,
|
|
61
|
+
hasVideo: !!f.hasVideo,
|
|
62
|
+
hasAudio: !!f.hasAudio,
|
|
63
|
+
container: f.container || '',
|
|
64
|
+
codecs: f.codecs || '',
|
|
65
|
+
}))
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
id,
|
|
69
|
+
title: vd.title || '',
|
|
70
|
+
author: vd.author?.name || vd.ownerChannelName || '',
|
|
71
|
+
channelId: vd.channelId || '',
|
|
72
|
+
duration: Number(vd.lengthSeconds || 0),
|
|
73
|
+
views: Number(vd.viewCount || 0),
|
|
74
|
+
thumbnail: vd.thumbnails?.at(-1)?.url || '',
|
|
75
|
+
description: vd.description?.slice(0, 300) || '',
|
|
76
|
+
isLive: vd.isLiveContent || false,
|
|
77
|
+
formats,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function ytDownload(url, type = 'video', quality = '360') {
|
|
82
|
+
const id = parseVideoId(url)
|
|
83
|
+
if (!id) throw new Error('URL de YouTube inválida')
|
|
84
|
+
|
|
85
|
+
const info = await ytInfo(id)
|
|
86
|
+
const formats = info.formats
|
|
87
|
+
|
|
88
|
+
if (type === 'mp3' || type === 'audio') {
|
|
89
|
+
const audioFormats = formats
|
|
90
|
+
.filter(f => f.hasAudio && !f.hasVideo)
|
|
91
|
+
.sort((a, b) => b.bitrate - a.bitrate)
|
|
92
|
+
if (!audioFormats.length) throw new Error('Sin formatos de audio disponibles')
|
|
93
|
+
const best = audioFormats[0]
|
|
94
|
+
return {
|
|
95
|
+
type: 'audio',
|
|
96
|
+
url: best.url,
|
|
97
|
+
mimeType: best.mimeType,
|
|
98
|
+
bitrate: best.bitrate,
|
|
99
|
+
duration: info.duration,
|
|
100
|
+
title: info.title,
|
|
101
|
+
author: info.author,
|
|
102
|
+
thumbnail: info.thumbnail,
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const q = parseInt(quality)
|
|
107
|
+
const videoFormats = formats
|
|
108
|
+
.filter(f => f.hasVideo && f.hasAudio)
|
|
109
|
+
.sort((a, b) => b.height - a.height)
|
|
110
|
+
|
|
111
|
+
if (!videoFormats.length) {
|
|
112
|
+
const videoOnly = formats
|
|
113
|
+
.filter(f => f.hasVideo && !f.hasAudio)
|
|
114
|
+
.sort((a, b) => b.height - a.height)
|
|
115
|
+
if (!videoOnly.length) throw new Error('Sin formatos de video disponibles')
|
|
116
|
+
const best = videoOnly.find(f => f.height <= q) || videoOnly[0]
|
|
117
|
+
return {
|
|
118
|
+
type: 'video', url: best.url, mimeType: best.mimeType,
|
|
119
|
+
quality: best.quality, width: best.width, height: best.height,
|
|
120
|
+
fps: best.fps, title: info.title, author: info.author,
|
|
121
|
+
thumbnail: info.thumbnail, duration: info.duration,
|
|
122
|
+
note: 'solo_video_sin_audio',
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const best = videoFormats.find(f => f.height <= q) || videoFormats[0]
|
|
127
|
+
return {
|
|
128
|
+
type: 'video', url: best.url, mimeType: best.mimeType,
|
|
129
|
+
quality: best.quality, width: best.width, height: best.height,
|
|
130
|
+
fps: best.fps, title: info.title, author: info.author,
|
|
131
|
+
thumbnail: info.thumbnail, duration: info.duration,
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function ytSearch(query, limit = 5) {
|
|
136
|
+
const cfg = await getConfig()
|
|
137
|
+
const res = await axios.post(
|
|
138
|
+
`${INNERTUBE_URL}/search?key=${cfg.key}&prettyPrint=false`,
|
|
139
|
+
{
|
|
140
|
+
query,
|
|
141
|
+
context: {
|
|
142
|
+
client: {
|
|
143
|
+
clientName: 'WEB',
|
|
144
|
+
clientVersion: cfg.clientVersion,
|
|
145
|
+
hl: 'en', gl: 'US',
|
|
146
|
+
visitorData: cfg.visitorData,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
headers: {
|
|
152
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36',
|
|
153
|
+
'X-YouTube-Client-Name': '1',
|
|
154
|
+
'X-YouTube-Client-Version': cfg.clientVersion,
|
|
155
|
+
'Content-Type': 'application/json',
|
|
156
|
+
'X-Goog-Visitor-Id': cfg.visitorData,
|
|
157
|
+
},
|
|
158
|
+
timeout: 15000,
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
const contents =
|
|
163
|
+
res.data?.contents
|
|
164
|
+
?.twoColumnSearchResultsRenderer
|
|
165
|
+
?.primaryContents
|
|
166
|
+
?.sectionListRenderer
|
|
167
|
+
?.contents?.[0]
|
|
168
|
+
?.itemSectionRenderer
|
|
169
|
+
?.contents || []
|
|
170
|
+
|
|
171
|
+
const results = []
|
|
172
|
+
for (const item of contents) {
|
|
173
|
+
if (results.length >= limit) break
|
|
174
|
+
const v = item?.videoRenderer
|
|
175
|
+
if (!v?.videoId) continue
|
|
176
|
+
results.push({
|
|
177
|
+
id: v.videoId,
|
|
178
|
+
title: v.title?.runs?.[0]?.text || '',
|
|
179
|
+
url: `https://www.youtube.com/watch?v=${v.videoId}`,
|
|
180
|
+
thumbnail: v.thumbnail?.thumbnails?.at(-1)?.url || '',
|
|
181
|
+
duration: v.lengthText?.simpleText || '',
|
|
182
|
+
views: v.viewCountText?.simpleText || '',
|
|
183
|
+
channel: v.ownerText?.runs?.[0]?.text || '',
|
|
184
|
+
published: v.publishedTimeText?.simpleText || '',
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!results.length) throw new Error('Sin resultados')
|
|
189
|
+
return results
|
|
190
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Créditos a FG-error
|
|
2
|
+
import axios from 'axios'
|
|
3
|
+
|
|
4
|
+
const delay = ms => new Promise(r => setTimeout(r, ms))
|
|
5
|
+
|
|
6
|
+
function parseFileSize(size) {
|
|
7
|
+
if (!size) return 0
|
|
8
|
+
const units = { B: 1, KB: 1024, MB: 1024 ** 2, GB: 1024 ** 3, TB: 1024 ** 4 }
|
|
9
|
+
const match = size.toString().trim().match(/([\d.]+)\s*(B|KB|MB|GB|TB)/i)
|
|
10
|
+
if (!match) return 0
|
|
11
|
+
return Math.round(parseFloat(match[1]) * (units[match[2].toUpperCase()] || 1))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function formatFileSize(bytes) {
|
|
15
|
+
if (!bytes || isNaN(bytes)) return '0 B'
|
|
16
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
17
|
+
let i = 0
|
|
18
|
+
while (bytes >= 1024 && i < units.length - 1) { bytes /= 1024; i++ }
|
|
19
|
+
return `${bytes.toFixed(1).replace(/\.0$/, '')} ${units[i]}`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function getFileSizeV2(url) {
|
|
23
|
+
try {
|
|
24
|
+
const res = await axios.head(url, { timeout: 10000 })
|
|
25
|
+
const bytes = parseInt(res.headers['content-length'] || 0)
|
|
26
|
+
return formatFileSize(bytes)
|
|
27
|
+
} catch { return '0 B' }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeYT(url) {
|
|
31
|
+
try {
|
|
32
|
+
const u = new URL(url)
|
|
33
|
+
if (u.hostname.includes('youtu.be')) return url
|
|
34
|
+
if (u.hostname.includes('youtube.com')) {
|
|
35
|
+
if (u.pathname.includes('/watch')) return `https://youtu.be/${u.searchParams.get('v')}`
|
|
36
|
+
if (u.pathname.includes('/shorts/')) return `https://youtu.be/${u.pathname.split('/shorts/')[1]}`
|
|
37
|
+
if (u.pathname.includes('/embed/')) return `https://youtu.be/${u.pathname.split('/embed/')[1]}`
|
|
38
|
+
}
|
|
39
|
+
return url
|
|
40
|
+
} catch { return url }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function waitForDownload(mediaUrl) {
|
|
44
|
+
for (let i = 0; i < 15; i++) {
|
|
45
|
+
try {
|
|
46
|
+
const { data } = await axios.get(mediaUrl, { timeout: 15000 })
|
|
47
|
+
if (data?.percent === 'Completed' && data?.fileUrl && data.fileUrl !== 'In Processing...')
|
|
48
|
+
return data.fileUrl
|
|
49
|
+
} catch {}
|
|
50
|
+
await delay(4000)
|
|
51
|
+
}
|
|
52
|
+
throw new Error('No se pudo generar el enlace de descarga')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function fetchYtdownto(url) {
|
|
56
|
+
const { data } = await axios.post(
|
|
57
|
+
'https://app.ytdown.to/proxy.php',
|
|
58
|
+
new URLSearchParams({ url }).toString(),
|
|
59
|
+
{
|
|
60
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
61
|
+
timeout: 20000,
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
const api = data?.api
|
|
65
|
+
if (!api) throw new Error('No se pudo obtener información del video')
|
|
66
|
+
if (api.status === 'ERROR') throw new Error(api.message)
|
|
67
|
+
|
|
68
|
+
const qualities = (api.mediaItems || []).map((v, i) => {
|
|
69
|
+
const match = v?.mediaUrl?.match(/(\d+)p|(\d+)k/)
|
|
70
|
+
const res = match ? match[0] : v.mediaQuality
|
|
71
|
+
return {
|
|
72
|
+
id: i + 1,
|
|
73
|
+
type: v.type,
|
|
74
|
+
quality: res,
|
|
75
|
+
label: `${v.mediaExtension?.toUpperCase()} - ${v.mediaQuality}`,
|
|
76
|
+
size: v.mediaFileSize,
|
|
77
|
+
sizeB: parseFileSize(v.mediaFileSize),
|
|
78
|
+
mediaUrl: v.mediaUrl,
|
|
79
|
+
duration: v.mediaDuration,
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
return { api, qualities }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function ytDownloadV2(url, type = 'video', quality = '360p') {
|
|
87
|
+
url = normalizeYT(url)
|
|
88
|
+
|
|
89
|
+
const { api, qualities } = await fetchYtdownto(url)
|
|
90
|
+
|
|
91
|
+
const isAudio = type === 'mp3' || type === 'audio'
|
|
92
|
+
const targetQ = quality.toLowerCase()
|
|
93
|
+
|
|
94
|
+
const filtered = isAudio
|
|
95
|
+
? qualities.filter(v => v.type === 'audio' || v.quality?.includes('k'))
|
|
96
|
+
: qualities.filter(v => v.type === 'video' || v.quality?.includes('p'))
|
|
97
|
+
|
|
98
|
+
const selected = filtered.find(v => v.quality?.toLowerCase() === targetQ) || filtered[0]
|
|
99
|
+
|
|
100
|
+
if (!selected) {
|
|
101
|
+
const disponibles = qualities.map(v => v.quality).filter(Boolean).join(', ')
|
|
102
|
+
throw new Error(`Calidad ${quality} no disponible. Disponibles: ${disponibles}`)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const dlUrl = await waitForDownload(selected.mediaUrl)
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
title: api.title,
|
|
109
|
+
uploader: api.userInfo?.name || '',
|
|
110
|
+
views: api.mediaStats?.viewsCount || '',
|
|
111
|
+
thumb: api.imagePreviewUrl || '',
|
|
112
|
+
type: isAudio ? 'audio' : 'video',
|
|
113
|
+
quality: selected.quality,
|
|
114
|
+
size: selected.size,
|
|
115
|
+
sizeB: selected.sizeB,
|
|
116
|
+
duration: selected.duration,
|
|
117
|
+
url: dlUrl,
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function ytInfoV2(url) {
|
|
122
|
+
url = normalizeYT(url)
|
|
123
|
+
const { api, qualities } = await fetchYtdownto(url)
|
|
124
|
+
return {
|
|
125
|
+
title: api.title,
|
|
126
|
+
uploader: api.userInfo?.name || '',
|
|
127
|
+
views: api.mediaStats?.viewsCount || '',
|
|
128
|
+
thumb: api.imagePreviewUrl || '',
|
|
129
|
+
qualities,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
|
|
10
|
+
const HEADERS = {
|
|
11
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const TENOR_KEY = 'AIzaSyAyimkuYQYF_FXVALexPuGQctUWRURdCYQ'
|
|
15
|
+
|
|
16
|
+
export async function giphy(query, limit = 5, type = 'search') {
|
|
17
|
+
const endpoint = type === 'trending'
|
|
18
|
+
? `https://tenor.googleapis.com/v2/featured?key=${TENOR_KEY}&limit=${limit}&media_filter=gif`
|
|
19
|
+
: `https://tenor.googleapis.com/v2/search?q=${encodeURIComponent(query)}&key=${TENOR_KEY}&limit=${limit}&media_filter=gif`
|
|
20
|
+
|
|
21
|
+
const res = await axios.get(endpoint, { headers: HEADERS, timeout: 15000 })
|
|
22
|
+
const data = res.data?.results || []
|
|
23
|
+
|
|
24
|
+
if (!data.length) throw new Error('Sin resultados en Tenor')
|
|
25
|
+
|
|
26
|
+
return data.map(g => ({
|
|
27
|
+
id: g.id,
|
|
28
|
+
title: g.title || g.content_description || '',
|
|
29
|
+
url: g.itemurl || '',
|
|
30
|
+
gif: g.media_formats?.gif?.url || g.media_formats?.mediumgif?.url || '',
|
|
31
|
+
preview: g.media_formats?.nanogif?.url || g.media_formats?.tinygif?.url || '',
|
|
32
|
+
mp4: g.media_formats?.mp4?.url || g.media_formats?.loopedmp4?.url || '',
|
|
33
|
+
width: g.media_formats?.gif?.dims?.[0] || 0,
|
|
34
|
+
height: g.media_formats?.gif?.dims?.[1] || 0,
|
|
35
|
+
}))
|
|
36
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
|
|
10
|
+
const HEADERS = {
|
|
11
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function googleSearch(query, limit = 5) {
|
|
15
|
+
const res = await axios.get(
|
|
16
|
+
`https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1&skip_disambig=1`,
|
|
17
|
+
{ headers: HEADERS, timeout: 15000 }
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
const data = res.data
|
|
21
|
+
const results = []
|
|
22
|
+
|
|
23
|
+
if (data?.AbstractText && data?.AbstractURL) {
|
|
24
|
+
results.push({
|
|
25
|
+
title: data.Heading || query,
|
|
26
|
+
url: data.AbstractURL,
|
|
27
|
+
snippet: data.AbstractText,
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const item of (data?.RelatedTopics || [])) {
|
|
32
|
+
if (results.length >= limit) break
|
|
33
|
+
if (item?.Topics) {
|
|
34
|
+
for (const sub of item.Topics) {
|
|
35
|
+
if (results.length >= limit) break
|
|
36
|
+
if (sub?.FirstURL && sub?.Text) {
|
|
37
|
+
results.push({
|
|
38
|
+
title: sub.Text.split(' - ')[0] || sub.Text.slice(0, 60),
|
|
39
|
+
url: sub.FirstURL,
|
|
40
|
+
snippet: sub.Text,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} else if (item?.FirstURL && item?.Text) {
|
|
45
|
+
results.push({
|
|
46
|
+
title: item.Text.split(' - ')[0] || item.Text.slice(0, 60),
|
|
47
|
+
url: item.FirstURL,
|
|
48
|
+
snippet: item.Text,
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!results.length) throw new Error('Sin resultados')
|
|
54
|
+
return results.slice(0, limit)
|
|
55
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* © Created by AxelDev09 🔥
|
|
3
|
+
* GitHub: https://github.com/AxelDev09
|
|
4
|
+
* Instagram: @axeldev09
|
|
5
|
+
* Deja los créditos we 🗣️
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { ytSearch } from './youtube.js'
|
|
9
|
+
export { googleSearch } from './google.js'
|
|
10
|
+
export { spotify } from './spotify.js'
|
|
11
|
+
export { giphy } from './giphy.js'
|
|
12
|
+
export { pinsearch, pinimg, pinvid } from './pinterest.js'
|
|
@@ -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
|
+
|
|
11
|
+
const HEADERS = {
|
|
12
|
+
'User-Agent': 'Mozilla/5.0 (Linux; Android 10; SM-G975F) AppleWebKit/537.36 Chrome/120.0.0.0 Mobile Safari/537.36',
|
|
13
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function toOriginal(src) {
|
|
17
|
+
return src.replace(/\/\d+x\//, '/originals/').replace(/\/\d+x\d+\//, '/originals/')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isValidPin(src) {
|
|
21
|
+
return (
|
|
22
|
+
src.includes('i.pinimg.com') &&
|
|
23
|
+
/\.(jpg|jpeg|png|webp)/.test(src) &&
|
|
24
|
+
!src.includes('/60x60/') &&
|
|
25
|
+
!src.includes('/videos/thumbnails/')
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function extractVidsFromHtml(html, limit) {
|
|
30
|
+
const seen = new Set()
|
|
31
|
+
const vids = []
|
|
32
|
+
const RE = /https:\/\/v\d+\.pinimg\.com\/videos\/[^\s"'\\]+\.mp4/g
|
|
33
|
+
for (const m of html.matchAll(RE)) {
|
|
34
|
+
const original = m[0]
|
|
35
|
+
if (seen.has(original)) continue
|
|
36
|
+
seen.add(original)
|
|
37
|
+
const hd = original.replace(/\/\d+p\//, '/720p/')
|
|
38
|
+
const sd = original.replace(/\/\d+p\//, '/480p/')
|
|
39
|
+
vids.push({ video: hd, sd, original })
|
|
40
|
+
if (vids.length >= limit) break
|
|
41
|
+
}
|
|
42
|
+
return vids
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function pinvid(input, limit = 5) {
|
|
46
|
+
if (input.includes('pinterest.com/pin/') || input.includes('pin.it/')) {
|
|
47
|
+
let resolvedUrl = input
|
|
48
|
+
if (input.includes('pin.it/')) {
|
|
49
|
+
const r = await axios.get(input, { headers: HEADERS, maxRedirects: 5, timeout: 10000 })
|
|
50
|
+
resolvedUrl = r.request?.res?.responseUrl || r.config?.url || input
|
|
51
|
+
}
|
|
52
|
+
const res = await axios.get(resolvedUrl, { headers: HEADERS, timeout: 15000 })
|
|
53
|
+
const vids = extractVidsFromHtml(res.data, limit)
|
|
54
|
+
if (!vids.length) throw new Error('Este pin no tiene video')
|
|
55
|
+
return vids.map((v, i) => ({ index: i + 1, ...v }))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const res = await axios.get(
|
|
59
|
+
`https://www.pinterest.com/search/pins/?q=${encodeURIComponent(input + ' video')}`,
|
|
60
|
+
{ headers: HEADERS, timeout: 15000 }
|
|
61
|
+
)
|
|
62
|
+
const vids = extractVidsFromHtml(res.data, limit)
|
|
63
|
+
if (!vids.length) throw new Error('Sin videos en Pinterest')
|
|
64
|
+
return vids.map((v, i) => ({ index: i + 1, ...v }))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function pinsearch(query, limit = 10) {
|
|
68
|
+
const res = await axios.get(
|
|
69
|
+
`https://www.pinterest.com/search/pins/?q=${encodeURIComponent(query)}`,
|
|
70
|
+
{ headers: HEADERS, timeout: 15000 }
|
|
71
|
+
)
|
|
72
|
+
const $ = cheerio.load(res.data)
|
|
73
|
+
const seen = new Set()
|
|
74
|
+
const imgs = []
|
|
75
|
+
|
|
76
|
+
$('img').each((_, el) => {
|
|
77
|
+
const src = $(el).attr('src') || ''
|
|
78
|
+
const srcset = $(el).attr('srcset') || ''
|
|
79
|
+
const sources = [src, ...srcset.split(',').map(s => s.trim().split(' ')[0])]
|
|
80
|
+
for (const s of sources) {
|
|
81
|
+
if (!isValidPin(s)) continue
|
|
82
|
+
const high = toOriginal(s)
|
|
83
|
+
if (!seen.has(high)) { seen.add(high); imgs.push(high) }
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
$('[style]').each((_, el) => {
|
|
88
|
+
const style = $(el).attr('style') || ''
|
|
89
|
+
const m = style.match(/url\(['"]?(https:\/\/i\.pinimg\.com[^'")\s]+)['"]?\)/)
|
|
90
|
+
if (m && isValidPin(m[1])) {
|
|
91
|
+
const high = toOriginal(m[1])
|
|
92
|
+
if (!seen.has(high)) { seen.add(high); imgs.push(high) }
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const result = imgs.slice(0, limit)
|
|
97
|
+
if (!result.length) throw new Error('Sin resultados en Pinterest')
|
|
98
|
+
return result.map((url, i) => ({ index: i + 1, image: url, url }))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function pinimg(input, limit = 5) {
|
|
102
|
+
if (!input.includes('pinterest.com/pin/') && !input.includes('pin.it/')) {
|
|
103
|
+
return pinsearch(input, limit)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let resolvedUrl = input
|
|
107
|
+
if (input.includes('pin.it/')) {
|
|
108
|
+
const r = await axios.get(input, { headers: HEADERS, maxRedirects: 5, timeout: 10000 })
|
|
109
|
+
resolvedUrl = r.request?.res?.responseUrl || r.config?.url || input
|
|
110
|
+
}
|
|
111
|
+
const url = resolvedUrl
|
|
112
|
+
|
|
113
|
+
const res = await axios.get(url, { headers: HEADERS, timeout: 15000 })
|
|
114
|
+
const html = res.data
|
|
115
|
+
const $ = cheerio.load(html)
|
|
116
|
+
const seen = new Set()
|
|
117
|
+
const imgs = []
|
|
118
|
+
|
|
119
|
+
const jsonMatch = html.match(/"orig"\s*:\s*\{"url"\s*:\s*"([^"]+)"/)
|
|
120
|
+
if (jsonMatch) {
|
|
121
|
+
const u = jsonMatch[1].replace(/\\u002F/g, '/').replace(/\\\//g, '/')
|
|
122
|
+
if (u.includes('i.pinimg.com')) { seen.add(u); imgs.push(u) }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const PIN_IMG_RE = /"url"\s*:\s*"(https:\\?\/\\?\/i\.pinimg\.com[^"]+)"/g
|
|
126
|
+
const pinMatches = [...html.matchAll(PIN_IMG_RE)]
|
|
127
|
+
for (const m of pinMatches) {
|
|
128
|
+
const u = m[1].replace(/\\u002F/g, '/').replace(/\\\//g, '/').replace(/\\/g, '')
|
|
129
|
+
if (isValidPin(u)) {
|
|
130
|
+
const high = toOriginal(u)
|
|
131
|
+
if (!seen.has(high)) { seen.add(high); imgs.push(high) }
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
$('img').each((_, el) => {
|
|
136
|
+
const src = $(el).attr('src') || ''
|
|
137
|
+
const srcset = $(el).attr('srcset') || ''
|
|
138
|
+
const sources = [src, ...srcset.split(',').map(s => s.trim().split(' ')[0])]
|
|
139
|
+
for (const s of sources) {
|
|
140
|
+
if (!isValidPin(s)) continue
|
|
141
|
+
const high = toOriginal(s)
|
|
142
|
+
if (!seen.has(high)) { seen.add(high); imgs.push(high) }
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const ogImg = $('meta[property="og:image"]').attr('content') || ''
|
|
147
|
+
const ogTitle = $('meta[property="og:title"]').attr('content') || ''
|
|
148
|
+
const ogDesc = $('meta[property="og:description"]').attr('content') || ''
|
|
149
|
+
if (ogImg && !seen.has(ogImg)) imgs.push(ogImg)
|
|
150
|
+
|
|
151
|
+
const unique = [...new Set(imgs)].filter(Boolean)
|
|
152
|
+
if (!unique.length) throw new Error('No se pudo extraer la imagen del pin')
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
id: resolvedUrl.match(/\/pin\/(\d+)/)?.[1] || '',
|
|
156
|
+
title: ogTitle,
|
|
157
|
+
description: ogDesc,
|
|
158
|
+
image: unique[0],
|
|
159
|
+
images: unique,
|
|
160
|
+
url: resolvedUrl,
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
|
|
10
|
+
const HEADERS = {
|
|
11
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function searchTracks(query, limit = 5) {
|
|
15
|
+
const res = await axios.get(
|
|
16
|
+
`https://api.deezer.com/search?q=${encodeURIComponent(query)}&limit=${limit}`,
|
|
17
|
+
{ headers: HEADERS, timeout: 15000 }
|
|
18
|
+
)
|
|
19
|
+
const tracks = res.data?.data || []
|
|
20
|
+
if (!tracks.length) throw new Error('Sin resultados')
|
|
21
|
+
return tracks.map(t => ({
|
|
22
|
+
type: 'track',
|
|
23
|
+
title: t.title,
|
|
24
|
+
artist: t.artist?.name || '',
|
|
25
|
+
album: t.album?.title || '',
|
|
26
|
+
duration: t.duration || 0,
|
|
27
|
+
thumbnail: t.album?.cover_big || t.album?.cover || '',
|
|
28
|
+
url: t.link || '',
|
|
29
|
+
preview: t.preview || null,
|
|
30
|
+
id: String(t.id),
|
|
31
|
+
}))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function searchAlbums(query, limit = 5) {
|
|
35
|
+
const res = await axios.get(
|
|
36
|
+
`https://api.deezer.com/search/album?q=${encodeURIComponent(query)}&limit=${limit}`,
|
|
37
|
+
{ headers: HEADERS, timeout: 15000 }
|
|
38
|
+
)
|
|
39
|
+
const albums = res.data?.data || []
|
|
40
|
+
if (!albums.length) throw new Error('Sin resultados')
|
|
41
|
+
return albums.map(a => ({
|
|
42
|
+
type: 'album',
|
|
43
|
+
title: a.title,
|
|
44
|
+
artist: a.artist?.name || '',
|
|
45
|
+
thumbnail: a.cover_big || a.cover || '',
|
|
46
|
+
url: a.link || '',
|
|
47
|
+
tracks: a.nb_tracks || 0,
|
|
48
|
+
year: a.release_date?.slice(0, 4) || '',
|
|
49
|
+
id: String(a.id),
|
|
50
|
+
}))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function spotify(query, type = 'track', limit = 5) {
|
|
54
|
+
if (type === 'album') return searchAlbums(query, limit)
|
|
55
|
+
return searchTracks(query, limit)
|
|
56
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* © Created by AxelDev09 🔥
|
|
3
|
+
* GitHub: https://github.com/AxelDev09
|
|
4
|
+
* Instagram: @axeldev09
|
|
5
|
+
* Deja los créditos we 🗣️
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { tiktokStalk } from './tiktokstalk.js'
|
|
9
|
+
export { lyricsSearch, lyricsGet } from './lyrics.js'
|
|
10
|
+
export { translate, getLangs } from './translator.js'
|
|
11
|
+
export { weather } from './weather.js'
|
|
12
|
+
export { qrGenerate, qrRead } from './qr.js'
|
|
13
|
+
export { shortenUrl, expandUrl } from './urlshortener.js'
|
|
14
|
+
export { news, newsCategories } from './news.js'
|
|
15
|
+
export { upload } from './upload.js'
|