| const axios = require('axios'); | |
| const cheerio = require('cheerio'); | |
| const { shannz: cf } = require('bycf'); | |
| async function getTurnstileToken() { | |
| try { | |
| const token = await cf.turnstileMin( | |
| "https://claptools.com/tiktok-profile-viewer/", | |
| "0x4AAAAAAA0aU62HwPRV0j1U" | |
| ); | |
| return token; | |
| } catch (error) { | |
| throw new Error("Gagal mendapatkan Turnstile token: " + error.message); | |
| } | |
| } | |
| async function getNonce() { | |
| const response = await axios.get('https://claptools.com/tiktok-profile-viewer/', { | |
| headers: { | |
| 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' | |
| } | |
| }); | |
| const $ = cheerio.load(response.data); | |
| const nonce = $('input[name="tt_nonce"]').val(); | |
| return nonce; | |
| } | |
| async function tiktokStalk(username) { | |
| const token = await getTurnstileToken(); | |
| const nonce = await getNonce(); | |
| const formData = new URLSearchParams(); | |
| formData.append('username', username); | |
| formData.append('tt_nonce', nonce); | |
| formData.append('cf-turnstile-response', token); | |
| const response = await axios.post('https://claptools.com/tiktok-profile-viewer/', formData, { | |
| headers: { | |
| 'Content-Type': 'application/x-www-form-urlencoded', | |
| 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', | |
| 'Referer': 'https://claptools.com/tiktok-profile-viewer/', | |
| 'Origin': 'https://claptools.com' | |
| } | |
| }); | |
| const $ = cheerio.load(response.data); | |
| const avatar = $('.profile img').attr('src'); | |
| const fullText = $('.profile-details h2').text().trim(); | |
| const bio = $('.profile-details p').text().trim(); | |
| const nameMatch = fullText.match(/^(.+?)\s*\(@(.+?)\)$/); | |
| const nickname = nameMatch ? nameMatch[1].trim() : ''; | |
| const usernameFromPage = nameMatch ? nameMatch[2].trim() : username; | |
| const followers = $('.stats .box').eq(0).find('.number').text().trim(); | |
| const likes = $('.stats .box').eq(1).find('.number').text().trim(); | |
| const videos = $('.stats .box').eq(2).find('.number').text().trim(); | |
| const videoList = []; | |
| $('.thumb').each((i, elem) => { | |
| const thumbnail = $(elem).find('img').attr('src'); | |
| if (thumbnail) { | |
| videoList.push({ | |
| thumbnail: thumbnail | |
| }); | |
| } | |
| }); | |
| return { | |
| username: usernameFromPage, | |
| nickname: nickname, | |
| bio: bio, | |
| avatar: avatar, | |
| stats: { | |
| followers: followers, | |
| likes: likes, | |
| videos: videos | |
| }, | |
| videoList: videoList | |
| }; | |
| } | |
| const handler = async (req, res) => { | |
| try { | |
| const { username } = req.query; | |
| if (!username) { | |
| return res.status(400).json({ | |
| success: false, | |
| error: 'Missing required parameter: username' | |
| }); | |
| } | |
| const usernameRegex = /^[a-zA-Z0-9._]{1,24}$/; | |
| if (!usernameRegex.test(username)) { | |
| return res.status(400).json({ | |
| success: false, | |
| error: 'Invalid username format' | |
| }); | |
| } | |
| const result = await tiktokStalk(username); | |
| res.json({ | |
| author: "Herza", | |
| success: true, | |
| data: result | |
| }); | |
| } catch (error) { | |
| res.status(500).json({ | |
| author: "Herza", | |
| success: false, | |
| error: error.message | |
| }); | |
| } | |
| }; | |
| module.exports = { | |
| name: 'TikTok Stalk', | |
| description: 'Get TikTok user profile information including stats and avatar', | |
| type: 'GET', | |
| routes: ['api/stalk/tiktok'], | |
| tags: ['social', 'tiktok', 'stalk', 'profile'], | |
| parameters: ['username', 'key'], | |
| enabled: true, | |
| main: ['Stalk'], | |
| handler | |
| }; |