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 };