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
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import fse from 'fs-extra';
|
|
4
|
+
import makeDir from 'make-dir';
|
|
5
|
+
import sanitizeFilename from 'sanitize-filename';
|
|
6
|
+
import escapeStringRegexp from 'escape-string-regexp';
|
|
7
|
+
import hasha from 'hasha';
|
|
8
|
+
import FilenameFormatHelper from './FilenameFormatHelper.js';
|
|
9
|
+
const CAMPAIGN_FIXED_DIR_NAMES = {
|
|
10
|
+
INFO: 'campaign_info'
|
|
11
|
+
};
|
|
12
|
+
const PRODUCT_FIXED_DIR_NAMES = {
|
|
13
|
+
SHOP: 'shop',
|
|
14
|
+
INFO: 'product_info',
|
|
15
|
+
CONTENT_MEDIA: 'content_media',
|
|
16
|
+
PREVIEW_MEDIA: 'preview_media'
|
|
17
|
+
};
|
|
18
|
+
const POST_FIXED_DIR_NAMES = {
|
|
19
|
+
POSTS: 'posts',
|
|
20
|
+
INFO: 'post_info',
|
|
21
|
+
AUDIO: 'audio',
|
|
22
|
+
VIDEO: 'video',
|
|
23
|
+
IMAGES: 'images',
|
|
24
|
+
AUDIO_PREVIEW: 'audio_preview',
|
|
25
|
+
VIDEO_PREVIEW: 'video_preview',
|
|
26
|
+
IMAGE_PREVIEWS: 'image_previews',
|
|
27
|
+
ATTACHMENTS: 'attachments',
|
|
28
|
+
EMBED: 'embed'
|
|
29
|
+
};
|
|
30
|
+
const INTERNAL_DATA_DIR_NAME = '.patreon-dl';
|
|
31
|
+
export default class FSHelper {
|
|
32
|
+
static getCampaignDirs(campaign, config) {
|
|
33
|
+
const dirName = FilenameFormatHelper.getCampaignDirName(campaign, config.dirNameFormat.campaign);
|
|
34
|
+
const root = path.resolve(config.outDir, dirName);
|
|
35
|
+
return {
|
|
36
|
+
root,
|
|
37
|
+
info: path.resolve(root, CAMPAIGN_FIXED_DIR_NAMES.INFO)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
static getPostDirs(post, config) {
|
|
41
|
+
const dirName = FilenameFormatHelper.getContentDirName(post, config.dirNameFormat.content);
|
|
42
|
+
let postRootPath;
|
|
43
|
+
let statusCachePath;
|
|
44
|
+
if (post.campaign) {
|
|
45
|
+
const campaignRootDir = this.getCampaignDirs(post.campaign, config).root;
|
|
46
|
+
const postsDir = this.createDir(path.resolve(campaignRootDir, POST_FIXED_DIR_NAMES.POSTS));
|
|
47
|
+
postRootPath = path.resolve(postsDir, dirName);
|
|
48
|
+
statusCachePath = path.resolve(campaignRootDir, INTERNAL_DATA_DIR_NAME);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
postRootPath = path.resolve(config.outDir, dirName);
|
|
52
|
+
statusCachePath = path.resolve(postRootPath, INTERNAL_DATA_DIR_NAME);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
root: postRootPath,
|
|
56
|
+
info: path.resolve(postRootPath, POST_FIXED_DIR_NAMES.INFO),
|
|
57
|
+
audio: path.resolve(postRootPath, POST_FIXED_DIR_NAMES.AUDIO),
|
|
58
|
+
video: path.resolve(postRootPath, POST_FIXED_DIR_NAMES.VIDEO),
|
|
59
|
+
images: path.resolve(postRootPath, POST_FIXED_DIR_NAMES.IMAGES),
|
|
60
|
+
audioPreview: path.resolve(postRootPath, POST_FIXED_DIR_NAMES.AUDIO_PREVIEW),
|
|
61
|
+
videoPreview: path.resolve(postRootPath, POST_FIXED_DIR_NAMES.VIDEO_PREVIEW),
|
|
62
|
+
imagePreviews: path.resolve(postRootPath, POST_FIXED_DIR_NAMES.IMAGE_PREVIEWS),
|
|
63
|
+
attachments: path.resolve(postRootPath, POST_FIXED_DIR_NAMES.ATTACHMENTS),
|
|
64
|
+
embed: path.resolve(postRootPath, POST_FIXED_DIR_NAMES.EMBED),
|
|
65
|
+
statusCache: statusCachePath
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
static getProductDirs(product, config) {
|
|
69
|
+
const dirName = FilenameFormatHelper.getContentDirName(product, config.dirNameFormat.content);
|
|
70
|
+
let productRootPath;
|
|
71
|
+
let statusCachePath;
|
|
72
|
+
if (product.campaign) {
|
|
73
|
+
const campaignRootDir = this.getCampaignDirs(product.campaign, config).root;
|
|
74
|
+
const shopDir = this.createDir(path.resolve(campaignRootDir, PRODUCT_FIXED_DIR_NAMES.SHOP));
|
|
75
|
+
productRootPath = path.resolve(shopDir, dirName);
|
|
76
|
+
statusCachePath = path.resolve(campaignRootDir, INTERNAL_DATA_DIR_NAME);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
productRootPath = path.resolve(config.outDir, dirName);
|
|
80
|
+
statusCachePath = path.resolve(productRootPath, INTERNAL_DATA_DIR_NAME);
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
root: productRootPath,
|
|
84
|
+
info: path.resolve(productRootPath, PRODUCT_FIXED_DIR_NAMES.INFO),
|
|
85
|
+
contentMedia: path.resolve(productRootPath, PRODUCT_FIXED_DIR_NAMES.CONTENT_MEDIA),
|
|
86
|
+
previewMedia: path.resolve(productRootPath, PRODUCT_FIXED_DIR_NAMES.PREVIEW_MEDIA),
|
|
87
|
+
statusCache: statusCachePath
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
static createDir(dir, parents = true) {
|
|
91
|
+
if (fs.existsSync(dir)) {
|
|
92
|
+
if (!fs.lstatSync(dir).isDirectory()) {
|
|
93
|
+
throw Error(`"${dir}" exists but is not a directory`);
|
|
94
|
+
}
|
|
95
|
+
return dir;
|
|
96
|
+
}
|
|
97
|
+
if (!parents) {
|
|
98
|
+
fs.mkdirSync(dir);
|
|
99
|
+
return dir;
|
|
100
|
+
}
|
|
101
|
+
return makeDir.sync(dir);
|
|
102
|
+
}
|
|
103
|
+
static checkFileExistsAndIncrement(file) {
|
|
104
|
+
const { name, base, dir, ext } = path.parse(file);
|
|
105
|
+
// Regex to match filename with increment: 'filename (number).ext'
|
|
106
|
+
const regex = new RegExp(`^${escapeStringRegexp(name)} \\((\\d+?)\\)${escapeStringRegexp(ext)}$`, 'g');
|
|
107
|
+
const files = fs.readdirSync(dir);
|
|
108
|
+
let currentLargestInc = -1;
|
|
109
|
+
let currentLargestIncFilename = base;
|
|
110
|
+
for (const file of files) {
|
|
111
|
+
const match = regex.exec(file);
|
|
112
|
+
if (match && match[1] !== undefined && Number(match[1]) > currentLargestInc) {
|
|
113
|
+
currentLargestInc = Number(match[1]);
|
|
114
|
+
currentLargestIncFilename = file;
|
|
115
|
+
}
|
|
116
|
+
regex.lastIndex = 0;
|
|
117
|
+
}
|
|
118
|
+
if (currentLargestInc === -1) {
|
|
119
|
+
if (!fs.existsSync(file)) {
|
|
120
|
+
const fileParts = path.parse(file);
|
|
121
|
+
return {
|
|
122
|
+
filename: fileParts.base,
|
|
123
|
+
filePath: path.resolve(file),
|
|
124
|
+
preceding: null
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
currentLargestInc = 0;
|
|
128
|
+
}
|
|
129
|
+
const _filename = `${name} (${currentLargestInc + 1})${ext}`;
|
|
130
|
+
const _filePath = path.resolve(dir, _filename);
|
|
131
|
+
return {
|
|
132
|
+
filename: _filename,
|
|
133
|
+
filePath: _filePath,
|
|
134
|
+
preceding: path.resolve(dir, currentLargestIncFilename)
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
static sanitizeFilePath(filePath) {
|
|
138
|
+
const splitted = filePath.split(path.sep);
|
|
139
|
+
const root = path.isAbsolute(filePath) ? splitted.shift() || '' : null;
|
|
140
|
+
const sanitized = splitted.map((s) => sanitizeFilename(s));
|
|
141
|
+
if (root !== null) {
|
|
142
|
+
sanitized.unshift(root);
|
|
143
|
+
}
|
|
144
|
+
return sanitized.join(path.sep);
|
|
145
|
+
}
|
|
146
|
+
static async compareFiles(f1, f2) {
|
|
147
|
+
const [checksum1, checksum2] = await Promise.all([
|
|
148
|
+
hasha.fromFile(f1, { algorithm: 'md5' }),
|
|
149
|
+
hasha.fromFile(f2, { algorithm: 'md5' })
|
|
150
|
+
]);
|
|
151
|
+
return checksum1 === checksum2;
|
|
152
|
+
}
|
|
153
|
+
static async writeTextFile(file, data, fileExistsAction) {
|
|
154
|
+
const resolvedFile = {
|
|
155
|
+
original: file,
|
|
156
|
+
final: file,
|
|
157
|
+
incrementedFrom: null
|
|
158
|
+
};
|
|
159
|
+
try {
|
|
160
|
+
if (fs.existsSync(file)) {
|
|
161
|
+
if (fileExistsAction === 'skip') {
|
|
162
|
+
return {
|
|
163
|
+
status: 'skipped',
|
|
164
|
+
message: `Destination file exists (${file})`
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (fileExistsAction === 'saveAsCopy' || fileExistsAction === 'saveAsCopyIfNewer') {
|
|
168
|
+
const checked = this.checkFileExistsAndIncrement(file);
|
|
169
|
+
resolvedFile.final = checked.filePath;
|
|
170
|
+
resolvedFile.incrementedFrom = checked.preceding;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const tmpFile = `${resolvedFile.final}.part`;
|
|
174
|
+
if (typeof data === 'object') {
|
|
175
|
+
fse.writeJsonSync(tmpFile, data, { spaces: 2 });
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
fs.writeFileSync(tmpFile, data);
|
|
179
|
+
}
|
|
180
|
+
if (fileExistsAction === 'saveAsCopyIfNewer' &&
|
|
181
|
+
resolvedFile.incrementedFrom && fs.existsSync(resolvedFile.incrementedFrom)) {
|
|
182
|
+
const filesMatch = await this.compareFiles(tmpFile, resolvedFile.incrementedFrom);
|
|
183
|
+
if (filesMatch) {
|
|
184
|
+
fs.unlinkSync(tmpFile);
|
|
185
|
+
return {
|
|
186
|
+
status: 'skipped',
|
|
187
|
+
message: `Destination file exists with same content (${resolvedFile.incrementedFrom})`
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
fs.renameSync(tmpFile, resolvedFile.final);
|
|
192
|
+
return {
|
|
193
|
+
status: 'completed',
|
|
194
|
+
filePath: resolvedFile.final
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
return {
|
|
199
|
+
status: 'error',
|
|
200
|
+
filePath: resolvedFile.final,
|
|
201
|
+
error
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
static changeFilePathExtension(filePath, extension) {
|
|
206
|
+
const filePathParts = {
|
|
207
|
+
...path.parse(filePath),
|
|
208
|
+
base: undefined,
|
|
209
|
+
ext: extension
|
|
210
|
+
};
|
|
211
|
+
return path.format(filePathParts);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=FSHelper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FSHelper.js","sourceRoot":"","sources":["../../src/utils/FSHelper.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,OAAO,MAAM,UAAU,CAAC;AAC/B,OAAO,gBAAgB,MAAM,mBAAmB,CAAC;AACjD,OAAO,kBAAkB,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,oBAAoB,MAAM,2BAA2B,CAAC;AAI7D,MAAM,wBAAwB,GAAG;IAC/B,IAAI,EAAE,eAAe;CACtB,CAAC;AAEF,MAAM,uBAAuB,GAAG;IAC9B,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,cAAc;IACpB,aAAa,EAAE,eAAe;IAC9B,aAAa,EAAE,eAAe;CAC/B,CAAC;AAEF,MAAM,oBAAoB,GAAG;IAC3B,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,WAAW;IACjB,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,MAAM,EAAE,QAAQ;IAChB,aAAa,EAAE,eAAe;IAC9B,aAAa,EAAE,eAAe;IAC9B,cAAc,EAAE,gBAAgB;IAChC,WAAW,EAAE,aAAa;IAC1B,KAAK,EAAE,OAAO;CACf,CAAC;AAEF,MAAM,sBAAsB,GAAG,aAAa,CAAC;AAc7C,MAAM,CAAC,OAAO,OAAO,QAAQ;IAE3B,MAAM,CAAC,eAAe,CAAC,QAAkB,EAAE,MAA6B;QACtE,MAAM,OAAO,GAAG,oBAAoB,CAAC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QACjG,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClD,OAAO;YACL,IAAI;YACJ,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,wBAAwB,CAAC,IAAI,CAAC;SACxD,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,WAAW,CAAC,IAAU,EAAE,MAAgC;QAC7D,MAAM,OAAO,GAAG,oBAAoB,CAAC,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC3F,IAAI,YAAoB,CAAC;QACzB,IAAI,eAAuB,CAAC;QAC5B,IAAI,IAAI,CAAC,QAAQ,EAAE;YACjB,MAAM,eAAe,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC;YACzE,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC;YAC3F,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAC/C,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAAC;SACzE;aACI;YACH,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YACpD,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,sBAAsB,CAAC,CAAC;SACtE;QACD,OAAO;YACL,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,oBAAoB,CAAC,IAAI,CAAC;YAC3D,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,oBAAoB,CAAC,KAAK,CAAC;YAC7D,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,oBAAoB,CAAC,KAAK,CAAC;YAC7D,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,oBAAoB,CAAC,MAAM,CAAC;YAC/D,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,oBAAoB,CAAC,aAAa,CAAC;YAC5E,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,oBAAoB,CAAC,aAAa,CAAC;YAC5E,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,oBAAoB,CAAC,cAAc,CAAC;YAC9E,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,oBAAoB,CAAC,WAAW,CAAC;YACzE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,oBAAoB,CAAC,KAAK,CAAC;YAC7D,WAAW,EAAE,eAAe;SAC7B,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,cAAc,CAAC,OAAgB,EAAE,MAAmC;QACzE,MAAM,OAAO,GAAG,oBAAoB,CAAC,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC9F,IAAI,eAAuB,CAAC;QAC5B,IAAI,eAAuB,CAAC;QAC5B,IAAI,OAAO,CAAC,QAAQ,EAAE;YACpB,MAAM,eAAe,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC;YAC5E,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC;YAC5F,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACjD,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAAC;SACzE;aACI;YACH,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YACvD,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAAC;SACzE;QACD,OAAO;YACL,IAAI,EAAE,eAAe;YACrB,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,uBAAuB,CAAC,IAAI,CAAC;YACjE,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,uBAAuB,CAAC,aAAa,CAAC;YAClF,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,uBAAuB,CAAC,aAAa,CAAC;YAClF,WAAW,EAAE,eAAe;SAC7B,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,SAAS,CAAC,GAAW,EAAE,OAAO,GAAG,IAAI;QAC1C,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;YACtB,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE;gBACpC,MAAM,KAAK,CAAC,IAAI,GAAG,iCAAiC,CAAC,CAAC;aACvD;YACD,OAAO,GAAG,CAAC;SACZ;QACD,IAAI,CAAC,OAAO,EAAE;YACZ,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YAClB,OAAO,GAAG,CAAC;SACZ;QAED,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAE3B,CAAC;IAED,MAAM,CAAC,2BAA2B,CAAC,IAAY;QAC7C,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAClD,kEAAkE;QAClE,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,IAAI,kBAAkB,CAAC,IAAI,CAAC,iBAAiB,kBAAkB,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACvG,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,iBAAiB,GAAG,CAAC,CAAC,CAAC;QAC3B,IAAI,yBAAyB,GAAG,IAAI,CAAC;QACrC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;YACxB,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC/B,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,iBAAiB,EAAE;gBAC3E,iBAAiB,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;gBACrC,yBAAyB,GAAG,IAAI,CAAC;aAClC;YACD,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;SACrB;QAED,IAAI,iBAAiB,KAAK,CAAC,CAAC,EAAE;YAC5B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE;gBACxB,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACnC,OAAO;oBACL,QAAQ,EAAE,SAAS,CAAC,IAAI;oBACxB,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;oBAC5B,SAAS,EAAE,IAAI;iBAChB,CAAC;aACH;YACD,iBAAiB,GAAG,CAAC,CAAC;SACvB;QAED,MAAM,SAAS,GAAG,GAAG,IAAI,KAAK,iBAAiB,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;QAC7D,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAE/C,OAAO;YACL,QAAQ,EAAE,SAAS;YACnB,QAAQ,EAAE,SAAS;YACnB,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,yBAAyB,CAAC;SACxD,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,gBAAgB,CAAC,QAAgB;QACtC,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACvE,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3D,IAAI,IAAI,KAAK,IAAI,EAAE;YACjB,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;SACzB;QACD,OAAO,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,EAAU,EAAE,EAAU;QAC9C,MAAM,CAAE,SAAS,EAAE,SAAS,CAAE,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACjD,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;YACxC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;SACzC,CAAC,CAAC;QACH,OAAO,SAAS,KAAK,SAAS,CAAC;IACjC,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,IAAY,EAAE,IAAqB,EAAE,gBAAkC;QAChG,MAAM,YAAY,GAAG;YACnB,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,IAAI;YACX,eAAe,EAAE,IAAqB;SACvC,CAAC;QACF,IAAI;YACF,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE;gBACvB,IAAI,gBAAgB,KAAK,MAAM,EAAE;oBAC/B,OAAO;wBACL,MAAM,EAAE,SAAS;wBACjB,OAAO,EAAE,4BAA4B,IAAI,GAAG;qBAC7C,CAAC;iBACH;gBACD,IAAI,gBAAgB,KAAK,YAAY,IAAI,gBAAgB,KAAK,mBAAmB,EAAE;oBACjF,MAAM,OAAO,GAAG,IAAI,CAAC,2BAA2B,CAAC,IAAI,CAAC,CAAC;oBACvD,YAAY,CAAC,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC;oBACtC,YAAY,CAAC,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC;iBAClD;aACF;YACD,MAAM,OAAO,GAAG,GAAG,YAAY,CAAC,KAAK,OAAO,CAAC;YAC7C,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE;gBAC5B,GAAG,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;aACjD;iBACI;gBACH,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;aACjC;YACD,IAAI,gBAAgB,KAAK,mBAAmB;gBAC1C,YAAY,CAAC,eAAe,IAAI,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,eAAe,CAAC,EAAE;gBAE7E,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,YAAY,CAAC,eAAe,CAAC,CAAC;gBAClF,IAAI,UAAU,EAAE;oBACd,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;oBACvB,OAAO;wBACL,MAAM,EAAE,SAAS;wBACjB,OAAO,EAAE,8CAA8C,YAAY,CAAC,eAAe,GAAG;qBACvF,CAAC;iBACH;aACF;YAED,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC;YAC3C,OAAO;gBACL,MAAM,EAAE,WAAW;gBACnB,QAAQ,EAAE,YAAY,CAAC,KAAK;aAC7B,CAAC;SACH;QACD,OAAO,KAAK,EAAE;YACZ,OAAO;gBACL,MAAM,EAAE,OAAO;gBACf,QAAQ,EAAE,YAAY,CAAC,KAAK;gBAC5B,KAAK;aACN,CAAC;SACH;IACH,CAAC;IAED,MAAM,CAAC,uBAAuB,CAAC,QAAgB,EAAE,SAAuB;QACtE,MAAM,aAAa,GAAG;YACpB,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;YACvB,IAAI,EAAE,SAAS;YACf,GAAG,EAAE,SAAS;SACf,CAAC;QACF,OAAO,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IACpC,CAAC;CACF","sourcesContent":["import path from 'path';\nimport fs from 'fs';\nimport fse from 'fs-extra';\nimport makeDir from 'make-dir';\nimport sanitizeFilename from 'sanitize-filename';\nimport escapeStringRegexp from 'escape-string-regexp';\nimport hasha from 'hasha';\nimport { Product } from '../entities/Product.js';\nimport { Campaign } from '../entities/Campaign.js';\nimport { DownloaderConfig } from '../downloaders/Downloader.js';\nimport FilenameFormatHelper from './FilenameFormatHelper.js';\nimport { Post } from '../entities/Post.js';\nimport { FileExistsAction } from '../downloaders/DownloaderOptions.js';\n\nconst CAMPAIGN_FIXED_DIR_NAMES = {\n INFO: 'campaign_info'\n};\n\nconst PRODUCT_FIXED_DIR_NAMES = {\n SHOP: 'shop',\n INFO: 'product_info',\n CONTENT_MEDIA: 'content_media',\n PREVIEW_MEDIA: 'preview_media'\n};\n\nconst POST_FIXED_DIR_NAMES = {\n POSTS: 'posts',\n INFO: 'post_info',\n AUDIO: 'audio',\n VIDEO: 'video',\n IMAGES: 'images',\n AUDIO_PREVIEW: 'audio_preview',\n VIDEO_PREVIEW: 'video_preview',\n IMAGE_PREVIEWS: 'image_previews',\n ATTACHMENTS: 'attachments',\n EMBED: 'embed'\n};\n\nconst INTERNAL_DATA_DIR_NAME = '.patreon-dl';\n\nexport type WriteTextFileResult = {\n status: 'completed';\n filePath: string;\n} | {\n status: 'skipped';\n message: string;\n} | {\n status: 'error';\n filePath: string;\n error: any\n};\n\nexport default class FSHelper {\n\n static getCampaignDirs(campaign: Campaign, config: DownloaderConfig<any>) {\n const dirName = FilenameFormatHelper.getCampaignDirName(campaign, config.dirNameFormat.campaign);\n const root = path.resolve(config.outDir, dirName);\n return {\n root,\n info: path.resolve(root, CAMPAIGN_FIXED_DIR_NAMES.INFO)\n };\n }\n\n static getPostDirs(post: Post, config: DownloaderConfig<'post'>) {\n const dirName = FilenameFormatHelper.getContentDirName(post, config.dirNameFormat.content);\n let postRootPath: string;\n let statusCachePath: string;\n if (post.campaign) {\n const campaignRootDir = this.getCampaignDirs(post.campaign, config).root;\n const postsDir = this.createDir(path.resolve(campaignRootDir, POST_FIXED_DIR_NAMES.POSTS));\n postRootPath = path.resolve(postsDir, dirName);\n statusCachePath = path.resolve(campaignRootDir, INTERNAL_DATA_DIR_NAME);\n }\n else {\n postRootPath = path.resolve(config.outDir, dirName);\n statusCachePath = path.resolve(postRootPath, INTERNAL_DATA_DIR_NAME);\n }\n return {\n root: postRootPath,\n info: path.resolve(postRootPath, POST_FIXED_DIR_NAMES.INFO),\n audio: path.resolve(postRootPath, POST_FIXED_DIR_NAMES.AUDIO),\n video: path.resolve(postRootPath, POST_FIXED_DIR_NAMES.VIDEO),\n images: path.resolve(postRootPath, POST_FIXED_DIR_NAMES.IMAGES),\n audioPreview: path.resolve(postRootPath, POST_FIXED_DIR_NAMES.AUDIO_PREVIEW),\n videoPreview: path.resolve(postRootPath, POST_FIXED_DIR_NAMES.VIDEO_PREVIEW),\n imagePreviews: path.resolve(postRootPath, POST_FIXED_DIR_NAMES.IMAGE_PREVIEWS),\n attachments: path.resolve(postRootPath, POST_FIXED_DIR_NAMES.ATTACHMENTS),\n embed: path.resolve(postRootPath, POST_FIXED_DIR_NAMES.EMBED),\n statusCache: statusCachePath\n };\n }\n\n static getProductDirs(product: Product, config: DownloaderConfig<'product'>) {\n const dirName = FilenameFormatHelper.getContentDirName(product, config.dirNameFormat.content);\n let productRootPath: string;\n let statusCachePath: string;\n if (product.campaign) {\n const campaignRootDir = this.getCampaignDirs(product.campaign, config).root;\n const shopDir = this.createDir(path.resolve(campaignRootDir, PRODUCT_FIXED_DIR_NAMES.SHOP));\n productRootPath = path.resolve(shopDir, dirName);\n statusCachePath = path.resolve(campaignRootDir, INTERNAL_DATA_DIR_NAME);\n }\n else {\n productRootPath = path.resolve(config.outDir, dirName);\n statusCachePath = path.resolve(productRootPath, INTERNAL_DATA_DIR_NAME);\n }\n return {\n root: productRootPath,\n info: path.resolve(productRootPath, PRODUCT_FIXED_DIR_NAMES.INFO),\n contentMedia: path.resolve(productRootPath, PRODUCT_FIXED_DIR_NAMES.CONTENT_MEDIA),\n previewMedia: path.resolve(productRootPath, PRODUCT_FIXED_DIR_NAMES.PREVIEW_MEDIA),\n statusCache: statusCachePath\n };\n }\n\n static createDir(dir: string, parents = true) {\n if (fs.existsSync(dir)) {\n if (!fs.lstatSync(dir).isDirectory()) {\n throw Error(`\"${dir}\" exists but is not a directory`);\n }\n return dir;\n }\n if (!parents) {\n fs.mkdirSync(dir);\n return dir;\n }\n\n return makeDir.sync(dir);\n\n }\n\n static checkFileExistsAndIncrement(file: string) {\n const { name, base, dir, ext } = path.parse(file);\n // Regex to match filename with increment: 'filename (number).ext'\n const regex = new RegExp(`^${escapeStringRegexp(name)} \\\\((\\\\d+?)\\\\)${escapeStringRegexp(ext)}$`, 'g');\n const files = fs.readdirSync(dir);\n let currentLargestInc = -1;\n let currentLargestIncFilename = base;\n for (const file of files) {\n const match = regex.exec(file);\n if (match && match[1] !== undefined && Number(match[1]) > currentLargestInc) {\n currentLargestInc = Number(match[1]);\n currentLargestIncFilename = file;\n }\n regex.lastIndex = 0;\n }\n\n if (currentLargestInc === -1) {\n if (!fs.existsSync(file)) {\n const fileParts = path.parse(file);\n return {\n filename: fileParts.base,\n filePath: path.resolve(file),\n preceding: null\n };\n }\n currentLargestInc = 0;\n }\n\n const _filename = `${name} (${currentLargestInc + 1})${ext}`;\n const _filePath = path.resolve(dir, _filename);\n\n return {\n filename: _filename,\n filePath: _filePath,\n preceding: path.resolve(dir, currentLargestIncFilename)\n };\n }\n\n static sanitizeFilePath(filePath: string) {\n const splitted = filePath.split(path.sep);\n const root = path.isAbsolute(filePath) ? splitted.shift() || '' : null;\n const sanitized = splitted.map((s) => sanitizeFilename(s));\n if (root !== null) {\n sanitized.unshift(root);\n }\n return sanitized.join(path.sep);\n }\n\n static async compareFiles(f1: string, f2: string) {\n const [ checksum1, checksum2 ] = await Promise.all([\n hasha.fromFile(f1, { algorithm: 'md5' }),\n hasha.fromFile(f2, { algorithm: 'md5' })\n ]);\n return checksum1 === checksum2;\n }\n\n static async writeTextFile(file: string, data: string | object, fileExistsAction: FileExistsAction): Promise<WriteTextFileResult> {\n const resolvedFile = {\n original: file,\n final: file,\n incrementedFrom: null as string | null\n };\n try {\n if (fs.existsSync(file)) {\n if (fileExistsAction === 'skip') {\n return {\n status: 'skipped',\n message: `Destination file exists (${file})`\n };\n }\n if (fileExistsAction === 'saveAsCopy' || fileExistsAction === 'saveAsCopyIfNewer') {\n const checked = this.checkFileExistsAndIncrement(file);\n resolvedFile.final = checked.filePath;\n resolvedFile.incrementedFrom = checked.preceding;\n }\n }\n const tmpFile = `${resolvedFile.final}.part`;\n if (typeof data === 'object') {\n fse.writeJsonSync(tmpFile, data, { spaces: 2 });\n }\n else {\n fs.writeFileSync(tmpFile, data);\n }\n if (fileExistsAction === 'saveAsCopyIfNewer' &&\n resolvedFile.incrementedFrom && fs.existsSync(resolvedFile.incrementedFrom)) {\n\n const filesMatch = await this.compareFiles(tmpFile, resolvedFile.incrementedFrom);\n if (filesMatch) {\n fs.unlinkSync(tmpFile);\n return {\n status: 'skipped',\n message: `Destination file exists with same content (${resolvedFile.incrementedFrom})`\n };\n }\n }\n\n fs.renameSync(tmpFile, resolvedFile.final);\n return {\n status: 'completed',\n filePath: resolvedFile.final\n };\n }\n catch (error) {\n return {\n status: 'error',\n filePath: resolvedFile.final,\n error\n };\n }\n }\n\n static changeFilePathExtension(filePath: string, extension: `.${string}`) {\n const filePathParts = {\n ...path.parse(filePath),\n base: undefined,\n ext: extension\n };\n return path.format(filePathParts);\n }\n}\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import FetcherProgressMonitor from './FetcherProgressMonitor.js';
|
|
2
|
+
import { Downloadable } from '../entities/Downloadable.js';
|
|
3
|
+
import FilenameResolver from './FllenameResolver.js';
|
|
4
|
+
import Logger, { LogLevel } from './logging/Logger.js';
|
|
5
|
+
export interface PrepareDownloadParams<T extends Downloadable> {
|
|
6
|
+
url: string;
|
|
7
|
+
destDir: string;
|
|
8
|
+
srcEntity: T;
|
|
9
|
+
destFilenameResolver: FilenameResolver<T>;
|
|
10
|
+
signal: AbortSignal;
|
|
11
|
+
}
|
|
12
|
+
export interface StartDownloadOverrides {
|
|
13
|
+
destFilePath?: string;
|
|
14
|
+
tmpFilePath?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare class FetcherError extends Error {
|
|
17
|
+
url: string;
|
|
18
|
+
constructor(message: string, url: string);
|
|
19
|
+
}
|
|
20
|
+
export type FetcherGetType = 'html' | 'json';
|
|
21
|
+
export type FetcherGetResultOf<T extends FetcherGetType> = T extends 'html' ? string : T extends 'json' ? any : never;
|
|
22
|
+
export default class Fetcher {
|
|
23
|
+
#private;
|
|
24
|
+
name: string;
|
|
25
|
+
constructor(cookie?: string, logger?: Logger | null);
|
|
26
|
+
get<T extends FetcherGetType>(args: {
|
|
27
|
+
url: string;
|
|
28
|
+
type: T;
|
|
29
|
+
payload?: Record<string, any>;
|
|
30
|
+
maxRetries: number;
|
|
31
|
+
signal?: AbortSignal;
|
|
32
|
+
}, rt?: number): Promise<FetcherGetResultOf<T>>;
|
|
33
|
+
prepareDownload<T extends Downloadable>(params: PrepareDownloadParams<T>): Promise<{
|
|
34
|
+
destFilePath: string;
|
|
35
|
+
monitor: FetcherProgressMonitor;
|
|
36
|
+
start: (overrides?: StartDownloadOverrides) => Promise<{
|
|
37
|
+
tmpFilePath: string;
|
|
38
|
+
commit: () => void;
|
|
39
|
+
discard: () => void;
|
|
40
|
+
}>;
|
|
41
|
+
abort: () => void;
|
|
42
|
+
}>;
|
|
43
|
+
protected log(level: LogLevel, ...msg: Array<any>): void;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=Fetcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Fetcher.d.ts","sourceRoot":"","sources":["../../src/utils/Fetcher.ts"],"names":[],"mappings":"AAMA,OAAO,sBAAsB,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,gBAAgB,MAAM,uBAAuB,CAAC;AAErD,OAAO,MAAM,EAAE,EAAE,QAAQ,EAAa,MAAM,qBAAqB,CAAC;AAElE,MAAM,WAAW,qBAAqB,CAAC,CAAC,SAAS,YAAY;IAC3D,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,CAAC,CAAC;IACb,oBAAoB,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAC1C,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,YAAa,SAAQ,KAAK;IAErC,GAAG,EAAE,MAAM,CAAC;gBAEA,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;CAKzC;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC;AAC7C,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,cAAc,IACrD,CAAC,SAAS,MAAM,GAAG,MAAM,GACzB,CAAC,SAAS,MAAM,GAAG,GAAG,GACtB,KAAK,CAAC;AAIR,MAAM,CAAC,OAAO,OAAO,OAAO;;IAE1B,IAAI,SAAa;gBAKL,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAK7C,GAAG,CAAC,CAAC,SAAS,cAAc,EAAE,IAAI,EAAE;QACxC,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,CAAC,CAAC;QACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC9B,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,WAAW,CAAA;KACrB,EAAE,EAAE,SAAI,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC;IA6BpC,eAAe,CAAC,CAAC,SAAS,YAAY,EAAE,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAC;;;4BA8C/C,sBAAsB;;;;;;;IA6GrD,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC;CAGlD"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
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");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _Fetcher_instances, _Fetcher_cookie, _Fetcher_logger, _Fetcher_startDownload, _Fetcher_commitDownload, _Fetcher_cleanupDownload, _Fetcher_setHeaders, _Fetcher_assertResponseOK;
|
|
13
|
+
import progressStream from 'progress-stream';
|
|
14
|
+
import * as fs from 'fs';
|
|
15
|
+
import fetch, { AbortError, Request } from 'node-fetch';
|
|
16
|
+
import { pipeline } from 'stream/promises';
|
|
17
|
+
import { URL } from 'url';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import FetcherProgressMonitor from './FetcherProgressMonitor.js';
|
|
20
|
+
import { pickDefined } from './Misc.js';
|
|
21
|
+
import { commonLog } from './logging/Logger.js';
|
|
22
|
+
export class FetcherError extends Error {
|
|
23
|
+
constructor(message, url) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = 'FetcherError';
|
|
26
|
+
this.url = url;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0';
|
|
30
|
+
export default class Fetcher {
|
|
31
|
+
constructor(cookie, logger) {
|
|
32
|
+
_Fetcher_instances.add(this);
|
|
33
|
+
this.name = 'Fetcher';
|
|
34
|
+
_Fetcher_cookie.set(this, void 0);
|
|
35
|
+
_Fetcher_logger.set(this, void 0);
|
|
36
|
+
__classPrivateFieldSet(this, _Fetcher_cookie, cookie, "f");
|
|
37
|
+
__classPrivateFieldSet(this, _Fetcher_logger, logger, "f");
|
|
38
|
+
}
|
|
39
|
+
async get(args, rt = 0) {
|
|
40
|
+
const { url, type, payload, maxRetries, signal } = args;
|
|
41
|
+
const urlObj = new URL(url);
|
|
42
|
+
if (payload) {
|
|
43
|
+
for (const [p, v] of Object.entries(payload)) {
|
|
44
|
+
urlObj.searchParams.set(p, v);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const request = new Request(urlObj, { method: 'GET' });
|
|
48
|
+
__classPrivateFieldGet(this, _Fetcher_instances, "m", _Fetcher_setHeaders).call(this, request, type);
|
|
49
|
+
try {
|
|
50
|
+
const res = await fetch(request, { signal });
|
|
51
|
+
return await (type === 'html' ? res.text() : res.json());
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (error instanceof AbortError) {
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
if (rt < maxRetries) {
|
|
58
|
+
return this.get({ url, type, payload, maxRetries }, rt + 1);
|
|
59
|
+
}
|
|
60
|
+
const errMsg = error instanceof Error ? error.message : error;
|
|
61
|
+
const retriedMsg = rt > 0 ? ` (retried ${rt} times)` : '';
|
|
62
|
+
throw new FetcherError(`${errMsg}${retriedMsg}`, urlObj.toString());
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async prepareDownload(params) {
|
|
66
|
+
const { url, destFilenameResolver, destDir, signal } = params;
|
|
67
|
+
const request = new Request(url, { method: 'GET' });
|
|
68
|
+
// Note: do not set cookie and host in headers except for attachments, otherwise you'll get 404 Not Found.
|
|
69
|
+
if (params.srcEntity.type === 'attachment') {
|
|
70
|
+
/**
|
|
71
|
+
* Special handling for attachments.
|
|
72
|
+
* 1. Send request with cookie. Server will redirect response to actual download URL.
|
|
73
|
+
* However, in the request to redirect URL, the cookie and host set in headers will
|
|
74
|
+
* cause server to return 404 - Not Found.
|
|
75
|
+
* 2. So we need to make another request to the redirect URL, this time without
|
|
76
|
+
* setting cookie and host in headers. Server should return response 200, allowing us to stream
|
|
77
|
+
* the response body to file.
|
|
78
|
+
*/
|
|
79
|
+
__classPrivateFieldGet(this, _Fetcher_instances, "m", _Fetcher_setHeaders).call(this, request, 'html');
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
__classPrivateFieldGet(this, _Fetcher_instances, "m", _Fetcher_setHeaders).call(this, request, 'html', { setCookie: false, setHost: false });
|
|
83
|
+
}
|
|
84
|
+
const internalAbortController = new AbortController();
|
|
85
|
+
if (signal) {
|
|
86
|
+
signal.addEventListener('abort', () => internalAbortController.abort(), { once: true });
|
|
87
|
+
}
|
|
88
|
+
let res = await fetch(request, { signal: internalAbortController.signal });
|
|
89
|
+
// Special handling for attachment
|
|
90
|
+
if (params.srcEntity.type === 'attachment' && !res.ok && res.redirected && res.url) {
|
|
91
|
+
const redirectReq = new Request(res.url, { method: 'GET' });
|
|
92
|
+
__classPrivateFieldGet(this, _Fetcher_instances, "m", _Fetcher_setHeaders).call(this, redirectReq, 'html', { setCookie: false, setHost: false });
|
|
93
|
+
res = await fetch(redirectReq, { signal: internalAbortController.signal });
|
|
94
|
+
}
|
|
95
|
+
if (__classPrivateFieldGet(this, _Fetcher_instances, "m", _Fetcher_assertResponseOK).call(this, res, url)) {
|
|
96
|
+
const destFilename = destFilenameResolver.resolve(res);
|
|
97
|
+
const destFilePath = path.resolve(destDir, destFilename);
|
|
98
|
+
const contentLength = res.headers.get('content-length');
|
|
99
|
+
const progress = progressStream({
|
|
100
|
+
length: contentLength ? parseInt(contentLength, 10) : 0,
|
|
101
|
+
time: 300
|
|
102
|
+
});
|
|
103
|
+
const monitor = new FetcherProgressMonitor(progress, destFilename, destFilePath);
|
|
104
|
+
const _res = res;
|
|
105
|
+
const start = (overrides) => {
|
|
106
|
+
const _tmpFilePath = overrides?.tmpFilePath || path.resolve(destDir, `${destFilename}.part`);
|
|
107
|
+
const _destFilePath = overrides?.destFilePath || destFilePath;
|
|
108
|
+
return __classPrivateFieldGet(this, _Fetcher_instances, "m", _Fetcher_startDownload).call(this, _res, _tmpFilePath, _destFilePath, progress);
|
|
109
|
+
};
|
|
110
|
+
const abort = () => {
|
|
111
|
+
internalAbortController.abort();
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
destFilePath,
|
|
115
|
+
monitor,
|
|
116
|
+
start,
|
|
117
|
+
abort
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
log(level, ...msg) {
|
|
123
|
+
commonLog(__classPrivateFieldGet(this, _Fetcher_logger, "f"), level, this.name, ...msg);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
_Fetcher_cookie = new WeakMap(), _Fetcher_logger = new WeakMap(), _Fetcher_instances = new WeakSet(), _Fetcher_startDownload = async function _Fetcher_startDownload(response, tmpFilePath, destFilePath, progress) {
|
|
127
|
+
try {
|
|
128
|
+
this.log('debug', `Pipe "${response.url}" to "${tmpFilePath}"`);
|
|
129
|
+
await pipeline(response.body, progress, fs.createWriteStream(tmpFilePath));
|
|
130
|
+
const commit = () => {
|
|
131
|
+
__classPrivateFieldGet(this, _Fetcher_instances, "m", _Fetcher_commitDownload).call(this, tmpFilePath, destFilePath);
|
|
132
|
+
};
|
|
133
|
+
const discard = () => {
|
|
134
|
+
__classPrivateFieldGet(this, _Fetcher_instances, "m", _Fetcher_cleanupDownload).call(this, tmpFilePath);
|
|
135
|
+
};
|
|
136
|
+
return {
|
|
137
|
+
tmpFilePath,
|
|
138
|
+
commit,
|
|
139
|
+
discard
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
__classPrivateFieldGet(this, _Fetcher_instances, "m", _Fetcher_cleanupDownload).call(this, tmpFilePath);
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}, _Fetcher_commitDownload = function _Fetcher_commitDownload(tmpFilePath, destFilePath) {
|
|
147
|
+
try {
|
|
148
|
+
this.log('debug', `Commit "${tmpFilePath}" to "${destFilePath}; filesize: ${fs.lstatSync(tmpFilePath).size} bytes`);
|
|
149
|
+
fs.renameSync(tmpFilePath, destFilePath);
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
__classPrivateFieldGet(this, _Fetcher_instances, "m", _Fetcher_cleanupDownload).call(this, tmpFilePath);
|
|
153
|
+
}
|
|
154
|
+
}, _Fetcher_cleanupDownload = function _Fetcher_cleanupDownload(tmpFilePath) {
|
|
155
|
+
try {
|
|
156
|
+
if (fs.existsSync(tmpFilePath)) {
|
|
157
|
+
this.log('debug', `Clean up "${tmpFilePath}"`);
|
|
158
|
+
fs.unlinkSync(tmpFilePath);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
this.log('error', `Error cleaning up "${tmpFilePath}:`, error);
|
|
163
|
+
}
|
|
164
|
+
}, _Fetcher_setHeaders = function _Fetcher_setHeaders(request, type, opts) {
|
|
165
|
+
const setCookie = pickDefined(opts?.setCookie, true);
|
|
166
|
+
const setHost = pickDefined(opts?.setHost, true);
|
|
167
|
+
if (__classPrivateFieldGet(this, _Fetcher_cookie, "f") && setCookie) {
|
|
168
|
+
request.headers.set('Cookie', __classPrivateFieldGet(this, _Fetcher_cookie, "f"));
|
|
169
|
+
}
|
|
170
|
+
if (setHost) {
|
|
171
|
+
request.headers.set('Host', 'www.patreon.com');
|
|
172
|
+
}
|
|
173
|
+
request.headers.set('User-Agent', USER_AGENT);
|
|
174
|
+
if (type === 'json') {
|
|
175
|
+
request.headers.set('Content-Type', 'application/vnd.api+json');
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
request.headers.set('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8');
|
|
179
|
+
}
|
|
180
|
+
}, _Fetcher_assertResponseOK = function _Fetcher_assertResponseOK(response, originURL, requireBody = true) {
|
|
181
|
+
if (!response) {
|
|
182
|
+
throw new FetcherError('No response', originURL);
|
|
183
|
+
}
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
throw new FetcherError(`${response.status} - ${response.statusText}`, originURL);
|
|
186
|
+
}
|
|
187
|
+
if (requireBody && !response.body) {
|
|
188
|
+
throw new FetcherError('Empty response body', originURL);
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
};
|
|
192
|
+
//# sourceMappingURL=Fetcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Fetcher.js","sourceRoot":"","sources":["../../src/utils/Fetcher.ts"],"names":[],"mappings":"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","sourcesContent":["import progressStream from 'progress-stream';\nimport * as fs from 'fs';\nimport fetch, { AbortError, Request, Response } from 'node-fetch';\nimport { pipeline } from 'stream/promises';\nimport { URL } from 'url';\nimport path from 'path';\nimport FetcherProgressMonitor from './FetcherProgressMonitor.js';\nimport { Downloadable } from '../entities/Downloadable.js';\nimport FilenameResolver from './FllenameResolver.js';\nimport { pickDefined } from './Misc.js';\nimport Logger, { LogLevel, commonLog } from './logging/Logger.js';\n\nexport interface PrepareDownloadParams<T extends Downloadable> {\n url: string;\n destDir: string;\n srcEntity: T;\n destFilenameResolver: FilenameResolver<T>;\n signal: AbortSignal;\n}\n\nexport interface StartDownloadOverrides {\n destFilePath?: string;\n tmpFilePath?: string;\n}\n\nexport class FetcherError extends Error {\n\n url: string;\n\n constructor(message: string, url: string) {\n super(message);\n this.name = 'FetcherError';\n this.url = url;\n }\n}\n\nexport type FetcherGetType = 'html' | 'json';\nexport type FetcherGetResultOf<T extends FetcherGetType> =\n T extends 'html' ? string :\n T extends 'json' ? any :\n never;\n\nconst USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0';\n\nexport default class Fetcher {\n\n name = 'Fetcher';\n\n #cookie?: string;\n #logger?: Logger | null;\n\n constructor(cookie?: string, logger?: Logger | null) {\n this.#cookie = cookie;\n this.#logger = logger;\n }\n\n async get<T extends FetcherGetType>(args: {\n url: string,\n type: T,\n payload?: Record<string, any>,\n maxRetries: number,\n signal?: AbortSignal\n }, rt = 0): Promise<FetcherGetResultOf<T>> {\n\n const { url, type, payload, maxRetries, signal } = args;\n\n const urlObj = new URL(url);\n if (payload) {\n for (const [ p, v ] of Object.entries(payload)) {\n urlObj.searchParams.set(p, v);\n }\n }\n const request = new Request(urlObj, { method: 'GET' });\n this.#setHeaders(request, type);\n try {\n const res = await fetch(request, { signal });\n return await (type === 'html' ? res.text() : res.json()) as Promise<FetcherGetResultOf<T>>;\n }\n catch (error) {\n if (error instanceof AbortError) {\n throw error;\n }\n if (rt < maxRetries) {\n return this.get({ url, type, payload, maxRetries }, rt + 1);\n }\n const errMsg = error instanceof Error ? error.message : error;\n const retriedMsg = rt > 0 ? ` (retried ${rt} times)` : '';\n throw new FetcherError(`${errMsg}${retriedMsg}`, urlObj.toString());\n }\n }\n\n async prepareDownload<T extends Downloadable>(params: PrepareDownloadParams<T>) {\n const { url, destFilenameResolver, destDir, signal } = params;\n const request = new Request(url, { method: 'GET' });\n // Note: do not set cookie and host in headers except for attachments, otherwise you'll get 404 Not Found.\n if (params.srcEntity.type === 'attachment') {\n /**\n * Special handling for attachments.\n * 1. Send request with cookie. Server will redirect response to actual download URL.\n * However, in the request to redirect URL, the cookie and host set in headers will\n * cause server to return 404 - Not Found.\n * 2. So we need to make another request to the redirect URL, this time without\n * setting cookie and host in headers. Server should return response 200, allowing us to stream\n * the response body to file.\n */\n this.#setHeaders(request, 'html');\n }\n else {\n this.#setHeaders(request, 'html', { setCookie: false, setHost: false });\n }\n\n const internalAbortController = new AbortController();\n if (signal) {\n signal.addEventListener('abort', () => internalAbortController.abort(), {once: true});\n }\n\n let res = await fetch(request, { signal: internalAbortController.signal });\n\n // Special handling for attachment\n if (params.srcEntity.type === 'attachment' && !res.ok && res.redirected && res.url) {\n const redirectReq = new Request(res.url, { method: 'GET' });\n this.#setHeaders(redirectReq, 'html', { setCookie: false, setHost: false });\n res = await fetch(redirectReq, { signal: internalAbortController.signal });\n }\n\n if (this.#assertResponseOK(res, url)) {\n const destFilename = destFilenameResolver.resolve(res);\n const destFilePath = path.resolve(destDir, destFilename);\n\n const contentLength = res.headers.get('content-length');\n const progress = progressStream({\n length: contentLength ? parseInt(contentLength, 10) : 0,\n time: 300\n });\n const monitor = new FetcherProgressMonitor(progress, destFilename, destFilePath);\n\n const _res = res;\n const start = (overrides?: StartDownloadOverrides) => {\n const _tmpFilePath = overrides?.tmpFilePath || path.resolve(destDir, `${destFilename}.part`);\n const _destFilePath = overrides?.destFilePath || destFilePath;\n return this.#startDownload(_res, _tmpFilePath, _destFilePath, progress);\n };\n const abort = () => {\n internalAbortController.abort();\n };\n\n return {\n destFilePath,\n monitor,\n start,\n abort\n };\n }\n\n return undefined as never;\n }\n\n async #startDownload(\n response: Response & { body: NonNullable<Response['body']> },\n tmpFilePath: string,\n destFilePath: string,\n progress: progressStream.ProgressStream) {\n\n try {\n this.log('debug', `Pipe \"${response.url}\" to \"${tmpFilePath}\"`);\n await pipeline(\n response.body,\n progress,\n fs.createWriteStream(tmpFilePath)\n );\n\n const commit = () => {\n this.#commitDownload(tmpFilePath, destFilePath);\n };\n\n const discard = () => {\n this.#cleanupDownload(tmpFilePath);\n };\n\n return {\n tmpFilePath,\n commit,\n discard\n };\n }\n catch (error) {\n this.#cleanupDownload(tmpFilePath);\n throw error;\n }\n }\n\n #commitDownload(tmpFilePath: string, destFilePath: string) {\n try {\n this.log('debug', `Commit \"${tmpFilePath}\" to \"${destFilePath}; filesize: ${fs.lstatSync(tmpFilePath).size} bytes`);\n fs.renameSync(tmpFilePath, destFilePath);\n }\n finally {\n this.#cleanupDownload(tmpFilePath);\n }\n }\n\n #cleanupDownload(tmpFilePath: string) {\n try {\n if (fs.existsSync(tmpFilePath)) {\n this.log('debug', `Clean up \"${tmpFilePath}\"`);\n fs.unlinkSync(tmpFilePath);\n }\n }\n catch (error) {\n this.log('error', `Error cleaning up \"${tmpFilePath}:`, error);\n }\n }\n\n #setHeaders(request: Request, type: 'html' | 'json', opts?: { setCookie?: boolean; setHost?: boolean; }) {\n const setCookie = pickDefined(opts?.setCookie, true);\n const setHost = pickDefined(opts?.setHost, true);\n if (this.#cookie && setCookie) {\n request.headers.set('Cookie', this.#cookie);\n }\n if (setHost) {\n request.headers.set('Host', 'www.patreon.com');\n }\n request.headers.set('User-Agent', USER_AGENT);\n if (type === 'json') {\n request.headers.set('Content-Type', 'application/vnd.api+json');\n }\n else {\n request.headers.set('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8');\n }\n }\n\n #assertResponseOK(response: Response | null, originURL: string, requireBody: false): response is Response;\n #assertResponseOK(response: Response | null, originURL: string, requireBody?: true): response is Response & { body: NonNullable<Response['body']> };\n #assertResponseOK(response: Response | null, originURL: string, requireBody = true) {\n if (!response) {\n throw new FetcherError('No response', originURL);\n }\n if (!response.ok) {\n throw new FetcherError(`${response.status} - ${response.statusText}`, originURL);\n }\n if (requireBody && !response.body) {\n throw new FetcherError('Empty response body', originURL);\n }\n return true;\n }\n\n protected log(level: LogLevel, ...msg: Array<any>) {\n commonLog(this.#logger, level, this.name, ...msg);\n }\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Progress, ProgressStream } from 'progress-stream';
|
|
3
|
+
import { EventEmitter } from 'events';
|
|
4
|
+
export type FetcherProgress = Progress & {
|
|
5
|
+
destFilename: string;
|
|
6
|
+
destFilePath: string;
|
|
7
|
+
};
|
|
8
|
+
export default class FetcherProgressMonitor extends EventEmitter {
|
|
9
|
+
#private;
|
|
10
|
+
constructor(progressStream: ProgressStream, destFilename: string, destFilePath: string);
|
|
11
|
+
getDestFilename(): string;
|
|
12
|
+
getDestFilePath(): string;
|
|
13
|
+
getProgress(): FetcherProgress;
|
|
14
|
+
emit(event: 'progress', progress: FetcherProgress): boolean;
|
|
15
|
+
on(event: 'progress', listener: (progress: FetcherProgress) => void): this;
|
|
16
|
+
once(event: 'progress', listener: (progress: FetcherProgress) => void): this;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=FetcherProgressMonitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FetcherProgressMonitor.d.ts","sourceRoot":"","sources":["../../src/utils/FetcherProgressMonitor.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG;IACvC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,CAAC,OAAO,OAAO,sBAAuB,SAAQ,YAAY;;gBAMlD,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;IAStF,eAAe;IAIf,eAAe;IAIf,WAAW,IAAI,eAAe;IAgB9B,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,GAAG,OAAO;IAK3D,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI;IAK1E,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI;CAI7E"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
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");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _FetcherProgressMonitor_instances, _FetcherProgressMonitor_progressStream, _FetcherProgressMonitor_destFilename, _FetcherProgressMonitor_destFilePath, _FetcherProgressMonitor_handleProgressStreamProgressEvent;
|
|
13
|
+
import { EventEmitter } from 'events';
|
|
14
|
+
export default class FetcherProgressMonitor extends EventEmitter {
|
|
15
|
+
constructor(progressStream, destFilename, destFilePath) {
|
|
16
|
+
super();
|
|
17
|
+
_FetcherProgressMonitor_instances.add(this);
|
|
18
|
+
_FetcherProgressMonitor_progressStream.set(this, void 0);
|
|
19
|
+
_FetcherProgressMonitor_destFilename.set(this, void 0);
|
|
20
|
+
_FetcherProgressMonitor_destFilePath.set(this, void 0);
|
|
21
|
+
__classPrivateFieldSet(this, _FetcherProgressMonitor_progressStream, progressStream, "f");
|
|
22
|
+
__classPrivateFieldSet(this, _FetcherProgressMonitor_destFilename, destFilename, "f");
|
|
23
|
+
__classPrivateFieldSet(this, _FetcherProgressMonitor_destFilePath, destFilePath, "f");
|
|
24
|
+
__classPrivateFieldGet(this, _FetcherProgressMonitor_progressStream, "f").on('progress', __classPrivateFieldGet(this, _FetcherProgressMonitor_instances, "m", _FetcherProgressMonitor_handleProgressStreamProgressEvent).bind(this));
|
|
25
|
+
}
|
|
26
|
+
getDestFilename() {
|
|
27
|
+
return __classPrivateFieldGet(this, _FetcherProgressMonitor_destFilename, "f");
|
|
28
|
+
}
|
|
29
|
+
getDestFilePath() {
|
|
30
|
+
return __classPrivateFieldGet(this, _FetcherProgressMonitor_destFilePath, "f");
|
|
31
|
+
}
|
|
32
|
+
getProgress() {
|
|
33
|
+
return {
|
|
34
|
+
...__classPrivateFieldGet(this, _FetcherProgressMonitor_progressStream, "f").progress(),
|
|
35
|
+
destFilename: __classPrivateFieldGet(this, _FetcherProgressMonitor_destFilename, "f"),
|
|
36
|
+
destFilePath: __classPrivateFieldGet(this, _FetcherProgressMonitor_destFilePath, "f")
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
emit(event, ...args) {
|
|
40
|
+
return super.emit(event, ...args);
|
|
41
|
+
}
|
|
42
|
+
on(event, listener) {
|
|
43
|
+
return super.on(event, listener);
|
|
44
|
+
}
|
|
45
|
+
once(event, listener) {
|
|
46
|
+
return super.once(event, listener);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
_FetcherProgressMonitor_progressStream = new WeakMap(), _FetcherProgressMonitor_destFilename = new WeakMap(), _FetcherProgressMonitor_destFilePath = new WeakMap(), _FetcherProgressMonitor_instances = new WeakSet(), _FetcherProgressMonitor_handleProgressStreamProgressEvent = function _FetcherProgressMonitor_handleProgressStreamProgressEvent(progress) {
|
|
50
|
+
this.emit('progress', {
|
|
51
|
+
...progress,
|
|
52
|
+
destFilename: __classPrivateFieldGet(this, _FetcherProgressMonitor_destFilename, "f"),
|
|
53
|
+
destFilePath: __classPrivateFieldGet(this, _FetcherProgressMonitor_destFilePath, "f")
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=FetcherProgressMonitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FetcherProgressMonitor.js","sourceRoot":"","sources":["../../src/utils/FetcherProgressMonitor.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAOtC,MAAM,CAAC,OAAO,OAAO,sBAAuB,SAAQ,YAAY;IAM9D,YAAY,cAA8B,EAAE,YAAoB,EAAE,YAAoB;QACpF,KAAK,EAAE,CAAC;;QALV,yDAAgC;QAChC,uDAAsB;QACtB,uDAAsB;QAIpB,uBAAA,IAAI,0CAAmB,cAAc,MAAA,CAAC;QACtC,uBAAA,IAAI,wCAAiB,YAAY,MAAA,CAAC;QAClC,uBAAA,IAAI,wCAAiB,YAAY,MAAA,CAAC;QAElC,uBAAA,IAAI,8CAAgB,CAAC,EAAE,CAAC,UAAU,EAAE,uBAAA,IAAI,oGAAmC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1F,CAAC;IAED,eAAe;QACb,OAAO,uBAAA,IAAI,4CAAc,CAAC;IAC5B,CAAC;IAED,eAAe;QACb,OAAO,uBAAA,IAAI,4CAAc,CAAC;IAC5B,CAAC;IAED,WAAW;QACT,OAAO;YACL,GAAG,uBAAA,IAAI,8CAAgB,CAAC,QAAQ,EAAE;YAClC,YAAY,EAAE,uBAAA,IAAI,4CAAc;YAChC,YAAY,EAAE,uBAAA,IAAI,4CAAc;SACjC,CAAC;IACJ,CAAC;IAWD,IAAI,CAAC,KAAsB,EAAE,GAAG,IAAW;QACzC,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC;IACpC,CAAC;IAGD,EAAE,CAAC,KAAsB,EAAE,QAAkC;QAC3D,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC;IAGD,IAAI,CAAC,KAAsB,EAAE,QAAkC;QAC7D,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACrC,CAAC;CACF;sVAtBoC,QAAkB;IACnD,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;QACpB,GAAG,QAAQ;QACX,YAAY,EAAE,uBAAA,IAAI,4CAAc;QAChC,YAAY,EAAE,uBAAA,IAAI,4CAAc;KACjC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { Progress, ProgressStream } from 'progress-stream';\nimport { EventEmitter } from 'events';\n\nexport type FetcherProgress = Progress & {\n destFilename: string;\n destFilePath: string;\n};\n\nexport default class FetcherProgressMonitor extends EventEmitter {\n\n #progressStream: ProgressStream;\n #destFilename: string;\n #destFilePath: string;\n\n constructor(progressStream: ProgressStream, destFilename: string, destFilePath: string) {\n super();\n this.#progressStream = progressStream;\n this.#destFilename = destFilename;\n this.#destFilePath = destFilePath;\n\n this.#progressStream.on('progress', this.#handleProgressStreamProgressEvent.bind(this));\n }\n\n getDestFilename() {\n return this.#destFilename;\n }\n\n getDestFilePath() {\n return this.#destFilePath;\n }\n\n getProgress(): FetcherProgress {\n return {\n ...this.#progressStream.progress(),\n destFilename: this.#destFilename,\n destFilePath: this.#destFilePath\n };\n }\n\n #handleProgressStreamProgressEvent(progress: Progress) {\n this.emit('progress', {\n ...progress,\n destFilename: this.#destFilename,\n destFilePath: this.#destFilePath\n });\n }\n\n emit(event: 'progress', progress: FetcherProgress): boolean;\n emit(event: string | symbol, ...args: any[]): boolean {\n return super.emit(event, ...args);\n }\n\n on(event: 'progress', listener: (progress: FetcherProgress) => void): this;\n on(event: string | symbol, listener: (...args: any[]) => void): this {\n return super.on(event, listener);\n }\n\n once(event: 'progress', listener: (progress: FetcherProgress) => void): this;\n once(event: string | symbol, listener: (...args: any[]) => void): this {\n return super.once(event, listener);\n }\n}\n"]}
|