Skip to content

Commit

Permalink
Merge pull request #8 from benVigie/feat/options
Browse files Browse the repository at this point in the history
Feat/options
  • Loading branch information
benVigie authored Nov 26, 2024
2 parents 91df1bc + 26efc19 commit 907b706
Show file tree
Hide file tree
Showing 8 changed files with 54 additions and 33 deletions.
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"type": "node",
"request": "launch",
"name": "Debug",
"program": "${workspaceFolder}/src/index.ts",
"program": "${workspaceFolder}/src/news_cli.ts",
"preLaunchTask": "tsc",
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
Expand All @@ -18,7 +18,7 @@
"type": "node",
"request": "launch",
"name": "Debug interactive mode",
"program": "${workspaceFolder}/src/index.ts",
"program": "${workspaceFolder}/src/news_cli.ts",
"preLaunchTask": "tsc",
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Options:
-t, --title <title> Ebook title (default: "today's date")
-e, --envPath <path> Path to the env file to load (default: "./.env")
-d, --debug Debug options (default: false)
-v, --verbose Print all app operations in terminal (default: true)
-h, --help Show tool help
```

Expand Down
19 changes: 14 additions & 5 deletions src/epub_news.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ const dt: DateTime = DateTime.now().setLocale(LOCALE);

export type EpubArticle = EpubContentOptions;

/** MediaSource options */
export interface Options {
debug?: boolean;
details?: boolean;
}

/**
* Describe article list
*/
Expand All @@ -37,10 +43,13 @@ export class NoMediaSourceError extends Error {}
* EpubNews is the lib which controls the fetching of articles and generate the epub news
*/
export default class EpubNews {
private _debug: boolean;
private _options: Options;

constructor(debug: boolean = false) {
this._debug = debug;
constructor(options: Options) {
// Default values
if (options.debug === undefined) options.debug = false;
if (options.details === undefined) options.details = false;
this._options = options;
}

/**
Expand All @@ -52,7 +61,7 @@ export default class EpubNews {
*/
async listArticlesFromFeed(rssFeedUrl: string): Promise<ArticlesList> {
// Try to retrieve a media source from the feed
const mediaSource = createMediaSource(rssFeedUrl, this._debug);
const mediaSource = createMediaSource(rssFeedUrl, this._options);
if (!mediaSource) {
throw new NoMediaSourceError(
`No media source implemented for ${rssFeedUrl}> Are you sure it matches one of your MediaSource implementation ?`,
Expand All @@ -77,7 +86,7 @@ export default class EpubNews {
*/
async getEpubDataFromArticles(rssFeedUrl: string, articles: Array<Parser.Item>): Promise<EpubArticlesData> {
// Try to retrieve a media source from the feed
const mediaSource = createMediaSource(rssFeedUrl, this._debug);
const mediaSource = createMediaSource(rssFeedUrl, this._options);
if (!mediaSource) {
throw new NoMediaSourceError(
`No media source implemented for ${rssFeedUrl}> Are you sure it matches one of your MediaSource implementation ?`,
Expand Down
33 changes: 19 additions & 14 deletions src/media_sources/media_source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Parser from "rss-parser";
import ora, { Ora } from "ora";
import terminalLink from "terminal-link";
import { EpubContentOptions } from "@lesjoursfr/html-to-epub";
import { Options } from "../epub_news.js";

/** Default trim article error */
export class TrimArticleError extends Error {}
Expand All @@ -15,15 +16,15 @@ export class TrimArticleError extends Error {}
export abstract class MediaSource<T = { [key: string]: any }, U = { [key: string]: any }> {
protected _rss_sources: string;
protected _rss_parser: Parser<T, U>;
protected _debug: boolean;
protected _options: Options;
protected _feedTitle: string | undefined;
protected _spinner: Ora;
protected _fetchOptions: https.RequestOptions;
protected _cover: string | undefined;

constructor(source: string, debug: boolean = false) {
constructor(source: string, options: Options) {
this._rss_sources = source;
this._debug = debug;
this._options = options;
this._spinner = ora();
this._fetchOptions = {};
// You may need to create a new parser which will match your source's feed and items custom fields
Expand Down Expand Up @@ -66,7 +67,7 @@ export abstract class MediaSource<T = { [key: string]: any }, U = { [key: string
* @param rssFeedUrl
*/
public static isHandlingRssFeed(rssFeedUrl: string): boolean {
throw new Error("Your MediaSOurce implementation must implement this function");
throw new Error("Your MediaSource implementation must implement this function");
}

/**
Expand Down Expand Up @@ -95,18 +96,20 @@ export abstract class MediaSource<T = { [key: string]: any }, U = { [key: string
data: trimedArticle,
author: news.creator,
});
this._spinner.suffixText = chalk.green("ok");
this._spinner.succeed();
if (this._options.details) {
this._spinner.suffixText = chalk.green("ok");
this._spinner.succeed();
}
} catch (error) {
if (error instanceof TrimArticleError) {
this._spinner.suffixText = chalk.yellow(error.toString());
if (this._options.details) this._spinner.suffixText = chalk.yellow(error.toString());
}
// If an error occurs, display the article link in console for information
const link = terminalLink(news.title, news.link);
this._spinner.warn(chalk.italic.gray(link));
if (this._options.details) this._spinner.warn(chalk.italic.gray(link));
}
} else {
this._spinner.warn(chalk.italic.red("No title or link for this news, skip"));
if (this._options.details) this._spinner.warn(chalk.italic.red("No title or link for this news, skip"));
}
}
return articles;
Expand All @@ -119,11 +122,11 @@ export abstract class MediaSource<T = { [key: string]: any }, U = { [key: string
*/
protected async parseRSSFeed(): Promise<Array<U & Parser.Item>> {
const feed = await this._rss_parser.parseURL(this._rss_sources);
if (this._debug) console.info(chalk.blue.magenta(feed.title));
if (this._options.debug) console.info(chalk.blue.magenta(feed.title));

this._feedTitle = feed.title;
feed.items.forEach((item) => {
if (this._debug) {
if (this._options.debug) {
console.log(chalk.blue(` - ${item.title}`), chalk.gray(`(${item.pubDate})`));
}
});
Expand Down Expand Up @@ -190,8 +193,10 @@ export abstract class MediaSource<T = { [key: string]: any }, U = { [key: string
* @param text Spinner text
*/
protected startInfoSpinner(text: string): void {
this._spinner = ora(chalk.italic(text));
this._spinner.indent = 2;
this._spinner.start();
if (this._options.details) {
this._spinner = ora(chalk.italic(text));
this._spinner.indent = 2;
this._spinner.start();
}
}
}
9 changes: 5 additions & 4 deletions src/media_sources/media_source_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@ import chalk from "chalk";
import { MediaSource } from "./media_source.js";
import LeMondeMediaSource from "./media_source_le_monde.js";
import GamekultMediaSource from "./media_source_gamekult.js";
import { Options } from "../epub_news.js";

/**
* MediaSource factory. It will create a MediaSource instance according to the given rss feed url.
* If no match is found, null will be returned.
* @param rssFeedUrl The rss feed we want to retrieve
* @param debug True if we want to print debug traces. Default false
* @param options Options to pass to the MediaSource constructor
*/
export default function createMediaSource(rssFeedUrl: string, debug: boolean = false): MediaSource | null {
export default function createMediaSource(rssFeedUrl: string, options: Options): MediaSource | null {
// Try to match with Le Monde implementation
if (LeMondeMediaSource.isHandlingRssFeed(rssFeedUrl)) return new LeMondeMediaSource(rssFeedUrl, debug);
if (LeMondeMediaSource.isHandlingRssFeed(rssFeedUrl)) return new LeMondeMediaSource(rssFeedUrl, options);

// Try to match with Gamekult implementation
if (GamekultMediaSource.isHandlingRssFeed(rssFeedUrl)) return new GamekultMediaSource(rssFeedUrl, debug);
if (GamekultMediaSource.isHandlingRssFeed(rssFeedUrl)) return new GamekultMediaSource(rssFeedUrl, options);

console.error(chalk.bold.red("No MediaSource match"), chalk.bold.yellow(rssFeedUrl));
console.error(chalk.red("Did you miss the implementation for this media or the url is malformed ?"));
Expand Down
8 changes: 5 additions & 3 deletions src/media_sources/media_source_gamekult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import chalk from "chalk";
import Parser from "rss-parser";
import { parse } from "node-html-parser";
import { MediaSource, TrimArticleError } from "./media_source.js";
import { Options } from "../epub_news.js";

const GAMEKULT_FEED_URL = "https://www.gamekult.com/feed.xml";
// Regex to match Gamekult image sizes
Expand Down Expand Up @@ -33,8 +34,9 @@ type CustomRssItem = { "media:content": any }; // Adding media:content to retrie
* https://www.gamekult.com/
*/
export default class GamekultMediaSource extends MediaSource<CustomRssFeed, CustomRssItem> {
constructor(source: string, debug: boolean = false) {
super(source, debug);
constructor(source: string, options: Options) {
super(source, options);

// Create a new rss parser with custom Gamekult fields
this._rss_parser = new Parser({
customFields: { feed: [], item: ["media:content"] },
Expand All @@ -55,7 +57,7 @@ export default class GamekultMediaSource extends MediaSource<CustomRssFeed, Cust
*/
protected trimArticleForEpub(newsFeed: Parser.Item & CustomRssItem, article: string): string {
// Update user feedback
this._spinner.suffixText = chalk.italic.green("trimming...");
if (this._options.details) this._spinner.suffixText = chalk.italic.green("trimming...");

// Remove useless nodes
const root = parse(article);
Expand Down
8 changes: 5 additions & 3 deletions src/media_sources/media_source_le_monde.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import chalk from "chalk";
import Parser from "rss-parser";
import { parse } from "node-html-parser";
import { MediaSource, TrimArticleError } from "./media_source.js";
import { Options } from "../epub_news.js";

// List of useless html nodes we can rid of
const CLEAN_DOM_LIST = [
Expand Down Expand Up @@ -32,8 +33,9 @@ type CustomRssItem = { "media:content": any }; // Adding media:content to retrie
* https://www.lemonde.fr
*/
export default class LeMondeMediaSource extends MediaSource<CustomRssFeed, CustomRssItem> {
constructor(source: string, debug: boolean = false) {
super(source, debug);
constructor(source: string, options: Options) {
super(source, options);

// Create a new rss parser with custom "Le Monde" fields
this._rss_parser = new Parser({
customFields: { feed: [], item: ["media:content"] },
Expand Down Expand Up @@ -75,7 +77,7 @@ export default class LeMondeMediaSource extends MediaSource<CustomRssFeed, Custo
}

// Update user feedback
this._spinner.suffixText = chalk.italic.green("trimming...");
if (this._options.details) this._spinner.suffixText = chalk.italic.green("trimming...");

// Remove useless nodes
const root = parse(article);
Expand Down
5 changes: 3 additions & 2 deletions src/news_cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ program
.option("-p, --path <path>", "default path for epub export", "./ if no DEFAULT_EXPORT_PATH set in env")
.option("-t, --title <title>", "Ebook title", dt.toLocaleString(DateTime.DATE_FULL))
.option("-e, --envPath <path>", "Path to the env file to load (if any)", ".env")
.option("-d, --debug", "Debug options", false);
.option("-d, --debug", "Debug options", false)
.option("-v, --verbose", "Print all app operations in terminal", true);
program.parse();
prg_options = program.opts();

// Load environment variables from file if specified
dotenv.config({ path: prg_options.envPath });

async function main() {
const epub = new EpubNews(prg_options.debug);
const epub = new EpubNews({ debug: prg_options.debug, details: prg_options.verbose });
let epubContent: EpubArticle[] = [];
let customCss: string = "";
let epubCover: string | undefined;
Expand Down

0 comments on commit 907b706

Please sign in to comment.