patreon-dl 3.6.1 → 3.7.1

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 (68) hide show
  1. package/NOTICE +23 -0
  2. package/README.md +30 -6
  3. package/bin/patreon-dl-vimeo.js +7 -4
  4. package/dist/browse/api/CampaignAPIMixin.d.ts +1 -1
  5. package/dist/browse/api/ContentAPIMixin.d.ts +4 -1
  6. package/dist/browse/api/ContentAPIMixin.js +44 -11
  7. package/dist/browse/api/ContentAPIMixin.js.map +1 -1
  8. package/dist/browse/api/FilterAPIMixin.d.ts +1 -1
  9. package/dist/browse/api/SettingsAPIMixin.d.ts +1 -0
  10. package/dist/browse/api/SettingsAPIMixin.js +4 -2
  11. package/dist/browse/api/SettingsAPIMixin.js.map +1 -1
  12. package/dist/browse/api/index.d.ts +12 -6
  13. package/dist/browse/db/CampaignDBMixin.d.ts +3 -3
  14. package/dist/browse/db/ContentDBMixin.d.ts +14 -14
  15. package/dist/browse/db/ContentDBMixin.js +12 -5
  16. package/dist/browse/db/ContentDBMixin.js.map +1 -1
  17. package/dist/browse/db/index.d.ts +17 -17
  18. package/dist/browse/server/handler/SettingsAPIRequestHandler.js +2 -1
  19. package/dist/browse/server/handler/SettingsAPIRequestHandler.js.map +1 -1
  20. package/dist/browse/types/Settings.d.ts +3 -0
  21. package/dist/browse/types/Settings.js.map +1 -1
  22. package/dist/browse/web/assets/{index-Dw_64hkR.js → index-B1h15ViZ.js} +31 -31
  23. package/dist/browse/web/assets/index-CQnQD2xz.css +1 -0
  24. package/dist/browse/web/index.html +2 -2
  25. package/dist/cli/CLIOptions.js +2 -0
  26. package/dist/cli/CLIOptions.js.map +1 -1
  27. package/dist/cli/ConfigFileParser.js +2 -0
  28. package/dist/cli/ConfigFileParser.js.map +1 -1
  29. package/dist/downloaders/Downloader.d.ts +5 -4
  30. package/dist/downloaders/Downloader.js +3 -1
  31. package/dist/downloaders/Downloader.js.map +1 -1
  32. package/dist/downloaders/DownloaderOptions.d.ts +1 -0
  33. package/dist/downloaders/DownloaderOptions.js +2 -0
  34. package/dist/downloaders/DownloaderOptions.js.map +1 -1
  35. package/dist/downloaders/PostDownloader.js +10 -0
  36. package/dist/downloaders/PostDownloader.js.map +1 -1
  37. package/dist/downloaders/ProductDownloader.js +2 -0
  38. package/dist/downloaders/ProductDownloader.js.map +1 -1
  39. package/dist/downloaders/task/DownloadTaskFactory.d.ts +4 -0
  40. package/dist/downloaders/task/DownloadTaskFactory.js +3 -2
  41. package/dist/downloaders/task/DownloadTaskFactory.js.map +1 -1
  42. package/dist/downloaders/task/M3U8DownloadTask.d.ts +1 -0
  43. package/dist/downloaders/task/M3U8DownloadTask.js +137 -39
  44. package/dist/downloaders/task/M3U8DownloadTask.js.map +1 -1
  45. package/dist/downloaders/task/YouTubeDownloadTask.js +7 -2
  46. package/dist/downloaders/task/YouTubeDownloadTask.js.map +1 -1
  47. package/dist/parsers/PageParser.js +1 -1
  48. package/dist/parsers/PageParser.js.map +1 -1
  49. package/dist/parsers/PostParser.js +62 -7
  50. package/dist/parsers/PostParser.js.map +1 -1
  51. package/dist/utils/Fetcher.d.ts +4 -1
  52. package/dist/utils/Fetcher.js +6 -1
  53. package/dist/utils/Fetcher.js.map +1 -1
  54. package/dist/utils/FilenameFormatHelper.d.ts +2 -2
  55. package/dist/utils/FilenameFormatHelper.js +45 -2
  56. package/dist/utils/FilenameFormatHelper.js.map +1 -1
  57. package/dist/utils/MediaFilenameResolver.d.ts +4 -1
  58. package/dist/utils/MediaFilenameResolver.js +6 -4
  59. package/dist/utils/MediaFilenameResolver.js.map +1 -1
  60. package/dist/utils/URLHelper.js +8 -8
  61. package/dist/utils/URLHelper.js.map +1 -1
  62. package/dist/utils/YouTubeCredentialsCapturer.js +1 -1
  63. package/dist/utils/YouTubeCredentialsCapturer.js.map +1 -1
  64. package/dist/utils/yt/InnertubeLoader.d.ts +19 -1
  65. package/dist/utils/yt/InnertubeLoader.js +55 -15
  66. package/dist/utils/yt/InnertubeLoader.js.map +1 -1
  67. package/package.json +2 -1
  68. package/dist/browse/web/assets/index-DjOKbT1U.css +0 -1
