Replace LF endings with CRLF

This commit is contained in:
freearhey
2025-07-31 22:29:01 +03:00
parent 17e3b4ddda
commit 29aa427923
379 changed files with 29332 additions and 29332 deletions

View File

@@ -1,189 +1,189 @@
const axios = require('axios')
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')
const doFetch = require('@ntlab/sfetch')
const debug = require('debug')('site:rotana.net')
dayjs.extend(timezone)
dayjs.extend(utc)
dayjs.extend(customParseFormat)
doFetch.setCheckResult(false).setDebugger(debug)
const tz = 'Asia/Riyadh'
const defaultHeaders = {
'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'
}
const cookies = {}
module.exports = {
site: 'rotana.net',
days: 2,
url({ channel }) {
return `https://rotana.net/${channel.lang}/streams?channel=${channel.site_id}&tz=`
},
request: {
headers: defaultHeaders,
timeout: 15000
},
async parser({ content, headers, channel, date }) {
const programs = []
if (!cookies[channel.lang]) {
cookies[channel.lang] = parseCookies(headers)
}
const items = parseItems(content, date)
if (items.length) {
const queues = []
for (const item of items) {
const url = `https://rotana.net/${channel.lang}/streams?channel=${channel.site_id}&itemId=${item.program}`
const params = {
headers: {
...defaultHeaders,
'X-Requested-With': 'XMLHttpRequest',
cookie: cookies[channel.lang]
}
}
queues.push({ i: item, url, params })
}
await doFetch(queues, (queue, res) => {
programs.push(parseProgram(queue.i, res))
})
}
return programs
},
async channels({ lang = 'en' }) {
const result = await axios
.get('https://rotana.net/api/channels')
.then(response => response.data)
.catch(console.error)
return result.data.map(item => {
return {
lang,
site_id: item.id,
name: item.name[lang]
}
})
}
}
function parseProgram(item, result) {
const $ = cheerio.load(result)
const details = $('.trending-info .row div > span')
if (details.length) {
for (const el of details[0].children) {
switch (el.constructor.name) {
case 'Text':
if (item.description === undefined) {
const desc = $(el).text().trim()
if (desc) {
item.description = desc
}
}
break
case 'Element':
if (el.name === 'span') {
const [k, v] = $(el)
.text()
.split(':')
.map(a => a.trim())
switch (k) {
case 'Category':
case 'التصنيف':
item.category = v
break
case 'Country':
case 'البلد':
item.country = v
break
case 'Director':
case 'المخرج':
item.director = v
break
case 'Language':
case 'اللغة':
item.language = v
break
case 'Release Year':
case 'سنة الإصدار':
item.date = v
break
}
}
break
}
}
}
const img = $('.row > div > img')
if (img.length) {
item.image = img.attr('src')
}
delete item.program
return item
}
function parseItems(content, date) {
const $ = cheerio.load(content)
const items = []
let curDate
$('.hour > div').each((_, item) => {
const $item = $(item)
if ($item.hasClass('bg')) {
curDate = $item.attr('id')
curDate = curDate.substr(curDate.indexOf('-') + 1).split('-')
} else if ($item.hasClass('iq-accordion')) {
const top = $item.find('.iq-accordion-block')
const heading = top.find('.iq-accordion-title .big-title')
if (heading.length) {
const progId = top.attr('id')
const title = heading
.find('span:eq(1)')
.text()
.split('\n')
.map(a => a.trim())
.join(' ')
const time = heading.find('span:eq(0)').text()
const [d, m, y] = curDate
items.push({
program: progId.substr(progId.indexOf('-') + 1),
title: title ? title.trim() : title,
start: `${y}-${m}-${d} ${time.trim()}`
})
}
}
})
items.sort((a, b) => a.start.localeCompare(b.start))
for (let i = 0; i < items.length; i++) {
if (i < items.length - 2) {
items[i].stop = items[i + 1].start
} else {
const dt = dayjs.tz(items[i].start).add(1, 'd')
items[i].stop = `${dt.format('YYYY-MM-DD')} 00:00`
}
}
const expectedDate = `${date.format('YYYY-MM-DD')}`
return items
.filter(a => a.start.startsWith(expectedDate) || a.stop.startsWith(expectedDate))
.map(a => {
a.start = dayjs.tz(a.start, tz)
a.stop = dayjs.tz(a.stop, tz)
return a
})
}
function parseCookies(headers) {
const cookies = []
if (headers && Array.isArray(headers['set-cookie'])) {
headers['set-cookie'].forEach(cookie => {
cookies.push(cookie.split('; ')[0])
})
}
return cookies.length ? cookies.join('; ') : null
}
const axios = require('axios')
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')
const doFetch = require('@ntlab/sfetch')
const debug = require('debug')('site:rotana.net')
dayjs.extend(timezone)
dayjs.extend(utc)
dayjs.extend(customParseFormat)
doFetch.setCheckResult(false).setDebugger(debug)
const tz = 'Asia/Riyadh'
const defaultHeaders = {
'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'
}
const cookies = {}
module.exports = {
site: 'rotana.net',
days: 2,
url({ channel }) {
return `https://rotana.net/${channel.lang}/streams?channel=${channel.site_id}&tz=`
},
request: {
headers: defaultHeaders,
timeout: 15000
},
async parser({ content, headers, channel, date }) {
const programs = []
if (!cookies[channel.lang]) {
cookies[channel.lang] = parseCookies(headers)
}
const items = parseItems(content, date)
if (items.length) {
const queues = []
for (const item of items) {
const url = `https://rotana.net/${channel.lang}/streams?channel=${channel.site_id}&itemId=${item.program}`
const params = {
headers: {
...defaultHeaders,
'X-Requested-With': 'XMLHttpRequest',
cookie: cookies[channel.lang]
}
}
queues.push({ i: item, url, params })
}
await doFetch(queues, (queue, res) => {
programs.push(parseProgram(queue.i, res))
})
}
return programs
},
async channels({ lang = 'en' }) {
const result = await axios
.get('https://rotana.net/api/channels')
.then(response => response.data)
.catch(console.error)
return result.data.map(item => {
return {
lang,
site_id: item.id,
name: item.name[lang]
}
})
}
}
function parseProgram(item, result) {
const $ = cheerio.load(result)
const details = $('.trending-info .row div > span')
if (details.length) {
for (const el of details[0].children) {
switch (el.constructor.name) {
case 'Text':
if (item.description === undefined) {
const desc = $(el).text().trim()
if (desc) {
item.description = desc
}
}
break
case 'Element':
if (el.name === 'span') {
const [k, v] = $(el)
.text()
.split(':')
.map(a => a.trim())
switch (k) {
case 'Category':
case 'التصنيف':
item.category = v
break
case 'Country':
case 'البلد':
item.country = v
break
case 'Director':
case 'المخرج':
item.director = v
break
case 'Language':
case 'اللغة':
item.language = v
break
case 'Release Year':
case 'سنة الإصدار':
item.date = v
break
}
}
break
}
}
}
const img = $('.row > div > img')
if (img.length) {
item.image = img.attr('src')
}
delete item.program
return item
}
function parseItems(content, date) {
const $ = cheerio.load(content)
const items = []
let curDate
$('.hour > div').each((_, item) => {
const $item = $(item)
if ($item.hasClass('bg')) {
curDate = $item.attr('id')
curDate = curDate.substr(curDate.indexOf('-') + 1).split('-')
} else if ($item.hasClass('iq-accordion')) {
const top = $item.find('.iq-accordion-block')
const heading = top.find('.iq-accordion-title .big-title')
if (heading.length) {
const progId = top.attr('id')
const title = heading
.find('span:eq(1)')
.text()
.split('\n')
.map(a => a.trim())
.join(' ')
const time = heading.find('span:eq(0)').text()
const [d, m, y] = curDate
items.push({
program: progId.substr(progId.indexOf('-') + 1),
title: title ? title.trim() : title,
start: `${y}-${m}-${d} ${time.trim()}`
})
}
}
})
items.sort((a, b) => a.start.localeCompare(b.start))
for (let i = 0; i < items.length; i++) {
if (i < items.length - 2) {
items[i].stop = items[i + 1].start
} else {
const dt = dayjs.tz(items[i].start).add(1, 'd')
items[i].stop = `${dt.format('YYYY-MM-DD')} 00:00`
}
}
const expectedDate = `${date.format('YYYY-MM-DD')}`
return items
.filter(a => a.start.startsWith(expectedDate) || a.stop.startsWith(expectedDate))
.map(a => {
a.start = dayjs.tz(a.start, tz)
a.stop = dayjs.tz(a.stop, tz)
return a
})
}
function parseCookies(headers) {
const cookies = []
if (headers && Array.isArray(headers['set-cookie'])) {
headers['set-cookie'].forEach(cookie => {
cookies.push(cookie.split('; ')[0])
})
}
return cookies.length ? cookies.join('; ') : null
}

