diff --git a/sites/arirang.com/__data__/detail.json b/sites/arirang.com/__data__/detail.json
new file mode 100644
index 00000000..bf7ce091
--- /dev/null
+++ b/sites/arirang.com/__data__/detail.json
@@ -0,0 +1,187 @@
+{
+ "resultCode": {
+ "code": 200000,
+ "http_status": 200,
+ "timestamp": 0,
+ "message": null,
+ "trace": null,
+ "access_token": null,
+ "expire_time": 0
+ },
+ "program_id": 173,
+ "order": 21,
+ "is_news_allow": true,
+ "bis_program": [
+ {
+ "bis_program_code": "2023003T",
+ "bis_program_title": "WITHIN THE FRAME [L]"
+ },
+ {
+ "bis_program_code": "2023004T",
+ "bis_program_title": "WITHIN THE FRAME [R]"
+ }
+ ],
+ "bis_bundle_program_code": "2023003T",
+ "bis_category_code": "시사보도",
+ "program_type": "tv",
+ "category_Info": [
+ {
+ "category_id": 29,
+ "local": "en",
+ "title": "Current Affairs"
+ },
+ {
+ "category_id": 29,
+ "local": "ko",
+ "title": "Current Affairs"
+ }
+ ],
+ "title": [
+ {
+ "lan_code": "en",
+ "text": "WITHIN THE FRAME [L]"
+ },
+ {
+ "lan_code": "ko",
+ "text": "WITHIN THE FRAME [L]"
+ }
+ ],
+ "content": [
+ {
+ "lan_code": "en",
+ "text": "NEWS
"
+ },
+ {
+ "lan_code": "ko",
+ "text": "NEWS 대담"
+ }
+ ],
+ "property": {
+ "open_status": {
+ "is_allow": true,
+ "is_origin_allow": null,
+ "start_date": null,
+ "end_date": null
+ },
+ "is_onair": true,
+ "is_teaser_allow": false,
+ "running_time": 30,
+ "schedule": [
+ {
+ "week": [
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri"
+ ],
+ "start_time": 1110
+ },
+ {
+ "week": [
+ ""
+ ],
+ "start_time": -1
+ },
+ {
+ "week": [
+ ""
+ ],
+ "start_time": -1
+ },
+ {
+ "week": [
+ ""
+ ],
+ "start_time": -1
+ },
+ {
+ "week": [
+ ""
+ ],
+ "start_time": -1
+ },
+ {
+ "week": [
+ ""
+ ],
+ "start_time": -1
+ },
+ {
+ "week": [
+ ""
+ ],
+ "start_time": -1
+ },
+ {
+ "week": [
+ ""
+ ],
+ "start_time": -1
+ },
+ {
+ "week": [
+ ""
+ ],
+ "start_time": -1
+ },
+ {
+ "week": [
+ ""
+ ],
+ "start_time": -1
+ }
+ ]
+ },
+ "platform": {
+ "is_aos_allow": true,
+ "is_ios_allow": true,
+ "is_smat_tv_allow": true
+ },
+ "image": [
+ {
+ "order": 0,
+ "type": "horizontal",
+ "name": "2080840096998752900.png",
+ "action": null,
+ "url": "https://img.arirang.com/v1/AUTH_d52449c16d3b4bbca17d4fffd9fc44af/public/images/202308/2080840096998752900.png"
+ },
+ {
+ "order": 0,
+ "type": "vertical",
+ "name": "1773516657138860509.png",
+ "action": null,
+ "url": "https://img.arirang.com/v1/AUTH_d52449c16d3b4bbca17d4fffd9fc44af/public/images/202301/1773516657138860509.png"
+ },
+ {
+ "order": 0,
+ "type": "mobile",
+ "name": "1773516657893835229.png",
+ "action": null,
+ "url": "https://img.arirang.com/v1/AUTH_d52449c16d3b4bbca17d4fffd9fc44af/public/images/202301/1773516657893835229.png"
+ },
+ {
+ "order": 0,
+ "type": "pc",
+ "name": "1773742773929771485.png",
+ "action": null,
+ "url": "https://img.arirang.com/v1/AUTH_d52449c16d3b4bbca17d4fffd9fc44af/public/images/202301/1773742773929771485.png"
+ },
+ {
+ "order": 0,
+ "type": "smarttv",
+ "name": "1773742775607493085.png",
+ "action": null,
+ "url": "https://img.arirang.com/v1/AUTH_d52449c16d3b4bbca17d4fffd9fc44af/public/images/202301/1773742775607493085.png"
+ },
+ {
+ "order": 0,
+ "type": "square",
+ "name": "1773742767839642077.png",
+ "action": null,
+ "url": "https://img.arirang.com/v1/AUTH_d52449c16d3b4bbca17d4fffd9fc44af/public/images/202301/1773742767839642077.png"
+ }
+ ],
+ "reg_date": "2023-01-03 10:21:56.0",
+ "update_date": "2023-08-03 10:55:34.0"
+}
\ No newline at end of file
diff --git a/sites/arirang.com/__data__/schedule.json b/sites/arirang.com/__data__/schedule.json
new file mode 100644
index 00000000..b6f620fa
--- /dev/null
+++ b/sites/arirang.com/__data__/schedule.json
@@ -0,0 +1,93 @@
+{
+ "resultCode": {
+ "code": 200000,
+ "http_status": 200,
+ "timestamp": 0,
+ "message": null,
+ "trace": null,
+ "access_token": null,
+ "expire_time": 0
+ },
+ "responseBody": {
+ "dsSchWeek": [
+ {
+ "chanId": "CH_W",
+ "broadYmd": "20230825",
+ "planNo": 1,
+ "scheduleSeq": 1,
+ "broadHm": "0000",
+ "viewHm": "0000",
+ "broadRun": 30,
+ "timeGrade": "1",
+ "pgmCd": "2023004T",
+ "broadType": "R",
+ "displayNm": "WITHIN THE FRAME [R]",
+ "episodeNo": 4,
+ "episodeNm": "#4",
+ "displayEpisodeNm": null,
+ "partNo": 0,
+ "firstClf": "02",
+ "broadClf": "02",
+ "scheduleClf": "0",
+ "scheduleGrp": "01",
+ "videoClf": "H",
+ "audioClf": "0",
+ "liveClf": null,
+ "captionYn": "N",
+ "signLangYn": "N",
+ "dvsYn": "N",
+ "captionExceptClf": "N",
+ "signLangExceptClf": "N",
+ "dvsExceptClf": "N",
+ "delibGrade": "00",
+ "delibTopicYn": "N",
+ "delibLanguageYn": "N",
+ "delibCopyYn": "N",
+ "delibViolenceYn": "N",
+ "delibSexualYn": "N",
+ "infoGrade": "0+",
+ "episodeRun": null,
+ "bandCd": null,
+ "bandNm": null,
+ "keepYn": "N",
+ "firstYn": "N",
+ "addInfo": null,
+ "viewRating": null,
+ "scheduleColor": null,
+ "bgColor": null,
+ "bgColorR": null,
+ "bgColorG": null,
+ "bgColorB": null,
+ "textColorCd": null,
+ "textColorR": null,
+ "textColorG": null,
+ "textColorB": null,
+ "textColorHex": null,
+ "scheduleLineYn": "N",
+ "mediaInfo": null,
+ "regClf": "0",
+ "uuid": null,
+ "regUserId": "kylek",
+ "regDt": "20230816112556023",
+ "updUserId": "kylek",
+ "updDt": "20230817094411 ",
+ "weekDay": null,
+ "mtrlYn": null,
+ "timeGradeColor": null,
+ "timeGradeNm": "SA",
+ "broadClfNm": "재방",
+ "broadTypeNm": null,
+ "delibGradeNm": null,
+ "newsYn": "Y",
+ "bundlePgmCd": "2023003T",
+ "bundlePgmNm": "WITHIN THE FRAME",
+ "pgmOnm": "WITHIN THE FRAME [R]"
+ }
+ ],
+ "dmResult": {
+ "resultCode": "0",
+ "resultMsg": "success"
+ }
+ },
+ "responseXML": null
+}
\ No newline at end of file
diff --git a/sites/arirang.com/arirang.com.channels.xml b/sites/arirang.com/arirang.com.channels.xml
new file mode 100644
index 00000000..140ab525
--- /dev/null
+++ b/sites/arirang.com/arirang.com.channels.xml
@@ -0,0 +1,8 @@
+
+
+
+ Arirang TV
+ Arirang UN
+ Arirang World
+
+
\ No newline at end of file
diff --git a/sites/arirang.com/arirang.com.config.js b/sites/arirang.com/arirang.com.config.js
new file mode 100644
index 00000000..d0c133d5
--- /dev/null
+++ b/sites/arirang.com/arirang.com.config.js
@@ -0,0 +1,139 @@
+const axios = require('axios')
+const dayjs = require('dayjs')
+const utc = require('dayjs/plugin/utc')
+const timezone = require('dayjs/plugin/timezone')
+const customParseFormat = require('dayjs/plugin/customParseFormat')
+dayjs.extend(utc)
+dayjs.extend(timezone)
+dayjs.extend(customParseFormat)
+
+module.exports = {
+ site: 'arirang.com',
+ output: 'arirang.com.guide.xml',
+ channels: 'arirang.com.channels.xml',
+ lang: 'en',
+ days: 7,
+ delay: 5000,
+ url: 'https://www.arirang.com/v1.0/open/external/proxy',
+
+ request: {
+ method: 'POST',
+ timeout: 5000,
+ cache: { ttl: 60 * 60 * 1000 },
+ headers: {
+ 'Accept': 'application/json, text/plain, */*',
+ 'Content-Type': 'application/json',
+ 'Origin': 'https://www.arirang.com',
+ 'Referer': 'https://www.arirang.com/schedule',
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'
+ },
+ data: function (context) {
+ const { channel, date } = context
+ return {
+ 'address': 'https://script.arirang.com/api/v1/bis/listScheduleV3.do',
+ 'method': 'POST',
+ 'headers': {},
+ 'body': {
+ 'data': {
+ 'dmParam': {
+ 'chanId': channel.site_id,
+ 'broadYmd': dayjs.tz(date, 'Asia/Seoul').format('YYYYMMDD'),
+ 'planNo': '1'
+ }
+ }
+ }
+ }
+ }
+ },
+
+ logo: function (context) {
+ return context.channel.logo
+ },
+
+ async parser(context) {
+ const programs = []
+ const items = parseItems(context.content)
+
+ for (let item of items) {
+ const programDetail = await parseProgramDetail(item)
+
+ programs.push({
+ title: item.displayNm,
+ start: parseStart(item),
+ stop: parseStop(item),
+ icon: parseIcon(programDetail),
+ category: parseCategory(programDetail),
+ description: parseDescription(programDetail)
+ })
+ }
+
+ return programs
+ }
+}
+
+function parseItems(content) {
+ if (content != '') {
+ const data = JSON.parse(content)
+ return (!data || !data.responseBody || !Array.isArray(data.responseBody.dsSchWeek)) ? [] : data.responseBody.dsSchWeek
+ } else {
+ return []
+ }
+}
+
+function parseStart(item) {
+ return dayjs.tz(item.broadYmd + ' ' + item.broadHm, 'YYYYMMDD HHmm', 'Asia/Seoul')
+}
+
+function parseStop(item) {
+ return dayjs.tz(item.broadYmd + ' ' + item.broadHm, 'YYYYMMDD HHmm', 'Asia/Seoul').add(item.broadRun, 'minute')
+}
+
+async function parseProgramDetail(item) {
+ return axios.post(
+ 'https://www.arirang.com/v1.0/open/program/detail',
+ {
+ 'bis_program_code': item.pgmCd
+ },
+ {
+ headers: {
+ 'Accept': 'application/json, text/plain, */*',
+ 'Content-Type': 'application/json',
+ 'Origin': 'https://www.arirang.com',
+ 'Referer': 'https://www.arirang.com/schedule',
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'
+ },
+ timeout: 5000,
+ cache: { ttl: 60 * 1000 },
+ }
+ ).then(function (response) {
+ return response.data
+ }).catch(function (error) {
+ // console.log(error)
+ })
+}
+
+function parseIcon(programDetail) {
+ if (programDetail && programDetail.image && programDetail.image[0].url) {
+ return programDetail.image[0].url
+ } else {
+ return ''
+ }
+}
+
+function parseCategory(programDetail) {
+ if (programDetail && programDetail.category_Info && programDetail.category_Info[0].title) {
+ return programDetail.category_Info[0].title
+ } else {
+ return ''
+ }
+}
+
+function parseDescription(programDetail) {
+ if (programDetail && programDetail.content && programDetail.content[0] && programDetail.content[0].text) {
+ let description = programDetail.content[0].text
+ let regex = /(<([^>]+)>)/ig
+ return description.replace(regex, '')
+ } else {
+ return ''
+ }
+}
diff --git a/sites/arirang.com/arirang.com.test.js b/sites/arirang.com/arirang.com.test.js
new file mode 100644
index 00000000..f0d4d165
--- /dev/null
+++ b/sites/arirang.com/arirang.com.test.js
@@ -0,0 +1,59 @@
+// npx epg-grabber --config=sites/arirang.com/arirang.com.config.js --channels=sites/arirang.com/arirang.com.channels.xml --output=guide.xml --days=2
+// npx jest arirang.com.test.js
+
+const { url, parser } = require('./arirang.com.config.js')
+const fs = require('fs')
+const path = require('path')
+const axios = require('axios')
+const dayjs = require('dayjs')
+const utc = require('dayjs/plugin/utc')
+const { program } = require('commander')
+dayjs.extend(utc)
+
+jest.mock('axios')
+
+const date = dayjs.tz('2023-08-25', 'Asia/Seoul').startOf('d')
+const channel = { xmltv_id: 'ArirangWorld.kr', site_id: 'CH_W', name: 'Arirang World', lang: 'en', logo: 'https://i.imgur.com/5Aoithj.png' }
+const content = fs.readFileSync(path.resolve(__dirname, '__data__/schedule.json'), 'utf8')
+const programDetail = fs.readFileSync(path.resolve(__dirname, '__data__/detail.json'), 'utf8')
+const context = { 'channel': channel, 'content': content, 'date': date }
+
+it('can generate valid url', () => {
+ expect(url).toBe('https://www.arirang.com/v1.0/open/external/proxy')
+})
+
+it('can handle empty guide', async () => {
+ const results = await parser({ 'channel': channel, 'content': '', 'date': date })
+ expect(results).toMatchObject([])
+})
+
+it('can parse response', async () => {
+ axios.post.mockImplementation((url, data) => {
+ if (url === 'https://www.arirang.com/v1.0/open/external/proxy' && JSON.stringify(data) === JSON.stringify({ "address": "https://script.arirang.com/api/v1/bis/listScheduleV3.do", "method": "POST", "headers": {}, "body": { "data": { "dmParam": { "chanId": "CH_W", "broadYmd": "20230825", "planNo": "1" } } } })) {
+ return Promise.resolve({
+ data: JSON.parse(content)
+ })
+ } else if (url === 'https://www.arirang.com/v1.0/open/program/detail' && JSON.stringify(data) === JSON.stringify({ "bis_program_code": "2023004T" })) {
+ return Promise.resolve({
+ data: JSON.parse(programDetail)
+ })
+ } else {
+ return Promise.resolve({
+ data: ''
+ })
+ }
+ })
+
+ const results = await parser(context)
+
+ expect(results[0]).toMatchObject(
+ {
+ title: "WITHIN THE FRAME [R]",
+ start: dayjs.tz(date, 'Asia/Seoul'),
+ stop: dayjs.tz(date, 'Asia/Seoul').add(30, 'minute'),
+ icon: "https://img.arirang.com/v1/AUTH_d52449c16d3b4bbca17d4fffd9fc44af/public/images/202308/2080840096998752900.png",
+ description: "NEWS",
+ category: "Current Affairs"
+ }
+ )
+})
\ No newline at end of file
diff --git a/sites/moji.id/moji.id.channels.xml b/sites/moji.id/moji.id.channels.xml
new file mode 100644
index 00000000..5157482b
--- /dev/null
+++ b/sites/moji.id/moji.id.channels.xml
@@ -0,0 +1,7 @@
+
+
+
+ Moji
+
+
\ No newline at end of file
diff --git a/sites/moji.id/moji.id.config.js b/sites/moji.id/moji.id.config.js
new file mode 100644
index 00000000..b9c4ffe3
--- /dev/null
+++ b/sites/moji.id/moji.id.config.js
@@ -0,0 +1,104 @@
+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')
+
+dayjs.extend(utc)
+dayjs.extend(timezone)
+dayjs.extend(customParseFormat)
+
+const currentYear = new Date().getFullYear()
+
+module.exports = {
+ site: 'moji.id',
+ days: 4,
+ output: 'moji.id.guide.xml',
+ channels: 'moji.id.channels.xml',
+ lang: 'en',
+ delay: 5000,
+
+ url: function () {
+ return 'https://moji.id/schedule'
+ },
+
+ request: {
+ method: 'GET',
+ timeout: 5000,
+ cache: { ttl: 60 * 60 * 1000 },
+ headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36' }
+ },
+
+ logo: function (context) {
+ return context.channel.logo
+ },
+
+ parser: function (context) {
+ const programs = []
+ const items = parseItems(context)
+
+ items.forEach(function(item, i) {
+ programs.push({
+ title: item.progTitle,
+ description: item.progDesc,
+ start: item.progStart,
+ stop: item.progStop
+ })
+ })
+
+ return programs
+ }
+}
+
+function parseItems(context) {
+ const $ = cheerio.load(context.content)
+ const schDayMonths = $('.date-slider .month').toArray()
+ const schPrograms = $('.desc-slider .list-slider').toArray()
+ const monthDate = dayjs(context.date).format('MMM DD')
+ const items = [];
+
+ schDayMonths.forEach(function(schDayMonth, i) {
+ if (monthDate == $(schDayMonth).text()) {
+ let schDayPrograms = $(schPrograms[i]).find('.accordion').toArray()
+ schDayPrograms.forEach(function(program, i) {
+ let itemDay = {
+ progStart: parseStart(schDayMonth, program),
+ progStop: parseStop(schDayMonth, program, schDayPrograms[i+1]),
+ progTitle: parseTitle(program),
+ progDesc: parseDescription(program)
+ };
+ items.push(itemDay)
+ })
+ }
+ })
+
+ return items
+}
+
+function parseTitle(item) {
+ return cheerio.load(item)('.name-prog').text()
+}
+
+function parseDescription(item) {
+ return cheerio.load(item)('.content-acc span').text()
+}
+
+function parseStart(schDayMonth, item) {
+ let monthDate = cheerio.load(schDayMonth).text().split(' ')
+ let startTime = cheerio.load(item)('.pkl').text()
+ let progStart = dayjs.tz(currentYear + ' ' + monthDate[0] + ' ' + monthDate[1] + ' ' + startTime, 'YYYY MMM DD HH:mm', 'Asia/Jakarta')
+ return progStart
+}
+
+function parseStop(schDayMonth, itemCurrent, itemNext) {
+ let monthDate = cheerio.load(schDayMonth).text().split(' ')
+
+ if (itemNext) {
+ let stopTime = cheerio.load(itemNext)('.pkl').text()
+ return dayjs.tz(currentYear + ' ' + monthDate[0] + ' ' + monthDate[1] + ' ' + stopTime, 'YYYY MMM DD HH:mm', 'Asia/Jakarta')
+ }
+ else
+ {
+ return dayjs.tz(currentYear + ' ' + monthDate[0] + ' ' + (parseInt(monthDate[1]) + 1).toString().padStart(2, '0') + ' 00:00', 'YYYY MMM DD HH:mm', 'Asia/Jakarta')
+ }
+}
\ No newline at end of file
diff --git a/sites/moji.id/moji.id.test.js b/sites/moji.id/moji.id.test.js
new file mode 100644
index 00000000..b579aa37
--- /dev/null
+++ b/sites/moji.id/moji.id.test.js
@@ -0,0 +1,39 @@
+// npx epg-grabber --config=sites/moji.id/moji.id.config.js --channels=sites/moji.id/moji.id.channels.xml --output=guide.xml --days=2
+// npx jest moji.id.test.js
+
+const { url, parser } = require('./moji.id.config.js')
+const dayjs = require('dayjs')
+const utc = require('dayjs/plugin/utc')
+dayjs.extend(utc)
+
+const date = dayjs.utc('2023-08-18', 'YYYY-MM-DD').startOf('d')
+const channel = { site_id: '0', xmltv_id: 'moji.id', lang: 'en', logo: 'https://moji.id/site/uploads/logo/62f9387ce00a2-224-x-71.png' }
+
+const content = `schedule
Jam TayangProgram
00:00TRUST
Informasi seputar menjaga vitalitas pria
00:302023 AVC CHALLENGE CUP FOR WOMEN (RECORDED)
India Vs. Vietnam
02:30ONE CHAMPIONSHIP 2021
Siaran laga-laga pertandingan tinju gaya bebas internasional. Meyuguhkan pertarungan sengit dari para petarung profeisional kelas dunia.
03:30VOLLEYBALL NATION'S LEAGUE 2023 (RECORDED)
TURKI vs BRAZIL
05:00MOJI SPORT
MOJI SPORT
06:15LIPUTAN 6 PAGI MOJI
Kompilasi ragam berita hard news dan soft news baik dari dalam negeri maupun internasional juga info prediksi cuaca di wilayah Indonesia
07:00UNGKAP
Liputan investigasi seputar berbagai topik dan peristiwa hangat serta kontroversial yang terjadi di Indonesia
08:00PIALA KAPOLRI 2023 PUTRI (LIVE)
PIALA KAPOLRI 2023 PUTRI (LIVE)
10:30SERIES PAGI
GANTENG GANTENG SERIGALA
12:30DIAM-DIAM SUKA
DIAM-DIAM SUKA
13:30PIALA KAPOLRI 2023 PUTRA (LIVE)
PIALA KAPOLRI 2023 PUTRA (LIVE)
16:00PIALA KAPOLRI 2023 PUTRI (LIVE)
PIALA KAPOLRI 2023 PUTRI (LIVE)
18:00PIALA KAPOLRI 2023 PUTRA (LIVE)
PIALA KAPOLRI 2023 PUTRA (LIVE)
20:00MOJI DRAMA (CHHOTI SARDARNI)
CHHOTI SARDARNI
21:30SINEMA MALAM (BIDADARI CANTIK DI RUMAH KOST)
(BIDADARI CANTIK DI RUMAH KOST
23:00TRUST
Informasi seputar menjaga vitalitas pria
23:30TRUST
Informasi seputar menjaga vitalitas pria
00:00TRUST
Informasi seputar menjaga vitalitas pria
00:30UNGKAP
Liputan investigasi seputar berbagai topik dan peristiwa hangat serta kontroversial yang terjadi di Indonesia
01:00EXCLUSIVE INTERVIEW
PETER SCHMEICHEL
01:30PREMIER LEAGUE (LIVE)
NOTT'M FOREST VS SHEFFIELD UTD
03:30VOLLEYBALL NATION'S LEAGUE 2023 FOR WOMEN (RECORDED)
GERMANY vs USA
05:00MOJI SPORT
MOJI SPORT
06:15LIPUTAN 6 PAGI MOJI
Kompilasi ragam berita hard news dan soft news baik dari dalam negeri maupun internasional juga info prediksi cuaca di wilayah Indonesia
07:00UNGKAP
Liputan investigasi seputar berbagai topik dan peristiwa hangat serta kontroversial yang terjadi di Indonesia
08:00TRUST
Informasi Kesehatan
08:30TRUST
Informasi Kesehatan
09:00MASAK APA CEU?
Jenny Hendrawati, seorang koki selebritis, menyajikan beraneka hidangan internasional serta membagikan tips-tips seputar dunia memasak yang mudah diikuti oleh para pemirsa di rumah.
09:30BISIK PAGI
berbagai berita dan perkembangan terbaru dari sederet kasus yang menimpa sejumlah sosok ternama di Indonesia.
10:30GANTENG GANTENG SERIGALA
GANTENG GANTENG SERIGALA
11:30PIALA KAPOLRI 2023 PUTRI (LIVE)
PIALA KAPOLRI 2023 PUTRI (LIVE)
14:00PIALA KAPOLRI 2023 PUTRA (LIVE)
PIALA KAPOLRI 2023 PUTRA (LIVE)
16:00PIALA KAPOLRI 2023 PUTRI (LIVE)
PIALA KAPOLRI 2023 PUTRI (LIVE)
18:00PIALA KAPOLRI 2023 PUTRA (LIVE)
PIALA KAPOLRI 2023 PUTRA (LIVE)
20:00MOJI DRAMA (CHHOTI SARDARNI)
CHHOTI SARDARNI
20:45PREMIER LEAGUE (LIVE)
WOLVES VS BRIGHTON
00:00TRUST
Informasi seputar menjaga vitalitas pria
00:302023 AVC CHALLENGE CUP FOR WOMEN (RECORDED)
Chinese Taipei Vs. India
02:30ONE CHAMPIONSHIP 2021
Siaran laga-laga pertandingan tinju gaya bebas internasional. Meyuguhkan pertarungan sengit dari para petarung profeisional kelas dunia.
03:30VOLLEYBALL NATION'S LEAGUE 2023 FOR WOMEN (RECORDED)
THAILAND vs JAPAN
05:00MOJI SPORT
MOJI SPORT
06:15LIPUTAN 6 PAGI MOJI
Kompilasi ragam berita hard news dan soft news baik dari dalam negeri maupun internasional juga info prediksi cuaca di wilayah Indonesia
07:00UNGKAP
Liputan investigasi seputar berbagai topik dan peristiwa hangat serta kontroversial yang terjadi di Indonesia
08:00TRUST
Informasi Kesehatan
08:30TRUST
Informasi Kesehatan
09:00MASAK APA CEU?
Jenny Hendrawati, seorang koki selebritis, menyajikan beraneka hidangan internasional serta membagikan tips-tips seputar dunia memasak yang mudah diikuti oleh para pemirsa di rumah.
09:30BISIK PAGI
berbagai berita dan perkembangan terbaru dari sederet kasus yang menimpa sejumlah sosok ternama di Indonesia.
10:30GANTENG GANTENG SERIGALA
GANTENG GANTENG SERIGALA
12:30DIAM-DIAM SUKA
DIAM-DIAM SUKA
16:00SEA VLEAGUE (RECORDED)
PHILIPPINES VS INDONESIA
18:00NETIZEN UPDATE
berbagai berita dan perkembangan terbaru dari sederet kasus yang menimpa sejumlah sosok ternama di Indonesia.
19:00MOJI DRAMA (CHHOTI SARDARNI)
CHHOTI SARDARNI
21:00MOJI MOVIE (LONDON LOVE STORY)
LONDON LOVE STORY
23:00TRUST
Informasi seputar menjaga vitalitas pria
23:30TRUST
Informasi seputar menjaga vitalitas pria
`
+
+const context = {
+ 'content': content,
+ 'date': date
+}
+
+it('can generate valid url', () => {
+ expect(url({ channel, date })).toBe('https://moji.id/schedule')
+})
+
+it('can handle empty guide', () => {
+ const results = parser({ content: '' })
+ expect(results).toMatchObject([])
+})
+
+it('can parse response', () => {
+ const results = parser({ content: content, date: date })
+
+ expect(results[0]).toMatchObject(
+ {
+ title: 'TRUST',
+ start: dayjs.tz('2023 Aug 18 00:00', 'YYYY MMM DD HH:mm', 'Asia/Jakarta'),
+ stop: dayjs.tz('2023 Aug 18 00:30', 'YYYY MMM DD HH:mm', 'Asia/Jakarta'),
+ description: 'Informasi seputar menjaga vitalitas pria'
+ }
+ )
+})
\ No newline at end of file
diff --git a/sites/taiwanplus.com/taiwanplus.com.channels.xml b/sites/taiwanplus.com/taiwanplus.com.channels.xml
new file mode 100644
index 00000000..320a0ec0
--- /dev/null
+++ b/sites/taiwanplus.com/taiwanplus.com.channels.xml
@@ -0,0 +1,7 @@
+
+
+
+ Taiwan Plus TV
+
+
\ No newline at end of file
diff --git a/sites/taiwanplus.com/taiwanplus.com.config.js b/sites/taiwanplus.com/taiwanplus.com.config.js
new file mode 100644
index 00000000..d8cb4896
--- /dev/null
+++ b/sites/taiwanplus.com/taiwanplus.com.config.js
@@ -0,0 +1,68 @@
+const dayjs = require('dayjs')
+const utc = require('dayjs/plugin/utc')
+const isSameOrAfter = require('dayjs/plugin/isSameOrAfter')
+const isSameOrBefore = require('dayjs/plugin/isSameOrBefore')
+
+dayjs.extend(utc)
+dayjs.extend(isSameOrAfter)
+dayjs.extend(isSameOrBefore)
+
+module.exports = {
+ site: 'taiwanplus.com',
+ days: 7,
+ output: 'taiwanplus.com.guide.xml',
+ channels: 'taiwanplus.com.channels.xml',
+ lang: 'en',
+ delay: 5000,
+
+ url: function () {
+ return 'https://www.taiwanplus.com/api/video/live/schedule/0'
+ },
+
+ request: {
+ method: 'GET',
+ timeout: 5000,
+ cache: { ttl: 60 * 60 * 1000 }, // 60 * 60 seconds = 1 hour
+ headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36' }
+ },
+
+ logo: function (context) {
+ return context.channel.logo
+ },
+
+ parser: function (context) {
+ const programs = []
+ const scheduleDates = parseItems(context.content)
+ const today = dayjs.utc(context.date).startOf('day')
+ const lastDay = today.add(1, 'day')
+
+ for(let scheduleDate of scheduleDates) {
+ const currentScheduleDate = new dayjs.utc(scheduleDate.date, 'YYYY/MM/DD')
+
+ if (currentScheduleDate.isSame(today)) {
+ scheduleDate.schedule.forEach(function(program, i) {
+ programs.push({
+ title: program.title,
+ start: dayjs.utc(program.dateTime, 'YYYY/MM/DD HH:mm'),
+ stop: (i != (scheduleDate.schedule.length - 1)) ? dayjs.utc(scheduleDate.schedule[i+1].dateTime, 'YYYY/MM/DD HH:mm') : dayjs.utc(program.dateTime, 'YYYY/MM/DD HH:mm').add(1, 'day').startOf('day'),
+ description: program.description,
+ icon: program.image,
+ category: program.categoryName,
+ rating: program.ageRating
+ })
+ });
+ }
+ }
+
+ return programs
+ }
+}
+
+function parseItems(content) {
+ if (content != '') {
+ const data = JSON.parse(content)
+ return (!data || !data.data || !Array.isArray(data.data)) ? [] : data.data
+ } else {
+ return []
+ }
+}
\ No newline at end of file
diff --git a/sites/taiwanplus.com/taiwanplus.com.test.js b/sites/taiwanplus.com/taiwanplus.com.test.js
new file mode 100644
index 00000000..dace4883
--- /dev/null
+++ b/sites/taiwanplus.com/taiwanplus.com.test.js
@@ -0,0 +1,38 @@
+// npx epg-grabber --config=sites/taiwanplus.com/taiwanplus.com.config.js --channels=sites/taiwanplus.com/taiwanplus.com.channels.xml --output=guide.xml --days=3
+// npx jest taiwanplus.com.test.js
+
+const { url, parser } = require('./taiwanplus.com.config.js')
+const dayjs = require('dayjs')
+const utc = require('dayjs/plugin/utc')
+dayjs.extend(utc)
+
+const date = dayjs.utc('2023-08-20', 'YYYY-MM-DD').startOf('d')
+const channel = { site_id: '#', xmltv_id: 'TaiwanPlusTV.tw', lang: 'en', logo: 'https://i.imgur.com/SfcZyqm.png' }
+
+it('can generate valid url', () => {
+ expect(url({ channel, date })).toBe('https://www.taiwanplus.com/api/video/live/schedule/0')
+})
+
+it('can parse response', () => {
+ const content = `{"data":[{"date":"2023/08/20","weekday":"SUN","schedule":[{"programId":30668,"dateTime":"2023/08/20 00:00","time":"00:00","image":"https://prod-img.taiwanplus.com/live-schedule/Single/S30668_20230810104937.webp","title":"Master Class","shortDescription":"From blockchain to Buddha statues, Taiwan’s culture is a kaleidoscope of old and new just waiting to be discovered.","description":"From blockchain to Buddha statues, Taiwan’s culture is a kaleidoscope of old and new just waiting to be discovered.","ageRating":"0+","programWebSiteType":"4","url":"","vodId":null,"categoryId":90000474,"categoryType":2,"categoryName":"TaiwanPlus ✕ Discovery","categoryFullPath":"Originals/TaiwanPlus ✕ Discovery","encodedCategoryFullPath":"originals/taiwanplus-discovery"}]}],"success":true,"code":"0000","message":""}`
+
+ const results = parser({ content, date })
+
+ expect(results).toMatchObject([
+ {
+ title: 'Master Class',
+ start: dayjs.utc('2023/08/20 00:00', 'YYYY/MM/DD HH:mm'),
+ stop: dayjs.utc('2023/08/21 00:00', 'YYYY/MM/DD HH:mm'),
+ description: `From blockchain to Buddha statues, Taiwan’s culture is a kaleidoscope of old and new just waiting to be discovered.`,
+ icon: 'https://prod-img.taiwanplus.com/live-schedule/Single/S30668_20230810104937.webp',
+ category: 'TaiwanPlus ✕ Discovery',
+ rating: '0+'
+ }
+ ])
+})
+
+it('can handle empty guide', () => {
+ const results = parser({ content: '' })
+
+ expect(results).toMatchObject([])
+})