package/NOTICE ADDED
@@ -0,0 +1,23 @@
1
+ NOTICE
2
+
3
+ This project includes the following third-party software:
4
+
5
+ - LightGallery (https://www.lightgalleryjs.com/)
6
+ Copyright © 2016–2025
7
+ Licensed under the GNU General Public License v3.0 (GPLv3)
8
+ Developed by Srinivas Tamada and contributors.
9
+ Source: https://github.com/sachinchoolur/lightGallery
10
+
11
+ - Bottleneck (https://github.com/SGrondin/bottleneck)
12
+ Licensed under the BSD 2-Clause License
13
+
14
+ - Fast-Copy (https://github.com/planttheidea/fast-copy)
15
+ Licensed under the GNU General Public License v3.0 (GPLv3)
16
+
17
+ - argv-split (https://github.com/75lb/argv-split)
18
+ Licensed under the GNU General Public License v2.0 (GPLv2)
19
+
20
+ All other dependencies are used under permissive licenses such as MIT, Apache-2.0, or ISC, and do not require attribution beyond preservation of their license terms in `node_modules`.
21
+
22
+ This project is open source and licensed under the MIT License.
23
+ In accordance with the terms of the above licenses, this NOTICE file provides attribution and license information for included third-party software.
package/README.md CHANGED
@@ -11,7 +11,7 @@ This repo contains the `patreon-dl` library and its command-line tool. For GUI a
11
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
- - videos
14
+ - videos - but see [limitations](#limitations) on Patreon-hosted videos
15
15
  - images
16
16
  - audio
17
17
  - attachments
@@ -26,12 +26,23 @@ You can run `patreon-dl` from the command-line or [use it as a library](./docs/L
26
26
 
27
27
  ### Limitations
28
28
 
29
+ **Embedded videos / links**
30
+
29
31
  - Embedded links are not followed; only info about the embed is saved. Exception:
30
32
  - YouTube video link - in which case the video is downloaded; or
31
33
  - An external downloader is configured for the link provider.
32
34
 
33
35
  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
36
 
37
+ **Patreon-hosted videos with DRM protection**
38
+
39
+ Some videos served by Patreon are now protected with DRM. Because these videos require a real-time decryption key available only during authorized streaming, they cannot be played back normally after being downloaded.
40
+
41
+ By default, `patreon-dl` will skip DRM-protected content to avoid broken downloads. If you still wish to download these files, you can force the download using:
42
+
43
+ - CLI: Use the `protected.media` option (see [example.conf](./example.conf)).
44
+ - Library: Use the `include.protectedMedia` option (see [documentation](./docs/Library.md)).
45
+
35
46
  ### FFmpeg dependency
36
47
 
37
48
  [FFmpeg](https://ffmpeg.org) is required when downloading:
@@ -60,16 +71,16 @@ path.to.deno = "path/to/deno"
60
71
  ...
61
72
  ```
62
73
 
63
- #### Premium access
74
+ #### Premium access / "Login required" error
75
+
76
+ If you have a YouTube Premium subscription, you can connect `patreon-dl` to your account and download videos at qualities available only to Premium accounts (e.g. '1080p Premium'). You will also need to connect to an account (not necessarily Premium) if you get a "Login required" error message during download.
64
77
 
65
- If you have a YouTube Premium subscription, you can connect `patreon-dl` to your account and download videos at qualities available only to Premium accounts (e.g. '1080p Premium'). For CLI users, you would configure `patreon-dl` as follows:
78
+ For CLI users, you would configure `patreon-dl` as follows:
66
79
 
67
80
  ```
68
81
  $ patreon-dl --configure-youtube
69
82
  ```
70
83
 
71
- > ...or you may just refer to the next section on how to download enhanecd-quality videos without a Premium account.
72
-
73
84
  ### Embedded videos / links - external downloader
74
85
 
75
86
  You can specify external programs to download embedded videos or from embedded links. For YouTube videos, this will replace the built-in downloader.
@@ -109,7 +120,7 @@ $ patreon-dl [OPTION]... URL
109
120
  | `--dry-run` | | Run without writing files to disk (except logs, if any). Intended for testing / debugging. |
110
121
  | <code><nobr>--list-tiers &lt;creator&gt;</nobr></code> | | <p>List tiers for the given creator(s). Separate multiple creators with a comma.</p>The purpose of this is to let you find out what tier IDs to set for `posts.in.tier` filtering option under `include` section of [configuration file](#configuration-file). |
111
122
  | <code><nobr>--list-tiers-uid &lt;user ID&gt;</nobr></code> | | Same as `--list-tiers`, but takes user ID instead of vanity. |
112
- | `--configure-youtube` | | <p>Configure YouTube connection.</p>`patreon-dl` supports downloading embedded YouTube videos. If you have a YouTube Premium account, you can connect `patreon-dl` to it for downloading Premium-quality streams. |
123
+ | `--configure-youtube` | | <p>Configure YouTube connection.</p>`patreon-dl` supports downloading embedded YouTube videos. If you have a YouTube Premium account, you can connect `patreon-dl` to it for downloading Premium-quality streams. You will also need to connect to an account if you get a "Login required" error message during download.|
113
124
 
114
125
  ### URL
115
126
 
@@ -203,6 +214,7 @@ include.posts.in.tier = 123456, 789100
203
214
  # include.content.info
204
215
  # include.preview.media
205
216
  # include.content.media
217
+ # include.protected.media
206
218
  # include.all.media.variants
207
219
  # include.images.by.filename
208
220
  # include.audio.by.filename
@@ -293,6 +305,18 @@ Note the URL shown in the output. Open this URL in a web browser to begin viewin
293
305
 
294
306
  ## Changelog
295
307
 
308
+ 3.7.1
309
+ - Check and skip download of Patreon-hosted videos that are protected by DRM; add `include.protectedMedia` / `protected.media` option.
310
+ - Fix campaign not found when targeting collections ([#124](https://github.com/patrickkfkan/patreon-dl/issues/124)).
311
+
312
+ v3.7.0
313
+ - Vimeo download script: fetch full player URL ([#118](https://github.com/patrickkfkan/patreon-dl/issues/118))
314
+ - Fix downloaded posts missing content / teaser ([@Fabelwesen](https://github.com/Fabelwesen) - [#119](https://github.com/patrickkfkan/patreon-dl/issues/119))
315
+ - Add "max content width" option to browse settings ([#122](https://github.com/patrickkfkan/patreon-dl/issues/122))
316
+ - Map inline post links to local server routes (contrib by [@Fabelwesen](https://github.com/Fabelwesen) - [#121](https://github.com/patrickkfkan/patreon-dl/issues/121))
317
+ - Support additional fields in `media.filename.format` ([patreon-dl-gui#51](https://github.com/patrickkfkan/patreon-dl-gui/issues/51))
318
+ - YT downloader: fix error in n/sig decipher function extraction
319
+
296
320
  v3.6.1
297
321
  - Fix Embedly download script error on retrying with alternative URL.
298
322
 
@@ -24,6 +24,7 @@
24
24
  */
25
25
 
26
26
  import EmbedlyDownloader from './EmbedlyDownloader.js';
27
+ import * as entities from 'entities';
27
28
 
28
29
  class VimeoDownloader extends EmbedlyDownloader {
29
30
  constructor() {
@@ -33,12 +34,14 @@ class VimeoDownloader extends EmbedlyDownloader {
33
34
  // Override
34
35
  getPlayerURL(html) {
35
36
  if (html) {
36
- const regex = /https:\/\/player\.vimeo\.com\/video\/\d+/g;
37
+ const regex = /src=\"(https:\/\/player\.vimeo\.com\/video\/\d+(?:\?.+?)?)\"/g;
37
38
  const match = regex.exec(html);
38
- if (match && match[0]) {
39
- console.log('Found Vimeo player URL from embed HTML:', match[0]);
40
- return match[0];
39
+ if (match && match[1]) {
40
+ const url = entities.decodeHTML(match[1]);
41
+ console.log("Found Vimeo player URL from embed HTML:", url);
42
+ return url;
41
43
  }
44
+ console.warn("Vimeo player URL not found in embed HTML");
42
45
  }
43
46
  return super.getPlayerURL(html);
44
47
  }
@@ -11,7 +11,7 @@ export declare function CampaignAPIMixin<TBase extends APIConstructor>(Base: TBa
11
11
  withCounts?: false;
12
12
  }): Campaign | null;
13
13
  getCampaign(params: GetCampaignParams): Campaign | CampaignWithCounts | null;
14
- "__#129@#sanitizeCampaign"(campaign: Campaign): void;
14
+ "__#130@#sanitizeCampaign"(campaign: Campaign): void;
15
15
  name: string;
16
16
  db: import("../db").DBInstance;
17
17
  logger?: import("../..").Logger | null;
@@ -1,3 +1,4 @@
1
+ import { type CheerioAPI } from 'cheerio';
1
2
  import { type APIConstructor } from ".";
2
3
  import { type Product, type Post } from "../../entities";
3
4
  import { type GetContentContext, type ContentType, type GetContentListParams, type GetCollectionListParams, type GetPostTagListParams } from "../types/Content.js";
@@ -13,7 +14,9 @@ export declare function ContentAPIMixin<TBase extends APIConstructor>(Base: TBas
13
14
  } | null;
14
15
  getCollectionList(params: GetCollectionListParams): import("../types/Content.js").CollectionList;
15
16
  getPostTagList(params: GetPostTagListParams): import("../types/Content.js").PostTagList;
16
- "__#130@#processPostContentInlineMedia"(post: Post): void;
17
+ "__#131@#processPostContentElements"(post: Post): void;
18
+ "__#131@#processInlineMedia"($: CheerioAPI, post: Post): boolean;
19
+ "__#131@#processInlineLinks"($: CheerioAPI): boolean;
17
20
  name: string;
18
21
  db: import("../db").DBInstance;
19
22
  logger?: import("../..").Logger | null;
@@ -11,7 +11,7 @@ const DEFAULT_CONTENT_LIST_SORT_BY = 'a-z';
11
11
  const DEFAULT_COLLECTION_LIST_SIZE = 10;
12
12
  const DEFAULT_COLLECTION_LIST_SORT_BY = 'a-z';
13
13
  export function ContentAPIMixin(Base) {
14
- var _ContentAPI_instances, _ContentAPI_processPostContentInlineMedia, _a;
14
+ var _ContentAPI_instances, _ContentAPI_processPostContentElements, _ContentAPI_processInlineMedia, _ContentAPI_processInlineLinks, _a;
15
15
  return _a = class ContentAPI extends Base {
16
16
  constructor() {
17
17
  super(...arguments);
@@ -28,7 +28,7 @@ export function ContentAPIMixin(Base) {
28
28
  for (const item of list.items) {
29
29
  switch (item.type) {
30
30
  case 'post':
31
- __classPrivateFieldGet(this, _ContentAPI_instances, "m", _ContentAPI_processPostContentInlineMedia).call(this, item);
31
+ __classPrivateFieldGet(this, _ContentAPI_instances, "m", _ContentAPI_processPostContentElements).call(this, item);
32
32
  item.content = this.sanitizeHTML(item.content || '');
33
33
  break;
34
34
  case 'product': {
@@ -43,7 +43,7 @@ export function ContentAPIMixin(Base) {
43
43
  getPost(id) {
44
44
  const post = this.db.getContent(id, 'post');
45
45
  if (post) {
46
- __classPrivateFieldGet(this, _ContentAPI_instances, "m", _ContentAPI_processPostContentInlineMedia).call(this, post);
46
+ __classPrivateFieldGet(this, _ContentAPI_instances, "m", _ContentAPI_processPostContentElements).call(this, post);
47
47
  post.content = this.sanitizeHTML(post.content || '');
48
48
  }
49
49
  return post;
@@ -74,17 +74,28 @@ export function ContentAPIMixin(Base) {
74
74
  }
75
75
  },
76
76
  _ContentAPI_instances = new WeakSet(),
77
- _ContentAPI_processPostContentInlineMedia = function _ContentAPI_processPostContentInlineMedia(post) {
77
+ _ContentAPI_processPostContentElements = function _ContentAPI_processPostContentElements(post) {
78
78
  const html = post.content || '';
79
- const hasImages = post.images.length > 0;
80
- const hasLinkedAttachments = post.linkedAttachments && post.linkedAttachments.length > 0;
81
- if (!html || (!hasImages && !hasLinkedAttachments)) {
79
+ if (!html) {
82
80
  return;
83
81
  }
84
82
  const $ = cheerioLoad(html);
85
- const replacedMediaIds = [];
83
+ const inlineMediaModified = __classPrivateFieldGet(this, _ContentAPI_instances, "m", _ContentAPI_processInlineMedia).call(this, $, post);
84
+ const inlineLinksModified = __classPrivateFieldGet(this, _ContentAPI_instances, "m", _ContentAPI_processInlineLinks).call(this, $);
85
+ if (inlineMediaModified || inlineLinksModified) {
86
+ post.content = $.html();
87
+ }
88
+ },
89
+ _ContentAPI_processInlineMedia = function _ContentAPI_processInlineMedia($, post) {
90
+ const hasImages = post.images.length > 0;
91
+ const hasLinkedAttachments = post.linkedAttachments && post.linkedAttachments.length > 0;
92
+ if (!hasImages && !hasLinkedAttachments) {
93
+ return false;
94
+ }
86
95
  let hasModified = false;
96
+ // Images
87
97
  if (hasImages) {
98
+ const replacedMediaIds = [];
88
99
  $('img').each((_, _el) => {
89
100
  const el = $(_el);
90
101
  const id = el.attr('data-media-id');
@@ -136,9 +147,31 @@ export function ContentAPIMixin(Base) {
136
147
  }
137
148
  });
138
149
  }
139
- if (hasModified) {
140
- post.content = $.html();
141
- }
150
+ return hasModified;
151
+ },
152
+ _ContentAPI_processInlineLinks = function _ContentAPI_processInlineLinks($) {
153
+ let hasModified = false;
154
+ $('a').each((_, _el) => {
155
+ const el = $(_el);
156
+ const href = el.attr('href') || '';
157
+ let an;
158
+ try {
159
+ an = href.startsWith('https://') ? URLHelper.analyzeURL(href) : null;
160
+ }
161
+ catch (error) {
162
+ this.log('warn', `Error analyzing inline link "${href}":`, error);
163
+ an = null;
164
+ }
165
+ if (an && an.type === 'post') {
166
+ const postExistsInDB = this.db.checkContentExists(an.postId, 'post');
167
+ if (postExistsInDB) {
168
+ el.attr('href', `/posts/${an.postId}`);
169
+ el.removeAttr('target');
170
+ hasModified = true;
171
+ }
172
+ }
173
+ });
174
+ return hasModified;
142
175
  },
143
176
  _a;
144
177
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ContentAPIMixin.js","sourceRoot":"","sources":["../../../src/browse/api/ContentAPIMixin.ts"],"names":[],"mappings":"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","sourcesContent":["import { load as cheerioLoad } from 'cheerio';\nimport { type APIConstructor } from \".\";\nimport { type Product, type Post } from \"../../entities\";\nimport { type GetContentContext, type ContentListSortBy, type ContentType, type GetContentListParams, type GetCollectionListParams, type CollectionListSortBy, type GetPostTagListParams } from \"../types/Content.js\";\nimport RawDataExtractor from '../web/utils/RawDataExtractor.js';\nimport { URLHelper } from '../../utils/index.js';\n\nconst DEFAULT_CONTENT_LIST_SIZE = 10;\nconst DEFAULT_CONTENT_LIST_SORT_BY: ContentListSortBy = 'a-z';\n\nconst DEFAULT_COLLECTION_LIST_SIZE = 10;\nconst DEFAULT_COLLECTION_LIST_SORT_BY: CollectionListSortBy = 'a-z';\n\nexport function ContentAPIMixin<TBase extends APIConstructor>(Base: TBase) {\n return class ContentAPI extends Base {\n getContentList<T extends ContentType>(params: GetContentListParams<T>) {\n const { sortBy = DEFAULT_CONTENT_LIST_SORT_BY, limit = DEFAULT_CONTENT_LIST_SIZE, offset = 0 } = params;\n const list = this.db.getContentList({\n ...params,\n sortBy,\n limit,\n offset,\n });\n for (const item of list.items) {\n switch (item.type) {\n case 'post':\n this.#processPostContentInlineMedia(item);\n item.content = this.sanitizeHTML(item.content || '');\n break;\n case 'product': {\n const description = RawDataExtractor.getProductRichTextDescription(item);\n item.description = description ? this.sanitizeHTML(description) : null;\n break;\n }\n }\n }\n return list;\n }\n\n getPost(id: string) {\n const post = this.db.getContent(id, 'post');\n if (post) {\n this.#processPostContentInlineMedia(post);\n post.content = this.sanitizeHTML(post.content || '');\n }\n return post;\n }\n\n getProduct(id: string) {\n return this.db.getContent(id, 'product');\n }\n\n getPreviousNextContent<T extends ContentType>(content: Post | Product, context: GetContentContext<T>) {\n return this.db.getPreviousNextContent(content, context);\n }\n\n getCollection(id: string) {\n return this.db.getCollection(id);\n }\n\n getCollectionList(params: GetCollectionListParams) {\n const {\n search = '',\n sortBy = DEFAULT_COLLECTION_LIST_SORT_BY,\n limit = DEFAULT_COLLECTION_LIST_SIZE,\n offset = 0\n } = params;\n return this.db.getCollectionList({\n campaign: params.campaign,\n search,\n sortBy,\n limit,\n offset\n });\n }\n\n getPostTagList(params: GetPostTagListParams) {\n return this.db.getPostTagList({\n campaign: params.campaign,\n });\n }\n\n #processPostContentInlineMedia(post: Post) {\n const html = post.content || '';\n const hasImages = post.images.length > 0;\n const hasLinkedAttachments = post.linkedAttachments && post.linkedAttachments.length > 0;\n if (!html || (!hasImages && !hasLinkedAttachments)) {\n return;\n }\n\n const $ = cheerioLoad(html);\n const replacedMediaIds: string[] = [];\n let hasModified = false;\n\n if (hasImages) {\n $('img').each((_, _el) => {\n const el = $(_el);\n const id = el.attr('data-media-id');\n const matched = id ? post.images.find(img => img.id === id && img.downloaded) : null;\n const src = matched ? `/media/${matched.id}` : el.attr('src');\n const imgEl = $('<img>').attr('src', src);\n const aEl = $('<a>')\n .attr('href', src)\n .attr('class', 'lightgallery-item')\n .append(imgEl);\n const wrapperEl = $('<div>')\n .attr('class', 'post-card__inline-media-wrapper')\n .append(aEl);\n if (!matched) {\n const caption = \"(Externally hosted - not stored locally)\";\n wrapperEl.append(\n $('<span>').attr('class', 'post-card__inline-media-caption').append(caption)\n );\n }\n el.replaceWith(wrapperEl);\n if (id && matched) {\n replacedMediaIds.push(id);\n }\n });\n if (replacedMediaIds.length > 0) {\n hasModified = true;\n // Remove images that have been inlined\n post.images = post.images.filter((img) => !replacedMediaIds.includes(img.id));\n }\n }\n\n // Linked attachments\n if (hasLinkedAttachments) {\n $('a').each((_, _el) => {\n const aEl = $(_el);\n const href = aEl.attr('href') || '';\n const { validated, ownerId, mediaId } = URLHelper.isAttachmentLink(href);\n if (validated) {\n let modifiedPath: string | undefined;\n if (post.id !== ownerId) {\n const isDownloaded = post.linkedAttachments?.find((att) => att.postId === ownerId && att.mediaId === mediaId)?.downloadable?.downloaded;\n modifiedPath = isDownloaded && `/media/${mediaId}?lapid=${post.id}`;\n }\n else {\n const isDownloaded = post.attachments.find((att) => att.id === mediaId)?.downloaded;\n modifiedPath = isDownloaded && `/media/${mediaId}`;\n }\n if (modifiedPath) {\n aEl.attr('href', modifiedPath);\n hasModified = true;\n }\n }\n });\n }\n\n if (hasModified) {\n post.content = $.html();\n }\n }\n }\n}"]}
1
+ {"version":3,"file":"ContentAPIMixin.js","sourceRoot":"","sources":["../../../src/browse/api/ContentAPIMixin.ts"],"names":[],"mappings":"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","sourcesContent":["import { type CheerioAPI, load as cheerioLoad } from 'cheerio';\nimport { type APIConstructor } from \".\";\nimport { type Product, type Post } from \"../../entities\";\nimport { type GetContentContext, type ContentListSortBy, type ContentType, type GetContentListParams, type GetCollectionListParams, type CollectionListSortBy, type GetPostTagListParams } from \"../types/Content.js\";\nimport RawDataExtractor from '../web/utils/RawDataExtractor.js';\nimport { URLHelper } from '../../utils/index.js';\n\nconst DEFAULT_CONTENT_LIST_SIZE = 10;\nconst DEFAULT_CONTENT_LIST_SORT_BY: ContentListSortBy = 'a-z';\n\nconst DEFAULT_COLLECTION_LIST_SIZE = 10;\nconst DEFAULT_COLLECTION_LIST_SORT_BY: CollectionListSortBy = 'a-z';\n\nexport function ContentAPIMixin<TBase extends APIConstructor>(Base: TBase) {\n return class ContentAPI extends Base {\n getContentList<T extends ContentType>(params: GetContentListParams<T>) {\n const { sortBy = DEFAULT_CONTENT_LIST_SORT_BY, limit = DEFAULT_CONTENT_LIST_SIZE, offset = 0 } = params;\n const list = this.db.getContentList({\n ...params,\n sortBy,\n limit,\n offset,\n });\n for (const item of list.items) {\n switch (item.type) {\n case 'post':\n this.#processPostContentElements(item);\n item.content = this.sanitizeHTML(item.content || '');\n break;\n case 'product': {\n const description = RawDataExtractor.getProductRichTextDescription(item);\n item.description = description ? this.sanitizeHTML(description) : null;\n break;\n }\n }\n }\n return list;\n }\n\n getPost(id: string) {\n const post = this.db.getContent(id, 'post');\n if (post) {\n this.#processPostContentElements(post);\n post.content = this.sanitizeHTML(post.content || '');\n }\n return post;\n }\n\n getProduct(id: string) {\n return this.db.getContent(id, 'product');\n }\n\n getPreviousNextContent<T extends ContentType>(content: Post | Product, context: GetContentContext<T>) {\n return this.db.getPreviousNextContent(content, context);\n }\n\n getCollection(id: string) {\n return this.db.getCollection(id);\n }\n\n getCollectionList(params: GetCollectionListParams) {\n const {\n search = '',\n sortBy = DEFAULT_COLLECTION_LIST_SORT_BY,\n limit = DEFAULT_COLLECTION_LIST_SIZE,\n offset = 0\n } = params;\n return this.db.getCollectionList({\n campaign: params.campaign,\n search,\n sortBy,\n limit,\n offset\n });\n }\n\n getPostTagList(params: GetPostTagListParams) {\n return this.db.getPostTagList({\n campaign: params.campaign,\n });\n }\n\n #processPostContentElements(post: Post) {\n const html = post.content || '';\n \n if (!html) {\n return;\n }\n\n const $ = cheerioLoad(html);\n const inlineMediaModified = this.#processInlineMedia($, post);\n const inlineLinksModified = this.#processInlineLinks($);\n if (inlineMediaModified || inlineLinksModified) {\n post.content = $.html();\n }\n }\n\n #processInlineMedia($: CheerioAPI, post: Post) {\n const hasImages = post.images.length > 0;\n const hasLinkedAttachments = post.linkedAttachments && post.linkedAttachments.length > 0;\n if (!hasImages && !hasLinkedAttachments) {\n return false;\n }\n let hasModified = false;\n \n // Images\n if (hasImages) {\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 hasModified = true;\n // Remove images that have been inlined\n post.images = post.images.filter((img) => !replacedMediaIds.includes(img.id));\n }\n }\n\n // Linked attachments\n if (hasLinkedAttachments) {\n $('a').each((_, _el) => {\n const aEl = $(_el);\n const href = aEl.attr('href') || '';\n const { validated, ownerId, mediaId } = URLHelper.isAttachmentLink(href);\n if (validated) {\n let modifiedPath: string | undefined;\n if (post.id !== ownerId) {\n const isDownloaded = post.linkedAttachments?.find((att) => att.postId === ownerId && att.mediaId === mediaId)?.downloadable?.downloaded;\n modifiedPath = isDownloaded && `/media/${mediaId}?lapid=${post.id}`;\n }\n else {\n const isDownloaded = post.attachments.find((att) => att.id === mediaId)?.downloaded;\n modifiedPath = isDownloaded && `/media/${mediaId}`;\n }\n if (modifiedPath) {\n aEl.attr('href', modifiedPath);\n hasModified = true;\n }\n }\n });\n }\n\n return hasModified;\n }\n\n #processInlineLinks($: CheerioAPI) {\n let hasModified = false;\n $('a').each((_, _el) => {\n const el = $(_el);\n const href = el.attr('href') || '';\n let an;\n try {\n an = href.startsWith('https://') ? URLHelper.analyzeURL(href) : null;\n }\n catch (error: unknown) { \n this.log('warn', `Error analyzing inline link \"${href}\":`, error);\n an = null;\n }\n if (an && an.type === 'post') {\n const postExistsInDB = this.db.checkContentExists(an.postId, 'post');\n if (postExistsInDB) {\n el.attr('href', `/posts/${an.postId}`);\n el.removeAttr('target');\n hasModified = true;\n }\n }\n });\n return hasModified;\n }\n }\n}"]}
@@ -3,7 +3,7 @@ import { type FilterData, type MediaFilterSearchParams, type PostFilterSearchPar
3
3
  export declare function FilterAPIMixin<TBase extends APIConstructor>(Base: TBase): {
4
4
  new (...args: any[]): {
5
5
  getPostFilterData(campaignId: string): FilterData<PostFilterSearchParams>;
6
- "__#131@#getPostTypeTitle"(postType: string): "Link" | "Audio" | "Image" | "Video" | "Other" | "Text" | "Podcast" | "Poll";
6
+ "__#132@#getPostTypeTitle"(postType: string): "Link" | "Audio" | "Image" | "Video" | "Other" | "Text" | "Podcast" | "Poll";
7
7
  getProductFilterData(campaignId: string): FilterData<ProductFilterSearchParams>;
8
8
  getMediaFilterData(campaignId: string): FilterData<MediaFilterSearchParams>;
9
9
  name: string;
@@ -6,6 +6,7 @@ export declare function SettingsAPIMixin<TBase extends APIConstructor>(Base: TBa
6
6
  theme: string;
7
7
  listItemsPerPage: number;
8
8
  galleryItemsPerPage: number;
9
+ maxContentWidth: import("../types/Settings.js").MaxContentWidth;
9
10
  };
10
11
  saveBrowseSettings(settings: BrowseSettings): void;
11
12
  getBrowseSettingOptions(): BrowseSettingOptions;
@@ -41,7 +41,8 @@ const THEMES = [
41
41
  const DEFAULT_BROWSE_SETTINGS = {
42
42
  theme: THEMES[0].value,
43
43
  listItemsPerPage: 20,
44
- galleryItemsPerPage: 100
44
+ galleryItemsPerPage: 100,
45
+ maxContentWidth: 'Standard'
45
46
  };
46
47
  export function SettingsAPIMixin(Base) {
47
48
  return class SettingsAPI extends Base {
@@ -62,7 +63,8 @@ export function SettingsAPIMixin(Base) {
62
63
  return {
63
64
  themes: THEMES,
64
65
  listItemsPerPage: [10, 20, 30, 50],
65
- galleryItemsPerPage: [50, 100, 150, 200]
66
+ galleryItemsPerPage: [50, 100, 150, 200],
67
+ maxContentWidth: ['Narrower', 'Standard', 'Wider']
66
68
  };
67
69
  }
68
70
  };
@@ -1 +1 @@
1
- {"version":3,"file":"SettingsAPIMixin.js","sourceRoot":"","sources":["../../../src/browse/api/SettingsAPIMixin.ts"],"names":[],"mappings":"AAGA,MAAM,uBAAuB,GAAG,iBAAiB,CAAC;AAElD,MAAM,MAAM,GAAkB;IAC5B;QACE,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,SAAS;QAChB,WAAW,EAAE,CAAC,iDAAiD,CAAC;KACjE;IACD,GAAG;QACD,OAAO;QACP,UAAU;QACV,OAAO;QACP,QAAQ;QACR,QAAQ;QACR,QAAQ;QACR,SAAS;QACT,QAAQ;QACR,OAAO;QACP,KAAK;QACL,SAAS;QACT,OAAO;QACP,OAAO;QACP,OAAO;QACP,QAAQ;QACR,WAAW;QACX,SAAS;QACT,SAAS;QACT,OAAO;QACP,OAAO;QACP,UAAU;QACV,WAAW;QACX,QAAQ;QACR,OAAO;QACP,MAAM;QACN,QAAQ;KACT,CAAC,GAAG,CAAC,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;QAC9B,IAAI,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC;QAChF,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EAAE,CAAC,sBAAsB,mBAAmB,oBAAoB,CAAC;KAC7E,CAAC,CAAC;CACJ,CAAA;AAED,MAAM,uBAAuB,GAAmB;IAC9C,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK;IACtB,gBAAgB,EAAE,EAAE;IACpB,mBAAmB,EAAE,GAAG;CACzB,CAAC;AAEF,MAAM,UAAU,gBAAgB,CAA+B,IAAW;IACxE,OAAO,MAAM,WAAY,SAAQ,IAAI;QACnC,iBAAiB;YACf,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,WAAW,CAAiB,uBAAuB,CAAC,CAAC;YAC9E,IAAI,QAAQ,EAAE,CAAC;gBACb,OAAO;oBACL,GAAG,uBAAuB;oBAC1B,GAAG,QAAQ;iBACZ,CAAC;YACJ,CAAC;YACD,OAAO,EAAC,GAAG,uBAAuB,EAAC,CAAC;QACtC,CAAC;QAED,kBAAkB,CAAC,QAAwB;YACzC,OAAO,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,uBAAuB,EAAE,QAAQ,CAAC,CAAC;QACjE,CAAC;QAED,uBAAuB;YACrB,OAAO;gBACL,MAAM,EAAE,MAAM;gBACd,gBAAgB,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;gBAClC,mBAAmB,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;aACzC,CAAC;QACJ,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["import { type APIConstructor } from \".\";\nimport { type BrowseSettingOptions, type BrowseSettings, type BrowseTheme } from \"../types/Settings.js\";\n\nconst BROWSE_SETTINGS_ENV_KEY = 'browse_settings';\n\nconst THEMES: BrowseTheme[] = [\n {\n name: 'Default',\n value: 'default',\n stylesheets: ['/themes/bootstrap/default/css/bootstrap.min.css']\n },\n ...[\n 'brite',\n 'cerulean',\n 'cosmo',\n 'cyborg',\n 'darkly',\n 'flatly',\n 'journal',\n 'litera',\n 'lumen',\n 'lux',\n 'materia',\n 'minty',\n 'morph',\n 'pulse',\n 'quartz',\n 'sandstone',\n 'simplex',\n 'sketchy',\n 'slate',\n 'solar',\n 'spacelab',\n 'superhero',\n 'united',\n 'vapor',\n 'yeti',\n 'zephyr'\n ].map((bootswatchThemeName) => ({\n name: bootswatchThemeName.charAt(0).toUpperCase() + bootswatchThemeName.slice(1),\n value: bootswatchThemeName,\n stylesheets: [`/themes/bootswatch/${bootswatchThemeName}/bootstrap.min.css`]\n }))\n]\n\nconst DEFAULT_BROWSE_SETTINGS: BrowseSettings = {\n theme: THEMES[0].value,\n listItemsPerPage: 20,\n galleryItemsPerPage: 100\n};\n\nexport function SettingsAPIMixin<TBase extends APIConstructor>(Base: TBase) {\n return class SettingsAPI extends Base {\n getBrowseSettings() {\n const settings = this.db.getEnvValue<BrowseSettings>(BROWSE_SETTINGS_ENV_KEY);\n if (settings) {\n return {\n ...DEFAULT_BROWSE_SETTINGS,\n ...settings\n };\n }\n return {...DEFAULT_BROWSE_SETTINGS};\n }\n\n saveBrowseSettings(settings: BrowseSettings) {\n return this.db.saveEnvValue(BROWSE_SETTINGS_ENV_KEY, settings);\n }\n\n getBrowseSettingOptions(): BrowseSettingOptions {\n return {\n themes: THEMES,\n listItemsPerPage: [10, 20, 30, 50],\n galleryItemsPerPage: [50, 100, 150, 200]\n };\n }\n }\n}"]}
1
+ {"version":3,"file":"SettingsAPIMixin.js","sourceRoot":"","sources":["../../../src/browse/api/SettingsAPIMixin.ts"],"names":[],"mappings":"AAGA,MAAM,uBAAuB,GAAG,iBAAiB,CAAC;AAElD,MAAM,MAAM,GAAkB;IAC5B;QACE,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,SAAS;QAChB,WAAW,EAAE,CAAC,iDAAiD,CAAC;KACjE;IACD,GAAG;QACD,OAAO;QACP,UAAU;QACV,OAAO;QACP,QAAQ;QACR,QAAQ;QACR,QAAQ;QACR,SAAS;QACT,QAAQ;QACR,OAAO;QACP,KAAK;QACL,SAAS;QACT,OAAO;QACP,OAAO;QACP,OAAO;QACP,QAAQ;QACR,WAAW;QACX,SAAS;QACT,SAAS;QACT,OAAO;QACP,OAAO;QACP,UAAU;QACV,WAAW;QACX,QAAQ;QACR,OAAO;QACP,MAAM;QACN,QAAQ;KACT,CAAC,GAAG,CAAC,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;QAC9B,IAAI,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC;QAChF,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EAAE,CAAC,sBAAsB,mBAAmB,oBAAoB,CAAC;KAC7E,CAAC,CAAC;CACJ,CAAA;AAED,MAAM,uBAAuB,GAAmB;IAC9C,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK;IACtB,gBAAgB,EAAE,EAAE;IACpB,mBAAmB,EAAE,GAAG;IACxB,eAAe,EAAE,UAAU;CAC5B,CAAC;AAEF,MAAM,UAAU,gBAAgB,CAA+B,IAAW;IACxE,OAAO,MAAM,WAAY,SAAQ,IAAI;QACnC,iBAAiB;YACf,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,WAAW,CAAiB,uBAAuB,CAAC,CAAC;YAC9E,IAAI,QAAQ,EAAE,CAAC;gBACb,OAAO;oBACL,GAAG,uBAAuB;oBAC1B,GAAG,QAAQ;iBACZ,CAAC;YACJ,CAAC;YACD,OAAO,EAAC,GAAG,uBAAuB,EAAC,CAAC;QACtC,CAAC;QAED,kBAAkB,CAAC,QAAwB;YACzC,OAAO,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,uBAAuB,EAAE,QAAQ,CAAC,CAAC;QACjE,CAAC;QAED,uBAAuB;YACrB,OAAO;gBACL,MAAM,EAAE,MAAM;gBACd,gBAAgB,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;gBAClC,mBAAmB,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;gBACxC,eAAe,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,CAAC;aACnD,CAAC;QACJ,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["import { type APIConstructor } from \".\";\nimport { type BrowseSettingOptions, type BrowseSettings, type BrowseTheme } from \"../types/Settings.js\";\n\nconst BROWSE_SETTINGS_ENV_KEY = 'browse_settings';\n\nconst THEMES: BrowseTheme[] = [\n {\n name: 'Default',\n value: 'default',\n stylesheets: ['/themes/bootstrap/default/css/bootstrap.min.css']\n },\n ...[\n 'brite',\n 'cerulean',\n 'cosmo',\n 'cyborg',\n 'darkly',\n 'flatly',\n 'journal',\n 'litera',\n 'lumen',\n 'lux',\n 'materia',\n 'minty',\n 'morph',\n 'pulse',\n 'quartz',\n 'sandstone',\n 'simplex',\n 'sketchy',\n 'slate',\n 'solar',\n 'spacelab',\n 'superhero',\n 'united',\n 'vapor',\n 'yeti',\n 'zephyr'\n ].map((bootswatchThemeName) => ({\n name: bootswatchThemeName.charAt(0).toUpperCase() + bootswatchThemeName.slice(1),\n value: bootswatchThemeName,\n stylesheets: [`/themes/bootswatch/${bootswatchThemeName}/bootstrap.min.css`]\n }))\n]\n\nconst DEFAULT_BROWSE_SETTINGS: BrowseSettings = {\n theme: THEMES[0].value,\n listItemsPerPage: 20,\n galleryItemsPerPage: 100,\n maxContentWidth: 'Standard'\n};\n\nexport function SettingsAPIMixin<TBase extends APIConstructor>(Base: TBase) {\n return class SettingsAPI extends Base {\n getBrowseSettings() {\n const settings = this.db.getEnvValue<BrowseSettings>(BROWSE_SETTINGS_ENV_KEY);\n if (settings) {\n return {\n ...DEFAULT_BROWSE_SETTINGS,\n ...settings\n };\n }\n return {...DEFAULT_BROWSE_SETTINGS};\n }\n\n saveBrowseSettings(settings: BrowseSettings) {\n return this.db.saveEnvValue(BROWSE_SETTINGS_ENV_KEY, settings);\n }\n\n getBrowseSettingOptions(): BrowseSettingOptions {\n return {\n themes: THEMES,\n listItemsPerPage: [10, 20, 30, 50],\n galleryItemsPerPage: [50, 100, 150, 200],\n maxContentWidth: ['Narrower', 'Standard', 'Wider']\n };\n }\n }\n}"]}
@@ -10,7 +10,7 @@ export declare class APIBase {
10
10
  constructor(db: DBInstance, logger?: Logger | null);
11
11
  static getInstance(db: DBInstance, logger?: Logger | null): {
12
12
  getPostFilterData(campaignId: string): import("../types/Filter.js").FilterData<import("../types/Filter.js").PostFilterSearchParams>;
13
- "__#131@#getPostTypeTitle"(postType: string): "Link" | "Audio" | "Image" | "Video" | "Other" | "Text" | "Podcast" | "Poll";
13
+ "__#132@#getPostTypeTitle"(postType: string): "Link" | "Audio" | "Image" | "Video" | "Other" | "Text" | "Podcast" | "Poll";
14
14
  getProductFilterData(campaignId: string): import("../types/Filter.js").FilterData<import("../types/Filter.js").ProductFilterSearchParams>;
15
15
  getMediaFilterData(campaignId: string): import("../types/Filter.js").FilterData<import("../types/Filter.js").MediaFilterSearchParams>;
16
16
  name: string;
@@ -30,6 +30,7 @@ export declare class APIBase {
30
30
  theme: string;
31
31
  listItemsPerPage: number;
32
32
  galleryItemsPerPage: number;
33
+ maxContentWidth: import("../types/Settings.js").MaxContentWidth;
33
34
  };
34
35
  saveBrowseSettings(settings: import("../types/Settings.js").BrowseSettings): void;
35
36
  getBrowseSettingOptions(): import("../types/Settings.js").BrowseSettingOptions;
@@ -49,7 +50,9 @@ export declare class APIBase {
49
50
  } | null;
50
51
  getCollectionList(params: import("../types/Content.js").GetCollectionListParams): import("../types/Content.js").CollectionList;
51
52
  getPostTagList(params: import("../types/Content.js").GetPostTagListParams): import("../types/Content.js").PostTagList;
52
- "__#130@#processPostContentInlineMedia"(post: import("../../index.js").Post): void;
53
+ "__#131@#processPostContentElements"(post: import("../../index.js").Post): void;
54
+ "__#131@#processInlineMedia"($: import("cheerio").CheerioAPI, post: import("../../index.js").Post): boolean;
55
+ "__#131@#processInlineLinks"($: import("cheerio").CheerioAPI): boolean;
53
56
  name: string;
54
57
  db: DBInstance;
55
58
  logger?: Logger | null;
@@ -64,7 +67,7 @@ export declare class APIBase {
64
67
  withCounts?: false;
65
68
  }): import("../../index.js").Campaign | null;
66
69
  getCampaign(params: import("../types/Campaign.js").GetCampaignParams): import("../../index.js").Campaign | import("../types/Campaign.js").CampaignWithCounts | null;
67
- "__#129@#sanitizeCampaign"(campaign: import("../../index.js").Campaign): void;
70
+ "__#130@#sanitizeCampaign"(campaign: import("../../index.js").Campaign): void;
68
71
  name: string;
69
72
  db: DBInstance;
70
73
  logger?: Logger | null;
@@ -77,7 +80,7 @@ export declare class APIBase {
77
80
  declare const API: {
78
81
  new (...args: any[]): {
79
82
  getPostFilterData(campaignId: string): import("../types/Filter.js").FilterData<import("../types/Filter.js").PostFilterSearchParams>;
80
- "__#131@#getPostTypeTitle"(postType: string): "Link" | "Audio" | "Image" | "Video" | "Other" | "Text" | "Podcast" | "Poll";
83
+ "__#132@#getPostTypeTitle"(postType: string): "Link" | "Audio" | "Image" | "Video" | "Other" | "Text" | "Podcast" | "Poll";
81
84
  getProductFilterData(campaignId: string): import("../types/Filter.js").FilterData<import("../types/Filter.js").ProductFilterSearchParams>;
82
85
  getMediaFilterData(campaignId: string): import("../types/Filter.js").FilterData<import("../types/Filter.js").MediaFilterSearchParams>;
83
86
  name: string;
@@ -101,6 +104,7 @@ declare const API: {
101
104
  theme: string;
102
105
  listItemsPerPage: number;
103
106
  galleryItemsPerPage: number;
107
+ maxContentWidth: import("../types/Settings.js").MaxContentWidth;
104
108
  };
105
109
  saveBrowseSettings(settings: import("../types/Settings.js").BrowseSettings): void;
106
110
  getBrowseSettingOptions(): import("../types/Settings.js").BrowseSettingOptions;
@@ -122,7 +126,9 @@ declare const API: {
122
126
  } | null;
123
127
  getCollectionList(params: import("../types/Content.js").GetCollectionListParams): import("../types/Content.js").CollectionList;
124
128
  getPostTagList(params: import("../types/Content.js").GetPostTagListParams): import("../types/Content.js").PostTagList;
125
- "__#130@#processPostContentInlineMedia"(post: import("../../index.js").Post): void;
129
+ "__#131@#processPostContentElements"(post: import("../../index.js").Post): void;
130
+ "__#131@#processInlineMedia"($: import("cheerio").CheerioAPI, post: import("../../index.js").Post): boolean;
131
+ "__#131@#processInlineLinks"($: import("cheerio").CheerioAPI): boolean;
126
132
  name: string;
127
133
  db: DBInstance;
128
134
  logger?: Logger | null;
@@ -139,7 +145,7 @@ declare const API: {
139
145
  withCounts?: false;
140
146
  }): import("../../index.js").Campaign | null;
141
147
  getCampaign(params: import("../types/Campaign.js").GetCampaignParams): import("../../index.js").Campaign | import("../types/Campaign.js").CampaignWithCounts | null;
142
- "__#129@#sanitizeCampaign"(campaign: import("../../index.js").Campaign): void;
148
+ "__#130@#sanitizeCampaign"(campaign: import("../../index.js").Campaign): void;
143
149
  name: string;
144
150
  db: DBInstance;
145
151
  logger?: Logger | null;
@@ -7,10 +7,10 @@ export declare function CampaignDBMixin<TBase extends UserDBConstructor>(Base: T
7
7
  new (...args: any[]): {
8
8
  saveCampaign(campaign: Campaign | null, downloadDate: Date, overwriteIfExists?: boolean): void;
9
9
  getCampaign(params: GetCampaignParams): Campaign | null;
10
- "__#113@#saveRewards"(campaign: Campaign): void;
11
- "__#113@#doSaveReward"(campaign: Campaign, reward: Reward): void;
10
+ "__#114@#saveRewards"(campaign: Campaign): void;
11
+ "__#114@#doSaveReward"(campaign: Campaign, reward: Reward): void;
12
12
  getCampaignList(params: GetCampaignListParams): CampaignList;
13
- "__#113@#getCampaignWithCounts"(params: GetCampaignParams): CampaignWithCounts | null;
13
+ "__#114@#getCampaignWithCounts"(params: GetCampaignParams): CampaignWithCounts | null;
14
14
  checkCampaignExists(id: string): boolean;
15
15
  saveUser(user: import("../../index.js").User | null): void;
16
16
  getUserByID(id: string): import("../../index.js").User | null;
@@ -5,13 +5,13 @@ import { type CampaignDBConstructor } from './CampaignDBMixin.js';
5
5
  export declare function ContentDBMixin<TBase extends CampaignDBConstructor>(Base: TBase): {
6
6
  new (...args: any[]): {
7
7
  saveContent(content: Post | Product): void;
8
- "__#114@#saveContentMedia"(content: Post | Product): void;
9
- "__#114@#savepostMedia"(post: Post): void;
10
- "__#114@#saveProductMedia"(product: Product): void;
11
- "__#114@#doSaveContentMedia"(content: Post | Product, media: Downloadable, mediaIndex: number, isPreview: boolean): void;
12
- "__#114@#publishedAtToTime"(publishedAt: string | null): number | null;
13
- "__#114@#savePostTiers"(post: Post): void;
14
- "__#114@#doSaveTier"(post: Post, tier: Tier): void;
8
+ "__#115@#saveContentMedia"(content: Post | Product): void;
9
+ "__#115@#savepostMedia"(post: Post): void;
10
+ "__#115@#saveProductMedia"(product: Product): void;
11
+ "__#115@#doSaveContentMedia"(content: Post | Product, media: Downloadable, mediaIndex: number, isPreview: boolean): void;
12
+ "__#115@#publishedAtToTime"(publishedAt: string | null): number | null;
13
+ "__#115@#savePostTiers"(post: Post): void;
14
+ "__#115@#doSaveTier"(post: Post, tier: Tier): void;
15
15
  savePostComments(post: Post, comments: Comment[]): void;
16
16
  checkPostCommentsExist(post: Post): boolean;
17
17
  getContent(id: string, contentType: "post"): PostWithComments | null;
@@ -26,7 +26,7 @@ export declare function ContentDBMixin<TBase extends CampaignDBConstructor>(Base
26
26
  * @param row Must have `details`, `comment_count` and `comments`
27
27
  * @returns
28
28
  */
29
- "__#114@#parseContentRowJoinedComments"(row: any): any;
29
+ "__#115@#parseContentRowJoinedComments"(row: any): any;
30
30
  getContentCountByDate(contentType: ContentType, groupBy: "year" | "month", filter?: {
31
31
  campaign?: Campaign | string | null;
32
32
  date?: Date | null;
@@ -43,26 +43,26 @@ export declare function ContentDBMixin<TBase extends CampaignDBConstructor>(Base
43
43
  title: string;
44
44
  count: number;
45
45
  }[];
46
- checkContentExists(id: string, contentType: ContentType, campaign: Campaign | null): boolean;
46
+ checkContentExists(id: string, contentType: ContentType, campaign?: Campaign | null): boolean;
47
47
  saveCollection(collection: Collection, campaign: Campaign | null, overwriteIfExists?: boolean): void;
48
48
  savePostTag(tag: PostTag, campaign: Campaign | null, overwriteIfExists?: boolean): void;
49
49
  getCollection(id: string): {
50
50
  collection: Collection;
51
51
  campaignId: string;
52
52
  } | null;
53
- "__#114@#savePostCollection"(post: Post): void;
53
+ "__#115@#savePostCollection"(post: Post): void;
54
54
  getCollectionList(params: GetCollectionListParams): CollectionList;
55
55
  checkCollectionExists(id: string): boolean;
56
56
  checkPostTagExists(id: string, campaign: Campaign | null): boolean;
57
57
  getPostComments(post: Post | string): Comment[] | null;
58
- "__#114@#savePostTags"(post: Post): void;
58
+ "__#115@#savePostTags"(post: Post): void;
59
59
  getPostTagList(params: GetPostTagListParams): PostTagList;
60
60
  saveCampaign(campaign: Campaign | null, downloadDate: Date, overwriteIfExists?: boolean): void;
61
61
  getCampaign(params: import("../types/Campaign").GetCampaignParams): Campaign | null;
62
- "__#113@#saveRewards"(campaign: Campaign): void;
63
- "__#113@#doSaveReward"(campaign: Campaign, reward: import("../../entities").Reward): void;
62
+ "__#114@#saveRewards"(campaign: Campaign): void;
63
+ "__#114@#doSaveReward"(campaign: Campaign, reward: import("../../entities").Reward): void;
64
64
  getCampaignList(params: import("../types/Campaign").GetCampaignListParams): import("../types/Campaign").CampaignList;
65
- "__#113@#getCampaignWithCounts"(params: import("../types/Campaign").GetCampaignParams): import("../types/Campaign").CampaignWithCounts | null;
65
+ "__#114@#getCampaignWithCounts"(params: import("../types/Campaign").GetCampaignParams): import("../types/Campaign").CampaignWithCounts | null;
66
66
  checkCampaignExists(id: string): boolean;
67
67
  saveUser(user: import("../../entities").User | null): void;
68
68
  getUserByID(id: string): import("../../entities").User | null;
@@ -506,15 +506,22 @@ export function ContentDBMixin(Base) {
506
506
  }
507
507
  checkContentExists(id, contentType, campaign) {
508
508
  this.log('debug', `Check if ${contentType} #${id} exists in DB`);
509
+ const whereClauseParts = [
510
+ 'content_id = ?',
511
+ 'content_type = ?'
512
+ ];
513
+ const whereValues = [id, contentType];
514
+ if (campaign !== undefined) {
515
+ whereClauseParts.push('campaign_id = ?');
516
+ whereValues.push(campaign?.id || '-1');
517
+ }
518
+ const whereClause = `WHERE ${whereClauseParts.join(' AND ')}`;
509
519
  try {
510
520
  const result = this.get(`
511
521
  SELECT COUNT(*) as count
512
522
  FROM content
513
- WHERE
514
- content_id = ? AND
515
- content_type = ? AND
516
- campaign_id = ?
517
- `, [id, contentType, campaign?.id || '-1']);
523
+ ${whereClause}
524
+ `, whereValues);
518
525
  return result.count > 0;
519
526
  }
520
527
  catch (error) {