Skip to content

Commit 52bba4c

Browse files
committed
util refactor
1 parent 76b60f7 commit 52bba4c

File tree

6 files changed

+291
-271
lines changed

6 files changed

+291
-271
lines changed

src/process-commands/channel.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
*/
1616

1717
import { processVideo } from './video'
18-
import { validateChannelOptions, saveInfo, execFilePromise } from '../utils/validate-option'
19-
import { l, err, logSeparator, logChannelProcessingStatus, logInitialFunctionCall } from '../utils/logging'
18+
import { saveInfo, execFilePromise } from '../utils/validate-option'
19+
import { l, err, logSeparator, logInitialFunctionCall } from '../utils/logging'
20+
import { validateChannelOptions, logChannelProcessingStatus } from '../utils/channel-utils'
2021

2122
import type { ProcessingOptions, VideoInfo } from '../utils/types/process'
2223
import type { TranscriptServices } from '../utils/types/transcription'

src/process-commands/rss.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ import { downloadAudio } from '../process-steps/02-download-audio'
1919
import { runTranscription } from '../process-steps/03-run-transcription'
2020
import { selectPrompts } from '../process-steps/04-select-prompt'
2121
import { runLLM } from '../process-steps/05-run-llm'
22-
import { filterRSSItems, saveAudio, saveInfo, parser } from '../utils/validate-option'
23-
import { l, err, logSeparator, logInitialFunctionCall, logRSSProcessingStatus } from '../utils/logging'
22+
import { saveAudio, saveInfo, parser } from '../utils/validate-option'
23+
import { filterRSSItems } from '../utils/rss-utils'
24+
import { l, err, logSeparator, logInitialFunctionCall } from '../utils/logging'
25+
import { logRSSProcessingStatus } from '../utils/rss-utils'
2426
import { retryRSSFetch } from '../utils/retry'
2527

2628
import type { ProcessingOptions, RSSItem } from '../utils/types/process'

src/utils/channel-utils.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// src/utils/channel-utils.ts
2+
3+
import { l, err } from '../utils/logging'
4+
5+
import type { ProcessingOptions } from './types/process'
6+
7+
/**
8+
* Validates channel processing options for consistency and correct values.
9+
* Logs the current channel processing action based on provided options.
10+
*
11+
* @param options - Configuration options to validate
12+
* @throws Will exit the process if validation fails
13+
*/
14+
export function validateChannelOptions(options: ProcessingOptions): void {
15+
if (options.last !== undefined) {
16+
if (!Number.isInteger(options.last) || options.last < 1) {
17+
err('Error: The --last option must be a positive integer.')
18+
process.exit(1)
19+
}
20+
if (options.skip !== undefined || options.order !== undefined) {
21+
err('Error: The --last option cannot be used with --skip or --order.')
22+
process.exit(1)
23+
}
24+
}
25+
26+
if (options.skip !== undefined && (!Number.isInteger(options.skip) || options.skip < 0)) {
27+
err('Error: The --skip option must be a non-negative integer.')
28+
process.exit(1)
29+
}
30+
31+
if (options.order !== undefined && !['newest', 'oldest'].includes(options.order)) {
32+
err("Error: The --order option must be either 'newest' or 'oldest'.")
33+
process.exit(1)
34+
}
35+
36+
if (options.last) {
37+
l.dim(`\nProcessing the last ${options.last} videos`)
38+
} else if (options.skip) {
39+
l.dim(`\nSkipping first ${options.skip || 0} videos`)
40+
}
41+
}
42+
43+
/**
44+
* Logs the processing status and video counts for channel downloads.
45+
*
46+
* @param total - Total number of videos found.
47+
* @param processing - Number of videos to process.
48+
* @param options - Configuration options.
49+
*/
50+
export function logChannelProcessingStatus(
51+
total: number,
52+
processing: number,
53+
options: ProcessingOptions
54+
): void {
55+
if (options.last) {
56+
l.dim(`\n - Found ${total} videos in the channel.`)
57+
l.dim(` - Processing the last ${processing} videos.`)
58+
} else if (options.skip) {
59+
l.dim(`\n - Found ${total} videos in the channel.`)
60+
l.dim(` - Processing ${processing} videos after skipping ${options.skip || 0}.\n`)
61+
} else {
62+
l.dim(`\n - Found ${total} videos in the channel.`)
63+
l.dim(` - Processing all ${processing} videos.\n`)
64+
}
65+
}

src/utils/logging.ts

Lines changed: 2 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import chalk from 'chalk'
44

55
import type { ChainableLogger } from './types/logging'
6-
import type { ProcessingOptions, SeparatorParams } from './types/process'
6+
import type { SeparatorParams } from './types/process'
77

