diff --git a/sites/tivie.id/__data__/content.html b/sites/tivie.id/__data__/content.html new file mode 100644 index 00000000..f6caa196 --- /dev/null +++ b/sites/tivie.id/__data__/content.html @@ -0,0 +1,1327 @@ + + + +
+
+
+
+
+ +
+ +
+
+
+
+
+
+ + \ No newline at end of file diff --git a/sites/tivie.id/__data__/program01.html b/sites/tivie.id/__data__/program01.html new file mode 100644 index 00000000..973e0511 --- /dev/null +++ b/sites/tivie.id/__data__/program01.html @@ -0,0 +1,102 @@ + + + +
+
+ +
+
+
+
+ +
+
+
+ Film +
+

White House Down

+

2013

+
+

Saat melakukan tur di Gedung Putih bersama putrinya yang masih kecil, seorang perwira polisi + beraksi untuk melindungi anaknya dan presiden dari sekelompok penjajah paramiliter + bersenjata lengkap.

+
+
+ + + +
+
+

Terakhir Tayang

+ +
+
+
+
+
+ + \ No newline at end of file diff --git a/sites/tivie.id/__data__/program02.html b/sites/tivie.id/__data__/program02.html new file mode 100644 index 00000000..5233f2b0 --- /dev/null +++ b/sites/tivie.id/__data__/program02.html @@ -0,0 +1,106 @@ + + + +
+
+ +
+
+
+
+ +
+
+
+ Serial +
+

Hudson & Rex S6

+

Episode 14

+
+

Saat guru musik Jesse terbunuh di studio rekamannya, Charlie dan Rex menghubungkan kejahatan + tersebut dengan pembunuhan yang tampaknya tak ada hubungannya.

+
+
+ + + +
+
+

Terakhir Tayang

