DiscoverProfile Scraper

JavaScriptPublic
by zx-apiAug 18, 2026, 12:54 PMExpires: Never164 views
DiscoverProfile Scraper
Raw
1/**
2 * DiscoverProfile Scraper
3 * Scraper untuk mencari profil media sosial berdasarkan username/nickname
4 * Sumber: https://whatsapp.com/channel/0029VbDLqe7EquiSF4STU13o/324
5 * Credit By Zx 
6 * Note! Kalau mau di sher harap jangan hapus credit atau sumber saluran ๐Ÿฅถ
7 */
8
9const axios = require('axios');
10
11class DiscoverProfile {
12  constructor(options = {}) {
13    this.baseURL = options.baseURL || 'https://api.discoverprofile.com';
14    this.timeout = options.timeout || 30000;
15    this.retries = options.retries || 3;
16    this.retryDelay = options.retryDelay || 1000;
17    
18    this.client = axios.create({
19      baseURL: this.baseURL,
20      timeout: this.timeout,
21      headers: {
22        'Accept': 'application/json, text/plain, */*',
23        'Content-Type': 'application/json',
24        'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Mobile Safari/537.36',
25        'Referer': 'https://discoverprofile.com/',
26        'Origin': 'https://discoverprofile.com',
27        ...options.headers
28      }
29    });
30
31    // Cache untuk menyimpan hasil pencarian
32    this._cache = new Map();
33    this._cacheTTL = options.cacheTTL || 5 * 60 * 1000; // 5 menit default
34  }
35
36  /**
37   * Cari profil berdasarkan username/nickname
38   * @param {string} source - Username/nickname yang dicari
39   * @param {Object} [options={}] - Opsi pencarian
40   * @param {string} [options.type='name'] - Tipe pencarian: 'name' atau 'email'
41   * @param {boolean} [options.rescan=false] - Force rescan
42   * @returns {Promise<Object>} Hasil pencarian
43   */
44  async search(source, options = {}) {
45    const {
46      type = 'name',
47      rescan = false
48    } = options;
49
50    if (!source || typeof source !== 'string') {
51      throw new Error('Parameter "source" harus berupa string yang valid');
52    }
53
54    const cacheKey = `${source}:${type}:${rescan}`;
55    
56    // Cek cache jika bukan rescan
57    if (!rescan && this._cache.has(cacheKey)) {
58      const cached = this._cache.get(cacheKey);
59      if (Date.now() - cached.timestamp < this._cacheTTL) {
60        return cached.data;
61      }
62      this._cache.delete(cacheKey);
63    }
64
65    const payload = {
66      source: source.trim(),
67      type,
68      rescan
69    };
70
71    const result = await this._requestWithRetry('/discoverprofile', 'POST', payload);
72
73    // Simpan ke cache
74    this._cache.set(cacheKey, {
75      data: result,
76      timestamp: Date.now()
77    });
78
79    return result;
80  }
81
82  /**
83   * Cari profil berdasarkan username (alias untuk search dengan type='name')
84   * @param {string} username - Username yang dicari
85   * @returns {Promise<Object>}
86   */
87  async searchByUsername(username) {
88    return this.search(username, { type: 'name' });
89  }
90
91  /**
92   * Cari profil berdasarkan email
93   * @param {string} email - Email yang dicari
94   * @returns {Promise<Object>}
95   */
96  async searchByEmail(email) {
97    return this.search(email, { type: 'email' });
98  }
99
100  /**
101   * Filter hasil berdasarkan kategori
102   * @param {Object} searchResult - Hasil dari search()
103   * @param {string} category - Kategori yang difilter
104   * @returns {Array} Daftar profil sesuai kategori
105   */
106  filterByCategory(searchResult, category) {
107    if (!searchResult?.result) return [];
108    return searchResult.result.filter(
109      item => item.category?.toLowerCase() === category.toLowerCase()
110    );
111  }
112
113  /**
114   * Filter hasil hanya yang eksis (isExist = true)
115   * @param {Object} searchResult - Hasil dari search()
116   * @returns {Array} Daftar profil yang ditemukan
117   */
118  filterExisting(searchResult) {
119    if (!searchResult?.result) return [];
120    return searchResult.result.filter(item => item.isExist === true);
121  }
122
123  /**
124   * Filter hasil yang tidak eksis
125   * @param {Object} searchResult - Hasil dari search()
126   * @returns {Array} Daftar profil yang tidak ditemukan
127   */
128  filterNonExisting(searchResult) {
129    if (!searchResult?.result) return [];
130    return searchResult.result.filter(item => item.isExist === false);
131  }
132
133  /**
134   * Dapatkan semua kategori unik dari hasil
135   * @param {Object} searchResult - Hasil dari search()
136   * @returns {Array<string>} Daftar kategori unik
137   */
138  getCategories(searchResult) {
139    if (!searchResult?.result) return [];
140    const categories = new Set(
141      searchResult.result.map(item => item.category).filter(Boolean)
142    );
143    return [...categories].sort();
144  }
145
146  /**
147   * Dapatkan ringkasan statistik hasil pencarian
148   * @param {Object} searchResult - Hasil dari search()
149   * @returns {Object} Statistik
150   */
151  getStats(searchResult) {
152    const results = searchResult?.result || [];
153    const existing = results.filter(r => r.isExist);
154    const categories = this.getCategories(searchResult);
155    
156    const categoryStats = {};
157    for (const cat of categories) {
158      const catItems = results.filter(r => r.category === cat);
159      const catExisting = catItems.filter(r => r.isExist);
160      categoryStats[cat] = {
161        total: catItems.length,
162        found: catExisting.length,
163        notFound: catItems.length - catExisting.length
164      };
165    }
166
167    return {
168      totalChecked: results.length,
169      totalFound: existing.length,
170      totalNotFound: results.length - existing.length,
171      totalCategories: categories.length,
172      categories: categoryStats,
173      createdAt: searchResult?.createdAt || null
174    };
175  }
176
177  /**
178   * Dapatkan semua platform yang tersedia dari hasil
179   * @param {Object} searchResult - Hasil dari search()
180   * @returns {Array<string>} Daftar nama platform
181   */
182  getPlatforms(searchResult) {
183    if (!searchResult?.result) return [];
184    return searchResult.result.map(item => item.source).filter(Boolean);
185  }
186
187  /**
188   * Cari di platform tertentu dari hasil
189   * @param {Object} searchResult - Hasil dari search()
190   * @param {string} platformName - Nama platform (case-insensitive)
191   * @returns {Object|null} Profil di platform tersebut
192   */
193  findByPlatform(searchResult, platformName) {
194    if (!searchResult?.result) return null;
195    return searchResult.result.find(
196      item => item.source?.toLowerCase() === platformName.toLowerCase()
197    ) || null;
198  }
199
200  /**
201   * Batch search beberapa username sekaligus
202   * @param {Array<string>} usernames - Daftar username
203   * @param {Object} [options={}] - Opsi
204   * @param {number} [options.concurrency=3] - Jumlah request paralel
205   * @param {number} [options.delay=500] - Delay antar batch dalam ms
206   * @returns {Promise<Object>} Map username -> hasil
207   */
208  async batchSearch(usernames, options = {}) {
209    const { concurrency = 3, delay = 500 } = options;
210    const results = {};
211
212    for (let i = 0; i < usernames.length; i += concurrency) {
213      const batch = usernames.slice(i, i + concurrency);
214      
215      const batchPromises = batch.map(async (username) => {
216        try {
217          const result = await this.search(username);
218          results[username] = { success: true, data: result };
219        } catch (error) {
220          results[username] = { success: false, error: error.message };
221        }
222      });
223
224      await Promise.all(batchPromises);
225
226      if (i + concurrency < usernames.length) {
227        await this._sleep(delay);
228      }
229    }
230
231    return results;
232  }
233
234  clearCache() {
235    this._cache.clear();
236  }
237
238  get cacheSize() {
239    return this._cache.size;
240  }
241
242  async _requestWithRetry(endpoint, method = 'GET', data = null, attempt = 1) {
243    try {
244      const config = { method, url: endpoint };
245      if (data && (method === 'POST' || method === 'PUT')) {
246        config.data = data;
247      }
248
249      const response = await this.client.request(config);
250      
251      if (response.data?.error) {
252        throw new Error(`API Error: ${response.data.error}`);
253      }
254
255      return response.data;
256    } catch (error) {
257      if (attempt < this.retries) {
258        const delay = this.retryDelay * attempt;
259        await this._sleep(delay);
260        return this._requestWithRetry(endpoint, method, data, attempt + 1);
261      }
262      
263      throw new Error(
264        `Request gagal setelah ${this.retries} percobaan: ${error.message}`
265      );
266    }
267  }
268
269  _sleep(ms) {
270    return new Promise(resolve => setTimeout(resolve, ms));
271  }
272
273  _escapeCSV(value) {
274    if (typeof value !== 'string') return String(value || '');
275    if (value.includes(',') || value.includes('"') || value.includes('\n')) {
276      return `"${value.replace(/"/g, '""')}"`;
277    }
278    return value;
279  }
280}
281
282// ==================== EXPORT ====================
283module.exports = DiscoverProfile;
284
285// ==================== CONTOH PENGGUNAAN ====================
286if (require.main === module) {
287  (async () => {
288    const scraper = new DiscoverProfile({
289      timeout: 30000,
290      retries: 3,
291      retryDelay: 1000
292    });
293
294    try {
295      // Cari username
296      console.log('๐Ÿ” Mencari username "jokowi"...');
297      const result = await scraper.searchByUsername('jokowi');
298
299      // Statistik
300      const stats = scraper.getStats(result);
301      console.log('\n๐Ÿ“Š Statistik:');
302      console.log(`   Total platform dicek: ${stats.totalChecked}`);
303      console.log(`   Ditemukan: ${stats.totalFound}`);
304      console.log(`   Tidak ditemukan: ${stats.totalNotFound}`);
305      console.log(`   Total kategori: ${stats.totalCategories}`);
306
307      // Hanya yang eksis
308      const existing = scraper.filterExisting(result);
309      console.log(`\nโœ… Profil yang ditemukan (${existing.length}):`);
310      existing.forEach(item => {
311        console.log(`   โ€ข ${item.source}: ${item.url} [${item.category}]`);
312      });
313
314      // Filter per kategori
315      const socialMedia = scraper.filterByCategory(result, 'Social network');
316      console.log(`\n๐ŸŒ Social Networks (${socialMedia.length}):`);
317      socialMedia.forEach(item => {
318        const status = item.isExist ? 'โœ…' : 'โŒ';
319        console.log(`   ${status} ${item.source}: ${item.url}`);
320      });
321
322      // Cari platform spesifik
323      const instagram = scraper.findByPlatform(result, 'Instagram');
324      if (instagram) {
325        console.log(`\n๐Ÿ“ท Instagram: ${instagram.url} (${instagram.isExist ? 'Found' : 'Not Found'})`);
326      }
327
328    } catch (error) {
329      console.error('โŒ Error:', error.message);
330    }
331  })();
332}
332 linesยท10,160 charsยท9.9 KB