88
/**
99
* Logs the first step of a top-level function call with its relevant options or parameters.
@@ -112,52 +112,4 @@ function createChainableErrorLogger(): ChainableLogger {
112112

113113
// Create and export the chainable loggers
114114
export const l = createChainableLogger()
115-
export const err = createChainableErrorLogger()
116-
117-
/**
118-
* Logs the processing status and item counts for RSS feeds.
119-
*
120-
* @param total - Total number of RSS items found.
121-
* @param processing - Number of RSS items to process.
122-
* @param options - Configuration options.
123-
*/
124-
export function logRSSProcessingStatus(
125-
total: number,
126-
processing: number,
127-
options: ProcessingOptions
128-
): void {
129-
if (options.item && options.item.length > 0) {
130-
l.dim(`\n - Found ${total} items in the RSS feed.`)
131-
l.dim(` - Processing ${processing} specified items.`)
132-
} else if (options.last) {
133-
l.dim(`\n - Found ${total} items in the RSS feed.`)
134-
l.dim(` - Processing the last ${options.last} items.`)
135-
} else {
136-
l.dim(`\n - Found ${total} item(s) in the RSS feed.`)
137-
l.dim(` - Processing ${processing} item(s) after skipping ${options.skip || 0}.\n`)
138-
}
139-
}
140-
141-
/**
142-
* Logs the processing status and video counts for channel downloads.
143-
*
144-
* @param total - Total number of videos found.
145-
* @param processing - Number of videos to process.
146-
* @param options - Configuration options.
147-
*/
148-
export function logChannelProcessingStatus(
149-
total: number,
150-
processing: number,
151-
options: ProcessingOptions
152-
): void {
153-
if (options.last) {
154-
l.dim(`\n - Found ${total} videos in the channel.`)
155-
l.dim(` - Processing the last ${processing} videos.`)
156-
} else if (options.skip) {
157-
l.dim(`\n - Found ${total} videos in the channel.`)
158-
l.dim(` - Processing ${processing} videos after skipping ${options.skip || 0}.\n`)
159-
} else {
160-
l.dim(`\n - Found ${total} videos in the channel.`)
161-
l.dim(` - Processing all ${processing} videos.\n`)
162-
}
163-
}
115+
export const err = createChainableErrorLogger()