+ +
+
+ +
+
+
+ + \ No newline at end of file diff --git a/sites/tivie.id/readme.md b/sites/tivie.id/readme.md new file mode 100644 index 00000000..340aa9d3 --- /dev/null +++ b/sites/tivie.id/readme.md @@ -0,0 +1,21 @@ +# tivie.id + +https://tivie.id + +### Download the guide + +```sh +npm run grab --- --site=tivie.id +``` + +### Update channel list + +```sh +npm run channels:parse --- --config=./sites/tivie.id/tivie.id.config.js --output=./sites/tivie.id/tivie.id.channels.xml +``` + +### Test + +```sh +npm test --- tivie.id +``` diff --git a/sites/tivie.id/tivie.id.channels.xml b/sites/tivie.id/tivie.id.channels.xml new file mode 100644 index 00000000..759dacbc --- /dev/null +++ b/sites/tivie.id/tivie.id.channels.xml @@ -0,0 +1,53 @@ + + + ANTV + AXN + beIN Sports 1 + beIN Sports 2 + beIN Sports 3 + BTV + Cinemax + CNN + Galaxy + Galaxy Premium + Garuda TV + GTV + HBO + HBO Family + HBO Hits + HBO Signature + HITS + HITS Movies + HITS Now + Indosiar + iNews + Kompas TV + Lifetime + Mentari TV + Metro TV + MNC TV + Moji + My Cinema + My Cinema Asia + My Family Channel + My Kidz + NET. + Nusantara TV + RTV + RCTI + Rock Entertainment + Rock Action + SCTV + SEA Today + Sindonews TV + SpoTV 2 + SpoTV + Trans7 + Trans TV + tvN + tvN Movies + TvOne + TVRI + VTV + Zee Bioskop + diff --git a/sites/tivie.id/tivie.id.config.js b/sites/tivie.id/tivie.id.config.js new file mode 100644 index 00000000..cb6be07c --- /dev/null +++ b/sites/tivie.id/tivie.id.config.js @@ -0,0 +1,132 @@ +const axios = require('axios') +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const doFetch = require('@ntlab/sfetch') +const debug = require('debug')('site:tivie.id') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +doFetch + .setDebugger(debug) + +const tz = 'Asia/Jakarta' + +module.exports = { + site: 'tivie.id', + days: 2, + url({ channel, date }) { + return `https://tivie.id/channel/${ + channel.site_id + }/${ + date.format('YYYYMMDD') + }` + }, + async parser({ content, date }) { + const programs = [] + if (content) { + const $ = cheerio.load(content) + const items = $('ul[x-data] > li[id*="event-"] > div.w-full').toArray() + .map(item => { + const $item = $(item) + const time = $item.find('div:nth-child(1) span:nth-child(1)') + const info = $item.find('div:nth-child(2) h5') + const detail = info.find('a') + const p = { + start: dayjs.tz(`${date.format('YYYY-MM-DD')} ${time.html()}`, 'YYYY-MM-DD HH:mm', tz) + } + if (detail.length) { + const subtitle = detail.find('div') + p.title = parseText(subtitle.length ? subtitle : detail) + p.url = detail.attr('href') + } else { + p.title = parseText(info) + } + if (p.title) { + const [, , season, episode] = p.title.match(/( S(\d+))?, Ep\. (\d+)/) || [null, null, null, null] + if (season) { + p.season = parseInt(season) + } + if (episode) { + p.episode = parseInt(episode) + } + } + return p + }) + // fetch detailed guide if necessary + const queues = items + .filter(i => i.url) + .map(i => { + const url = i.url + delete i.url + return {i, url} + }) + if (queues.length) { + await doFetch(queues, (queue, res) => { + const $ = cheerio.load(res) + const img = $('#main-content > div > div:nth-child(1) img') + const info = $('#main-content > div > div:nth-child(2)') + const title = parseText(info.find('h2:nth-child(2)')) + if (!queue.i.title.startsWith(title)) { + queue.i.subTitle = parseText(info.find('h2:nth-child(2)')) + } + queue.i.description = parseText(info.find('div[class=""]:nth-child(4)')) + queue.i.date = parseText(info.find('h2:nth-child(3)')) + queue.i.image = img.length ? img.attr('src') : null + }) + } + // fill start-stop + for (let i = 0; i < items.length; i++) { + if (i < items.length - 1) { + items[i].stop = items[i + 1].start + } else { + items[i].stop = dayjs.tz(`${date.add(1, 'd').format('YYYY-MM-DD')} 00:00`, 'YYYY-MM-DD HH:mm', tz) + } + } + // add programs + programs.push(...items) + } + + return programs + }, + async channels({ lang = 'id' }) { + const result = await axios + .get('https://tivie.id/channel') + .then(response => response.data) + .catch(console.error) + + const $ = cheerio.load(result) + const items = $('ul[x-data] li[x-data] div header h2 a').toArray() + const channels = items.map(item => { + const $item = $(item) + const url = $item.attr('href') + return { + lang, + site_id: url.substr(url.lastIndexOf('/') + 1), + name: $item.find('strong').text() + } + }) + + return channels + } +} + +function parseText($item) { + let text = $item.text() + .replace(/\t/g, '') + .replace(/\n/g, ' ') + .trim() + while (true) { + if (text.match(/ /)) { + text = text.replace(/ /g, ' ') + continue + } + break + } + + return text +} diff --git a/sites/tivie.id/tivie.id.test.js b/sites/tivie.id/tivie.id.test.js new file mode 100644 index 00000000..e03247d2 --- /dev/null +++ b/sites/tivie.id/tivie.id.test.js @@ -0,0 +1,77 @@ +const { parser, url } = require('./tivie.id.config') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2024-12-31').startOf('d') +const channel = { + site_id: 'axn', + xmltv_id: 'AXN.id', + lang: 'id' +} + +axios.get.mockImplementation(url => { + const urls = { + 'https://tivie.id/film/white-house-down-nwzDnwz9nAv6': + 'program01.html', + 'https://tivie.id/program/hudson-rex-s6-e14-nwzDnwvBmQr9': + 'program02.html', + } + let data = '' + if (urls[url] !== undefined) { + data = fs.readFileSync(path.join(__dirname, '__data__', urls[url])).toString() + } + return Promise.resolve({ data }) +}) + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://tivie.id/channel/axn/20241231') +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.html')) + const results = ( + await parser({ date, content, channel }) + ).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(27) + expect(results[0]).toMatchObject({ + start: '2024-12-30T17:00:00.000Z', + stop: '2024-12-30T17:05:00.000Z', + title: 'White House Down', + description: + 'Saat melakukan tur di Gedung Putih bersama putrinya yang masih kecil, seorang perwira polisi beraksi untuk melindungi anaknya dan presiden dari sekelompok penjajah paramiliter bersenjata lengkap.', + image: 'https://i0.wp.com/is3.cloudhost.id/tivie/poster/2023/09/65116c78791c2-1695640694.jpg?resize=480,270', + }) + expect(results[2]).toMatchObject({ + start: '2024-12-30T18:00:00.000Z', + stop: '2024-12-30T18:55:00.000Z', + title: 'Hudson & Rex S6, Ep. 14', + description: + 'Saat guru musik Jesse terbunuh di studio rekamannya, Charlie dan Rex menghubungkan kejahatan tersebut dengan pembunuhan yang tampaknya tak ada hubungannya.', + image: 'https://i0.wp.com/is3.cloudhost.id/tivie/poster/2024/07/668b7ced47b25-1720417517.jpg?resize=480,270', + season: 6, + episode: 14, + }) +}) + +it('can handle empty guide', async () => { + const results = await parser({ + date, + channel, + content: '', + }) + expect(results).toMatchObject([]) +})