mirror of
https://github.com/iptv-org/iptv-org.github.io.git
synced 2025-05-11 17:40:05 -04:00
Merge pull request #2059 from iptv-org/patch-2025.03.1
This commit is contained in:
commit
9e938795d9
28 changed files with 9803 additions and 556 deletions
7234
package-lock.json
generated
7234
package-lock.json
generated
File diff suppressed because it is too large
Load diff
14
package.json
14
package.json
|
@ -8,11 +8,19 @@
|
|||
"build": "NODE_OPTIONS=--max_old_space_size=4096 vite build",
|
||||
"preview": "vite preview",
|
||||
"postbuild": "npx svelte-sitemap -d https://iptv-org.github.io -o docs",
|
||||
"postinstall": "node ./src/load.js"
|
||||
"postinstall": "node ./src/load.js",
|
||||
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {},
|
||||
"moduleNameMapper": {
|
||||
"^\\$app/environment$": "<rootDir>/tests/__mocks__/$app/environment.js",
|
||||
"^\\$app/navigation$": "<rootDir>/tests/__mocks__/$app/navigation.js"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@freearhey/core": "^0.5.1",
|
||||
"@freearhey/search-js": "^0.1.1",
|
||||
"@freearhey/search-js": "^0.1.2",
|
||||
"@sveltejs/adapter-static": "^3.0.8",
|
||||
"@sveltejs/kit": "^2.17.1",
|
||||
"@tailwindcss/line-clamp": "^0.4.2",
|
||||
|
@ -22,11 +30,13 @@
|
|||
"cli-progress": "^3.12.0",
|
||||
"dayjs": "^1.11.1",
|
||||
"iptv-playlist-generator": "^0.1.5",
|
||||
"jest": "^29.7.0",
|
||||
"lodash": "^4.17.21",
|
||||
"numeral": "^2.0.6",
|
||||
"postcss": "^8.5.1",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"qs": "^6.11.2",
|
||||
"svelte": "^5.22.6",
|
||||
"svelte-simple-modal": "^2.0.0",
|
||||
"svelte-sitemap": "^2.6.0",
|
||||
"sveltejs-tippy": "^3.0.0",
|
||||
|
|
|
@ -3,13 +3,32 @@
|
|||
|
||||
export let channel
|
||||
|
||||
const blocklistRefs = channel.blocklist_records
|
||||
let reason
|
||||
const messages = {
|
||||
dmca: 'The channel has been added to our blocklist due to the claims of the copyright holder',
|
||||
nsfw: 'The channel has been added to our blocklist due to NSFW content'
|
||||
}
|
||||
|
||||
const blocklistRefs = channel._blocklistRecords
|
||||
.map(record => {
|
||||
let refName
|
||||
|
||||
const isIssue = /issues|pull/.test(record.ref)
|
||||
const isAttachment = /github\.zendesk\.com\/attachments\/token/.test(record.ref)
|
||||
if (isIssue) {
|
||||
const parts = record.ref.split('/')
|
||||
const issueId = parts.pop()
|
||||
const prefix = record.ref.includes('/issues/') ? '#' : ''
|
||||
refName = `#${issueId}`
|
||||
} else if (isAttachment) {
|
||||
const [, filename] = record.ref.match(/\?name=(.*)/) || [null, undefined]
|
||||
refName = filename
|
||||
} else {
|
||||
refName = record.ref.split('/').pop()
|
||||
}
|
||||
|
||||
return `<a class="underline" target="_blank" rel="noreferrer" href="${record.ref}">${prefix}${issueId}</a>`
|
||||
reason = record.reason
|
||||
|
||||
return `<a class="underline" target="_blank" rel="noreferrer" href="${record.ref}">${refName}</a>`
|
||||
})
|
||||
.join(', ')
|
||||
</script>
|
||||
|
@ -17,7 +36,7 @@
|
|||
<div
|
||||
class="text-gray-500 border-[1px] border-gray-200 text-xs inline-flex items-center px-2.5 py-0.5 dark:text-gray-300 rounded-full"
|
||||
use:tippy={{
|
||||
content: `The channel has been added to our blocklist due to the claims of the copyright holder: ${blocklistRefs}`,
|
||||
content: `${messages[reason]}: ${blocklistRefs}`,
|
||||
allowHTML: true,
|
||||
placement: 'right',
|
||||
interactive: true
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{#each channelsDisplay as channel (channel.id)}
|
||||
{#each channelsDisplay as channel, idx (channel)}
|
||||
<ChannelItem bind:channel />
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
@ -8,11 +8,13 @@
|
|||
import ClosedBadge from './ClosedBadge.svelte'
|
||||
import { downloadMode, selected } from '~/store'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { pushState } from '$app/navigation'
|
||||
|
||||
export let channel
|
||||
|
||||
const guides = channel._guides
|
||||
const streams = channel._streams
|
||||
const displayName = channel._displayName
|
||||
|
||||
const [name, country] = channel.id.split('.')
|
||||
|
||||
|
@ -20,25 +22,21 @@
|
|||
let prevUrl = '/'
|
||||
const onOpened = () => {
|
||||
prevUrl = window.location.href
|
||||
window.history.pushState(
|
||||
{},
|
||||
`${channel.displayName} • iptv-org`,
|
||||
`/channels/${country}/${name}`
|
||||
)
|
||||
pushState(`/channels/${country}/${name}`, {})
|
||||
}
|
||||
const onClose = () => {
|
||||
window.history.pushState({}, `iptv-org`, prevUrl)
|
||||
pushState(prevUrl, {})
|
||||
}
|
||||
const showGuides = () =>
|
||||
open(
|
||||
GuidesPopup,
|
||||
{ guides, title: channel.displayName },
|
||||
{ guides, title: displayName },
|
||||
{ transitionBgProps: { duration: 0 }, transitionWindowProps: { duration: 0 } }
|
||||
)
|
||||
const showStreams = () =>
|
||||
open(
|
||||
StreamsPopup,
|
||||
{ streams, title: channel.displayName },
|
||||
{ streams, title: displayName },
|
||||
{ transitionBgProps: { duration: 0 }, transitionWindowProps: { duration: 0 } }
|
||||
)
|
||||
const showChannelData = () => {
|
||||
|
@ -89,7 +87,7 @@
|
|||
loading="lazy"
|
||||
referrerpolicy="no-referrer"
|
||||
src={channel.logo}
|
||||
alt={channel.displayName}
|
||||
alt={displayName}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -103,9 +101,9 @@
|
|||
href="/channels/{country}/{name}"
|
||||
tabindex="0"
|
||||
class="font-normal text-gray-600 dark:text-white hover:underline hover:text-blue-500 truncate whitespace-nowrap"
|
||||
title={channel.displayName}
|
||||
title={displayName}
|
||||
>
|
||||
{channel.displayName}
|
||||
{displayName}
|
||||
</a>
|
||||
<div class="flex space-x-2">
|
||||
{#if channel.is_closed}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
>
|
||||
<div class="w-2/3 overflow-hidden">
|
||||
<div class="flex items-center space-x-3">
|
||||
<h3 class="text-l font-medium text-gray-900 dark:text-white">{channel.displayName}</h3>
|
||||
<h3 class="text-l font-medium text-gray-900 dark:text-white">{channel._displayName}</h3>
|
||||
<div class="flex space-x-2">
|
||||
{#if channel.is_closed}
|
||||
<ClosedBadge {channel} />
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
export let channel
|
||||
|
||||
const endpoint = 'https://github.com/iptv-org/database/issues/new'
|
||||
const title = `Edit: ${channel.displayName}`
|
||||
const title = `Edit: ${channel._displayName}`
|
||||
const labels = 'channels:edit'
|
||||
const template = '__channels_edit.yml'
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<script>
|
||||
import dayjs from 'dayjs'
|
||||
import { goto } from '$app/navigation'
|
||||
import { query, hasQuery, setSearchParam } from '~/store'
|
||||
|
||||
export let data
|
||||
export let close = () => {}
|
||||
|
@ -19,7 +17,7 @@
|
|||
{
|
||||
name: 'owners',
|
||||
type: 'link[]',
|
||||
value: data.owners.map(value => ({ label: value, query: `owners:${norm(value)}` }))
|
||||
value: data.owners.map(value => ({ label: value, query: `owner:${norm(value)}` }))
|
||||
},
|
||||
{
|
||||
name: 'country',
|
||||
|
@ -41,7 +39,7 @@
|
|||
{
|
||||
name: 'broadcast_area',
|
||||
type: 'link[]',
|
||||
value: data._broadcast_area.map(v => ({
|
||||
value: data._broadcastArea.map(v => ({
|
||||
label: v.name,
|
||||
query: `broadcast_area:${v.type}/${v.code}`
|
||||
}))
|
||||
|
@ -49,12 +47,12 @@
|
|||
{
|
||||
name: 'languages',
|
||||
type: 'link[]',
|
||||
value: data._languages.map(v => ({ label: v.name, query: `languages:${v.code}` }))
|
||||
value: data._languages.map(v => ({ label: v.name, query: `language:${v.code}` }))
|
||||
},
|
||||
{
|
||||
name: 'categories',
|
||||
type: 'link[]',
|
||||
value: data._categories.map(v => ({ label: v.name, query: `categories:${v.id}` }))
|
||||
value: data._categories.map(v => ({ label: v.name, query: `category:${v.id}` }))
|
||||
},
|
||||
{
|
||||
name: 'is_nsfw',
|
||||
|
@ -96,7 +94,7 @@
|
|||
</div>
|
||||
</td>
|
||||
<td class="align-top w-full overflow-hidden">
|
||||
<div class="flex pb-3 text-sm text-gray-800 dark:text-gray-100">
|
||||
<div class="pb-3 text-sm text-gray-800 dark:text-gray-100">
|
||||
{#if field.type === 'image'}
|
||||
<img
|
||||
src={field.value}
|
||||
|
|
|
@ -15,12 +15,12 @@
|
|||
result: 'Find channels that have "Nat Geo" in the name.'
|
||||
},
|
||||
{
|
||||
query: 'alt_names:חינוכית',
|
||||
query: 'alt_name:חינוכית',
|
||||
result: 'Finds channels whose alternative name contains "חינוכית".'
|
||||
},
|
||||
{ query: 'network:ABC', result: 'Finds all channels operated by the ABC Network.' },
|
||||
{
|
||||
query: 'owners:^$',
|
||||
query: 'owner:^$',
|
||||
result: 'Finds channels that have no owner listed.'
|
||||
},
|
||||
{ query: 'country:GY', result: 'Finds all channels that are broadcast from Guyana.' },
|
||||
|
@ -30,8 +30,8 @@
|
|||
},
|
||||
{ query: 'city:"San Francisco"', result: 'Finds all channels broadcast from San Francisco.' },
|
||||
{ query: 'broadcast_area:c/CV', result: 'Finds channels that are broadcast in Cape Verde.' },
|
||||
{ query: 'languages:fra', result: 'Find channels that are broadcast in French.' },
|
||||
{ query: 'categories:news', result: 'Finds all the news channels.' },
|
||||
{ query: 'language:fra', result: 'Find channels that are broadcast in French.' },
|
||||
{ query: 'category:news', result: 'Finds all the news channels.' },
|
||||
{ query: 'website:.', result: 'Finds channels that have a link to the official website.' },
|
||||
{ query: 'is_nsfw:true', result: 'Finds channels marked as NSFW.' },
|
||||
{
|
||||
|
@ -43,7 +43,8 @@
|
|||
result:
|
||||
'Finds channels that have been added to our blocklist due to the claim of the copyright holder.'
|
||||
},
|
||||
{ query: 'streams:<2', result: 'Finds channels with less than 2 streams.' }
|
||||
{ query: 'streams:<2', result: 'Finds channels with less than 2 streams.' },
|
||||
{ query: 'guides:>0', result: 'Finds channels that have guides.' }
|
||||
]
|
||||
</script>
|
||||
|
||||
|
|
56
src/models/channel.js
Normal file
56
src/models/channel.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
export class Channel {
|
||||
constructor(data) {
|
||||
const _streams = Array.isArray(data.streams) ? data.streams : []
|
||||
const _guides = Array.isArray(data.guides) ? data.guides : []
|
||||
const _blocklistRecords = Array.isArray(data.blocklistRecords) ? data.blocklistRecords : []
|
||||
|
||||
this.id = data.id
|
||||
this.name = data.name
|
||||
this.alt_names = this.alt_name = data.altNames
|
||||
this.network = data.network
|
||||
this.owners = this.owner = data.owners
|
||||
this.city = data.city
|
||||
this.country = [data.country?.code, data.country?.name].filter(Boolean)
|
||||
this.subdivision = data.subdivision?.code || null
|
||||
this.languages = this.language = [
|
||||
...data.languages.map(language => language.code),
|
||||
...data.languages.map(language => language.name)
|
||||
]
|
||||
this.categories = this.category = data.categories.map(category => category.name)
|
||||
this.broadcast_area = [
|
||||
...data.broadcastArea.map(area => `${area.type}/${area.code}`).filter(Boolean),
|
||||
...data.broadcastArea.map(area => area.name).filter(Boolean),
|
||||
...data.regionCountries.map(country => country.code).filter(Boolean),
|
||||
...data.regionCountries.map(country => country.name).filter(Boolean)
|
||||
]
|
||||
this.is_nsfw = data.isNSFW
|
||||
this.launched = data.launched
|
||||
this.closed = data.closed
|
||||
this.is_closed = !!data.closed || !!data.replacedBy
|
||||
this.replaced_by = data.replacedBy
|
||||
this.website = data.website
|
||||
this.logo = data.logo
|
||||
this.streams = _streams.length
|
||||
this.guides = _guides.length
|
||||
this.is_blocked = _blocklistRecords.length > 0
|
||||
|
||||
this._hasUniqueName = data.hasUniqueName
|
||||
this._displayName = data.hasUniqueName ? data.name : `${data.name} (${data.country?.name})`
|
||||
this._country = data.country
|
||||
this._subdivision = data.subdivision || null
|
||||
this._languages = data.languages
|
||||
this._categories = data.categories
|
||||
this._broadcastArea = data.broadcastArea
|
||||
this._streams = _streams
|
||||
this._guides = _guides
|
||||
this._blocklistRecords = _blocklistRecords
|
||||
this._guideNames = _guides.map(guide => guide.site_name).filter(Boolean)
|
||||
this._streamUrls = _streams.map(stream => stream.url).filter(Boolean)
|
||||
}
|
||||
|
||||
toObject() {
|
||||
const { ...object } = this
|
||||
|
||||
return object
|
||||
}
|
||||
}
|
1
src/models/index.js
Normal file
1
src/models/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './channel'
|
|
@ -10,12 +10,11 @@
|
|||
countries,
|
||||
filteredChannels,
|
||||
query,
|
||||
setSearchParam,
|
||||
setPageTitle,
|
||||
downloadMode,
|
||||
search
|
||||
} from '~/store'
|
||||
import { onMount, onDestroy } from 'svelte'
|
||||
import { onMount } from 'svelte'
|
||||
import CountryItem from '~/components/CountryItem.svelte'
|
||||
import SearchField from '~/components/SearchField.svelte'
|
||||
import _ from 'lodash'
|
||||
|
@ -24,17 +23,7 @@
|
|||
let _countries = []
|
||||
let isLoading = true
|
||||
|
||||
$: groupedByCountry = _.groupBy($filteredChannels, 'country')
|
||||
|
||||
function loadMore({ detail }) {
|
||||
let { loaded, complete } = detail
|
||||
if (limit < _countries.length) {
|
||||
limit++
|
||||
loaded()
|
||||
} else {
|
||||
complete()
|
||||
}
|
||||
}
|
||||
$: groupedByCountry = _.groupBy($filteredChannels, channel => channel._country.code)
|
||||
|
||||
onMount(async () => {
|
||||
if (!$channels.length) {
|
||||
|
@ -92,7 +81,7 @@
|
|||
loading...
|
||||
</div>
|
||||
{/if}
|
||||
{#each _countries as country (country.code)}
|
||||
{#each _countries as country, idx (country)}
|
||||
{#if groupedByCountry[country.code] && groupedByCountry[country.code].length > 0}
|
||||
<CountryItem
|
||||
bind:country
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { transformChannel } from '~/store'
|
||||
import { createChannel } from '~/store'
|
||||
import _ from 'lodash'
|
||||
import channels from '~/data/channels.json'
|
||||
import countries from '~/data/countries.json'
|
||||
|
@ -47,6 +47,6 @@ export function load({ params }) {
|
|||
let channel = data.channels[id]
|
||||
|
||||
return {
|
||||
channel: channel ? transformChannel(channel, data) : null
|
||||
channel: channel ? createChannel(channel, data).toObject() : null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
export let data
|
||||
|
||||
let isLoading = false
|
||||
let channel = data.channel
|
||||
let streams = channel ? channel._streams : []
|
||||
let guides = channel ? channel._guides : []
|
||||
const channel = data.channel
|
||||
const streams = channel ? channel._streams : []
|
||||
const guides = channel ? channel._guides : []
|
||||
const displayName = channel._displayName
|
||||
|
||||
const structuredData = {
|
||||
'@context': 'https://schema.org/',
|
||||
|
@ -28,8 +29,8 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{channel && channel.displayName ? `${channel.displayName} • iptv-org` : 'iptv-org'}</title>
|
||||
<meta name="description" content="Detailed description of {channel.displayName}." />
|
||||
<title>{channel && displayName ? `${displayName} • iptv-org` : 'iptv-org'}</title>
|
||||
<meta name="description" content="Detailed description of {displayName}." />
|
||||
{@html schema()}
|
||||
</svelte:head>
|
||||
|
||||
|
@ -54,7 +55,7 @@
|
|||
<div class="w-2/3 overflow-hidden">
|
||||
<div class="flex space-x-3">
|
||||
<h1 class="text-l font-medium text-gray-900 dark:text-white">
|
||||
{channel.displayName}
|
||||
{displayName}
|
||||
</h1>
|
||||
<div class="flex items-center space-x-2">
|
||||
{#if channel.is_closed}
|
||||
|
|
107
src/store.js
107
src/store.js
|
@ -3,6 +3,8 @@ import { Playlist, Link } from 'iptv-playlist-generator'
|
|||
import sj from '@freearhey/search-js'
|
||||
import _ from 'lodash'
|
||||
import { browser } from '$app/environment'
|
||||
import { Channel } from './models'
|
||||
import { pushState } from '$app/navigation'
|
||||
|
||||
export const query = writable('')
|
||||
export const hasQuery = writable(false)
|
||||
|
@ -14,7 +16,6 @@ export const downloadMode = writable(false)
|
|||
|
||||
let searchIndex = {}
|
||||
export function search(q) {
|
||||
console.log('.')
|
||||
if (!q) {
|
||||
filteredChannels.set(get(channels))
|
||||
hasQuery.set(false)
|
||||
|
@ -33,7 +34,7 @@ export async function fetchChannels() {
|
|||
|
||||
countries.set(api.countries)
|
||||
|
||||
let _channels = api.channels.map(c => transformChannel(c, api))
|
||||
let _channels = api.channels.map(c => createChannel(c, api))
|
||||
|
||||
channels.set(_channels)
|
||||
filteredChannels.set(_channels)
|
||||
|
@ -42,37 +43,42 @@ export async function fetchChannels() {
|
|||
'id',
|
||||
'name',
|
||||
'alt_names',
|
||||
'alt_name',
|
||||
'network',
|
||||
'owner',
|
||||
'owners',
|
||||
'country',
|
||||
'subdivision',
|
||||
'city',
|
||||
'broadcast_area',
|
||||
'language',
|
||||
'languages',
|
||||
'category',
|
||||
'categories',
|
||||
'launched',
|
||||
'closed',
|
||||
'replaced_by',
|
||||
'website',
|
||||
'streams',
|
||||
'guides',
|
||||
'is_nsfw',
|
||||
'is_closed',
|
||||
'is_blocked'
|
||||
'is_blocked',
|
||||
'_guideNames',
|
||||
'_streamUrls'
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
export function setSearchParam(key, value) {
|
||||
if (window.history.pushState) {
|
||||
let query = key && value ? `?${key}=${value}` : ''
|
||||
query = query.replace(/\+/g, '%2B')
|
||||
const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}${query}`
|
||||
const state = {}
|
||||
state[key] = value
|
||||
window.history.pushState(state, '', url)
|
||||
pushState(url, state)
|
||||
setPageTitle(value)
|
||||
}
|
||||
}
|
||||
|
||||
export function setPageTitle(value) {
|
||||
if (browser) {
|
||||
|
@ -81,6 +87,61 @@ export function setPageTitle(value) {
|
|||
}
|
||||
}
|
||||
|
||||
export function createChannel(data, api) {
|
||||
let broadcastArea = []
|
||||
let regionCountries = []
|
||||
|
||||
data.broadcast_area.forEach(areaCode => {
|
||||
const [type, code] = areaCode.split('/')
|
||||
switch (type) {
|
||||
case 'c':
|
||||
const country = api.countries[code]
|
||||
if (country) broadcastArea.push({ type, code: country.code, name: country.name })
|
||||
break
|
||||
case 'r':
|
||||
const region = api.regions[code]
|
||||
if (region) {
|
||||
broadcastArea.push({ type, code: region.code, name: region.name })
|
||||
regionCountries = [
|
||||
...regionCountries,
|
||||
...region.countries.map(code => api.countries[code]).filter(Boolean)
|
||||
]
|
||||
}
|
||||
break
|
||||
case 's':
|
||||
const subdivision = api.subdivisions[code]
|
||||
if (subdivision)
|
||||
broadcastArea.push({ type, code: subdivision.code, name: subdivision.name })
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
return new Channel({
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
altNames: data.alt_names,
|
||||
network: data.network,
|
||||
owners: data.owners,
|
||||
city: data.city,
|
||||
country: api.countries[data.country],
|
||||
subdivision: api.subdivisions[data.subdivision],
|
||||
languages: data.languages.map(code => api.languages[code]).filter(Boolean),
|
||||
categories: data.categories.map(id => api.categories[id]).filter(Boolean),
|
||||
isNSFW: data.is_nsfw,
|
||||
launched: data.launched,
|
||||
closed: data.closed,
|
||||
replacedBy: data.replaced_by,
|
||||
website: data.website,
|
||||
logo: data.logo,
|
||||
streams: api.streams[data.id],
|
||||
guides: api.guides[data.id],
|
||||
blocklistRecords: api.blocklist[data.id],
|
||||
hasUniqueName: api.nameIndex[data.name.toLowerCase()].length === 1,
|
||||
broadcastArea,
|
||||
regionCountries
|
||||
})
|
||||
}
|
||||
|
||||
async function loadAPI() {
|
||||
const api = {}
|
||||
|
||||
|
@ -155,40 +216,6 @@ async function loadAPI() {
|
|||
return api
|
||||
}
|
||||
|
||||
export function transformChannel(channel, data) {
|
||||
channel._streams = data.streams[channel.id] || []
|
||||
channel._guides = data.guides[channel.id] || []
|
||||
channel._country = data.countries[channel.country]
|
||||
channel._subdivision = data.subdivisions[channel.subdivision]
|
||||
channel._languages = channel.languages.map(code => data.languages[code]).filter(i => i)
|
||||
channel._categories = channel.categories.map(id => data.categories[id]).filter(i => i)
|
||||
channel._broadcast_area = channel.broadcast_area.map(value => {
|
||||
const [type, code] = value.split('/')
|
||||
switch (type) {
|
||||
case 'c':
|
||||
return { type, ...data.countries[code] }
|
||||
case 'r':
|
||||
return { type, ...data.regions[code] }
|
||||
case 's':
|
||||
return { type, ...data.subdivisions[code] }
|
||||
}
|
||||
})
|
||||
channel.is_closed = !!channel.closed || !!channel.replaced_by
|
||||
channel.is_blocked = !!data.blocklist[channel.id]
|
||||
channel.streams = channel._streams.length
|
||||
channel.guides = channel._guides.length
|
||||
channel.blocklist_records = Array.isArray(data.blocklist[channel.id])
|
||||
? data.blocklist[channel.id]
|
||||
: []
|
||||
|
||||
const isChannelNameRepeated = data.nameIndex[channel.name.toLowerCase()].length > 1
|
||||
channel.displayName = isChannelNameRepeated
|
||||
? `${channel.name} (${channel._country.name})`
|
||||
: channel.name
|
||||
|
||||
return channel
|
||||
}
|
||||
|
||||
function getStreams() {
|
||||
let streams = []
|
||||
get(selected).forEach(channel => {
|
||||
|
|
7
tests/__data__/input/blocklist.json
Normal file
7
tests/__data__/input/blocklist.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
[
|
||||
{
|
||||
"channel": "Bizarre.al",
|
||||
"reason": "nsfw",
|
||||
"ref": "https://github.com/iptv-org/iptv/issues/15723"
|
||||
}
|
||||
]
|
1
tests/__data__/input/categories.json
Normal file
1
tests/__data__/input/categories.json
Normal file
|
@ -0,0 +1 @@
|
|||
[{"id":"auto","name":"Auto"},{"id":"animation","name":"Animation"},{"id":"business","name":"Business"},{"id":"classic","name":"Classic"},{"id":"comedy","name":"Comedy"},{"id":"cooking","name":"Cooking"},{"id":"culture","name":"Culture"},{"id":"documentary","name":"Documentary"},{"id":"education","name":"Education"},{"id":"entertainment","name":"Entertainment"},{"id":"family","name":"Family"},{"id":"general","name":"General"},{"id":"kids","name":"Kids"},{"id":"legislative","name":"Legislative"},{"id":"lifestyle","name":"Lifestyle"},{"id":"movies","name":"Movies"},{"id":"music","name":"Music"},{"id":"news","name":"News"},{"id":"outdoor","name":"Outdoor"},{"id":"relax","name":"Relax"},{"id":"religious","name":"Religious"},{"id":"series","name":"Series"},{"id":"science","name":"Science"},{"id":"shop","name":"Shop"},{"id":"sports","name":"Sports"},{"id":"travel","name":"Travel"},{"id":"weather","name":"Weather"},{"id":"xxx","name":"XXX"}]
|
406
tests/__data__/input/channels.json
Normal file
406
tests/__data__/input/channels.json
Normal file
|
@ -0,0 +1,406 @@
|
|||
[
|
||||
{
|
||||
"id": "002RadioTV.do",
|
||||
"name": "002 Radio TV",
|
||||
"alt_names": [],
|
||||
"network": null,
|
||||
"owners": [],
|
||||
"country": "DO",
|
||||
"subdivision": null,
|
||||
"city": "Santo Domingo",
|
||||
"broadcast_area": [
|
||||
"c/DO"
|
||||
],
|
||||
"languages": [
|
||||
"spa"
|
||||
],
|
||||
"categories": [
|
||||
"general"
|
||||
],
|
||||
"is_nsfw": false,
|
||||
"launched": null,
|
||||
"closed": null,
|
||||
"replaced_by": null,
|
||||
"website": "https://www.002radio.com/",
|
||||
"logo": "https://i.imgur.com/7oNe8xj.png"
|
||||
},
|
||||
{
|
||||
"id": "01TV.fr",
|
||||
"name": "01 TV",
|
||||
"alt_names": [],
|
||||
"network": null,
|
||||
"owners": [],
|
||||
"country": "FR",
|
||||
"subdivision": null,
|
||||
"city": "Boulogne",
|
||||
"broadcast_area": [
|
||||
"c/FR"
|
||||
],
|
||||
"languages": [
|
||||
"fra"
|
||||
],
|
||||
"categories": [
|
||||
"education",
|
||||
"news"
|
||||
],
|
||||
"is_nsfw": false,
|
||||
"launched": null,
|
||||
"closed": null,
|
||||
"replaced_by": null,
|
||||
"website": "https://www.01net.com/tag/01nettv/",
|
||||
"logo": "https://i.imgur.com/RMucFq8.png"
|
||||
},
|
||||
{
|
||||
"id": "FashionTVJohannesburg.fr",
|
||||
"name": "FashionTV Johannesburg",
|
||||
"alt_names": [],
|
||||
"network": null,
|
||||
"owners": [
|
||||
"Michel Adam Lisowski"
|
||||
],
|
||||
"country": "FR",
|
||||
"subdivision": null,
|
||||
"city": "Paris",
|
||||
"broadcast_area": [
|
||||
"c/FR"
|
||||
],
|
||||
"languages": [
|
||||
"eng"
|
||||
],
|
||||
"categories": [
|
||||
"lifestyle"
|
||||
],
|
||||
"is_nsfw": false,
|
||||
"launched": "2000-10-01",
|
||||
"closed": null,
|
||||
"replaced_by": null,
|
||||
"website": "https://www.fashiontv.com/",
|
||||
"logo": "https://i.imgur.com/6u4tZn6.png"
|
||||
},
|
||||
{
|
||||
"id": "XtremaCartoons.ar",
|
||||
"name": "Xtrema Cartoons",
|
||||
"alt_names": [],
|
||||
"network": null,
|
||||
"owners": [],
|
||||
"country": "AR",
|
||||
"subdivision": null,
|
||||
"city": null,
|
||||
"broadcast_area": [
|
||||
"c/AR"
|
||||
],
|
||||
"languages": [
|
||||
"spa"
|
||||
],
|
||||
"categories": [
|
||||
"animation",
|
||||
"kids"
|
||||
],
|
||||
"is_nsfw": false,
|
||||
"launched": null,
|
||||
"closed": null,
|
||||
"replaced_by": null,
|
||||
"website": "https://xtrematv.com/",
|
||||
"logo": "https://i.imgur.com/X2d8y4e.png"
|
||||
},
|
||||
{
|
||||
"id": "XtremaRetroCartoons.ar",
|
||||
"name": "Xtrema Retro Cartoons",
|
||||
"alt_names": [],
|
||||
"network": null,
|
||||
"owners": [],
|
||||
"country": "AR",
|
||||
"subdivision": null,
|
||||
"city": null,
|
||||
"broadcast_area": [
|
||||
"c/AR"
|
||||
],
|
||||
"languages": [
|
||||
"spa"
|
||||
],
|
||||
"categories": [
|
||||
"animation"
|
||||
],
|
||||
"is_nsfw": false,
|
||||
"launched": null,
|
||||
"closed": null,
|
||||
"replaced_by": null,
|
||||
"website": null,
|
||||
"logo": "https://i.imgur.com/60ylWbQ.png"
|
||||
},
|
||||
{
|
||||
"id": "TV1.dz",
|
||||
"name": "TV1",
|
||||
"alt_names": [
|
||||
"الجزائرية الأولى",
|
||||
"Algerian Television",
|
||||
"التلفزيون الجزائري",
|
||||
"The Terrestrial Channel",
|
||||
"القناة الأرضية"
|
||||
],
|
||||
"network": null,
|
||||
"owners": [
|
||||
"EPTV"
|
||||
],
|
||||
"country": "DZ",
|
||||
"subdivision": null,
|
||||
"city": "Algiers",
|
||||
"broadcast_area": [
|
||||
"c/DZ"
|
||||
],
|
||||
"languages": [
|
||||
"ara",
|
||||
"fra"
|
||||
],
|
||||
"categories": [
|
||||
"general"
|
||||
],
|
||||
"is_nsfw": false,
|
||||
"launched": "1956-12-24",
|
||||
"closed": null,
|
||||
"replaced_by": null,
|
||||
"website": "https://www.entv.dz/",
|
||||
"logo": "https://i.imgur.com/F0DOrxX.png"
|
||||
},
|
||||
{
|
||||
"id": "K11UUD1.as",
|
||||
"name": "K11UU-D1",
|
||||
"alt_names": [],
|
||||
"network": "Hope Channel",
|
||||
"owners": [
|
||||
"American Samoa Adventist Media Ministry Inc"
|
||||
],
|
||||
"country": "AS",
|
||||
"subdivision": null,
|
||||
"city": "Pago Pago",
|
||||
"broadcast_area": [
|
||||
"c/AS"
|
||||
],
|
||||
"languages": [
|
||||
"eng"
|
||||
],
|
||||
"categories": [
|
||||
"religious"
|
||||
],
|
||||
"is_nsfw": false,
|
||||
"launched": "2003-10-14",
|
||||
"closed": null,
|
||||
"replaced_by": null,
|
||||
"website": "https://asamtv.org/",
|
||||
"logo": "https://i.imgur.com/NQD8Zer.png"
|
||||
},
|
||||
{
|
||||
"id": "13MaxTelevision.ar",
|
||||
"name": "13Max Television",
|
||||
"alt_names": [
|
||||
"13Max Televisión"
|
||||
],
|
||||
"network": null,
|
||||
"owners": [
|
||||
"Río Paraná TV SRL"
|
||||
],
|
||||
"country": "AR",
|
||||
"subdivision": "AR-W",
|
||||
"city": "Corrientes",
|
||||
"broadcast_area": [
|
||||
"s/AR-W"
|
||||
],
|
||||
"languages": [
|
||||
"spa"
|
||||
],
|
||||
"categories": [
|
||||
"general"
|
||||
],
|
||||
"is_nsfw": false,
|
||||
"launched": "1965-01-01",
|
||||
"closed": null,
|
||||
"replaced_by": null,
|
||||
"website": "https://live-tv-channels.org/livetv/ar-13-max-tv.html",
|
||||
"logo": "https://i.imgur.com/QvF4l2t.png"
|
||||
},
|
||||
{
|
||||
"id": "Bizarre.al",
|
||||
"name": "Bizarre",
|
||||
"alt_names": [],
|
||||
"network": null,
|
||||
"owners": [],
|
||||
"country": "AL",
|
||||
"subdivision": null,
|
||||
"city": null,
|
||||
"broadcast_area": [
|
||||
"c/AL"
|
||||
],
|
||||
"languages": [
|
||||
"sqi"
|
||||
],
|
||||
"categories": [
|
||||
"xxx"
|
||||
],
|
||||
"is_nsfw": true,
|
||||
"launched": null,
|
||||
"closed": null,
|
||||
"replaced_by": null,
|
||||
"website": "http://www.tring.al/",
|
||||
"logo": "https://i.imgur.com/vpS477d.png"
|
||||
},
|
||||
{
|
||||
"id": "AynaTV.af",
|
||||
"name": "Ayna TV",
|
||||
"alt_names": [],
|
||||
"network": null,
|
||||
"owners": [
|
||||
"Abdul Rashid Dostum"
|
||||
],
|
||||
"country": "AF",
|
||||
"subdivision": null,
|
||||
"city": "Kabul",
|
||||
"broadcast_area": [
|
||||
"c/AF"
|
||||
],
|
||||
"languages": [
|
||||
"pus"
|
||||
],
|
||||
"categories": [
|
||||
"general"
|
||||
],
|
||||
"is_nsfw": false,
|
||||
"launched": "2004-01-01",
|
||||
"closed": "2022-02-16",
|
||||
"replaced_by": null,
|
||||
"website": "http://www.ayna.af/",
|
||||
"logo": "https://i.imgur.com/2tHlT5Q.png"
|
||||
},
|
||||
{
|
||||
"id": "LaLiganaZap.ao",
|
||||
"name": "LaLiga na Zap",
|
||||
"alt_names": [],
|
||||
"network": null,
|
||||
"owners": [
|
||||
"ZAP Angola"
|
||||
],
|
||||
"country": "AO",
|
||||
"subdivision": null,
|
||||
"city": null,
|
||||
"broadcast_area": [
|
||||
"c/AO"
|
||||
],
|
||||
"languages": [
|
||||
"por"
|
||||
],
|
||||
"categories": [
|
||||
"sports"
|
||||
],
|
||||
"is_nsfw": false,
|
||||
"launched": null,
|
||||
"closed": null,
|
||||
"replaced_by": null,
|
||||
"website": "https://www.zap.co.ao/",
|
||||
"logo": "https://i.imgur.com/NWFShcJ.png"
|
||||
},
|
||||
{
|
||||
"id": "SEN502.us",
|
||||
"name": "SEN 502",
|
||||
"alt_names": [
|
||||
"TV2 Sports"
|
||||
],
|
||||
"network": null,
|
||||
"owners": [],
|
||||
"country": "US",
|
||||
"subdivision": null,
|
||||
"city": null,
|
||||
"broadcast_area": [
|
||||
"c/US"
|
||||
],
|
||||
"languages": [
|
||||
"eng"
|
||||
],
|
||||
"categories": [
|
||||
"sports"
|
||||
],
|
||||
"is_nsfw": false,
|
||||
"launched": null,
|
||||
"closed": null,
|
||||
"replaced_by": null,
|
||||
"website": "https://www.senetwork.tv/",
|
||||
"logo": "https://i.imgur.com/cEGW3pw.png"
|
||||
},
|
||||
{
|
||||
"id": "SEN550.us",
|
||||
"name": "SEN 550",
|
||||
"alt_names": [],
|
||||
"network": null,
|
||||
"owners": [],
|
||||
"country": "US",
|
||||
"subdivision": null,
|
||||
"city": null,
|
||||
"broadcast_area": [
|
||||
"c/US"
|
||||
],
|
||||
"languages": [
|
||||
"eng"
|
||||
],
|
||||
"categories": [
|
||||
"sports"
|
||||
],
|
||||
"is_nsfw": false,
|
||||
"launched": null,
|
||||
"closed": null,
|
||||
"replaced_by": null,
|
||||
"website": "https://www.senetwork.tv/",
|
||||
"logo": "https://i.imgur.com/T4vLkeH.png"
|
||||
},
|
||||
{
|
||||
"id": "CFCNTV2.ca",
|
||||
"name": "CFCN-TV-2",
|
||||
"alt_names": [],
|
||||
"network": "CTV",
|
||||
"owners": [
|
||||
"Bell Media"
|
||||
],
|
||||
"country": "CA",
|
||||
"subdivision": "CA-AB",
|
||||
"city": "Banff",
|
||||
"broadcast_area": [
|
||||
"s/CA-AB"
|
||||
],
|
||||
"languages": [
|
||||
"eng"
|
||||
],
|
||||
"categories": [
|
||||
"general"
|
||||
],
|
||||
"is_nsfw": false,
|
||||
"launched": null,
|
||||
"closed": null,
|
||||
"replaced_by": null,
|
||||
"website": "https://calgary.ctvnews.ca/",
|
||||
"logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/CTV_logo_2018.svg/512px-CTV_logo_2018.svg.png"
|
||||
},
|
||||
{
|
||||
"id": "ORF2Europe.at",
|
||||
"name": "ORF 2 Europe",
|
||||
"alt_names": [],
|
||||
"network": "ORF",
|
||||
"owners": [
|
||||
"ORF"
|
||||
],
|
||||
"country": "AT",
|
||||
"subdivision": "AT-8",
|
||||
"city": "Vienna",
|
||||
"broadcast_area": [
|
||||
"r/EUR"
|
||||
],
|
||||
"languages": [
|
||||
"deu"
|
||||
],
|
||||
"categories": [
|
||||
"culture"
|
||||
],
|
||||
"is_nsfw": false,
|
||||
"launched": "2004-07-05",
|
||||
"closed": null,
|
||||
"replaced_by": null,
|
||||
"website": "https://tv.orf.at/",
|
||||
"logo": "https://i.imgur.com/Hmcl4qR.png"
|
||||
}
|
||||
]
|
18
tests/__data__/input/countries.json
Normal file
18
tests/__data__/input/countries.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
[
|
||||
{
|
||||
"name": "Dominican Republic",
|
||||
"code": "DO",
|
||||
"languages": [
|
||||
"spa"
|
||||
],
|
||||
"flag": "🇩🇴"
|
||||
},
|
||||
{
|
||||
"name": "France",
|
||||
"code": "FR",
|
||||
"languages": [
|
||||
"fra"
|
||||
],
|
||||
"flag": "🇫🇷"
|
||||
}
|
||||
]
|
9
tests/__data__/input/guides.json
Normal file
9
tests/__data__/input/guides.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
[
|
||||
{
|
||||
"channel": "LaLiganaZap.ao",
|
||||
"site": "zap.co.ao",
|
||||
"site_id": "2386",
|
||||
"site_name": "La Liga HD",
|
||||
"lang": "pt"
|
||||
}
|
||||
]
|
14
tests/__data__/input/languages.json
Normal file
14
tests/__data__/input/languages.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
[
|
||||
{
|
||||
"code": "eng",
|
||||
"name": "English"
|
||||
},
|
||||
{
|
||||
"code": "fra",
|
||||
"name": "French"
|
||||
},
|
||||
{
|
||||
"code": "spa",
|
||||
"name": "Spanish"
|
||||
}
|
||||
]
|
58
tests/__data__/input/regions.json
Normal file
58
tests/__data__/input/regions.json
Normal file
|
@ -0,0 +1,58 @@
|
|||
[
|
||||
{
|
||||
"code": "EUR",
|
||||
"name": "Europe",
|
||||
"countries": [
|
||||
"AD",
|
||||
"AL",
|
||||
"AM",
|
||||
"AT",
|
||||
"AZ",
|
||||
"BA",
|
||||
"BE",
|
||||
"BG",
|
||||
"BY",
|
||||
"CH",
|
||||
"CY",
|
||||
"CZ",
|
||||
"DE",
|
||||
"DK",
|
||||
"EE",
|
||||
"ES",
|
||||
"FI",
|
||||
"FR",
|
||||
"GE",
|
||||
"GR",
|
||||
"HR",
|
||||
"HU",
|
||||
"IE",
|
||||
"IS",
|
||||
"IT",
|
||||
"KZ",
|
||||
"LI",
|
||||
"LT",
|
||||
"LU",
|
||||
"LV",
|
||||
"MC",
|
||||
"MD",
|
||||
"ME",
|
||||
"MK",
|
||||
"MT",
|
||||
"NL",
|
||||
"NO",
|
||||
"PL",
|
||||
"PT",
|
||||
"RO",
|
||||
"RS",
|
||||
"RU",
|
||||
"SE",
|
||||
"SI",
|
||||
"SK",
|
||||
"SM",
|
||||
"TR",
|
||||
"UA",
|
||||
"UK",
|
||||
"VA"
|
||||
]
|
||||
}
|
||||
]
|
9
tests/__data__/input/streams.json
Normal file
9
tests/__data__/input/streams.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
[
|
||||
{
|
||||
"channel": "XtremaCartoons.ar",
|
||||
"url": "https://stmv6.voxtvhd.com.br/xtremacartoons/xtremacartoons/playlist.m3u8",
|
||||
"timeshift": null,
|
||||
"http_referrer": "https://xtrematv.com/?p=1390",
|
||||
"user_agent": null
|
||||
}
|
||||
]
|
7
tests/__data__/input/subdivisions.json
Normal file
7
tests/__data__/input/subdivisions.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
[
|
||||
{
|
||||
"country": "AR",
|
||||
"name": "Corrientes",
|
||||
"code": "AR-W"
|
||||
}
|
||||
]
|
1
tests/__mocks__/$app/environment.js
Normal file
1
tests/__mocks__/$app/environment.js
Normal file
|
@ -0,0 +1 @@
|
|||
export const browser = true
|
1
tests/__mocks__/$app/navigation.js
Normal file
1
tests/__mocks__/$app/navigation.js
Normal file
|
@ -0,0 +1 @@
|
|||
export function pushState() {}
|
359
tests/store.test.js
Normal file
359
tests/store.test.js
Normal file
|
@ -0,0 +1,359 @@
|
|||
import { search, fetchChannels, filteredChannels } from '../src/store'
|
||||
import { get } from 'svelte/store'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { jest } from '@jest/globals'
|
||||
|
||||
const API_ENDPOINT = 'https://iptv-org.github.io/api'
|
||||
|
||||
beforeEach(async () => {
|
||||
global.fetch = mockFetch()
|
||||
await fetchChannels()
|
||||
})
|
||||
|
||||
describe('search', () => {
|
||||
it('return all channels by default', () => {
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(15)
|
||||
})
|
||||
|
||||
it('returns empty list if there is no such channel', () => {
|
||||
search('lorem')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(0)
|
||||
})
|
||||
|
||||
it('can find channel by name', () => {
|
||||
search('name:002')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: '002RadioTV.do'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channels by multiple words', () => {
|
||||
search('Xtrema Cartoons')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(2)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: 'XtremaCartoons.ar'
|
||||
})
|
||||
expect(results[1]).toMatchObject({
|
||||
id: 'XtremaRetroCartoons.ar'
|
||||
})
|
||||
})
|
||||
|
||||
it('can search for one of two words', () => {
|
||||
search('Johannesburg,002')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(2)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: '002RadioTV.do'
|
||||
})
|
||||
expect(results[1]).toMatchObject({
|
||||
id: 'FashionTVJohannesburg.fr'
|
||||
})
|
||||
})
|
||||
|
||||
it('can search for exact word matches', () => {
|
||||
search('"Xtrema Cartoons"')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: 'XtremaCartoons.ar'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channels by id', () => {
|
||||
search('id:002RadioTV.do')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: '002RadioTV.do'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channels by alternative names', () => {
|
||||
search('alt_names:التلفزيون')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: 'TV1.dz'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channels by network', () => {
|
||||
search('network:Hope')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: 'K11UUD1.as'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channels without the owner', () => {
|
||||
search('owners:^$')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(7)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: '002RadioTV.do'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channels by country code', () => {
|
||||
search('country:DO')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: '002RadioTV.do'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channels that are broadcast from the same region', () => {
|
||||
search('subdivision:AR-W')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: '13MaxTelevision.ar'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channels that are broadcast from the same city', () => {
|
||||
search('city:Corrientes')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: '13MaxTelevision.ar'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channels that are broadcast in the same region', () => {
|
||||
search('broadcast_area:s/AR-W')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: '13MaxTelevision.ar'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channels that are broadcast in the same language', () => {
|
||||
search('languages:spa')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(4)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: '002RadioTV.do'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channels that have the same category', () => {
|
||||
search('categories:lifestyle')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: 'FashionTVJohannesburg.fr'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channels with website', () => {
|
||||
search('website:.')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(14)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: '002RadioTV.do'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channels marked as NSFW', () => {
|
||||
search('is_nsfw:true')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: 'Bizarre.al'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find closed channels', () => {
|
||||
search('is_closed:true')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: 'AynaTV.af'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find blocked channels', () => {
|
||||
search('is_blocked:true')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: 'Bizarre.al'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channels that have streams', () => {
|
||||
search('streams:>0')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: 'XtremaCartoons.ar'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channels that have guides', () => {
|
||||
search('guides:>0')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: 'LaLiganaZap.ao'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channels by query in lower case', () => {
|
||||
search('tv2')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(2)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: 'SEN502.us'
|
||||
})
|
||||
expect(results[1]).toMatchObject({
|
||||
id: 'CFCNTV2.ca'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channel by alternative name after another query', () => {
|
||||
search('tv2')
|
||||
search('alt_names:tv2')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: 'SEN502.us'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channel by broadcast area name', () => {
|
||||
search('broadcast_area:"dominican republic"')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: '002RadioTV.do'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channel by country name', () => {
|
||||
search('country:"dominican republic"')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: '002RadioTV.do'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channel by region code', () => {
|
||||
search('broadcast_area:r/EUR')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: 'ORF2Europe.at'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channel by region name', () => {
|
||||
search('broadcast_area:europe')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: 'ORF2Europe.at'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channel by country name from broadcast region', () => {
|
||||
search('broadcast_area:france')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(3)
|
||||
expect(results[2]).toMatchObject({
|
||||
id: 'ORF2Europe.at'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channel by display name from the guides', () => {
|
||||
search('La Liga HD')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: 'LaLiganaZap.ao'
|
||||
})
|
||||
})
|
||||
|
||||
it('can find channel by stream url', () => {
|
||||
search('https://stmv6.voxtvhd.com.br/xtremacartoons/xtremacartoons/playlist.m3u8')
|
||||
|
||||
const results = get(filteredChannels)
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0]).toMatchObject({
|
||||
id: 'XtremaCartoons.ar'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function mockFetch() {
|
||||
return jest.fn().mockImplementation(url =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () => {
|
||||
if (url === `${API_ENDPOINT}/channels.json`)
|
||||
return loadJson('tests/__data__/input/channels.json')
|
||||
if (url === `${API_ENDPOINT}/countries.json`)
|
||||
return loadJson('tests/__data__/input/countries.json')
|
||||
if (url === `${API_ENDPOINT}/languages.json`)
|
||||
return loadJson('tests/__data__/input/languages.json')
|
||||
if (url === `${API_ENDPOINT}/guides.json`)
|
||||
return loadJson('tests/__data__/input/guides.json')
|
||||
if (url === `${API_ENDPOINT}/regions.json`)
|
||||
return loadJson('tests/__data__/input/regions.json')
|
||||
if (url === `${API_ENDPOINT}/blocklist.json`)
|
||||
return loadJson('tests/__data__/input/blocklist.json')
|
||||
if (url === `${API_ENDPOINT}/subdivisions.json`)
|
||||
return loadJson('tests/__data__/input/subdivisions.json')
|
||||
if (url === `${API_ENDPOINT}/categories.json`)
|
||||
return loadJson('tests/__data__/input/categories.json')
|
||||
if (url === `${API_ENDPOINT}/streams.json`)
|
||||
return loadJson('tests/__data__/input/streams.json')
|
||||
return []
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function loadJson(filepath) {
|
||||
return JSON.parse(fs.readFileSync(path.resolve(filepath), 'utf8'))
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue