HerzaJ commited on
Commit
e49ea3c
·
verified ·
1 Parent(s): 558ea6b

Create pinlens.js

Browse files
Files changed (1) hide show
  1. plugins/pinlens.js +210 -0
plugins/pinlens.js ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const axios = require('axios');
2
+ const FormData = require('form-data');
3
+ const crypto = require('crypto');
4
+ // SCRAPE BY SIPUTZX
5
+ class PinterestLensScraper {
6
+ constructor(authToken) {
7
+ this.authToken = authToken;
8
+ }
9
+
10
+ getRandomHeaders() {
11
+ const devices = [
12
+ { model: 'SM-G991B', manufacturer: 'samsung', name: 'Samsung Galaxy S21' },
13
+ { model: 'SM-A525F', manufacturer: 'samsung', name: 'Samsung Galaxy A52' },
14
+ { model: 'Pixel 6', manufacturer: 'Google', name: 'Google Pixel 6' },
15
+ { model: 'Pixel 7 Pro', manufacturer: 'Google', name: 'Google Pixel 7 Pro' },
16
+ { model: 'M2101K6G', manufacturer: 'Xiaomi', name: 'Xiaomi Redmi Note 10' },
17
+ { model: '2201117TG', manufacturer: 'Xiaomi', name: 'Xiaomi 11T' },
18
+ { model: 'CPH2121', manufacturer: 'OPPO', name: 'OPPO Reno5' },
19
+ { model: 'RMX3085', manufacturer: 'realme', name: 'realme 8 Pro' },
20
+ { model: 'itel S665L', manufacturer: 'ITEL', name: 'itel S665L' },
21
+ { model: 'TECNO KE5', manufacturer: 'TECNO', name: 'TECNO Spark 7' }
22
+ ];
23
+
24
+ const versions = ['13.36.2', '13.35.0', '13.34.1', '13.33.0', '13.32.1'];
25
+ const androidVersions = ['11', '12', '13'];
26
+
27
+ const device = devices[Math.floor(Math.random() * devices.length)];
28
+ const version = versions[Math.floor(Math.random() * versions.length)];
29
+ const androidVer = androidVersions[Math.floor(Math.random() * androidVersions.length)];
30
+ const advertisingId = crypto.randomUUID();
31
+ const hardwareId = crypto.randomBytes(8).toString('hex');
32
+ const installId = crypto.randomBytes(16).toString('hex');
33
+
34
+ return {
35
+ 'User-Agent': `Pinterest for Android/${version} (${device.model}; ${androidVer})`,
36
+ 'accept-language': 'id-ID',
37
+ 'x-pinterest-advertising-id': advertisingId,
38
+ 'x-pinterest-app-type-detailed': '3',
39
+ 'x-pinterest-device': device.model,
40
+ 'x-pinterest-device-hardwareid': hardwareId,
41
+ 'x-pinterest-device-manufacturer': device.manufacturer,
42
+ 'x-pinterest-installid': installId,
43
+ 'x-pinterest-webview-supported': 'false',
44
+ 'x-pinterest-appstate': 'active',
45
+ 'x-node-id': 'true',
46
+ 'authorization': `Bearer ${this.authToken}`
47
+ };
48
+ }
49
+
50
+ async searchByImage(imageUrl, pageSize = 12) {
51
+ const data = new FormData();
52
+ data.append('camera_type', '0');
53
+ data.append('source_type', '1');
54
+ data.append('video_autoplay_disabled', '0');
55
+ data.append('fields', this.getFields());
56
+ data.append('page_size', pageSize.toString());
57
+ data.append('image_url', imageUrl);
58
+
59
+ const headers = this.getRandomHeaders();
60
+ const response = await axios.post(
61
+ 'https://api.pinterest.com/v3/visual_search/lens/search/',
62
+ data,
63
+ { headers: { ...headers, ...data.getHeaders() } }
64
+ );
65
+
66
+ return this.parseResults(response.data);
67
+ }
68
+
69
+ async getMoreResults(bookmark, url, pageSize = 12) {
70
+ const params = new URLSearchParams({
71
+ bookmark,
72
+ camera_type: '0',
73
+ source_type: '1',
74
+ video_autoplay_disabled: '0',
75
+ fields: this.getFields(),
76
+ url,
77
+ page_size: pageSize.toString(),
78
+ view_type: '119',
79
+ view_parameter: '3064'
80
+ });
81
+
82
+ const headers = this.getRandomHeaders();
83
+ const response = await axios.get(
84
+ `https://api.pinterest.com/v3/visual_search/lens/search/?${params}`,
85
+ { headers }
86
+ );
87
+
88
+ return this.parseResults(response.data);
89
+ }
90
+
91
+ async scrapeAll(imageUrl, maxPages = 5) {
92
+ const allResults = [];
93
+
94
+ const firstPage = await this.searchByImage(imageUrl);
95
+ allResults.push(...firstPage.pins);
96
+
97
+ let bookmark = firstPage.bookmark;
98
+ let url = firstPage.url;
99
+ let page = 2;
100
+
101
+ while (bookmark && page <= maxPages) {
102
+ const nextPage = await this.getMoreResults(bookmark, url);
103
+ allResults.push(...nextPage.pins);
104
+ bookmark = nextPage.bookmark;
105
+ page++;
106
+ await this.delay(100);
107
+ }
108
+
109
+ return {
110
+ total: allResults.length,
111
+ pins: allResults,
112
+ visualObjects: firstPage.visualObjects,
113
+ searchIdentifier: firstPage.searchIdentifier
114
+ };
115
+ }
116
+
117
+ parseResults(response) {
118
+ const pins = response.data.map(pin => ({
119
+ id: pin.id,
120
+ title: pin.title || '',
121
+ description: pin.description || '',
122
+ imageUrl: pin.images?.['736x']?.url || pin.images?.originals?.url,
123
+ thumbnailUrl: pin.images?.['236x']?.url,
124
+ dominantColor: pin.dominant_color,
125
+ creator: {
126
+ id: pin.pinner?.id,
127
+ username: pin.pinner?.username,
128
+ fullName: pin.pinner?.full_name,
129
+ imageUrl: pin.pinner?.image_medium_url
130
+ },
131
+ board: {
132
+ id: pin.board?.id,
133
+ name: pin.board?.name,
134
+ url: pin.board?.url
135
+ },
136
+ stats: {
137
+ saves: pin.aggregated_pin_data?.aggregated_stats?.saves || 0,
138
+ comments: pin.comment_count || 0
139
+ },
140
+ createdAt: pin.created_at,
141
+ isVideo: pin.is_video || false,
142
+ link: pin.link,
143
+ domain: pin.domain
144
+ }));
145
+
146
+ return {
147
+ pins,
148
+ bookmark: response.bookmark,
149
+ url: response.url,
150
+ visualObjects: response.visual_objects,
151
+ searchIdentifier: response.search_identifier
152
+ };
153
+ }
154
+
155
+ getFields() {
156
+ 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}';
157
+ }
158
+
159
+ delay(ms) {
160
+ return new Promise(resolve => setTimeout(resolve, ms));
161
+ }
162
+ }
163
+
164
+ const handler = async (req, res) => {
165
+ try {
166
+ const { imageUrl, token, maxPages = 5, key } = req.query;
167
+
168
+ if (!imageUrl) {
169
+ return res.status(400).json({
170
+ success: false,
171
+ error: 'Missing required parameter: imageUrl'
172
+ });
173
+ }
174
+
175
+ if (!token) {
176
+ return res.status(400).json({
177
+ success: false,
178
+ error: 'Missing required parameter: token'
179
+ });
180
+ }
181
+
182
+ const scraper = new PinterestLensScraper(token);
183
+ const results = await scraper.scrapeAll(imageUrl, parseInt(maxPages));
184
+
185
+ res.json({
186
+ author: 'siputzx',
187
+ success: true,
188
+ data: results
189
+ });
190
+
191
+ } catch (error) {
192
+ res.status(500).json({
193
+ success: false,
194
+ error: error.message
195
+ });
196
+ }
197
+ };
198
+
199
+ module.exports = {
200
+ name: 'Pinterest Lens Scraper',
201
+ description: 'Scrape Pinterest pins using image search with lens',
202
+ type: 'GET',
203
+ routes: ['api/tools/pinlens'],
204
+ tags: ['tools', 'pinterest', 'image-search'],
205
+ main: ['tools', 'Search'],
206
+ parameters: ['imageUrl', 'token', 'maxPages', 'key'],
207
+ enabled: true,
208
+ limit: 10,
209
+ handler
210
+ };