Backlight Blog and Social Media

OK. Time to seek advice. Iโ€™ve never been happy with WordPress and its complexity, cost, security and data sharing issues, so when the BL blog became available, I jumped on that bandwagon like no tomorrow.

The only component of WP I miss is the ability to automatically post blog entries onto social media, FaceBook in my case. Not currently a component of BL blog and seems unlikely in the future based upon my review of the discussions here.

Is there a semi painless way to share blog entries on FaceBook? How are others handling this dilemma?

Regards,
Jim H.

Have you looked into using the blog rss feed?
(I donโ€™t use them so donโ€™t know if feeds can be tied into Facebook)
You can turn on blog feeds in Settings under Public RSS Feeds

We have some information here how to use the RSS feeds as event triggers. You might be able to set up Zapier or IFTTT to post articles to Facebook.

https://backlight.me/docs/rss-feeds#using-rss-feeds-as-event-triggers

Thanks Rod and Matt. Based upon Rodโ€™s comment, I found the BL documentation on RSS and Zapier just before Matt weighed in. Iโ€™ve now, after multiple long conversations with CoPilot, cured my neuroses, psychoses and prostate. Off the subject.

I have managed to create a JavaScript snippet in Code by Zapier to parse the blog (or website) feed to properly handle embedded links, title, text, etc, It can optionally make the title bold and place emojis around the title. And a hand full of other useful things. Indeed everything works wellโ€ฆexcept.

It appears that FaceBook demands an image link ending in .jpg, .png, .tiff, etc. If not present, FB just dumps the input and leaves it blank. Tried, Switch to the field if available and Scrape og:image from the article page. Neither produce a clean URL ending with .jpg. It seems browsers are perfectly happy without the suffix. The best I can produce is something like: https://jamesherman.net/backlight/designer/page/image/275 which a browser is happy to display and makes a sharp left if I add .jpg to the link, the browser serves up the BL admin page.

CoPilot:

  1. Facebookโ€™s โ€œPhotoโ€ field in Zapier doesnโ€™t accept any URL โ€” it must be a direct, publicly accessible image file URL (ending in .jpg, .png, etc.), not a page URL or a CMSโ€‘style path that requires cookies/authentication.
  2. Many RSS feeds embed images with relative paths (/backlight/designer/page/image/274) or dynamic handlers that donโ€™t resolve to a raw file. When you drop those into Facebook, the post fails to render the image.

It seems odd that at times FaceBook will display an image with the link to the post page. I havenโ€™t yet been able to sort out how this happens. I suspect FB plucks an image when a link is added to a post outside my RSS feed manipulations.

CoPilot and I have reached an impasse on this problem and not sure where to go from here. I can include the JavaScript if needed, but was hoping to publish a robust finished product for all to use.

Suggestions?

Thanks,
James.

@Ben Can we check on the file URLs in RSS? Probably using absolute URLs to images will be better than are PHP aliases.

I think we also have a pending task to investigate open-graph data for blog posts, which should help with sharing to social media.

Iโ€™ve gone as far as I can at this point without resolution of embedded images such as hero or kookaburra essay image collections using the current RSS feeds. For those interested in using RSS feeds to forward to social media, here is my work thus far.

I started with Mattโ€™s documentation on RSS feeds https://backlight.me/docs/rss-feeds#using-rss-feeds-as-event-triggers and picking up at: Design Your Action, I proceeded to develop a Zapier zap to process RSS feeds into FaceBook posts. I have attached the current code and have developed it to be used with either the blog or the website feeds.

I am happy to provide screen shots and a Design Your Action update if requested.

The code does a number of cleanup steps to the incoming RSS feed and some reasonable formatting, link handling, etc., within the limits of RSS and what FB will accept.
Caveats:

  1. Images remain a work in progress
    There are several FB limitations, all images must be <4MB or they are ignored
    Photo links must have proper suffix such as .jpg, .gif, etc. (.png must be less than 1MB)
    Maximum of 10 photos, the excess will be ignored
  2. I have not yet sorted out how/when the https://example.com/feed is updated to properly trigger Zapier.

