diff --git a/scripts/commands/playlist/update.ts b/scripts/commands/playlist/update.ts index a1ed93d83..928e30584 100644 --- a/scripts/commands/playlist/update.ts +++ b/scripts/commands/playlist/update.ts @@ -7,11 +7,15 @@ import validUrl from 'valid-url' let processedIssues = new Collection() let streams: Collection let groupedChannels: Dictionary +let issues: Collection async function main() { const logger = new Logger({ disabled: true }) const loader = new IssueLoader() + logger.info('loading issues...') + issues = await loader.load() + logger.info('loading channels from api...') const dataStorage = new Storage(DATA_DIR) const channelsContent = await dataStorage.json('channels.json') @@ -51,8 +55,10 @@ async function main() { main() async function removeStreams(loader: IssueLoader) { - const issues = await loader.load({ labels: ['streams:remove', 'approved'] }) - issues.forEach((issue: Issue) => { + const requests = issues.filter( + issue => issue.labels.includes('streams:remove') && issue.labels.includes('approved') + ) + requests.forEach((issue: Issue) => { const data = issue.data if (data.missing('broken_links')) return @@ -72,8 +78,10 @@ async function removeStreams(loader: IssueLoader) { } async function editStreams(loader: IssueLoader) { - const issues = await loader.load({ labels: ['streams:edit', 'approved'] }) - issues.forEach((issue: Issue) => { + const requests = issues.filter( + issue => issue.labels.includes('streams:edit') && issue.labels.includes('approved') + ) + requests.forEach((issue: Issue) => { const data = issue.data if (data.missing('stream_url')) return @@ -106,8 +114,10 @@ async function editStreams(loader: IssueLoader) { } async function addStreams(loader: IssueLoader) { - const issues = await loader.load({ labels: ['streams:add', 'approved'] }) - issues.forEach((issue: Issue) => { + const requests = issues.filter( + issue => issue.labels.includes('streams:add') && issue.labels.includes('approved') + ) + requests.forEach((issue: Issue) => { const data = issue.data if (data.missing('channel_id') || data.missing('stream_url')) return if (streams.includes((_stream: Stream) => _stream.url === data.getString('stream_url'))) return diff --git a/scripts/commands/report/create.ts b/scripts/commands/report/create.ts index 4b361e5f9..4a8f33370 100644 --- a/scripts/commands/report/create.ts +++ b/scripts/commands/report/create.ts @@ -9,31 +9,34 @@ async function main() { const storage = new Storage(DATA_DIR) - logger.info('loading channels from api...') - const channelsContent = await storage.json('channels.json') - const groupedChannels = new Collection(channelsContent) - .map(data => new Channel(data)) - .groupBy((channel: Channel) => channel.id) - - logger.info('loading blocklist from api...') - const blocklistContent = await storage.json('blocklist.json') - const groupedBlocklist = new Collection(blocklistContent) - .map(data => new Blocked(data)) - .groupBy((blocked: Blocked) => blocked.channel) + logger.info('loading issues...') + const issues = await loader.load() logger.info('loading streams...') const streamsStorage = new Storage(STREAMS_DIR) const parser = new PlaylistParser({ storage: streamsStorage }) const files = await streamsStorage.list('**/*.m3u') const streams = await parser.parse(files) - const groupedStreams = streams.groupBy((stream: Stream) => stream.url) + const streamsGroupedByUrl = streams.groupBy((stream: Stream) => stream.url) + const streamsGroupedByChannel = streams.groupBy((stream: Stream) => stream.channel) + + logger.info('loading channels from api...') + const channelsContent = await storage.json('channels.json') + const channelsGroupedById = new Collection(channelsContent) + .map(data => new Channel(data)) + .groupBy((channel: Channel) => channel.id) + + logger.info('loading blocklist from api...') + const blocklistContent = await storage.json('blocklist.json') + const blocklistGroupedByChannel = new Collection(blocklistContent) + .map(data => new Blocked(data)) + .groupBy((blocked: Blocked) => blocked.channel) - logger.info('creating report...') let report = new Collection() logger.info('checking streams:add requests...') - const addRequests = await loader.load({ labels: ['streams:add'] }) - const buffer = new Dictionary() + const addRequests = issues.filter(issue => issue.labels.includes('streams:add')) + const addRequestsBuffer = new Dictionary() addRequests.forEach((issue: Issue) => { const channelId = issue.data.getString('channel_id') || undefined const streamUrl = issue.data.getString('stream_url') @@ -42,24 +45,25 @@ async function main() { issueNumber: issue.number, type: 'streams:add', channelId, - status: undefined + streamUrl, + status: 'pending' }) if (!channelId) result.set('status', 'missing_id') else if (!streamUrl) result.set('status', 'missing_link') - else if (groupedBlocklist.has(channelId)) result.set('status', 'blocked') - else if (groupedChannels.missing(channelId)) result.set('status', 'invalid_id') - else if (groupedStreams.has(streamUrl)) result.set('status', 'fullfilled') - else if (buffer.has(streamUrl)) result.set('status', 'duplicate') + else if (blocklistGroupedByChannel.has(channelId)) result.set('status', 'blocked') + else if (channelsGroupedById.missing(channelId)) result.set('status', 'wrong_id') + else if (streamsGroupedByUrl.has(streamUrl)) result.set('status', 'on_playlist') + else if (addRequestsBuffer.has(streamUrl)) result.set('status', 'duplicate') else result.set('status', 'pending') - buffer.set(streamUrl, true) + addRequestsBuffer.set(streamUrl, true) report.add(result.data()) }) logger.info('checking streams:edit requests...') - const editRequests = await loader.load({ labels: ['streams:edit'] }) + const editRequests = issues.filter(issue => issue.labels.find(label => label === 'streams:edit')) editRequests.forEach((issue: Issue) => { const channelId = issue.data.getString('channel_id') || undefined const streamUrl = issue.data.getString('stream_url') || undefined @@ -68,37 +72,82 @@ async function main() { issueNumber: issue.number, type: 'streams:edit', channelId, - status: undefined + streamUrl, + status: 'pending' }) if (!streamUrl) result.set('status', 'missing_link') - else if (groupedStreams.missing(streamUrl)) result.set('status', 'invalid_link') - else if (channelId && groupedChannels.missing(channelId)) result.set('status', 'invalid_id') - else result.set('status', 'pending') + else if (streamsGroupedByUrl.missing(streamUrl)) result.set('status', 'invalid_link') + else if (channelId && channelsGroupedById.missing(channelId)) result.set('status', 'invalid_id') report.add(result.data()) }) logger.info('checking broken streams reports...') - const brokenStreamReports = await loader.load({ labels: ['broken stream'] }) + const brokenStreamReports = issues.filter(issue => + issue.labels.find(label => label === 'broken stream') + ) brokenStreamReports.forEach((issue: Issue) => { - const brokenLinks = issue.data.getString('broken_links') || undefined + const brokenLinks = issue.data.getArray('broken_links') || [] + + if (!brokenLinks.length) { + const result = new Dictionary({ + issueNumber: issue.number, + type: 'broken stream', + channelId: undefined, + streamUrl: undefined, + status: 'missing_link' + }) + + report.add(result.data()) + } else { + for (const streamUrl of brokenLinks) { + const result = new Dictionary({ + issueNumber: issue.number, + type: 'broken stream', + channelId: undefined, + streamUrl: undefined, + status: 'pending' + }) + + if (streamsGroupedByUrl.missing(streamUrl)) { + result.set('streamUrl', streamUrl) + result.set('status', 'wrong_link') + } + + report.add(result.data()) + } + } + }) + + logger.info('checking channel search requests...') + const channelSearchRequests = issues.filter(issue => + issue.labels.find(label => label === 'channel search') + ) + const channelSearchRequestsBuffer = new Dictionary() + channelSearchRequests.forEach((issue: Issue) => { + const channelId = issue.data.getString('channel_id') const result = new Dictionary({ issueNumber: issue.number, - type: 'broken stream', - channelId: undefined, - status: undefined + type: 'channel search', + channelId, + streamUrl: undefined, + status: 'pending' }) - if (!brokenLinks) result.set('status', 'missing_link') - else if (groupedStreams.missing(brokenLinks)) result.set('status', 'invalid_link') - else result.set('status', 'pending') + if (!channelId) result.set('status', 'missing_id') + else if (channelsGroupedById.missing(channelId)) result.set('status', 'invalid_id') + else if (channelSearchRequestsBuffer.has(channelId)) result.set('status', 'duplicate') + else if (blocklistGroupedByChannel.has(channelId)) result.set('status', 'blocked') + else if (streamsGroupedByChannel.has(channelId)) result.set('status', 'fulfilled') + + channelSearchRequestsBuffer.set(channelId, true) report.add(result.data()) }) - report = report.orderBy(item => item.issueNumber) + report = report.orderBy(item => item.issueNumber).filter(item => item.status !== 'pending') console.table(report.all()) } diff --git a/scripts/core/issueData.ts b/scripts/core/issueData.ts index 879a64b8b..ee8918b65 100644 --- a/scripts/core/issueData.ts +++ b/scripts/core/issueData.ts @@ -27,6 +27,6 @@ export class IssueData { getArray(key: string): string[] { const deleteSymbol = '~' - return this._data.get(key) === deleteSymbol ? [] : this._data.get(key).split(';') + return this._data.get(key) === deleteSymbol ? [] : this._data.get(key).split('\r\n') } } diff --git a/scripts/core/issueLoader.ts b/scripts/core/issueLoader.ts index 4656fa8c9..535e2e744 100644 --- a/scripts/core/issueLoader.ts +++ b/scripts/core/issueLoader.ts @@ -9,33 +9,14 @@ const CustomOctokit = Octokit.plugin(paginateRest, restEndpointMethods) const octokit = new CustomOctokit() export class IssueLoader { - async load({ labels }: { labels: string[] | string }) { - labels = Array.isArray(labels) ? labels.join(',') : labels + async load(props?: { labels: string | string[] }) { + let labels = '' + if (props && props.labels) { + labels = Array.isArray(props.labels) ? props.labels.join(',') : props.labels + } let issues: object[] = [] if (TESTING) { - switch (labels) { - case 'streams:add': - issues = (await import('../../tests/__data__/input/issues/streams_add.js')).default - break - case 'streams:edit': - issues = (await import('../../tests/__data__/input/issues/streams_edit.js')).default - break - case 'broken stream': - issues = (await import('../../tests/__data__/input/issues/broken_stream.js')).default - break - case 'streams:add,approved': - issues = (await import('../../tests/__data__/input/issues/streams_add_approved.js')) - .default - break - case 'streams:edit,approved': - issues = (await import('../../tests/__data__/input/issues/streams_edit_approved.js')) - .default - break - case 'streams:remove,approved': - issues = (await import('../../tests/__data__/input/issues/streams_remove_approved.js')) - .default - break - } + issues = (await import('../../tests/__data__/input/issues/all.js')).default } else { issues = await octokit.paginate(octokit.rest.issues.listForRepo, { owner: OWNER, diff --git a/scripts/core/issueParser.ts b/scripts/core/issueParser.ts index 921e645f6..8b3dd8a67 100644 --- a/scripts/core/issueParser.ts +++ b/scripts/core/issueParser.ts @@ -25,11 +25,11 @@ const FIELDS = new Dictionary({ export class IssueParser { parse(issue: { number: number; body: string; labels: { name: string }[] }): Issue { - const fields = issue.body.split('###') + const fields = typeof issue.body === 'string' ? issue.body.split('###') : [] const data = new Dictionary() fields.forEach((field: string) => { - const parsed = field.split(/\r?\n/).filter(Boolean) + const parsed = typeof field === 'string' ? field.split(/\r?\n/).filter(Boolean) : [] let _label = parsed.shift() _label = _label ? _label.trim() : '' let _value = parsed.join('\r\n')