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:
- 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
- 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 = โ
โ; // 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 ? โ
No usable image found for this item.โ : โโ
};
}
return run(inputData);