const axios = require('axios'); const FormData = require('form-data'); const crypto = require('crypto'); // SCRAPE BY SIPUTZX class PinterestLensScraper { constructor(authToken) { this.authToken = authToken; } getRandomHeaders() { const devices = [ { model: 'SM-G991B', manufacturer: 'samsung', name: 'Samsung Galaxy S21' }, { model: 'SM-A525F', manufacturer: 'samsung', name: 'Samsung Galaxy A52' }, { model: 'Pixel 6', manufacturer: 'Google', name: 'Google Pixel 6' }, { model: 'Pixel 7 Pro', manufacturer: 'Google', name: 'Google Pixel 7 Pro' }, { model: 'M2101K6G', manufacturer: 'Xiaomi', name: 'Xiaomi Redmi Note 10' }, { model: '2201117TG', manufacturer: 'Xiaomi', name: 'Xiaomi 11T' }, { model: 'CPH2121', manufacturer: 'OPPO', name: 'OPPO Reno5' }, { model: 'RMX3085', manufacturer: 'realme', name: 'realme 8 Pro' }, { model: 'itel S665L', manufacturer: 'ITEL', name: 'itel S665L' }, { model: 'TECNO KE5', manufacturer: 'TECNO', name: 'TECNO Spark 7' } ]; const versions = ['13.36.2', '13.35.0', '13.34.1', '13.33.0', '13.32.1']; const androidVersions = ['11', '12', '13']; const device = devices[Math.floor(Math.random() * devices.length)]; const version = versions[Math.floor(Math.random() * versions.length)]; const androidVer = androidVersions[Math.floor(Math.random() * androidVersions.length)]; const advertisingId = crypto.randomUUID(); const hardwareId = crypto.randomBytes(8).toString('hex'); const installId = crypto.randomBytes(16).toString('hex'); return { 'User-Agent': `Pinterest for Android/${version} (${device.model}; ${androidVer})`, 'accept-language': 'id-ID', 'x-pinterest-advertising-id': advertisingId, 'x-pinterest-app-type-detailed': '3', 'x-pinterest-device': device.model, 'x-pinterest-device-hardwareid': hardwareId, 'x-pinterest-device-manufacturer': device.manufacturer, 'x-pinterest-installid': installId, 'x-pinterest-webview-supported': 'false', 'x-pinterest-appstate': 'active', 'x-node-id': 'true', 'authorization': `Bearer ${this.authToken}` }; } async searchByImage(imageUrl, pageSize = 12) { const data = new FormData(); data.append('camera_type', '0'); data.append('source_type', '1'); data.append('video_autoplay_disabled', '0'); data.append('fields', this.getFields()); data.append('page_size', pageSize.toString()); data.append('image_url', imageUrl); const headers = this.getRandomHeaders(); const response = await axios.post( 'https://api.pinterest.com/v3/visual_search/lens/search/', data, { headers: { ...headers, ...data.getHeaders() } } ); return this.parseResults(response.data); } async getMoreResults(bookmark, url, pageSize = 12) { const params = new URLSearchParams({ bookmark, camera_type: '0', source_type: '1', video_autoplay_disabled: '0', fields: this.getFields(), url, page_size: pageSize.toString(), view_type: '119', view_parameter: '3064' }); const headers = this.getRandomHeaders(); const response = await axios.get( `https://api.pinterest.com/v3/visual_search/lens/search/?${params}`, { headers } ); return this.parseResults(response.data); } async scrapeAll(imageUrl, maxPages = 5) { const allResults = []; const firstPage = await this.searchByImage(imageUrl); allResults.push(...firstPage.pins); let bookmark = firstPage.bookmark; let url = firstPage.url; let page = 2; while (bookmark && page <= maxPages) { const nextPage = await this.getMoreResults(bookmark, url); allResults.push(...nextPage.pins); bookmark = nextPage.bookmark; page++; await this.delay(100); } return { total: allResults.length, pins: allResults, visualObjects: firstPage.visualObjects, searchIdentifier: firstPage.searchIdentifier }; } parseResults(response) { const pins = response.data.map(pin => ({ id: pin.id, title: pin.title || '', description: pin.description || '', imageUrl: pin.images?.['736x']?.url || pin.images?.originals?.url, thumbnailUrl: pin.images?.['236x']?.url, dominantColor: pin.dominant_color, creator: { id: pin.pinner?.id, username: pin.pinner?.username, fullName: pin.pinner?.full_name, imageUrl: pin.pinner?.image_medium_url }, board: { id: pin.board?.id, name: pin.board?.name, url: pin.board?.url }, stats: { saves: pin.aggregated_pin_data?.aggregated_stats?.saves || 0, comments: pin.comment_count || 0 }, createdAt: pin.created_at, isVideo: pin.is_video || false, link: pin.link, domain: pin.domain })); return { pins, bookmark: response.bookmark, url: response.url, visualObjects: response.visual_objects, searchIdentifier: response.search_identifier }; } getFields() { return 'pin.{id,title,description,images[736x,236x],dominant_color,pinner(),board(),aggregated_pin_data(),comment_count,created_at,is_video,link,domain},user.{id,username,full_name,image_medium_url},board.{id,name,url},aggregatedpindata.{aggregated_stats}'; } delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } const handler = async (req, res) => { try { const { imageUrl, token, maxPages = 5, key } = req.query; if (!imageUrl) { return res.status(400).json({ success: false, error: 'Missing required parameter: imageUrl' }); } if (!token) { return res.status(400).json({ success: false, error: 'Missing required parameter: token' }); } const scraper = new PinterestLensScraper(token); const results = await scraper.scrapeAll(imageUrl, parseInt(maxPages)); res.json({ author: 'siputzx', success: true, data: results }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } }; module.exports = { name: 'Pinterest Lens Scraper', description: 'Scrape Pinterest pins using image search with lens', type: 'GET', routes: ['api/tools/pinlens'], tags: ['tools', 'pinterest', 'image-search'], main: ['tools', 'Search'], parameters: ['imageUrl', 'token', 'maxPages', 'key'], enabled: true, limit: 10, handler };