Added A-Cam (Vilde).
This commit is contained in:
228
src/scrapers/acam.js
Executable file
228
src/scrapers/acam.js
Executable file
@@ -0,0 +1,228 @@
|
||||
'use strict';
|
||||
|
||||
const unprint = require('unprint');
|
||||
|
||||
const slugify = require('../utils/slugify');
|
||||
|
||||
function extractEntryId(poster) {
|
||||
try {
|
||||
return slugify(new URL(poster).pathname.match(/\/images\/(.*?)\.jpg/)?.[1]?.replace(/smak.*/i, ''), '');
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function extractTags(title) {
|
||||
if (!title) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const firstTagIndex = title.match(/[A-Z]{2}/)?.index;
|
||||
|
||||
if (firstTagIndex) {
|
||||
const tagSection = title
|
||||
.slice(firstTagIndex)
|
||||
.match(/([A-Z0-9\s]{2,})/g);
|
||||
|
||||
if (tagSection) {
|
||||
return tagSection
|
||||
.map((tag) => tag.trim().toLowerCase())
|
||||
.filter(Boolean) || [];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
// derived photo is usually uncensored and preferred as poster, but not guaranteed to exist, so fall back to original image
|
||||
function getPhotos(poster) {
|
||||
const photo = poster?.replace(/(s[ma]{2}kprov\d*)|([._]preview)/i, ''); // sic
|
||||
|
||||
if (photo === poster) {
|
||||
return {
|
||||
poster,
|
||||
photos: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
poster: [photo, poster],
|
||||
photos: [poster],
|
||||
};
|
||||
}
|
||||
|
||||
function scrapeAll(scenes, channel, parameters) {
|
||||
return scenes.map(({ query }) => {
|
||||
const release = {};
|
||||
|
||||
// Vilde URLs are temporary tokens for some reason, seem to be handled entirely back-end
|
||||
const url = query.url('a[href*="/show-video"]');
|
||||
|
||||
release.token = new URL(url).pathname.match(/\/show-video\/([a-z0-9]+)/)?.[1];
|
||||
release.forceDeep = true;
|
||||
|
||||
release.title = query.content('a h5, .product-content p, .video_text');
|
||||
release.tags = extractTags(release.title);
|
||||
|
||||
const { poster, photos } = getPhotos(query.img('img[src*="/videos/images"], img[src*="/uploads/images"]'));
|
||||
|
||||
release.poster = poster;
|
||||
release.photos = photos;
|
||||
|
||||
if (parameters.staticUrl) {
|
||||
release.url = url;
|
||||
release.entryId = release.token;
|
||||
} else {
|
||||
release.entryId = extractEntryId(release.poster);
|
||||
}
|
||||
|
||||
return release;
|
||||
});
|
||||
}
|
||||
|
||||
async function setLanguage(parameters) {
|
||||
if (parameters.languageUrl) {
|
||||
const langRes = await unprint.post(parameters.languageUrl, {
|
||||
[parameters.languageKey || 'select_language']: 'english',
|
||||
}, {
|
||||
form: true,
|
||||
});
|
||||
|
||||
return langRes.cookies;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function fetchLatest(channel, page = 1, { parameters }) {
|
||||
const cookies = await setLanguage(parameters);
|
||||
|
||||
const res = await unprint.post(`${channel.origin}/pagination`, {
|
||||
i: page,
|
||||
status: true,
|
||||
}, {
|
||||
selectAll: '.movi-area',
|
||||
form: true,
|
||||
cookies,
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
return scrapeAll(res.context, channel, parameters);
|
||||
}
|
||||
|
||||
return res.status;
|
||||
}
|
||||
|
||||
async function fetchLatestHooked(channel, page = 1, { parameters }) {
|
||||
const cookies = await setLanguage(parameters);
|
||||
|
||||
const res = await unprint.get(`${channel.origin}/Welcome/index/${(page - 1) * 9}`, {
|
||||
selectAll: '.product-main',
|
||||
cookies,
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
return scrapeAll(res.context, channel, parameters);
|
||||
}
|
||||
|
||||
return res.status;
|
||||
}
|
||||
|
||||
async function fetchLatestKanal(channel, page = 1, { parameters }) {
|
||||
const cookies = await setLanguage(parameters);
|
||||
|
||||
const res = await unprint.post(`${channel.origin}/pagination`, {
|
||||
k: page,
|
||||
status: 1,
|
||||
}, {
|
||||
selectAll: '.video_bx',
|
||||
form: true,
|
||||
cookies,
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
return scrapeAll(res.context, channel, parameters);
|
||||
}
|
||||
|
||||
return res.status;
|
||||
}
|
||||
|
||||
function scrapeScene({ query }, { url, baseRelease, parameters }) {
|
||||
const release = {};
|
||||
|
||||
// URL is temporary token
|
||||
if (!query.exists('.login-sec.for-browser, .video-description, .video_co_title')) {
|
||||
// URL likely expired, still returns 200
|
||||
return null;
|
||||
}
|
||||
|
||||
if (query.exists('.video-description')) {
|
||||
const descriptions = query.contents('.video-description p').filter(Boolean);
|
||||
|
||||
release.title = descriptions[0];
|
||||
release.description = descriptions.slice(1).join(' ') || null;
|
||||
} else {
|
||||
release.title = query.content('.login-sec.for-browser h3, .video_co_title h3');
|
||||
release.description = query.contents('.login-sec.for-browser h3 ~ *').join(' ') || null;
|
||||
}
|
||||
|
||||
release.tags = extractTags(release.title);
|
||||
|
||||
const { poster, photos } = getPhotos(query.poster('.play_video_cont video'));
|
||||
|
||||
release.poster = poster;
|
||||
release.photos = photos;
|
||||
|
||||
release.trailer = query.all('.play_video_cont source')
|
||||
.map((videoEl) => ({
|
||||
src: unprint.query.url(videoEl, null, { attribute: 'src' }),
|
||||
quality: unprint.query.number(videoEl, null, { attribute: 'size' }),
|
||||
referer: url,
|
||||
}))
|
||||
.toSorted((videoA, videoB) => videoB.quality - videoA.quality);
|
||||
|
||||
if (parameters.staticUrl) {
|
||||
release.url = url;
|
||||
release.entryId = baseRelease?.token || new URL(url).pathname.match(/\/show-video\/([a-z0-9]+)/)?.[1];
|
||||
} else {
|
||||
release.entryId = extractEntryId(release.poster);
|
||||
}
|
||||
|
||||
return release;
|
||||
}
|
||||
|
||||
async function fetchScene(baseUrl, entity, baseRelease, { parameters }) {
|
||||
const url = baseUrl || (baseRelease?.token && `${entity.origin}/show-video/${baseRelease.token}`) || null;
|
||||
|
||||
if (!url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cookies = await setLanguage(parameters);
|
||||
|
||||
const res = await unprint.get(url, {
|
||||
headers: {
|
||||
'accept-language': 'en-US,en',
|
||||
},
|
||||
cookies,
|
||||
});
|
||||
|
||||
if (res.ok || res.status === 500) { // Anal Hooked returns 500 for valid scene pages
|
||||
return scrapeScene(res.context, { url, baseRelease, parameters });
|
||||
}
|
||||
|
||||
return res.status;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fetchLatest,
|
||||
fetchScene,
|
||||
hooked: {
|
||||
fetchLatest: fetchLatestHooked,
|
||||
fetchScene,
|
||||
},
|
||||
kanal: {
|
||||
fetchLatest: fetchLatestKanal,
|
||||
fetchScene,
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user