patreon-dl 3.1.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 (70) hide show
  1. package/README.md +17 -0
  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/index.js +24 -0
  30. package/dist/cli/index.js.map +1 -1
  31. package/dist/cli/server/ServerCLIOptions.d.ts +1 -1
  32. package/dist/downloaders/Downloader.d.ts +15 -2
  33. package/dist/downloaders/Downloader.js +21 -4
  34. package/dist/downloaders/Downloader.js.map +1 -1
  35. package/dist/downloaders/PostDownloader.js +7 -1
  36. package/dist/downloaders/PostDownloader.js.map +1 -1
  37. package/dist/downloaders/PostsFetcher.js +10 -3
  38. package/dist/downloaders/PostsFetcher.js.map +1 -1
  39. package/dist/downloaders/task/DownloadTask.js +1 -1
  40. package/dist/downloaders/task/DownloadTask.js.map +1 -1
  41. package/dist/downloaders/task/DownloadTaskFactory.d.ts +1 -0
  42. package/dist/downloaders/task/DownloadTaskFactory.js +2 -1
  43. package/dist/downloaders/task/DownloadTaskFactory.js.map +1 -1
  44. package/dist/downloaders/task/FFmpegDownloadTaskBase.d.ts +1 -0
  45. package/dist/downloaders/task/FFmpegDownloadTaskBase.js +37 -2
  46. package/dist/downloaders/task/FFmpegDownloadTaskBase.js.map +1 -1
  47. package/dist/downloaders/task/FetcherDownloadTask.d.ts +1 -0
  48. package/dist/downloaders/task/FetcherDownloadTask.js +30 -18
  49. package/dist/downloaders/task/FetcherDownloadTask.js.map +1 -1
  50. package/dist/downloaders/task/M3U8DownloadTask.js +10 -6
  51. package/dist/downloaders/task/M3U8DownloadTask.js.map +1 -1
  52. package/dist/downloaders/task/YouTubeDownloadTask.js +9 -2
  53. package/dist/downloaders/task/YouTubeDownloadTask.js.map +1 -1
  54. package/dist/downloaders/task/YouTubeStreamDownloadTask.js +31 -14
  55. package/dist/downloaders/task/YouTubeStreamDownloadTask.js.map +1 -1
  56. package/dist/parsers/PageParser.js +35 -0
  57. package/dist/parsers/PageParser.js.map +1 -1
  58. package/dist/utils/FSHelper.js +6 -5
  59. package/dist/utils/FSHelper.js.map +1 -1
  60. package/dist/utils/Fetcher.d.ts +8 -1
  61. package/dist/utils/Fetcher.js +26 -1
  62. package/dist/utils/Fetcher.js.map +1 -1
  63. package/dist/utils/logging/ConsoleLogger.js +6 -0
  64. package/dist/utils/logging/ConsoleLogger.js.map +1 -1
  65. package/dist/utils/yt/InnertubeLoader.d.ts +0 -1
  66. package/dist/utils/yt/InnertubeLoader.js +9 -196
  67. package/dist/utils/yt/InnertubeLoader.js.map +1 -1
  68. package/package.json +4 -6
  69. package/dist/browse/web/assets/index-Cmw66Jra.js +0 -250
  70. package/dist/browse/web/assets/index-_Usha2nm.css +0 -1
package/README.md CHANGED
@@ -267,6 +267,23 @@ Note the URL shown in the output. Open this URL in a web browser to begin viewin
267
267
 
268
268
  ## Changelog
269
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
+
270
287
  v3.1.0
271
288
  - Defer database initialization until downloader starts
272
289
  - UI: fix post column width possibly exceeding screen width
