123 lines
4.2 KiB
JavaScript
123 lines
4.2 KiB
JavaScript
class Tenor {
|
|
baseUrl = "https://tenor.com";
|
|
|
|
constructor(fetchPath, cheerioPath, browser) {
|
|
this.browser = browser;
|
|
this.type = "image-board";
|
|
|
|
this.lastQuery = null;
|
|
this.seenIds = new Set();
|
|
}
|
|
|
|
async fetchSearchResult(query = "hello", page = 1, perPage = 48) {
|
|
if (query !== this.lastQuery) {
|
|
this.lastQuery = query;
|
|
this.seenIds.clear();
|
|
}
|
|
|
|
const url = `${this.baseUrl}/search/${query.replace(" ", "-")}-gifs`;
|
|
|
|
const data = await this.browser.scrape(
|
|
url,
|
|
() => {
|
|
// Fallback selectors: try specific class first, then generic figure
|
|
const items = document.querySelectorAll('div.GifList figure, figure');
|
|
const results = [];
|
|
|
|
items.forEach(fig => {
|
|
const link = fig.querySelector('a');
|
|
const img = fig.querySelector('img');
|
|
if (!link || !img) return;
|
|
|
|
const href = link.getAttribute('href') || "";
|
|
|
|
let idMatch = href.match(/-(\d+)(?:$|\/?$)/);
|
|
const id = idMatch ? idMatch[1] : null;
|
|
|
|
// Tenor lazy loads images, so we check 'src' AND 'data-src'
|
|
const imgUrl = img.getAttribute('src') || img.getAttribute('data-src');
|
|
const tagsRaw = img.getAttribute('alt') || "";
|
|
const tags = tagsRaw.trim().split(/\s+/).filter(Boolean);
|
|
|
|
if (id && imgUrl && !imgUrl.includes('placeholder')) {
|
|
results.push({
|
|
id,
|
|
image: imgUrl,
|
|
sampleImageUrl: imgUrl,
|
|
tags,
|
|
type: "preview"
|
|
});
|
|
}
|
|
});
|
|
|
|
const uniqueResults = Array.from(new Map(results.map(item => [item.id, item])).values());
|
|
|
|
return { results: uniqueResults, hasNextPage: true };
|
|
},
|
|
{
|
|
waitSelector: 'figure',
|
|
timeout: 30000,
|
|
scrollToBottom: true,
|
|
renderWaitTime: 3000,
|
|
loadImages: true
|
|
}
|
|
);
|
|
|
|
const newResults = data.results.filter(item => !this.seenIds.has(item.id));
|
|
|
|
newResults.forEach(item => this.seenIds.add(item.id));
|
|
|
|
return {
|
|
results: newResults,
|
|
hasNextPage: data.hasNextPage,
|
|
page
|
|
};
|
|
}
|
|
|
|
async fetchInfo(id) {
|
|
const url = `${this.baseUrl}/view/gif-${id}`;
|
|
|
|
const data = await this.browser.scrape(
|
|
url,
|
|
() => {
|
|
const img = document.querySelector('img[alt]');
|
|
const fullImage = img?.src || null;
|
|
|
|
const tags = [...document.querySelectorAll('.tag-list li a .RelatedTag')]
|
|
.map(tag => tag.textContent.trim())
|
|
.filter(Boolean);
|
|
|
|
let createdAt = Date.now();
|
|
|
|
const detailNodes = [...document.querySelectorAll('.gif-details dd')];
|
|
const createdNode = detailNodes.find(n => n.textContent.includes("Created:"));
|
|
|
|
if (createdNode) {
|
|
const raw = createdNode.textContent.replace("Created:", "").trim();
|
|
const parts = raw.split(/[\/,: ]+/);
|
|
if (parts.length >= 6) {
|
|
let [dd, mm, yyyy, hh, min, ss] = parts.map(p => parseInt(p, 10));
|
|
createdAt = new Date(yyyy, mm - 1, dd, hh, min, ss).getTime();
|
|
}
|
|
}
|
|
|
|
return {
|
|
fullImage,
|
|
tags,
|
|
createdAt
|
|
};
|
|
},
|
|
{ waitSelector: 'img[alt]', timeout: 15000 }
|
|
);
|
|
|
|
return {
|
|
id,
|
|
fullImage: data.fullImage,
|
|
tags: data.tags,
|
|
createdAt: data.createdAt,
|
|
rating: "Unknown"
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = { Tenor }; |