// Unified Zapier Code Step with async og:image integration, source feed link exclusion, configurable paragraph spacing, and enhanced debug
async function run(inputData) {
const title = inputData.title || โ€œโ€;
const html = inputData.description || โ€œโ€;
const mainLink = inputData.link || โ€œโ€;

// ==============================
// Configurable Options
// ==============================
const maxDescriptionLength = 1000; // Maximum length of description text before truncation
const maxLinks = 3; // Maximum number of links to show in the Links section
const includeLinks = true; // true = include extracted links, false = skip them
const includeReadMore = true; // true = include canonical โ€œRead moreโ€ link, false = skip it
const includeImages = true; // true = include images, false = skip them
const headlineEmoji = โ€œ:fire:โ€; // Emoji framing around headline (set โ€œโ€ to disable)
const debugImages = true; // true = include debug outputs (raw candidates, chosen image, notes)
const preferOgImage = true; // true = always use og:image if available, false = only fallback
const paragraphSpacing = โ€œdoubleโ€; // โ€œsingleโ€ = one blank line, โ€œdoubleโ€ = two blank lines between paragraphs
// ==============================

// Helpers
function toBold(str) {
const boldMap = {
A:โ€œ๐—”โ€,B:โ€œ๐—•โ€,C:โ€œ๐—–โ€,D:โ€œ๐——โ€,E:โ€œ๐—˜โ€,F:โ€œ๐—™โ€,G:โ€œ๐—šโ€,H:โ€œ๐—›โ€,I:โ€œ๐—œโ€,J:โ€œ๐—โ€,K:โ€œ๐—žโ€,L:โ€œ๐—Ÿโ€,M:โ€œ๐— โ€,
N:โ€œ๐—กโ€,O:โ€œ๐—ขโ€,P:โ€œ๐—ฃโ€,Q:โ€œ๐—คโ€,R:โ€œ๐—ฅโ€,S:โ€œ๐—ฆโ€,T:โ€œ๐—งโ€,U:โ€œ๐—จโ€,V:โ€œ๐—ฉโ€,W:โ€œ๐—ชโ€,X:โ€œ๐—ซโ€,Y:โ€œ๐—ฌโ€,Z:โ€œ๐—ญโ€,
a:โ€œ๐—ฎโ€,b:โ€œ๐—ฏโ€,c:โ€œ๐—ฐโ€,d:โ€œ๐—ฑโ€,e:โ€œ๐—ฒโ€,f:โ€œ๐—ณโ€,g:โ€œ๐—ดโ€,h:โ€œ๐—ตโ€,i:โ€œ๐—ถโ€,j:โ€œ๐—ทโ€,k:โ€œ๐—ธโ€,l:โ€œ๐—นโ€,m:โ€œ๐—บโ€,
n:โ€œ๐—ปโ€,o:โ€œ๐—ผโ€,p:โ€œ๐—ฝโ€,q:โ€œ๐—พโ€,r:โ€œ๐—ฟโ€,s:โ€œ๐˜€โ€,t:โ€œ๐˜โ€,u:โ€œ๐˜‚โ€,v:โ€œ๐˜ƒโ€,w:โ€œ๐˜„โ€,x:โ€œ๐˜…โ€,y:โ€œ๐˜†โ€,z:โ€œ๐˜‡โ€,
โ€œ0โ€:โ€œ๐Ÿฌโ€,โ€œ1โ€:โ€œ๐Ÿญโ€,โ€œ2โ€:โ€œ๐Ÿฎโ€,โ€œ3โ€:โ€œ๐Ÿฏโ€,โ€œ4โ€:โ€œ๐Ÿฐโ€,โ€œ5โ€:โ€œ๐Ÿฑโ€,โ€œ6โ€:โ€œ๐Ÿฒโ€,โ€œ7โ€:โ€œ๐Ÿณโ€,โ€œ8โ€:โ€œ๐Ÿดโ€,โ€œ9โ€:โ€œ๐Ÿตโ€
};
return str.split("").map(c => boldMap[c] || c).join("");
}

function resolveUrl(u, base) {
try {
if (!u) return โ€œโ€;
return new URL(u, base || mainLink || undefined).toString();
} catch (e) {
return u;
}
}

function decodeEntities(s) {
return s.replace(/&/g, โ€œ&โ€)
.replace(/"/g, โ€œโ€")
.replace(/โ€™/g, โ€œโ€™โ€)
.replace(/</g, โ€œ<โ€)
.replace(/>/g, โ€œ>โ€);
}

async function getOgImage(url) {
try {
const res = await fetch(url);
const text = await res.text();
const ogMatch = text.match(/<meta[^>]+property=["โ€™]og:image["โ€™][^>]+content="โ€™["โ€™]/i);
if (ogMatch) return resolveUrl(decodeEntities(ogMatch[1]));
} catch (e) {}
return โ€œโ€;
}

