Update scripts

This commit is contained in:
freearhey 2025-04-29 00:18:35 +03:00
parent 37b4197fb2
commit 6244ba7adb
54 changed files with 2020 additions and 1145 deletions

View file

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

View file

@ -0,0 +1,42 @@
import { BlocklistRecordData } from '../types/blocklistRecord'
import { Dictionary } from '@freearhey/core'
import { Model } from './model'
import Joi from 'joi'
export class BlocklistRecord extends Model {
channelId: string
reason: string
ref: string
constructor(data: BlocklistRecordData) {
super()
this.channelId = data.channel
this.reason = data.reason
this.ref = data.ref
}
hasValidChannelId(channelsKeyById: Dictionary): boolean {
return channelsKeyById.has(this.channelId)
}
data(): BlocklistRecordData {
return {
channel: this.channelId,
reason: this.reason,
ref: this.ref
}
}
getSchema() {
return Joi.object({
channel: Joi.string()
.regex(/^[A-Za-z0-9]+\.[a-z]{2}$/)
.required(),
reason: Joi.string()
.valid(...['dmca', 'nsfw'])
.required(),
ref: Joi.string().uri().required()
})
}
}

View file

@ -0,0 +1,33 @@
import { CategoryData } from '../types/category'
import { Model } from './model'
import Joi from 'joi'
export class Category extends Model {
id: string
name: string
constructor(data: CategoryData) {
super()
this.id = data.id
this.name = data.name
}
data(): CategoryData {
return {
id: this.id,
name: this.name
}
}
getSchema() {
return Joi.object({
id: Joi.string()
.regex(/^[a-z]+$/)
.required(),
name: Joi.string()
.regex(/^[A-Z]+$/i)
.required()
})
}
}

View file

