mirror of
https://github.com/iptv-org/iptv-org.github.io.git
synced 2025-05-11 17:40:05 -04:00
542 lines
16 KiB
TypeScript
542 lines
16 KiB
TypeScript
import type { ChannelSearchable, ChannelSerializedData, ChannelData } from '../types/channel'
|
|
import type { BlocklistRecordSerializedData } from '~/types/blocklistRecord'
|
|
import type { HTMLPreviewField } from '../types/htmlPreviewField'
|
|
import type { CategorySerializedData } from '~/types/category'
|
|
import type { FeedSerializedData } from '~/types/feed'
|
|
import { Collection, type Dictionary } from '@freearhey/core/browser'
|
|
import dayjs, { type Dayjs } from 'dayjs'
|
|
import {
|
|
BlocklistRecord,
|
|
BroadcastArea,
|
|
Subdivision,
|
|
Category,
|
|
Language,
|
|
Country,
|
|
Stream,
|
|
Guide,
|
|
Feed
|
|
} from '.'
|
|
|
|
export class Channel {
|
|
id: string
|
|
name: string
|
|
altNames: Collection = new Collection()
|
|
networkName?: string
|
|
ownerNames: Collection = new Collection()
|
|
countryCode: string
|
|
country?: Country
|
|
subdivisionCode?: string
|
|
subdivision?: Subdivision
|
|
cityName?: string
|
|
categoryIds: Collection = new Collection()
|
|
categories: Collection = new Collection()
|
|
isNSFW: boolean
|
|
launchedDateString?: string
|
|
launchedDate?: Dayjs
|
|
closedDateString?: string
|
|
closedDate?: Dayjs
|
|
replacedByStreamId?: string
|
|
replacedByChannelId?: string
|
|
websiteUrl?: string
|
|
logoUrl: string
|
|
blocklistRecords: Collection = new Collection()
|
|
feeds: Collection = new Collection()
|
|
hasUniqueName: boolean = true
|
|
|
|
constructor(data?: ChannelData) {
|
|
if (!data) return
|
|
|
|
this.id = data.id
|
|
this.name = data.name
|
|
this.altNames = new Collection(data.alt_names)
|
|
this.networkName = data.network
|
|
this.ownerNames = new Collection(data.owners)
|
|
this.countryCode = data.country
|
|
this.subdivisionCode = data.subdivision
|
|
this.cityName = data.city
|
|
this.categoryIds = new Collection(data.categories)
|
|
this.isNSFW = data.is_nsfw
|
|
this.launchedDateString = data.launched
|
|
this.launchedDate = data.launched ? dayjs(data.launched) : undefined
|
|
this.closedDateString = data.closed
|
|
this.closedDate = data.closed ? dayjs(data.closed) : undefined
|
|
this.replacedByStreamId = data.replaced_by || undefined
|
|
const [replacedByChannelId] = data.replaced_by ? data.replaced_by.split('@') : [undefined]
|
|
this.replacedByChannelId = replacedByChannelId
|
|
this.websiteUrl = data.website
|
|
this.logoUrl = data.logo
|
|
}
|
|
|
|
withCountry(countriesKeyByCode: Dictionary): this {
|
|
this.country = countriesKeyByCode.get(this.countryCode)
|
|
|
|
return this
|
|
}
|
|
|
|
withSubdivision(subdivisionsKeyByCode: Dictionary): this {
|
|
if (!this.subdivisionCode) return this
|
|
|
|
this.subdivision = subdivisionsKeyByCode.get(this.subdivisionCode)
|
|
|
|
return this
|
|
}
|
|
|
|
withCategories(categoriesKeyById: Dictionary): this {
|
|
this.categories = this.categoryIds.map((id: string) => categoriesKeyById.get(id))
|
|
|
|
return this
|
|
}
|
|
|
|
withBlocklistRecords(blocklistGroupedByChannelId: Dictionary): this {
|
|
this.blocklistRecords = new Collection(blocklistGroupedByChannelId.get(this.id))
|
|
|
|
return this
|
|
}
|
|
|
|
withFeeds(feedsGroupedByChannelId: Dictionary): this {
|
|
this.feeds = new Collection(feedsGroupedByChannelId.get(this.id))
|
|
|
|
return this
|
|
}
|
|
|
|
setHasUniqueName(channelsGroupedByName: Dictionary): this {
|
|
this.hasUniqueName = new Collection(channelsGroupedByName.get(this.name)).count() === 1
|
|
|
|
return this
|
|
}
|
|
|
|
getUniqueName(): string {
|
|
if (this.hasUniqueName) return this.name
|
|
if (!this.country) return this.name
|
|
|
|
return `${this.name} (${this.country.name})`
|
|
}
|
|
|
|
getCategories(): Collection {
|
|
if (!this.categories) return new Collection()
|
|
|
|
return this.categories
|
|
}
|
|
|
|
getStreams(): Collection {
|
|
let streams = new Collection()
|
|
|
|
this.getFeeds().forEach((feed: Feed) => {
|
|
streams = streams.concat(feed.getStreams())
|
|
})
|
|
|
|
return streams
|
|
}
|
|
|
|
getGuides(): Collection {
|
|
let guides = new Collection()
|
|
|
|
this.getFeeds().forEach((feed: Feed) => {
|
|
guides = guides.concat(feed.getGuides())
|
|
})
|
|
|
|
return guides
|
|
}
|
|
|
|
getLanguages(): Collection {
|
|
let languages = new Collection()
|
|
|
|
this.getFeeds().forEach((feed: Feed) => {
|
|
languages = languages.concat(feed.getLanguages())
|
|
})
|
|
|
|
return languages.uniqBy((language: Language) => language.code)
|
|
}
|
|
|
|
getLanguageCodes(): Collection {
|
|
return this.getLanguages().map((language: Language) => language.code)
|
|
}
|
|
|
|
getLanguageNames(): Collection {
|
|
return this.getLanguages().map((language: Language) => language.name)
|
|
}
|
|
|
|
getBroadcastAreaCodes(): Collection {
|
|
let broadcastAreaCodes = new Collection()
|
|
|
|
this.getFeeds().forEach((feed: Feed) => {
|
|
broadcastAreaCodes = broadcastAreaCodes.concat(feed.broadcastAreaCodes)
|
|
})
|
|
|
|
return broadcastAreaCodes.uniq()
|
|
}
|
|
|
|
hasGuides(): boolean {
|
|
return this.getGuides().notEmpty()
|
|
}
|
|
|
|
hasFeeds(): boolean {
|
|
return this.getFeeds().notEmpty()
|
|
}
|
|
|
|
hasStreams(): boolean {
|
|
return this.getStreams().notEmpty()
|
|
}
|
|
|
|
getDisplayName(): string {
|
|
return this.name
|
|
}
|
|
|
|
getPagePath(): string {
|
|
const [channelSlug, countryCode] = this.id.split('.')
|
|
if (!channelSlug || !countryCode) return ''
|
|
|
|
return `/channels/${countryCode}/${channelSlug}`
|
|
}
|
|
|
|
getPageUrl(): string {
|
|
const [channelSlug, countryCode] = this.id.split('.') || [null, null]
|
|
if (!channelSlug || !countryCode || typeof window === 'undefined') return ''
|
|
|
|
return `${window.location.protocol}//${window.location.host}/channels/${countryCode}/${channelSlug}`
|
|
}
|
|
|
|
isClosed(): boolean {
|
|
return !!this.closedDateString || !!this.replacedByStreamId
|
|
}
|
|
|
|
isBlocked(): boolean {
|
|
return this.blocklistRecords ? this.blocklistRecords.notEmpty() : false
|
|
}
|
|
|
|
getCountryName(): string {
|
|
return this.country ? this.country.name : ''
|
|
}
|
|
|
|
getGuideSiteNames(): Collection {
|
|
return this.getGuides().map((guide: Guide) => guide.siteName)
|
|
}
|
|
|
|
getStreamUrls(): Collection {
|
|
return this.getStreams().map((stream: Stream) => stream.url)
|
|
}
|
|
|
|
getFeeds(): Collection {
|
|
if (!this.feeds) return new Collection()
|
|
|
|
return this.feeds
|
|
}
|
|
|
|
getBroadcastLocationCodes(): Collection {
|
|
let broadcastLocationCodes = new Collection()
|
|
|
|
this.getFeeds().forEach((feed: Feed) => {
|
|
broadcastLocationCodes = broadcastLocationCodes.concat(feed.getBroadcastLocationCodes())
|
|
})
|
|
|
|
return broadcastLocationCodes.uniq()
|
|
}
|
|
|
|
getBroadcastLocationNames(): Collection {
|
|
let broadcastLocationNames = new Collection()
|
|
|
|
this.getFeeds().forEach((feed: Feed) => {
|
|
broadcastLocationNames = broadcastLocationNames.concat(feed.getBroadcastLocationNames())
|
|
})
|
|
|
|
return broadcastLocationNames.uniq()
|
|
}
|
|
|
|
getVideoFormats(): Collection {
|
|
let videoFormats = new Collection()
|
|
|
|
this.getFeeds().forEach((feed: Feed) => {
|
|
videoFormats.add(feed.videoFormat)
|
|
})
|
|
|
|
return videoFormats.uniq()
|
|
}
|
|
|
|
getTimezoneIds(): Collection {
|
|
let timezoneIds = new Collection()
|
|
|
|
this.getFeeds().forEach((feed: Feed) => {
|
|
timezoneIds = timezoneIds.concat(feed.timezoneIds)
|
|
})
|
|
|
|
return timezoneIds.uniq()
|
|
}
|
|
|
|
getBroadcastArea(): Collection {
|
|
let broadcastArea = new Collection()
|
|
|
|
this.getFeeds().forEach((feed: Feed) => {
|
|
broadcastArea = broadcastArea.concat(feed.getBroadcastArea())
|
|
})
|
|
|
|
return broadcastArea.uniqBy((broadcastArea: BroadcastArea) => broadcastArea.code)
|
|
}
|
|
|
|
getFeedNames(): Collection {
|
|
return this.getFeeds().map((feed: Feed) => feed.name)
|
|
}
|
|
|
|
getSearchable(): ChannelSearchable {
|
|
return {
|
|
id: this.id,
|
|
name: this.name,
|
|
alt_names: this.altNames.all(),
|
|
alt_name: this.altNames.all(),
|
|
network: this.networkName,
|
|
owner: this.ownerNames.all(),
|
|
owners: this.ownerNames.all(),
|
|
country: this.countryCode,
|
|
subdivision: this.subdivisionCode,
|
|
city: this.cityName,
|
|
category: this.categoryIds.all(),
|
|
categories: this.categoryIds.all(),
|
|
launched: this.launchedDateString,
|
|
closed: this.closedDateString,
|
|
replaced_by: this.replacedByStreamId,
|
|
website: this.websiteUrl,
|
|
is_nsfw: this.isNSFW,
|
|
is_closed: this.isClosed(),
|
|
is_blocked: this.isBlocked(),
|
|
feeds: this.getFeeds().count(),
|
|
streams: this.getStreams().count(),
|
|
guides: this.getGuides().count(),
|
|
language: this.getLanguageCodes().all(),
|
|
languages: this.getLanguageCodes().all(),
|
|
broadcast_area: this.getBroadcastAreaCodes().all(),
|
|
video_format: this.getVideoFormats().all(),
|
|
video_formats: this.getVideoFormats().all(),
|
|
timezone: this.getTimezoneIds().all(),
|
|
timezones: this.getTimezoneIds().all(),
|
|
_languageNames: this.getLanguageNames().all(),
|
|
_broadcastLocationCodes: this.getBroadcastLocationCodes().all(),
|
|
_broadcastLocationNames: this.getBroadcastLocationNames().all(),
|
|
_countryName: this.getCountryName(),
|
|
_guideSiteNames: this.getGuideSiteNames().all(),
|
|
_streamUrls: this.getStreamUrls().all(),
|
|
_feedNames: this.getFeedNames().all()
|
|
}
|
|
}
|
|
|
|
serialize(props = { withFeeds: true }): ChannelSerializedData {
|
|
return {
|
|
id: this.id,
|
|
name: this.name,
|
|
altNames: this.altNames.all(),
|
|
networkName: this.networkName,
|
|
ownerNames: this.ownerNames.all(),
|
|
countryCode: this.countryCode,
|
|
country: this.country ? this.country.serialize() : null,
|
|
subdivisionCode: this.subdivisionCode,
|
|
subdivision: this.subdivision ? this.subdivision.serialize() : null,
|
|
cityName: this.cityName,
|
|
categoryIds: this.categoryIds.all(),
|
|
categories: this.categories.map((category: Category) => category.serialize()).all(),
|
|
isNSFW: this.isNSFW,
|
|
launchedDateString: this.launchedDateString,
|
|
launchedDate: this.launchedDate ? this.launchedDate.toJSON() : null,
|
|
closedDateString: this.closedDateString,
|
|
closedDate: this.closedDate ? this.closedDate.toJSON() : null,
|
|
replacedByStreamId: this.replacedByStreamId,
|
|
replacedByChannelId: this.replacedByChannelId,
|
|
websiteUrl: this.websiteUrl,
|
|
logoUrl: this.logoUrl,
|
|
blocklistRecords: this.blocklistRecords
|
|
.map((blocklistRecord: BlocklistRecord) => blocklistRecord.serialize())
|
|
.all(),
|
|
feeds: props.withFeeds
|
|
? this.getFeeds()
|
|
.map((feed: Feed) => feed.serialize())
|
|
.all()
|
|
: [],
|
|
hasUniqueName: this.hasUniqueName
|
|
}
|
|
}
|
|
|
|
deserialize(data: ChannelSerializedData): this {
|
|
this.id = data.id || ''
|
|
this.name = data.name
|
|
this.altNames = new Collection(data.altNames)
|
|
this.networkName = data.networkName
|
|
this.ownerNames = new Collection(data.ownerNames)
|
|
this.countryCode = data.countryCode
|
|
this.country = new Country().deserialize(data.country)
|
|
this.subdivisionCode = data.subdivisionCode
|
|
this.cityName = data.cityName
|
|
this.categoryIds = new Collection(data.categoryIds)
|
|
this.categories = new Collection(data.categories).map((data: CategorySerializedData) =>
|
|
new Category().deserialize(data)
|
|
)
|
|
this.isNSFW = data.isNSFW
|
|
this.launchedDateString = data.launchedDateString
|
|
this.launchedDate = data.launchedDate ? dayjs(data.launchedDate) : undefined
|
|
this.closedDateString = data.closedDateString
|
|
this.closedDate = data.closedDate ? dayjs(data.closedDate) : undefined
|
|
this.replacedByStreamId = data.replacedByStreamId
|
|
this.replacedByChannelId = data.replacedByChannelId
|
|
this.websiteUrl = data.websiteUrl
|
|
this.logoUrl = data.logoUrl
|
|
this.blocklistRecords = new Collection(data.blocklistRecords).map(
|
|
(data: BlocklistRecordSerializedData) => new BlocklistRecord().deserialize(data)
|
|
)
|
|
this.feeds = new Collection(data.feeds).map((data: FeedSerializedData) =>
|
|
new Feed().deserialize(data)
|
|
)
|
|
this.hasUniqueName = data.hasUniqueName
|
|
|
|
return this
|
|
}
|
|
|
|
getFieldset(): HTMLPreviewField[] {
|
|
return [
|
|
{
|
|
name: 'logo',
|
|
type: 'image',
|
|
value: { src: this.logoUrl, alt: `${this.name} logo`, title: this.logoUrl }
|
|
},
|
|
{ name: 'id', type: 'string', value: this.id, title: this.id },
|
|
{ name: 'name', type: 'string', value: this.name, title: this.name },
|
|
{ name: 'alt_names', type: 'string[]', value: this.altNames.all() },
|
|
{
|
|
name: 'network',
|
|
type: 'link',
|
|
value: this.networkName
|
|
? { label: this.networkName, query: `network:${normalize(this.networkName)}` }
|
|
: null
|
|
},
|
|
{
|
|
name: 'owners',
|
|
type: 'link[]',
|
|
value: this.ownerNames
|
|
.map((name: string) => ({
|
|
label: name,
|
|
query: `owner:${normalize(name)}`
|
|
}))
|
|
.all()
|
|
},
|
|
{
|
|
name: 'country',
|
|
type: 'link',
|
|
value: this.country
|
|
? { label: this.country.name, query: `country:${this.country.code}` }
|
|
: null
|
|
},
|
|
{
|
|
name: 'subdivision',
|
|
type: 'link',
|
|
value: this.subdivision
|
|
? { label: this.subdivision.name, query: `subdivision:${this.subdivision.code}` }
|
|
: null
|
|
},
|
|
{
|
|
name: 'city',
|
|
type: 'link',
|
|
value: this.cityName ? { label: this.cityName, query: `city:${this.cityName}` } : null
|
|
},
|
|
{
|
|
name: 'broadcast_area',
|
|
type: 'link[]',
|
|
value: this.getBroadcastArea()
|
|
.map((broadcastArea: BroadcastArea) => ({
|
|
label: broadcastArea.getName(),
|
|
query: `broadcast_area:${broadcastArea.code}`
|
|
}))
|
|
.all()
|
|
},
|
|
{
|
|
name: 'timezones',
|
|
type: 'link[]',
|
|
value: this.getTimezoneIds()
|
|
.map((id: string) => ({
|
|
label: id,
|
|
query: `timezone:${id}`
|
|
}))
|
|
.all()
|
|
},
|
|
{
|
|
name: 'languages',
|
|
type: 'link[]',
|
|
value: this.getLanguages()
|
|
.map((language: Language) => ({
|
|
label: language.name,
|
|
query: `language:${language.code}`
|
|
}))
|
|
.all()
|
|
},
|
|
{
|
|
name: 'categories',
|
|
type: 'link[]',
|
|
value: this.categories
|
|
.map((category: Category) => ({
|
|
label: category.name,
|
|
query: `category:${category.id}`
|
|
}))
|
|
.all()
|
|
},
|
|
{
|
|
name: 'is_nsfw',
|
|
type: 'link',
|
|
value: { label: this.isNSFW.toString(), query: `is_nsfw:${this.isNSFW.toString()}` }
|
|
},
|
|
{
|
|
name: 'video_formats',
|
|
type: 'link[]',
|
|
value: this.getVideoFormats()
|
|
.map((format: string) => ({
|
|
label: format,
|
|
query: `video_format:${format}`
|
|
}))
|
|
.all()
|
|
},
|
|
{
|
|
name: 'launched',
|
|
type: 'string',
|
|
value: this.launchedDate ? this.launchedDate.format('D MMMM YYYY') : null,
|
|
title: this.launchedDateString
|
|
},
|
|
{
|
|
name: 'closed',
|
|
type: 'string',
|
|
value: this.closedDate ? this.closedDate.format('D MMMM YYYY') : null,
|
|
title: this.closedDateString
|
|
},
|
|
{
|
|
name: 'replaced_by',
|
|
type: 'link',
|
|
value: this.replacedByStreamId
|
|
? {
|
|
label: this.replacedByStreamId,
|
|
query: `id:${this.replacedByChannelId.replace('.', '\\.')}`
|
|
}
|
|
: null
|
|
},
|
|
{
|
|
name: 'website',
|
|
type: 'external_link',
|
|
value: this.websiteUrl
|
|
? { href: this.websiteUrl, title: this.websiteUrl, label: this.websiteUrl }
|
|
: null
|
|
}
|
|
].filter((field: HTMLPreviewField) =>
|
|
Array.isArray(field.value) ? field.value.length : field.value
|
|
)
|
|
}
|
|
|
|
getStructuredData() {
|
|
return {
|
|
'@context': 'https://schema.org/',
|
|
'@type': 'TelevisionChannel',
|
|
image: this.logoUrl,
|
|
identifier: this.id,
|
|
name: this.name,
|
|
alternateName: this.altNames.map((name: string) => ({ '@value': name })),
|
|
genre: this.categories.map((category: Category) => ({ '@value': category.name })),
|
|
sameAs: this.websiteUrl
|
|
}
|
|
}
|
|
}
|
|
|
|
function normalize(value: string) {
|
|
value = value.includes(' ') ? `"${value}"` : value
|
|
|
|
return encodeURIComponent(value)
|
|
}
|