@@ -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;
@@ -4,13 +4,13 @@ import { type CampaignDBConstructor } from './CampaignDBMixin.js';
4
4
  export declare function ContentDBMixin<TBase extends CampaignDBConstructor>(Base: TBase): {
5
5
  new (...args: any[]): {
6
6
  saveContent(content: Post | Product): Promise<void>;
7
- "__#94@#saveContentMedia"(content: Post | Product): Promise<void>;
8
- "__#94@#savepostMedia"(post: Post): Promise<void>;
9
- "__#94@#saveProductMedia"(product: Product): Promise<void>;
10
- "__#94@#doSaveContentMedia"(content: Post | Product, media: Downloadable, mediaIndex: number, isPreview: boolean): Promise<void>;
11
- "__#94@#publishedAtToTime"(publishedAt: string | null): number | null;
12
- "__#94@#savePostTiers"(post: Post): Promise<void>;
13
- "__#94@#doSaveTier"(post: Post, tier: Tier): Promise<void>;
7
+ "__#119@#saveContentMedia"(content: Post | Product): Promise<void>;
8
+ "__#119@#savepostMedia"(post: Post): Promise<void>;
9
+ "__#119@#saveProductMedia"(product: Product): Promise<void>;
10
+ "__#119@#doSaveContentMedia"(content: Post | Product, media: Downloadable, mediaIndex: number, isPreview: boolean): Promise<void>;
11
+ "__#119@#publishedAtToTime"(publishedAt: string | null): number | null;
12
+ "__#119@#savePostTiers"(post: Post): Promise<void>;
13
+ "__#119@#doSaveTier"(post: Post, tier: Tier): Promise<void>;
14
14
  savePostComments(post: Post, comments: Comment[]): Promise<void>;
15
15
  checkPostCommentsExist(post: Post): Promise<boolean>;
16
16
  getContent(id: string, contentType: "post"): Promise<PostWithComments | null>;
@@ -21,7 +21,7 @@ export declare function ContentDBMixin<TBase extends CampaignDBConstructor>(Base
21
21
  * @param row Must have `details`, `comment_count` and `comments`
22
22
  * @returns
23
23
  */
24
- "__#94@#parseContentRowJoinedComments"(row: any): any;
24
+ "__#119@#parseContentRowJoinedComments"(row: any): any;
25
25
  getContentCountByDate(contentType: ContentType, groupBy: "year" | "month", filter?: {
26
26
  campaign?: Campaign | string | null;
27
27
  date?: Date | null;
@@ -42,10 +42,10 @@ export declare function ContentDBMixin<TBase extends CampaignDBConstructor>(Base
42
42
  getPostComments(post: Post | string): Promise<Comment[] | null>;
43
43
  saveCampaign(campaign: Campaign | null, downloadDate: Date, overwriteIfExists?: boolean): Promise<void>;
44
44
  getCampaign(params: import("../types/Campaign").GetCampaignParams): Promise<Campaign | null>;
45
- "__#93@#saveRewards"(campaign: Campaign): Promise<void>;
46
- "__#93@#doSaveReward"(campaign: Campaign, reward: import("../../entities").Reward): Promise<void>;
45
+ "__#118@#saveRewards"(campaign: Campaign): Promise<void>;
46
+ "__#118@#doSaveReward"(campaign: Campaign, reward: import("../../entities").Reward): Promise<void>;
47
47
  getCampaignList(params: import("../types/Campaign").GetCampaignListParams): Promise<import("../types/Campaign").CampaignList>;
48
- "__#93@#getCampaignWithCounts"(params: import("../types/Campaign").GetCampaignParams): Promise<import("../types/Campaign").CampaignWithCounts | null>;
48
+ "__#118@#getCampaignWithCounts"(params: import("../types/Campaign").GetCampaignParams): Promise<import("../types/Campaign").CampaignWithCounts | null>;
49
49
  checkCampaignExists(id: string): Promise<boolean>;
50
50
  saveUser(user: import("../../entities").User | null): Promise<void>;
51
51
  getUserByID(id: string): Promise<import("../../entities").User | null>;
@@ -86,7 +86,7 @@ export declare function ContentDBMixin<TBase extends CampaignDBConstructor>(Base
86
86
  run(sql: import("sqlite").ISqlite.SqlType, ...params: any[]): Promise<import("sqlite").ISqlite.RunResult<import("sqlite3").Statement>>;
87
87
  get<T = any>(sql: import("sqlite").ISqlite.SqlType, ...params: any[]): Promise<T | undefined>;
88
88
  all<T = any[]>(sql: import("sqlite").ISqlite.SqlType, ...params: any[]): Promise<T>;
89
- "__#95@#interpolateSqlParams"(sql: import("sqlite").ISqlite.SqlType | string, params: any[]): string;
89
+ "__#120@#interpolateSqlParams"(sql: import("sqlite").ISqlite.SqlType | string, params: any[]): string;
90
90
  log(level: import("../..").LogLevel, ...msg: any[]): void;
91
91
  };
92
92
  } & TBase;
@@ -12,7 +12,7 @@ export declare function EnvDBMixin<TBase extends DBConstructor>(Base: TBase): {
12
12
  run(sql: import("sqlite").ISqlite.SqlType, ...params: any[]): Promise<import("sqlite").ISqlite.RunResult<import("sqlite3").Statement>>;
13
13
  get<T = any>(sql: import("sqlite").ISqlite.SqlType, ...params: any[]): Promise<T | undefined>;
14
14
  all<T = any[]>(sql: import("sqlite").ISqlite.SqlType, ...params: any[]): Promise<T>;
15
- "__#95@#interpolateSqlParams"(sql: import("sqlite").ISqlite.SqlType | string, params: any[]): string;
15
+ "__#120@#interpolateSqlParams"(sql: import("sqlite").ISqlite.SqlType | string, params: any[]): string;
16
16
  log(level: import("../..").LogLevel, ...msg: any[]): void;
17
17
  };
18
18
  } & TBase;
@@ -41,7 +41,7 @@ export declare function MediaDBMixin<TBase extends DBConstructor>(Base: TBase):
41
41
  run(sql: import("sqlite").ISqlite.SqlType, ...params: any[]): Promise<import("sqlite").ISqlite.RunResult<import("sqlite3").Statement>>;
42
42
  get<T = any>(sql: import("sqlite").ISqlite.SqlType, ...params: any[]): Promise<T | undefined>;
43
43
  all<T = any[]>(sql: import("sqlite").ISqlite.SqlType, ...params: any[]): Promise<T>;
44
- "__#95@#interpolateSqlParams"(sql: import("sqlite").ISqlite.SqlType | string, params: any[]): string;
44
+ "__#120@#interpolateSqlParams"(sql: import("sqlite").ISqlite.SqlType | string, params: any[]): string;
45
45
  log(level: import("../..").LogLevel, ...msg: any[]): void;
46
46
  };
47
47
  } & TBase;
@@ -42,7 +42,7 @@ export declare function UserDBMixin<TBase extends MediaDBConstructor>(Base: TBas
42
42
  run(sql: import("sqlite").ISqlite.SqlType, ...params: any[]): Promise<import("sqlite").ISqlite.RunResult<import("sqlite3").Statement>>;
43
43
  get<T = any>(sql: import("sqlite").ISqlite.SqlType, ...params: any[]): Promise<T | undefined>;
44
44
  all<T = any[]>(sql: import("sqlite").ISqlite.SqlType, ...params: any[]): Promise<T>;
45
- "__#95@#interpolateSqlParams"(sql: import("sqlite").ISqlite.SqlType | string, params: any[]): string;
45
+ "__#120@#interpolateSqlParams"(sql: import("sqlite").ISqlite.SqlType | string, params: any[]): string;
46
46
  log(level: import("../..").LogLevel, ...msg: any[]): void;
47
47
  };
48
48
  } & TBase;