Back to Home
Gistify Snippet
Public
TikTok Media Scraper & Downloader TikWM API
Mengimplementasikan klien API TikWM untuk mengunduh video TikTok tanpa watermark, mengekstrak metadata, gambar multi-slide, dan audio latar. Mendukung pencarian video dan feed trending global.
#tiktok#scraper#downloader#tikwm#nodejs#api-client
4
Rawtikwm.jsjavascript
198 lines| 1 | /** |
| 2 | * @fileoverview Modul Downloader & Scraper TikTok Tanpa Watermark (TikWM API). |
| 3 | * |
| 4 | * Mengimplementasikan antarmuka klien API TikWM untuk mengekstrak metadata, video tanpa watermark (HD/SD), |
| 5 | * format gambar multi-slide, serta audio latar (MP3) dari video TikTok. |
| 6 | * Didukung fitur pencarian video berbasis kata kunci dan feed trending global. |
| 7 | * |
| 8 | * @module scrapers/tiktok |
| 9 | * @version 2.0.0 |
| 10 | * @author kyiov |
| 11 | * @license MIT |
| 12 | */ |
| 13 | |
| 14 | import { pathToFileURL } from 'node:url' |
| 15 | |
| 16 | /** |
| 17 | * @typedef {Object} TikTokAuthor |
| 18 | * @property {string} id - Pengenal unik pembuat konten |
| 19 | * @property {string} unique_id - Username handle TikTok (@username) |
| 20 | * @property {string} nickname - Nama tampilan profil |
| 21 | * @property {string} avatar - Tautan foto profil |
| 22 | */ |
| 23 | |
| 24 | /** |
| 25 | * @typedef {Object} TikTokMedia |
| 26 | * @property {'video'|'image'} type - Jenis media konten |
| 27 | * @property {number} [duration] - Durasi video dalam detik |
| 28 | * @property {string} [play] - URL unduhan video tanpa watermark |
| 29 | * @property {string} [hdplay] - URL unduhan video kualitas HD tanpa watermark |
| 30 | * @property {string} [wmplay] - URL unduhan video dengan watermark asli |
| 31 | * @property {string[]} [images] - Daftar tautan gambar untuk post slide |
| 32 | */ |
| 33 | |
| 34 | /** |
| 35 | * @typedef {Object} TikTokDownloadResult |
| 36 | * @property {string} id - ID unik video TikTok |
| 37 | * @property {string} title - Judul atau takarir (caption) video |
| 38 | * @property {string} cover - URL gambar sampul (thumbnail) |
| 39 | * @property {TikTokMedia} media - Objek aset media (video/gambar) |
| 40 | * @property {TikTokAuthor} author - Informasi pembuat konten |
| 41 | * @property {Object} music - Metadata musik/audio latar |
| 42 | * @property {number} views - Jumlah penayangan |
| 43 | * @property {number} likes - Jumlah suka |
| 44 | * @property {number} comments - Jumlah komentar |
| 45 | * @property {number} shares - Jumlah pembagian |
| 46 | */ |
| 47 | |
| 48 | export class TikWMScraper { |
| 49 | /** |
| 50 | * Menginisialisasi klien TikWM dengan base URL dan konfigurasi header default. |
| 51 | */ |
| 52 | constructor() { |
| 53 | this.baseUrl = 'https://tikwm.com' |
| 54 | this.headers = { |
| 55 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', |
| 56 | 'User-Agent': |
| 57 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', |
| 58 | 'Accept': 'application/json, text/javascript, */*; q=0.01' |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | /** |
| 63 | * Mengunduh metadata video TikTok beserta tautan media tanpa watermark. |
| 64 | * |
| 65 | * @param {string} url - Tautan URL postingan TikTok lengkap atau format pendek (vt.tiktok.com) |
| 66 | * @returns {Promise<TikTokDownloadResult>} Metadata lengkap dan tautan download media |
| 67 | * @throws {Error} Jika URL tidak valid atau video gagal diekstrak |
| 68 | */ |
| 69 | async download(url) { |
| 70 | if (!url) throw new Error('Parameter URL video TikTok wajib disertakan.') |
| 71 | |
| 72 | const params = new URLSearchParams({ url, hd: '1' }) |
| 73 | const res = await fetch(`${this.baseUrl}/api/`, { |
| 74 | method: 'POST', |
| 75 | headers: this.headers, |
| 76 | body: params |
| 77 | }) |
| 78 | |
| 79 | if (!res.ok) throw new Error(`HTTP Error: ${res.status}`) |
| 80 | const json = await res.json() |
| 81 | |
| 82 | if (json.code !== 0 || !json.data) { |
| 83 | throw new Error(json.msg || 'Gagal mengekstrak data video dari URL tersebut.') |
| 84 | } |
| 85 | |
| 86 | const data = json.data |
| 87 | const isImage = data.duration === 0 && Array.isArray(data.images) && data.images.length > 0 |
| 88 | |
| 89 | return { |
| 90 | id: data.id, |
| 91 | title: data.title || '', |
| 92 | cover: data.cover || '', |
| 93 | media: isImage |
| 94 | ? { |
| 95 | type: 'image', |
| 96 | images: data.images, |
| 97 | image_count: data.images.length |
| 98 | } |
| 99 | : { |
| 100 | type: 'video', |
| 101 | duration: data.duration, |
| 102 | play: data.play, |
| 103 | hdplay: data.hdplay || data.play, |
| 104 | wmplay: data.wmplay, |
| 105 | size: data.size, |
| 106 | hd_size: data.hd_size |
| 107 | }, |
| 108 | author: { |
| 109 | id: data.author?.id, |
| 110 | unique_id: data.author?.unique_id, |
| 111 | nickname: data.author?.nickname, |
| 112 | avatar: data.author?.avatar |
| 113 | }, |
| 114 | music: data.music_info || {}, |
| 115 | views: data.play_count || 0, |
| 116 | likes: data.digg_count || 0, |
| 117 | comments: data.comment_count || 0, |
| 118 | shares: data.share_count || 0, |
| 119 | created_at: data.create_time |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | /** |
| 124 | * Mencari video TikTok berdasarkan kata kunci tertentu. |
| 125 | * |
| 126 | * @param {string} keywords - Kata kunci penelusuran video |
| 127 | * @param {number} [count=10] - Jumlah video maksimum yang ingin diambil |
| 128 | * @returns {Promise<Array<Object>>} Daftar video yang cocok dengan kata kunci |
| 129 | * @throws {Error} Jika kata kunci kosong atau penelusuran gagal |
| 130 | */ |
| 131 | async search(keywords, count = 10) { |
| 132 | if (!keywords) throw new Error('Kata kunci pencarian wajib disertakan.') |
| 133 | |
| 134 | const params = new URLSearchParams({ |
| 135 | keywords, |
| 136 | count: String(count), |
| 137 | cursor: '0', |
| 138 | hd: '1' |
| 139 | }) |
| 140 | |
| 141 | const res = await fetch(`${this.baseUrl}/api/feed/search/`, { |
| 142 | method: 'POST', |
| 143 | headers: this.headers, |
| 144 | body: params |
| 145 | }) |
| 146 | |
| 147 | if (!res.ok) throw new Error(`HTTP Error: ${res.status}`) |
| 148 | const json = await res.json() |
| 149 | |
| 150 | if (json.code !== 0 || !json.data?.videos) { |
| 151 | throw new Error(json.msg || 'Tidak ditemukan video yang sesuai dengan kata kunci.') |
| 152 | } |
| 153 | |
| 154 | return json.data.videos.map((item) => ({ |
| 155 | id: item.video_id, |
| 156 | title: item.title, |
| 157 | cover: item.cover, |
| 158 | play: item.play, |
| 159 | duration: item.duration, |
| 160 | author: { |
| 161 | id: item.author?.id, |
| 162 | unique_id: item.author?.unique_id, |
| 163 | nickname: item.author?.nickname, |
| 164 | avatar: item.author?.avatar |
| 165 | }, |
| 166 | views: item.play_count, |
| 167 | likes: item.digg_count |
| 168 | })) |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | export const tikwm = new TikWMScraper() |
| 173 | export default tikwm |
| 174 | |
| 175 | // Eksekusi mandiri untuk pengujian langsung tanpa argumen CLI |
| 176 | if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { |
| 177 | try { |
| 178 | console.log('Menguji pencarian video TikTok...') |
| 179 | const searchResults = await tikwm.search('nature', 2) |
| 180 | console.log('Hasil Pencarian:', JSON.stringify(searchResults, null, 2)) |
| 181 | |
| 182 | if (searchResults.length > 0) { |
| 183 | const first = searchResults[0] |
| 184 | const videoUrl = `https://www.tiktok.com/@${first.author.unique_id}/video/${first.id}` |
| 185 | console.log(`Menguji ekstraksi video langsung: ${videoUrl}`) |
| 186 | const downloadData = await tikwm.download(videoUrl) |
| 187 | console.log('Hasil Ekstraksi Video:', { |
| 188 | id: downloadData.id, |
| 189 | title: downloadData.title, |
| 190 | play: downloadData.media.play, |
| 191 | author: downloadData.author.unique_id |
| 192 | }) |
| 193 | } |
| 194 | } catch (err) { |
| 195 | console.error('Galat eksekusi TikWM:', err.message || err) |
| 196 | } |
| 197 | } |
| 198 | |
Discussion & Comments0
No comments yet — be the first to leave feedback or start a code discussion.
Sign in with your GitHub account to write comments and join the discussion.