mirror of
https://github.com/iptv-org/epg
synced 2026-09-09 21:36:50 -04:00
Update osn.com Api and channels.
Signed-off-by: Toha <tohenk@yahoo.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
+130
-36
@@ -7,6 +7,11 @@ const crypto = require('crypto')
|
|||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OSN.COM note:
|
||||||
|
* - API allows only at max 5 channels, otherwise returns 406
|
||||||
|
*/
|
||||||
|
|
||||||
// https://www.osn.com/_next/static/chunks/87cf1e375968671c.js
|
// https://www.osn.com/_next/static/chunks/87cf1e375968671c.js
|
||||||
|
|
||||||
const cipherName = 'aes-256-cbc'
|
const cipherName = 'aes-256-cbc'
|
||||||
@@ -15,36 +20,50 @@ const cipherIv = Buffer.from('b7cc0a48d6d023bc1a2a670953ec5622', 'hex')
|
|||||||
|
|
||||||
const tz = dayjs.tz.guess()
|
const tz = dayjs.tz.guess()
|
||||||
const oneDayMs = 864e5
|
const oneDayMs = 864e5
|
||||||
const headers = {
|
const headers = {}
|
||||||
'User-Agent':
|
const caches = {}
|
||||||
'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'
|
let allChannels
|
||||||
}
|
|
||||||
let batch = 0
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'osn.com',
|
site: 'osn.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
url({ channel, date }) {
|
async url({ date }) {
|
||||||
const data = getChannelData({ channel, date })
|
return (await getUrlData(date)).url
|
||||||
return `https://www.osn.com/apidata/tv-schedule-timeline?t=batch${++batch}-time${data.startTime}-${data.endTime}-boxAndroid`
|
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
headers({ channel, date }) {
|
cache: {
|
||||||
return {
|
ttl: 24 * 60 * 60 * 1000 // 1 day
|
||||||
...headers,
|
},
|
||||||
'X-Encrypted-Data': encrypt(JSON.stringify(getChannelData({ channel, date })))
|
async headers({ date }) {
|
||||||
}
|
return await getHeaders(date)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
parser({ content, channel }) {
|
async parser({ content, channel, date }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
if (typeof content === 'string' || Buffer.isBuffer(content)) {
|
content = getDecryptedData(content)
|
||||||
content = JSON.parse(content)
|
|
||||||
}
|
|
||||||
if (content?.encrypted) {
|
|
||||||
content = JSON.parse(decrypt(content.encrypted))
|
|
||||||
}
|
|
||||||
if (Array.isArray(content?.entries)) {
|
if (Array.isArray(content?.entries)) {
|
||||||
|
// get remaining segments entries
|
||||||
|
const cacheId = date.format('YYYYMMDD')
|
||||||
|
if (caches[cacheId] === undefined) {
|
||||||
|
caches[cacheId] = []
|
||||||
|
const segments = await getUrlData(date, false)
|
||||||
|
while (segments.length) {
|
||||||
|
const segment = segments.shift()
|
||||||
|
const result = await axios
|
||||||
|
.get(segment.url, { headers: await getHeaders(segment.data) })
|
||||||
|
.then(res => getDecryptedData(res.data))
|
||||||
|
.catch(err => console.error(`${segment.url}: ${err.message}!`))
|
||||||
|
if (Array.isArray(result?.entries)) {
|
||||||
|
caches[cacheId].push(...result.entries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// add 5s delay to avoid 406
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 5000))
|
||||||
|
}
|
||||||
|
// add entries from cache
|
||||||
|
if (Array.isArray(caches[cacheId])) {
|
||||||
|
content.entries.push(...caches[cacheId])
|
||||||
|
}
|
||||||
content.entries
|
content.entries
|
||||||
.filter(entry => entry.guid == channel.site_id)
|
.filter(entry => entry.guid == channel.site_id)
|
||||||
.forEach(entry => {
|
.forEach(entry => {
|
||||||
@@ -83,42 +102,117 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
async channels({ lang = 'ar' }) {
|
async channels({ lang = 'ar' }) {
|
||||||
const channels = []
|
const channels = []
|
||||||
const result = await axios
|
const items = await fetchChannels()
|
||||||
.get('https://www.osn.com/apidata/channels?platform=Android', { headers })
|
if (Array.isArray(items)) {
|
||||||
.then(response => response.data)
|
|
||||||
.catch(console.error)
|
|
||||||
|
|
||||||
if (result?.encrypted) {
|
|
||||||
const items = JSON.parse(decrypt(result.encrypted))
|
|
||||||
channels.push(...items.map(item => ({
|
channels.push(...items.map(item => ({
|
||||||
lang,
|
lang,
|
||||||
site_id: item.guid,
|
site_id: item.guid,
|
||||||
name: item.title
|
name: item.title
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
return channels
|
return channels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchChannels() {
|
||||||
|
if (allChannels === undefined) {
|
||||||
|
const result = await axios
|
||||||
|
.get('https://www.osn.com/apidata/channels?platform=Android', { headers: await getHeaders() })
|
||||||
|
.then(res => getDecryptedData(res.data))
|
||||||
|
.catch(console.error)
|
||||||
|
|
||||||
|
if (Array.isArray(result)) {
|
||||||
|
allChannels = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allChannels
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getHeaders(data) {
|
||||||
|
if (dayjs.isDayjs(data)) {
|
||||||
|
data = (await getUrlData(data)).data
|
||||||
|
}
|
||||||
|
const res = { ...headers }
|
||||||
|
if (data) {
|
||||||
|
res['X-Encrypted-Data'] = getEncryptedData(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUrlData(date, first = true) {
|
||||||
|
const parts = []
|
||||||
|
const startOfDay = date.tz(tz).startOf('d')
|
||||||
|
const endOfDay = startOfDay.add(oneDayMs, 'ms')
|
||||||
|
const startStop = {
|
||||||
|
startTime: startOfDay.valueOf(),
|
||||||
|
endTime: endOfDay.valueOf()
|
||||||
|
}
|
||||||
|
await fetchChannels()
|
||||||
|
const segments = await getUrlSegments()
|
||||||
|
let i = 0
|
||||||
|
for (const v of segments) {
|
||||||
|
const data = {
|
||||||
|
url: `https://www.osn.com/apidata/tv-schedule-timeline?t=batch${++i}-time${
|
||||||
|
startStop.startTime
|
||||||
|
}-${
|
||||||
|
startStop.endTime
|
||||||
|
}-boxAndroid`,
|
||||||
|
data: {
|
||||||
|
channelGuid: v.join('|'),
|
||||||
|
...startStop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (first) {
|
||||||
|
return data
|
||||||
|
} else if (i === 1) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts.push(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUrlSegments() {
|
||||||
|
await fetchChannels()
|
||||||
|
const segments = []
|
||||||
|
const _channels = [...allChannels.map(item => item.guid)]
|
||||||
|
while (_channels.length) {
|
||||||
|
segments.push(_channels.splice(0, 5))
|
||||||
|
}
|
||||||
|
return segments
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEncryptedData(data) {
|
||||||
|
return encrypt(JSON.stringify(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDecryptedData(data) {
|
||||||
|
if (typeof data === 'string' || Buffer.isBuffer(data)) {
|
||||||
|
data = JSON.parse(data)
|
||||||
|
}
|
||||||
|
if (data?.encrypted) {
|
||||||
|
data = JSON.parse(decrypt(data.encrypted))
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
function encrypt(data, encoding = 'utf8') {
|
function encrypt(data, encoding = 'utf8') {
|
||||||
const cipher = crypto.createCipheriv(cipherName, cipherKey, cipherIv)
|
const cipher = crypto.createCipheriv(cipherName, cipherKey, cipherIv)
|
||||||
const encrypted = cipher.update(data, encoding, 'base64')
|
const encrypted = cipher.update(data, encoding, 'base64')
|
||||||
|
|
||||||
return (encrypted + cipher.final('base64'))
|
return (encrypted + cipher.final('base64'))
|
||||||
}
|
}
|
||||||
|
|
||||||
function decrypt(data, encoding = 'utf8') {
|
function decrypt(data, encoding = 'utf8') {
|
||||||
const decipher = crypto.createDecipheriv(cipherName, cipherKey, cipherIv)
|
const decipher = crypto.createDecipheriv(cipherName, cipherKey, cipherIv)
|
||||||
const decrypted = decipher.update(data, 'base64', encoding)
|
const decrypted = decipher.update(data, 'base64', encoding)
|
||||||
return (decrypted + decipher.final(encoding))
|
|
||||||
}
|
|
||||||
|
|
||||||
function getChannelData({ channel, date }) {
|
return (decrypted + decipher.final(encoding))
|
||||||
const startOfDay = date.tz(tz).startOf('d')
|
|
||||||
return {
|
|
||||||
channelGuid: channel.site_id,
|
|
||||||
startTime: startOfDay.valueOf(),
|
|
||||||
endTime: startOfDay.valueOf() + oneDayMs
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function dotProp(o, prop) {
|
function dotProp(o, prop) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const { parser, url, request } = require('./osn.com.config')
|
const { parser, url, request } = require('./osn.com.config')
|
||||||
|
const axios = require('axios')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
@@ -8,28 +9,57 @@ const customParseFormat = require('dayjs/plugin/customParseFormat')
|
|||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
|
jest.mock('axios')
|
||||||
|
|
||||||
const date = dayjs.utc('2026-05-16').startOf('d')
|
const date = dayjs.utc('2026-05-16').startOf('d')
|
||||||
const channelAR = { site_id: '4506', xmltv_id: 'OSNKids.ae@SD', lang: 'ar' }
|
const channelAR = { site_id: '4506', xmltv_id: 'OSNKids.ae@SD', lang: 'ar' }
|
||||||
const channelEN = { site_id: '4506', xmltv_id: 'OSNKids.ae@SD', lang: 'en' }
|
const channelEN = { site_id: '4506', xmltv_id: 'OSNKids.ae@SD', lang: 'en' }
|
||||||
const content = fs.readFileSync(path.join(__dirname, '/__data__/content.json'))
|
const content = fs.readFileSync(path.join(__dirname, '/__data__/content.json'))
|
||||||
|
|
||||||
it('can generate valid request headers', () => {
|
axios.get.mockImplementation(url => {
|
||||||
const result = request.headers({ channel: channelAR, date })
|
const urls = {
|
||||||
|
'https://www.osn.com/apidata/channels?platform=Android': 'channel.json',
|
||||||
|
'https://www.osn.com/apidata/tv-schedule-timeline?t=batch1-time1778846400000-1778932800000-boxAndroid': 'content.json',
|
||||||
|
'https://www.osn.com/apidata/tv-schedule-timeline?t=batch2-time1778846400000-1778932800000-boxAndroid': 'empty.json',
|
||||||
|
'https://www.osn.com/apidata/tv-schedule-timeline?t=batch3-time1778846400000-1778932800000-boxAndroid': 'empty.json',
|
||||||
|
'https://www.osn.com/apidata/tv-schedule-timeline?t=batch4-time1778846400000-1778932800000-boxAndroid': 'empty.json',
|
||||||
|
'https://www.osn.com/apidata/tv-schedule-timeline?t=batch5-time1778846400000-1778932800000-boxAndroid': 'empty.json',
|
||||||
|
'https://www.osn.com/apidata/tv-schedule-timeline?t=batch6-time1778846400000-1778932800000-boxAndroid': 'empty.json',
|
||||||
|
'https://www.osn.com/apidata/tv-schedule-timeline?t=batch7-time1778846400000-1778932800000-boxAndroid': 'empty.json',
|
||||||
|
'https://www.osn.com/apidata/tv-schedule-timeline?t=batch8-time1778846400000-1778932800000-boxAndroid': 'empty.json',
|
||||||
|
'https://www.osn.com/apidata/tv-schedule-timeline?t=batch9-time1778846400000-1778932800000-boxAndroid': 'empty.json',
|
||||||
|
'https://www.osn.com/apidata/tv-schedule-timeline?t=batch10-time1778846400000-1778932800000-boxAndroid': 'empty.json',
|
||||||
|
'https://www.osn.com/apidata/tv-schedule-timeline?t=batch11-time1778846400000-1778932800000-boxAndroid': 'empty.json',
|
||||||
|
'https://www.osn.com/apidata/tv-schedule-timeline?t=batch12-time1778846400000-1778932800000-boxAndroid': 'empty.json',
|
||||||
|
'https://www.osn.com/apidata/tv-schedule-timeline?t=batch13-time1778846400000-1778932800000-boxAndroid': 'empty.json',
|
||||||
|
'https://www.osn.com/apidata/tv-schedule-timeline?t=batch14-time1778846400000-1778932800000-boxAndroid': 'empty.json',
|
||||||
|
'https://www.osn.com/apidata/tv-schedule-timeline?t=batch15-time1778846400000-1778932800000-boxAndroid': 'empty.json',
|
||||||
|
'https://www.osn.com/apidata/tv-schedule-timeline?t=batch16-time1778846400000-1778932800000-boxAndroid': 'empty.json'
|
||||||
|
}
|
||||||
|
let data = ''
|
||||||
|
if (urls[url] !== undefined) {
|
||||||
|
data = fs.readFileSync(path.join(__dirname, '__data__', urls[url])).toString()
|
||||||
|
}
|
||||||
|
return Promise.resolve({ data })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can generate valid request headers', async () => {
|
||||||
|
const result = await request.headers({ date })
|
||||||
expect(result).toMatchObject({
|
expect(result).toMatchObject({
|
||||||
'X-Encrypted-Data':
|
'X-Encrypted-Data':
|
||||||
'e0srp4UFarSGxZt4iWaXSZTCg6vB46NRZmY5V9wEzxHeB1bwR1HXrAuTI8FCVH7i3+uVqkDQSgRxjRFPYdrXhqedzEogwcjDnjRPmLtFxEA='
|
'e0srp4UFarSGxZt4iWaXSSZCApUQYlb/CPhOH0/r/rUzyU/aVEst9+dVQueAwTe3A/or1iCzDEaiVfQ/VTxF9ZdIzOZeb3SSRap3YK7IFkj7Km6L8aXxpsNeQua/L/Ar'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', async () => {
|
||||||
const result = url({ channel: channelAR, date })
|
const result = await url({ date })
|
||||||
expect(result).toBe(
|
expect(result).toBe(
|
||||||
'https://www.osn.com/apidata/tv-schedule-timeline?t=batch1-time1778846400000-1778932800000-boxAndroid'
|
'https://www.osn.com/apidata/tv-schedule-timeline?t=batch1-time1778846400000-1778932800000-boxAndroid'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response (ar)', () => {
|
it('can parse response (ar)', async () => {
|
||||||
const result = parser({ date, channel: channelAR, content })
|
const result = (await parser({ date, channel: channelAR, content }))
|
||||||
.map(a => {
|
.map(a => {
|
||||||
a.start = a.start.toJSON()
|
a.start = a.start.toJSON()
|
||||||
a.stop = a.stop.toJSON()
|
a.stop = a.stop.toJSON()
|
||||||
@@ -51,8 +81,8 @@ it('can parse response (ar)', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response (en)', () => {
|
it('can parse response (en)', async () => {
|
||||||
const result = parser({ date, channel: channelEN, content })
|
const result = (await parser({ date, channel: channelEN, content }))
|
||||||
.map(a => {
|
.map(a => {
|
||||||
a.start = a.start.toJSON()
|
a.start = a.start.toJSON()
|
||||||
a.stop = a.stop.toJSON()
|
a.stop = a.stop.toJSON()
|
||||||
@@ -74,7 +104,7 @@ it('can parse response (en)', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', async () => {
|
||||||
const result = parser({ date, channel: channelAR, content: '[]' })
|
const result = await parser({ date, channel: channelAR, content: '[]' })
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,11 +7,12 @@
|
|||||||
<channel site="osn.com" site_id="225" lang="ar" xmltv_id="">OSNtv Movies Horror</channel>
|
<channel site="osn.com" site_id="225" lang="ar" xmltv_id="">OSNtv Movies Horror</channel>
|
||||||
<channel site="osn.com" site_id="307" lang="ar" xmltv_id="">OSNtv Crime</channel>
|
<channel site="osn.com" site_id="307" lang="ar" xmltv_id="">OSNtv Crime</channel>
|
||||||
<channel site="osn.com" site_id="314" lang="ar" xmltv_id="">OSNtv Documentary</channel>
|
<channel site="osn.com" site_id="314" lang="ar" xmltv_id="">OSNtv Documentary</channel>
|
||||||
|
<channel site="osn.com" site_id="411" lang="ar" xmltv_id="">MBC Mood</channel>
|
||||||
<channel site="osn.com" site_id="1101" lang="ar" xmltv_id="">KTV Channel 1 HD</channel>
|
<channel site="osn.com" site_id="1101" lang="ar" xmltv_id="">KTV Channel 1 HD</channel>
|
||||||
<channel site="osn.com" site_id="4502" lang="ar" xmltv_id="">OSNtv Now</channel>
|
<channel site="osn.com" site_id="4502" lang="ar" xmltv_id="">OSNtv Now</channel>
|
||||||
<channel site="osn.com" site_id="5666" lang="ar" xmltv_id="">OSNtv Movies Family</channel>
|
<channel site="osn.com" site_id="5666" lang="ar" xmltv_id="">OSNtv Movies Family</channel>
|
||||||
<channel site="osn.com" site_id="5669" lang="ar" xmltv_id="">OSNtv Movies Comedy</channel>
|
<channel site="osn.com" site_id="5669" lang="ar" xmltv_id="">OSNtv Movies Comedy</channel>
|
||||||
<channel site="osn.com" site_id="5672" lang="ar" xmltv_id="">OSNtv Pop Up Beyond Earth</channel>
|
<channel site="osn.com" site_id="5672" lang="ar" xmltv_id="">OSNtv Pop Up 90 Day Fiance</channel>
|
||||||
<channel site="osn.com" site_id="6607" lang="ar" xmltv_id="">eClutch Access</channel>
|
<channel site="osn.com" site_id="6607" lang="ar" xmltv_id="">eClutch Access</channel>
|
||||||
<channel site="osn.com" site_id="6609" lang="ar" xmltv_id="">Esport 24</channel>
|
<channel site="osn.com" site_id="6609" lang="ar" xmltv_id="">Esport 24</channel>
|
||||||
<channel site="osn.com" site_id="6610" lang="ar" xmltv_id="">eClutch LIVE</channel>
|
<channel site="osn.com" site_id="6610" lang="ar" xmltv_id="">eClutch LIVE</channel>
|
||||||
@@ -78,5 +79,4 @@
|
|||||||
<channel site="osn.com" site_id="9047" lang="ar" xmltv_id="SaudiThaqafiyaTV.sa@SD">Al Thaqafeya</channel>
|
<channel site="osn.com" site_id="9047" lang="ar" xmltv_id="SaudiThaqafiyaTV.sa@SD">Al Thaqafeya</channel>
|
||||||
<channel site="osn.com" site_id="2015" lang="ar" xmltv_id="SkyNewsArabia.ae@SD">Sky News Arabia HD</channel>
|
<channel site="osn.com" site_id="2015" lang="ar" xmltv_id="SkyNewsArabia.ae@SD">Sky News Arabia HD</channel>
|
||||||
<channel site="osn.com" site_id="308" lang="ar" xmltv_id="TLCArabia.us@SD">TLC HD</channel>
|
<channel site="osn.com" site_id="308" lang="ar" xmltv_id="TLCArabia.us@SD">TLC HD</channel>
|
||||||
<channel site="osn.com" site_id="411" lang="ar" xmltv_id="Wanasah.ae@SD">Wanasah</channel>
|
|
||||||
</channels>
|
</channels>
|
||||||
|
|||||||
@@ -7,11 +7,12 @@
|
|||||||
<channel site="osn.com" site_id="225" lang="en" xmltv_id="">OSNtv Movies Horror</channel>
|
<channel site="osn.com" site_id="225" lang="en" xmltv_id="">OSNtv Movies Horror</channel>
|
||||||
<channel site="osn.com" site_id="307" lang="en" xmltv_id="">OSNtv Crime</channel>
|
<channel site="osn.com" site_id="307" lang="en" xmltv_id="">OSNtv Crime</channel>
|
||||||
<channel site="osn.com" site_id="314" lang="en" xmltv_id="">OSNtv Documentary</channel>
|
<channel site="osn.com" site_id="314" lang="en" xmltv_id="">OSNtv Documentary</channel>
|
||||||
|
<channel site="osn.com" site_id="411" lang="en" xmltv_id="">MBC Mood</channel>
|
||||||
<channel site="osn.com" site_id="1101" lang="en" xmltv_id="">KTV Channel 1 HD</channel>
|
<channel site="osn.com" site_id="1101" lang="en" xmltv_id="">KTV Channel 1 HD</channel>
|
||||||
<channel site="osn.com" site_id="4502" lang="en" xmltv_id="">OSNtv Now</channel>
|
<channel site="osn.com" site_id="4502" lang="en" xmltv_id="">OSNtv Now</channel>
|
||||||
<channel site="osn.com" site_id="5666" lang="en" xmltv_id="">OSNtv Movies Family</channel>
|
<channel site="osn.com" site_id="5666" lang="en" xmltv_id="">OSNtv Movies Family</channel>
|
||||||
<channel site="osn.com" site_id="5669" lang="en" xmltv_id="">OSNtv Movies Comedy</channel>
|
<channel site="osn.com" site_id="5669" lang="en" xmltv_id="">OSNtv Movies Comedy</channel>
|
||||||
<channel site="osn.com" site_id="5672" lang="en" xmltv_id="">OSNtv Pop Up Beyond Earth</channel>
|
<channel site="osn.com" site_id="5672" lang="en" xmltv_id="">OSNtv Pop Up 90 Day Fiance</channel>
|
||||||
<channel site="osn.com" site_id="6607" lang="en" xmltv_id="">eClutch Access</channel>
|
<channel site="osn.com" site_id="6607" lang="en" xmltv_id="">eClutch Access</channel>
|
||||||
<channel site="osn.com" site_id="6609" lang="en" xmltv_id="">Esport 24</channel>
|
<channel site="osn.com" site_id="6609" lang="en" xmltv_id="">Esport 24</channel>
|
||||||
<channel site="osn.com" site_id="6610" lang="en" xmltv_id="">eClutch LIVE</channel>
|
<channel site="osn.com" site_id="6610" lang="en" xmltv_id="">eClutch LIVE</channel>
|
||||||
@@ -78,5 +79,4 @@
|
|||||||
<channel site="osn.com" site_id="9047" lang="en" xmltv_id="SaudiThaqafiyaTV.sa@SD">Al Thaqafeya</channel>
|
<channel site="osn.com" site_id="9047" lang="en" xmltv_id="SaudiThaqafiyaTV.sa@SD">Al Thaqafeya</channel>
|
||||||
<channel site="osn.com" site_id="2015" lang="en" xmltv_id="SkyNewsArabia.ae@SD">Sky News Arabia HD</channel>
|
<channel site="osn.com" site_id="2015" lang="en" xmltv_id="SkyNewsArabia.ae@SD">Sky News Arabia HD</channel>
|
||||||
<channel site="osn.com" site_id="308" lang="en" xmltv_id="TLCArabia.us@SD">TLC HD</channel>
|
<channel site="osn.com" site_id="308" lang="en" xmltv_id="TLCArabia.us@SD">TLC HD</channel>
|
||||||
<channel site="osn.com" site_id="411" lang="en" xmltv_id="Wanasah.ae@SD">Wanasah</channel>
|
|
||||||
</channels>
|
</channels>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# osn.com
|
# osn.com
|
||||||
|
|
||||||
https://www.osn.com/en-sa/osntv _[Geo-blocked]_
|
https://www.osn.com/en-sa/watch/tv-schedule _[Geo-blocked]_
|
||||||
|
|
||||||
### Download the guide
|
### Download the guide
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user