Update scripts

This commit is contained in:
freearhey
2025-04-16 20:54:55 +03:00
parent d39af32f18
commit d42b102cdf
39 changed files with 1256 additions and 508 deletions

View File

@@ -1,17 +0,0 @@
type BlockedProps = {
channel: string
reason: string
ref: string
}
export class Blocked {
channelId: string
reason: string
ref: string
constructor(data: BlockedProps) {
this.channelId = data.channel
this.reason = data.reason
this.ref = data.ref
}
}

View File

@@ -0,0 +1,15 @@
import type { BlocklistRecordData } from '../types/blocklistRecord'
export class BlocklistRecord {
channelId: string
reason: string
ref: string
constructor(data?: BlocklistRecordData) {
if (!data) return
this.channelId = data.channel
this.reason = data.reason
this.ref = data.ref
}
}

View File

@@ -1,7 +1,4 @@
type CategoryData = {
id: string
name: string
}
import type { CategoryData, CategorySerializedData } from '../types/category'
export class Category {
id: string
@@ -11,4 +8,11 @@ export class Category {
this.id = data.id
this.name = data.name
}
serialize(): CategorySerializedData {
return {
id: this.id,
name: this.name
}
}
}

View File

@@ -1,23 +1,6 @@
import { Collection, Dictionary } from '@freearhey/core'
import { Category, Country, Subdivision } from './index'
type ChannelData = {
id: string
name: string
alt_names: string[]
network: string
owners: Collection
country: string
subdivision: string
city: string
categories: Collection
is_nsfw: boolean
launched: string
closed: string
replaced_by: string
website: string
logo: string
}
import { Category, Country, Feed, Guide, Stream, Subdivision } from './index'
import type { ChannelData, ChannelSearchableData, ChannelSerializedData } from '../types/channel'
export class Channel {
id: string
@@ -31,15 +14,18 @@ export class Channel {
subdivision?: Subdivision
cityName?: string
categoryIds: Collection
categories?: Collection
categories: Collection = new Collection()
isNSFW: boolean
launched?: string
closed?: string
replacedBy?: string
website?: string
logo: string
feeds?: Collection
constructor(data?: ChannelData) {
if (!data) return
constructor(data: ChannelData) {
this.id = data.id
this.name = data.name
this.altNames = new Collection(data.alt_names)
@@ -57,28 +43,34 @@ export class Channel {
this.logo = data.logo
}
withSubdivision(subdivisionsGroupedByCode: Dictionary): this {
withSubdivision(subdivisionsKeyByCode: Dictionary): this {
if (!this.subdivisionCode) return this
this.subdivision = subdivisionsGroupedByCode.get(this.subdivisionCode)
this.subdivision = subdivisionsKeyByCode.get(this.subdivisionCode)
return this
}
withCountry(countriesGroupedByCode: Dictionary): this {
this.country = countriesGroupedByCode.get(this.countryCode)
withCountry(countriesKeyByCode: Dictionary): this {
this.country = countriesKeyByCode.get(this.countryCode)
return this
}
withCategories(groupedCategories: Dictionary): this {
withCategories(categoriesKeyById: Dictionary): this {
this.categories = this.categoryIds
.map((id: string) => groupedCategories.get(id))
.map((id: string) => categoriesKeyById.get(id))
.filter(Boolean)
return this
}
withFeeds(feedsGroupedByChannelId: Dictionary): this {
this.feeds = new Collection(feedsGroupedByChannelId.get(this.id))
return this
}
getCountry(): Country | undefined {
return this.country
}
@@ -102,7 +94,106 @@ export class Channel {
)
}
getFeeds(): Collection {
if (!this.feeds) return new Collection()
return this.feeds
}
getGuides(): Collection {
let guides = new Collection()
this.getFeeds().forEach((feed: Feed) => {
guides = guides.concat(feed.getGuides())
})
return guides
}
getGuideNames(): Collection {
return this.getGuides()
.map((guide: Guide) => guide.siteName)
.uniq()
}
getStreams(): Collection {
let streams = new Collection()
this.getFeeds().forEach((feed: Feed) => {
streams = streams.concat(feed.getStreams())
})
return streams
}
getStreamNames(): Collection {
return this.getStreams()
.map((stream: Stream) => stream.getName())
.uniq()
}
getFeedFullNames(): Collection {
return this.getFeeds()
.map((feed: Feed) => feed.getFullName())
.uniq()
}
isSFW(): boolean {
return this.isNSFW === false
}
getSearchable(): ChannelSearchableData {
return {
id: this.id,
name: this.name,
altNames: this.altNames.all(),
guideNames: this.getGuideNames().all(),
streamNames: this.getStreamNames().all(),
feedFullNames: this.getFeedFullNames().all()
}
}
serialize(): ChannelSerializedData {
return {
id: this.id,
name: this.name,
altNames: this.altNames.all(),
network: this.network,
owners: this.owners.all(),
countryCode: this.countryCode,
country: this.country ? this.country.serialize() : undefined,
subdivisionCode: this.subdivisionCode,
subdivision: this.subdivision ? this.subdivision.serialize() : undefined,
cityName: this.cityName,
categoryIds: this.categoryIds.all(),
categories: this.categories.map((category: Category) => category.serialize()).all(),
isNSFW: this.isNSFW,
launched: this.launched,
closed: this.closed,
replacedBy: this.replacedBy,
website: this.website,
logo: this.logo
}
}
deserialize(data: ChannelSerializedData): this {
this.id = data.id
this.name = data.name
this.altNames = new Collection(data.altNames)
this.network = data.network
this.owners = new Collection(data.owners)
this.countryCode = data.countryCode
this.country = data.country ? new Country().deserialize(data.country) : undefined
this.subdivisionCode = data.subdivisionCode
this.cityName = data.cityName
this.categoryIds = new Collection(data.categoryIds)
this.isNSFW = data.isNSFW
this.launched = data.launched
this.closed = data.closed
this.replacedBy = data.replacedBy
this.website = data.website
this.logo = data.logo
return this
}
}

View File

@@ -1,12 +1,8 @@
import { Collection, Dictionary } from '@freearhey/core'
import { Region, Language } from '.'
type CountryData = {
code: string
name: string
lang: string
flag: string
}
import { Region, Language, Subdivision } from '.'
import type { CountryData, CountrySerializedData } from '../types/country'
import { SubdivisionSerializedData } from '../types/subdivision'
import { RegionSerializedData } from '../types/region'
export class Country {
code: string
@@ -17,7 +13,9 @@ export class Country {
subdivisions?: Collection
regions?: Collection
constructor(data: CountryData) {
constructor(data?: CountryData) {
if (!data) return
this.code = data.code
this.name = data.name
this.flag = data.flag
@@ -38,8 +36,8 @@ export class Country {
return this
}
withLanguage(languagesGroupedByCode: Dictionary): this {
this.language = languagesGroupedByCode.get(this.languageCode)
withLanguage(languagesKeyByCode: Dictionary): this {
this.language = languagesKeyByCode.get(this.languageCode)
return this
}
@@ -55,4 +53,34 @@ export class Country {
getSubdivisions(): Collection {
return this.subdivisions || new Collection()
}
serialize(): CountrySerializedData {
return {
code: this.code,
name: this.name,
flag: this.flag,
languageCode: this.languageCode,
language: this.language ? this.language.serialize() : null,
subdivisions: this.subdivisions
? this.subdivisions.map((subdivision: Subdivision) => subdivision.serialize()).all()
: [],
regions: this.regions ? this.regions.map((region: Region) => region.serialize()).all() : []
}
}
deserialize(data: CountrySerializedData): this {
this.code = data.code
this.name = data.name
this.flag = data.flag
this.languageCode = data.languageCode
this.language = data.language ? new Language().deserialize(data.language) : undefined
this.subdivisions = new Collection(data.subdivisions).map((data: SubdivisionSerializedData) =>
new Subdivision().deserialize(data)
)
this.regions = new Collection(data.regions).map((data: RegionSerializedData) =>
new Region().deserialize(data)
)
return this
}
}

View File

@@ -1,16 +1,6 @@
import { Collection, Dictionary } from '@freearhey/core'
import { Country, Language, Region, Channel, Subdivision } from './index'
type FeedData = {
channel: string
id: string
name: string
is_main: boolean
broadcast_area: Collection
languages: Collection
timezones: Collection
video_format: string
}
import type { FeedData } from '../types/feed'
export class Feed {
channelId: string
@@ -30,6 +20,8 @@ export class Feed {
timezoneIds: Collection
timezones?: Collection
videoFormat: string
guides?: Collection
streams?: Collection
constructor(data: FeedData) {
this.channelId = data.channel
@@ -61,40 +53,58 @@ export class Feed {
})
}
withChannel(channelsGroupedById: Dictionary): this {
this.channel = channelsGroupedById.get(this.channelId)
withChannel(channelsKeyById: Dictionary): this {
this.channel = channelsKeyById.get(this.channelId)
return this
}
withLanguages(languagesGroupedByCode: Dictionary): this {
withStreams(streamsGroupedById: Dictionary): this {
this.streams = new Collection(streamsGroupedById.get(`${this.channelId}@${this.id}`))
if (this.isMain) {
this.streams = this.streams.concat(new Collection(streamsGroupedById.get(this.channelId)))
}
return this
}
withGuides(guidesGroupedByStreamId: Dictionary): this {
this.guides = new Collection(guidesGroupedByStreamId.get(`${this.channelId}@${this.id}`))
if (this.isMain) {
this.guides = this.guides.concat(new Collection(guidesGroupedByStreamId.get(this.channelId)))
}
return this
}
withLanguages(languagesKeyByCode: Dictionary): this {
this.languages = this.languageCodes
.map((code: string) => languagesGroupedByCode.get(code))
.map((code: string) => languagesKeyByCode.get(code))
.filter(Boolean)
return this
}
withTimezones(timezonesGroupedById: Dictionary): this {
this.timezones = this.timezoneIds
.map((id: string) => timezonesGroupedById.get(id))
.filter(Boolean)
withTimezones(timezonesKeyById: Dictionary): this {
this.timezones = this.timezoneIds.map((id: string) => timezonesKeyById.get(id)).filter(Boolean)
return this
}
withBroadcastSubdivisions(subdivisionsGroupedByCode: Dictionary): this {
withBroadcastSubdivisions(subdivisionsKeyByCode: Dictionary): this {
this.broadcastSubdivisions = this.broadcastSubdivisionCodes.map((code: string) =>
subdivisionsGroupedByCode.get(code)
subdivisionsKeyByCode.get(code)
)
return this
}
withBroadcastCountries(
countriesGroupedByCode: Dictionary,
regionsGroupedByCode: Dictionary,
subdivisionsGroupedByCode: Dictionary
countriesKeyByCode: Dictionary,
regionsKeyByCode: Dictionary,
subdivisionsKeyByCode: Dictionary
): this {
let broadcastCountries = new Collection()
@@ -104,22 +114,22 @@ export class Feed {
}
this.broadcastCountryCodes.forEach((code: string) => {
broadcastCountries.add(countriesGroupedByCode.get(code))
broadcastCountries.add(countriesKeyByCode.get(code))
})
this.broadcastRegionCodes.forEach((code: string) => {
const region: Region = regionsGroupedByCode.get(code)
const region: Region = regionsKeyByCode.get(code)
if (region) {
region.countryCodes.forEach((countryCode: string) => {
broadcastCountries.add(countriesGroupedByCode.get(countryCode))
broadcastCountries.add(countriesKeyByCode.get(countryCode))
})
}
})
this.broadcastSubdivisionCodes.forEach((code: string) => {
const subdivision: Subdivision = subdivisionsGroupedByCode.get(code)
const subdivision: Subdivision = subdivisionsKeyByCode.get(code)
if (subdivision) {
broadcastCountries.add(countriesGroupedByCode.get(subdivision.countryCode))
broadcastCountries.add(countriesKeyByCode.get(subdivision.countryCode))
}
})
@@ -197,4 +207,22 @@ export class Feed {
return this.getBroadcastRegions().includes((_region: Region) => _region.code === region.code)
}
getGuides(): Collection {
if (!this.guides) return new Collection()
return this.guides
}
getStreams(): Collection {
if (!this.streams) return new Collection()
return this.streams
}
getFullName(): string {
if (!this.channel) return ''
return `${this.channel.name} ${this.name}`
}
}

54
scripts/models/guide.ts Normal file
View File

@@ -0,0 +1,54 @@
import type { GuideData, GuideSerializedData } from '../types/guide'
export class Guide {
channelId?: string
feedId?: string
siteDomain: string
siteId: string
siteName: string
languageCode: string
constructor(data?: GuideData) {
if (!data) return
this.channelId = data.channel
this.feedId = data.feed
this.siteDomain = data.site
this.siteId = data.site_id
this.siteName = data.site_name
this.languageCode = data.lang
}
getUUID(): string {
return this.getStreamId() + this.siteId
}
getStreamId(): string | undefined {
if (!this.channelId) return undefined
if (!this.feedId) return this.channelId
return `${this.channelId}@${this.feedId}`
}
serialize(): GuideSerializedData {
return {
channelId: this.channelId,
feedId: this.feedId,
siteDomain: this.siteDomain,
siteId: this.siteId,
siteName: this.siteName,
languageCode: this.languageCode
}
}
deserialize(data: GuideSerializedData): this {
this.channelId = data.channelId
this.feedId = data.feedId
this.siteDomain = data.siteDomain
this.siteId = data.siteId
this.siteName = data.siteName
this.languageCode = data.languageCode
return this
}
}

View File

@@ -1,13 +1,14 @@
export * from './issue'
export * from './playlist'
export * from './blocked'
export * from './stream'
export * from './blocklistRecord'
export * from './broadcastArea'
export * from './category'
export * from './channel'
export * from './language'
export * from './country'
export * from './region'
export * from './subdivision'
export * from './feed'
export * from './broadcastArea'
export * from './guide'
export * from './issue'
export * from './language'
export * from './playlist'
export * from './region'
export * from './stream'
export * from './subdivision'
export * from './timezone'

View File

@@ -1,14 +1,27 @@
type LanguageData = {
code: string
name: string
}
import type { LanguageData, LanguageSerializedData } from '../types/language'
export class Language {
code: string
name: string
constructor(data: LanguageData) {
constructor(data?: LanguageData) {
if (!data) return
this.code = data.code
this.name = data.name
}
serialize(): LanguageSerializedData {
return {
code: this.code,
name: this.name
}
}
deserialize(data: LanguageSerializedData): this {
this.code = data.code
this.name = data.name
return this
}
}

View File

@@ -1,27 +1,26 @@
import { Collection, Dictionary } from '@freearhey/core'
import { Subdivision } from '.'
type RegionData = {
code: string
name: string
countries: string[]
}
import { Country, Subdivision } from '.'
import type { RegionData, RegionSerializedData } from '../types/region'
import { CountrySerializedData } from '../types/country'
import { SubdivisionSerializedData } from '../types/subdivision'
export class Region {
code: string
name: string
countryCodes: Collection
countries?: Collection
subdivisions?: Collection
countries: Collection = new Collection()
subdivisions: Collection = new Collection()
constructor(data?: RegionData) {
if (!data) return
constructor(data: RegionData) {
this.code = data.code
this.name = data.name
this.countryCodes = new Collection(data.countries)
}
withCountries(countriesGroupedByCode: Dictionary): this {
this.countries = this.countryCodes.map((code: string) => countriesGroupedByCode.get(code))
withCountries(countriesKeyByCode: Dictionary): this {
this.countries = this.countryCodes.map((code: string) => countriesKeyByCode.get(code))
return this
}
@@ -35,11 +34,11 @@ export class Region {
}
getSubdivisions(): Collection {
return this.subdivisions || new Collection()
return this.subdivisions
}
getCountries(): Collection {
return this.countries || new Collection()
return this.countries
}
includesCountryCode(code: string): boolean {
@@ -49,4 +48,30 @@ export class Region {
isWorldwide(): boolean {
return this.code === 'INT'
}
serialize(): RegionSerializedData {
return {
code: this.code,
name: this.name,
countryCodes: this.countryCodes.all(),
countries: this.countries.map((country: Country) => country.serialize()).all(),
subdivisions: this.subdivisions
.map((subdivision: Subdivision) => subdivision.serialize())
.all()
}
}
deserialize(data: RegionSerializedData): this {
this.code = data.code
this.name = data.name
this.countryCodes = new Collection(data.countryCodes)
this.countries = new Collection(data.countries).map((data: CountrySerializedData) =>
new Country().deserialize(data)
)
this.subdivisions = new Collection(data.subdivisions).map((data: SubdivisionSerializedData) =>
new Subdivision().deserialize(data)
)
return this
}
}

View File

@@ -1,26 +1,45 @@
import { URL, Collection, Dictionary } from '@freearhey/core'
import { Feed, Channel, Category, Region, Subdivision, Country, Language } from './index'
import { URL, Collection, Dictionary } from '@freearhey/core'
import type { StreamData } from '../types/stream'
import parser from 'iptv-playlist-parser'
export class Stream {
name: string
name?: string
url: string
id?: string
groupTitle: string
channelId?: string
channel?: Channel
feedId?: string
feed?: Feed
filepath?: string
line: number
line?: number
label?: string
verticalResolution?: number
isInterlaced?: boolean
httpReferrer?: string
httpUserAgent?: string
referrer?: string
userAgent?: string
groupTitle: string = 'Undefined'
removed: boolean = false
constructor(data: parser.PlaylistItem) {
constructor(data?: StreamData) {
if (!data) return
const id = data.channel && data.feed ? [data.channel, data.feed].join('@') : data.channel
const { verticalResolution, isInterlaced } = parseQuality(data.quality)
this.id = id || undefined
this.channelId = data.channel || undefined
this.feedId = data.feed || undefined
this.name = data.name || undefined
this.url = data.url
this.referrer = data.referrer || undefined
this.userAgent = data.user_agent || undefined
this.verticalResolution = verticalResolution || undefined
this.isInterlaced = isInterlaced || undefined
this.label = data.label || undefined
}
fromPlaylistItem(data: parser.PlaylistItem): this {
if (!data.name) throw new Error('"name" property is required')
if (!data.url) throw new Error('"url" property is required')
@@ -37,15 +56,16 @@ export class Stream {
this.verticalResolution = verticalResolution || undefined
this.isInterlaced = isInterlaced || undefined
this.url = data.url
this.httpReferrer = data.http.referrer || undefined
this.httpUserAgent = data.http['user-agent'] || undefined
this.groupTitle = 'Undefined'
this.referrer = data.http.referrer || undefined
this.userAgent = data.http['user-agent'] || undefined
return this
}
withChannel(channelsGroupedById: Dictionary): this {
withChannel(channelsKeyById: Dictionary): this {
if (!this.channelId) return this
this.channel = channelsGroupedById.get(this.channelId)
this.channel = channelsKeyById.get(this.channelId)
return this
}
@@ -93,18 +113,22 @@ export class Stream {
return this
}
setHttpUserAgent(httpUserAgent: string): this {
this.httpUserAgent = httpUserAgent
setUserAgent(userAgent: string): this {
this.userAgent = userAgent
return this
}
setHttpReferrer(httpReferrer: string): this {
this.httpReferrer = httpReferrer
setReferrer(referrer: string): this {
this.referrer = referrer
return this
}
getLine(): number {
return this.line || -1
}
setFilepath(filepath: string): this {
this.filepath = filepath
@@ -133,12 +157,12 @@ export class Stream {
return this.filepath || ''
}
getHttpReferrer(): string {
return this.httpReferrer || ''
getReferrer(): string {
return this.referrer || ''
}
getHttpUserAgent(): string {
return this.httpUserAgent || ''
getUserAgent(): string {
return this.userAgent || ''
}
getQuality(): string {
@@ -198,14 +222,6 @@ export class Stream {
return Object.assign(Object.create(Object.getPrototypeOf(this)), this)
}
hasName(): boolean {
return !!this.name
}
noName(): boolean {
return !this.name
}
hasChannel() {
return !!this.channel
}
@@ -281,8 +297,12 @@ export class Stream {
return this?.channel?.logo || ''
}
getName(): string {
return this.name || ''
}
getTitle(): string {
let title = `${this.name}`
let title = `${this.getName()}`
if (this.getQuality()) {
title += ` (${this.getQuality()})`
@@ -303,30 +323,13 @@ export class Stream {
return this.id || ''
}
data() {
return {
id: this.id,
channel: this.channel,
feed: this.feed,
filepath: this.filepath,
label: this.label,
name: this.name,
verticalResolution: this.verticalResolution,
isInterlaced: this.isInterlaced,
url: this.url,
httpReferrer: this.httpReferrer,
httpUserAgent: this.httpUserAgent,
line: this.line
}
}
toJSON() {
return {
channel: this.channelId || null,
feed: this.feedId || null,
url: this.url,
referrer: this.httpReferrer || null,
user_agent: this.httpUserAgent || null,
referrer: this.referrer || null,
user_agent: this.userAgent || null,
quality: this.getQuality() || null
}
}
@@ -338,22 +341,22 @@ export class Stream {
output += ` tvg-logo="${this.getLogo()}" group-title="${this.groupTitle}"`
}
if (this.httpReferrer) {
output += ` http-referrer="${this.httpReferrer}"`
if (this.referrer) {
output += ` http-referrer="${this.referrer}"`
}
if (this.httpUserAgent) {
output += ` http-user-agent="${this.httpUserAgent}"`
if (this.userAgent) {
output += ` http-user-agent="${this.userAgent}"`
}
output += `,${this.getTitle()}`
if (this.httpReferrer) {
output += `\n#EXTVLCOPT:http-referrer=${this.httpReferrer}`
if (this.referrer) {
output += `\n#EXTVLCOPT:http-referrer=${this.referrer}`
}
if (this.httpUserAgent) {
output += `\n#EXTVLCOPT:http-user-agent=${this.httpUserAgent}`
if (this.userAgent) {
output += `\n#EXTVLCOPT:http-user-agent=${this.userAgent}`
}
output += `\n${this.url}`
@@ -379,7 +382,11 @@ function escapeRegExp(text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
}
function parseQuality(quality: string): { verticalResolution: number; isInterlaced: boolean } {
function parseQuality(quality: string | null): {
verticalResolution: number | null
isInterlaced: boolean | null
} {
if (!quality) return { verticalResolution: null, isInterlaced: null }
let [, verticalResolutionString] = quality.match(/^(\d+)/) || [null, undefined]
const isInterlaced = /i$/i.test(quality)
let verticalResolution = 0

View File

@@ -1,26 +1,41 @@
import { SubdivisionData, SubdivisionSerializedData } from '../types/subdivision'
import { Dictionary } from '@freearhey/core'
import { Country } from '.'
type SubdivisionData = {
code: string
name: string
country: string
}
export class Subdivision {
code: string
name: string
countryCode: string
country?: Country
constructor(data: SubdivisionData) {
constructor(data?: SubdivisionData) {
if (!data) return
this.code = data.code
this.name = data.name
this.countryCode = data.country
}
withCountry(countriesGroupedByCode: Dictionary): this {
this.country = countriesGroupedByCode.get(this.countryCode)
withCountry(countriesKeyByCode: Dictionary): this {
this.country = countriesKeyByCode.get(this.countryCode)
return this
}
serialize(): SubdivisionSerializedData {
return {
code: this.code,
name: this.name,
countryCode: this.code,
country: this.country ? this.country.serialize() : undefined
}
}
deserialize(data: SubdivisionSerializedData): this {
this.code = data.code
this.name = data.name
this.countryCode = data.countryCode
this.country = data.country ? new Country().deserialize(data.country) : undefined
return this
}

View File

@@ -18,8 +18,8 @@ export class Timezone {
this.countryCodes = new Collection(data.countries)
}
withCountries(countriesGroupedByCode: Dictionary): this {
this.countries = this.countryCodes.map((code: string) => countriesGroupedByCode.get(code))
withCountries(countriesKeyByCode: Dictionary): this {
this.countries = this.countryCodes.map((code: string) => countriesKeyByCode.get(code))
return this
}