@ -1,91 +1,237 @@
type ChannelProps = {
id: string
name?: string
alt_names?: string[]
network?: string
owners?: string[]
country?: string
subdivision?: string
city?: string
broadcast_area?: string[]
languages?: string[]
categories?: string[]
is_nsfw?: boolean
launched?: string
closed?: string
replaced_by?: string
website?: string
logo?: string
}
import { Dictionary, Collection } from '@freearhey/core'
import { ChannelData } from '../types/channel'
import { createChannelId } from '../utils'
import JoiDate from '@joi/date'
import { Model } from './model'
import { Feed } from './feed'
import BaseJoi from 'joi'
import { IssueData } from '../core'
export class Channel {
id: string
name?: string
alt_names?: string[]
network?: string
owners?: string[]
country?: string
subdivision?: string
city?: string
broadcast_area?: string[]
languages?: string[]
categories?: string[]
is_nsfw?: boolean
launched?: string
closed?: string
replaced_by?: string
website?: string
logo?: string
const Joi = BaseJoi.extend(JoiDate)
constructor({
id,
name,
alt_names,
network,
owners,
country,
subdivision,
city,
broadcast_area,
languages,
categories,
is_nsfw,
launched,
closed,
replaced_by,
website,
logo
}: ChannelProps) {
export class Channel extends Model {
id: string
name: string
altNames?: Collection
networkName?: string
ownerNames?: Collection
countryCode: string
subdivisionCode?: string
cityName?: string
categoryIds?: Collection
isNSFW: boolean
launchedDateString?: string
closedDateString?: string
replacedBy?: string
websiteUrl?: string
logoUrl: string
feeds?: Collection
constructor(data: ChannelData) {
super()
this.id = data.id
this.name = data.name
this.altNames = data.alt_names ? new Collection(data.alt_names) : undefined
this.networkName = data.network
this.ownerNames = data.owners ? new Collection(data.owners) : undefined
this.countryCode = data.country
this.subdivisionCode = data.subdivision
this.cityName = data.city
this.categoryIds = data.categories ? new Collection(data.categories) : undefined
this.isNSFW = data.is_nsfw
this.launchedDateString = data.launched
this.closedDateString = data.closed
this.replacedBy = data.replaced_by
this.websiteUrl = data.website
this.logoUrl = data.logo
}
setId(id: string): this {
this.id = id
this.name = name
this.alt_names = alt_names
this.network = network
this.owners = owners
this.country = country
this.subdivision = subdivision
this.city = city
this.broadcast_area = broadcast_area
this.languages = languages
this.categories = categories
this.is_nsfw = is_nsfw
this.launched = launched
this.closed = closed
this.replaced_by = replaced_by
this.website = website
this.logo = logo
return this
}
data() {
const { ...object } = this
update(issueData: IssueData): this {
const data = {
channel_name: issueData.getString('channel_name'),
alt_names: issueData.getArray('alt_names'),
network: issueData.getString('network'),
owners: issueData.getArray('owners'),
country: issueData.getString('country'),
subdivision: issueData.getString('subdivision'),
city: issueData.getString('city'),
categories: issueData.getArray('categories'),
is_nsfw: issueData.getBoolean('is_nsfw'),
launched: issueData.getString('launched'),
closed: issueData.getString('closed'),
replaced_by: issueData.getString('replaced_by'),
website: issueData.getString('website'),
logo: issueData.getString('logo')
}
return object
if (data.channel_name !== undefined) this.name = data.channel_name
if (data.alt_names !== undefined) this.altNames = new Collection(data.alt_names)
if (data.network !== undefined) this.networkName = data.network
if (data.owners !== undefined) this.ownerNames = new Collection(data.owners)
if (data.country !== undefined) this.countryCode = data.country
if (data.subdivision !== undefined) this.subdivisionCode = data.subdivision
if (data.city !== undefined) this.cityName = data.city
if (data.categories !== undefined) this.categoryIds = new Collection(data.categories)
if (data.is_nsfw !== undefined) this.isNSFW = data.is_nsfw
if (data.launched !== undefined) this.launchedDateString = data.launched
if (data.closed !== undefined) this.closedDateString = data.closed
if (data.replaced_by !== undefined) this.replacedBy = data.replaced_by
if (data.website !== undefined) this.websiteUrl = data.website
if (data.logo !== undefined) this.logoUrl = data.logo
return this
}
merge(channel: Channel) {
const data: { [key: string]: string | string[] | boolean | undefined } = channel.data()
for (const prop in data) {
if (data[prop] === undefined) continue
this[prop] = data[prop]
withFeeds(feedsGroupedByChannelId: Dictionary): this {
this.feeds = new Collection(feedsGroupedByChannelId.get(this.id))
return this
}
getFeeds(): Collection {
if (!this.feeds) return new Collection()
return this.feeds
}
hasValidId(): boolean {
const expectedId = createChannelId(this.name, this.countryCode)
return expectedId === this.id
}
hasMainFeed(): boolean {
const feeds = this.getFeeds()
if (feeds.isEmpty()) return false
const mainFeed = feeds.find((feed: Feed) => feed.isMain)
return !!mainFeed
}
hasMoreThanOneMainFeed(): boolean {
const mainFeeds = this.getFeeds().filter((feed: Feed) => feed.isMain)
return mainFeeds.count() > 1
}
hasValidReplacedBy(channelsKeyById: Dictionary, feedsKeyByStreamId: Dictionary): boolean {
if (!this.replacedBy) return true
const [channelId, feedId] = this.replacedBy.split('@')
if (channelsKeyById.missing(channelId)) return false
if (feedId && feedsKeyByStreamId.missing(this.replacedBy)) return false
return true
}
hasValidCountryCode(countriesKeyByCode: Dictionary): boolean {
return countriesKeyByCode.has(this.countryCode)
}
hasValidSubdivisionCode(subdivisionsKeyByCode: Dictionary): boolean {
return !this.subdivisionCode || subdivisionsKeyByCode.has(this.subdivisionCode)
}
hasValidCategoryIds(categoriesKeyById: Dictionary): boolean {
const hasInvalid = this.getCategoryIds().find((id: string) => categoriesKeyById.missing(id))
return !hasInvalid
}
getCategoryIds(): Collection {
if (!this.categoryIds) return new Collection()
return this.categoryIds
}
getAltNames(): Collection {
if (!this.altNames) return new Collection()
return this.altNames
}
getOwnerNames(): Collection {
if (!this.ownerNames) return new Collection()
return this.ownerNames
}
data(): ChannelData {
return {
id: this.id,
name: this.name,
alt_names: this.getAltNames().all(),
network: this.networkName,
owners: this.getOwnerNames().all(),
country: this.countryCode,
subdivision: this.subdivisionCode,
city: this.cityName,
categories: this.getCategoryIds().all(),
is_nsfw: this.isNSFW,
launched: this.launchedDateString,
closed: this.closedDateString,
replaced_by: this.replacedBy,
website: this.websiteUrl,
logo: this.logoUrl
}
}
getSchema() {
return Joi.object({
id: Joi.string()
.regex(/^[A-Za-z0-9]+\.[a-z]{2}$/)
.required(),
name: Joi.string()
.regex(/^[a-z0-9-!:&.+'/»#%°$@?|¡–\s_—]+$/i)
.regex(/^((?!\s-\s).)*$/)
.required(),
alt_names: Joi.array().items(
Joi.string()
.regex(/^[^",]+$/)
.invalid(Joi.ref('name'))
),
network: Joi.string()
.regex(/^[^",]+$/)
.allow(null),
owners: Joi.array().items(Joi.string().regex(/^[^",]+$/)),
country: Joi.string()
.regex(/^[A-Z]{2}$/)
.required(),
subdivision: Joi.string()
.regex(/^[A-Z]{2}-[A-Z0-9]{1,3}$/)
.allow(null),
city: Joi.string()
.regex(/^[^",]+$/)
.allow(null),
categories: Joi.array().items(Joi.string().regex(/^[a-z]+$/)),
is_nsfw: Joi.boolean().strict().required(),
launched: Joi.date().format('YYYY-MM-DD').raw().allow(null),
closed: Joi.date().format('YYYY-MM-DD').raw().allow(null).greater(Joi.ref('launched')),
replaced_by: Joi.string()
.regex(/^[A-Za-z0-9]+\.[a-z]{2}($|@[A-Za-z0-9]+$)/)
.allow(null),
website: Joi.string()
.regex(/,/, { invert: true })
.uri({
scheme: ['http', 'https']
})
.allow(null),
logo: Joi.string()
.regex(/,/, { invert: true })
.uri({
scheme: ['https']
})
.required()
})
}
}

54
scripts/models/country.ts Normal file
View file

@ -0,0 +1,54 @@
import { Collection, Dictionary } from '@freearhey/core'
import { CountryData } from '../types/country'
import { Model } from './model'
import Joi from 'joi'
export class Country extends Model {
code: string
name: string
flagEmoji: string
languageCodes: Collection
constructor(data: CountryData) {
super()
this.code = data.code
this.name = data.name
this.flagEmoji = data.flag
this.languageCodes = new Collection(data.languages)
}
hasValidLanguageCodes(languagesKeyByCode: Dictionary): boolean {
const hasInvalid = this.languageCodes.find((code: string) => languagesKeyByCode.missing(code))
return !hasInvalid
}
data(): CountryData {
return {
code: this.code,
name: this.name,
flag: this.flagEmoji,
languages: this.languageCodes.all()
}
}
getSchema() {
return Joi.object({
name: Joi.string()
.regex(/^[\sA-Z\u00C0-\u00FF().-]+$/i)
.required(),
code: Joi.string()
.regex(/^[A-Z]{2}$/)
.required(),
languages: Joi.array().items(
Joi.string()
.regex(/^[a-z]{3}$/)
.required()
),
flag: Joi.string()
.regex(/^[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]$/)
.required()
})
}
}

View file

@ -1,55 +1,147 @@
type FeedProps = {
channel: string
id: string
name?: string
is_main?: boolean
broadcast_area?: string[]
timezones?: string[]
languages?: string[]
video_format?: string
}
import { Collection, Dictionary } from '@freearhey/core'
import { FeedData } from '../types/feed'
import { createFeedId } from '../utils'
import { Model } from './model'
import JoiDate from '@joi/date'
import BaseJoi from 'joi'
import { IssueData } from '../core'
export class Feed {
channel: string
id: string
name?: string
is_main?: boolean
broadcast_area: string[]
timezones: string[]
languages: string[]
video_format?: string
const Joi = BaseJoi.extend(JoiDate)
constructor({
channel,
id,
name,
is_main,
broadcast_area,
timezones,
languages,
video_format
}: FeedProps) {
this.channel = channel
export class Feed extends Model {
channelId: string
id: string
name: string
isMain: boolean
broadcastAreaCodes: Collection
timezoneIds: Collection
languageCodes: Collection
videoFormat?: string
constructor(data: FeedData) {
super()
this.channelId = data.channel
this.id = data.id
this.name = data.name
this.isMain = data.is_main
this.broadcastAreaCodes = new Collection(data.broadcast_area)
this.timezoneIds = new Collection(data.timezones)
this.languageCodes = new Collection(data.languages)
this.videoFormat = data.video_format
}
setId(id: string): this {
this.id = id
this.name = name
this.is_main = is_main
this.broadcast_area = broadcast_area || []
this.timezones = timezones || []
this.languages = languages || []
this.video_format = video_format
return this
}
data() {
const { ...object } = this
update(issueData: IssueData): this {
const data = {
feed_name: issueData.getString('feed_name'),
is_main: issueData.getBoolean('is_main'),
broadcast_area: issueData.getArray('broadcast_area'),
timezones: issueData.getArray('timezones'),
languages: issueData.getArray('languages'),
video_format: issueData.getString('video_format')
}
return object
if (data.feed_name !== undefined) this.name = data.feed_name
if (data.is_main !== undefined) this.isMain = data.is_main
if (data.broadcast_area !== undefined)
this.broadcastAreaCodes = new Collection(data.broadcast_area)
if (data.timezones !== undefined) this.timezoneIds = new Collection(data.timezones)
if (data.languages !== undefined) this.languageCodes = new Collection(data.languages)
if (data.video_format !== undefined) this.videoFormat = data.video_format
return this
}
merge(feed: Feed) {
const data: { [key: string]: string | string[] | boolean | undefined } = feed.data()
for (const prop in data) {
if (data[prop] === undefined) continue
this[prop] = data[prop]
hasValidId(): boolean {
const expectedId = createFeedId(this.name)
return expectedId === this.id
}
hasValidChannelId(channelsKeyById: Dictionary): boolean {
return channelsKeyById.has(this.channelId)
}
hasValidTimezones(timezonesKeyById: Dictionary): boolean {
const hasInvalid = this.timezoneIds.find((id: string) => timezonesKeyById.missing(id))
return !hasInvalid
}
hasValidBroadcastAreaCodes(
countriesKeyByCode: Dictionary,
subdivisionsKeyByCode: Dictionary,
regionsKeyByCode: Dictionary
): boolean {
const hasInvalid = this.broadcastAreaCodes.find((areaCode: string) => {
const [type, code] = areaCode.split('/')
switch (type) {
case 'c':
return countriesKeyByCode.missing(code)
case 's':
return subdivisionsKeyByCode.missing(code)
case 'r':
return regionsKeyByCode.missing(code)
}
})
return !hasInvalid
}
getStreamId(): string {
return `${this.channelId}@${this.id}`
}
data(): FeedData {
return {
channel: this.channelId,
id: this.id,
name: this.name,
is_main: this.isMain,
broadcast_area: this.broadcastAreaCodes.all(),
timezones: this.timezoneIds.all(),
languages: this.languageCodes.all(),
video_format: this.videoFormat
}
}
getSchema() {
return Joi.object({
channel: Joi.string()
.regex(/^[A-Za-z0-9]+\.[a-z]{2}$/)
.required(),
id: Joi.string()
.regex(/^[A-Za-z0-9]+$/)
.required(),
name: Joi.string()
.regex(/^[a-z0-9-!:&.+'/»#%°$@?|¡–\s_—]+$/i)
.regex(/^((?!\s-\s).)*$/)
.required(),
is_main: Joi.boolean().strict().required(),
broadcast_area: Joi.array().items(
Joi.string()
.regex(/^(s\/[A-Z]{2}-[A-Z0-9]{1,3}|c\/[A-Z]{2}|r\/[A-Z0-9]{2,7})$/)
.required()
),
timezones: Joi.array().items(
Joi.string()
.regex(/^[a-z-_/]+$/i)
.required()
),
languages: Joi.array().items(
Joi.string()
.regex(/^[a-z]{3}$/)
.required()
),
video_format: Joi.string()
.regex(/^\d+(i|p)$/)
.allow(null)
})
}
}

View file

@ -1,3 +1,9 @@
export * from './channel'
export * from './blocked'
export * from './blocklistRecord'
export * from './feed'
export * from './region'
export * from './subdivision'
export * from './category'
export * from './country'
export * from './language'
export * from './timezone'

View file

@ -0,0 +1,31 @@
import { LanguageData } from '../types/language'
import { Model } from './model'
import Joi from 'joi'
export class Language extends Model {
code: string
name: string
constructor(data: LanguageData) {
super()
this.code = data.code
this.name = data.name
}
data(): LanguageData {
return {
code: this.code,
name: this.name
}
}
getSchema() {
return Joi.object({
code: Joi.string()
.regex(/^[a-z]{3}$/)
.required(),
name: Joi.string().required()
})
}
}

15
scripts/models/model.ts Normal file
View file

@ -0,0 +1,15 @@
export class Model {
line?: number
constructor() {}
setLine(line: number): this {
this.line = line
return this
}
getLine(): number {
return this.line || 0
}
}

48
scripts/models/region.ts Normal file
View file

@ -0,0 +1,48 @@
import { Collection, Dictionary } from '@freearhey/core'
import { RegionData } from '../types/region'
import { Model } from './model'
import Joi from 'joi'
export class Region extends Model {
code: string
name: string
countryCodes: Collection
constructor(data: RegionData) {
super()
this.code = data.code
this.name = data.name
this.countryCodes = new Collection(data.countries)
}
hasValidCountryCodes(countriesKeyByCode: Dictionary): boolean {
const hasInvalid = this.countryCodes.find((code: string) => countriesKeyByCode.missing(code))
return !hasInvalid
}
data(): RegionData {
return {
code: this.code,
name: this.name,
countries: this.countryCodes.all()
}
}
getSchema() {
return Joi.object({
name: Joi.string()
.regex(/^[\sA-Z\u00C0-\u00FF().,-]+$/i)
.required(),
code: Joi.string()
.regex(/^[A-Z]{2,7}$/)
.required(),
countries: Joi.array().items(
Joi.string()
.regex(/^[A-Z]{2}$/)
.required()
)
})
}
}

View file

@ -0,0 +1,42 @@
import { SubdivisionData } from '../types/subdivision'
import { Dictionary } from '@freearhey/core'
import { Model } from './model'
import Joi from 'joi'
export class Subdivision extends Model {
code: string
name: string
countryCode: string
constructor(data: SubdivisionData) {
super()
this.code = data.code
this.name = data.name
this.countryCode = data.country
}
hasValidCountryCode(countriesKeyByCode: Dictionary): boolean {
return countriesKeyByCode.has(this.countryCode)
}
data(): SubdivisionData {
return {
code: this.code,
name: this.name,
country: this.countryCode
}
}
getSchema() {
return Joi.object({
country: Joi.string()
.regex(/^[A-Z]{2}$/)
.required(),
name: Joi.string().required(),
code: Joi.string()
.regex(/^[A-Z]{2}-[A-Z0-9]{1,3}$/)
.required()
})
}
}

View file

@ -0,0 +1,44 @@
import { Collection, Dictionary } from '@freearhey/core'
import { TimezoneData } from '../types/timezone'
import { Model } from './model'
import Joi from 'joi'
export class Timezone extends Model {
id: string
utcOffset: string
countryCodes: Collection
constructor(data: TimezoneData) {
super()
this.id = data.id
this.utcOffset = data.utc_offset
this.countryCodes = new Collection(data.countries)
}
hasValidCountryCodes(countriesKeyByCode: Dictionary): boolean {
const hasInvalid = this.countryCodes.find((code: string) => countriesKeyByCode.missing(code))
return !hasInvalid
}
data(): TimezoneData {
return {
id: this.id,
utc_offset: this.utcOffset,
countries: this.countryCodes.all()
}
}
getSchema() {
return Joi.object({
id: Joi.string()
.regex(/^[a-z-_/]+$/i)
.required(),
utc_offset: Joi.string()
.regex(/^(\+|-)\d{2}:\d{2}$/)
.required(),
countries: Joi.array().items(Joi.string().regex(/^[A-Z]{2}$/))
})
}
}