patreon-dl 3.4.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 +22 -2
- 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/ContentAPIMixin.d.ts +7 -1
- package/dist/browse/api/ContentAPIMixin.js +20 -0
- package/dist/browse/api/ContentAPIMixin.js.map +1 -1
- package/dist/browse/api/FilterAPIMixin.js +29 -2
- package/dist/browse/api/FilterAPIMixin.js.map +1 -1
- package/dist/browse/api/index.d.ts +12 -0
- package/dist/browse/db/CampaignDBMixin.d.ts +3 -3
- package/dist/browse/db/CampaignDBMixin.js +13 -1
- package/dist/browse/db/CampaignDBMixin.js.map +1 -1
- package/dist/browse/db/CollectionFTS.d.ts +2 -0
- package/dist/browse/db/CollectionFTS.js +68 -0
- package/dist/browse/db/CollectionFTS.js.map +1 -0
- package/dist/browse/db/ContentDBMixin.d.ts +25 -12
- package/dist/browse/db/ContentDBMixin.js +412 -15
- package/dist/browse/db/ContentDBMixin.js.map +1 -1
- package/dist/browse/db/Init.js +63 -5
- package/dist/browse/db/Init.js.map +1 -1
- package/dist/browse/db/MediaDBMixin.js +2 -6
- package/dist/browse/db/MediaDBMixin.js.map +1 -1
- package/dist/browse/db/PostFTS.d.ts +4 -0
- package/dist/browse/db/PostFTS.js +163 -0
- package/dist/browse/db/PostFTS.js.map +1 -0
- package/dist/browse/db/ProductFTS.d.ts +4 -0
- package/dist/browse/db/ProductFTS.js +163 -0
- package/dist/browse/db/ProductFTS.js.map +1 -0
- package/dist/browse/db/Update.js +3 -1
- package/dist/browse/db/Update.js.map +1 -1
- package/dist/browse/db/index.d.ts +26 -14
- package/dist/browse/db/updaters/DBUpdater_1_2_0.d.ts +2 -0
- package/dist/browse/db/updaters/DBUpdater_1_2_0.js +13 -0
- package/dist/browse/db/updaters/DBUpdater_1_2_0.js.map +1 -0
- package/dist/browse/server/Router.js +12 -1
- package/dist/browse/server/Router.js.map +1 -1
- package/dist/browse/server/handler/ContentAPIRequestHandler.d.ts +3 -0
- package/dist/browse/server/handler/ContentAPIRequestHandler.js +34 -2
- package/dist/browse/server/handler/ContentAPIRequestHandler.js.map +1 -1
- package/dist/browse/types/Campaign.d.ts +2 -0
- package/dist/browse/types/Campaign.js.map +1 -1
- package/dist/browse/types/Content.d.ts +43 -2
- package/dist/browse/types/Content.js.map +1 -1
- package/dist/browse/types/Filter.d.ts +6 -3
- package/dist/browse/types/Filter.js.map +1 -1
- package/dist/browse/web/assets/{index-b301OTnD.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/CLIOptions.js +20 -1
- package/dist/cli/CLIOptions.js.map +1 -1
- package/dist/cli/CommandLineParser.js +2 -1
- package/dist/cli/CommandLineParser.js.map +1 -1
- package/dist/cli/ConfigFileParser.js +8 -0
- package/dist/cli/ConfigFileParser.js.map +1 -1
- package/dist/cli/index.js +21 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/downloaders/Bootstrap.d.ts +11 -1
- package/dist/downloaders/Bootstrap.js +14 -1
- package/dist/downloaders/Bootstrap.js.map +1 -1
- package/dist/downloaders/Downloader.d.ts +7 -3
- package/dist/downloaders/Downloader.js +159 -109
- package/dist/downloaders/Downloader.js.map +1 -1
- package/dist/downloaders/DownloaderEvent.d.ts +6 -6
- package/dist/downloaders/DownloaderEvent.js.map +1 -1
- package/dist/downloaders/DownloaderOptions.d.ts +14 -1
- package/dist/downloaders/DownloaderOptions.js +10 -0
- package/dist/downloaders/DownloaderOptions.js.map +1 -1
- package/dist/downloaders/InitialData.d.ts +16 -0
- package/dist/downloaders/InitialData.js +94 -0
- package/dist/downloaders/InitialData.js.map +1 -0
- package/dist/downloaders/PostDownloader.d.ts +11 -2
- package/dist/downloaders/PostDownloader.js +337 -277
- package/dist/downloaders/PostDownloader.js.map +1 -1
- package/dist/downloaders/PostsFetcher.d.ts +5 -5
- package/dist/downloaders/PostsFetcher.js +24 -75
- package/dist/downloaders/PostsFetcher.js.map +1 -1
- package/dist/downloaders/ProductDownloader.js +367 -165
- package/dist/downloaders/ProductDownloader.js.map +1 -1
- package/dist/downloaders/ProductsFetcher.d.ts +57 -0
- package/dist/downloaders/ProductsFetcher.js +331 -0
- package/dist/downloaders/ProductsFetcher.js.map +1 -0
- package/dist/downloaders/index.d.ts +1 -1
- package/dist/downloaders/index.js.map +1 -1
- package/dist/downloaders/task/DownloadTaskFactory.js +15 -1
- package/dist/downloaders/task/DownloadTaskFactory.js.map +1 -1
- package/dist/downloaders/templates/CollectionInfo.d.ts +2 -0
- package/dist/downloaders/templates/CollectionInfo.js +20 -0
- package/dist/downloaders/templates/CollectionInfo.js.map +1 -0
- package/dist/entities/Comment.d.ts +3 -3
- package/dist/entities/Comment.js.map +1 -1
- package/dist/entities/{Collection.d.ts → List.d.ts} +1 -1
- package/dist/entities/List.js +2 -0
- package/dist/entities/List.js.map +1 -0
- package/dist/entities/MediaItem.d.ts +17 -2
- package/dist/entities/MediaItem.js.map +1 -1
- package/dist/entities/Post.d.ts +34 -3
- package/dist/entities/Post.js.map +1 -1
- package/dist/entities/Product.d.ts +25 -0
- package/dist/entities/Product.js +6 -1
- package/dist/entities/Product.js.map +1 -1
- package/dist/entities/index.d.ts +1 -1
- package/dist/entities/index.js.map +1 -1
- package/dist/parsers/CommentParser.d.ts +3 -3
- package/dist/parsers/CommentParser.js.map +1 -1
- package/dist/parsers/Parser.d.ts +5 -1
- package/dist/parsers/Parser.js +70 -0
- package/dist/parsers/Parser.js.map +1 -1
- package/dist/parsers/PostParser.d.ts +2 -2
- package/dist/parsers/PostParser.js +34 -0
- package/dist/parsers/PostParser.js.map +1 -1
- package/dist/parsers/ProductParser.d.ts +3 -2
- package/dist/parsers/ProductParser.js +115 -49
- package/dist/parsers/ProductParser.js.map +1 -1
- package/dist/utils/FSHelper.d.ts +12 -8
- package/dist/utils/FSHelper.js +10 -0
- package/dist/utils/FSHelper.js.map +1 -1
- package/dist/utils/Fetcher.js +1 -1
- package/dist/utils/Fetcher.js.map +1 -1
- package/dist/utils/FilenameFormatHelper.d.ts +2 -1
- package/dist/utils/FilenameFormatHelper.js +14 -1
- package/dist/utils/FilenameFormatHelper.js.map +1 -1
- package/dist/utils/Misc.d.ts +2 -1
- package/dist/utils/Misc.js +38 -5
- package/dist/utils/Misc.js.map +1 -1
- package/dist/utils/URLHelper.d.ts +7 -0
- package/dist/utils/URLHelper.js +161 -3
- package/dist/utils/URLHelper.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 +13 -5
- package/dist/browse/web/assets/index-C5gLqRAU.js +0 -209
- package/dist/entities/Collection.js +0 -2
- package/dist/entities/Collection.js.map +0 -1
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
|
|
|
@@ -116,7 +116,12 @@ $ patreon-dl [OPTION]... URL
|
|
|
116
116
|
#### Supported URL formats
|
|
117
117
|
|
|
118
118
|
```
|
|
119
|
-
// Download a
|
|
119
|
+
// Download products from a creator's shop
|
|
120
|
+
https://www.patreon.com/<creator>/shop
|
|
121
|
+
https://www.patreon.com/c/<creator>/shop
|
|
122
|
+
https://www.patreon.com/cw/<creator>/shop
|
|
123
|
+
|
|
124
|
+
// Download a single product
|
|
120
125
|
https://www.patreon.com/<creator>/shop/<slug>-<product_id>
|
|
121
126
|
|
|
122
127
|
// Download posts by creator
|
|
@@ -288,6 +293,21 @@ Note the URL shown in the output. Open this URL in a web browser to begin viewin
|
|
|
288
293
|
|
|
289
294
|
## Changelog
|
|
290
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
|
+
|
|
302
|
+
v3.5.0
|
|
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.
|
|
304
|
+
- Add `productsPublished` / `products.published.after` / `products.published.before` option to set publish date criteria of products included in download.
|
|
305
|
+
- Since `stopOn` / `stop.on` option now also applies to products, the `postPreviouslyDownloaded` and `postPublishDateOutOfRange` values have been deprecated in favor of `previouslyDownloaded` and `publishDateOutOfRange`, respectively.
|
|
306
|
+
- Add Collections support. Collection info is now saved when downloading posts. This means you can browse posts by collection. ([#107](https://github.com/patrickkfkan/patreon-dl/issues/107))
|
|
307
|
+
- (Browse) Add search functionality ([#106](https://github.com/patrickkfkan/patreon-dl/issues/106))
|
|
308
|
+
- Add Tags support. Tag info is now saved when downloading posts. This means you can filter posts by tag.
|
|
309
|
+
- Add `include.mediaThumbnails` option
|
|
310
|
+
|
|
291
311
|
v3.4.0
|
|
292
312
|
- Fix "no posts found" on "cw" pages ([patreon-dl-gui#30](https://github.com/patrickkfkan/patreon-dl-gui/issues/30))
|
|
293
313
|
- Fix YouTube streams returning 403 error ([patreon-dl-gui#31](https://github.com/patrickkfkan/patreon-dl-gui/issues/31))
|
|
@@ -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
|
+
})();
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { type APIConstructor } from ".";
|
|
2
2
|
import { type Product, type Post } from "../../entities";
|
|
3
|
-
import { type GetContentContext, type ContentType, type GetContentListParams } from "../types/Content.js";
|
|
3
|
+
import { type GetContentContext, type ContentType, type GetContentListParams, type GetCollectionListParams, type GetPostTagListParams } from "../types/Content.js";
|
|
4
4
|
export declare function ContentAPIMixin<TBase extends APIConstructor>(Base: TBase): {
|
|
5
5
|
new (...args: any[]): {
|
|
6
6
|
getContentList<T extends ContentType>(params: GetContentListParams<T>): import("../types/Content.js").ContentList<T>;
|
|
7
7
|
getPost(id: string): import("../types/Content.js").PostWithComments | null;
|
|
8
8
|
getProduct(id: string): Product | null;
|
|
9
9
|
getPreviousNextContent<T extends ContentType>(content: Post | Product, context: GetContentContext<T>): import("../types/Content.js").GetPreviousNextContentResult<T>;
|
|
10
|
+
getCollection(id: string): {
|
|
11
|
+
collection: import("../../entities").Collection;
|
|
12
|
+
campaignId: string;
|
|
13
|
+
} | null;
|
|
14
|
+
getCollectionList(params: GetCollectionListParams): import("../types/Content.js").CollectionList;
|
|
15
|
+
getPostTagList(params: GetPostTagListParams): import("../types/Content.js").PostTagList;
|
|
10
16
|
"__#130@#processPostContentInlineMedia"(post: Post): void;
|
|
11
17
|
name: string;
|
|
12
18
|
db: import("../db").DBInstance;
|
|
@@ -8,6 +8,8 @@ import RawDataExtractor from '../web/utils/RawDataExtractor.js';
|
|
|
8
8
|
import { URLHelper } from '../../utils/index.js';
|
|
9
9
|
const DEFAULT_CONTENT_LIST_SIZE = 10;
|
|
10
10
|
const DEFAULT_CONTENT_LIST_SORT_BY = 'a-z';
|
|
11
|
+
const DEFAULT_COLLECTION_LIST_SIZE = 10;
|
|
12
|
+
const DEFAULT_COLLECTION_LIST_SORT_BY = 'a-z';
|
|
11
13
|
export function ContentAPIMixin(Base) {
|
|
12
14
|
var _ContentAPI_instances, _ContentAPI_processPostContentInlineMedia, _a;
|
|
13
15
|
return _a = class ContentAPI extends Base {
|
|
@@ -52,6 +54,24 @@ export function ContentAPIMixin(Base) {
|
|
|
52
54
|
getPreviousNextContent(content, context) {
|
|
53
55
|
return this.db.getPreviousNextContent(content, context);
|
|
54
56
|
}
|
|
57
|
+
getCollection(id) {
|
|
58
|
+
return this.db.getCollection(id);
|
|
59
|
+
}
|
|
60
|
+
getCollectionList(params) {
|
|
61
|
+
const { search = '', sortBy = DEFAULT_COLLECTION_LIST_SORT_BY, limit = DEFAULT_COLLECTION_LIST_SIZE, offset = 0 } = params;
|
|
62
|
+
return this.db.getCollectionList({
|
|
63
|
+
campaign: params.campaign,
|
|
64
|
+
search,
|
|
65
|
+
sortBy,
|
|
66
|
+
limit,
|
|
67
|
+
offset
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
getPostTagList(params) {
|
|
71
|
+
return this.db.getPostTagList({
|
|
72
|
+
campaign: params.campaign,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
55
75
|
},
|
|
56
76
|
_ContentAPI_instances = new WeakSet(),
|
|
57
77
|
_ContentAPI_processPostContentInlineMedia = function _ContentAPI_processPostContentInlineMedia(post) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContentAPIMixin.js","sourceRoot":"","sources":["../../../src/browse/api/ContentAPIMixin.ts"],"names":[],"mappings":"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","sourcesContent":["import { load as cheerioLoad } from 'cheerio';\nimport { type APIConstructor } from \".\";\nimport { type Product, type Post } from \"../../entities\";\nimport { type GetContentContext, type ContentListSortBy, type ContentType, type GetContentListParams } from \"../types/Content.js\";\nimport RawDataExtractor from '../web/utils/RawDataExtractor.js';\nimport { URLHelper } from '../../utils/index.js';\n\nconst DEFAULT_CONTENT_LIST_SIZE = 10;\nconst DEFAULT_CONTENT_LIST_SORT_BY: ContentListSortBy = 'a-z';\n\nexport function ContentAPIMixin<TBase extends APIConstructor>(Base: TBase) {\n return class ContentAPI extends Base {\n getContentList<T extends ContentType>(params: GetContentListParams<T>) {\n const { sortBy = DEFAULT_CONTENT_LIST_SORT_BY, limit = DEFAULT_CONTENT_LIST_SIZE, offset = 0 } = params;\n const list = this.db.getContentList({\n ...params,\n sortBy,\n limit,\n offset,\n });\n for (const item of list.items) {\n switch (item.type) {\n case 'post':\n this.#processPostContentInlineMedia(item);\n item.content = this.sanitizeHTML(item.content || '');\n break;\n case 'product': {\n const description = RawDataExtractor.getProductRichTextDescription(item);\n item.description = description ? this.sanitizeHTML(description) : null;\n break;\n }\n }\n }\n return list;\n }\n\n getPost(id: string) {\n const post = this.db.getContent(id, 'post');\n if (post) {\n this.#processPostContentInlineMedia(post);\n post.content = this.sanitizeHTML(post.content || '');\n }\n return post;\n }\n\n getProduct(id: string) {\n return this.db.getContent(id, 'product');\n }\n\n getPreviousNextContent<T extends ContentType>(content: Post | Product, context: GetContentContext<T>) {\n return this.db.getPreviousNextContent(content, context);\n }\n\n #processPostContentInlineMedia(post: Post) {\n const html = post.content || '';\n const hasImages = post.images.length > 0;\n const hasLinkedAttachments = post.linkedAttachments && post.linkedAttachments.length > 0;\n if (!html || (!hasImages && !hasLinkedAttachments)) {\n return;\n }\n\n const $ = cheerioLoad(html);\n const replacedMediaIds: string[] = [];\n let hasModified = false;\n\n if (hasImages) {\n $('img').each((_, _el) => {\n const el = $(_el);\n const id = el.attr('data-media-id');\n const matched = id ? post.images.find(img => img.id === id && img.downloaded) : null;\n const src = matched ? `/media/${matched.id}` : el.attr('src');\n const imgEl = $('<img>').attr('src', src);\n const aEl = $('<a>')\n .attr('href', src)\n .attr('class', 'lightgallery-item')\n .append(imgEl);\n const wrapperEl = $('<div>')\n .attr('class', 'post-card__inline-media-wrapper')\n .append(aEl);\n if (!matched) {\n const caption = \"(Externally hosted - not stored locally)\";\n wrapperEl.append(\n $('<span>').attr('class', 'post-card__inline-media-caption').append(caption)\n );\n }\n el.replaceWith(wrapperEl);\n if (id && matched) {\n replacedMediaIds.push(id);\n }\n });\n if (replacedMediaIds.length > 0) {\n hasModified = true;\n // Remove images that have been inlined\n post.images = post.images.filter((img) => !replacedMediaIds.includes(img.id));\n }\n }\n\n // Linked attachments\n if (hasLinkedAttachments) {\n $('a').each((_, _el) => {\n const aEl = $(_el);\n const href = aEl.attr('href') || '';\n const { validated, ownerId, mediaId } = URLHelper.isAttachmentLink(href);\n if (validated) {\n let modifiedPath: string | undefined;\n if (post.id !== ownerId) {\n const isDownloaded = post.linkedAttachments?.find((att) => att.postId === ownerId && att.mediaId === mediaId)?.downloadable?.downloaded;\n modifiedPath = isDownloaded && `/media/${mediaId}?lapid=${post.id}`;\n }\n else {\n const isDownloaded = post.attachments.find((att) => att.id === mediaId)?.downloaded;\n modifiedPath = isDownloaded && `/media/${mediaId}`;\n }\n if (modifiedPath) {\n aEl.attr('href', modifiedPath);\n hasModified = true;\n }\n }\n });\n }\n\n if (hasModified) {\n post.content = $.html();\n }\n }\n }\n}"]}
|
|
1
|
+
{"version":3,"file":"ContentAPIMixin.js","sourceRoot":"","sources":["../../../src/browse/api/ContentAPIMixin.ts"],"names":[],"mappings":"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","sourcesContent":["import { load as cheerioLoad } from 'cheerio';\nimport { type APIConstructor } from \".\";\nimport { type Product, type Post } from \"../../entities\";\nimport { type GetContentContext, type ContentListSortBy, type ContentType, type GetContentListParams, type GetCollectionListParams, type CollectionListSortBy, type GetPostTagListParams } from \"../types/Content.js\";\nimport RawDataExtractor from '../web/utils/RawDataExtractor.js';\nimport { URLHelper } from '../../utils/index.js';\n\nconst DEFAULT_CONTENT_LIST_SIZE = 10;\nconst DEFAULT_CONTENT_LIST_SORT_BY: ContentListSortBy = 'a-z';\n\nconst DEFAULT_COLLECTION_LIST_SIZE = 10;\nconst DEFAULT_COLLECTION_LIST_SORT_BY: CollectionListSortBy = 'a-z';\n\nexport function ContentAPIMixin<TBase extends APIConstructor>(Base: TBase) {\n return class ContentAPI extends Base {\n getContentList<T extends ContentType>(params: GetContentListParams<T>) {\n const { sortBy = DEFAULT_CONTENT_LIST_SORT_BY, limit = DEFAULT_CONTENT_LIST_SIZE, offset = 0 } = params;\n const list = this.db.getContentList({\n ...params,\n sortBy,\n limit,\n offset,\n });\n for (const item of list.items) {\n switch (item.type) {\n case 'post':\n this.#processPostContentInlineMedia(item);\n item.content = this.sanitizeHTML(item.content || '');\n break;\n case 'product': {\n const description = RawDataExtractor.getProductRichTextDescription(item);\n item.description = description ? this.sanitizeHTML(description) : null;\n break;\n }\n }\n }\n return list;\n }\n\n getPost(id: string) {\n const post = this.db.getContent(id, 'post');\n if (post) {\n this.#processPostContentInlineMedia(post);\n post.content = this.sanitizeHTML(post.content || '');\n }\n return post;\n }\n\n getProduct(id: string) {\n return this.db.getContent(id, 'product');\n }\n\n getPreviousNextContent<T extends ContentType>(content: Post | Product, context: GetContentContext<T>) {\n return this.db.getPreviousNextContent(content, context);\n }\n\n getCollection(id: string) {\n return this.db.getCollection(id);\n }\n\n getCollectionList(params: GetCollectionListParams) {\n const {\n search = '',\n sortBy = DEFAULT_COLLECTION_LIST_SORT_BY,\n limit = DEFAULT_COLLECTION_LIST_SIZE,\n offset = 0\n } = params;\n return this.db.getCollectionList({\n campaign: params.campaign,\n search,\n sortBy,\n limit,\n offset\n });\n }\n\n getPostTagList(params: GetPostTagListParams) {\n return this.db.getPostTagList({\n campaign: params.campaign,\n });\n }\n\n #processPostContentInlineMedia(post: Post) {\n const html = post.content || '';\n const hasImages = post.images.length > 0;\n const hasLinkedAttachments = post.linkedAttachments && post.linkedAttachments.length > 0;\n if (!html || (!hasImages && !hasLinkedAttachments)) {\n return;\n }\n\n const $ = cheerioLoad(html);\n const replacedMediaIds: string[] = [];\n let hasModified = false;\n\n if (hasImages) {\n $('img').each((_, _el) => {\n const el = $(_el);\n const id = el.attr('data-media-id');\n const matched = id ? post.images.find(img => img.id === id && img.downloaded) : null;\n const src = matched ? `/media/${matched.id}` : el.attr('src');\n const imgEl = $('<img>').attr('src', src);\n const aEl = $('<a>')\n .attr('href', src)\n .attr('class', 'lightgallery-item')\n .append(imgEl);\n const wrapperEl = $('<div>')\n .attr('class', 'post-card__inline-media-wrapper')\n .append(aEl);\n if (!matched) {\n const caption = \"(Externally hosted - not stored locally)\";\n wrapperEl.append(\n $('<span>').attr('class', 'post-card__inline-media-caption').append(caption)\n );\n }\n el.replaceWith(wrapperEl);\n if (id && matched) {\n replacedMediaIds.push(id);\n }\n });\n if (replacedMediaIds.length > 0) {\n hasModified = true;\n // Remove images that have been inlined\n post.images = post.images.filter((img) => !replacedMediaIds.includes(img.id));\n }\n }\n\n // Linked attachments\n if (hasLinkedAttachments) {\n $('a').each((_, _el) => {\n const aEl = $(_el);\n const href = aEl.attr('href') || '';\n const { validated, ownerId, mediaId } = URLHelper.isAttachmentLink(href);\n if (validated) {\n let modifiedPath: string | undefined;\n if (post.id !== ownerId) {\n const isDownloaded = post.linkedAttachments?.find((att) => att.postId === ownerId && att.mediaId === mediaId)?.downloadable?.downloaded;\n modifiedPath = isDownloaded && `/media/${mediaId}?lapid=${post.id}`;\n }\n else {\n const isDownloaded = post.attachments.find((att) => att.id === mediaId)?.downloaded;\n modifiedPath = isDownloaded && `/media/${mediaId}`;\n }\n if (modifiedPath) {\n aEl.attr('href', modifiedPath);\n hasModified = true;\n }\n }\n });\n }\n\n if (hasModified) {\n post.content = $.html();\n }\n }\n }\n}"]}
|
|
@@ -134,7 +134,27 @@ export function FilterAPIMixin(Base) {
|
|
|
134
134
|
searchParam: 'date_published',
|
|
135
135
|
options: datePublishedOptions
|
|
136
136
|
});
|
|
137
|
-
|
|
137
|
+
const { tags } = this.db.getPostTagList({ campaign: campaignId });
|
|
138
|
+
if (tags.length > 0) {
|
|
139
|
+
const tagOptions = tags.map((tag) => ({
|
|
140
|
+
title: tag.value,
|
|
141
|
+
value: tag.id
|
|
142
|
+
}));
|
|
143
|
+
sections.push({
|
|
144
|
+
title: 'Tagged',
|
|
145
|
+
displayHint: 'pill_small',
|
|
146
|
+
searchParam: 'tag_id',
|
|
147
|
+
options: tagOptions
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
sections,
|
|
152
|
+
external: [
|
|
153
|
+
{
|
|
154
|
+
searchParam: 'search'
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
};
|
|
138
158
|
}
|
|
139
159
|
getProductFilterData(campaignId) {
|
|
140
160
|
const productCountByYear = this.db.getContentCountByDate('product', 'year', {
|
|
@@ -208,7 +228,14 @@ export function FilterAPIMixin(Base) {
|
|
|
208
228
|
searchParam: 'date_published',
|
|
209
229
|
options: datePublishedOptions
|
|
210
230
|
});
|
|
211
|
-
return {
|
|
231
|
+
return {
|
|
232
|
+
sections,
|
|
233
|
+
external: [
|
|
234
|
+
{
|
|
235
|
+
searchParam: 'search'
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
};
|
|
212
239
|
}
|
|
213
240
|
getMediaFilterData(campaignId) {
|
|
214
241
|
const mediaCountByTier = this.db.getMediaCountByTier(campaignId);
|