patreon-dl 3.0.0 → 3.2.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.
Files changed (81) hide show
  1. package/README.md +30 -5
  2. package/bin/patreon-dl-vimeo.js +17 -7
  3. package/dist/browse/api/CampaignAPIMixin.d.ts +2 -0
  4. package/dist/browse/api/CampaignAPIMixin.js +43 -13
  5. package/dist/browse/api/CampaignAPIMixin.js.map +1 -1
  6. package/dist/browse/api/ContentAPIMixin.d.ts +4 -1
  7. package/dist/browse/api/ContentAPIMixin.js +84 -16
  8. package/dist/browse/api/ContentAPIMixin.js.map +1 -1
  9. package/dist/browse/api/FilterAPIMixin.d.ts +2 -1
  10. package/dist/browse/api/MediaAPIMixin.d.ts +1 -0
  11. package/dist/browse/api/SettingsAPIMixin.d.ts +1 -0
  12. package/dist/browse/api/index.d.ts +17 -2
  13. package/dist/browse/api/index.js +11 -0
  14. package/dist/browse/api/index.js.map +1 -1
  15. package/dist/browse/db/CampaignDBMixin.d.ts +4 -4
  16. package/dist/browse/db/ContentDBMixin.d.ts +12 -12
  17. package/dist/browse/db/EnvDBMixin.d.ts +1 -1
  18. package/dist/browse/db/MediaDBMixin.d.ts +1 -1
  19. package/dist/browse/db/UserDBMixin.d.ts +1 -1
  20. package/dist/browse/db/index.d.ts +19 -19
  21. package/dist/browse/web/assets/index-BqQSEIOs.js +209 -0
  22. package/dist/browse/web/assets/index-USVypdWT.css +1 -0
  23. package/dist/browse/web/index.html +2 -2
  24. package/dist/browse/web/utils/RawDataExtractor.d.ts +5 -0
  25. package/dist/browse/web/utils/RawDataExtractor.js +17 -0
  26. package/dist/browse/web/utils/RawDataExtractor.js.map +1 -0
  27. package/dist/cli/CLIOptionValidator.d.ts +1 -1
  28. package/dist/cli/CLIOptions.d.ts +1 -1
  29. package/dist/cli/CLIOptions.js +2 -1
  30. package/dist/cli/CLIOptions.js.map +1 -1
  31. package/dist/cli/CommandLineParser.js +2 -1
  32. package/dist/cli/CommandLineParser.js.map +1 -1
  33. package/dist/cli/ConfigFileParser.js +4 -2
  34. package/dist/cli/ConfigFileParser.js.map +1 -1
  35. package/dist/cli/index.js +24 -0
  36. package/dist/cli/index.js.map +1 -1
  37. package/dist/cli/server/ServerCLIOptions.d.ts +1 -1
  38. package/dist/downloaders/Downloader.d.ts +17 -4
  39. package/dist/downloaders/Downloader.js +35 -10
  40. package/dist/downloaders/Downloader.js.map +1 -1
  41. package/dist/downloaders/DownloaderOptions.d.ts +1 -0
  42. package/dist/downloaders/DownloaderOptions.js +4 -1
  43. package/dist/downloaders/DownloaderOptions.js.map +1 -1
  44. package/dist/downloaders/PostDownloader.js +13 -6
  45. package/dist/downloaders/PostDownloader.js.map +1 -1
  46. package/dist/downloaders/PostsFetcher.js +10 -3
  47. package/dist/downloaders/PostsFetcher.js.map +1 -1
  48. package/dist/downloaders/ProductDownloader.js +3 -2
  49. package/dist/downloaders/ProductDownloader.js.map +1 -1
  50. package/dist/downloaders/task/DownloadTask.js +1 -1
  51. package/dist/downloaders/task/DownloadTask.js.map +1 -1
  52. package/dist/downloaders/task/DownloadTaskFactory.d.ts +1 -0
  53. package/dist/downloaders/task/DownloadTaskFactory.js +2 -1
  54. package/dist/downloaders/task/DownloadTaskFactory.js.map +1 -1
  55. package/dist/downloaders/task/FFmpegDownloadTaskBase.d.ts +1 -0
  56. package/dist/downloaders/task/FFmpegDownloadTaskBase.js +37 -2
  57. package/dist/downloaders/task/FFmpegDownloadTaskBase.js.map +1 -1
  58. package/dist/downloaders/task/FetcherDownloadTask.d.ts +1 -0
  59. package/dist/downloaders/task/FetcherDownloadTask.js +30 -18
  60. package/dist/downloaders/task/FetcherDownloadTask.js.map +1 -1
  61. package/dist/downloaders/task/M3U8DownloadTask.js +10 -6
  62. package/dist/downloaders/task/M3U8DownloadTask.js.map +1 -1
  63. package/dist/downloaders/task/YouTubeDownloadTask.js +9 -2
  64. package/dist/downloaders/task/YouTubeDownloadTask.js.map +1 -1
  65. package/dist/downloaders/task/YouTubeStreamDownloadTask.js +31 -14
  66. package/dist/downloaders/task/YouTubeStreamDownloadTask.js.map +1 -1
  67. package/dist/parsers/PageParser.js +35 -0
  68. package/dist/parsers/PageParser.js.map +1 -1
  69. package/dist/utils/FSHelper.js +6 -5
  70. package/dist/utils/FSHelper.js.map +1 -1
  71. package/dist/utils/Fetcher.d.ts +8 -1
  72. package/dist/utils/Fetcher.js +31 -5
  73. package/dist/utils/Fetcher.js.map +1 -1
  74. package/dist/utils/logging/ConsoleLogger.js +6 -0
  75. package/dist/utils/logging/ConsoleLogger.js.map +1 -1
  76. package/dist/utils/yt/InnertubeLoader.d.ts +0 -1
  77. package/dist/utils/yt/InnertubeLoader.js +9 -196
  78. package/dist/utils/yt/InnertubeLoader.js.map +1 -1
  79. package/package.json +4 -6
  80. package/dist/browse/web/assets/index-DCSXR5HZ.css +0 -1
  81. package/dist/browse/web/assets/index-DTUJdO9u.js +0 -250
package/README.md CHANGED
@@ -8,7 +8,7 @@ This repo contains the `patreon-dl` library and its command-line tool. For GUI a
8
8
 
