Skip to content

Commit 5dd131e

Browse files
committed
Update scripts
1 parent 37664b4 commit 5dd131e

File tree

15 files changed

+264
-194
lines changed

15 files changed

+264
-194
lines changed

scripts/commands/api/generate.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import { Logger, Storage, Collection } from '@freearhey/core'
22
import { ChannelsParser } from '../../core'
33
import path from 'path'
44
import { SITES_DIR, API_DIR } from '../../constants'
5-
import { Channel } from 'epg-grabber'
5+
import epgGrabber from 'epg-grabber'
66

77
type OutputItem = {
88
channel: string | null
9+
feed: string | null
910
site: string
1011
site_id: string
1112
site_name: string
@@ -31,9 +32,13 @@ async function main() {
3132

3233
logger.info(` found ${parsedChannels.count()} channel(s)`)
3334

34-
const output = parsedChannels.map((channel: Channel): OutputItem => {
35+
const output = parsedChannels.map((channel: epgGrabber.Channel): OutputItem => {
36+
const xmltv_id = channel.xmltv_id || ''
37+
const [channelId, feedId] = xmltv_id.split('@')
38+
3539
return {
36-
channel: channel.xmltv_id || null,
40+
channel: channelId || null,
41+
feed: feedId || null,
3742
site: channel.site || '',
3843
site_id: channel.site_id || '',
3944
site_name: channel.name,

scripts/commands/api/load.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ async function main() {
77

88
const requests = [
99
client.download('channels.json'),
10+
client.download('feeds.json'),
1011
client.download('countries.json'),
1112
client.download('regions.json'),
1213
client.download('subdivisions.json')

scripts/commands/channels/.gitignore

Lines changed: 0 additions & 1 deletion
This file was deleted.

scripts/commands/channels/edit.ts

Lines changed: 127 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
import { Storage, Collection, Logger, Dictionary } from '@freearhey/core'
2+
import { select, input } from '@inquirer/prompts'
3+
import { ChannelsParser, XML } from '../../core'
4+
import { Channel, Feed } from '../../models'
15
import { DATA_DIR } from '../../constants'
2-
import { Storage, Collection, Logger } from '@freearhey/core'
3-
import { ChannelsParser, XML, ApiChannel } from '../../core'
4-
import { Channel } from 'epg-grabber'
56
import nodeCleanup from 'node-cleanup'
6-
import { program } from 'commander'
7-
import inquirer, { QuestionCollection } from 'inquirer'
8-
import Fuse from 'fuse.js'
7+
import epgGrabber from 'epg-grabber'
8+
import { Command } from 'commander'
99
import readline from 'readline'
10+
import Fuse from 'fuse.js'
11+
12+
type ChoiceValue = { type: string; value?: Feed | Channel }
13+
type Choice = { name: string; short?: string; value: ChoiceValue }
1014

1115
if (process.platform === 'win32') {
1216
readline
@@ -19,105 +23,159 @@ if (process.platform === 'win32') {
1923
})
2024
}
2125

26+
const program = new Command()
27+
2228
program.argument('<filepath>', 'Path to *.channels.xml file to edit').parse(process.argv)
2329

2430
const filepath = program.args[0]
25-
2631
const logger = new Logger()
2732
const storage = new Storage()
28-
let channels = new Collection()
33+
let parsedChannels = new Collection()
2934

30-
async function main() {
35+
main(filepath)
36+
nodeCleanup(() => {
37+
save(filepath)
38+
})
39+
40+
export default async function main(filepath: string) {
3141
if (!(await storage.exists(filepath))) {
3242
throw new Error(`File "${filepath}" does not exists`)
3343
}
3444

3545
const parser = new ChannelsParser({ storage })
36-
channels = await parser.parse(filepath)
46+
parsedChannels = await parser.parse(filepath)
3747

3848
const dataStorage = new Storage(DATA_DIR)
39-
const channelsContent = await dataStorage.json('channels.json')
40-
const searchIndex = new Fuse(channelsContent, { keys: ['name', 'alt_names'], threshold: 0.4 })
49+
const channelsData = await dataStorage.json('channels.json')
50+
const channels = new Collection(channelsData).map(data => new Channel(data))
51+
const feedsData = await dataStorage.json('feeds.json')
52+
const feeds = new Collection(feedsData).map(data => new Feed(data))
53+
const feedsGroupedByChannelId = feeds.groupBy((feed: Feed) => feed.channelId)
54+
55+
const searchIndex: Fuse<Channel> = new Fuse(channels.all(), {
56+
keys: ['name', 'alt_names'],
57+
threshold: 0.4
58+
})
4159

42-
for (const channel of channels.all()) {
60+
for (const channel of parsedChannels.all()) {
4361
if (channel.xmltv_id) continue
44-
const question: QuestionCollection = {
45-
name: 'option',
46-
message: `Select xmltv_id for "${channel.name}" (${channel.site_id}):`,
47-
type: 'list',
48-
choices: getOptions(searchIndex, channel),
49-
pageSize: 10
62+
try {
63+
channel.xmltv_id = await selectChannel(channel, searchIndex, feedsGroupedByChannelId)
64+
} catch {
65+
break
5066
}
51-
52-
await inquirer.prompt(question).then(async selected => {
53-
switch (selected.option) {
54-
case 'Type...':
55-
const input = await getInput(channel)
56-
channel.xmltv_id = input.xmltv_id
57-
break
58-
case 'Skip':
59-
channel.xmltv_id = '-'
60-
break
61-
default:
62-
const [, xmltv_id] = selected.option
63-
.replace(/ \[.*\]/, '')
64-
.split('|')
65-
.map((i: string) => i.trim())
66-
channel.xmltv_id = xmltv_id
67-
break
68-
}
69-
})
7067
}
7168

72-
channels.forEach((channel: Channel) => {
69+
parsedChannels.forEach((channel: epgGrabber.Channel) => {
7370
if (channel.xmltv_id === '-') {
7471
channel.xmltv_id = ''
7572
}
7673
})
7774
}
7875

79-
main()
76+
async function selectChannel(
77+
channel: epgGrabber.Channel,
78+
searchIndex: Fuse<Channel>,
79+
feedsGroupedByChannelId: Dictionary
80+
): Promise<string> {
81+
const similarChannels = searchIndex
82+
.search(channel.name)
83+
.map((result: { item: Channel }) => result.item)
84+
85+
const selected: ChoiceValue = await select({
86+
message: `Select channel ID for "${channel.name}" (${channel.site_id}):`,
87+
choices: getChannelChoises(new Collection(similarChannels)),
88+
pageSize: 10
89+
})
8090

81-
function save() {
82-
if (!storage.existsSync(filepath)) return
91+
switch (selected.type) {
92+
case 'skip':
93+
return '-'
94+
case 'type': {
95+
const typedChannelId = await input({ message: ' Channel ID:' })
96+
const typedFeedId = await input({ message: ' Feed ID:', default: 'SD' })
97+
return [typedChannelId, typedFeedId].join('@')
98+
}
99+
case 'channel': {
100+
const selectedChannel = selected.value
101+
if (!selectedChannel) return ''
102+
const selectedFeedId = await selectFeed(selectedChannel.id, feedsGroupedByChannelId)
103+
return [selectedChannel.id, selectedFeedId].join('@')
104+
}
105+
}
83106

84-
const xml = new XML(channels)
107+
return ''
108+
}
85109

86-
storage.saveSync(filepath, xml.toString())
110+
async function selectFeed(channelId: string, feedsGroupedByChannelId: Dictionary): Promise<string> {
111+
const channelFeeds = feedsGroupedByChannelId.get(channelId) || []
112+
if (channelFeeds.length <= 1) return ''
87113

88-
logger.info(`\nFile '${filepath}' successfully saved`)
114+
const selected: ChoiceValue = await select({
115+
message: `Select feed ID for "${channelId}":`,
116+
choices: getFeedChoises(channelFeeds),
117+
pageSize: 10
118+
})
119+
120+
switch (selected.type) {
121+
case 'type':
122+
return await input({ message: ' Feed ID:' })
123+
case 'feed':
124+
const selectedFeed = selected.value
125+
if (!selectedFeed) return ''
126+
return selectedFeed.id
127+
}
128+
129+
return ''
89130
}
90131

91-
nodeCleanup(() => {
92-
save()
93-
})
132+
function getChannelChoises(channels: Collection): Choice[] {
133+
const choises: Choice[] = []
94134

95-
async function getInput(channel: Channel) {
96-
const name = channel.name.trim()
97-
const input = await inquirer.prompt([
98-
{
99-
name: 'xmltv_id',
100-
message: ' xmltv_id:',
101-
type: 'input'
102-
}
103-
])
135+
channels.forEach((channel: Channel) => {
136+
const names = [channel.name, ...channel.altNames.all()].join(', ')
137+
138+
choises.push({
139+
value: {
140+
type: 'channel',
141+
value: channel
142+
},
143+
name: `${channel.id} (${names})`,
144+
short: `${channel.id}`
145+
})
146+
})
147+
148+
choises.push({ name: 'Type...', value: { type: 'type' } })
149+
choises.push({ name: 'Skip', value: { type: 'skip' } })
104150

105-
return { name, xmltv_id: input['xmltv_id'] }
151+
return choises
106152
}
107153

108-
function getOptions(index, channel: Channel) {
109-
const similar = index.search(channel.name).map(result => new ApiChannel(result.item))
154+
function getFeedChoises(feeds: Collection): Choice[] {
155+
const choises: Choice[] = []
110156

111-
const variants = new Collection()
112-
similar.forEach((_channel: ApiChannel) => {
113-
const altNames = _channel.altNames.notEmpty() ? ` (${_channel.altNames.join(',')})` : ''
114-
const closed = _channel.closed ? ` [closed:${_channel.closed}]` : ''
115-
const replacedBy = _channel.replacedBy ? `[replaced_by:${_channel.replacedBy}]` : ''
157+
feeds.forEach((feed: Feed) => {
158+
let name = `${feed.id} (${feed.name})`
159+
if (feed.isMain) name += ' [main]'
116160

117-
variants.add(`${_channel.name}${altNames} | ${_channel.id}${closed}${replacedBy}`)
161+
choises.push({
162+
value: {
163+
type: 'feed',
164+
value: feed
165+
},
166+
name,
167+
short: feed.id
168+
})
118169
})
119-
variants.add('Type...')
120-
variants.add('Skip')
121170

122-
return variants.all()
171+
choises.push({ name: 'Type...', value: { type: 'type' } })
172+
173+
return choises
174+
}
175+
176+
function save(filepath: string) {
177+
if (!storage.existsSync(filepath)) return
178+
const xml = new XML(parsedChannels)
179+
storage.saveSync(filepath, xml.toString())
180+
logger.info(`\nFile '${filepath}' successfully saved`)
123181
}

scripts/commands/channels/validate.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { Storage, Collection, Dictionary, File } from '@freearhey/core'
2-
import { ChannelsParser, ApiChannel } from '../../core'
2+
import { ChannelsParser } from '../../core'
3+
import { Channel } from '../../models'
34
import { program } from 'commander'
45
import chalk from 'chalk'
56
import langs from 'langs'
67
import { DATA_DIR } from '../../constants'
7-
import { Channel } from 'epg-grabber'
8+
import epgGrabber from 'epg-grabber'
89

910
program.argument('[filepath]', 'Path to *.channels.xml files to validate').parse(process.argv)
1011

@@ -21,8 +22,9 @@ async function main() {
2122
const parser = new ChannelsParser({ storage: new Storage() })
2223

2324
const dataStorage = new Storage(DATA_DIR)
24-
const channelsContent = await dataStorage.json('channels.json')
25-
const channels = new Collection(channelsContent).map(data => new ApiChannel(data))
25+
const channelsData = await dataStorage.json('channels.json')
26+
const channels = new Collection(channelsData).map(data => new Channel(data))
27+
const channelsGroupedById = channels.groupBy((channel: Channel) => channel.id)
2628

2729
let totalFiles = 0
2830
let totalErrors = 0
@@ -37,7 +39,7 @@ async function main() {
3739

3840
const bufferBySiteId = new Dictionary()
3941
const errors: ValidationError[] = []
40-
parsedChannels.forEach((channel: Channel) => {
42+
parsedChannels.forEach((channel: epgGrabber.Channel) => {
4143
const bufferId: string = channel.site_id
4244
if (bufferBySiteId.missing(bufferId)) {
4345
bufferBySiteId.set(bufferId, true)
@@ -52,10 +54,8 @@ async function main() {
5254
}
5355

5456
if (!channel.xmltv_id) return
55-
56-
const foundChannel = channels.first(
57-
(_channel: ApiChannel) => _channel.id === channel.xmltv_id
58-
)
57+
const [channelId] = channel.xmltv_id.split('@')
58+
const foundChannel = channelsGroupedById.get(channelId)
5959
if (!foundChannel) {
6060
errors.push({ type: 'wrong_xmltv_id', ...channel })
6161
totalErrors++

scripts/commands/sites/init.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Logger, Storage } from '@freearhey/core'
2-
import { program } from 'commander'
32
import { SITES_DIR } from '../../constants'
4-
import fs from 'fs-extra'
53
import { pathToFileURL } from 'node:url'
4+
import { program } from 'commander'
5+
import fs from 'fs-extra'
66

77
program.argument('<site>', 'Domain name of the site').parse(process.argv)
88

scripts/commands/sites/update.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { Channel } from 'epg-grabber'
2-
import { Logger, Storage, Collection } from '@freearhey/core'
31
import { IssueLoader, HTMLTable, ChannelsParser } from '../../core'
4-
import { Issue, Site } from '../../models'
2+
import { Logger, Storage, Collection } from '@freearhey/core'
53
import { SITES_DIR, ROOT_DIR } from '../../constants'
4+
import { Issue, Site } from '../../models'
5+
import { Channel } from 'epg-grabber'
66

77
async function main() {
88
const logger = new Logger({ disabled: true })
@@ -15,11 +15,17 @@ async function main() {
1515
const folders = await sitesStorage.list('*/')
1616

1717
logger.info('loading issues...')
18-
const issues = await loadIssues(loader)
18+
const issues = await loader.load()
1919

2020
logger.info('putting the data together...')
21+
const brokenGuideReports = issues.filter(issue =>
22+
issue.labels.find((label: string) => label === 'broken guide')
23+
)
2124
for (const domain of folders) {
22-
const filteredIssues = issues.filter((issue: Issue) => domain === issue.data.get('site'))
25+
const filteredIssues = brokenGuideReports.filter(
26+
(issue: Issue) => domain === issue.data.get('site')
27+
)
28+
2329
const site = new Site({
2430
domain,
2531
issues: filteredIssues
@@ -62,10 +68,3 @@ async function main() {
6268
}
6369

6470
main()
65-
66-
async function loadIssues(loader: IssueLoader) {
67-
const issuesWithStatusWarning = await loader.load({ labels: ['broken guide', 'status:warning'] })
68-
const issuesWithStatusDown = await loader.load({ labels: ['broken guide', 'status:down'] })
69-
70-
return issuesWithStatusWarning.concat(issuesWithStatusDown)
71-
}

0 commit comments

Comments
 (0)