// Headline
let boldTitle = title ? toBold(title) : โ€œโ€;
if (headlineEmoji && boldTitle) boldTitle = ${headlineEmoji} ${boldTitle} ${headlineEmoji};

// Extract links
const linkRegex = /<a[^>]href="([^"]+)"[^>]>([\s\S]*?)</a>/gi;
let linkMatches;
let links = [];

while ((linkMatches = linkRegex.exec(html)) !== null) {
const rawUrl = linkMatches[1];
const url = resolveUrl(decodeEntities(rawUrl));
let inner = linkMatches[2];

let text = decodeEntities(inner.replace(/<[^>]+>/g, "")).trim();

if (!text) {
  const titleAttr = linkMatches[0].match(/title="([^"]+)"/i);
  if (titleAttr) text = decodeEntities(titleAttr[1]).trim();
}
if (!text) {
  const imgAlt = linkMatches[0].match(/<img[^>]*alt="([^"]+)"[^>]*>/i);
  if (imgAlt) text = decodeEntities(imgAlt[1]).trim();
}
if (!text) {
  try { text = new URL(url).hostname.replace(/^www\./, ""); }
  catch (e) { text = url; }
}

links.push(`${text}: ${url}`);

}

// Deduplicate links
links = [โ€ฆnew Set(links)];

// Remove only the source feed URL (mainLink) from Links section
links = links.filter(l => {
try {
const urlPart = l.split(": ").pop();
return urlPart !== mainLink;
} catch (e) {
return true;
}
});

// Apply link cap
if (includeLinks && links.length > maxLinks) {
links = links.slice(0, maxLinks);
links.push("โ€ฆ");
}

// Extract images
let rawImages = [];
const imgTagRegex = /<img[^>]*>/gi;
let tagMatch;
while ((tagMatch = imgTagRegex.exec(html)) !== null) {
const tag = tagMatch[0];
const srcMatch = tag.match(/src="([^"]+)"/i);
if (srcMatch) rawImages.push(resolveUrl(decodeEntities(srcMatch[1])));
const dataSrcMatch = tag.match(/data-src="([^"]+)"/i);
if (dataSrcMatch) rawImages.push(resolveUrl(decodeEntities(dataSrcMatch[1])));
const srcsetMatch = tag.match(/srcset="([^"]+)"/i);
if (srcsetMatch) {
const candidates = srcsetMatch[1].split(",");
for (const candidate of candidates) {
const urlPart = candidate.trim().match(/https?:[^\s]+?.(jpe?g|png|gif)/i);
if (urlPart) rawImages.push(resolveUrl(decodeEntities(urlPart[0])));
}
}
}

let images = rawImages.filter(Boolean);
images = [โ€ฆnew Set(images)];
images = images.filter(u => /.(jpe?g|png|gif)$/i.test(u));

// Prefer og:image or fallback
let imageSource = images.length > 0 ? โ€œfeedโ€ : โ€œnoneโ€;
if (mainLink) {
const ogImage = await getOgImage(mainLink);
if (ogImage) {
if (preferOgImage) {
images = [ogImage];
imageSource = โ€œog:imageโ€;
} else if (images.length === 0) {
images.push(ogImage);
imageSource = โ€œog:imageโ€;
}
}
}

// Clean description, preserving blank lines
let cleaned = html
.replace(/<script[^>]>[\s\S]?</script>/gi, โ€œโ€)
.replace(/<style[^>]>[\s\S]?</style>/gi, โ€œโ€)
.replace(/<img[^>]>/gi, โ€œโ€)
// Replace paragraph and break tags with newlines
.replace(/</p>/gi, paragraphSpacing === โ€œdoubleโ€ ? โ€œ\n\nโ€ : โ€œ\nโ€)
.replace(/<br\s
/?>/gi, โ€œ\nโ€)
// Strip remaining tags
.replace(/</?[^>]+(>|$)/g, โ€œโ€);
cleaned = decodeEntities(cleaned).trim();