src/utils/rss-utils.ts

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
// src/utils/rss-utils.ts
2+
3+
import { exec, execFile } from 'node:child_process'
4+
import { promisify } from 'node:util'
5+
import { l, err } from '../utils/logging'
6+
7+
import type { TranscriptServices } from './types/transcription'
8+
import type { LLMServices } from './types/llms'
9+
import type { ProcessingOptions, RSSItem, HandlerFunction } from './types/process'
10+
11+
export const execPromise = promisify(exec)
12+
export const execFilePromise = promisify(execFile)
13+
14+
/**
15+
* Validates RSS flags (e.g., --last, --skip, --order, --date, --lastDays) without requiring feed data.
16+
*
17+
* @param options - The command-line options provided by the user
18+
* @throws Exits the process if any flag is invalid
19+
*/
20+
export function validateRSSOptions(options: ProcessingOptions): void {
21+
if (options.last !== undefined) {
22+
if (!Number.isInteger(options.last) || options.last < 1) {
23+
err('Error: The --last option must be a positive integer.')
24+
process.exit(1)
25+
}
26+
if (options.skip !== undefined || options.order !== undefined) {
27+
err('Error: The --last option cannot be used with --skip or --order.')
28+
process.exit(1)
29+
}
30+
}
31+
32+
if (options.skip !== undefined && (!Number.isInteger(options.skip) || options.skip < 0)) {
33+
err('Error: The --skip option must be a non-negative integer.')
34+
process.exit(1)
35+
}
36+
37+
if (options.order !== undefined && !['newest', 'oldest'].includes(options.order)) {
38+
err("Error: The --order option must be either 'newest' or 'oldest'.")
39+
process.exit(1)
40+
}
41+
42+
if (options.lastDays !== undefined) {
43+
if (!Number.isInteger(options.lastDays) || options.lastDays < 1) {
44+
err('Error: The --lastDays option must be a positive integer.')
45+
process.exit(1)
46+
}
47+
if (
48+
options.last !== undefined ||
49+
options.skip !== undefined ||
50+
options.order !== undefined ||
51+
(options.date && options.date.length > 0)
52+
) {
53+
err('Error: The --lastDays option cannot be used with --last, --skip, --order, or --date.')
54+
process.exit(1)
55+
}
56+
}
57+
58+
if (options.date && options.date.length > 0) {
59+
const dateRegex = /^\d{4}-\d{2}-\d{2}$/
60+
for (const d of options.date) {
61+
if (!dateRegex.test(d)) {
62+
err(`Error: Invalid date format "${d}". Please use YYYY-MM-DD format.`)
63+
process.exit(1)
64+
}
65+
}
66+
67+
if (
68+
options.last !== undefined ||
69+
options.skip !== undefined ||
70+
options.order !== undefined
71+
) {
72+
err('Error: The --date option cannot be used with --last, --skip, or --order.')
73+
process.exit(1)
74+
}
75+
}
76+
}
77+
78+
/**
79+
* Filters RSS feed items based on user-supplied options (e.g., item URLs, date ranges, etc.).
80+
*
81+
* @param options - Configuration options to filter the feed items
82+
* @param feedItemsArray - Parsed array of RSS feed items (raw JSON from XML parser)
83+
* @param channelTitle - Title of the RSS channel (optional)
84+
* @param channelLink - URL to the RSS channel (optional)
85+
* @param channelImage - A fallback channel image URL (optional)
86+
* @returns Filtered RSS items based on the provided options
87+
*/
88+
export async function filterRSSItems(
89+
options: ProcessingOptions,
90+
feedItemsArray?: any,
91+
channelTitle?: string,
92+
channelLink?: string,
93+
channelImage?: string
94+
): Promise<RSSItem[]> {
95+
const defaultDate = new Date().toISOString().substring(0, 10)
96+
const unfilteredItems: RSSItem[] = (feedItemsArray || [])
97+
.filter((item: any) => {
98+
if (!item.enclosure || !item.enclosure.type) return false
99+
const audioVideoTypes = ['audio/', 'video/']
100+
return audioVideoTypes.some((type) => item.enclosure.type.startsWith(type))
101+
})
102+
.map((item: any) => {
103+
let publishDate: string
104+
try {
105+
const date = item.pubDate ? new Date(item.pubDate) : new Date()
106+
publishDate = date.toISOString().substring(0, 10)
107+
} catch {
108+
publishDate = defaultDate
109+
}
110+
111+
return {
112+
showLink: item.enclosure?.url || '',
113+
channel: channelTitle || '',
114+
channelURL: channelLink || '',
115+
title: item.title || '',
116+
description: '',
117+
publishDate,
118+
coverImage: item['itunes:image']?.href || channelImage || '',
119+
}
120+
})
121+
122+
let itemsToProcess: RSSItem[] = []
123+
124+
if (options.item && options.item.length > 0) {
125+
itemsToProcess = unfilteredItems.filter((it) =>
126+
options.item!.includes(it.showLink)
127+
)
128+
} else if (options.lastDays !== undefined) {
129+
const now = new Date()
130+
const cutoff = new Date(now.getTime() - options.lastDays * 24 * 60 * 60 * 1000)
131+
132+
itemsToProcess = unfilteredItems.filter((it) => {
133+
const itDate = new Date(it.publishDate)
134+
return itDate >= cutoff
135+
})
136+
} else if (options.date && options.date.length > 0) {
137+
const selectedDates = new Set(options.date)
138+
itemsToProcess = unfilteredItems.filter((it) =>
139+
selectedDates.has(it.publishDate)
140+
)
141+
} else if (options.last) {
142+
itemsToProcess = unfilteredItems.slice(0, options.last)
143+
} else {
144+
const sortedItems =
145+
options.order === 'oldest'
146+
? unfilteredItems.slice().reverse()
147+
: unfilteredItems
148+
itemsToProcess = sortedItems.slice(options.skip || 0)
149+
}
150+
151+
return itemsToProcess
152+
}
153+
154+
/**
155+
* A helper function that validates RSS action input and processes it if valid.
156+
* Separately validates flags with {@link validateRSSOptions} and leaves feed-item filtering to {@link filterRSSItems}.
157+
*
158+
* @param options - The ProcessingOptions containing RSS feed details
159+
* @param handler - The function to handle each RSS feed
160+
* @param llmServices - The optional LLM service for processing
161+
* @param transcriptServices - The chosen transcription service
162+
* @throws An error if no valid RSS URLs are provided
163+
* @returns A promise that resolves when all RSS feeds have been processed
164+
*/
165+
export async function validateRSSAction(
166+
options: ProcessingOptions,
167+
handler: HandlerFunction,
168+
llmServices?: LLMServices,
169+
transcriptServices?: TranscriptServices
170+
): Promise<void> {
171+
if (options.item && !Array.isArray(options.item)) {
172+
options.item = [options.item]
173+
}
174+
if (typeof options.rss === 'string') {
175+
options.rss = [options.rss]
176+
}
177+
178+
validateRSSOptions(options)
179+
180+
const rssUrls = options.rss
181+
if (!rssUrls || rssUrls.length === 0) {
182+
throw new Error(`No valid RSS URLs provided for processing`)
183+
}
184+
185+
for (const rssUrl of rssUrls) {
186+
await handler(options, rssUrl, llmServices, transcriptServices)
187+
}
188+
}
189+
190+
/**
191+
* Logs the processing status and item counts for RSS feeds.
192+
*
193+
* @param total - Total number of RSS items found.
194+
* @param processing - Number of RSS items to process.
195+
* @param options - Configuration options.
196+
*/
197+
export function logRSSProcessingStatus(
198+
total: number,
199+
processing: number,
200+
options: ProcessingOptions
201+
): void {
202+
if (options.item && options.item.length > 0) {
203+
l.dim(`\n - Found ${total} items in the RSS feed.`)
204+
l.dim(` - Processing ${processing} specified items.`)
205+
} else if (options.last) {
206+
l.dim(`\n - Found ${total} items in the RSS feed.`)
207+
l.dim(` - Processing the last ${options.last} items.`)
208+
} else {
209+
l.dim(`\n - Found ${total} item(s) in the RSS feed.`)
210+
l.dim(` - Processing ${processing} item(s) after skipping ${options.skip || 0}.\n`)
211+
}
212+
}

0 commit comments

Comments
 (0)