patreon-dl 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +422 -0
- package/bin/patreon-dl.js +5 -0
- package/dist/cli/CLIOptionValidator.d.ts +9 -0
- package/dist/cli/CLIOptionValidator.d.ts.map +1 -0
- package/dist/cli/CLIOptionValidator.js +85 -0
- package/dist/cli/CLIOptionValidator.js.map +1 -0
- package/dist/cli/CLIOptions.d.ts +20 -0
- package/dist/cli/CLIOptions.d.ts.map +1 -0
- package/dist/cli/CLIOptions.js +75 -0
- package/dist/cli/CLIOptions.js.map +1 -0
- package/dist/cli/CommandLineParser.d.ts +11 -0
- package/dist/cli/CommandLineParser.d.ts.map +1 -0
- package/dist/cli/CommandLineParser.js +212 -0
- package/dist/cli/CommandLineParser.js.map +1 -0
- package/dist/cli/ConfigFileParser.d.ts +9 -0
- package/dist/cli/ConfigFileParser.d.ts.map +1 -0
- package/dist/cli/ConfigFileParser.js +163 -0
- package/dist/cli/ConfigFileParser.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +162 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/downloaders/Bootstrap.d.ts +29 -0
- package/dist/downloaders/Bootstrap.d.ts.map +1 -0
- package/dist/downloaders/Bootstrap.js +51 -0
- package/dist/downloaders/Bootstrap.js.map +1 -0
- package/dist/downloaders/Downloader.d.ts +59 -0
- package/dist/downloaders/Downloader.d.ts.map +1 -0
- package/dist/downloaders/Downloader.js +357 -0
- package/dist/downloaders/Downloader.js.map +1 -0
- package/dist/downloaders/DownloaderEvent.d.ts +47 -0
- package/dist/downloaders/DownloaderEvent.d.ts.map +1 -0
- package/dist/downloaders/DownloaderEvent.js +6 -0
- package/dist/downloaders/DownloaderEvent.js.map +1 -0
- package/dist/downloaders/DownloaderOptions.d.ts +39 -0
- package/dist/downloaders/DownloaderOptions.d.ts.map +1 -0
- package/dist/downloaders/DownloaderOptions.js +69 -0
- package/dist/downloaders/DownloaderOptions.js.map +1 -0
- package/dist/downloaders/PostDownloader.d.ts +8 -0
- package/dist/downloaders/PostDownloader.d.ts.map +1 -0
- package/dist/downloaders/PostDownloader.js +428 -0
- package/dist/downloaders/PostDownloader.js.map +1 -0
- package/dist/downloaders/ProductDownloader.d.ts +8 -0
- package/dist/downloaders/ProductDownloader.d.ts.map +1 -0
- package/dist/downloaders/ProductDownloader.js +171 -0
- package/dist/downloaders/ProductDownloader.js.map +1 -0
- package/dist/downloaders/cache/StatusCache.d.ts +43 -0
- package/dist/downloaders/cache/StatusCache.d.ts.map +1 -0
- package/dist/downloaders/cache/StatusCache.js +206 -0
- package/dist/downloaders/cache/StatusCache.js.map +1 -0
- package/dist/downloaders/index.d.ts +7 -0
- package/dist/downloaders/index.d.ts.map +1 -0
- package/dist/downloaders/index.js +6 -0
- package/dist/downloaders/index.js.map +1 -0
- package/dist/downloaders/task/DownloadTask.d.ts +89 -0
- package/dist/downloaders/task/DownloadTask.d.ts.map +1 -0
- package/dist/downloaders/task/DownloadTask.js +240 -0
- package/dist/downloaders/task/DownloadTask.js.map +1 -0
- package/dist/downloaders/task/DownloadTaskBatch.d.ts +45 -0
- package/dist/downloaders/task/DownloadTaskBatch.d.ts.map +1 -0
- package/dist/downloaders/task/DownloadTaskBatch.js +195 -0
- package/dist/downloaders/task/DownloadTaskBatch.js.map +1 -0
- package/dist/downloaders/task/DownloadTaskBatchEvent.d.ts +32 -0
- package/dist/downloaders/task/DownloadTaskBatchEvent.d.ts.map +1 -0
- package/dist/downloaders/task/DownloadTaskBatchEvent.js +2 -0
- package/dist/downloaders/task/DownloadTaskBatchEvent.js.map +1 -0
- package/dist/downloaders/task/DownloadTaskFactory.d.ts +20 -0
- package/dist/downloaders/task/DownloadTaskFactory.d.ts.map +1 -0
- package/dist/downloaders/task/DownloadTaskFactory.js +177 -0
- package/dist/downloaders/task/DownloadTaskFactory.js.map +1 -0
- package/dist/downloaders/task/FFmpegDownloadTask.d.ts +27 -0
- package/dist/downloaders/task/FFmpegDownloadTask.d.ts.map +1 -0
- package/dist/downloaders/task/FFmpegDownloadTask.js +206 -0
- package/dist/downloaders/task/FFmpegDownloadTask.js.map +1 -0
- package/dist/downloaders/task/FetcherDownloadTask.d.ts +21 -0
- package/dist/downloaders/task/FetcherDownloadTask.d.ts.map +1 -0
- package/dist/downloaders/task/FetcherDownloadTask.js +213 -0
- package/dist/downloaders/task/FetcherDownloadTask.js.map +1 -0
- package/dist/downloaders/task/index.d.ts +4 -0
- package/dist/downloaders/task/index.d.ts.map +1 -0
- package/dist/downloaders/task/index.js +3 -0
- package/dist/downloaders/task/index.js.map +1 -0
- package/dist/downloaders/templates/CampaignInfo.d.ts +3 -0
- package/dist/downloaders/templates/CampaignInfo.d.ts.map +1 -0
- package/dist/downloaders/templates/CampaignInfo.js +58 -0
- package/dist/downloaders/templates/CampaignInfo.js.map +1 -0
- package/dist/downloaders/templates/PostInfo.d.ts +4 -0
- package/dist/downloaders/templates/PostInfo.d.ts.map +1 -0
- package/dist/downloaders/templates/PostInfo.js +45 -0
- package/dist/downloaders/templates/PostInfo.js.map +1 -0
- package/dist/downloaders/templates/ProductInfo.d.ts +3 -0
- package/dist/downloaders/templates/ProductInfo.d.ts.map +1 -0
- package/dist/downloaders/templates/ProductInfo.js +20 -0
- package/dist/downloaders/templates/ProductInfo.js.map +1 -0
- package/dist/entities/Attachment.d.ts +7 -0
- package/dist/entities/Attachment.d.ts.map +1 -0
- package/dist/entities/Attachment.js +2 -0
- package/dist/entities/Attachment.js.map +1 -0
- package/dist/entities/Campaign.d.ts +19 -0
- package/dist/entities/Campaign.d.ts.map +1 -0
- package/dist/entities/Campaign.js +2 -0
- package/dist/entities/Campaign.js.map +1 -0
- package/dist/entities/Downloadable.d.ts +6 -0
- package/dist/entities/Downloadable.d.ts.map +1 -0
- package/dist/entities/Downloadable.js +5 -0
- package/dist/entities/Downloadable.js.map +1 -0
- package/dist/entities/MediaItem.d.ts +95 -0
- package/dist/entities/MediaItem.d.ts.map +1 -0
- package/dist/entities/MediaItem.js +2 -0
- package/dist/entities/MediaItem.js.map +1 -0
- package/dist/entities/Post.d.ts +87 -0
- package/dist/entities/Post.d.ts.map +1 -0
- package/dist/entities/Post.js +2 -0
- package/dist/entities/Post.js.map +1 -0
- package/dist/entities/Product.d.ts +17 -0
- package/dist/entities/Product.d.ts.map +1 -0
- package/dist/entities/Product.js +2 -0
- package/dist/entities/Product.js.map +1 -0
- package/dist/entities/Reward.d.ts +14 -0
- package/dist/entities/Reward.d.ts.map +1 -0
- package/dist/entities/Reward.js +2 -0
- package/dist/entities/Reward.js.map +1 -0
- package/dist/entities/User.d.ts +15 -0
- package/dist/entities/User.d.ts.map +1 -0
- package/dist/entities/User.js +2 -0
- package/dist/entities/User.js.map +1 -0
- package/dist/entities/index.d.ts +9 -0
- package/dist/entities/index.d.ts.map +1 -0
- package/dist/entities/index.js +6 -0
- package/dist/entities/index.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/PageParser.d.ts +6 -0
- package/dist/parsers/PageParser.d.ts.map +1 -0
- package/dist/parsers/PageParser.js +23 -0
- package/dist/parsers/PageParser.js.map +1 -0
- package/dist/parsers/Parser.d.ts +43 -0
- package/dist/parsers/Parser.d.ts.map +1 -0
- package/dist/parsers/Parser.js +439 -0
- package/dist/parsers/Parser.js.map +1 -0
- package/dist/parsers/PostParser.d.ts +7 -0
- package/dist/parsers/PostParser.d.ts.map +1 -0
- package/dist/parsers/PostParser.js +259 -0
- package/dist/parsers/PostParser.js.map +1 -0
- package/dist/parsers/ProductParser.d.ts +7 -0
- package/dist/parsers/ProductParser.d.ts.map +1 -0
- package/dist/parsers/ProductParser.js +70 -0
- package/dist/parsers/ProductParser.js.map +1 -0
- package/dist/utils/AttachmentFilenameResolver.d.ts +9 -0
- package/dist/utils/AttachmentFilenameResolver.d.ts.map +1 -0
- package/dist/utils/AttachmentFilenameResolver.js +73 -0
- package/dist/utils/AttachmentFilenameResolver.js.map +1 -0
- package/dist/utils/FSHelper.d.ts +57 -0
- package/dist/utils/FSHelper.d.ts.map +1 -0
- package/dist/utils/FSHelper.js +214 -0
- package/dist/utils/FSHelper.js.map +1 -0
- package/dist/utils/Fetcher.d.ts +45 -0
- package/dist/utils/Fetcher.d.ts.map +1 -0
- package/dist/utils/Fetcher.js +192 -0
- package/dist/utils/Fetcher.js.map +1 -0
- package/dist/utils/FetcherProgressMonitor.d.ts +18 -0
- package/dist/utils/FetcherProgressMonitor.d.ts.map +1 -0
- package/dist/utils/FetcherProgressMonitor.js +56 -0
- package/dist/utils/FetcherProgressMonitor.js.map +1 -0
- package/dist/utils/FilenameFormatHelper.d.ts +44 -0
- package/dist/utils/FilenameFormatHelper.d.ts.map +1 -0
- package/dist/utils/FilenameFormatHelper.js +98 -0
- package/dist/utils/FilenameFormatHelper.js.map +1 -0
- package/dist/utils/FllenameResolver.d.ts +20 -0
- package/dist/utils/FllenameResolver.d.ts.map +1 -0
- package/dist/utils/FllenameResolver.js +55 -0
- package/dist/utils/FllenameResolver.js.map +1 -0
- package/dist/utils/Formatter.d.ts +21 -0
- package/dist/utils/Formatter.d.ts.map +1 -0
- package/dist/utils/Formatter.js +112 -0
- package/dist/utils/Formatter.js.map +1 -0
- package/dist/utils/MediaFilenameResolver.d.ts +9 -0
- package/dist/utils/MediaFilenameResolver.d.ts.map +1 -0
- package/dist/utils/MediaFilenameResolver.js +90 -0
- package/dist/utils/MediaFilenameResolver.js.map +1 -0
- package/dist/utils/Misc.d.ts +14 -0
- package/dist/utils/Misc.d.ts.map +1 -0
- package/dist/utils/Misc.js +4 -0
- package/dist/utils/Misc.js.map +1 -0
- package/dist/utils/ObjectHelper.d.ts +4 -0
- package/dist/utils/ObjectHelper.d.ts.map +1 -0
- package/dist/utils/ObjectHelper.js +30 -0
- package/dist/utils/ObjectHelper.js.map +1 -0
- package/dist/utils/PackageInfo.d.ts +10 -0
- package/dist/utils/PackageInfo.d.ts.map +1 -0
- package/dist/utils/PackageInfo.js +33 -0
- package/dist/utils/PackageInfo.js.map +1 -0
- package/dist/utils/URLHelper.d.ts +40 -0
- package/dist/utils/URLHelper.d.ts.map +1 -0
- package/dist/utils/URLHelper.js +192 -0
- package/dist/utils/URLHelper.js.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logging/ChainLogger.d.ts +11 -0
- package/dist/utils/logging/ChainLogger.d.ts.map +1 -0
- package/dist/utils/logging/ChainLogger.js +50 -0
- package/dist/utils/logging/ChainLogger.js.map +1 -0
- package/dist/utils/logging/ConsoleLogger.d.ts +31 -0
- package/dist/utils/logging/ConsoleLogger.d.ts.map +1 -0
- package/dist/utils/logging/ConsoleLogger.js +126 -0
- package/dist/utils/logging/ConsoleLogger.js.map +1 -0
- package/dist/utils/logging/FileLogger.d.ts +26 -0
- package/dist/utils/logging/FileLogger.d.ts.map +1 -0
- package/dist/utils/logging/FileLogger.js +147 -0
- package/dist/utils/logging/FileLogger.js.map +1 -0
- package/dist/utils/logging/Logger.d.ts +12 -0
- package/dist/utils/logging/Logger.d.ts.map +1 -0
- package/dist/utils/logging/Logger.js +15 -0
- package/dist/utils/logging/Logger.js.map +1 -0
- package/dist/utils/logging/index.d.ts +7 -0
- package/dist/utils/logging/index.d.ts.map +1 -0
- package/dist/utils/logging/index.js +7 -0
- package/dist/utils/logging/index.js.map +1 -0
- package/package.json +78 -0
package/README.md
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
<a href='https://ko-fi.com/C0C5RGOOP' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi2.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
|
2
|
+
|
|
3
|
+
# patreon-dl
|
|
4
|
+
|
|
5
|
+
A Patreon downloader written in [Node.js](https://nodejs.org).
|
|
6
|
+
|
|
7
|
+
#### Features
|
|
8
|
+
- Access to patron-only content through cookie
|
|
9
|
+
- Download posts by user, in a collection or single post
|
|
10
|
+
- Download products (aka shop purchases)
|
|
11
|
+
- Items included in downloads:
|
|
12
|
+
- videos
|
|
13
|
+
- images
|
|
14
|
+
- audio
|
|
15
|
+
- attachments
|
|
16
|
+
- Save campaign and content info
|
|
17
|
+
- Extensively configurable
|
|
18
|
+
|
|
19
|
+
You can run `patreon-dl` from the command-line or use it as a library for your project. Node.js v16.16.0 or higher required.
|
|
20
|
+
|
|
21
|
+
#### Limitations
|
|
22
|
+
|
|
23
|
+
- Embedded videos, i.e. those linked from YouTube, Vimeo, etc., are not supported. Only info about the embed is saved.
|
|
24
|
+
- Likewise, embedded links are not followed; only info about the embed is saved.
|
|
25
|
+
|
|
26
|
+
#### FFmpeg requirement
|
|
27
|
+
[FFmpeg](https://ffmpeg.org) is required for downloading videos that are provided only in streaming format. Not all video downloads require FFmpeg, but you should have it installed on your system anyway.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
npm i -g patreon-dl
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The `-g` option is for installing `patreon-dl` globally and have the CLI executable added to the PATH. Depending on your usage, you might not need this.
|
|
36
|
+
|
|
37
|
+
## CLI usage
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
$ patreon-dl [OPTION]... URL
|
|
41
|
+
|
|
42
|
+
Options
|
|
43
|
+
|
|
44
|
+
-h, --help Display this usage guide
|
|
45
|
+
-C, --config-file <file> Load configuration file for setting full options
|
|
46
|
+
-c, --cookie <string> Cookie for accessing patron-only content
|
|
47
|
+
-f, --ffmpeg <string> Path to FFmpeg executable
|
|
48
|
+
-o, --out-dir <dir> Path to directory where content is saved
|
|
49
|
+
-l, --log-level <level> Log level of the console logger: 'info', 'debug',
|
|
50
|
+
'warn' or 'error'; set to 'none' to disable the
|
|
51
|
+
logger.
|
|
52
|
+
-y, --no-prompt Do not prompt for confirmation to proceed
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### Supported URL formats
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
// Product
|
|
59
|
+
https://www.patreon.com/<creator>/shop/<slug>-<product_id>
|
|
60
|
+
|
|
61
|
+
// Posts
|
|
62
|
+
https://www.patreon.com/<creator>/posts
|
|
63
|
+
|
|
64
|
+
// Single post
|
|
65
|
+
https://www.patreon.com/posts/<slug>-<post_id>
|
|
66
|
+
|
|
67
|
+
// Posts in collection
|
|
68
|
+
https://www.patreon.com/collection/<collection_id>
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Content is saved with the following directory structure:
|
|
73
|
+
```
|
|
74
|
+
out-dir
|
|
75
|
+
├── campaign
|
|
76
|
+
├── campaign_info
|
|
77
|
+
├── posts
|
|
78
|
+
│ ├── post 1
|
|
79
|
+
│ │ ├── post_info
|
|
80
|
+
│ │ ├── images
|
|
81
|
+
│ │ ├── ...
|
|
82
|
+
│ ├── post 2
|
|
83
|
+
│ ├── post_info
|
|
84
|
+
│ ├── images
|
|
85
|
+
│ ├── ...
|
|
86
|
+
├──shop
|
|
87
|
+
├── product 1
|
|
88
|
+
├── product_info
|
|
89
|
+
├── content_media
|
|
90
|
+
├── ...
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
### Configuration file
|
|
95
|
+
|
|
96
|
+
Command-line options are limited. To access the full range of options, create a configuration file and pass it to `patreon-dl` with the (capital) `-C` option.
|
|
97
|
+
|
|
98
|
+
Refer to the [example config](./example.conf) to see what options are offered. Also see [How to obtain Cookie](https://github.com/patrickkfkan/patreon-dl/wiki/How-to-obtain-Cookie).
|
|
99
|
+
|
|
100
|
+
Note that you can override an option from a configuration file with one provided at the command-line, provided of course that a command-line equivalent is available.
|
|
101
|
+
|
|
102
|
+
## Library usage
|
|
103
|
+
|
|
104
|
+
To use `patreon-dl` in your own project:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
import PatreonDownloader from 'patreon-dl';
|
|
108
|
+
|
|
109
|
+
const url = '....';
|
|
110
|
+
|
|
111
|
+
const downloader = await PatreonDownloader.getInstance(url, [options]);
|
|
112
|
+
|
|
113
|
+
await downloader.start();
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Here, we first obtain a downloader instance by calling `PatreonDownloader.getInstance()`, passing to it the URL we want to download from (one of the [supported URL formats](#supported-url-formats)) and [downloader options](#downloader-options), if any.
|
|
117
|
+
|
|
118
|
+
Then, we call `start()` on the downloader instance to begin the download process. The `start()` method returns a Promise that resolves when the download process has ended.
|
|
119
|
+
- To monitor status and progress, see [Workflow and Events](#workflow-and-events).
|
|
120
|
+
- To abort a download process, see [Aborting](#aborting).
|
|
121
|
+
|
|
122
|
+
### Downloader options
|
|
123
|
+
|
|
124
|
+
An object with the following properties (all *optional*):
|
|
125
|
+
|
|
126
|
+
| Option | Description |
|
|
127
|
+
|-----------------|-------------------------------------------------------------|
|
|
128
|
+
| `cookie` | Cookie to include in requests; required for accessing patron-only content. See [How to obtain Cookie](https://github.com/patrickkfkan/patreon-dl/wiki/How-to-obtain-Cookie). |
|
|
129
|
+
| `useStatusCache` | Whether to use status cache to quickly determine whether a target that had been downloaded before has changed since the last download. Default: `true` |
|
|
130
|
+
| `pathToFFmpeg` | Path to `ffmpeg` executable. If not specified, `ffmpeg` will be called directly when needed, so make sure it is in the PATH. |
|
|
131
|
+
| `outDir` | Path to directory where content is saved. Default: current working directory |
|
|
132
|
+
| `dirNameFormat` | How to name directories: (object)<ul><li>`campaign`: see [Campaign directory name format](#campaign-directory-name-format)</li><li>`content`: see [Content directory name format](#content-directory-name-format)</li></ul> |
|
|
133
|
+
| `filenameFormat` | Naming of files: (object)<ul><li>`media`: see [Media filename format](#media-filename-format) |
|
|
134
|
+
| `include` | What to include in the download: (object) <ul><li>`lockedContent`: whether to process locked content. Default: `true`</li><li>`campaignInfo`: whether to save campaign info. Default: `true`</li><li>`contentInfo`: whether to save content info. Default: `true`</li><li>`contentMedia`: whether to download content media (images, videos, audio, attachments, excluding previews). Default: `true`</li><li>`previewMedia`: whether to download preview media, if available. Default: `true`</li><li>`allMediaVariants`: whether to download all media variants, if available. If `false`, only the best quality variant will be downloaded. Default: `false`</li></ul> |
|
|
135
|
+
| `request` | Rate limiting and retry on error: (object)<ul><li>`maxRetries`: maximum number of retries if a request or download fails. Default: 3</li><li>`maxConcurrent`: maximum number of concurrent downloads. Default: 10</li><li>`minTime`: minimum time to wait between starting requests or downloads (milliseconds). Default: 333</li></ul> |
|
|
136
|
+
| `fileExistsAction` | What to do when a target file already exists: (object)<ul><li>`info`: in the context of saving info (such as campaign or post info), the action to take when a file belonging to the info already exists. Default: `saveAsCopyIfNewer`</li><li>`infoAPI`: API data is saved as part of info. Because it changes frequently, and usually used for debugging purpose only, you can set a different action when saving an API data file that already exists. Default: `overwrite`</li><li>`content`: in the context of downloading content, the action to take when a file belonging to the content already exists. Default: `skip`</li></ul><p>Supported actions:<li>`overwrite`: overwrite existing file.</li><li>`skip`: skip saving the file.</li><li>`saveAsCopy`: save the file under incremented filename (e.g. "abc.jpg" becomes "abc (1).jpg").</li><li>`saveAsCopyIfNewer`: like `saveAsCopy`, but only do so if the contents have actually changed.</li></ul></p> |
|
|
137
|
+
|`logger` | See [Logger](#logger) |
|
|
138
|
+
|
|
139
|
+
#### Campaign directory name format
|
|
140
|
+
|
|
141
|
+
Format to apply when naming campaign directories. A format is a string pattern consisting of fields enclosed in curly braces.
|
|
142
|
+
|
|
143
|
+
> ***What is a campaign directory?***
|
|
144
|
+
> <p>When you download content, a directory is created for the campaign that hosts the content. Content directories, which stores the downloaded content, are then placed under the campaign directory.
|
|
145
|
+
> If campaign info could not be obtained from content, then content directory
|
|
146
|
+
> will be created directly under <code>outDir</code>.</p>
|
|
147
|
+
|
|
148
|
+
A format must contain at least one of the following fields:
|
|
149
|
+
- `creator.vanity`
|
|
150
|
+
- `creator.name`
|
|
151
|
+
- `creator.id`
|
|
152
|
+
- `campaign.name`
|
|
153
|
+
- `campaign.id`
|
|
154
|
+
|
|
155
|
+
Characters enclosed in square brackets followed by a question mark denote
|
|
156
|
+
conditional separators. If the value of a field could not be obtained or
|
|
157
|
+
is empty, the conditional separator immediately adjacent to it will be
|
|
158
|
+
omitted from the name.
|
|
159
|
+
|
|
160
|
+
Default: '{creator.vanity}[ - ]?{campaign.name}'</br>
|
|
161
|
+
Fallback: 'campaign-{campaign.id}'
|
|
162
|
+
|
|
163
|
+
#### Content directory name format
|
|
164
|
+
|
|
165
|
+
Format to apply when naming content directories. A format is a string pattern consisting of fields enclosed in curly braces.
|
|
166
|
+
|
|
167
|
+
> ***What is a content directory?***
|
|
168
|
+
> <p>Content can be a post or product. A directory is created for each piece of content. Downloaded items for the content are placed under this directory.</p>
|
|
169
|
+
|
|
170
|
+
A format must contain at least one of the following unique identifier fields:
|
|
171
|
+
- `content.id`: ID of content
|
|
172
|
+
- `content.slug`: last segment of the content URL
|
|
173
|
+
|
|
174
|
+
In addition, a format can contain the following fields:
|
|
175
|
+
- `content.name`: post title or product name
|
|
176
|
+
- `content.type`: type of content ('product' or 'post')
|
|
177
|
+
|
|
178
|
+
Characters enclosed in square brackets followed by a question mark denote conditional separators. If the value of a field could not be obtained or is empty, the conditional separator immediately adjacent to it will be omitted from the name.
|
|
179
|
+
|
|
180
|
+
Default: '{content.id}[ - ]?{content.name}'</br>
|
|
181
|
+
Fallback: '{content.type}-{content.id}'
|
|
182
|
+
|
|
183
|
+
#### Media filename format
|
|
184
|
+
|
|
185
|
+
Filename format of a downloaded item. A format is a string pattern consisting of fields enclosed in curly braces.
|
|
186
|
+
|
|
187
|
+
A format must contain at least one of the following fields:
|
|
188
|
+
- `media.id`: ID of the item downloaded (assigned by Patreon)
|
|
189
|
+
- `media.filename`: can be one of the following, in order of availability:
|
|
190
|
+
- original filename included in the item's API data; or
|
|
191
|
+
- filename derived from the header of the response to the HTTP download request.
|
|
192
|
+
|
|
193
|
+
In addition, a format can contain the following fields:
|
|
194
|
+
- `media.type`: type of item (e.g. 'image' or 'video')
|
|
195
|
+
- `media.variant`: where applicable, the variant of the item (e.g. 'original', 'thumbnailSmall'...for images)
|
|
196
|
+
|
|
197
|
+
> If `media.variant` is not included in the format, it will be appended to it if `allMediaVariants` is `true`.
|
|
198
|
+
|
|
199
|
+
Sometimes `media.filename` could not be obtained, in which case it will be replaced with `media.id`, unless it is already present in the format.
|
|
200
|
+
|
|
201
|
+
Characters enclosed in square brackets followed by a question mark denote conditional separators. If the value of a field could not be obtained or is empty, the conditional separator immediately adjacent to it will be omitted from the name.
|
|
202
|
+
|
|
203
|
+
Default: '{media.filename}'</br>
|
|
204
|
+
Fallback: '{media.type}-{media.id}'
|
|
205
|
+
|
|
206
|
+
### Logger
|
|
207
|
+
|
|
208
|
+
Logging is optional, but provides useful information about the download process. You can implement your own logger by extending the `Logger` abstract class:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
import { Logger } from 'patreon-dl';
|
|
212
|
+
|
|
213
|
+
class MyLogger extends Logger {
|
|
214
|
+
|
|
215
|
+
log(entry) {
|
|
216
|
+
// Do something with log entry
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Called when downloader ends, so you can
|
|
220
|
+
// clean up the logger process if necessary.
|
|
221
|
+
end() {
|
|
222
|
+
// This is not an abstract function, so you don't have to
|
|
223
|
+
// implement it if there is no action to be taken here. Default is
|
|
224
|
+
// to resolve right away.
|
|
225
|
+
return Promise.resolve();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Each entry passed to `log()` is an object with the following properties:
|
|
231
|
+
- `level`: `info`, `debug`, `warn` or `error`, indicating the severity of the log message.
|
|
232
|
+
- `originator`: (string or `undefined`) where the message is coming from.
|
|
233
|
+
- `message`: array of elements comprising the message. An element can be anything such as a string, Error or object.
|
|
234
|
+
|
|
235
|
+
#### Built-in loggers
|
|
236
|
+
|
|
237
|
+
The `patreon-dl` library comes with the following `Logger` implementations that you may utilize:
|
|
238
|
+
|
|
239
|
+
- `ConsoleLogger`
|
|
240
|
+
|
|
241
|
+
Outputs messages to the console:
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
import { ConsoleLogger } from 'patreon-dl';
|
|
245
|
+
|
|
246
|
+
const myLogger = new ConsoleLogger([options]);
|
|
247
|
+
|
|
248
|
+
const downloader = await PatreonDownloader.getInstance(url, {
|
|
249
|
+
...
|
|
250
|
+
logger: myLogger
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`options`: (object)
|
|
256
|
+
|
|
257
|
+
| Option | Description |
|
|
258
|
+
|---------------|------------------------------------------------|
|
|
259
|
+
| `enabled` | Whether to enable this logger. Default: `true` |
|
|
260
|
+
| `logLevel` | <p>`info`, `debug`, `warn` or `error`. Default: `info`</p><p>Output messages up to the specified severity level.</p> |
|
|
261
|
+
| `include` | What to include in log messages: (object)<ul><li>`dateTime`: show date / time of log messages. Default: `true`</li><li>`level`: show the severity level. Default: `true`</li><li>`originator`: show where the messsage came from. Default: `true`</li><li>`errorStack`: for Errors, whether to show the full error stack. Default: `false`</li></ul> |
|
|
262
|
+
| `dateTimeFormat` | <p>The pattern to format data-time strings, when `include.dateTime` is `true`.</p><p>Date-time formatting is provided by [dateformat](https://github.com/felixge/node-dateformat) library. Refer to the README of that project for pattern rules.</p><p>Default: 'mmm dd HH:MM:ss'</p> |
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
- `FileLogger`
|
|
266
|
+
|
|
267
|
+
Like `ConsoleLogger`, but writes messages to file.
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
import { FileLogger } from 'patreon-dl';
|
|
271
|
+
|
|
272
|
+
const myLogger = new FileLogger(init, [options]);
|
|
273
|
+
|
|
274
|
+
const downloader = await PatreonDownloader.getInstance(url, {
|
|
275
|
+
...
|
|
276
|
+
logger: myLogger
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
`init`: values that determine the name of the log file (object)
|
|
281
|
+
|
|
282
|
+
| Property | Description |
|
|
283
|
+
|----------------|-----------------------------------------------|
|
|
284
|
+
| `targetURL` | The url passed to `PatreonDownloader.getInstance()` |
|
|
285
|
+
| `outDir` | Value of `outDir` specified in `PatreonDownloader.getInstance()` options, or `undefined` if none specified (in which case defaults to current working directory). |
|
|
286
|
+
| `date` | <p>(*optional*) `Date` instance representing the creation date / time of the logger. Default: current date-time</p><p>You might want to provide this if you are creating multiple `FileLogger` instances and filenames are to be formatted with the date, otherwise the date-time part of the filenames might have different values.</p>|
|
|
287
|
+
|
|
288
|
+
`options`: all `ConsoleLogger` options plus the following:
|
|
289
|
+
|
|
290
|
+
| Option | Description |
|
|
291
|
+
|----------------|-----------------------------------------------|
|
|
292
|
+
| `logDir` | <p>Path to directory of the log file.</p><p>The path can be a string pattern consisting of the following fields enclosed in curly braces:<ul><li>`out.dir`: value of `outDir` provided in `init` (or the default current working directory if none provided).</li><li>`target.url.path`: the pathname of `targetURL` provided in `init`, sanitized as necessary.</li><li>`datetime.<date-time format>`: the date-time of logger creation, as represented by `date` in `init` and formatted according to `<date-time format>` (using pattern rules defined by the [dateformat](https://github.com/felixge/node-dateformat) library).</li></ul></p> |
|
|
293
|
+
| `logFilename` | <p>Name of the log file.</p><p>The path can be a string pattern consisting of the following fields enclosed in curly braces:<ul><li>`target.url.path`: the pathname of `targetURL` provided in `init`, sanitized as necessary.</li><li>`datetime.<date-time format>`: the date-time of logger creation, as represented by `date` in `init` and formatted according to `<date-time format>` (using pattern rules defined by the [dateformat](https://github.com/felixge/node-dateformat) library).</li></ul></p><p>Default: '{datetime.yyyymmdd}-{log.level}.log'</p> |
|
|
294
|
+
| `fileExistsAction` | <p>What to do if log file already exists? One of the following values: <ul><li>`append`: append logs to existing file</li><li>`overwrite`: overwrite the existing file</li></ul></p><p>Default: `append`</p> |
|
|
295
|
+
|
|
296
|
+
- `ChainLogger`
|
|
297
|
+
|
|
298
|
+
Combines multiple loggers into one single logger.
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
import { ConsoleLogger, FileLogger, ChainLogger } from 'patreon-dl';
|
|
302
|
+
|
|
303
|
+
const consoleLogger = new ConsoleLogger(...);
|
|
304
|
+
const fileLogger = new FileLogger(...);
|
|
305
|
+
const chainLogger = new ChainLogger([ consoleLogger, fileLogger ]);
|
|
306
|
+
|
|
307
|
+
const downloader = await PatreonDownloader.getInstance(url, {
|
|
308
|
+
...
|
|
309
|
+
logger: chainLogger
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Aborting
|
|
314
|
+
|
|
315
|
+
To prematurely end a download process, use `AbortController` to send an abort signal to the downloader instance.
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
const downloader = await PatreonDownloader.getInstance(...);
|
|
319
|
+
const abortController = new AbortController();
|
|
320
|
+
downloader.start({
|
|
321
|
+
signal: abortController.signal
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
...
|
|
325
|
+
|
|
326
|
+
abortController.abort();
|
|
327
|
+
|
|
328
|
+
// Downloader aborts current and pending tasks, then ends.
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Workflow and Events
|
|
332
|
+
|
|
333
|
+
#### Workflow
|
|
334
|
+
|
|
335
|
+
1. Downloader analyzes given URL and determines what targets to fetch.
|
|
336
|
+
2. Downloader begins fetching data from Patreon servers. Emits `fetchBegin` event.
|
|
337
|
+
2. Downloader obtains the target(s) from the fetched data for downloading.
|
|
338
|
+
3. For each target (which can be a campaign, product or post):
|
|
339
|
+
1. Downloader emits `targetBegin` event.
|
|
340
|
+
2. Downloader determines whether the target needs to be downloaded, based on downloader configuration and target info such as accessibility.
|
|
341
|
+
- If target is to be skipped, downloader emits `targetEnd` event with `isSkipped: true`. It then proceeds to the next target, if any.
|
|
342
|
+
3. If target is to be downloaded, downloader saves target info (subject to downloader configuration), and emits `phaseBegin` event with `phase: saveInfo`. When done, downloader emits `phaseEnd` event.
|
|
343
|
+
3. Downloader begins saving media belonging to target (again, subject to downloader configuration). Emits `phaseBegin` event with `phase: saveMedia`.
|
|
344
|
+
1. Downloader saves files that do not need to be downloaded, e.g. embedded video / link info.
|
|
345
|
+
2. Downloader proceeds to download files (images, videos, audio, attachments, etc.) belonging to the target in batches. For each batch, downloader emits `phaseBegin` event with `phase: batchDownload`. When done, downloader emits `phaseEnd` event with `phase: batchDownload`.
|
|
346
|
+
- In this `phaseBegin` event, you can attach listeners to the download batch to monitor events for each download. See Download Task Batch.
|
|
347
|
+
4. Downloader emits `phaseEnd` event with `phase: saveMedia`.
|
|
348
|
+
5. Downloader emits `targetEnd` event with `isSkipped: false`, and proceeds to the next target.
|
|
349
|
+
4. When there are no more targets to be processed, or a fatal error occurred, downloader ends with `end` event.
|
|
350
|
+
|
|
351
|
+
#### Events
|
|
352
|
+
|
|
353
|
+
```
|
|
354
|
+
const downloader = await PatreonDownloader.getInstance(...);
|
|
355
|
+
|
|
356
|
+
downloader.on('fetchBegin', (payload) => {
|
|
357
|
+
...
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
downloader.start();
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Each event emitted by a `PatreonDownloader` instance has a payload, which is an object with properties containing information about the event.
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
| Event | Description |
|
|
367
|
+
|---------------|-----------------------------------------------|
|
|
368
|
+
| `fetchBegin` | <p>Emitted when downloader begins fetching data about target(s).<p><p>Payload properties:<ul><li>`targetType`: the type of target being fetched; one of `product`, `post` or `post`.</li></ul></p> |
|
|
369
|
+
| `targetBegin` | <p>Emitted when downloader begins processing a target.</p><p>Payload properties:<ul><li>`target`: the target being processed; one of [Campaign](./docs/api/interfaces/Campaign.md), [Product](./docs/api/interfaces/Product.md) or [Post](./docs/api/interfaces/Post.md).</li></ul></p> |
|
|
370
|
+
| `targetEnd` | <p>Emitted when downloader is done processing a target.</p><p>Payload properties:<ul><li>`target`: the target processed; one of [Campaign](./docs/api/interfaces/Campaign.md), [Product](./docs/api/interfaces/Product.md) or [Post](./docs/api/interfaces/Post.md).</li><li>`isSkipped`: whether target was skipped.</li></ul></p><p>If `isSkipped` is `true`, the following additional properties are available:<ul><li>`skipReason`: the reason for skipping the target; one of the following enums:<ul><li>`TargetSkipReason.Inaccessible`</li><li>`TargetSkipReason.AlreadyDownloaded`</li></ul></li><li>`skipMessage`: description of the skip reason.</li></ul></p> |
|
|
371
|
+
| `phaseBegin` | <p>Emitted when downloader begins a phase in the processing of a target.</p><p>Payload properties:<ul><li>`target`: the subject target of the phase; one of [Campaign](./docs/api/interfaces/Campaign.md), [Product](./docs/api/interfaces/Product.md) or [Post](./docs/api/interfaces/Post.md).</li><li>`phase`: the phase that is about to begin; one of `saveInfo` or `batchDownload`.</li></ul></p><p>If `phase` is `batchDownload`, the following additional property is available:<ul><li>`batch`: an object representing the batch of downloads to be executed by the downloader. For monitoring downloads in the batch, see Download Task Batch.</li></ul></p> |
|
|
372
|
+
| `phaseEnd` | <p>Emitted when a phase ends for a target.</p><p>Payload properties:<ul><li>`target`: the subject target of the phase; one of [Campaign](./docs/api/interfaces/Campaign.md), [Product](./docs/api/interfaces/Product.md) or [Post](./docs/api/interfaces/Post.md).</li><li>`phase`: the phase that has ended; one of `saveInfo` or `batchDownload`.</li></ul></p> |
|
|
373
|
+
| `end` | <p>Emitted when downloader ends.</p><p>Payload properties:<ul><li>`aborted`: boolean indicating whether the downloader is ending because of an abort request</li><li>`error`: if downloader ends because of an error, then `error` will be the captured error. Note that `error` is not necessarily an `Error` object; it can be anything other than `undefined`.</li></ul></p> |
|
|
374
|
+
|
|
375
|
+
### Download Task Batch
|
|
376
|
+
|
|
377
|
+
Files are downloaded in batches. Each batch is provided in the payload of `phaseBegin` event with `phase: batchDownload`. You can monitor events of individual downloads in the batch as follows:
|
|
378
|
+
|
|
379
|
+
```
|
|
380
|
+
downloader.on('phaseBegin', (payload) => {
|
|
381
|
+
if (payload.phase === 'batchDownload') {
|
|
382
|
+
const batch = payload.batch;
|
|
383
|
+
batch.on(event, listener);
|
|
384
|
+
}
|
|
385
|
+
})
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Note that you don't have to remove listeners yourself. They will be removed once the batch ends and is destroyed by the downloader.
|
|
389
|
+
|
|
390
|
+
#### Download Task
|
|
391
|
+
|
|
392
|
+
Each download task in a batch is represented by an object with the following properties:
|
|
393
|
+
|
|
394
|
+
| Property | Description |
|
|
395
|
+
|---------------|---------------------------------------|
|
|
396
|
+
| `id` | ID assigned to the task. |
|
|
397
|
+
| `src` | The source of the download; URL or otherwise file path if downloading video from a previously-downloaded `m3u8` playlist. |
|
|
398
|
+
| `srcEntity` | The [Downloadable](./docs/api/README.md#downloadable) item from which the download task was created. |
|
|
399
|
+
| `retryCount` | The current retry count if download failed previously. |
|
|
400
|
+
| `resolvedDestFilename` | The resolved destination filename of the download, or `null` if it has not yet been resolved. |
|
|
401
|
+
| `resolvedDestFilename` | The resolved destination file path of the download, or `null` if it has not yet been reoslved. |
|
|
402
|
+
| `getProgress()` | Function that returns the download progress. |
|
|
403
|
+
|
|
404
|
+
#### Events
|
|
405
|
+
|
|
406
|
+
Each event emitted by a download task batch has a payload, which is an object with properties containing information about the event.
|
|
407
|
+
|
|
408
|
+
| Event | Description |
|
|
409
|
+
|---------------|---------------------------------------|
|
|
410
|
+
| `taskStart` | <p>Emitted when a download starts.</p><p>Payload properties:<ul><li>`task`: the download task</li></ul></p> |
|
|
411
|
+
| `taskProgress`| <p>Emitted when a download progress is updated.</p><p>Payload properties:<ul><li>`task`: the download task</li><li>`progress`: (object)<ul><li>`destFilename`: the destination filename of the download</li><li>`destFilePath`: the destination file path of the download</li><li>`lengthUnit`: the unit measuring the progress. Generally, it would be 'byte', but for videos the unit would be 'second'.</li><li>`length`: length downloaded, measured in `lengthUnit`.</li><li>`percent`: percent downloaded</li><li>`sizeDownloaded`: size of file downloaded (kb)</li><li>`speed`: download speed (kb/s)</li></ul></li></ul></p> |
|
|
412
|
+
| `taskComplete` | <p>Emitted when a download is complete.</p><p>Payload properties:<ul><li>`task`: the download task</li></ul></p> |
|
|
413
|
+
| `taskError` | <p>Emitted when a download error occurs.</p><p>Payload properties:<ul><li>`error`: (object)<ul><li>`task`: the download task</li><li>`cause`: `Error` object or `undefined`</li></ul></li><li>`willRetry`: whether the download will be reattempted</li></ul></p> |
|
|
414
|
+
| `taskAbort` | <p>Emitted when a download is aborted.</p><p>Payload properties:<ul><li>`task`: the download task</li></ul></p> |
|
|
415
|
+
| `taskSkip` | <p>Emitted when a download is skipped.</p><p>Payload properties:<ul><li>`task`: the download task</li><li>`reason`: (object)<ul><li>`name`: `destFileExists` or `other`</li><li>`message`: string indicating the skip reason</li></ul></li></ul></p><p>If `reason.name` is `destFileExists`, `reason` will also contain the following property:<ul><li>`existingDestFilePath`: the existing file path that is causing the download to skip</li></ul></p> |
|
|
416
|
+
| `taskSpawn` | <p>Emitted when a download task is spawned from another task.</p><p>Payload properties:<ul><li>`origin`: the original download task</li><li>`spawn`: the spawned download task</li></ul></p> |
|
|
417
|
+
| `complete` | <p>Emitted when the batch is complete and there are no more downloads pending.</p><p>Payload properties: *none*</p> |
|
|
418
|
+
|
|
419
|
+
## Changelog
|
|
420
|
+
|
|
421
|
+
v1.0.0
|
|
422
|
+
- Initial release
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CLIOptionParserEntry } from './CLIOptions.js';
|
|
2
|
+
export default class CLIOptionValidator {
|
|
3
|
+
#private;
|
|
4
|
+
static validateRequired(entry?: CLIOptionParserEntry, errMsg?: string): string;
|
|
5
|
+
static validateString<T extends string[]>(entry?: CLIOptionParserEntry, ...match: T): T[number] | undefined;
|
|
6
|
+
static validateBoolean(entry?: CLIOptionParserEntry): boolean | undefined;
|
|
7
|
+
static validateNumber(entry?: CLIOptionParserEntry, min?: number, max?: number): number | undefined;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=CLIOptionValidator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CLIOptionValidator.d.ts","sourceRoot":"","sources":["../../src/cli/CLIOptionValidator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAOvD,MAAM,CAAC,OAAO,OAAO,kBAAkB;;IAErC,MAAM,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,oBAAoB,EAAE,MAAM,CAAC,EAAE,MAAM;IAarE,MAAM,CAAC,cAAc,CAAC,CAAC,SAAS,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,oBAAoB,EAAE,GAAG,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,SAAS;IAW3G,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,oBAAoB;IA0BnD,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,oBAAoB,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM;CA0B/E"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
+
};
|
|
6
|
+
var _a, _CLIOptionValidator_logEntryKey;
|
|
7
|
+
const CLI_OPTION_SRC_NAME = {
|
|
8
|
+
cli: 'Command-line',
|
|
9
|
+
cfg: 'Config file'
|
|
10
|
+
};
|
|
11
|
+
export default class CLIOptionValidator {
|
|
12
|
+
static validateRequired(entry, errMsg) {
|
|
13
|
+
if (entry && entry.value) {
|
|
14
|
+
return entry.value;
|
|
15
|
+
}
|
|
16
|
+
if (errMsg) {
|
|
17
|
+
throw Error(errMsg);
|
|
18
|
+
}
|
|
19
|
+
if (entry) {
|
|
20
|
+
throw Error(`${__classPrivateFieldGet(this, _a, "m", _CLIOptionValidator_logEntryKey).call(this, entry)} requires a value`);
|
|
21
|
+
}
|
|
22
|
+
throw Error('A required option missing');
|
|
23
|
+
}
|
|
24
|
+
static validateString(entry, ...match) {
|
|
25
|
+
if (!entry) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const value = entry.value || undefined;
|
|
29
|
+
if (match.length > 0 && value && !match.includes(value)) {
|
|
30
|
+
throw Error(`${__classPrivateFieldGet(this, _a, "m", _CLIOptionValidator_logEntryKey).call(this, entry)} must be one of ${match.map((m) => `'${m}'`).join(', ')}`);
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
static validateBoolean(entry) {
|
|
35
|
+
if (!entry) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const value = entry.value || undefined;
|
|
39
|
+
const trueValues = ['yes', '1', ' true'];
|
|
40
|
+
const falseValues = ['no', '0', 'false'];
|
|
41
|
+
let sanitized;
|
|
42
|
+
if (value) {
|
|
43
|
+
if (trueValues.includes(value.toLowerCase())) {
|
|
44
|
+
sanitized = true;
|
|
45
|
+
}
|
|
46
|
+
else if (falseValues.includes(value.toLowerCase())) {
|
|
47
|
+
sanitized = false;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
const allowedValues = [...trueValues, ...falseValues];
|
|
51
|
+
throw Error(`${__classPrivateFieldGet(this, _a, "m", _CLIOptionValidator_logEntryKey).call(this, entry)} must be one of ${allowedValues.map((m) => `'${m}'`).join(', ')}; currently '${value}'`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
sanitized = undefined;
|
|
56
|
+
}
|
|
57
|
+
return sanitized;
|
|
58
|
+
}
|
|
59
|
+
static validateNumber(entry, min, max) {
|
|
60
|
+
if (!entry) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const value = entry.value || undefined;
|
|
64
|
+
const sanitized = value ? parseInt(value, 10) : undefined;
|
|
65
|
+
if (sanitized !== undefined) {
|
|
66
|
+
if (isNaN(sanitized)) {
|
|
67
|
+
throw Error(`${__classPrivateFieldGet(this, _a, "m", _CLIOptionValidator_logEntryKey).call(this, entry)} is not a valid number`);
|
|
68
|
+
}
|
|
69
|
+
else if (min !== undefined && sanitized < min) {
|
|
70
|
+
throw Error(`${__classPrivateFieldGet(this, _a, "m", _CLIOptionValidator_logEntryKey).call(this, entry)} must not be less than ${min}`);
|
|
71
|
+
}
|
|
72
|
+
else if (max !== undefined && sanitized > max) {
|
|
73
|
+
throw Error(`${__classPrivateFieldGet(this, _a, "m", _CLIOptionValidator_logEntryKey).call(this, entry)} must not be greater than ${max}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return sanitized;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
_a = CLIOptionValidator, _CLIOptionValidator_logEntryKey = function _CLIOptionValidator_logEntryKey(entry) {
|
|
80
|
+
const keyStr = entry.src === 'cli' ?
|
|
81
|
+
entry.key :
|
|
82
|
+
`[${entry.section}]->${entry.key}`;
|
|
83
|
+
return `${CLI_OPTION_SRC_NAME[entry.src]} option '${keyStr}'`;
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=CLIOptionValidator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CLIOptionValidator.js","sourceRoot":"","sources":["../../src/cli/CLIOptionValidator.ts"],"names":[],"mappings":";;;;;;AAEA,MAAM,mBAAmB,GAAG;IAC1B,GAAG,EAAE,cAAc;IACnB,GAAG,EAAE,aAAa;CACnB,CAAC;AAEF,MAAM,CAAC,OAAO,OAAO,kBAAkB;IAErC,MAAM,CAAC,gBAAgB,CAAC,KAA4B,EAAE,MAAe;QACnE,IAAI,KAAK,IAAI,KAAK,CAAC,KAAK,EAAE;YACxB,OAAO,KAAK,CAAC,KAAK,CAAC;SACpB;QACD,IAAI,MAAM,EAAE;YACV,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC;SACrB;QACD,IAAI,KAAK,EAAE;YACT,MAAM,KAAK,CAAC,GAAG,uBAAA,IAAI,2CAAa,MAAjB,IAAI,EAAc,KAAK,CAAC,mBAAmB,CAAC,CAAC;SAC7D;QACD,MAAM,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,CAAC,cAAc,CAAqB,KAA4B,EAAE,GAAG,KAAQ;QACjF,IAAI,CAAC,KAAK,EAAE;YACV,OAAO,SAAS,CAAC;SAClB;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,SAAS,CAAC;QACvC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE;YACvD,MAAM,KAAK,CAAC,GAAG,uBAAA,IAAI,2CAAa,MAAjB,IAAI,EAAc,KAAK,CAAC,mBAAmB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;SACpG;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,CAAC,eAAe,CAAC,KAA4B;QACjD,IAAI,CAAC,KAAK,EAAE;YACV,OAAO,SAAS,CAAC;SAClB;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,SAAS,CAAC;QACvC,MAAM,UAAU,GAAG,CAAE,KAAK,EAAE,GAAG,EAAE,OAAO,CAAE,CAAC;QAC3C,MAAM,WAAW,GAAG,CAAE,IAAI,EAAE,GAAG,EAAE,OAAO,CAAE,CAAC;QAC3C,IAAI,SAA8B,CAAC;QACnC,IAAI,KAAK,EAAE;YACT,IAAI,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,EAAE;gBAC5C,SAAS,GAAG,IAAI,CAAC;aAClB;iBACI,IAAI,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,EAAE;gBAClD,SAAS,GAAG,KAAK,CAAC;aACnB;iBACI;gBACH,MAAM,aAAa,GAAG,CAAE,GAAG,UAAU,EAAE,GAAG,WAAW,CAAE,CAAC;gBACxD,MAAM,KAAK,CAAC,GAAG,uBAAA,IAAI,2CAAa,MAAjB,IAAI,EAAc,KAAK,CAAC,mBAAmB,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,KAAK,GAAG,CAAC,CAAC;aAClI;SACF;aACI;YACH,SAAS,GAAG,SAAS,CAAC;SACvB;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,CAAC,cAAc,CAAC,KAA4B,EAAE,GAAY,EAAE,GAAY;QAC5E,IAAI,CAAC,KAAK,EAAE;YACV,OAAO,SAAS,CAAC;SAClB;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,SAAS,CAAC;QACvC,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC1D,IAAI,SAAS,KAAK,SAAS,EAAE;YAC3B,IAAI,KAAK,CAAC,SAAS,CAAC,EAAE;gBACpB,MAAM,KAAK,CAAC,GAAG,uBAAA,IAAI,2CAAa,MAAjB,IAAI,EAAc,KAAK,CAAC,wBAAwB,CAAC,CAAC;aAClE;iBACI,IAAI,GAAG,KAAK,SAAS,IAAI,SAAS,GAAG,GAAG,EAAE;gBAC7C,MAAM,KAAK,CAAC,GAAG,uBAAA,IAAI,2CAAa,MAAjB,IAAI,EAAc,KAAK,CAAC,0BAA0B,GAAG,EAAE,CAAC,CAAC;aACzE;iBACI,IAAI,GAAG,KAAK,SAAS,IAAI,SAAS,GAAG,GAAG,EAAE;gBAC7C,MAAM,KAAK,CAAC,GAAG,uBAAA,IAAI,2CAAa,MAAjB,IAAI,EAAc,KAAK,CAAC,6BAA6B,GAAG,EAAE,CAAC,CAAC;aAC5E;SACF;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;CAQF;oGANqB,KAA2B;IAC7C,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC;QAClC,KAAK,CAAC,GAAG,CAAC,CAAC;QACX,IAAI,KAAK,CAAC,OAAO,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC;IACrC,OAAO,GAAG,mBAAmB,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,MAAM,GAAG,CAAC;AAChE,CAAC","sourcesContent":["import { CLIOptionParserEntry } from './CLIOptions.js';\n\nconst CLI_OPTION_SRC_NAME = {\n cli: 'Command-line',\n cfg: 'Config file'\n};\n\nexport default class CLIOptionValidator {\n\n static validateRequired(entry?: CLIOptionParserEntry, errMsg?: string) {\n if (entry && entry.value) {\n return entry.value;\n }\n if (errMsg) {\n throw Error(errMsg);\n }\n if (entry) {\n throw Error(`${this.#logEntryKey(entry)} requires a value`);\n }\n throw Error('A required option missing');\n }\n\n static validateString<T extends string[]>(entry?: CLIOptionParserEntry, ...match: T): T[number] | undefined {\n if (!entry) {\n return undefined;\n }\n const value = entry.value || undefined;\n if (match.length > 0 && value && !match.includes(value)) {\n throw Error(`${this.#logEntryKey(entry)} must be one of ${match.map((m) => `'${m}'`).join(', ')}`);\n }\n return value;\n }\n\n static validateBoolean(entry?: CLIOptionParserEntry) {\n if (!entry) {\n return undefined;\n }\n const value = entry.value || undefined;\n const trueValues = [ 'yes', '1', ' true' ];\n const falseValues = [ 'no', '0', 'false' ];\n let sanitized: boolean | undefined;\n if (value) {\n if (trueValues.includes(value.toLowerCase())) {\n sanitized = true;\n }\n else if (falseValues.includes(value.toLowerCase())) {\n sanitized = false;\n }\n else {\n const allowedValues = [ ...trueValues, ...falseValues ];\n throw Error(`${this.#logEntryKey(entry)} must be one of ${allowedValues.map((m) => `'${m}'`).join(', ')}; currently '${value}'`);\n }\n }\n else {\n sanitized = undefined;\n }\n return sanitized;\n }\n\n static validateNumber(entry?: CLIOptionParserEntry, min?: number, max?: number) {\n if (!entry) {\n return undefined;\n }\n const value = entry.value || undefined;\n const sanitized = value ? parseInt(value, 10) : undefined;\n if (sanitized !== undefined) {\n if (isNaN(sanitized)) {\n throw Error(`${this.#logEntryKey(entry)} is not a valid number`);\n }\n else if (min !== undefined && sanitized < min) {\n throw Error(`${this.#logEntryKey(entry)} must not be less than ${min}`);\n }\n else if (max !== undefined && sanitized > max) {\n throw Error(`${this.#logEntryKey(entry)} must not be greater than ${max}`);\n }\n }\n return sanitized;\n }\n\n static #logEntryKey(entry: CLIOptionParserEntry) {\n const keyStr = entry.src === 'cli' ?\n entry.key :\n `[${entry.section}]->${entry.key}`;\n return `${CLI_OPTION_SRC_NAME[entry.src]} option '${keyStr}'`;\n }\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DownloaderOptions } from '../downloaders/DownloaderOptions.js';
|
|
2
|
+
import { ConsoleLoggerOptions } from '../utils/logging/ConsoleLogger.js';
|
|
3
|
+
import { FileLoggerOptions } from '../utils/logging/FileLogger.js';
|
|
4
|
+
export interface CLIOptions extends Omit<DownloaderOptions, 'logger'> {
|
|
5
|
+
targetURL: string;
|
|
6
|
+
noPrompt: boolean;
|
|
7
|
+
consoleLogger: ConsoleLoggerOptions;
|
|
8
|
+
fileLoggers?: FileLoggerOptions[];
|
|
9
|
+
}
|
|
10
|
+
export type CLIOptionParserEntry = ({
|
|
11
|
+
src: 'cli';
|
|
12
|
+
} | {
|
|
13
|
+
src: 'cfg';
|
|
14
|
+
section: string;
|
|
15
|
+
}) & {
|
|
16
|
+
key: string;
|
|
17
|
+
value?: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function getCLIOptions(): CLIOptions;
|
|
20
|
+
//# sourceMappingURL=CLIOptions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CLIOptions.d.ts","sourceRoot":"","sources":["../../src/cli/CLIOptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAExE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAKnE,MAAM,WAAW,UAAW,SAAQ,IAAI,CAAC,iBAAiB,EAAE,QAAQ,CAAC;IACnE,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,EAAE,oBAAoB,CAAC;IACpC,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;CACnC;AAED,MAAM,MAAM,oBAAoB,GAAG,CAAC;IAClC,GAAG,EAAE,KAAK,CAAA;CACX,GAAG;IACF,GAAG,EAAE,KAAK,CAAC;IACX,OAAO,EAAE,MAAM,CAAA;CAChB,CAAC,GAAG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAA;AAED,wBAAgB,aAAa,IAAI,UAAU,CAyE1C"}
|