Back to Home
Gistify Snippet
Public
NontonAnimeID Scraper with Dynamic Nonce Extraction
Mengimplementasikan web scraper untuk NontonAnimeID menggunakan Cheerio, secara dinamis mengekstrak nonce dan URL AJAX dari skrip base64 untuk navigasi dan pengambilan data yang akurat. Memproses kartu anime untuk judul, tautan, dan gambar.
#javascript#scraper#cheerio#nodejs#anime#web-scraping
7
Rawnontonanime.jsjavascript
1121 lines| 1 | import * as cheerio from 'cheerio'; |
| 2 | import { pathToFileURL } from 'node:url'; |
| 3 | import dns from 'node:dns'; |
| 4 | |
| 5 | dns.setDefaultResultOrder('ipv4first'); |
| 6 | |
| 7 | class NontonAnimeIDScraper { |
| 8 | constructor(baseUrl = process.env.NONTONANIME_URL || 'https://nontonanimeid.my.id') { |
| 9 | this.baseUrl = baseUrl.replace(/\/$/, ''); |
| 10 | this.headers = { |
| 11 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', |
| 12 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', |
| 13 | 'Accept-Language': 'id,en-US;q=0.7,en;q=0.3' |
| 14 | }; |
| 15 | this.lastNonce = null; |
| 16 | this.lastAjaxUrl = null; |
| 17 | this.lastUrl = null; |
| 18 | } |
| 19 | |
| 20 | async _getSoup(url, params = {}) { |
| 21 | this.lastUrl = url; |
| 22 | let finalUrl = url; |
| 23 | if (Object.keys(params).length > 0) { |
| 24 | const q = new URLSearchParams(); |
| 25 | for (const [k, v] of Object.entries(params)) { |
| 26 | if (Array.isArray(v)) { |
| 27 | v.forEach(val => q.append(`${k}[]`, val)); |
| 28 | } else { |
| 29 | q.append(k, v); |
| 30 | } |
| 31 | } |
| 32 | finalUrl = `${url}?${q.toString()}`; |
| 33 | } |
| 34 | |
| 35 | try { |
| 36 | const response = await fetch(finalUrl, { |
| 37 | headers: { |
| 38 | ...this.headers, |
| 39 | 'Referer': this.baseUrl |
| 40 | }, |
| 41 | signal: AbortSignal.timeout(20000) |
| 42 | }); |
| 43 | if (!response.ok) { |
| 44 | throw new Error(`HTTP error! status: ${response.status}`); |
| 45 | } |
| 46 | const html = await response.text(); |
| 47 | const $ = cheerio.load(html); |
| 48 | this._extractNonceAndAjaxUrl($); |
| 49 | return $; |
| 50 | } catch (error) { |
| 51 | console.warn(`[NontonAnimeID] Fetch failed (${finalUrl}):`, error?.message || error); |
| 52 | return null; |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | _extractNonceAndAjaxUrl($) { |
| 57 | const scraper = this; |
| 58 | $('script').each((i, el) => { |
| 59 | const src = $(el).attr('src') || ''; |
| 60 | if (src.startsWith('data:text/javascript;base64,')) { |
| 61 | try { |
| 62 | const b64Data = src.split('base64,')[1]; |
| 63 | const decoded = Buffer.from(b64Data, 'base64').toString('utf-8'); |
| 64 | const nonceMatch = decoded.match(/"nonce"\s*:\s*"([^"]+)"/); |
| 65 | const urlMatch = decoded.match(/"url"\s*:\s*"([^"]+)"/); |
| 66 | if (nonceMatch) scraper.lastNonce = nonceMatch[1]; |
| 67 | if (urlMatch) scraper.lastAjaxUrl = urlMatch[1].replace(/\\/g, ''); |
| 68 | } catch (e) { |
| 69 | |
| 70 | } |
| 71 | } |
| 72 | }); |
| 73 | } |
| 74 | |
| 75 | _parseAnimeCard($, cardEl) { |
| 76 | const card = $(cardEl); |
| 77 | const aTag = card.is('a') ? card : card.find('a').first(); |
| 78 | const link = aTag.attr('href') || card.attr('href') || ''; |
| 79 | |
| 80 | const imgTag = card.find('img').first(); |
| 81 | const image = imgTag.attr('src') || imgTag.attr('data-src') || ''; |
| 82 | |
| 83 | let title = ''; |
| 84 | const titleTag = card.find('[class*="title"], .tt'); |
| 85 | if (aTag.attr('title')) { |
| 86 | title = aTag.attr('title'); |
| 87 | } else if (titleTag.length > 0) { |
| 88 | const span = titleTag.find('span'); |
| 89 | if (span.length > 0) { |
| 90 | title = span.attr('data-title-default') || span.text().trim(); |
| 91 | } else { |
| 92 | title = titleTag.text().trim(); |
| 93 | } |
| 94 | } else if (imgTag.length > 0) { |
| 95 | title = imgTag.attr('alt') || ''; |
| 96 | } |
| 97 | title = title.trim(); |
| 98 | |
| 99 | const ratingTag = card.find('.rating, .kotakscore, .as-rating'); |
| 100 | let rating = ''; |
| 101 | if (ratingTag.length > 0) { |
| 102 | rating = ratingTag.text().replace('⭐', '').trim(); |
| 103 | if (!rating && ratingTag.hasClass('kotakscore')) { |
| 104 | rating = ratingTag.text().replace(/\n/g, '').trim(); |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | const typeTag = card.find('.type, .as-type'); |
| 109 | const typeVal = typeTag.length > 0 ? typeTag.text().replace('📺', '').trim() : ''; |
| 110 | |
| 111 | const seasonTag = card.find('.season, .as-season'); |
| 112 | const season = seasonTag.length > 0 ? seasonTag.text().replace('📅', '').trim() : ''; |
| 113 | |
| 114 | const synopsisTag = card.find('.synopsis, .as-synopsis'); |
| 115 | const synopsis = synopsisTag.length > 0 ? synopsisTag.text().trim() : ''; |
| 116 | |
| 117 | let genres = []; |
| 118 | const genresContainer = card.find('[class*="genres"]'); |
| 119 | if (genresContainer.length > 0) { |
| 120 | genresContainer.find('.genre-tag, .genre-pill, .as-genre-tag').each((i, el) => { |
| 121 | genres.push($(el).text().trim()); |
| 122 | }); |
| 123 | } else { |
| 124 | card.find('.genre-tag, .genre-pill, .as-genre-tag').each((i, el) => { |
| 125 | genres.push($(el).text().trim()); |
| 126 | }); |
| 127 | } |
| 128 | |
| 129 | return { |
| 130 | title, |
| 131 | link, |
| 132 | image, |
| 133 | rating, |
| 134 | type: typeVal, |
| 135 | season, |
| 136 | synopsis, |
| 137 | genres |
| 138 | }; |
| 139 | } |
| 140 | |
| 141 | async getHome() { |
| 142 | const $ = await this._getSoup(this.baseUrl); |
| 143 | if (!$) return {}; |
| 144 | |
| 145 | const data = { |
| 146 | episode_terbaru: [], |
| 147 | series_terbaru_movie: [], |
| 148 | series_terbaru_tv: [], |
| 149 | popular_series_semua: [], |
| 150 | popular_genre: [], |
| 151 | top_rating_anime: [], |
| 152 | series_popular_summer: [] |
| 153 | }; |
| 154 | |
| 155 | |
| 156 | $('#postbaru article.animeseries').each((i, el) => { |
| 157 | const article = $(el); |
| 158 | const aTag = article.find('a'); |
| 159 | if (aTag.length > 0) { |
| 160 | const link = aTag.attr('href') || ''; |
| 161 | const imgTag = aTag.find('img'); |
| 162 | const image = imgTag.attr('src') || ''; |
| 163 | |
| 164 | const titleSpan = aTag.find('h3.title span'); |
| 165 | let title = titleSpan.length > 0 ? (titleSpan.attr('data-title-default') || titleSpan.text().trim()) : ''; |
| 166 | if (!title && imgTag.length > 0) { |
| 167 | title = imgTag.attr('alt') || ''; |
| 168 | } |
| 169 | title = title.trim(); |
| 170 | |
| 171 | const epSpan = aTag.find('span.types.episodes'); |
| 172 | const episode = epSpan.length > 0 ? epSpan.text().trim() : ''; |
| 173 | |
| 174 | const statusSpan = aTag.find('span.types.status'); |
| 175 | const status = statusSpan.length > 0 ? statusSpan.text().trim() : ''; |
| 176 | |
| 177 | data.episode_terbaru.push({ |
| 178 | title, |
| 179 | link, |
| 180 | image, |
| 181 | episode, |
| 182 | status |
| 183 | }); |
| 184 | } |
| 185 | }); |
| 186 | |
| 187 | const parseTabContent = (tabId) => { |
| 188 | const items = []; |
| 189 | $(`#${tabId} div.animeseries`).each((i, el) => { |
| 190 | const article = $(el); |
| 191 | const aTag = article.find('a'); |
| 192 | if (aTag.length > 0) { |
| 193 | const link = aTag.attr('href') || ''; |
| 194 | const imgTag = aTag.find('img'); |
| 195 | const image = imgTag.attr('src') || ''; |
| 196 | |
| 197 | const titleDiv = aTag.find('div.title'); |
| 198 | let title = ''; |
| 199 | if (titleDiv.length > 0) { |
| 200 | const titleSpan = titleDiv.find('span'); |
| 201 | title = titleSpan.length > 0 ? (titleSpan.attr('data-title-default') || titleSpan.text().trim()) : titleDiv.text().trim(); |
| 202 | } |
| 203 | if (!title && imgTag.length > 0) { |
| 204 | title = imgTag.attr('alt') || ''; |
| 205 | } |
| 206 | title = title.trim(); |
| 207 | |
| 208 | const scoreSpan = aTag.find('span.kotakscore'); |
| 209 | const score = scoreSpan.length > 0 ? scoreSpan.text().replace(/\n/g, '').replace(/ /g, '').replace('⭐', '').trim() : ''; |
| 210 | |
| 211 | items.push({ |
| 212 | title, |
| 213 | link, |
| 214 | image, |
| 215 | score |
| 216 | }); |
| 217 | } |
| 218 | }); |
| 219 | return items; |
| 220 | }; |
| 221 | |
| 222 | data.series_terbaru_movie = parseTabContent('tab-7'); |
| 223 | data.series_terbaru_tv = parseTabContent('tab-8'); |
| 224 | data.popular_series_semua = parseTabContent('tab-9'); |
| 225 | data.popular_genre = parseTabContent('tab-10'); |
| 226 | |
| 227 | |
| 228 | const sidebar = $('#sidebar_right').length > 0 ? $('#sidebar_right') : $('body'); |
| 229 | let topRatingHeader = null; |
| 230 | sidebar.find('h3, h2').each((i, el) => { |
| 231 | if ($(el).text().includes('Top Rating Anime')) { |
| 232 | topRatingHeader = $(el); |
| 233 | } |
| 234 | }); |
| 235 | if (topRatingHeader) { |
| 236 | const ul = $(topRatingHeader).nextAll('ul.latestepisodes').first(); |
| 237 | if (ul.length > 0) { |
| 238 | ul.find('li').each((i, el) => { |
| 239 | const li = $(el); |
| 240 | const aTag = li.find('a'); |
| 241 | if (aTag.length > 0) { |
| 242 | const link = aTag.attr('href') || ''; |
| 243 | const lefts = aTag.find('div.lefts'); |
| 244 | const rights = aTag.find('div.rights'); |
| 245 | |
| 246 | const title = lefts.length > 0 ? lefts.text().trim() : ''; |
| 247 | const videoNum = rights.length > 0 ? rights.find('span.video').text().trim() : ''; |
| 248 | |
| 249 | data.top_rating_anime.push({ |
| 250 | title, |
| 251 | link, |
| 252 | episodes_count: videoNum |
| 253 | }); |
| 254 | } |
| 255 | }); |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | |
| 260 | let popularSummerHeader = null; |
| 261 | sidebar.find('h3, h2').each((i, el) => { |
| 262 | if ($(el).text().includes('Series Popular Summer')) { |
| 263 | popularSummerHeader = $(el); |
| 264 | } |
| 265 | }); |
| 266 | if (popularSummerHeader) { |
| 267 | const kotakbatas = $(popularSummerHeader).nextAll('div.kotakbatas').first(); |
| 268 | if (kotakbatas.length > 0) { |
| 269 | kotakbatas.find('div.bor').each((i, el) => { |
| 270 | const aTag = $(el).find('a.popseries'); |
| 271 | if (aTag.length > 0) { |
| 272 | const link = aTag.attr('href') || ''; |
| 273 | const imgTag = aTag.find('img'); |
| 274 | const image = imgTag.attr('src') || ''; |
| 275 | const title = imgTag.attr('alt') || ''; |
| 276 | |
| 277 | data.series_popular_summer.push({ |
| 278 | title: title.trim(), |
| 279 | link, |
| 280 | image |
| 281 | }); |
| 282 | } |
| 283 | }); |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | return data; |
| 288 | } |
| 289 | |
| 290 | async getAnimeList(page = 1, filters = {}) { |
| 291 | const url = page > 1 ? `${this.baseUrl}/anime/page/${page}/` : `${this.baseUrl}/anime/`; |
| 292 | const $ = await this._getSoup(url, filters); |
| 293 | if (!$) return []; |
| 294 | |
| 295 | const results = []; |
| 296 | const gridContainer = $('div.result'); |
| 297 | if (gridContainer.length > 0) { |
| 298 | const cards = gridContainer.find('a.as-anime-card'); |
| 299 | if (cards.length > 0) { |
| 300 | const scraper = this; |
| 301 | cards.each((i, el) => { |
| 302 | results.push(scraper._parseAnimeCard($, el)); |
| 303 | }); |
| 304 | } else { |
| 305 | const listItems = gridContainer.find('div.animeseries'); |
| 306 | const scraper = this; |
| 307 | listItems.each((i, el) => { |
| 308 | const aTag = $(el).find('a'); |
| 309 | if (aTag.length > 0) { |
| 310 | results.push(scraper._parseAnimeCard($, aTag)); |
| 311 | } |
| 312 | }); |
| 313 | } |
| 314 | } |
| 315 | return results; |
| 316 | } |
| 317 | |
| 318 | async searchAnime(query, page = 1) { |
| 319 | const url = page > 1 ? `${this.baseUrl}/page/${page}/` : `${this.baseUrl}/`; |
| 320 | const params = { s: query }; |
| 321 | const $ = await this._getSoup(url, params); |
| 322 | if (!$) return []; |
| 323 | |
| 324 | const results = []; |
| 325 | const gridContainer = $('div.result'); |
| 326 | if (gridContainer.length > 0) { |
| 327 | const cards = gridContainer.find('a.as-anime-card'); |
| 328 | if (cards.length > 0) { |
| 329 | const scraper = this; |
| 330 | cards.each((i, el) => { |
| 331 | results.push(scraper._parseAnimeCard($, el)); |
| 332 | }); |
| 333 | } else { |
| 334 | const listItems = gridContainer.find('div.animeseries'); |
| 335 | const scraper = this; |
| 336 | listItems.each((i, el) => { |
| 337 | const aTag = $(el).find('a'); |
| 338 | if (aTag.length > 0) { |
| 339 | results.push(scraper._parseAnimeCard($, aTag)); |
| 340 | } |
| 341 | }); |
| 342 | } |
| 343 | } |
| 344 | |
| 345 | if (results.length === 0) { |
| 346 | $('article.bs').each((i, el) => { |
| 347 | const a = $(el).find('a.tip').first(); |
| 348 | const href = a.attr('href') || ''; |
| 349 | const title = (a.attr('title') || a.text().trim() || '').replace(/\s+/g, ' '); |
| 350 | if (!href || !title) return; |
| 351 | results.push({ |
| 352 | title, |
| 353 | link: href, |
| 354 | image: $(el).find('img').first().attr('src') || '', |
| 355 | rating: $(el).find('.rating').first().text().trim() || '', |
| 356 | type: $(el).find('.typez').first().text().trim() || '', |
| 357 | status: $(el).find('.epx, .status').first().text().trim() || '' |
| 358 | }); |
| 359 | }); |
| 360 | } |
| 361 | |
| 362 | return results; |
| 363 | } |
| 364 | |
| 365 | async getOngoingList(page = 1, sort = 'date') { |
| 366 | const url = page > 1 ? `${this.baseUrl}/ongoing-list/page/${page}/` : `${this.baseUrl}/ongoing-list/`; |
| 367 | const params = { sort, mode: 'sort' }; |
| 368 | const $ = await this._getSoup(url, params); |
| 369 | if (!$) return []; |
| 370 | |
| 371 | const results = []; |
| 372 | const gachaGrid = $('div.gacha-grid'); |
| 373 | if (gachaGrid.length > 0) { |
| 374 | gachaGrid.find('a.gacha-card').each((i, el) => { |
| 375 | const card = $(el); |
| 376 | const link = card.attr('href') || ''; |
| 377 | |
| 378 | const imgTag = card.find('img'); |
| 379 | const image = imgTag.attr('src') || ''; |
| 380 | |
| 381 | const titleTag = card.find('h3.title'); |
| 382 | let title = titleTag.length > 0 ? titleTag.text().trim() : ''; |
| 383 | if (!title && imgTag.length > 0) { |
| 384 | title = imgTag.attr('alt') || ''; |
| 385 | } |
| 386 | title = title.trim(); |
| 387 | |
| 388 | const currentEp = card.find('span.current-ep'); |
| 389 | const currentEpText = currentEp.length > 0 ? currentEp.text().trim() : ''; |
| 390 | |
| 391 | const totalEp = card.find('span.total-ep'); |
| 392 | const totalEpText = totalEp.length > 0 ? totalEp.text().trim() : ''; |
| 393 | |
| 394 | const ratingSpan = card.find('span.skor-angka'); |
| 395 | const rating = ratingSpan.length > 0 ? ratingSpan.text().replace('(', '').replace(')', '').trim() : ''; |
| 396 | |
| 397 | const hotTag = card.find('div.hot-tag'); |
| 398 | const isHot = hotTag.length > 0; |
| 399 | |
| 400 | let rarity = ''; |
| 401 | const classes = card.attr('class') || ''; |
| 402 | const match = classes.match(/rarity-(\d+)/); |
| 403 | if (match) rarity = match[1]; |
| 404 | |
| 405 | results.push({ |
| 406 | title, |
| 407 | link, |
| 408 | image, |
| 409 | current_episode: currentEpText, |
| 410 | total_episodes: totalEpText, |
| 411 | rating, |
| 412 | hot: isHot, |
| 413 | rarity |
| 414 | }); |
| 415 | }); |
| 416 | } |
| 417 | return results; |
| 418 | } |
| 419 | |
| 420 | async getPopularSeries(page = 1) { |
| 421 | const url = page > 1 ? `${this.baseUrl}/popular-series/page/${page}/` : `${this.baseUrl}/popular-series/`; |
| 422 | const $ = await this._getSoup(url); |
| 423 | if (!$) return {}; |
| 424 | |
| 425 | const data = { |
| 426 | tabs: {}, |
| 427 | overall_rank: [] |
| 428 | }; |
| 429 | |
| 430 | const tabsNav = $('ul.tabs'); |
| 431 | if (tabsNav.length > 0) { |
| 432 | const scraper = this; |
| 433 | tabsNav.find('li.tab-link').each((i, el) => { |
| 434 | const linkLi = $(el); |
| 435 | const tabName = linkLi.text().trim(); |
| 436 | const tabId = linkLi.attr('data-tab'); |
| 437 | if (tabName && tabId) { |
| 438 | data.tabs[tabName] = []; |
| 439 | const tabContent = $(`#${tabId}`); |
| 440 | if (tabContent.length > 0) { |
| 441 | tabContent.find('div.animeseries').each((j, art) => { |
| 442 | const aTag = $(art).find('a'); |
| 443 | if (aTag.length > 0) { |
| 444 | data.tabs[tabName].push(scraper._parseAnimeCard($, aTag)); |
| 445 | } |
| 446 | }); |
| 447 | } |
| 448 | } |
| 449 | }); |
| 450 | } |
| 451 | |
| 452 | const rankList = $('ul.rank'); |
| 453 | if (rankList.length > 0) { |
| 454 | rankList.find('li').each((i, el) => { |
| 455 | const li = $(el); |
| 456 | const aTag = li.find('a'); |
| 457 | if (aTag.length > 0) { |
| 458 | const link = aTag.attr('href') || ''; |
| 459 | const imgTag = aTag.find('img'); |
| 460 | const image = imgTag.attr('src') || ''; |
| 461 | |
| 462 | const mid = aTag.find('div.mid'); |
| 463 | let title = ''; |
| 464 | let synopsis = ''; |
| 465 | let genres = []; |
| 466 | if (mid.length > 0) { |
| 467 | const h2Tag = mid.find('h2'); |
| 468 | title = h2Tag.length > 0 ? h2Tag.text().trim() : ''; |
| 469 | if (!title && imgTag.length > 0) { |
| 470 | title = imgTag.attr('alt') || ''; |
| 471 | } |
| 472 | title = title.trim(); |
| 473 | |
| 474 | const pTag = mid.find('p'); |
| 475 | synopsis = pTag.length > 0 ? pTag.text().trim() : ''; |
| 476 | |
| 477 | const viewerDiv = mid.find('div.viewer'); |
| 478 | if (viewerDiv.length > 0) { |
| 479 | const viewerText = viewerDiv.text().replace('Genre :', '').trim(); |
| 480 | genres = viewerText.split(',').map(g => g.trim()).filter(g => g); |
| 481 | } |
| 482 | } |
| 483 | |
| 484 | data.overall_rank.push({ |
| 485 | title, |
| 486 | link, |
| 487 | image, |
| 488 | synopsis, |
| 489 | genres |
| 490 | }); |
| 491 | } |
| 492 | }); |
| 493 | } |
| 494 | |
| 495 | return data; |
| 496 | } |
| 497 | |
| 498 | async getJadwalRilis() { |
| 499 | const url = `${this.baseUrl}/jadwal-rilis/`; |
| 500 | const $ = await this._getSoup(url); |
| 501 | if (!$) return {}; |
| 502 | |
| 503 | const data = { |
| 504 | pengumuman_libur: [], |
| 505 | perlu_diperhatikan: [], |
| 506 | perkiraan_rilis_mendatang: [], |
| 507 | kalender_rilis: {} |
| 508 | }; |
| 509 | |
| 510 | const liburBox = $('div.as-delay-announcements'); |
| 511 | if (liburBox.length > 0) { |
| 512 | liburBox.find('li').each((i, el) => { |
| 513 | data.pengumuman_libur.push($(el).text().trim()); |
| 514 | }); |
| 515 | } |
| 516 | |
| 517 | const notesBox = $('div.as-important-notes'); |
| 518 | if (notesBox.length > 0) { |
| 519 | notesBox.find('li').each((i, el) => { |
| 520 | data.perlu_diperhatikan.push($(el).text().trim()); |
| 521 | }); |
| 522 | } |
| 523 | |
| 524 | const upcomingBox = $('div.jr-upcoming-box'); |
| 525 | if (upcomingBox.length > 0) { |
| 526 | upcomingBox.find('div.jr-upcoming-item').each((i, el) => { |
| 527 | const item = $(el); |
| 528 | const imgTag = item.find('img'); |
| 529 | const image = imgTag.attr('src') || ''; |
| 530 | |
| 531 | const titleSpan = item.find('span.jr-upcoming-title'); |
| 532 | const title = titleSpan.length > 0 ? titleSpan.text().trim() : ''; |
| 533 | |
| 534 | const epSpan = item.find('span.jr-upcoming-ep'); |
| 535 | const episodeTime = epSpan.length > 0 ? epSpan.text().trim() : ''; |
| 536 | |
| 537 | const timeSpan = item.find('div.jr-upcoming-time'); |
| 538 | const timeLeft = timeSpan.length > 0 ? timeSpan.text().trim() : ''; |
| 539 | |
| 540 | data.perkiraan_rilis_mendatang.push({ |
| 541 | title, |
| 542 | image, |
| 543 | episode_time: episodeTime, |
| 544 | time_left: timeLeft |
| 545 | }); |
| 546 | }); |
| 547 | } |
| 548 | |
| 549 | const days = ['senin', 'selasa', 'rabu', 'kamis', 'jumat', 'sabtu', 'minggu']; |
| 550 | days.forEach(day => { |
| 551 | const tabContent = $(`#${day}`); |
| 552 | if (tabContent.length > 0) { |
| 553 | const dateText = tabContent.attr('data-date-text') || ''; |
| 554 | data.kalender_rilis[day] = { |
| 555 | date_text: dateText.trim(), |
| 556 | series: [] |
| 557 | }; |
| 558 | |
| 559 | tabContent.find('a.as-anime-card').each((i, el) => { |
| 560 | const card = $(el); |
| 561 | const link = card.attr('href') || ''; |
| 562 | const imgTag = card.find('img'); |
| 563 | const image = imgTag.attr('src') || ''; |
| 564 | |
| 565 | const titleTag = card.find('h3.as-anime-title'); |
| 566 | const title = titleTag.length > 0 ? titleTag.text().trim() : ''; |
| 567 | |
| 568 | const epText = card.find('span.jr-ep-text'); |
| 569 | const episode = epText.length > 0 ? epText.text().trim() : ''; |
| 570 | |
| 571 | const typeBadge = card.find('span.jr-type-badge'); |
| 572 | const typeVal = typeBadge.length > 0 ? typeBadge.text().trim() : ''; |
| 573 | |
| 574 | const timeSpan = card.find('span.time-text'); |
| 575 | const timeVal = timeSpan.length > 0 ? timeSpan.text().replace('⏰', '').trim() : ''; |
| 576 | |
| 577 | const ratingSpan = card.find('span.rating-text'); |
| 578 | const rating = ratingSpan.length > 0 ? ratingSpan.text().replace('⭐', '').trim() : ''; |
| 579 | |
| 580 | const membersSpan = card.find('span.members-text'); |
| 581 | const members = membersSpan.length > 0 ? membersSpan.text().replace('👤', '').trim() : ''; |
| 582 | |
| 583 | let genresList = []; |
| 584 | card.find('span.jr-genre-pill').each((j, gEl) => { |
| 585 | genresList.push($(gEl).text().trim()); |
| 586 | }); |
| 587 | |
| 588 | data.kalender_rilis[day].series.push({ |
| 589 | title, |
| 590 | link, |
| 591 | image, |
| 592 | episode, |
| 593 | type: typeVal, |
| 594 | time: timeVal, |
| 595 | rating, |
| 596 | members, |
| 597 | genres: genresList |
| 598 | }); |
| 599 | }); |
| 600 | } |
| 601 | }); |
| 602 | |
| 603 | return data; |
| 604 | } |
| 605 | |
| 606 | async getGenresList(sort = 'az') { |
| 607 | const url = `${this.baseUrl}/genres/`; |
| 608 | const params = { sort, mode: 'sort' }; |
| 609 | const $ = await this._getSoup(url, params); |
| 610 | if (!$) return []; |
| 611 | |
| 612 | const results = []; |
| 613 | const gridContainer = $('div.genre-grid-container'); |
| 614 | if (gridContainer.length > 0) { |
| 615 | gridContainer.find('a.genre-grid-card').each((i, el) => { |
| 616 | const card = $(el); |
| 617 | const link = card.attr('href') || ''; |
| 618 | const slug = link.replace(/\/$/, '').split('/').pop() || ''; |
| 619 | |
| 620 | const imgTag = card.find('img'); |
| 621 | const image = imgTag.attr('src') || ''; |
| 622 | |
| 623 | const titleTag = card.find('h3.genre-name'); |
| 624 | const name = titleTag.length > 0 ? titleTag.text().trim() : ''; |
| 625 | |
| 626 | let totalSeries = ''; |
| 627 | const countSpan = card.find('span[class*="count"]'); |
| 628 | if (countSpan.length > 0) { |
| 629 | totalSeries = countSpan.text().trim(); |
| 630 | } |
| 631 | |
| 632 | let ongoingSeries = ''; |
| 633 | const ongoingSpan = card.find('span[class*="ongoing"]'); |
| 634 | if (ongoingSpan.length > 0) { |
| 635 | ongoingSeries = ongoingSpan.text().trim(); |
| 636 | } |
| 637 | |
| 638 | results.push({ |
| 639 | name, |
| 640 | link, |
| 641 | slug, |
| 642 | image, |
| 643 | total_series: totalSeries, |
| 644 | ongoing_series: ongoingSeries |
| 645 | }); |
| 646 | }); |
| 647 | } |
| 648 | return results; |
| 649 | } |
| 650 | |
| 651 | async getAnimeDetail(animeUrlOrSlug) { |
| 652 | const url = animeUrlOrSlug.startsWith('http') ? animeUrlOrSlug : `${this.baseUrl}/anime/${animeUrlOrSlug}/`; |
| 653 | const $ = await this._getSoup(url); |
| 654 | if (!$) return {}; |
| 655 | |
| 656 | const titleH1 = $('h1.entry-title'); |
| 657 | let title = ''; |
| 658 | if (titleH1.length > 0) { |
| 659 | const span = titleH1.find('span'); |
| 660 | title = span.length > 0 ? (span.attr('data-title-default') || span.text().trim()) : titleH1.text().replace('Nonton', '').replace('Sub Indo', '').trim(); |
| 661 | } |
| 662 | |
| 663 | let poster = ''; |
| 664 | let score = ''; |
| 665 | let typeVal = ''; |
| 666 | let trailer = ''; |
| 667 | const animeCard = $('div.anime-card'); |
| 668 | if (animeCard.length > 0) { |
| 669 | const sidebar = animeCard.find('div.anime-card__sidebar'); |
| 670 | if (sidebar.length > 0) { |
| 671 | const imgTag = sidebar.find('img'); |
| 672 | poster = imgTag.attr('src') || ''; |
| 673 | |
| 674 | const scoreDiv = sidebar.find('div.anime-card__score'); |
| 675 | if (scoreDiv.length > 0) { |
| 676 | const valSpan = scoreDiv.find('span.value'); |
| 677 | score = valSpan.length > 0 ? valSpan.text().trim() : ''; |
| 678 | const typeSpan = scoreDiv.find('span.type'); |
| 679 | typeVal = typeSpan.length > 0 ? typeSpan.text().trim() : ''; |
| 680 | } |
| 681 | |
| 682 | const trailerA = sidebar.find('a.trailerbutton'); |
| 683 | trailer = trailerA.attr('href') || ''; |
| 684 | } |
| 685 | } |
| 686 | |
| 687 | let details = {}; |
| 688 | let synopsis = ''; |
| 689 | let genres = []; |
| 690 | if (animeCard.length > 0) { |
| 691 | const mainInfo = animeCard.find('div.anime-card__main'); |
| 692 | if (mainInfo.length > 0) { |
| 693 | const detailsUl = mainInfo.find('ul.details-list'); |
| 694 | if (detailsUl.length > 0) { |
| 695 | detailsUl.find('li').each((i, el) => { |
| 696 | const li = $(el); |
| 697 | const labelTag = li.find('strong, span.detail-label'); |
| 698 | if (labelTag.length > 0) { |
| 699 | const label = labelTag.text().replace(':', '').trim(); |
| 700 | const valText = li.text().replace(labelTag.text(), '').trim(); |
| 701 | details[label] = valText; |
| 702 | } |
| 703 | }); |
| 704 | } |
| 705 | |
| 706 | const genresDiv = mainInfo.find('div.anime-card__genres'); |
| 707 | if (genresDiv.length > 0) { |
| 708 | genresDiv.find('a').each((i, el) => { |
| 709 | genres.push({ |
| 710 | name: $(el).text().trim(), |
| 711 | link: $(el).attr('href') || '' |
| 712 | }); |
| 713 | }); |
| 714 | } |
| 715 | |
| 716 | const synDiv = mainInfo.find('div#tab-synopsis'); |
| 717 | if (synDiv.length > 0) { |
| 718 | synopsis = synDiv.text().trim(); |
| 719 | } |
| 720 | } |
| 721 | } |
| 722 | |
| 723 | let status = ''; |
| 724 | let totalEpisodes = ''; |
| 725 | let episodeDuration = ''; |
| 726 | let season = ''; |
| 727 | let seasonLink = ''; |
| 728 | const quickInfo = $('div.anime-card__quick-info'); |
| 729 | if (quickInfo.length > 0) { |
| 730 | const statusSpan = quickInfo.find('span[class*="status"]'); |
| 731 | status = statusSpan.length > 0 ? statusSpan.text().trim() : ''; |
| 732 | |
| 733 | quickInfo.find('span.info-item').each((i, el) => { |
| 734 | const text = $(el).text(); |
| 735 | if (text.toLowerCase().includes('episodes')) { |
| 736 | totalEpisodes = text.trim(); |
| 737 | } else if (text.includes('min') || text.includes('menit')) { |
| 738 | episodeDuration = text.trim(); |
| 739 | } |
| 740 | }); |
| 741 | |
| 742 | const seasonA = quickInfo.find('span.season a'); |
| 743 | if (seasonA.length > 0) { |
| 744 | season = seasonA.text().trim(); |
| 745 | seasonLink = seasonA.attr('href') || ''; |
| 746 | } |
| 747 | } |
| 748 | |
| 749 | let episodes = []; |
| 750 | const epSection = $('section.anime-card__episode-list-section'); |
| 751 | if (epSection.length > 0) { |
| 752 | const epItems = epSection.find('div.episode-list-items'); |
| 753 | if (epItems.length > 0) { |
| 754 | epItems.find('a.episode-item').each((i, el) => { |
| 755 | const aItem = $(el); |
| 756 | const link = aItem.attr('href') || ''; |
| 757 | const titleSpan = aItem.find('span.ep-title'); |
| 758 | const epTitle = titleSpan.length > 0 ? titleSpan.text().trim() : ''; |
| 759 | const dateSpan = aItem.find('span.ep-date'); |
| 760 | const epDate = dateSpan.length > 0 ? dateSpan.text().trim() : ''; |
| 761 | |
| 762 | episodes.push({ |
| 763 | title: epTitle, |
| 764 | link, |
| 765 | date: epDate |
| 766 | }); |
| 767 | }); |
| 768 | } |
| 769 | } |
| 770 | |
| 771 | let recommended = []; |
| 772 | const relatedDiv = $('div.related'); |
| 773 | if (relatedDiv.length > 0) { |
| 774 | const scraper = this; |
| 775 | relatedDiv.find('a.as-anime-card').each((i, el) => { |
| 776 | recommended.push(scraper._parseAnimeCard($, el)); |
| 777 | }); |
| 778 | } |
| 779 | // Themesia layout fallback |
| 780 | if (!poster) { |
| 781 | poster = $('.thumb img, .single-info .thumb img, .info-left img, .infox img').first().attr('src') || ''; |
| 782 | } |
| 783 | if (!title) { |
| 784 | title = $('h1.entry-title, h1').first().text().trim(); |
| 785 | } |
| 786 | if (Object.keys(details).length === 0) { |
| 787 | $('.spe span').each((i, el) => { |
| 788 | const b = $(el).find('b').text().replace(':', '').trim(); |
| 789 | const full = $(el).text().replace($(el).find('b').text(), '').trim(); |
| 790 | if (b) details[b] = full; |
| 791 | if (/status/i.test(b)) status = full; |
| 792 | if (/episode/i.test(b)) totalEpisodes = full; |
| 793 | if (/durasi|duration/i.test(b)) episodeDuration = full; |
| 794 | if (/tipe|type/i.test(b)) typeVal = full; |
| 795 | }); |
| 796 | } |
| 797 | if (genres.length === 0) { |
| 798 | $('.genxed a, a[href*="/genres/"]').each((i, el) => { |
| 799 | const name = $(el).text().trim(); |
| 800 | const link = $(el).attr('href') || ''; |
| 801 | if (name) genres.push({ name, link }); |
| 802 | }); |
| 803 | } |
| 804 | if (!synopsis) { |
| 805 | synopsis = $('.desc, .entry-content, .sinopc').first().text().trim(); |
| 806 | } |
| 807 | if (episodes.length === 0) { |
| 808 | $('.eplister ul li').each((i, el) => { |
| 809 | const a = $(el).find('a').first(); |
| 810 | const link = a.attr('href') || ''; |
| 811 | const epNum = $(el).find('.epl-num').text().trim(); |
| 812 | const epTitle = $(el).find('.epl-title').text().trim() || a.text().trim(); |
| 813 | const epDate = $(el).find('.epl-date').text().trim(); |
| 814 | if (link) { |
| 815 | episodes.push({ |
| 816 | episode: epNum, |
| 817 | title: epTitle, |
| 818 | link, |
| 819 | date: epDate |
| 820 | }); |
| 821 | } |
| 822 | }); |
| 823 | } |
| 824 | |
| 825 | return { |
| 826 | title, |
| 827 | poster, |
| 828 | score, |
| 829 | type: typeVal, |
| 830 | trailer, |
| 831 | synopsis, |
| 832 | genres, |
| 833 | details, |
| 834 | status, |
| 835 | total_episodes: totalEpisodes, |
| 836 | episode_duration: episodeDuration, |
| 837 | season, |
| 838 | season_link: seasonLink, |
| 839 | episodes, |
| 840 | recommended_series: recommended |
| 841 | }; |
| 842 | } |
| 843 | |
| 844 | async getStreamingDetail(episodeUrlOrSlug) { |
| 845 | const url = episodeUrlOrSlug.startsWith('http') ? episodeUrlOrSlug : `${this.baseUrl}/${episodeUrlOrSlug}/`; |
| 846 | const $ = await this._getSoup(url); |
| 847 | if (!$) return {}; |
| 848 | |
| 849 | const titleH1 = $('h1.entry-title'); |
| 850 | const title = titleH1.length > 0 ? titleH1.text().trim() : ''; |
| 851 | |
| 852 | let animeTitle = ''; |
| 853 | let animeLink = ''; |
| 854 | const breadcrumbs = $('nav.breadcrumbs'); |
| 855 | if (breadcrumbs.length > 0) { |
| 856 | const links = breadcrumbs.find('a').filter((i, el) => $(el).attr('href')); |
| 857 | if (links.length >= 3) { |
| 858 | const lastLink = links.last(); |
| 859 | animeTitle = lastLink.text().trim(); |
| 860 | animeLink = lastLink.attr('href') || ''; |
| 861 | } else if (links.length === 2) { |
| 862 | const lastLink = links.last(); |
| 863 | animeTitle = lastLink.text().trim(); |
| 864 | animeLink = lastLink.attr('href') || ''; |
| 865 | } |
| 866 | } |
| 867 | |
| 868 | let prevLink = null; |
| 869 | let nextLink = null; |
| 870 | let allEpsLink = null; |
| 871 | const naveps = $('div.naveps'); |
| 872 | if (naveps.length > 0) { |
| 873 | naveps.find('div.nvs').each((i, el) => { |
| 874 | const nvs = $(el); |
| 875 | const aTag = nvs.find('a'); |
| 876 | if (aTag.length > 0) { |
| 877 | const href = aTag.attr('href') || ''; |
| 878 | const label = aTag.text().toLowerCase(); |
| 879 | if (label.includes('prev')) { |
| 880 | prevLink = href; |
| 881 | } else if (label.includes('next')) { |
| 882 | nextLink = href; |
| 883 | } else if (label.includes('all') || label.includes('episode')) { |
| 884 | allEpsLink = href; |
| 885 | } |
| 886 | } else if (nvs.hasClass('nvsc')) { |
| 887 | const aTagC = nvs.find('a'); |
| 888 | if (aTagC.length > 0) { |
| 889 | allEpsLink = aTagC.attr('href') || ''; |
| 890 | } |
| 891 | } |
| 892 | }); |
| 893 | } |
| 894 | |
| 895 | let defaultVideoUrl = ''; |
| 896 | const videoku = $('div#videoku'); |
| 897 | if (videoku.length > 0) { |
| 898 | const iframe = videoku.find('iframe'); |
| 899 | if (iframe.length > 0) { |
| 900 | defaultVideoUrl = iframe.attr('src') || iframe.attr('data-src') || ''; |
| 901 | } |
| 902 | } |
| 903 | |
| 904 | let videoServers = []; |
| 905 | const playerTabs = $('ul.player'); |
| 906 | if (playerTabs.length > 0) { |
| 907 | playerTabs.find('li.serverplayer').each((i, el) => { |
| 908 | const li = $(el); |
| 909 | const serverName = li.text().trim(); |
| 910 | const postId = li.attr('data-post') || ''; |
| 911 | const serverType = li.attr('data-type') || ''; |
| 912 | const nume = li.attr('data-nume') || ''; |
| 913 | const isActive = li.hasClass('on'); |
| 914 | |
| 915 | videoServers.push({ |
| 916 | server_name: serverName, |
| 917 | post_id: postId, |
| 918 | server_type: serverType, |
| 919 | nume: nume, |
| 920 | is_active: isActive |
| 921 | }); |
| 922 | }); |
| 923 | } |
| 924 | |
| 925 | let downloadLinks = []; |
| 926 | const downloadArea = $('div#download_area'); |
| 927 | if (downloadArea.length > 0) { |
| 928 | const arealinker = downloadArea.find('div#arealinker'); |
| 929 | if (arealinker.length > 0) { |
| 930 | arealinker.find('div.listlink').each((i, el) => { |
| 931 | const listlink = $(el); |
| 932 | const spanTag = listlink.find('span'); |
| 933 | const formatName = spanTag.length > 0 ? spanTag.text().trim() : 'Unknown'; |
| 934 | |
| 935 | let links = []; |
| 936 | listlink.find('a').each((j, aEl) => { |
| 937 | const aLink = $(aEl); |
| 938 | links.push({ |
| 939 | label: aLink.text().trim(), |
| 940 | url: aLink.attr('href') || '' |
| 941 | }); |
| 942 | }); |
| 943 | |
| 944 | downloadLinks.push({ |
| 945 | format: formatName, |
| 946 | links |
| 947 | }); |
| 948 | }); |
| 949 | } |
| 950 | } |
| 951 | |
| 952 | let episodeTerbaru = []; |
| 953 | const sidebar = $('#sidebar_right').length > 0 ? $('#sidebar_right') : $('body'); |
| 954 | let sidebarLatestHeader = null; |
| 955 | sidebar.find('h3, h2').each((i, el) => { |
| 956 | if ($(el).text().includes('Episode Terbaru')) { |
| 957 | sidebarLatestHeader = $(el); |
| 958 | } |
| 959 | }); |
| 960 | if (sidebarLatestHeader) { |
| 961 | const ul = $(sidebarLatestHeader).nextAll('ul.latestepisodes').first(); |
| 962 | if (ul.length > 0) { |
| 963 | ul.find('li').each((i, el) => { |
| 964 | const li = $(el); |
| 965 | const aTag = li.find('a'); |
| 966 | if (aTag.length > 0) { |
| 967 | const link = aTag.attr('href') || ''; |
| 968 | const lefts = aTag.find('div.lefts'); |
| 969 | const rights = aTag.find('div.rights'); |
| 970 | |
| 971 | const epTitle = lefts.length > 0 ? lefts.text().trim() : ''; |
| 972 | const videoNum = rights.length > 0 ? rights.find('span.video').text().trim() : ''; |
| 973 | |
| 974 | episodeTerbaru.push({ |
| 975 | title: epTitle, |
| 976 | link, |
| 977 | episode: videoNum |
| 978 | }); |
| 979 | } |
| 980 | }); |
| 981 | } |
| 982 | } |
| 983 | |
| 984 | let seriesPopularSummer = []; |
| 985 | let sidebarPopHeader = null; |
| 986 | sidebar.find('h3, h2').each((i, el) => { |
| 987 | if ($(el).text().includes('Series Popular Summer')) { |
| 988 | sidebarPopHeader = $(el); |
| 989 | } |
| 990 | }); |
| 991 | if (sidebarPopHeader) { |
| 992 | const relatedGrid = $(sidebarPopHeader).nextAll('div.related').first(); |
| 993 | if (relatedGrid.length > 0) { |
| 994 | const scraper = this; |
| 995 | relatedGrid.find('a.as-anime-card').each((i, el) => { |
| 996 | seriesPopularSummer.push(scraper._parseAnimeCard($, el)); |
| 997 | }); |
| 998 | } |
| 999 | } |
| 1000 | |
| 1001 | return { |
| 1002 | title, |
| 1003 | anime_title: animeTitle, |
| 1004 | anime_link: animeLink, |
| 1005 | prev_episode_link: prevLink, |
| 1006 | next_episode_link: nextLink, |
| 1007 | all_episodes_link: allEpsLink, |
| 1008 | default_video_url: defaultVideoUrl, |
| 1009 | video_servers: videoServers, |
| 1010 | download_links: downloadLinks, |
| 1011 | episode_terbaru_sidebar: episodeTerbaru, |
| 1012 | series_popular_summer_sidebar: seriesPopularSummer, |
| 1013 | nonce: this.lastNonce, |
| 1014 | ajax_url: this.lastAjaxUrl |
| 1015 | }; |
| 1016 | } |
| 1017 | |
| 1018 | async getVideoIframe(postId, nume, serverName, nonce = null, ajaxUrl = null) { |
| 1019 | const finalNonce = nonce || this.lastNonce; |
| 1020 | const finalAjaxUrl = ajaxUrl || this.lastAjaxUrl || `${this.baseUrl}/wp-admin/admin-ajax.php`; |
| 1021 | |
| 1022 | if (!finalNonce) { |
| 1023 | throw new Error("Nonce is required. Scrape a streaming page first."); |
| 1024 | } |
| 1025 | |
| 1026 | const body = new URLSearchParams(); |
| 1027 | body.append('action', 'player_ajax'); |
| 1028 | body.append('post', postId); |
| 1029 | body.append('nume', nume); |
| 1030 | body.append('serverName', serverName); |
| 1031 | body.append('nonce', finalNonce); |
| 1032 | |
| 1033 | const headers = { |
| 1034 | ...this.headers, |
| 1035 | 'Content-Type': 'application/x-www-form-urlencoded', |
| 1036 | 'X-Requested-With': 'XMLHttpRequest', |
| 1037 | 'Origin': this.baseUrl |
| 1038 | }; |
| 1039 | if (this.lastUrl) { |
| 1040 | headers['Referer'] = this.lastUrl; |
| 1041 | } |
| 1042 | |
| 1043 | try { |
| 1044 | const response = await fetch(finalAjaxUrl, { |
| 1045 | method: 'POST', |
| 1046 | headers, |
| 1047 | body |
| 1048 | }); |
| 1049 | if (!response.ok) { |
| 1050 | throw new Error(`HTTP error! status: ${response.status}`); |
| 1051 | } |
| 1052 | const html = await response.text(); |
| 1053 | const $ = cheerio.load(html); |
| 1054 | const iframe = $('iframe'); |
| 1055 | if (iframe.length > 0) { |
| 1056 | return iframe.attr('src') || iframe.attr('data-src') || ''; |
| 1057 | } |
| 1058 | return ''; |
| 1059 | } catch (error) { |
| 1060 | console.error(`Error resolving video iframe for ${serverName}:`, error); |
| 1061 | return ''; |
| 1062 | } |
| 1063 | } |
| 1064 | } |
| 1065 | |
| 1066 | export default NontonAnimeIDScraper; |
| 1067 | |
| 1068 | // ───────────────────────────────────────────────────────────────────── |
| 1069 | // Direct Execution / CLI Runner |
| 1070 | // ───────────────────────────────────────────────────────────────────── |
| 1071 | if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { |
| 1072 | const scraper = new NontonAnimeIDScraper(); |
| 1073 | const args = process.argv.slice(2); |
| 1074 | |
| 1075 | ;(async () => { |
| 1076 | try { |
| 1077 | if (args.length === 0) { |
| 1078 | console.log('[+] Menjalankan default search: "one piece"...\n'); |
| 1079 | const res = await scraper.searchAnime('one piece'); |
| 1080 | console.log(JSON.stringify(res, null, 2)); |
| 1081 | return; |
| 1082 | } |
| 1083 | |
| 1084 | const cmd = args[0].toLowerCase(); |
| 1085 | |
| 1086 | if (cmd === 'search' || cmd === 'cari') { |
| 1087 | const query = args.slice(1).join(' ') || 'one piece'; |
| 1088 | console.log(`[+] Mencari: "${query}"...`); |
| 1089 | const res = await scraper.searchAnime(query); |
| 1090 | console.log(JSON.stringify(res, null, 2)); |
| 1091 | } else if (cmd === 'detail') { |
| 1092 | const url = args[1]; |
| 1093 | console.log(`[+] Detail: ${url}`); |
| 1094 | const res = await scraper.getAnimeDetail(url); |
| 1095 | console.log(JSON.stringify(res, null, 2)); |
| 1096 | } else if (cmd === 'stream' || cmd === 'episode') { |
| 1097 | const url = args[1]; |
| 1098 | console.log(`[+] Stream: ${url}`); |
| 1099 | const res = await scraper.getStreamingDetail(url); |
| 1100 | console.log(JSON.stringify(res, null, 2)); |
| 1101 | } else if (cmd === 'ongoing') { |
| 1102 | console.log('[+] Mengambil ongoing anime...'); |
| 1103 | const res = await scraper.getOngoingList(); |
| 1104 | console.log(JSON.stringify(res, null, 2)); |
| 1105 | } else if (cmd === 'popular') { |
| 1106 | console.log('[+] Mengambil popular series...'); |
| 1107 | const res = await scraper.getPopularSeries(); |
| 1108 | console.log(JSON.stringify(res, null, 2)); |
| 1109 | } else { |
| 1110 | const query = args.join(' '); |
| 1111 | console.log(`[+] Mencari: "${query}"...`); |
| 1112 | const res = await scraper.searchAnime(query); |
| 1113 | console.log(JSON.stringify(res, null, 2)); |
| 1114 | } |
| 1115 | } catch (err) { |
| 1116 | console.error('Error:', err.message || err); |
| 1117 | process.exitCode = 1; |
| 1118 | } |
| 1119 | })(); |
| 1120 | } |
| 1121 | |
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.