patreon-dl 3.5.0 → 3.6.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/LICENSE +21 -0
- package/README.md +7 -1
- package/bin/EmbedlyDownloader.js +173 -0
- package/bin/patreon-dl-sprout.js +43 -0
- package/bin/patreon-dl-vimeo.js +22 -157
- package/dist/browse/api/CampaignAPIMixin.d.ts +1 -1
- package/dist/browse/api/ContentAPIMixin.d.ts +1 -1
- package/dist/browse/api/FilterAPIMixin.d.ts +1 -1
- package/dist/browse/api/index.d.ts +6 -6
- package/dist/browse/db/CampaignDBMixin.d.ts +3 -3
- package/dist/browse/db/ContentDBMixin.d.ts +13 -13
- package/dist/browse/db/index.d.ts +16 -16
- package/dist/browse/web/assets/{index-C4S8SMVg.css → index-DjOKbT1U.css} +1 -1
- package/dist/browse/web/assets/index-Dw_64hkR.js +218 -0
- package/dist/browse/web/index.html +2 -2
- package/dist/cli/index.js +12 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/utils/Fetcher.js +1 -1
- package/dist/utils/Fetcher.js.map +1 -1
- package/dist/utils/Misc.js +15 -5
- package/dist/utils/Misc.js.map +1 -1
- package/dist/utils/yt/InnertubeLoader.d.ts +1 -0
- package/dist/utils/yt/InnertubeLoader.js +26 -4
- package/dist/utils/yt/InnertubeLoader.js.map +1 -1
- package/dist/utils/yt/PoToken.d.ts +11 -0
- package/dist/utils/yt/PoToken.js +107 -0
- package/dist/utils/yt/PoToken.js.map +1 -0
- package/package.json +8 -3
- package/dist/browse/web/assets/index-D5kKPxpC.js +0 -209
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Patrick Kan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ $ patreon-dl --configure-youtube
|
|
|
74
74
|
|
|
75
75
|
You can specify external programs to download embedded videos or from embedded links. For YouTube videos, this will replace the built-in downloader.
|
|
76
76
|
|
|
77
|
-
See the [example config](./example-embed.conf) on how to configure an external downloader to fetch YouTube and
|
|
77
|
+
See the [example config](./example-embed.conf) on how to configure an external downloader to fetch YouTube, Vimeo and SproutVideo content through [yt-dlp](https://github.com/yt-dlp/yt-dlp). Helper scripts bundled with `patreon-dl` are used in the case of Vimeo and SproutVideo ([patreon-dl-vimeo.js](./bin/patreon-dl-vimeo.js) and [patreon-dl-sprout.js](./bin/patreon-dl-sprout.js) respectively).
|
|
78
78
|
|
|
79
79
|
## Installation
|
|
80
80
|
|
|
@@ -293,6 +293,12 @@ Note the URL shown in the output. Open this URL in a web browser to begin viewin
|
|
|
293
293
|
|
|
294
294
|
## Changelog
|
|
295
295
|
|
|
296
|
+
v3.6.0
|
|
297
|
+
- Browse: affix nav links (previous / next post) to viewport bottom if post content overflows ([patreon-dl-gui#41](https://github.com/patrickkfkan/patreon-dl-gui/issues/41))
|
|
298
|
+
- Fix error when Deno path contains spaces ([patreon-dl-gui#42](https://github.com/patrickkfkan/patreon-dl-gui/issues/42))
|
|
299
|
+
- Add SproutVideo download script ([patreon-dl-gui#43](https://github.com/patrickkfkan/patreon-dl-gui/issues/43))
|
|
300
|
+
- Fix YouTube download returning "auth required" error
|
|
301
|
+
|
|
296
302
|
v3.5.0
|
|
297
303
|
- Add support for downloading from "shop" URLs (e.g. `https://www.patreon.com/<creator>/shop`). This will download all products from a creator's shop.
|
|
298
304
|
- Add `productsPublished` / `products.published.after` / `products.published.before` option to set publish date criteria of products included in download.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import parseArgs from 'yargs-parser';
|
|
2
|
+
import spawn from '@patrickkfkan/cross-spawn';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* EmbedlyDownloader uses yt-dlp to download content embedded through Embedly:
|
|
7
|
+
* - It obtains the video URL from 'embed.html' or 'embed.url' commandline args.
|
|
8
|
+
* The former ("player URL") is always preferable since it is what's actually played within
|
|
9
|
+
* the Patreon post, and furthermore 'embed.url' sometimes returns "Page not found" (see
|
|
10
|
+
* issue: https://github.com/patrickkfkan/patreon-dl/issues/65) or a password-protected page.
|
|
11
|
+
* - The URL is passed to yt-dlp.
|
|
12
|
+
* - yt-dlp downloads the video from URL and saves it to 'dest.dir'. The filename is determined by the specified
|
|
13
|
+
* format '%(title)s.%(ext)s' (see: https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#output-template).
|
|
14
|
+
* - Fallback to embed URL if player URL fails to download.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export default class EmbedlyDownloader {
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
*
|
|
21
|
+
* @param {*} provider Name of the provider.
|
|
22
|
+
* @param {*} srcHostname Hostname of the embedded source URL.
|
|
23
|
+
*/
|
|
24
|
+
constructor(provider, srcHostname) {
|
|
25
|
+
this.provider = provider;
|
|
26
|
+
this.srcHostname = srcHostname;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getPlayerURL(html) {
|
|
30
|
+
if (!html) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const regex = /src="(\/\/cdn.embedly.com\/widgets.+?)"/g;
|
|
35
|
+
const match = regex.exec(html);
|
|
36
|
+
if (match && match[1]) {
|
|
37
|
+
const embedlyURL = match[1];
|
|
38
|
+
console.log('Found Embedly URL from embed HTML:', embedlyURL);
|
|
39
|
+
let embedlySrc;
|
|
40
|
+
try {
|
|
41
|
+
const urlObj = new URL(`https:${embedlyURL}`);
|
|
42
|
+
embedlySrc = urlObj.searchParams.get('src');
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.error('Error parsing Embedly URL:', error);
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const embedlySrcObj = new URL(embedlySrc);
|
|
49
|
+
if (!this.srcHostname) {
|
|
50
|
+
console.log(`Got Embedly src "${embedlySrc}" - assume it is ${this.provider} player URL since no hostname was specified`);
|
|
51
|
+
}
|
|
52
|
+
else if (embedlySrcObj.hostname === this.srcHostname) {
|
|
53
|
+
console.log(`Got ${this.provider} player URL from Embedly src: ${embedlySrc}`);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
console.warn(`Embedly src "${embedlySrc}" does not correspond to ${this.provider} player URL`);
|
|
57
|
+
}
|
|
58
|
+
return embedlySrc;
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error(`Error parsing Embedly src "${embedlySrc}":`, error);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getCommandString(cmd, args) {
|
|
69
|
+
const quotedArgs = args.map((arg) => arg.includes(' ') ? `"${arg}"` : arg);
|
|
70
|
+
return [
|
|
71
|
+
cmd,
|
|
72
|
+
...quotedArgs
|
|
73
|
+
].join(' ');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async download(url, o, videoPassword, ytdlpPath, ytdlpArgs) {
|
|
77
|
+
let proc;
|
|
78
|
+
const ytdlp = ytdlpPath || 'yt-dlp';
|
|
79
|
+
const parsedYtdlpArgs = parseArgs(ytdlpArgs);
|
|
80
|
+
try {
|
|
81
|
+
return await new Promise((resolve, reject) => {
|
|
82
|
+
let settled = false;
|
|
83
|
+
const args = [];
|
|
84
|
+
if (!parsedYtdlpArgs['o'] && !parsedYtdlpArgs['output']) {
|
|
85
|
+
args.push('-o', o);
|
|
86
|
+
}
|
|
87
|
+
if (!parsedYtdlpArgs['referrer']) {
|
|
88
|
+
args.push('--add-header', 'Referer: https://patreon.com/');
|
|
89
|
+
}
|
|
90
|
+
args.push(...ytdlpArgs);
|
|
91
|
+
const printArgs = [...args];
|
|
92
|
+
if (videoPassword && !parsedYtdlpArgs['video-password']) {
|
|
93
|
+
args.push('--video-password', videoPassword);
|
|
94
|
+
printArgs.push('--video-password', '******');
|
|
95
|
+
}
|
|
96
|
+
args.push(url);
|
|
97
|
+
printArgs.push(url);
|
|
98
|
+
|
|
99
|
+
console.log(`Command: ${this.getCommandString(ytdlp, printArgs)}`);
|
|
100
|
+
proc = spawn(ytdlp, args);
|
|
101
|
+
|
|
102
|
+
proc.stdout?.on('data', (data) => {
|
|
103
|
+
console.log(data.toString());
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
proc.stderr?.on('data', (data_1) => {
|
|
107
|
+
console.error(data_1.toString());
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
proc.on('error', (err) => {
|
|
111
|
+
if (settled) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
settled = true;
|
|
115
|
+
reject(err);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
proc.on('exit', (code) => {
|
|
119
|
+
if (settled) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
settled = true;
|
|
123
|
+
resolve(code);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
} finally {
|
|
127
|
+
if (proc) {
|
|
128
|
+
proc.removeAllListeners();
|
|
129
|
+
proc.stdout?.removeAllListeners();
|
|
130
|
+
proc.stderr?.removeAllListeners();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async start() {
|
|
136
|
+
const args = parseArgs(process.argv.slice(2));
|
|
137
|
+
const {
|
|
138
|
+
'o': _o,
|
|
139
|
+
'embed-html': _embedHTML,
|
|
140
|
+
'embed-url': _embedURL,
|
|
141
|
+
'video-password': videoPassword,
|
|
142
|
+
'yt-dlp': _ytdlpPath
|
|
143
|
+
} = args;
|
|
144
|
+
const o = _o?.trim() ? path.resolve(_o.trim()) : null;
|
|
145
|
+
const embedHTML = _embedHTML?.trim();
|
|
146
|
+
const embedURL = _embedURL?.trim();
|
|
147
|
+
const ytdlpPath = _ytdlpPath?.trim() ? path.resolve(_ytdlpPath.trim()) : null;
|
|
148
|
+
const ytdlpArgs = args['_'];
|
|
149
|
+
|
|
150
|
+
if (!o) {
|
|
151
|
+
throw Error('No output file specified');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!embedHTML && !embedURL) {
|
|
155
|
+
throw Error('No embed HTML or URL provided');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const _url = this.getPlayerURL(embedHTML) || embedURL;
|
|
159
|
+
|
|
160
|
+
if (!_url) {
|
|
161
|
+
throw Error(`Failed to obtain video URL`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log(`Going to download video from "${_url}"`);
|
|
165
|
+
let code = await this.download(_url, o, videoPassword, ytdlpPath, ytdlpArgs);
|
|
166
|
+
if (code !== 0 && _url !== embedURL && embedURL) {
|
|
167
|
+
console.log(`Download failed - retrying with embed URL "${embedURL}"`);
|
|
168
|
+
return await this.download(embedURL);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return code;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* External downloader for embedded SproutVideo videos. Obtains the appropriate URL to download from and
|
|
5
|
+
* passes it to 'yt-dlp' (https://github.com/yt-dlp/yt-dlp).
|
|
6
|
+
*
|
|
7
|
+
* Usage
|
|
8
|
+
* -----
|
|
9
|
+
* Place the following two lines in your 'patreon-dl' config file:
|
|
10
|
+
*
|
|
11
|
+
* [embed.downloader.sproutvideo]
|
|
12
|
+
* exec = patreon-dl-sprout -o "{dest.dir}/%(title)s.%(ext)s" --embed-html "{embed.html}" --embed-url "{embed.url}"
|
|
13
|
+
*
|
|
14
|
+
* You can append the following additional options to the exec line if necessary:
|
|
15
|
+
* --video-password "<password>": for password-protected videos
|
|
16
|
+
* --yt-dlp "</path/to/yt-dlp>": if yt-dlp is not in the PATH
|
|
17
|
+
*
|
|
18
|
+
* You can pass options directly to yt-dlp. To do so, add '--' to the end of the exec line, followed by the options.
|
|
19
|
+
* For example:
|
|
20
|
+
* exec = patreon-dl-sprout -o "{dest.dir}/%(title)s.%(ext)s" --embed-html "{embed.html}" --embed-url "{embed.url}" -- --cookies-from-browser firefox
|
|
21
|
+
*
|
|
22
|
+
* Upon encountering a post with embedded SproutVideo content, 'patreon-dl' will call this script, which in turn proceeds to download through SproutVideoDownloader
|
|
23
|
+
* (see the parent class EmbedlyDownloader for more info).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import EmbedlyDownloader from './EmbedlyDownloader.js';
|
|
27
|
+
|
|
28
|
+
class SproutVideoDownloader extends EmbedlyDownloader {
|
|
29
|
+
constructor() {
|
|
30
|
+
super('SproutVideo', 'videos.sproutvideo.com');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
(async () => {
|
|
35
|
+
const downloader = new SproutVideoDownloader();
|
|
36
|
+
try {
|
|
37
|
+
process.exit(await downloader.start());
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
})();
|
package/bin/patreon-dl-vimeo.js
CHANGED
|
@@ -19,173 +19,38 @@
|
|
|
19
19
|
* For example:
|
|
20
20
|
* exec = patreon-dl-vimeo -o "{dest.dir}/%(title)s.%(ext)s" --embed-html "{embed.html}" --embed-url "{embed.url}" -- --cookies-from-browser firefox
|
|
21
21
|
*
|
|
22
|
-
* Upon encountering a post with embedded Vimeo content, 'patreon-dl' will call this script
|
|
23
|
-
*
|
|
24
|
-
* since it is what's actually played within the Patreon post, and furthermore 'embed.url' sometimes returns
|
|
25
|
-
* "Page not found" (see issue: https://github.com/patrickkfkan/patreon-dl/issues/65).
|
|
26
|
-
* - The URL is passed to yt-dlp.
|
|
27
|
-
* - yt-dlp downloads the video from URL and saves it to 'dest.dir'. The filename is determined by the specified
|
|
28
|
-
* format '%(title)s.%(ext)s' (see: https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#output-template).
|
|
29
|
-
* - Fallback to embed URL if player URL fails to download.
|
|
30
|
-
*
|
|
22
|
+
* Upon encountering a post with embedded Vimeo content, 'patreon-dl' will call this script, which in turn proceeds to download through VimeoDownloader
|
|
23
|
+
* (see the parent class EmbedlyDownloader for more info).
|
|
31
24
|
*/
|
|
32
25
|
|
|
33
|
-
import
|
|
34
|
-
import spawn from '@patrickkfkan/cross-spawn';
|
|
35
|
-
import path from 'path';
|
|
36
|
-
|
|
37
|
-
function tryGetPlayerURL(html) {
|
|
38
|
-
if (!html) {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
26
|
+
import EmbedlyDownloader from './EmbedlyDownloader.js';
|
|
41
27
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
console.log('Found Vimeo player URL from embed HTML:', match[0]);
|
|
46
|
-
return match[0];
|
|
28
|
+
class VimeoDownloader extends EmbedlyDownloader {
|
|
29
|
+
constructor() {
|
|
30
|
+
super('Vimeo', 'player.vimeo.com');
|
|
47
31
|
}
|
|
48
32
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
embedlySrc = urlObj.searchParams.get('src');
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
console.error('Error parsing Embedly URL:', error);
|
|
61
|
-
}
|
|
62
|
-
try {
|
|
63
|
-
const embedlySrcObj = new URL(embedlySrc);
|
|
64
|
-
if (embedlySrcObj.hostname === 'player.vimeo.com') {
|
|
65
|
-
console.log(`Got Vimeo player URL from Embedly src: ${embedlySrc}`);
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
console.warn(`Embedly src "${embedlySrc}" does not correspond to Vimeo player URL`);
|
|
33
|
+
// Override
|
|
34
|
+
getPlayerURL(html) {
|
|
35
|
+
if (html) {
|
|
36
|
+
const regex = /https:\/\/player\.vimeo\.com\/video\/\d+/g;
|
|
37
|
+
const match = regex.exec(html);
|
|
38
|
+
if (match && match[0]) {
|
|
39
|
+
console.log('Found Vimeo player URL from embed HTML:', match[0]);
|
|
40
|
+
return match[0];
|
|
69
41
|
}
|
|
70
|
-
return embedlySrc;
|
|
71
|
-
}
|
|
72
|
-
catch (error) {
|
|
73
|
-
console.error(`Error parsing Embedly src "${embedlySrc}":`, error);
|
|
74
42
|
}
|
|
43
|
+
return super.getPlayerURL(html);
|
|
75
44
|
}
|
|
76
|
-
|
|
77
|
-
return null;
|
|
78
45
|
}
|
|
79
46
|
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
return [
|
|
83
|
-
cmd,
|
|
84
|
-
...quotedArgs
|
|
85
|
-
].join(' ');
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async function download(url, o, videoPassword, ytdlpPath, ytdlpArgs) {
|
|
89
|
-
let proc;
|
|
90
|
-
const ytdlp = ytdlpPath || 'yt-dlp';
|
|
91
|
-
const parsedYtdlpArgs = parseArgs(ytdlpArgs);
|
|
47
|
+
(async () => {
|
|
48
|
+
const downloader = new VimeoDownloader();
|
|
92
49
|
try {
|
|
93
|
-
|
|
94
|
-
let settled = false;
|
|
95
|
-
const args = [];
|
|
96
|
-
if (!parsedYtdlpArgs['o'] && !parsedYtdlpArgs['output']) {
|
|
97
|
-
args.push('-o', o);
|
|
98
|
-
}
|
|
99
|
-
if (!parsedYtdlpArgs['referrer']) {
|
|
100
|
-
args.push('--referer', 'https://patreon.com/');
|
|
101
|
-
}
|
|
102
|
-
args.push(...ytdlpArgs);
|
|
103
|
-
const printArgs = [...args];
|
|
104
|
-
if (videoPassword && !parsedYtdlpArgs['video-password']) {
|
|
105
|
-
args.push('--video-password', videoPassword);
|
|
106
|
-
printArgs.push('--video-password', '******');
|
|
107
|
-
}
|
|
108
|
-
args.push(url);
|
|
109
|
-
printArgs.push(url);
|
|
110
|
-
|
|
111
|
-
console.log(`Command: ${getCommandString(ytdlp, printArgs)}`);
|
|
112
|
-
proc = spawn(ytdlp, args);
|
|
113
|
-
|
|
114
|
-
proc.stdout?.on('data', (data) => {
|
|
115
|
-
console.log(data.toString());
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
proc.stderr?.on('data', (data_1) => {
|
|
119
|
-
console.error(data_1.toString());
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
proc.on('error', (err) => {
|
|
123
|
-
if (settled) {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
settled = true;
|
|
127
|
-
reject(err);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
proc.on('exit', (code) => {
|
|
131
|
-
if (settled) {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
settled = true;
|
|
135
|
-
resolve(code);
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
} finally {
|
|
139
|
-
if (proc) {
|
|
140
|
-
proc.removeAllListeners();
|
|
141
|
-
proc.stdout?.removeAllListeners();
|
|
142
|
-
proc.stderr?.removeAllListeners();
|
|
143
|
-
}
|
|
50
|
+
process.exit(await downloader.start());
|
|
144
51
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const {
|
|
149
|
-
'o': _o,
|
|
150
|
-
'embed-html': _embedHTML,
|
|
151
|
-
'embed-url': _embedURL,
|
|
152
|
-
'video-password': videoPassword,
|
|
153
|
-
'yt-dlp': _ytdlpPath
|
|
154
|
-
} = args;
|
|
155
|
-
const o = _o?.trim() ? path.resolve(_o.trim()) : null;
|
|
156
|
-
const embedHTML = _embedHTML?.trim();
|
|
157
|
-
const embedURL = _embedURL?.trim();
|
|
158
|
-
const ytdlpPath = _ytdlpPath?.trim() ? path.resolve(_ytdlpPath.trim()) : null;
|
|
159
|
-
const ytdlpArgs = args['_'];
|
|
160
|
-
|
|
161
|
-
if (!o) {
|
|
162
|
-
console.error('No output file specified');
|
|
163
|
-
process.exit(1);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (!embedHTML && !embedURL) {
|
|
167
|
-
console.error('No embed HTML or URL provided');
|
|
168
|
-
process.exit(1);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const url = tryGetPlayerURL(embedHTML) || embedURL;
|
|
172
|
-
|
|
173
|
-
if (!url) {
|
|
174
|
-
console.error(`Failed to obtain video URL`);
|
|
175
|
-
process.exit(1);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
async function doDownload(_url) {
|
|
179
|
-
let code = await download(_url, o, videoPassword, ytdlpPath, ytdlpArgs);
|
|
180
|
-
if (code !== 0 && _url !== embedURL && embedURL) {
|
|
181
|
-
console.log(`Download failed - retrying with embed URL "${embedURL}"`);
|
|
182
|
-
return await doDownload(embedURL);
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
54
|
+
process.exit(1);
|
|
183
55
|
}
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
console.log(`Going to download video from "${url}"`);
|
|
188
|
-
|
|
189
|
-
doDownload(url).then((code) => {
|
|
190
|
-
process.exit(code);
|
|
191
|
-
});
|
|
56
|
+
})();
|
|
@@ -11,7 +11,7 @@ export declare function CampaignAPIMixin<TBase extends APIConstructor>(Base: TBa
|
|
|
11
11
|
withCounts?: false;
|
|
12
12
|
}): Campaign | null;
|
|
13
13
|
getCampaign(params: GetCampaignParams): Campaign | CampaignWithCounts | null;
|
|
14
|
-
"__#
|
|
14
|
+
"__#129@#sanitizeCampaign"(campaign: Campaign): void;
|
|
15
15
|
name: string;
|
|
16
16
|
db: import("../db").DBInstance;
|
|
17
17
|
logger?: import("../..").Logger | null;
|
|
@@ -13,7 +13,7 @@ export declare function ContentAPIMixin<TBase extends APIConstructor>(Base: TBas
|
|
|
13
13
|
} | null;
|
|
14
14
|
getCollectionList(params: GetCollectionListParams): import("../types/Content.js").CollectionList;
|
|
15
15
|
getPostTagList(params: GetPostTagListParams): import("../types/Content.js").PostTagList;
|
|
16
|
-
"__#
|
|
16
|
+
"__#130@#processPostContentInlineMedia"(post: Post): void;
|
|
17
17
|
name: string;
|
|
18
18
|
db: import("../db").DBInstance;
|
|
19
19
|
logger?: import("../..").Logger | null;
|
|
@@ -3,7 +3,7 @@ import { type FilterData, type MediaFilterSearchParams, type PostFilterSearchPar
|
|
|
3
3
|
export declare function FilterAPIMixin<TBase extends APIConstructor>(Base: TBase): {
|
|
4
4
|
new (...args: any[]): {
|
|
5
5
|
getPostFilterData(campaignId: string): FilterData<PostFilterSearchParams>;
|
|
6
|
-
"__#
|
|
6
|
+
"__#131@#getPostTypeTitle"(postType: string): "Link" | "Audio" | "Image" | "Video" | "Other" | "Text" | "Podcast" | "Poll";
|
|
7
7
|
getProductFilterData(campaignId: string): FilterData<ProductFilterSearchParams>;
|
|
8
8
|
getMediaFilterData(campaignId: string): FilterData<MediaFilterSearchParams>;
|
|
9
9
|
name: string;
|
|
@@ -10,7 +10,7 @@ export declare class APIBase {
|
|
|
10
10
|
constructor(db: DBInstance, logger?: Logger | null);
|
|
11
11
|
static getInstance(db: DBInstance, logger?: Logger | null): {
|
|
12
12
|
getPostFilterData(campaignId: string): import("../types/Filter.js").FilterData<import("../types/Filter.js").PostFilterSearchParams>;
|
|
13
|
-
"__#
|
|
13
|
+
"__#131@#getPostTypeTitle"(postType: string): "Link" | "Audio" | "Image" | "Video" | "Other" | "Text" | "Podcast" | "Poll";
|
|
14
14
|
getProductFilterData(campaignId: string): import("../types/Filter.js").FilterData<import("../types/Filter.js").ProductFilterSearchParams>;
|
|
15
15
|
getMediaFilterData(campaignId: string): import("../types/Filter.js").FilterData<import("../types/Filter.js").MediaFilterSearchParams>;
|
|
16
16
|
name: string;
|
|
@@ -49,7 +49,7 @@ export declare class APIBase {
|
|
|
49
49
|
} | null;
|
|
50
50
|
getCollectionList(params: import("../types/Content.js").GetCollectionListParams): import("../types/Content.js").CollectionList;
|
|
51
51
|
getPostTagList(params: import("../types/Content.js").GetPostTagListParams): import("../types/Content.js").PostTagList;
|
|
52
|
-
"__#
|
|
52
|
+
"__#130@#processPostContentInlineMedia"(post: import("../../index.js").Post): void;
|
|
53
53
|
name: string;
|
|
54
54
|
db: DBInstance;
|
|
55
55
|
logger?: Logger | null;
|
|
@@ -64,7 +64,7 @@ export declare class APIBase {
|
|
|
64
64
|
withCounts?: false;
|
|
65
65
|
}): import("../../index.js").Campaign | null;
|
|
66
66
|
getCampaign(params: import("../types/Campaign.js").GetCampaignParams): import("../../index.js").Campaign | import("../types/Campaign.js").CampaignWithCounts | null;
|
|
67
|
-
"__#
|
|
67
|
+
"__#129@#sanitizeCampaign"(campaign: import("../../index.js").Campaign): void;
|
|
68
68
|
name: string;
|
|
69
69
|
db: DBInstance;
|
|
70
70
|
logger?: Logger | null;
|
|
@@ -77,7 +77,7 @@ export declare class APIBase {
|
|
|
77
77
|
declare const API: {
|
|
78
78
|
new (...args: any[]): {
|
|
79
79
|
getPostFilterData(campaignId: string): import("../types/Filter.js").FilterData<import("../types/Filter.js").PostFilterSearchParams>;
|
|
80
|
-
"__#
|
|
80
|
+
"__#131@#getPostTypeTitle"(postType: string): "Link" | "Audio" | "Image" | "Video" | "Other" | "Text" | "Podcast" | "Poll";
|
|
81
81
|
getProductFilterData(campaignId: string): import("../types/Filter.js").FilterData<import("../types/Filter.js").ProductFilterSearchParams>;
|
|
82
82
|
getMediaFilterData(campaignId: string): import("../types/Filter.js").FilterData<import("../types/Filter.js").MediaFilterSearchParams>;
|
|
83
83
|
name: string;
|
|
@@ -122,7 +122,7 @@ declare const API: {
|
|
|
122
122
|
} | null;
|
|
123
123
|
getCollectionList(params: import("../types/Content.js").GetCollectionListParams): import("../types/Content.js").CollectionList;
|
|
124
124
|
getPostTagList(params: import("../types/Content.js").GetPostTagListParams): import("../types/Content.js").PostTagList;
|
|
125
|
-
"__#
|
|
125
|
+
"__#130@#processPostContentInlineMedia"(post: import("../../index.js").Post): void;
|
|
126
126
|
name: string;
|
|
127
127
|
db: DBInstance;
|
|
128
128
|
logger?: Logger | null;
|
|
@@ -139,7 +139,7 @@ declare const API: {
|
|
|
139
139
|
withCounts?: false;
|
|
140
140
|
}): import("../../index.js").Campaign | null;
|
|
141
141
|
getCampaign(params: import("../types/Campaign.js").GetCampaignParams): import("../../index.js").Campaign | import("../types/Campaign.js").CampaignWithCounts | null;
|
|
142
|
-
"__#
|
|
142
|
+
"__#129@#sanitizeCampaign"(campaign: import("../../index.js").Campaign): void;
|
|
143
143
|
name: string;
|
|
144
144
|
db: DBInstance;
|
|
145
145
|
logger?: Logger | null;
|
|
@@ -7,10 +7,10 @@ export declare function CampaignDBMixin<TBase extends UserDBConstructor>(Base: T
|
|
|
7
7
|
new (...args: any[]): {
|
|
8
8
|
saveCampaign(campaign: Campaign | null, downloadDate: Date, overwriteIfExists?: boolean): void;
|
|
9
9
|
getCampaign(params: GetCampaignParams): Campaign | null;
|
|
10
|
-
"__#
|
|
11
|
-
"__#
|
|
10
|
+
"__#113@#saveRewards"(campaign: Campaign): void;
|
|
11
|
+
"__#113@#doSaveReward"(campaign: Campaign, reward: Reward): void;
|
|
12
12
|
getCampaignList(params: GetCampaignListParams): CampaignList;
|
|
13
|
-
"__#
|
|
13
|
+
"__#113@#getCampaignWithCounts"(params: GetCampaignParams): CampaignWithCounts | null;
|
|
14
14
|
checkCampaignExists(id: string): boolean;
|
|
15
15
|
saveUser(user: import("../../index.js").User | null): void;
|
|
16
16
|
getUserByID(id: string): import("../../index.js").User | null;
|
|
@@ -5,13 +5,13 @@ import { type CampaignDBConstructor } from './CampaignDBMixin.js';
|
|
|
5
5
|
export declare function ContentDBMixin<TBase extends CampaignDBConstructor>(Base: TBase): {
|
|
6
6
|
new (...args: any[]): {
|
|
7
7
|
saveContent(content: Post | Product): void;
|
|
8
|
-
"__#
|
|
9
|
-
"__#
|
|
10
|
-
"__#
|
|
11
|
-
"__#
|
|
12
|
-
"__#
|
|
13
|
-
"__#
|
|
14
|
-
"__#
|
|
8
|
+
"__#114@#saveContentMedia"(content: Post | Product): void;
|
|
9
|
+
"__#114@#savepostMedia"(post: Post): void;
|
|
10
|
+
"__#114@#saveProductMedia"(product: Product): void;
|
|
11
|
+
"__#114@#doSaveContentMedia"(content: Post | Product, media: Downloadable, mediaIndex: number, isPreview: boolean): void;
|
|
12
|
+
"__#114@#publishedAtToTime"(publishedAt: string | null): number | null;
|
|
13
|
+
"__#114@#savePostTiers"(post: Post): void;
|
|
14
|
+
"__#114@#doSaveTier"(post: Post, tier: Tier): void;
|
|
15
15
|
savePostComments(post: Post, comments: Comment[]): void;
|
|
16
16
|
checkPostCommentsExist(post: Post): boolean;
|
|
17
17
|
getContent(id: string, contentType: "post"): PostWithComments | null;
|
|
@@ -26,7 +26,7 @@ export declare function ContentDBMixin<TBase extends CampaignDBConstructor>(Base
|
|
|
26
26
|
* @param row Must have `details`, `comment_count` and `comments`
|
|
27
27
|
* @returns
|
|
28
28
|
*/
|
|
29
|
-
"__#
|
|
29
|
+
"__#114@#parseContentRowJoinedComments"(row: any): any;
|
|
30
30
|
getContentCountByDate(contentType: ContentType, groupBy: "year" | "month", filter?: {
|
|
31
31
|
campaign?: Campaign | string | null;
|
|
32
32
|
date?: Date | null;
|
|
@@ -50,19 +50,19 @@ export declare function ContentDBMixin<TBase extends CampaignDBConstructor>(Base
|
|
|
50
50
|
collection: Collection;
|
|
51
51
|
campaignId: string;
|
|
52
52
|
} | null;
|
|
53
|
-
"__#
|
|
53
|
+
"__#114@#savePostCollection"(post: Post): void;
|
|
54
54
|
getCollectionList(params: GetCollectionListParams): CollectionList;
|
|
55
55
|
checkCollectionExists(id: string): boolean;
|
|
56
56
|
checkPostTagExists(id: string, campaign: Campaign | null): boolean;
|
|
57
57
|
getPostComments(post: Post | string): Comment[] | null;
|
|
58
|
-
"__#
|
|
58
|
+
"__#114@#savePostTags"(post: Post): void;
|
|
59
59
|
getPostTagList(params: GetPostTagListParams): PostTagList;
|
|
60
60
|
saveCampaign(campaign: Campaign | null, downloadDate: Date, overwriteIfExists?: boolean): void;
|
|
61
61
|
getCampaign(params: import("../types/Campaign").GetCampaignParams): Campaign | null;
|
|
62
|
-
"__#
|
|
63
|
-
"__#
|
|
62
|
+
"__#113@#saveRewards"(campaign: Campaign): void;
|
|
63
|
+
"__#113@#doSaveReward"(campaign: Campaign, reward: import("../../entities").Reward): void;
|
|
64
64
|
getCampaignList(params: import("../types/Campaign").GetCampaignListParams): import("../types/Campaign").CampaignList;
|
|
65
|
-
"__#
|
|
65
|
+
"__#113@#getCampaignWithCounts"(params: import("../types/Campaign").GetCampaignParams): import("../types/Campaign").CampaignWithCounts | null;
|
|
66
66
|
checkCampaignExists(id: string): boolean;
|
|
67
67
|
saveUser(user: import("../../entities").User | null): void;
|
|
68
68
|
getUserByID(id: string): import("../../entities").User | null;
|