Create Channel model

This commit is contained in:
freearhey 2025-03-07 04:27:23 +03:00
parent 4bfb062731
commit 9aa1b9fe50
11 changed files with 123 additions and 56 deletions

View file

@ -9,7 +9,7 @@
nsfw: 'The channel has been added to our blocklist due to NSFW content' nsfw: 'The channel has been added to our blocklist due to NSFW content'
} }
const blocklistRefs = channel.blocklist_records const blocklistRefs = channel._blocklistRecords
.map(record => { .map(record => {
let refName let refName

View file

@ -13,6 +13,7 @@
const guides = channel._guides const guides = channel._guides
const streams = channel._streams const streams = channel._streams
const displayName = channel._displayName
const [name, country] = channel.id.split('.') const [name, country] = channel.id.split('.')
@ -20,11 +21,7 @@
let prevUrl = '/' let prevUrl = '/'
const onOpened = () => { const onOpened = () => {
prevUrl = window.location.href prevUrl = window.location.href
window.history.pushState( window.history.pushState({}, `${displayName} • iptv-org`, `/channels/${country}/${name}`)
{},
`${channel.displayName} • iptv-org`,
`/channels/${country}/${name}`
)
} }
const onClose = () => { const onClose = () => {
window.history.pushState({}, `iptv-org`, prevUrl) window.history.pushState({}, `iptv-org`, prevUrl)
@ -32,13 +29,13 @@
const showGuides = () => const showGuides = () =>
open( open(
GuidesPopup, GuidesPopup,
{ guides, title: channel.displayName }, { guides, title: displayName },
{ transitionBgProps: { duration: 0 }, transitionWindowProps: { duration: 0 } } { transitionBgProps: { duration: 0 }, transitionWindowProps: { duration: 0 } }
) )
const showStreams = () => const showStreams = () =>
open( open(
StreamsPopup, StreamsPopup,
{ streams, title: channel.displayName }, { streams, title: displayName },
{ transitionBgProps: { duration: 0 }, transitionWindowProps: { duration: 0 } } { transitionBgProps: { duration: 0 }, transitionWindowProps: { duration: 0 } }
) )
const showChannelData = () => { const showChannelData = () => {
@ -89,7 +86,7 @@
loading="lazy" loading="lazy"
referrerpolicy="no-referrer" referrerpolicy="no-referrer"
src={channel.logo} src={channel.logo}
alt={channel.displayName} alt={displayName}
/> />
{/if} {/if}
</div> </div>
@ -103,9 +100,9 @@
href="/channels/{country}/{name}" href="/channels/{country}/{name}"
tabindex="0" tabindex="0"
class="font-normal text-gray-600 dark:text-white hover:underline hover:text-blue-500 truncate whitespace-nowrap" 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> </a>
<div class="flex space-x-2"> <div class="flex space-x-2">
{#if channel.is_closed} {#if channel.is_closed}

View file

@ -30,7 +30,7 @@
> >
<div class="w-2/3 overflow-hidden"> <div class="w-2/3 overflow-hidden">
<div class="flex items-center space-x-3"> <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"> <div class="flex space-x-2">
{#if channel.is_closed} {#if channel.is_closed}
<ClosedBadge {channel} /> <ClosedBadge {channel} />

View file

@ -6,7 +6,7 @@
export let channel export let channel
const endpoint = 'https://github.com/iptv-org/database/issues/new' 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 labels = 'channels:edit'
const template = '__channels_edit.yml' const template = '__channels_edit.yml'

View file

@ -1,7 +1,5 @@
<script> <script>
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { goto } from '$app/navigation'
import { query, hasQuery, setSearchParam } from '~/store'
export let data export let data
export let close = () => {} export let close = () => {}
@ -19,7 +17,7 @@
{ {
name: 'owners', name: 'owners',
type: 'link[]', 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', name: 'country',
@ -41,7 +39,7 @@
{ {
name: 'broadcast_area', name: 'broadcast_area',
type: 'link[]', type: 'link[]',
value: data._broadcast_area.map(v => ({ value: data._broadcastArea.map(v => ({
label: v.name, label: v.name,
query: `broadcast_area:${v.type}/${v.code}` query: `broadcast_area:${v.type}/${v.code}`
})) }))
@ -49,12 +47,12 @@
{ {
name: 'languages', name: 'languages',
type: 'link[]', 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', name: 'categories',
type: 'link[]', 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', name: 'is_nsfw',

View file

@ -15,12 +15,12 @@
result: 'Find channels that have "Nat Geo" in the name.' result: 'Find channels that have "Nat Geo" in the name.'
}, },
{ {
query: 'alt_names:חינוכית', query: 'alt_name:חינוכית',
result: 'Finds channels whose alternative name contains "חינוכית".' result: 'Finds channels whose alternative name contains "חינוכית".'
}, },
{ query: 'network:ABC', result: 'Finds all channels operated by the ABC Network.' }, { query: 'network:ABC', result: 'Finds all channels operated by the ABC Network.' },
{ {
query: 'owners:^$', query: 'owner:^$',
result: 'Finds channels that have no owner listed.' result: 'Finds channels that have no owner listed.'
}, },
{ query: 'country:GY', result: 'Finds all channels that are broadcast from Guyana.' }, { 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: '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: '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: 'language:fra', result: 'Find channels that are broadcast in French.' },
{ query: 'categories:news', result: 'Finds all the news channels.' }, { query: 'category:news', result: 'Finds all the news channels.' },
{ query: 'website:.', result: 'Finds channels that have a link to the official website.' }, { query: 'website:.', result: 'Finds channels that have a link to the official website.' },
{ query: 'is_nsfw:true', result: 'Finds channels marked as NSFW.' }, { query: 'is_nsfw:true', result: 'Finds channels marked as NSFW.' },
{ {

44
src/models/channel.js Normal file
View file

@ -0,0 +1,44 @@
export class Channel {
constructor(data) {
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 = Array.isArray(data.streams) ? data.streams.length : 0
this.guides = Array.isArray(data.guides) ? data.guides.length : 0
this.is_blocked = Array.isArray(data.blocklistRecords) && data.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 = data.streams || []
this._guides = data.guides || []
this._blocklistRecords = data.blocklistRecords || []
}
}

1
src/models/index.js Normal file
View file

@ -0,0 +1 @@
export * from './channel'

View file

@ -23,7 +23,7 @@
let _countries = [] let _countries = []
let isLoading = true let isLoading = true
$: groupedByCountry = _.groupBy($filteredChannels, 'country') $: groupedByCountry = _.groupBy($filteredChannels, channel => channel._country.code)
onMount(async () => { onMount(async () => {
if (!$channels.length) { if (!$channels.length) {

View file

@ -8,9 +8,10 @@
export let data export let data
let isLoading = false let isLoading = false
let channel = data.channel const channel = data.channel
let streams = channel ? channel._streams : [] const streams = channel ? channel._streams : []
let guides = channel ? channel._guides : [] const guides = channel ? channel._guides : []
const displayName = channel._displayName
const structuredData = { const structuredData = {
'@context': 'https://schema.org/', '@context': 'https://schema.org/',
@ -28,8 +29,8 @@
</script> </script>
<svelte:head> <svelte:head>
<title>{channel && channel.displayName ? `${channel.displayName} iptv-org` : 'iptv-org'}</title> <title>{channel && displayName ? `${displayName} iptv-org` : 'iptv-org'}</title>
<meta name="description" content="Detailed description of {channel.displayName}." /> <meta name="description" content="Detailed description of {displayName}." />
{@html schema()} {@html schema()}
</svelte:head> </svelte:head>
@ -54,7 +55,7 @@
<div class="w-2/3 overflow-hidden"> <div class="w-2/3 overflow-hidden">
<div class="flex space-x-3"> <div class="flex space-x-3">
<h1 class="text-l font-medium text-gray-900 dark:text-white"> <h1 class="text-l font-medium text-gray-900 dark:text-white">
{channel.displayName} {displayName}
</h1> </h1>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
{#if channel.is_closed} {#if channel.is_closed}

View file

@ -3,6 +3,7 @@ import { Playlist, Link } from 'iptv-playlist-generator'
import sj from '@freearhey/search-js' import sj from '@freearhey/search-js'
import _ from 'lodash' import _ from 'lodash'
import { browser } from '$app/environment' import { browser } from '$app/environment'
import { Channel } from './models'
export const query = writable('') export const query = writable('')
export const hasQuery = writable(false) export const hasQuery = writable(false)
@ -32,7 +33,7 @@ export async function fetchChannels() {
countries.set(api.countries) 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) channels.set(_channels)
filteredChannels.set(_channels) filteredChannels.set(_channels)
@ -41,13 +42,17 @@ export async function fetchChannels() {
'id', 'id',
'name', 'name',
'alt_names', 'alt_names',
'alt_name',
'network', 'network',
'owner',
'owners', 'owners',
'country', 'country',
'subdivision', 'subdivision',
'city', 'city',
'broadcast_area', 'broadcast_area',
'language',
'languages', 'languages',
'category',
'categories', 'categories',
'launched', 'launched',
'closed', 'closed',
@ -155,38 +160,59 @@ async function loadAPI() {
return api return api
} }
export function transformChannel(channel, data) { function createChannel(data, api) {
channel._streams = data.streams[channel.id] || [] let broadcastArea = []
channel._guides = data.guides[channel.id] || [] let regionCountries = []
channel._country = data.countries[channel.country]
channel._subdivision = data.subdivisions[channel.subdivision] data.broadcast_area.forEach(areaCode => {
channel._languages = channel.languages.map(code => data.languages[code]).filter(i => i) const [type, code] = areaCode.split('/')
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) { switch (type) {
case 'c': case 'c':
return { type, ...data.countries[code] } const country = api.countries[code]
if (country) broadcastArea.push({ type, code: country.code, name: country.name })
break
case 'r': case 'r':
return { type, ...data.regions[code] } 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': case 's':
return { type, ...data.subdivisions[code] } const subdivision = api.subdivisions[code]
if (subdivision)
broadcastArea.push({ type, code: subdivision.code, name: subdivision.name })
break
} }
}) })
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 return new Channel({
channel.displayName = isChannelNameRepeated id: data.id,
? `${channel.name} (${channel._country.name})` name: data.name,
: channel.name altNames: data.alt_names,
network: data.network,
return channel 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
})
} }
function getStreams() { function getStreams() {