@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,201 @@
|
|
|
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_DESKTOP = {
|
|
11
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36',
|
|
12
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
13
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
14
|
+
'Sec-Fetch-Mode': 'navigate',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const HEADERS_MOBILE = {
|
|
18
|
+
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 Mobile/15E148',
|
|
19
|
+
'Accept': 'text/html,application/xhtml+xml',
|
|
20
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function cleanUrl(url) {
|
|
24
|
+
try {
|
|
25
|
+
const u = new URL(url)
|
|
26
|
+
if (u.hostname.includes('fb.watch')) return url
|
|
27
|
+
u.search = ''
|
|
28
|
+
return u.toString()
|
|
29
|
+
} catch { return url }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function resolveShortUrl(url) {
|
|
33
|
+
if (!url.includes('fb.watch') && !url.includes('fb.com')) return url
|
|
34
|
+
const res = await axios.get(url, { headers: HEADERS_DESKTOP, maxRedirects: 5, timeout: 10000 })
|
|
35
|
+
return res.request?.res?.responseUrl || url
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function extractVideoUrls(html) {
|
|
39
|
+
const results = { hd: null, sd: null, thumb: null, title: '' }
|
|
40
|
+
|
|
41
|
+
const unescape = s => s
|
|
42
|
+
.replace(/\\u0025/g, '%')
|
|
43
|
+
.replace(/\\u002F/g, '/')
|
|
44
|
+
.replace(/\\\//g, '/')
|
|
45
|
+
.replace(/\\"/g, '"')
|
|
46
|
+
.replace(/\\u0026/g, '&')
|
|
47
|
+
.replace(/\\u003C/g, '<')
|
|
48
|
+
.replace(/\\u003E/g, '>')
|
|
49
|
+
|
|
50
|
+
const hdPatterns = [
|
|
51
|
+
/"browser_native_hd_url"\s*:\s*"([^"]+)"/,
|
|
52
|
+
/"hd_src"\s*:\s*"([^"]+)"/,
|
|
53
|
+
/hd_src\s*:\s*"([^"]+)"/,
|
|
54
|
+
/"hdUrl"\s*:\s*"([^"]+)"/,
|
|
55
|
+
/"playable_url_quality_hd"\s*:\s*"([^"]+)"/,
|
|
56
|
+
/"videoUrl"\s*:\s*"([^"]+)"/,
|
|
57
|
+
/sd_src_no_ratelimit\s*:\s*"([^"]+)"/,
|
|
58
|
+
]
|
|
59
|
+
for (const p of hdPatterns) {
|
|
60
|
+
const m = html.match(p)
|
|
61
|
+
if (m) { results.hd = unescape(m[1]); break }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const sdPatterns = [
|
|
65
|
+
/"browser_native_sd_url"\s*:\s*"([^"]+)"/,
|
|
66
|
+
/"sd_src"\s*:\s*"([^"]+)"/,
|
|
67
|
+
/sd_src\s*:\s*"([^"]+)"/,
|
|
68
|
+
/"sdUrl"\s*:\s*"([^"]+)"/,
|
|
69
|
+
/"playable_url"\s*:\s*"([^"]+)"/,
|
|
70
|
+
/"progressive_url"\s*:\s*"([^"]+)"/,
|
|
71
|
+
]
|
|
72
|
+
for (const p of sdPatterns) {
|
|
73
|
+
const m = html.match(p)
|
|
74
|
+
if (m) { results.sd = unescape(m[1]); break }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const thumbPatterns = [
|
|
78
|
+
/"thumbnailImage"\s*:\s*\{"uri"\s*:\s*"([^"]+)"/,
|
|
79
|
+
/og:image"\s+content="([^"]+)"/,
|
|
80
|
+
/"preferred_thumbnail"\s*:\s*\{"image"\s*:\s*\{"uri"\s*:\s*"([^"]+)"/,
|
|
81
|
+
]
|
|
82
|
+
for (const p of thumbPatterns) {
|
|
83
|
+
const m = html.match(p)
|
|
84
|
+
if (m) { results.thumb = unescape(m[1]); break }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const titlePatterns = [
|
|
88
|
+
/<title>([^<]+)<\/title>/,
|
|
89
|
+
/"story_name"\s*:\s*"([^"]+)"/,
|
|
90
|
+
/"title"\s*:\s*\{"text"\s*:\s*"([^"]+)"/,
|
|
91
|
+
]
|
|
92
|
+
for (const p of titlePatterns) {
|
|
93
|
+
const m = html.match(p)
|
|
94
|
+
if (m) {
|
|
95
|
+
results.title = m[1].replace(/'/g, "'").replace(/&/g, '&')
|
|
96
|
+
break
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return results
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function extractVideoId(url) {
|
|
104
|
+
const patterns = [
|
|
105
|
+
/\/videos\/(\d+)/,
|
|
106
|
+
/\/video\/(\d+)/,
|
|
107
|
+
/v=(\d+)/,
|
|
108
|
+
/story_fbid=(\d+)/,
|
|
109
|
+
/\/reel\/(\d+)/,
|
|
110
|
+
/\/(\d{10,})/,
|
|
111
|
+
]
|
|
112
|
+
for (const p of patterns) {
|
|
113
|
+
const m = url.match(p)
|
|
114
|
+
if (m) return m[1]
|
|
115
|
+
}
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function tryMobilePage(url) {
|
|
120
|
+
const murl = url.replace('www.facebook.com', 'm.facebook.com')
|
|
121
|
+
const res = await axios.get(murl, { headers: HEADERS_MOBILE, timeout: 15000 })
|
|
122
|
+
return extractVideoUrls(res.data)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function tryDesktopPage(url) {
|
|
126
|
+
const res = await axios.get(url, { headers: HEADERS_DESKTOP, timeout: 15000 })
|
|
127
|
+
return extractVideoUrls(res.data)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function tryGraphQL(videoId) {
|
|
131
|
+
const queries = [
|
|
132
|
+
{ doc_id: '10154874153461729', vars: { videoID: videoId } },
|
|
133
|
+
{ doc_id: '6861055570608956', vars: { UFICommentID: videoId, UFIFeedbackID: videoId } },
|
|
134
|
+
{ doc_id: '2443510449042541', vars: { videoID: videoId } },
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
for (const q of queries) {
|
|
138
|
+
try {
|
|
139
|
+
const res = await axios.post(
|
|
140
|
+
'https://www.facebook.com/api/graphql/',
|
|
141
|
+
new URLSearchParams({
|
|
142
|
+
variables: JSON.stringify(q.vars),
|
|
143
|
+
doc_id: q.doc_id,
|
|
144
|
+
server_timestamps: 'true',
|
|
145
|
+
}),
|
|
146
|
+
{
|
|
147
|
+
headers: {
|
|
148
|
+
...HEADERS_DESKTOP,
|
|
149
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
150
|
+
'X-FB-Friendly-Name':'VideoPlayerQuery',
|
|
151
|
+
},
|
|
152
|
+
timeout: 15000,
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
const d = res.data
|
|
156
|
+
const node = d?.data?.video || d?.data?.node || d?.data?.mediaset
|
|
157
|
+
const hd = node?.playable_url_quality_hd || node?.browser_native_hd_url
|
|
158
|
+
const sd = node?.playable_url || node?.browser_native_sd_url
|
|
159
|
+
if (hd || sd) return { hd: hd || null, sd: sd || null, thumb: node?.thumbnailImage?.uri || '', title: node?.name || node?.title?.text || '' }
|
|
160
|
+
} catch {}
|
|
161
|
+
}
|
|
162
|
+
return null
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function fbDownload(url) {
|
|
166
|
+
if (!url.includes('facebook.com') && !url.includes('fb.watch') && !url.includes('fb.com'))
|
|
167
|
+
throw new Error('URL de Facebook inválida')
|
|
168
|
+
|
|
169
|
+
const resolved = await resolveShortUrl(url)
|
|
170
|
+
const clean = cleanUrl(resolved)
|
|
171
|
+
const videoId = extractVideoId(clean)
|
|
172
|
+
const errors = []
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const parsed = await tryMobilePage(clean)
|
|
176
|
+
if (parsed.hd || parsed.sd) {
|
|
177
|
+
return { videoId: videoId || '', title: parsed.title, hd: parsed.hd, sd: parsed.sd, thumb: parsed.thumb, source: 'mobile' }
|
|
178
|
+
}
|
|
179
|
+
errors.push('mobile: sin urls')
|
|
180
|
+
} catch (e) { errors.push('mobile: ' + e.message) }
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const parsed = await tryDesktopPage(clean)
|
|
184
|
+
if (parsed.hd || parsed.sd) {
|
|
185
|
+
return { videoId: videoId || '', title: parsed.title, hd: parsed.hd, sd: parsed.sd, thumb: parsed.thumb, source: 'desktop' }
|
|
186
|
+
}
|
|
187
|
+
errors.push('desktop: sin urls')
|
|
188
|
+
} catch (e) { errors.push('desktop: ' + e.message) }
|
|
189
|
+
|
|
190
|
+
if (videoId) {
|
|
191
|
+
try {
|
|
192
|
+
const data = await tryGraphQL(videoId)
|
|
193
|
+
if (data?.hd || data?.sd) {
|
|
194
|
+
return { videoId, title: data.title, hd: data.hd, sd: data.sd, thumb: data.thumb, source: 'graphql' }
|
|
195
|
+
}
|
|
196
|
+
errors.push('graphql: sin urls')
|
|
197
|
+
} catch (e) { errors.push('graphql: ' + e.message) }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
throw new Error('No se pudo descargar. Errores: ' + errors.join(' | '))
|
|
201
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
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
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseRepo(input) {
|
|
16
|
+
const m = input.match(/github\.com\/([^/]+\/[^/]+)/)
|
|
17
|
+
if (m) return m[1].replace(/\.git$/, '')
|
|
18
|
+
if (/^[^/]+\/[^/]+$/.test(input)) return input
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function get(url) {
|
|
23
|
+
const res = await axios.get(url, { headers: HEADERS, timeout: 15000 })
|
|
24
|
+
return res.data
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function githubInfo(repo) {
|
|
28
|
+
const r = parseRepo(repo)
|
|
29
|
+
if (!r) throw new Error('Repo inválido. Usá owner/repo o URL de GitHub')
|
|
30
|
+
|
|
31
|
+
const data = await get(`https://api.github.com/repos/${r}`)
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
name: data.name,
|
|
35
|
+
fullName: data.full_name,
|
|
36
|
+
description: data.description || '',
|
|
37
|
+
url: data.html_url,
|
|
38
|
+
stars: data.stargazers_count,
|
|
39
|
+
forks: data.forks_count,
|
|
40
|
+
watchers: data.watchers_count,
|
|
41
|
+
issues: data.open_issues_count,
|
|
42
|
+
language: data.language || '',
|
|
43
|
+
license: data.license?.name || '',
|
|
44
|
+
topics: data.topics || [],
|
|
45
|
+
private: data.private,
|
|
46
|
+
fork: data.fork,
|
|
47
|
+
createdAt: data.created_at,
|
|
48
|
+
updatedAt: data.updated_at,
|
|
49
|
+
pushedAt: data.pushed_at,
|
|
50
|
+
size: data.size,
|
|
51
|
+
defaultBranch: data.default_branch,
|
|
52
|
+
owner: {
|
|
53
|
+
login: data.owner?.login || '',
|
|
54
|
+
avatar: data.owner?.avatar_url || '',
|
|
55
|
+
url: data.owner?.html_url || '',
|
|
56
|
+
type: data.owner?.type || '',
|
|
57
|
+
},
|
|
58
|
+
cloneUrl: data.clone_url,
|
|
59
|
+
sshUrl: data.ssh_url,
|
|
60
|
+
zipUrl: `https://github.com/${r}/archive/refs/heads/${data.default_branch}.zip`,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function githubRelease(repo) {
|
|
65
|
+
const r = parseRepo(repo)
|
|
66
|
+
if (!r) throw new Error('Repo inválido')
|
|
67
|
+
|
|
68
|
+
const data = await get(`https://api.github.com/repos/${r}/releases/latest`)
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
tag: data.tag_name,
|
|
72
|
+
name: data.name || data.tag_name,
|
|
73
|
+
body: data.body || '',
|
|
74
|
+
draft: data.draft,
|
|
75
|
+
prerelease: data.prerelease,
|
|
76
|
+
createdAt: data.created_at,
|
|
77
|
+
publishedAt: data.published_at,
|
|
78
|
+
url: data.html_url,
|
|
79
|
+
tarball: data.tarball_url,
|
|
80
|
+
zipball: data.zipball_url,
|
|
81
|
+
assets: (data.assets || []).map(a => ({
|
|
82
|
+
name: a.name,
|
|
83
|
+
size: a.size,
|
|
84
|
+
downloadUrl: a.browser_download_url,
|
|
85
|
+
downloads: a.download_count,
|
|
86
|
+
contentType: a.content_type,
|
|
87
|
+
})),
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function githubContents(repo, path = '') {
|
|
92
|
+
const r = parseRepo(repo)
|
|
93
|
+
if (!r) throw new Error('Repo inválido')
|
|
94
|
+
|
|
95
|
+
const url = path
|
|
96
|
+
? `https://api.github.com/repos/${r}/contents/${path}`
|
|
97
|
+
: `https://api.github.com/repos/${r}/contents`
|
|
98
|
+
const data = await get(url)
|
|
99
|
+
|
|
100
|
+
if (Array.isArray(data)) {
|
|
101
|
+
return data.map(f => ({
|
|
102
|
+
name: f.name,
|
|
103
|
+
path: f.path,
|
|
104
|
+
type: f.type,
|
|
105
|
+
size: f.size,
|
|
106
|
+
url: f.html_url,
|
|
107
|
+
download: f.download_url || null,
|
|
108
|
+
sha: f.sha,
|
|
109
|
+
}))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
name: data.name,
|
|
114
|
+
path: data.path,
|
|
115
|
+
type: data.type,
|
|
116
|
+
size: data.size,
|
|
117
|
+
url: data.html_url,
|
|
118
|
+
download: data.download_url,
|
|
119
|
+
content: data.encoding === 'base64' ? Buffer.from(data.content, 'base64').toString('utf-8') : data.content,
|
|
120
|
+
sha: data.sha,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function githubSearch(query, type = 'repositories', limit = 5) {
|
|
125
|
+
const validTypes = ['repositories', 'users', 'code', 'issues']
|
|
126
|
+
if (!validTypes.includes(type)) throw new Error('Tipo inválido. Usá: ' + validTypes.join(', '))
|
|
127
|
+
|
|
128
|
+
const data = await get(
|
|
129
|
+
`https://api.github.com/search/${type}?q=${encodeURIComponent(query)}&per_page=${limit}`
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
const items = data.items || []
|
|
133
|
+
|
|
134
|
+
if (type === 'repositories') {
|
|
135
|
+
return items.map(r => ({
|
|
136
|
+
name: r.full_name,
|
|
137
|
+
description: r.description || '',
|
|
138
|
+
url: r.html_url,
|
|
139
|
+
stars: r.stargazers_count,
|
|
140
|
+
forks: r.forks_count,
|
|
141
|
+
language: r.language || '',
|
|
142
|
+
updatedAt: r.updated_at,
|
|
143
|
+
}))
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (type === 'users') {
|
|
147
|
+
return items.map(u => ({
|
|
148
|
+
login: u.login,
|
|
149
|
+
url: u.html_url,
|
|
150
|
+
avatar: u.avatar_url,
|
|
151
|
+
type: u.type,
|
|
152
|
+
}))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return items.slice(0, limit)
|
|
156
|
+
}
|
|
@@ -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 { ytInfo, ytDownload, ytSearch } from './youtube.js'
|
|
9
|
+
export { ytInfoV2, ytDownloadV2, getFileSizeV2 } from './youtubev2.js'
|
|
10
|
+
export { tiktokInfo, tiktokDownload } from './tiktok.js'
|
|
11
|
+
export { fbDownload } from './facebook.js'
|
|
12
|
+
export { tweetInfo, tweetDownload } from './twitter.js'
|
|
13
|
+
export { mediafireInfo } from './mediafire.js'
|
|
14
|
+
export { githubInfo, githubRelease, githubContents, githubSearch } from './github.js'
|
|
15
|
+
export { apkSearch, apkInfo } from './apk.js'
|
|
@@ -0,0 +1,77 @@
|
|
|
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 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseKey(url) {
|
|
16
|
+
const m = url.match(/mediafire\.com\/file\/([a-z0-9]+)/)
|
|
17
|
+
return m ? m[1] : null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function deduplicateName(raw) {
|
|
21
|
+
const clean = raw.trim().replace(/\s+/g, ' ')
|
|
22
|
+
const half = Math.ceil(clean.length / 2)
|
|
23
|
+
const first = clean.slice(0, half)
|
|
24
|
+
const second = clean.slice(half).trim()
|
|
25
|
+
return second.startsWith(first.trim()) ? first.trim() : clean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function tryPage(url) {
|
|
29
|
+
const res = await axios.get(url, { headers: HEADERS, timeout: 15000 })
|
|
30
|
+
const $ = cheerio.load(res.data)
|
|
31
|
+
const link = $('a#downloadButton').attr('href') || $('a.input').attr('href')
|
|
32
|
+
if (!link) throw new Error('page: sin link de descarga')
|
|
33
|
+
const name = deduplicateName($('div.filename').text())
|
|
34
|
+
const size = $('ul.details li').first().text().replace('File size:', '').trim()
|
|
35
|
+
return { link, name, size }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function tryAPI(key) {
|
|
39
|
+
const res = await axios.get(
|
|
40
|
+
`https://www.mediafire.com/api/1.5/file/get_links.php?quick_key=${key}&link_type=normal_download&response_format=json`,
|
|
41
|
+
{ headers: HEADERS, timeout: 15000 }
|
|
42
|
+
)
|
|
43
|
+
const data = res.data?.response
|
|
44
|
+
if (data?.result !== 'Success') throw new Error('api: ' + (data?.message || 'sin resultado'))
|
|
45
|
+
const dl = data?.links?.[0]?.normal_download
|
|
46
|
+
if (!dl) throw new Error('api: sin download link')
|
|
47
|
+
return dl
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function mediafireInfo(url) {
|
|
51
|
+
if (!url.includes('mediafire.com')) throw new Error('URL de MediaFire inválida')
|
|
52
|
+
|
|
53
|
+
const key = parseKey(url)
|
|
54
|
+
const errors = []
|
|
55
|
+
let info = null
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
info = await tryPage(url)
|
|
59
|
+
} catch (e) { errors.push('page: ' + e.message) }
|
|
60
|
+
|
|
61
|
+
if (!info?.link && key) {
|
|
62
|
+
try {
|
|
63
|
+
const link = await tryAPI(key)
|
|
64
|
+
info = { ...(info || {}), link }
|
|
65
|
+
} catch (e) { errors.push('api: ' + e.message) }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!info?.link) throw new Error('No se pudo obtener el link. Errores: ' + errors.join(' | '))
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
key: key || '',
|
|
72
|
+
name: info.name || '',
|
|
73
|
+
size: info.size || '',
|
|
74
|
+
download: info.link,
|
|
75
|
+
url,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
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 resolveUrl(url) {
|
|
15
|
+
if (url.includes('vm.tiktok.com') || url.includes('vt.tiktok.com')) {
|
|
16
|
+
const res = await axios.get(url, { headers: HEADERS, maxRedirects: 5, timeout: 10000 })
|
|
17
|
+
return res.request?.res?.responseUrl || res.config?.url || url
|
|
18
|
+
}
|
|
19
|
+
return url
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function tiktokDownload(url) {
|
|
23
|
+
const resolved = await resolveUrl(url)
|
|
24
|
+
const res = await axios.get(
|
|
25
|
+
`https://tikwm.com/api/?url=${encodeURIComponent(resolved)}`,
|
|
26
|
+
{ headers: HEADERS, timeout: 20000 }
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
const data = res.data
|
|
30
|
+
if (data?.code !== 0 || !data?.data) throw new Error('tikwm: ' + (data?.msg || 'sin datos'))
|
|
31
|
+
|
|
32
|
+
const d = data.data
|
|
33
|
+
return {
|
|
34
|
+
id: d.id || '',
|
|
35
|
+
title: d.title || '',
|
|
36
|
+
author: d.author?.nickname || d.author?.unique_id || '',
|
|
37
|
+
thumbnail: d.cover || d.origin_cover || '',
|
|
38
|
+
duration: d.duration || 0,
|
|
39
|
+
nowatermark: d.play || null,
|
|
40
|
+
watermark: d.wmplay || null,
|
|
41
|
+
audio: d.music || null,
|
|
42
|
+
music: {
|
|
43
|
+
title: d.music_info?.title || '',
|
|
44
|
+
author: d.music_info?.author || '',
|
|
45
|
+
url: d.music_info?.play || null,
|
|
46
|
+
cover: d.music_info?.cover || '',
|
|
47
|
+
},
|
|
48
|
+
plays: d.play_count || 0,
|
|
49
|
+
likes: d.digg_count || 0,
|
|
50
|
+
comments: d.comment_count || 0,
|
|
51
|
+
shares: d.share_count || 0,
|
|
52
|
+
source: 'tikwm',
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function tiktokInfo(url) {
|
|
57
|
+
return tiktokDownload(url)
|
|
58
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
|
|
2
|
+
/*
|
|
3
|
+
* © Created by AxelDev09 🔥
|
|
4
|
+
* GitHub: https://github.com/AxelDev09
|
|
5
|
+
* Instagram: @axeldev09
|
|
6
|
+
* Deja los créditos we 🗣️
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import axios from 'axios'
|
|
10
|
+
|
|
11
|
+
const HEADERS = {
|
|
12
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36',
|
|
13
|
+
'Referer': 'https://platform.twitter.com/',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function parseTweetId(url) {
|
|
17
|
+
const m = url.match(/\/status\/(\d+)/)
|
|
18
|
+
return m ? m[1] : (/^\d+$/.test(url) ? url : null)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parseMedia(details) {
|
|
22
|
+
if (!details?.length) return []
|
|
23
|
+
return details.map(m => {
|
|
24
|
+
if (m.type === 'video' || m.type === 'animated_gif') {
|
|
25
|
+
const variants = m.video_info?.variants || []
|
|
26
|
+
const best = variants
|
|
27
|
+
.filter(v => v.content_type === 'video/mp4')
|
|
28
|
+
.sort((a, b) => (b.bitrate || 0) - (a.bitrate || 0))[0]
|
|
29
|
+
return {
|
|
30
|
+
type: m.type === 'animated_gif' ? 'gif' : 'video',
|
|
31
|
+
url: best?.url || '',
|
|
32
|
+
thumbnail: m.media_url_https + '?format=jpg&name=large',
|
|
33
|
+
width: m.original_info?.width || 0,
|
|
34
|
+
height: m.original_info?.height || 0,
|
|
35
|
+
variants: variants.filter(v => v.url).map(v => ({
|
|
36
|
+
url: v.url,
|
|
37
|
+
contentType: v.content_type,
|
|
38
|
+
bitrate: v.bitrate || 0,
|
|
39
|
+
})),
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
type: 'photo',
|
|
44
|
+
url: m.media_url_https + '?format=jpg&name=orig',
|
|
45
|
+
thumb: m.media_url_https + '?format=jpg&name=small',
|
|
46
|
+
width: m.original_info?.width || 0,
|
|
47
|
+
height: m.original_info?.height || 0,
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function tweetInfo(url) {
|
|
53
|
+
const id = parseTweetId(url)
|
|
54
|
+
if (!id) throw new Error('URL/ID de Twitter inválido')
|
|
55
|
+
|
|
56
|
+
const res = await axios.get(
|
|
57
|
+
`https://cdn.syndication.twimg.com/tweet-result?id=${id}&lang=en&features=tfw_timeline_list%3A%3Btfw_follower_count_sunset%3Atrue&token=abc123`,
|
|
58
|
+
{ headers: HEADERS, timeout: 15000 }
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
const d = res.data
|
|
62
|
+
if (!d?.id_str) throw new Error('Tweet no encontrado o privado')
|
|
63
|
+
|
|
64
|
+
const medias = parseMedia(d.mediaDetails)
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
id: d.id_str,
|
|
68
|
+
text: d.text || '',
|
|
69
|
+
lang: d.lang || '',
|
|
70
|
+
createdAt: d.created_at || '',
|
|
71
|
+
likes: d.favorite_count || 0,
|
|
72
|
+
replies: d.conversation_count || 0,
|
|
73
|
+
author: {
|
|
74
|
+
id: d.user?.id_str || '',
|
|
75
|
+
name: d.user?.name || '',
|
|
76
|
+
username: d.user?.screen_name || '',
|
|
77
|
+
avatar: d.user?.profile_image_url_https?.replace('_normal', '') || '',
|
|
78
|
+
verified: d.user?.is_blue_verified || false,
|
|
79
|
+
},
|
|
80
|
+
hashtags: d.entities?.hashtags?.map(h => h.text) || [],
|
|
81
|
+
mentions: d.entities?.user_mentions?.map(m => m.screen_name) || [],
|
|
82
|
+
urls: d.entities?.urls?.map(u => u.expanded_url) || [],
|
|
83
|
+
medias,
|
|
84
|
+
url: `https://x.com/${d.user?.screen_name}/status/${d.id_str}`,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function tweetDownload(url) {
|
|
89
|
+
const info = await tweetInfo(url)
|
|
90
|
+
const videos = info.medias.filter(m => m.type === 'video' || m.type === 'gif')
|
|
91
|
+
const photos = info.medias.filter(m => m.type === 'photo')
|
|
92
|
+
|
|
93
|
+
if (!videos.length && !photos.length)
|
|
94
|
+
throw new Error('Este tweet no tiene media descargable')
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
id: info.id,
|
|
98
|
+
text: info.text,
|
|
99
|
+
author: info.author,
|
|
100
|
+
createdAt: info.createdAt,
|
|
101
|
+
videos,
|
|
102
|
+
photos,
|
|
103
|
+
hasVideo: videos.length > 0,
|
|
104
|
+
hasPhoto: photos.length > 0,
|
|
105
|
+
}
|
|
106
|
+
}
|