// Normalize multiple newlines
cleaned = cleaned.replace(/\n{3,}/g, paragraphSpacing === โ€œdoubleโ€ ? โ€œ\n\nโ€ : โ€œ\nโ€);

if (cleaned.length > maxDescriptionLength) {
cleaned = cleaned.substring(0, maxDescriptionLength - 3) + โ€œโ€ฆโ€;
}

// Build final message
let combined = โ€œโ€;
if (boldTitle) combined += boldTitle + โ€œ\n\nโ€;
if (cleaned) combined += cleaned;
if (includeLinks && links.length > 0) combined += โ€œ\n\nLinks:\nโ€ + links.join("\n");
if (includeReadMore && mainLink) combined += "\n\nRead more: " + resolveUrl(mainLink);

// Images outputs
let firstImage = โ€œโ€;
let allImages = โ€œโ€;
if (includeImages) {
firstImage = images.length > 0 ? images[0] : โ€œโ€;
allImages = images.join(", ");
}

// Guard: no image found
if (!firstImage) {
imageSource = โ€œnoneโ€;
}

// Enhanced debug outputs
return {
finalMessage: combined,
firstImage: firstImage,
allImages: allImages,
imageSource: imageSource, // โ€œfeedโ€ | โ€œog:imageโ€ | โ€œnoneโ€
debugRawImages: debugImages ? rawImages.join(", ") : โ€œโ€,
debugFirstImage: debugImages ? firstImage : โ€œโ€,
debugOgImage: debugImages && imageSource === โ€œog:imageโ€ ? firstImage : โ€œโ€,
debugNote: !firstImage ? โ€œ:warning: No usable image found for this item.โ€ : โ€œโ€
};
}

return run(inputData);

2 Likes

Forging ahead on the website RSS feed into FaceBook and I have sorted out some of the rules.

Website feed rules: New feed entry isโ€ฆ

  1. not generated if โ€œhide from album setโ€ is selected
  2. not generated if number of photos are added, changed, removed
  3. not generated with change if page content.
  4. not generated with new album set creation
  5. generated if Title or description is changed
  6. generated if new album is created

Then using the JavaScript above. The debugging info shows that based on the website feed and the script to go to the site for the image information if needed. The image link generated is: https://jamesherman.net/galleries/zzzapier-test-album/photos/Seward-Dawn-2.jpg which works in the browser and FaceBook is happy to add to the post. Notice the .jpg suffix!

In summary, with the caveats above, Zapier from the website RSS feed will generate a FaceBook post with an image.

Still puzzling through the image issues with the blog. Possibly Ben can offer some insight.

Hope this helps.
James.

Hi @Matthew and @jherman, sorry Iโ€™m a bit late to the party.

Uploaded images donโ€™t exist as directly-accessible files within Backlight. They sit under backlight/data/designer/image_uploads with number filenames, e.g. 275. Files under backlight/data or intentionally blocked from browsers.

I can get this working by changing how the URL looks and the backend to use those URLs. The extension could be added to the URL in the feed, e.g. 275.jpeg or 275.png (depending on the type of file that was uploaded) and then change the backend to use those URLs to fetch the images, or the original filename used.

In those two options youโ€™d have something like:

https://yoursite.com/backlight/designer/page/image/275.jpeg

or

https://yoursite.com/backlight/designer/page/image/originalfilename.jpeg

The first option is safer, as it doesnโ€™t assume youโ€™d like the original filename to be exposed for somebody to find by inspecting the RSS response or browser content. On the other hand, the numeric URL isnโ€™t something that regular users would see exposed anywhere. Iโ€™ll work on getting that done.

1 Like

Hi @jherman, Backlight 6.4.2 addresses this. Image links in RSS feeds now include the file extension. The flip side of that is that a URL like https://yoursite.com/backlight/designer/page/image/275.jpeg will load the image via Backlight (i.e. there isnโ€™t an actual file called 275.jpeg at that location on disk).

The upgrade makes a change to the backlight/.htaccess file. Take not of any messaging during the upgrade, in case permission issues prevent that file from being updated.