View File

@@ -1,113 +1,113 @@
const { parser, url, request } = require('./rotana.net.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 customParseFormat = require('dayjs/plugin/customParseFormat')
dayjs.extend(customParseFormat)
dayjs.extend(utc)
jest.mock('axios')
const date = dayjs.utc('2024-11-26').startOf('d')
const channel = {
lang: 'en',
site_id: '439',
xmltv_id: 'RotanaCinemaMasr.sa'
}
const channelAr = Object.assign({}, channel, { lang: 'ar' })
axios.get.mockImplementation(url => {
if (url === 'https://rotana.net/en/streams?channel=439&itemId=736970') {
return Promise.resolve({
data: fs.readFileSync(path.resolve(__dirname, '__data__/program_en.html'))
})
}
if (url === 'https://rotana.net/ar/streams?channel=439&itemId=736970') {
return Promise.resolve({
data: fs.readFileSync(path.resolve(__dirname, '__data__/program_ar.html'))
})
}
return Promise.resolve({ data: '' })
})
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/en/streams?channel=439&tz=')
})
it('can generate valid arabic url', () => {
const result = url({ channel: channelAr, date })
expect(result).toBe('https://rotana.net/ar/streams?channel=439&tz=')
})
it('can parse english response', async () => {
const result = (
await parser({
channel,
date,
content: fs.readFileSync(path.join(__dirname, '/__data__/content_en.html'))
})
).map(a => {
a.start = a.start.toJSON()
a.stop = a.stop.toJSON()
return a
})
expect(result.length).toBe(12)
expect(result[11]).toMatchObject({
start: '2024-11-26T20:00:00.000Z',
stop: '2024-11-26T22:00:00.000Z',
title: 'Khiyana Mashroua',
description:
'Hisham knows that his father has given all his wealth to his elder brother. This leads him to plan to kill his brother to make it look like a defense of honor, which he does by killing his wife along...',
image:
'https://s3.eu-central-1.amazonaws.com/rotana.website/spider_storage/1398X1000/1687084565',
category: 'Movie'
})
})
it('can parse arabic response', async () => {
const result = (
await parser({
channel: channelAr,
date,
content: fs.readFileSync(path.join(__dirname, '/__data__/content_ar.html'))
})
).map(a => {
a.start = a.start.toJSON()
a.stop = a.stop.toJSON()
return a
})
expect(result.length).toBe(12)
expect(result[11]).toMatchObject({
start: '2024-11-26T20:00:00.000Z',
stop: '2024-11-26T22:00:00.000Z',
title: 'خيانة مشروعة',
description:
'يعلم هشام البحيري أن والده قد حرمه من الميراث، ووهب كل ثروته لشقيقه اﻷكبر، وهو ما يدفعه لتدبير جريمة قتل شقيقه لتبدو وكأنها دفاع عن الشرف، وذلك حين يقتل هشام زوجته مع شقيقه.',
image:
'https://s3.eu-central-1.amazonaws.com/rotana.website/spider_storage/1398X1000/1687084565',
category: 'فيلم'
})
})
it('can handle empty guide', async () => {
const result = await parser({
content: '<!DOCTYPE html><html><head></head><body></body></html>',
date,
channel
})
expect(result).toMatchObject([])
})
const { parser, url, request } = require('./rotana.net.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 customParseFormat = require('dayjs/plugin/customParseFormat')
dayjs.extend(customParseFormat)
dayjs.extend(utc)
jest.mock('axios')
const date = dayjs.utc('2024-11-26').startOf('d')
const channel = {
lang: 'en',
site_id: '439',
xmltv_id: 'RotanaCinemaMasr.sa'
}
const channelAr = Object.assign({}, channel, { lang: 'ar' })
axios.get.mockImplementation(url => {
if (url === 'https://rotana.net/en/streams?channel=439&itemId=736970') {
return Promise.resolve({
data: fs.readFileSync(path.resolve(__dirname, '__data__/program_en.html'))
})
}
if (url === 'https://rotana.net/ar/streams?channel=439&itemId=736970') {
return Promise.resolve({
data: fs.readFileSync(path.resolve(__dirname, '__data__/program_ar.html'))
})
}
return Promise.resolve({ data: '' })
})
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/en/streams?channel=439&tz=')
})
it('can generate valid arabic url', () => {
const result = url({ channel: channelAr, date })
expect(result).toBe('https://rotana.net/ar/streams?channel=439&tz=')
})
it('can parse english response', async () => {
const result = (
await parser({
channel,
date,
content: fs.readFileSync(path.join(__dirname, '/__data__/content_en.html'))
})
).map(a => {
a.start = a.start.toJSON()
a.stop = a.stop.toJSON()
return a
})
expect(result.length).toBe(12)
expect(result[11]).toMatchObject({
start: '2024-11-26T20:00:00.000Z',
stop: '2024-11-26T22:00:00.000Z',
title: 'Khiyana Mashroua',
description:
'Hisham knows that his father has given all his wealth to his elder brother. This leads him to plan to kill his brother to make it look like a defense of honor, which he does by killing his wife along...',
image:
'https://s3.eu-central-1.amazonaws.com/rotana.website/spider_storage/1398X1000/1687084565',
category: 'Movie'
})
})
it('can parse arabic response', async () => {
const result = (
await parser({
channel: channelAr,
date,
content: fs.readFileSync(path.join(__dirname, '/__data__/content_ar.html'))
})
).map(a => {
a.start = a.start.toJSON()
a.stop = a.stop.toJSON()
return a
})
expect(result.length).toBe(12)
expect(result[11]).toMatchObject({
start: '2024-11-26T20:00:00.000Z',
stop: '2024-11-26T22:00:00.000Z',
title: 'خيانة مشروعة',
description:
'يعلم هشام البحيري أن والده قد حرمه من الميراث، ووهب كل ثروته لشقيقه اﻷكبر، وهو ما يدفعه لتدبير جريمة قتل شقيقه لتبدو وكأنها دفاع عن الشرف، وذلك حين يقتل هشام زوجته مع شقيقه.',
image:
'https://s3.eu-central-1.amazonaws.com/rotana.website/spider_storage/1398X1000/1687084565',
category: 'فيلم'
})
})
it('can handle empty guide', async () => {
const result = await parser({
content: '<!DOCTYPE html><html><head></head><body></body></html>',
date,
channel
})
expect(result).toMatchObject([])
})