diff --git a/sites/rotana.net/__data__/content_ar.html b/sites/rotana.net/__data__/content_ar.html new file mode 100644 index 00000000..4c72c90d --- /dev/null +++ b/sites/rotana.net/__data__/content_ar.html @@ -0,0 +1,203 @@ +
+
+

+ السبت + 11/11/2023 +

+
+
+
+
+
+ 02:00 + + حريم كريم +
+
+ +
+
+
\ No newline at end of file diff --git a/sites/rotana.net/__data__/content_en.html b/sites/rotana.net/__data__/content_en.html new file mode 100644 index 00000000..893fd79c --- /dev/null +++ b/sites/rotana.net/__data__/content_en.html @@ -0,0 +1,203 @@ +
+
+

+ Saturday + 11/11/2023 +

+
+
+
+
+
+ 02:00 + + Harim Karim +
+
+ +
+
+
\ No newline at end of file diff --git a/sites/rotana.net/rotana.net.channels.xml b/sites/rotana.net/rotana.net.channels.xml deleted file mode 100644 index 96f48d1b..00000000 --- a/sites/rotana.net/rotana.net.channels.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - LBC - Rotana Aflam+ - Rotana America - Rotana Cinema Egypt - Rotana Cinema KSA - Rotana Classic - Rotana Comedy - Rotana Drama - Rotana Khalejia - Rotana Kids - Rotana+ - \ No newline at end of file diff --git a/sites/rotana.net/rotana.net.config.js b/sites/rotana.net/rotana.net.config.js index 6ad86acb..22dd63a9 100644 --- a/sites/rotana.net/rotana.net.config.js +++ b/sites/rotana.net/rotana.net.config.js @@ -1,66 +1,106 @@ -const stream = require('stream') -const csv = require('csv-parser') +const cheerio = require('cheerio') const dayjs = require('dayjs') +const timezone = require('dayjs/plugin/timezone') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(timezone) dayjs.extend(utc) dayjs.extend(customParseFormat) +const headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 OPR/104.0.0.0' +} + module.exports = { site: 'rotana.net', days: 2, url({ channel }) { - return `https://rotana.net/triAssets/uploads/2020/11/${channel.site_id}.csv` + return `https://rotana.net/${channel.lang}/streams?channel=${channel.site_id}` }, request: { - method: 'POST' + headers, + timeout: 15000 }, - parser: async function ({ buffer, date }) { - let programs = [] - const items = await parseItems(buffer, date) + parser({ content, date }) { + const programs = [] + + const items = parseItems(content, date) items.forEach(item => { - const start = parseStart(item) - const stop = parseStop(item) - programs.push({ - title: item['Arabic Event Name'], - category: item['Genre'], - description: item['Arabic Extended Description'], - start: start.toJSON(), - stop: stop.toJSON() - }) + const info = item.find('.iq-accordion-block > .iq-accordion-title .big-title span') + if (info.length) { + const details = item.find('.trending-info div > span') + const [ time, title ] = info.text().split('\n') + const [ _, duration, description ] = details.text().split('\n') + if (duration) { + const start = dayjs.tz(`${date.format('YYYY-MM-DD')} ${time.trim()}`, 'YYYY-MM-DD HH:mm', 'Asia/Riyadh') + const stop = addDuration(start, duration.trim()) + + programs.push({ + title: title.trim(), + description: description.trim(), + start: start.toISOString(), + stop: stop.toISOString() + }) + } + } }) return programs + }, + async channels({ lang = 'en'}) { + const axios = require('axios') + const options = {headers} + const result = await axios + .get(`https://rotana.net/${lang}/streams`, options) + .then(response => response.data) + .catch(console.error) + + const $ = cheerio.load(result) + const items = $('#channels-list a').toArray() + const channels = items.map(item => { + const $item = $(item) + const data = $item.attr('href').match(/channel=([A-Za-z0-9]+)/) + + return { + lang, + site_id: data[1], + name: $item.text().trim() + } + }) + + return channels + } +} + +function addDuration(date, duration) { + const matches = duration.matchAll(/(\d+)(h|m|s|ms)/g) + while (true) { + const m = matches.next() + if (!m.value) { + break + } + if (m.value[1] && m.value[2]) { + date = date.add(parseInt(m.value[1]), m.value[2]) + } } + return date } -function parseStart(item) { - const time = `${item['Start Date']} ${item['Start Time']}` +function parseItems(content, date) { + const result = [] + const $ = cheerio.load(content) - return dayjs.utc(time, 'DD/MM/YYYY HH:mm:ss:00') -} - -function parseStop(item) { - const time = `${item['End Date']} ${item['End Time']}` - - return dayjs.utc(time, 'DD/MM/YYYY HH:mm:ss:00') -} - -function parseItems(buffer, date) { - return new Promise(resolve => { - let items = [] - const input = new stream.PassThrough() - input.end(buffer) - input - .pipe(csv()) - .on('data', data => items.push(data)) - .on('end', () => { - items = items.filter(i => i['Start Date'] === date.format('DD/MM/YYYY')) - resolve(items) - }) - .on('error', () => { - resolve([]) - }) + const expectedId = `item-${date.format('DD-MM-YYYY')}` + let lastId + $('.hour > div').toArray().forEach(item => { + const $item = $(item) + if ($item.hasClass('bg')) { + lastId = $item.attr('id') + } else if ($item.hasClass('iq-accordion') && lastId === expectedId) { + result.push($item) + } }) + + return result } diff --git a/sites/rotana.net/rotana.net.test.js b/sites/rotana.net/rotana.net.test.js index a006b383..c2213f15 100644 --- a/sites/rotana.net/rotana.net.test.js +++ b/sites/rotana.net/rotana.net.test.js @@ -1,57 +1,75 @@ // npm run grab -- --site=rotana.net -const { parser, url } = require('./rotana.net.config.js') +const { parser, url, request } = require('./rotana.net.config.js') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') + dayjs.extend(customParseFormat) dayjs.extend(utc) -const date = dayjs.utc('2021-11-08', 'YYYY-MM-DD').startOf('d') +const date = dayjs.utc('2023-11-11').startOf('d') const channel = { - site_id: 'KHALIJIA-7', - xmltv_id: 'RotanaKhalejia.sa' + lang: 'en', + site_id: '640688871275c9aaa905902a', + xmltv_id: 'RotanaCinemaMasr.sa' +} +const channelAr = { + lang: 'ar', + site_id: '640688871275c9aaa905902a', + xmltv_id: 'RotanaCinemaMasr.sa' } -const buffer = - Buffer.from(`Event ID,Event Name,Arabic Event Name,Start Date,Start Time,End Date,End Time,Short Description,Arabic Short Description,Extended Description,Arabic Extended Description,,Genre,Audio,Video -,حسب الظروف,حسب الظروف بدون تترات - Episode 16,07/11/2021,23:30:00:00,08/11/2021,00:00:00:00,,,,,,Drama,, -,كورة,كورة,08/11/2021,01:30:00:00,08/11/2021,03:00:00:00,,,,,,Generic,,`) -it('can generate valid url', () => { +it('can use defined user agent', () => { + const result = request.headers['User-Agent'] + expect(result).toBe('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 OPR/104.0.0.0') +}) + +it('can generate valid english url', () => { const result = url({ channel, date }) - expect(result).toBe('https://rotana.net/triAssets/uploads/2020/11/KHALIJIA-7.csv') + expect(result).toBe('https://rotana.net/en/streams?channel=640688871275c9aaa905902a') }) -it('can parse response', done => { - parser({ date, channel, buffer }) - .then(result => { - expect(result).toMatchObject([ - { - start: '2021-11-08T01:30:00.000Z', - stop: '2021-11-08T03:00:00.000Z', - title: 'كورة', - category: 'Generic', - description: '' - } - ]) - done() - }) - .catch(() => { - done() - }) +it('can generate valid arabic url', () => { + const result = url({ channel: channelAr, date }) + expect(result).toBe('https://rotana.net/ar/streams?channel=640688871275c9aaa905902a') }) -it('can handle empty guide', done => { - parser({ +it('can parse english response', () => { + const fs = require('fs') + const path = require('path') + + const result = parser({ channel, date, content: fs.readFileSync(path.join(__dirname, '/__data__/content_en.html'))}) + expect(result).toMatchObject([ + { + start: '2023-11-10T23:00:00.000Z', + stop: '2023-11-11T01:00:00.000Z', + title: 'Harim Karim', + description: 'Karim and Jihan separate after a year of marriage due to her discovering his betrayal in her home. Karim tries to get his wife back, but she refuses. Karim calls his old colleague Maha to help him. Ho...' + } + ]) +}) + +it('can parse arabic response', () => { + const fs = require('fs') + const path = require('path') + + const result = parser({ channelAr, date, content: fs.readFileSync(path.join(__dirname, '/__data__/content_ar.html'))}) + expect(result).toMatchObject([ + { + start: '2023-11-10T23:00:00.000Z', + stop: '2023-11-11T01:00:00.000Z', + title: 'حريم كريم', + description: 'كريم وجيهان ينفصلا بعد عام من الزواج بسبب اكتشافها لخيانته في منزلها، يحاول كريم استعادة زوجته، لكنها ترفض، فيتصل كريم بزميلته القديمة مها، لتساعده، لكن متاعب تحدث بين مها وزوجها، فتأتي لتعيش مع كريم،...' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '', date, - channel, - buffer: Buffer.from('') + channel }) - .then(result => { - expect(result).toMatchObject([]) - done() - }) - .catch(() => { - done() - }) + expect(result).toMatchObject([]) }) diff --git a/sites/rotana.net/rotana.net_ar.channels.xml b/sites/rotana.net/rotana.net_ar.channels.xml new file mode 100644 index 00000000..94264893 --- /dev/null +++ b/sites/rotana.net/rotana.net_ar.channels.xml @@ -0,0 +1,16 @@ + + + الرساله + قناة رسالة الدولية + ضحك وبس + إل بي سي + رومانس + روتانا سينما مصر + روتانا سينما السعودية + روتانا كلاسيك + روتانا كليب + روتانا كوميدي + روتانا دراما + روتانا خليجية + سيدة الشاشة + diff --git a/sites/rotana.net/rotana.net_en.channels.xml b/sites/rotana.net/rotana.net_en.channels.xml new file mode 100644 index 00000000..4d5470fe --- /dev/null +++ b/sites/rotana.net/rotana.net_en.channels.xml @@ -0,0 +1,16 @@ + + + Al Resalah + Al Resalah International + Dahk wa Bass + LBC + Romance + Rotana Cinema Masr + Rotana Cinema KSA + Rotana Classic + Rotana Clip + Rotana Comedy + Rotana Drama + Rotana Khalijea HD + Sayedat Alshasha +