9
9
  ### Features
10
10
  - Access to patron-only content through cookie. This refers to content you have access to under your account. It does not include locked content that you don't have a subscription for.
11
- - Download posts by user, in a collection or single post
11
+ - Download posts by user, in a collection or single post.
12
12
  - Download products (aka shop purchases)
13
13
  - Items included in downloads:
14
14
  - videos
@@ -17,9 +17,10 @@ This repo contains the `patreon-dl` library and its command-line tool. For GUI a
17
17
  - attachments
18
18
  - embedded videos
19
19
  - YouTube downloader built-in
20
- - Supports [external downloader](#embedded-videos---external-downloader)
20
+ - Supports [external downloader](#embedded-videos--links---external-downloader)
21
21
  - Save campaign and content info
22
22
  - Extensively configurable
23
+ - Browse downloaded content through integrated web server
23
24
 
24
25
  You can run `patreon-dl` from the command-line or [use it as a library](./docs/Library.md) for your project. Node.js v20 or higher required.
25
26
 
@@ -29,6 +30,8 @@ You can run `patreon-dl` from the command-line or [use it as a library](./docs/L
29
30
  - YouTube video link - in which case the video is downloaded; or
30
31
  - An external downloader is configured for the link provider.
31
32
 
33
+ For information on external downloaders, see the [Embedded videos / links - external downloader](#embedded-videos--links---external-downloader) section. Example config is provided for fetching YouTube (replacing the built-in downloader) and Vimeo videos.
34
+
32
35
  ### FFmpeg dependency
33
36
 
34
37
  [FFmpeg](https://ffmpeg.org) is required when downloading:
@@ -49,9 +52,9 @@ $ patreon-dl --configure-youtube
49
52
 
50
53
  ### Embedded videos / links - external downloader
51
54
 
52
- You can specify external programs to download embedded videos or from embedded links. For YouTube videos, this will replace the built-in downloader. See the [example config](./example-embed.conf) on how to do this.
55
+ You can specify external programs to download embedded videos or from embedded links. For YouTube videos, this will replace the built-in downloader.
53
56
 
54
- > The example config utilizes [yt-dlp](https://github.com/yt-dlp/yt-dlp), a popular program capable of downloading YouTube and Vimeo content. As of current release, `yt-dlp` is also able to download Premium-quality YouTube videos without a Premium account.
57
+ See the [example config](./example-embed.conf) on how to configure an external downloader to fetch YouTube and Vimeo videos through [yt-dlp](https://github.com/yt-dlp/yt-dlp). For Vimeo videos, a [helper script](./bin/patreon-dl-vimeo.js) bundled with `patreon-dl` is used.
55
58
 
56
59
  ## Installation
57
60
 
@@ -242,7 +245,7 @@ $ patreon-dl-server [OPTION]
242
245
  | `--log-file <file>` | `-f` | Save logs to `<file>`. |
243
246
 
244
247
 
245
- ### Example
248
+ ### Example usage
246
249
 
247
250
  Say you downloaded something with `patreon-dl`:
248
251
 
@@ -264,6 +267,28 @@ Note the URL shown in the output. Open this URL in a web browser to begin viewin
264
267
 
265
268
  ## Changelog
266
269
 
270
+ v3.2.0
271
+ - Fix:
272
+ - "Initial data not found" error in `patreon.com/cw` pages ([#85](https://github.com/patrickkfkan/patreon-dl/issues/85)) and custom-domain pages
273
+ - FFmpeg v7.x compatibility issues ([#86](https://github.com/patrickkfkan/patreon-dl/issues/86))
274
+ - Dry-run mode executing ops that should have been skipped
275
+ - Wrong log file path returned in some cases
276
+ - Various YouTube downloading issues
277
+ - Add:
278
+ - Support passing options to `yt-dlp` in Vimeo download script
279
+ - Support case-sensitivity flag in `config.include.mediaByFilename` options
280
+ - Browse:
281
+ - Show inline images within post body
282
+ - Show YouTube embed HTML content if video not downloaded ([#87](https://github.com/patrickkfkan/patreon-dl/issues/87))
283
+ - Display "show more" toggle for long post bodies
284
+ - API:
285
+ - `Downloader.getCampaign(params)`: enable lookup by `params.campaignId`
286
+
287
+ v3.1.0
288
+ - Defer database initialization until downloader starts
289
+ - UI: fix post column width possibly exceeding screen width
290
+ - Add `request.userAgent` option
291
+
267
292
  v3.0.0
268
293
  - Add support for browsing downloaded content through integrated web server. Note: this feature will not work for downloads made with previous versions of `patreon-dl`.
269
294
 
@@ -15,6 +15,10 @@
15
15
  * --video-password "<password>": for password-protected videos
16
16
  * --yt-dlp "</path/to/yt-dlp>": if yt-dlp is not in the PATH
17
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-vimeo -o "{dest.dir}/%(title)s.%(ext)s" --embed-html "{embed.html}" --embed-url "{embed.url}" -- --cookies-from-browser firefox
21
+ *
18
22
  * Upon encountering a post with embedded Vimeo content, 'patreon-dl' will call this script. The following then happens:
19
23
  * - This script obtains the video URL from 'embed.html' or 'embed.url'. The former ("player URL") is always preferable
20
24
  * since it is what's actually played within the Patreon post, and furthermore 'embed.url' sometimes returns
@@ -81,18 +85,23 @@ function getCommandString(cmd, args) {
81
85
  ].join(' ');
82
86
  }
83
87
 
84
- async function download(url, o, videoPassword, ytdlpPath) {
88
+ async function download(url, o, videoPassword, ytdlpPath, ytdlpArgs) {
85
89
  let proc;
86
90
  const ytdlp = ytdlpPath || 'yt-dlp';
91
+ const parsedYtdlpArgs = parseArgs(ytdlpArgs);
87
92
  try {
88
93
  return await new Promise((resolve, reject) => {
89
94
  let settled = false;
90
- const args = [
91
- '-o', o,
92
- '--referer', 'https://patreon.com/'
93
- ];
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);
94
103
  const printArgs = [...args];
95
- if (videoPassword) {
104
+ if (videoPassword && !parsedYtdlpArgs['video-password']) {
96
105
  args.push('--video-password', videoPassword);
97
106
  printArgs.push('--video-password', '******');
98
107
  }
@@ -147,6 +156,7 @@ const o = _o?.trim() ? path.resolve(_o.trim()) : null;
147
156
  const embedHTML = _embedHTML?.trim();
148
157
  const embedURL = _embedURL?.trim();
149
158
  const ytdlpPath = _ytdlpPath?.trim() ? path.resolve(_ytdlpPath.trim()) : null;
159
+ const ytdlpArgs = args['_'];
150
160
 
151
161
  if (!o) {
152
162
  console.error('No output file specified');
@@ -166,7 +176,7 @@ if (!url) {
166
176
  }
167
177
 
168
178
  async function doDownload(_url) {
169
- let code = await download(_url, o, videoPassword, ytdlpPath);
179
+ let code = await download(_url, o, videoPassword, ytdlpPath, ytdlpArgs);
170
180
  if (code !== 0 && _url !== embedURL && embedURL) {
171
181
  console.log(`Download failed - retrying with embed URL "${embedURL}"`);
172
182
  return await doDownload(embedURL);
@@ -11,9 +11,11 @@ export declare function CampaignAPIMixin<TBase extends APIConstructor>(Base: TBa
11
11
  withCounts?: false;
12
12
  }): Promise<Campaign | null>;
13
13
  getCampaign(params: GetCampaignParams): Promise<Campaign | CampaignWithCounts | null>;
14
+ "__#132@#sanitizeCampaign"(campaign: Campaign): void;
14
15
  name: string;
15
16
  db: import("../db").DBInstance;
16
17
  logger?: import("../..").Logger | null;
18
+ sanitizeHTML(html: string): string;
17
19
  log(level: import("../..").LogLevel, ...msg: any[]): void;
18
20
  };
19
21
  } & TBase;
@@ -1,18 +1,48 @@
1
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
+ };
1
6
  const DEFAULT_CAMPAIGN_LIST_SIZE = 10;
2
7
  const DEFAULT_CAMPAIGN_LIST_SORT_BY = 'a-z';
3
8
  export function CampaignAPIMixin(Base) {
4
- return class CampaignAPI extends Base {
5
- getCampaignList(params) {
6
- const { sortBy = DEFAULT_CAMPAIGN_LIST_SORT_BY, limit = DEFAULT_CAMPAIGN_LIST_SIZE, offset = 0 } = params;
7
- return this.db.getCampaignList({
8
- sortBy,
9
- limit,
10
- offset
11
- });
12
- }
13
- getCampaign(params) {
14
- return this.db.getCampaign(params);
15
- }
16
- };
9
+ var _CampaignAPI_instances, _CampaignAPI_sanitizeCampaign, _a;
10
+ return _a = class CampaignAPI extends Base {
11
+ constructor() {
12
+ super(...arguments);
13
+ _CampaignAPI_instances.add(this);
14
+ }
15
+ async getCampaignList(params) {
16
+ const { sortBy = DEFAULT_CAMPAIGN_LIST_SORT_BY, limit = DEFAULT_CAMPAIGN_LIST_SIZE, offset = 0 } = params;
17
+ const list = await this.db.getCampaignList({
18
+ sortBy,
19
+ limit,
20
+ offset
21
+ });
22
+ for (const campaign of list.campaigns) {
23
+ __classPrivateFieldGet(this, _CampaignAPI_instances, "m", _CampaignAPI_sanitizeCampaign).call(this, campaign);
24
+ }
25
+ return list;
26
+ }
27
+ async getCampaign(params) {
28
+ const campaign = await this.db.getCampaign(params);
29
+ if (campaign) {
30
+ __classPrivateFieldGet(this, _CampaignAPI_instances, "m", _CampaignAPI_sanitizeCampaign).call(this, campaign);
31
+ }
32
+ return campaign;
33
+ }
34
+ },
35
+ _CampaignAPI_instances = new WeakSet(),
36
+ _CampaignAPI_sanitizeCampaign = function _CampaignAPI_sanitizeCampaign(campaign) {
37
+ if (campaign.summary) {
38
+ campaign.summary = this.sanitizeHTML(campaign.summary);
39
+ }
40
+ for (const reward of campaign.rewards) {
41
+ if (reward.description) {
42
+ reward.description = this.sanitizeHTML(reward.description);
43
+ }
44
+ }
45
+ },
46
+ _a;
17
47
  }
18
48
  //# sourceMappingURL=CampaignAPIMixin.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CampaignAPIMixin.js","sourceRoot":"","sources":["../../../src/browse/api/CampaignAPIMixin.ts"],"names":[],"mappings":"AAKA,MAAM,0BAA0B,GAAG,EAAE,CAAC;AACtC,MAAM,6BAA6B,GAAuB,KAAK,CAAC;AAGhE,MAAM,UAAU,gBAAgB,CAA+B,IAAW;IACxE,OAAO,MAAM,WAAY,SAAQ,IAAI;QACnC,eAAe,CAAC,MAA6B;YAC3C,MAAM,EAAE,MAAM,GAAG,6BAA6B,EAAE,KAAK,GAAG,0BAA0B,EAAE,MAAM,GAAG,CAAC,EAAE,GAAG,MAAM,CAAC;YAC1G,OAAO,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC;gBAC7B,MAAM;gBACN,KAAK;gBACL,MAAM;aACP,CAAC,CAAC;QACL,CAAC;QAKD,WAAW,CAAC,MAAyB;YACnC,OAAO,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACrC,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["import { type APIConstructor } from \".\";\nimport { type Campaign } from \"../../entities/index.js\";\nimport { type CampaignList, type CampaignListSortBy, type CampaignWithCounts, type GetCampaignListParams, type GetCampaignParams } from \"../types/Campaign.js\";\n\n\nconst DEFAULT_CAMPAIGN_LIST_SIZE = 10;\nconst DEFAULT_CAMPAIGN_LIST_SORT_BY: CampaignListSortBy = 'a-z';\n\n\nexport function CampaignAPIMixin<TBase extends APIConstructor>(Base: TBase) {\n return class CampaignAPI extends Base {\n getCampaignList(params: GetCampaignListParams): Promise<CampaignList> {\n const { sortBy = DEFAULT_CAMPAIGN_LIST_SORT_BY, limit = DEFAULT_CAMPAIGN_LIST_SIZE, offset = 0 } = params;\n return this.db.getCampaignList({\n sortBy,\n limit,\n offset\n });\n }\n\n getCampaign(params: GetCampaignParams & { withCounts: true }): Promise<CampaignWithCounts | null>;\n getCampaign(params: GetCampaignParams & { withCounts?: false }): Promise<Campaign | null>;\n getCampaign(params: GetCampaignParams): Promise<Campaign | CampaignWithCounts | null>;\n getCampaign(params: GetCampaignParams) {\n return this.db.getCampaign(params);\n }\n }\n}"]}
1
+ {"version":3,"file":"CampaignAPIMixin.js","sourceRoot":"","sources":["../../../src/browse/api/CampaignAPIMixin.ts"],"names":[],"mappings":";;;;;AAKA,MAAM,0BAA0B,GAAG,EAAE,CAAC;AACtC,MAAM,6BAA6B,GAAuB,KAAK,CAAC;AAGhE,MAAM,UAAU,gBAAgB,CAA+B,IAAW;;IACxE,YAAO,MAAM,WAAY,SAAQ,IAAI;YAA9B;;;YAmCP,CAAC;YAlCC,KAAK,CAAC,eAAe,CAAC,MAA6B;gBACjD,MAAM,EAAE,MAAM,GAAG,6BAA6B,EAAE,KAAK,GAAG,0BAA0B,EAAE,MAAM,GAAG,CAAC,EAAE,GAAG,MAAM,CAAC;gBAC1G,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC;oBACzC,MAAM;oBACN,KAAK;oBACL,MAAM;iBACP,CAAC,CAAC;gBACH,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;oBACtC,uBAAA,IAAI,6DAAkB,MAAtB,IAAI,EAAmB,QAAQ,CAAC,CAAC;gBACnC,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;YAKD,KAAK,CAAC,WAAW,CAAC,MAAyB;gBACzC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;gBACnD,IAAI,QAAQ,EAAE,CAAC;oBACb,uBAAA,IAAI,6DAAkB,MAAtB,IAAI,EAAmB,QAAQ,CAAC,CAAC;gBACnC,CAAC;gBACD,OAAO,QAAQ,CAAC;YAClB,CAAC;SAYF;;+EAVmB,QAAkB;YAClC,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACrB,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YACzD,CAAC;YACD,KAAK,MAAM,MAAM,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACtC,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;oBACvB,MAAM,CAAC,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;gBAC7D,CAAC;YACH,CAAC;QACH,CAAC;WACF;AACH,CAAC","sourcesContent":["import { type APIConstructor } from \".\";\nimport { type Campaign } from \"../../entities/index.js\";\nimport { type CampaignList, type CampaignListSortBy, type CampaignWithCounts, type GetCampaignListParams, type GetCampaignParams } from \"../types/Campaign.js\";\n\n\nconst DEFAULT_CAMPAIGN_LIST_SIZE = 10;\nconst DEFAULT_CAMPAIGN_LIST_SORT_BY: CampaignListSortBy = 'a-z';\n\n\nexport function CampaignAPIMixin<TBase extends APIConstructor>(Base: TBase) {\n return class CampaignAPI extends Base {\n async getCampaignList(params: GetCampaignListParams): Promise<CampaignList> {\n const { sortBy = DEFAULT_CAMPAIGN_LIST_SORT_BY, limit = DEFAULT_CAMPAIGN_LIST_SIZE, offset = 0 } = params;\n const list = await this.db.getCampaignList({\n sortBy,\n limit,\n offset\n });\n for (const campaign of list.campaigns) {\n this.#sanitizeCampaign(campaign);\n }\n return list;\n }\n\n async getCampaign(params: GetCampaignParams & { withCounts: true }): Promise<CampaignWithCounts | null>;\n async getCampaign(params: GetCampaignParams & { withCounts?: false }): Promise<Campaign | null>;\n async getCampaign(params: GetCampaignParams): Promise<Campaign | CampaignWithCounts | null>;\n async getCampaign(params: GetCampaignParams) {\n const campaign = await this.db.getCampaign(params);\n if (campaign) {\n this.#sanitizeCampaign(campaign);\n }\n return campaign;\n }\n\n #sanitizeCampaign(campaign: Campaign) {\n if (campaign.summary) {\n campaign.summary = this.sanitizeHTML(campaign.summary);\n }\n for (const reward of campaign.rewards) {\n if (reward.description) {\n reward.description = this.sanitizeHTML(reward.description);\n }\n }\n }\n }\n}"]}
@@ -1,13 +1,16 @@
1
1
  import { type APIConstructor } from ".";
2
+ import { type Post } from "../../entities";
2
3
  import { type ContentType, type GetContentListParams } from "../types/Content.js";
3
4
  export declare function ContentAPIMixin<TBase extends APIConstructor>(Base: TBase): {
4
5
  new (...args: any[]): {
5
6
  getContentList<T extends ContentType>(params: GetContentListParams<T>): Promise<import("../types/Content.js").ContentList<T>>;
6
7
  getPost(id: string): Promise<import("../types/Content.js").PostWithComments | null>;
7
- getProduct(id: string): Promise<import("../..").Product | null>;
8
+ getProduct(id: string): Promise<import("../../entities").Product | null>;
9
+ "__#133@#processPostContentInlineMedia"(post: Post): void;
8
10
  name: string;
9
11
  db: import("../db").DBInstance;
10
12
  logger?: import("../..").Logger | null;
13
+ sanitizeHTML(html: string): string;
11
14
  log(level: import("../..").LogLevel, ...msg: any[]): void;
12
15
  };
13
16
  } & TBase;
@@ -1,22 +1,90 @@
1
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
+ };
6
+ import { load as cheerioLoad } from 'cheerio';
7
+ import RawDataExtractor from '../web/utils/RawDataExtractor.js';
1
8
  const DEFAULT_CONTENT_LIST_SIZE = 10;
2
9
  const DEFAULT_CONTENT_LIST_SORT_BY = 'a-z';
3
10
  export function ContentAPIMixin(Base) {
4
- return class ContentAPI extends Base {
5
- getContentList(params) {
6
- const { sortBy = DEFAULT_CONTENT_LIST_SORT_BY, limit = DEFAULT_CONTENT_LIST_SIZE, offset = 0 } = params;
7
- return this.db.getContentList({
8
- ...params,
9
- sortBy,
10
- limit,
11
- offset,
11
+ var _ContentAPI_instances, _ContentAPI_processPostContentInlineMedia, _a;
12
+ return _a = class ContentAPI extends Base {
13
+ constructor() {
14
+ super(...arguments);
15
+ _ContentAPI_instances.add(this);
16
+ }
17
+ async getContentList(params) {
18
+ const { sortBy = DEFAULT_CONTENT_LIST_SORT_BY, limit = DEFAULT_CONTENT_LIST_SIZE, offset = 0 } = params;
19
+ const list = await this.db.getContentList({
20
+ ...params,
21
+ sortBy,
22
+ limit,
23
+ offset,
24
+ });
25
+ for (const item of list.items) {
26
+ switch (item.type) {
27
+ case 'post':
28
+ __classPrivateFieldGet(this, _ContentAPI_instances, "m", _ContentAPI_processPostContentInlineMedia).call(this, item);
29
+ item.content = this.sanitizeHTML(item.content || '');
30
+ break;
31
+ case 'product': {
32
+ const description = RawDataExtractor.getProductRichTextDescription(item);
33
+ item.description = description ? this.sanitizeHTML(description) : null;
34
+ break;
35
+ }
36
+ }
37
+ }
38
+ return list;
39
+ }
40
+ async getPost(id) {
41
+ const post = await this.db.getContent(id, 'post');
42
+ if (post) {
43
+ __classPrivateFieldGet(this, _ContentAPI_instances, "m", _ContentAPI_processPostContentInlineMedia).call(this, post);
44
+ post.content = this.sanitizeHTML(post.content || '');
45
+ }
46
+ return post;
47
+ }
48
+ getProduct(id) {
49
+ return this.db.getContent(id, 'product');
50
+ }
51
+ },
52
+ _ContentAPI_instances = new WeakSet(),
53
+ _ContentAPI_processPostContentInlineMedia = function _ContentAPI_processPostContentInlineMedia(post) {
54
+ const html = post.content || '';
55
+ if (!html || post.images.length === 0) {
56
+ return;
57
+ }
58
+ const $ = cheerioLoad(html);
59
+ const replacedMediaIds = [];
60
+ $('img').each((_, _el) => {
61
+ const el = $(_el);
62
+ const id = el.attr('data-media-id');
63
+ const matched = id ? post.images.find(img => img.id === id && img.downloaded) : null;
64
+ const src = matched ? `/media/${matched.id}` : el.attr('src');
65
+ const imgEl = $('<img>').attr('src', src);
66
+ const aEl = $('<a>')
67
+ .attr('href', src)
68
+ .attr('class', 'lightgallery-item')
69
+ .append(imgEl);
70
+ const wrapperEl = $('<div>')
71
+ .attr('class', 'post-card__inline-media-wrapper')
72
+ .append(aEl);
73
+ if (!matched) {
74
+ const caption = "(Externally hosted - not stored locally)";
75
+ wrapperEl.append($('<span>').attr('class', 'post-card__inline-media-caption').append(caption));
76
+ }
77
+ el.replaceWith(wrapperEl);
78
+ if (id && matched) {
79
+ replacedMediaIds.push(id);
80
+ }
12
81
  });
13
- }
14
- getPost(id) {
15
- return this.db.getContent(id, 'post');
16
- }
17
- getProduct(id) {
18
- return this.db.getContent(id, 'product');
19
- }
20
- };
82
+ if (replacedMediaIds.length > 0) {
83
+ post.content = $.html();
84
+ // Remove images that have been inlined
85
+ post.images = post.images.filter((img) => !replacedMediaIds.includes(img.id));
86
+ }
87
+ },
88
+ _a;
21
89
  }
22
90
  //# sourceMappingURL=ContentAPIMixin.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ContentAPIMixin.js","sourceRoot":"","sources":["../../../src/browse/api/ContentAPIMixin.ts"],"names":[],"mappings":"AAGA,MAAM,yBAAyB,GAAG,EAAE,CAAC;AACrC,MAAM,4BAA4B,GAAsB,KAAK,CAAC;AAE9D,MAAM,UAAU,eAAe,CAA+B,IAAW;IACvE,OAAO,MAAM,UAAW,SAAQ,IAAI;QAClC,cAAc,CAAwB,MAA+B;YACnE,MAAM,EAAE,MAAM,GAAG,4BAA4B,EAAE,KAAK,GAAG,yBAAyB,EAAE,MAAM,GAAG,CAAC,EAAE,GAAG,MAAM,CAAC;YACxG,OAAO,IAAI,CAAC,EAAE,CAAC,cAAc,CAAC;gBAC5B,GAAG,MAAM;gBACT,MAAM;gBACN,KAAK;gBACL,MAAM;aACP,CAAC,CAAC;QACL,CAAC;QAED,OAAO,CAAC,EAAU;YAChB,OAAO,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QACxC,CAAC;QAED,UAAU,CAAC,EAAU;YACnB,OAAO,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;QAC3C,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["import { type APIConstructor } from \".\";\nimport { type ContentListSortBy, type ContentType, type GetContentListParams } from \"../types/Content.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 return this.db.getContentList({\n ...params,\n sortBy,\n limit,\n offset,\n });\n }\n\n getPost(id: string) {\n return this.db.getContent(id, 'post');\n }\n\n getProduct(id: string) {\n return this.db.getContent(id, 'product');\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 Post } from \"../../entities\";\nimport { type ContentListSortBy, type ContentType, type GetContentListParams } from \"../types/Content.js\";\nimport RawDataExtractor from '../web/utils/RawDataExtractor.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 async 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 = await 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 async getPost(id: string) {\n const post = await 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 #processPostContentInlineMedia(post: Post) {\n const html = post.content || '';\n if (!html || post.images.length === 0) {\n return;\n }\n const $ = cheerioLoad(html);\n const replacedMediaIds: string[] = [];\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 post.content = $.html();\n // Remove images that have been inlined\n post.images = post.images.filter((img) => !replacedMediaIds.includes(img.id));\n }\n }\n }\n}"]}
@@ -3,12 +3,13 @@ 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): Promise<FilterData<PostFilterSearchParams>>;
6
- "__#107@#getPostTypeTitle"(postType: string): "Link" | "Text" | "Audio" | "Image" | "Podcast" | "Poll" | "Video" | "Other";
6
+ "__#134@#getPostTypeTitle"(postType: string): "Link" | "Audio" | "Image" | "Video" | "Other" | "Text" | "Podcast" | "Poll";
7
7
  getProductFilterData(campaignId: string): Promise<FilterData<ProductFilterSearchParams>>;
8
8
  getMediaFilterData(campaignId: string): Promise<FilterData<MediaFilterSearchParams>>;
9
9
  name: string;
10
10
  db: import("../db").DBInstance;
11
11
  logger?: import("../..").Logger | null;
12
+ sanitizeHTML(html: string): string;
12
13
  log(level: import("../..").LogLevel, ...msg: any[]): void;
13
14
  };
14
15
  } & TBase;
@@ -7,6 +7,7 @@ export declare function MediaAPIMixin<TBase extends APIConstructor>(Base: TBase)
7
7
  name: string;
8
8
  db: import("../db").DBInstance;
9
9
  logger?: import("../..").Logger | null;
10
+ sanitizeHTML(html: string): string;
10
11
  log(level: import("../..").LogLevel, ...msg: any[]): void;
11
12
  };
12
13
  } & TBase;
@@ -12,6 +12,7 @@ export declare function SettingsAPIMixin<TBase extends APIConstructor>(Base: TBa
12
12
  name: string;
13
13
  db: import("../db").DBInstance;
14
14
  logger?: import("../..").Logger | null;
15
+ sanitizeHTML(html: string): string;
15
16
  log(level: import("../..").LogLevel, ...msg: any[]): void;
16
17
  };
17
18
  } & TBase;
@@ -11,18 +11,20 @@ export declare class APIBase {
11
11
  constructor(db: DBInstance, logger?: Logger | null);
12
12
  static getInstance(db: DBInstance, logger?: Logger | null): {
13
13
  getPostFilterData(campaignId: string): Promise<import("../types/Filter.js").FilterData<import("../types/Filter.js").PostFilterSearchParams>>;
14
- "__#107@#getPostTypeTitle"(postType: string): "Link" | "Text" | "Audio" | "Image" | "Podcast" | "Poll" | "Video" | "Other";
14
+ "__#134@#getPostTypeTitle"(postType: string): "Link" | "Audio" | "Image" | "Video" | "Other" | "Text" | "Podcast" | "Poll";
15
15
  getProductFilterData(campaignId: string): Promise<import("../types/Filter.js").FilterData<import("../types/Filter.js").ProductFilterSearchParams>>;
16
16
  getMediaFilterData(campaignId: string): Promise<import("../types/Filter.js").FilterData<import("../types/Filter.js").MediaFilterSearchParams>>;
17
17
  name: string;
18
18
  db: DBInstance;
19
19
  logger?: Logger | null;
20
+ sanitizeHTML(html: string): string;
20
21
  log(level: LogLevel, ...msg: any[]): void;
21
22
  } & {
22
23
  getMediaList<T extends import("../types/Content.js").ContentType>(params: import("../types/Media.js").GetMediaListParams<T>): Promise<import("../types/Media.js").MediaList<T>>;
23
24
  name: string;
24
25
  db: DBInstance;
25
26
  logger?: Logger | null;
27
+ sanitizeHTML(html: string): string;
26
28
  log(level: LogLevel, ...msg: any[]): void;
27
29
  } & {
28
30
  getBrowseSettings(): Promise<{
@@ -35,14 +37,17 @@ export declare class APIBase {
35
37
  name: string;
36
38
  db: DBInstance;
37
39
  logger?: Logger | null;
40
+ sanitizeHTML(html: string): string;
38
41
  log(level: LogLevel, ...msg: any[]): void;
39
42
  } & {
40
43
  getContentList<T extends import("../types/Content.js").ContentType>(params: import("../types/Content.js").GetContentListParams<T>): Promise<import("../types/Content.js").ContentList<T>>;
41
44
  getPost(id: string): Promise<import("../types/Content.js").PostWithComments | null>;
42
45
  getProduct(id: string): Promise<import("../../index.js").Product | null>;
46
+ "__#133@#processPostContentInlineMedia"(post: import("../../index.js").Post): void;
43
47
  name: string;
44
48
  db: DBInstance;
45
49
  logger?: Logger | null;
50
+ sanitizeHTML(html: string): string;
46
51
  log(level: LogLevel, ...msg: any[]): void;
47
52
  } & {
48
53
  getCampaignList(params: import("../types/Campaign.js").GetCampaignListParams): Promise<import("../types/Campaign.js").CampaignList>;
@@ -53,22 +58,26 @@ export declare class APIBase {
53
58
  withCounts?: false;
54
59
  }): Promise<import("../../index.js").Campaign | null>;
55
60
  getCampaign(params: import("../types/Campaign.js").GetCampaignParams): Promise<import("../../index.js").Campaign | import("../types/Campaign.js").CampaignWithCounts | null>;
61
+ "__#132@#sanitizeCampaign"(campaign: import("../../index.js").Campaign): void;
56
62
  name: string;
57
63
  db: DBInstance;
58
64
  logger?: Logger | null;
65
+ sanitizeHTML(html: string): string;
59
66
  log(level: LogLevel, ...msg: any[]): void;
60
67
  } & APIBase;
68
+ sanitizeHTML(html: string): string;
61
69
  log(level: LogLevel, ...msg: any[]): void;
62
70
  }
63
71
  declare const API: {
64
72
  new (...args: any[]): {
65
73
  getPostFilterData(campaignId: string): Promise<import("../types/Filter.js").FilterData<import("../types/Filter.js").PostFilterSearchParams>>;
66
- "__#107@#getPostTypeTitle"(postType: string): "Link" | "Text" | "Audio" | "Image" | "Podcast" | "Poll" | "Video" | "Other";
74
+ "__#134@#getPostTypeTitle"(postType: string): "Link" | "Audio" | "Image" | "Video" | "Other" | "Text" | "Podcast" | "Poll";
67
75
  getProductFilterData(campaignId: string): Promise<import("../types/Filter.js").FilterData<import("../types/Filter.js").ProductFilterSearchParams>>;
68
76
  getMediaFilterData(campaignId: string): Promise<import("../types/Filter.js").FilterData<import("../types/Filter.js").MediaFilterSearchParams>>;
69
77
  name: string;
70
78
  db: DBInstance;
71
79
  logger?: Logger | null;
80
+ sanitizeHTML(html: string): string;
72
81
  log(level: LogLevel, ...msg: any[]): void;
73
82
  };
74
83
  } & {
@@ -77,6 +86,7 @@ declare const API: {
77
86
  name: string;
78
87
  db: DBInstance;
79
88
  logger?: Logger | null;
89
+ sanitizeHTML(html: string): string;
80
90
  log(level: LogLevel, ...msg: any[]): void;
81
91
  };
82
92
  } & {
@@ -91,6 +101,7 @@ declare const API: {
91
101
  name: string;
92
102
  db: DBInstance;
93
103
  logger?: Logger | null;
104
+ sanitizeHTML(html: string): string;
94
105
  log(level: LogLevel, ...msg: any[]): void;
95
106
  };
96
107
  } & {
@@ -98,9 +109,11 @@ declare const API: {
98
109
  getContentList<T extends import("../types/Content.js").ContentType>(params: import("../types/Content.js").GetContentListParams<T>): Promise<import("../types/Content.js").ContentList<T>>;
99
110
  getPost(id: string): Promise<import("../types/Content.js").PostWithComments | null>;
100
111
  getProduct(id: string): Promise<import("../../index.js").Product | null>;
112
+ "__#133@#processPostContentInlineMedia"(post: import("../../index.js").Post): void;
101
113
  name: string;
102
114
  db: DBInstance;
103
115
  logger?: Logger | null;
116
+ sanitizeHTML(html: string): string;
104
117
  log(level: LogLevel, ...msg: any[]): void;
105
118
  };
106
119
  } & {
@@ -113,9 +126,11 @@ declare const API: {
113
126
  withCounts?: false;
114
127
  }): Promise<import("../../index.js").Campaign | null>;
115
128
  getCampaign(params: import("../types/Campaign.js").GetCampaignParams): Promise<import("../../index.js").Campaign | import("../types/Campaign.js").CampaignWithCounts | null>;
129
+ "__#132@#sanitizeCampaign"(campaign: import("../../index.js").Campaign): void;
116
130
  name: string;
117
131
  db: DBInstance;
118
132
  logger?: Logger | null;
133
+ sanitizeHTML(html: string): string;
119
134
  log(level: LogLevel, ...msg: any[]): void;
120
135
  };
121
136
  } & typeof APIBase;
@@ -1,9 +1,17 @@
1
+ import _sanitizeHTML from 'sanitize-html';
1
2
  import { commonLog } from '../../utils/logging/Logger.js';
2
3
  import { CampaignAPIMixin } from './CampaignAPIMixin.js';
3
4
  import { ContentAPIMixin } from './ContentAPIMixin.js';
4
5
  import { SettingsAPIMixin } from './SettingsAPIMixin.js';
5
6
  import { MediaAPIMixin } from './MediaAPIMixin.js';
6
7
  import { FilterAPIMixin } from './FilterAPIMixin.js';
8
+ const SANITIZE_HTML_OPTIONS = {
9
+ allowedTags: _sanitizeHTML.defaults.allowedTags.concat(['img']),
10
+ allowedAttributes: {
11
+ ..._sanitizeHTML.defaults.allowedAttributes,
12
+ '*': ['class']
13
+ }
14
+ };
7
15
  export class APIBase {
8
16
  constructor(db, logger) {
9
17
  this.name = 'API';
@@ -16,6 +24,9 @@ export class APIBase {
16
24
  }
17
25
  return this.instance;
18
26
  }
27
+ sanitizeHTML(html) {
28
+ return _sanitizeHTML(html, SANITIZE_HTML_OPTIONS);
29
+ }
19
30
  log(level, ...msg) {
20
31
  const limiterStopOnError = msg.find((m) => m instanceof Error && m.message === 'LimiterStopOnError');
21
32
  if (limiterStopOnError) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/browse/api/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAiB,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAKrD,MAAM,OAAO,OAAO;IAOlB,YAAY,EAAc,EAAE,MAAsB;QANlD,SAAI,GAAG,KAAK,CAAC;QAOX,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,MAAM,CAAC,WAAW,CAAC,EAAc,EAAE,MAAsB;QACvD,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,GAAG,CAAC,KAAe,EAAE,GAAG,GAAU;QAChC,MAAM,kBAAkB,GAAG,GAAG,CAAC,IAAI,CACjC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,OAAO,KAAK,oBAAoB,CAChE,CAAC;QACF,IAAI,kBAAkB,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QACD,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,CAAC;IACnD,CAAC;;AAxBgB,gBAAQ,GAAuB,IAAI,AAA3B,CAA4B;AA2BvD,MAAM,GAAG,GAAG,cAAc,CAAC,aAAa,CAAC,gBAAgB,CAAC,eAAe,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAExG,eAAe,GAAG,CAAC","sourcesContent":["import type Logger from '../../utils/logging/Logger.js';\nimport { commonLog, type LogLevel } from '../../utils/logging/Logger.js';\nimport { CampaignAPIMixin } from './CampaignAPIMixin.js';\nimport { type DBInstance } from '../db';\nimport { ContentAPIMixin } from './ContentAPIMixin.js';\nimport { SettingsAPIMixin } from './SettingsAPIMixin.js';\nimport { MediaAPIMixin } from './MediaAPIMixin.js';\nimport { FilterAPIMixin } from './FilterAPIMixin.js';\n\nexport type APIConstructor = new (...args: any[]) => APIBase;\nexport type APIInstance = InstanceType<typeof API>;\n\nexport class APIBase {\n name = 'API';\n\n protected static instance: APIInstance | null = null;\n db: DBInstance;\n logger?: Logger | null;\n\n constructor(db: DBInstance, logger?: Logger | null) {\n this.db = db;\n this.logger = logger;\n }\n\n static getInstance(db: DBInstance, logger?: Logger | null) {\n if (!this.instance) {\n this.instance = new API(db, logger);\n }\n return this.instance;\n }\n\n log(level: LogLevel, ...msg: any[]) {\n const limiterStopOnError = msg.find(\n (m) => m instanceof Error && m.message === 'LimiterStopOnError'\n );\n if (limiterStopOnError) {\n return;\n }\n commonLog(this.logger, level, this.name, ...msg);\n }\n}\n\nconst API = FilterAPIMixin(MediaAPIMixin(SettingsAPIMixin(ContentAPIMixin(CampaignAPIMixin(APIBase)))));\n\nexport default API;\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/browse/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,eAAe,CAAC;AAE1C,OAAO,EAAE,SAAS,EAAiB,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAKrD,MAAM,qBAAqB,GAAG;IAC5B,WAAW,EAAE,aAAa,CAAC,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC;IAC/D,iBAAiB,EAAE;QACjB,GAAG,aAAa,CAAC,QAAQ,CAAC,iBAAiB;QAC3C,GAAG,EAAE,CAAC,OAAO,CAAC;KACf;CACF,CAAC;AAEF,MAAM,OAAO,OAAO;IAOlB,YAAY,EAAc,EAAE,MAAsB;QANlD,SAAI,GAAG,KAAK,CAAC;QAOX,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,MAAM,CAAC,WAAW,CAAC,EAAc,EAAE,MAAsB;QACvD,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,YAAY,CAAC,IAAY;QACvB,OAAO,aAAa,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC;IACpD,CAAC;IAED,GAAG,CAAC,KAAe,EAAE,GAAG,GAAU;QAChC,MAAM,kBAAkB,GAAG,GAAG,CAAC,IAAI,CACjC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,OAAO,KAAK,oBAAoB,CAChE,CAAC;QACF,IAAI,kBAAkB,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QACD,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,CAAC;IACnD,CAAC;;AA5BgB,gBAAQ,GAAuB,IAAI,AAA3B,CAA4B;AA+BvD,MAAM,GAAG,GAAG,cAAc,CAAC,aAAa,CAAC,gBAAgB,CAAC,eAAe,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAExG,eAAe,GAAG,CAAC","sourcesContent":["import _sanitizeHTML from 'sanitize-html';\nimport type Logger from '../../utils/logging/Logger.js';\nimport { commonLog, type LogLevel } from '../../utils/logging/Logger.js';\nimport { CampaignAPIMixin } from './CampaignAPIMixin.js';\nimport { type DBInstance } from '../db';\nimport { ContentAPIMixin } from './ContentAPIMixin.js';\nimport { SettingsAPIMixin } from './SettingsAPIMixin.js';\nimport { MediaAPIMixin } from './MediaAPIMixin.js';\nimport { FilterAPIMixin } from './FilterAPIMixin.js';\n\nexport type APIConstructor = new (...args: any[]) => APIBase;\nexport type APIInstance = InstanceType<typeof API>;\n\nconst SANITIZE_HTML_OPTIONS = {\n allowedTags: _sanitizeHTML.defaults.allowedTags.concat(['img']),\n allowedAttributes: {\n ..._sanitizeHTML.defaults.allowedAttributes,\n '*': ['class']\n }\n};\n\nexport class APIBase {\n name = 'API';\n\n protected static instance: APIInstance | null = null;\n db: DBInstance;\n logger?: Logger | null;\n\n constructor(db: DBInstance, logger?: Logger | null) {\n this.db = db;\n this.logger = logger;\n }\n\n static getInstance(db: DBInstance, logger?: Logger | null) {\n if (!this.instance) {\n this.instance = new API(db, logger);\n }\n return this.instance;\n }\n\n sanitizeHTML(html: string) {\n return _sanitizeHTML(html, SANITIZE_HTML_OPTIONS);\n }\n\n log(level: LogLevel, ...msg: any[]) {\n const limiterStopOnError = msg.find(\n (m) => m instanceof Error && m.message === 'LimiterStopOnError'\n );\n if (limiterStopOnError) {\n return;\n }\n commonLog(this.logger, level, this.name, ...msg);\n }\n}\n\nconst API = FilterAPIMixin(MediaAPIMixin(SettingsAPIMixin(ContentAPIMixin(CampaignAPIMixin(APIBase)))));\n\nexport default API;\n"]}
@@ -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): Promise<void>;
9
9
  getCampaign(params: GetCampaignParams): Promise<Campaign | null>;
10
- "__#93@#saveRewards"(campaign: Campaign): Promise<void>;
11
- "__#93@#doSaveReward"(campaign: Campaign, reward: Reward): Promise<void>;
10
+ "__#118@#saveRewards"(campaign: Campaign): Promise<void>;
11
+ "__#118@#doSaveReward"(campaign: Campaign, reward: Reward): Promise<void>;
12
12
  getCampaignList(params: GetCampaignListParams): Promise<CampaignList>;
13
- "__#93@#getCampaignWithCounts"(params: GetCampaignParams): Promise<CampaignWithCounts | null>;
13
+ "__#118@#getCampaignWithCounts"(params: GetCampaignParams): Promise<CampaignWithCounts | null>;
14
14
  checkCampaignExists(id: string): Promise<boolean>;
15
15
  saveUser(user: import("../../index.js").User | null): Promise<void>;
16
16
  getUserByID(id: string): Promise<import("../../index.js").User | null>;
@@ -51,7 +51,7 @@ export declare function CampaignDBMixin<TBase extends UserDBConstructor>(Base: T
51
51
  run(sql: import("sqlite").ISqlite.SqlType, ...params: any[]): Promise<import("sqlite").ISqlite.RunResult<import("sqlite3").Statement>>;
52
52
  get<T = any>(sql: import("sqlite").ISqlite.SqlType, ...params: any[]): Promise<T | undefined>;
53
53
  all<T = any[]>(sql: import("sqlite").ISqlite.SqlType, ...params: any[]): Promise<T>;
54
- "__#95@#interpolateSqlParams"(sql: import("sqlite").ISqlite.SqlType | string, params: any[]): string;
54
+ "__#120@#interpolateSqlParams"(sql: import("sqlite").ISqlite.SqlType | string, params: any[]): string;
55
55
  log(level: import("../../index.js").LogLevel, ...msg: any[]): void;
56
56
  };
57
57
  } & TBase;