@rmdes/indiekit-syndicator-bluesky 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -8,7 +8,8 @@ const defaults = {
8
8
  profileUrl: "https://bsky.app/profile",
9
9
  serviceUrl: "https://bsky.social",
10
10
  includePermalink: false,
11
- syndicateExternalLikes: true, // NEW: Enable syndication of external likes
11
+ syndicateExternalLikes: true, // Enable syndication of external likes
12
+ syndicateExternalReposts: true, // Enable syndication of external reposts
12
13
  checked: false,
13
14
  };
14
15
 
@@ -23,6 +24,7 @@ export default class BlueskySyndicator {
23
24
  * @param {string} [options.password] - Password
24
25
  * @param {boolean} [options.includePermalink] - Include permalink in status
25
26
  * @param {boolean} [options.syndicateExternalLikes] - Syndicate likes of external URLs as posts
27
+ * @param {boolean} [options.syndicateExternalReposts] - Syndicate reposts of external URLs as posts
26
28
  * @param {boolean} [options.checked] - Check syndicator in UI
27
29
  */
28
30
  constructor(options = {}) {
@@ -92,6 +94,7 @@ export default class BlueskySyndicator {
92
94
  serviceUrl: this.#serviceUrl,
93
95
  includePermalink: this.options.includePermalink,
94
96
  syndicateExternalLikes: this.options.syndicateExternalLikes,
97
+ syndicateExternalReposts: this.options.syndicateExternalReposts,
95
98
  });
96
99
 
97
100
  return await bluesky.post(properties, publication.me);
package/lib/bluesky.js CHANGED
@@ -6,6 +6,8 @@ import {
6
6
  getPostImage,
7
7
  getPostText,
8
8
  getLikePostText,
9
+ getRepostPostText,
10
+ getBookmarkPostText,
9
11
  buildPostText,
10
12
  getPostParts,
11
13
  uriToPostUrl,
@@ -23,6 +25,7 @@ export class Bluesky {
23
25
  * @param {string} options.serviceUrl - Service URL
24
26
  * @param {boolean} [options.includePermalink] - Include permalink in status
25
27
  * @param {boolean} [options.syndicateExternalLikes] - Syndicate likes of external URLs
28
+ * @param {boolean} [options.syndicateExternalReposts] - Syndicate reposts of external URLs
26
29
  */
27
30
  constructor(options) {
28
31
  this.identifier = options.identifier;
@@ -31,6 +34,7 @@ export class Bluesky {
31
34
  this.serviceUrl = options.serviceUrl;
32
35
  this.includePermalink = options.includePermalink || false;
33
36
  this.syndicateExternalLikes = options.syndicateExternalLikes !== false; // Default true
37
+ this.syndicateExternalReposts = options.syndicateExternalReposts !== false; // Default true
34
38
  }
35
39
 
36
40
  /**
@@ -331,7 +335,15 @@ export class Bluesky {
331
335
  if (isSameOrigin(repostUrl, this.profileUrl)) {
332
336
  return this.postRepost(repostUrl);
333
337
  }
334
- // Do not syndicate reposts of other URLs
338
+
339
+ // Syndicate reposts of external URLs as posts with link card
340
+ if (this.syndicateExternalReposts) {
341
+ const text = getRepostPostText(properties, repostUrl);
342
+ const richText = await createRichText(client, text);
343
+ const externalEmbed = await this.createExternalEmbed(repostUrl);
344
+ return this.postPost(richText, { images, externalEmbed });
345
+ }
346
+
335
347
  return;
336
348
  }
337
349
 
@@ -356,12 +368,11 @@ export class Bluesky {
356
368
  return;
357
369
  }
358
370
 
359
- // Handle bookmarks - similar to likes but with bookmark-of
371
+ // Handle bookmarks - OG card shows bookmarked URL, text has commentary + permalink
360
372
  const bookmarkOfUrl = properties["bookmark-of"];
361
373
  if (bookmarkOfUrl) {
362
- const text = getPostText(properties, this.includePermalink);
374
+ const text = getBookmarkPostText(properties, bookmarkOfUrl);
363
375
  const richText = await createRichText(client, text);
364
- // Create external embed for the bookmarked URL
365
376
  const externalEmbed = await this.createExternalEmbed(bookmarkOfUrl);
366
377
  return this.postPost(richText, { images, externalEmbed });
367
378
  }
package/lib/utils.js CHANGED
@@ -293,7 +293,9 @@ export function buildPostText(properties, options = {}) {
293
293
  }
294
294
 
295
295
  /**
296
- * Get post text for a like of an external URL
296
+ * Get post text for a like of an external URL (Bluesky version).
297
+ * The external URL is shown as an OG card embed, so the text contains
298
+ * commentary + blog permalink (for webmentions), not the liked URL.
297
299
  * @param {object} properties - JF2 properties
298
300
  * @param {string} likedUrl - The URL being liked
299
301
  * @returns {string} Post text
@@ -308,20 +310,125 @@ export const getLikePostText = (properties, likedUrl) => {
308
310
  text = properties.content.text;
309
311
  }
310
312
 
311
- // If there's content, append the liked URL
312
- if (text) {
313
- // Check if the URL is already in the text
314
- if (!text.includes(likedUrl)) {
315
- text = `${text}\n\n❤️ ${likedUrl}`;
313
+ // Remove the liked URL from text if already present (OG card shows it)
314
+ if (text && likedUrl) {
315
+ text = text.replace(likedUrl, "").trim();
316
+ }
317
+
318
+ // Append blog permalink for webmentions
319
+ const permalink = properties.url;
320
+ if (permalink) {
321
+ if (text) {
322
+ text = `${text}\n\n${permalink}`;
323
+ } else {
324
+ text = `❤️ ${permalink}`;
316
325
  }
317
- } else {
318
- // No content, just post the liked URL with a heart
326
+ } else if (!text) {
327
+ // Fallback: no content, no permalink use liked URL
319
328
  text = `❤️ ${likedUrl}`;
320
329
  }
321
330
 
322
331
  // Truncate if needed (Bluesky limit is 300 chars)
323
332
  if (text.length > 300) {
324
- const suffix = `\n\n❤️ ${likedUrl}`;
333
+ const suffix = permalink ? `\n\n${permalink}` : `\n\n❤️ ${likedUrl}`;
334
+ const maxLen = 300 - suffix.length - 3;
335
+ const contentPart = text.replace(suffix, "").slice(0, maxLen).trim();
336
+ text = contentPart + "..." + suffix;
337
+ }
338
+
339
+ return text;
340
+ };
341
+
342
+ /**
343
+ * Get post text for a repost of an external URL (Bluesky version).
344
+ * The external URL is shown as an OG card embed, so the text contains
345
+ * commentary + blog permalink (for webmentions), not the reposted URL.
346
+ * @param {object} properties - JF2 properties
347
+ * @param {string} repostUrl - The URL being reposted
348
+ * @returns {string} Post text
349
+ */
350
+ export const getRepostPostText = (properties, repostUrl) => {
351
+ let text = "";
352
+
353
+ // Get the content/comment
354
+ if (properties.content?.html) {
355
+ text = htmlToStatusText(properties.content.html);
356
+ } else if (properties.content?.text) {
357
+ text = properties.content.text;
358
+ }
359
+
360
+ // Remove the reposted URL from text if already present (OG card shows it)
361
+ if (text && repostUrl) {
362
+ text = text.replace(repostUrl, "").trim();
363
+ }
364
+
365
+ // Append blog permalink for webmentions
366
+ const permalink = properties.url;
367
+ if (permalink) {
368
+ if (text) {
369
+ text = `${text}\n\n${permalink}`;
370
+ } else {
371
+ text = `🔁 ${permalink}`;
372
+ }
373
+ } else if (!text) {
374
+ // Fallback: no content, no permalink — use repost URL
375
+ text = `🔁 ${repostUrl}`;
376
+ }
377
+
378
+ // Truncate if needed (Bluesky limit is 300 chars)
379
+ if (text.length > 300) {
380
+ const suffix = permalink ? `\n\n${permalink}` : `\n\n🔁 ${repostUrl}`;
381
+ const maxLen = 300 - suffix.length - 3;
382
+ const contentPart = text.replace(suffix, "").slice(0, maxLen).trim();
383
+ text = contentPart + "..." + suffix;
384
+ }
385
+
386
+ return text;
387
+ };
388
+
389
+ /**
390
+ * Get post text for a bookmark of an external URL (Bluesky version).
391
+ * The external URL is shown as an OG card embed, so the text contains
392
+ * commentary + blog permalink (for webmentions), not the bookmarked URL.
393
+ * @param {object} properties - JF2 properties
394
+ * @param {string} bookmarkUrl - The URL being bookmarked
395
+ * @returns {string} Post text
396
+ */
397
+ export const getBookmarkPostText = (properties, bookmarkUrl) => {
398
+ let text = "";
399
+
400
+ // Get the content/comment
401
+ if (properties.name && properties.name !== "") {
402
+ text = properties.name;
403
+ } else if (properties.content?.html) {
404
+ text = htmlToStatusText(properties.content.html);
405
+ } else if (properties.content?.text) {
406
+ text = properties.content.text;
407
+ }
408
+
409
+ // Remove the bookmarked URL from text if already present (OG card shows it)
410
+ if (text && bookmarkUrl) {
411
+ text = text.replace(bookmarkUrl, "").trim();
412
+ }
413
+
414
+ // Append blog permalink for webmentions
415
+ const permalink = properties.url;
416
+ if (permalink) {
417
+ if (text) {
418
+ if (!text.includes(permalink)) {
419
+ text = `${text}\n\n${permalink}`;
420
+ }
421
+ } else {
422
+ text = `🔖 ${permalink}`;
423
+ }
424
+ } else if (!text) {
425
+ // Fallback: no content, no permalink — use bookmarked URL
426
+ text = `🔖 ${bookmarkUrl}`;
427
+ }
428
+
429
+ // Truncate if needed (Bluesky limit is 300 chars)
430
+ if (text.length > 300) {
431
+ const suffix = permalink ? `\n\n${permalink}` : `\n\n🔖 ${bookmarkUrl}`;
325
432
  const maxLen = 300 - suffix.length - 3;
326
433
  const contentPart = text.replace(suffix, "").slice(0, maxLen).trim();
327
434
  text = contentPart + "..." + suffix;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-syndicator-bluesky",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "Bluesky syndicator for Indiekit with external like support",
5
5
  "type": "module",
6
6
  "main": "index.js",