Skip to content

Conversation

@aarongustafson
Copy link
Owner

No description provided.

@netlify
Copy link

netlify bot commented Dec 7, 2025

Deploy Preview for aaron-gustafson ready!

Name Link
🔨 Latest commit 00ee342
🔍 Latest deploy log https://app.netlify.com/projects/aaron-gustafson/deploys/6935b9424cbc810008458e44
😎 Deploy Preview https://deploy-preview-130--aaron-gustafson.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 86
Accessibility: 100
Best Practices: 92
SEO: 100
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR converts social media syndication from Zapier to a custom GitHub Actions-based solution. The implementation automates posting blog content and link shares to multiple social platforms (LinkedIn, Mastodon, Twitter, Bluesky, Pinterest) using a combination of direct APIs and IFTTT webhooks. The workflow triggers after successful Netlify deployments via a GitHub repository dispatch event.

Key Changes:

  • GitHub Actions workflow with automated syndication to 5+ social platforms
  • Netlify plugin integration to trigger GitHub workflow on successful deploys
  • Smart caching system to prevent duplicate posts and only syndicate items published today
  • Feed enhancement to support custom social media text via twitter_text field

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
package.json Added dependencies for HTTP requests (axios), HTML parsing (cheerio, html-to-text), and Prettier TOML support
netlify.toml Added GitHub dispatch plugin configuration and reformatted with consistent indentation
src/_includes/feeds/post.json.njk Added optional social_text field to feed output for custom social media content
.netlify/functions/syndicate-trigger.js Netlify function to trigger GitHub workflow after deploy (currently unused in favor of plugin)
.github/workflows/syndicate-content.yml GitHub Actions workflow orchestrating syndication with multiple triggers (webhook, manual, scheduled)
.github/scripts/syndicate-posts.js Script to syndicate blog posts to LinkedIn, Mastodon, Twitter, and Bluesky
.github/scripts/syndicate-links.js Script to syndicate link shares to LinkedIn, Pinterest, Mastodon, Twitter, and Bluesky
.github/scripts/social-media-utils.js Shared utilities for content processing, caching, and API interactions
.github/scripts/test-syndication.sh Bash script for local testing in test mode
.github/TESTING.md Comprehensive testing guide with test modes and debugging instructions
.github/SYNDICATION_SETUP.md Setup guide for API credentials and platform configuration
.github/INSTALL_DEPENDENCIES.md Dependency installation and local testing instructions
Comments suppressed due to low confidence (2)

.github/scripts/syndicate-links.js:71

  • Unused variable screenshotUrl.
		const screenshotUrl = ContentProcessor.createScreenshotUrl(relatedUrl);

.github/scripts/social-media-utils.js:5

  • Unused variable cheerio.
const cheerio = require("cheerio");

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +76 to +81
async ensureCacheDir() {
try {
await fs.mkdir(this.cacheDir, { recursive: true });
} catch (error) {
// Directory already exists or other error
}
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling silently ignores directory creation failures. If the directory creation fails for reasons other than "already exists" (e.g., permission issues), the error is swallowed. Consider logging the error or checking for specific error codes (e.g., EEXIST) to ensure only expected errors are ignored.

Copilot uses AI. Check for mistakes.
netlify.toml Outdated
package = "netlify-plugin-github-dispatch"
[plugins.inputs]
repo = "aarongustafson/aaron-gustafson.com"
token = "${GITHUB_ACCESS_TOKEN}"
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment references GITHUB_ACCESS_TOKEN (line 27) but then suggests using either GITHUB_ACCESS_TOKEN or GITHUB_TOKEN (line 149 in SYNDICATION_SETUP.md). However, the actual Netlify function at .netlify/functions/syndicate-trigger.js uses GITHUB_TOKEN (line 43). This inconsistency will cause the integration to fail. Ensure all references use the same environment variable name.

Suggested change
token = "${GITHUB_ACCESS_TOKEN}"
token = "${GITHUB_TOKEN}"

Copilot uses AI. Check for mistakes.
const relatedUrl = link.external_url || link.url;

// Generate screenshot URL
const screenshotUrl = ContentProcessor.createScreenshotUrl(relatedUrl);
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The screenshotUrl variable is generated but never used. It's created on line 71 but doesn't appear anywhere else in the function. Either remove this line if screenshots aren't needed for links, or pass it to the IFTTT webhooks or other platform integrations where it should be used.

Copilot uses AI. Check for mistakes.
run: |
npm install
# Install additional syndication dependencies
npm install axios cheerio html-to-text dotenv
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Installing dependencies twice is inefficient. Line 46 runs npm install, and then line 48 installs axios cheerio html-to-text dotenv again. Since these packages are already in package.json (axios, cheerio, html-to-text), the second install is redundant. Remove line 48 or only install packages not in package.json.

Suggested change
npm install axios cheerio html-to-text dotenv

Copilot uses AI. Check for mistakes.
3. Or use alternative services like:
- [URLBox](https://urlbox.io/)
- [Bannerbear](https://www.bannerbear.com/)
- [Htmlcsstoimage](https://htmlcsstoimage.com/)
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The service name should be capitalized properly as "HTML/CSS to Image" or match their branding. The current format "Htmlcsstoimage" looks like a typo or incorrect capitalization.

Suggested change
- [Htmlcsstoimage](https://htmlcsstoimage.com/)
- [HTML/CSS to Image](https://htmlcsstoimage.com/)

Copilot uses AI. Check for mistakes.
Comment on lines +133 to +179
// Fallback to IFTTT
await this.sendToIFTTT("mastodon_link", {
status: `${socialText} ${relatedUrl}`,
guid: link.id,
});
}

// Buffer for Twitter and Bluesky
try {
console.log("🐦 Posting to Buffer (Twitter & Bluesky)...");
const bufferText = ContentProcessor.truncateText(
`${socialText} ${relatedUrl}`,
260
);

const profileIds = [
process.env.BUFFER_TWITTER_PROFILE_ID,
process.env.BUFFER_BLUESKY_PROFILE_ID,
].filter(Boolean);

if (profileIds.length > 0) {
const bufferResults = await this.postToBuffer(bufferText, profileIds);
results.push({
platform: "Buffer (Twitter/Bluesky)",
success: true,
data: bufferResults,
});
console.log("✅ Buffer posts successful");
} else {
console.log("⚠️ No Buffer profile IDs configured");
}
} catch (error) {
console.log("❌ Buffer posts failed:", error.message);
results.push({
platform: "Buffer",
success: false,
error: error.message,
});

// Fallback to IFTTT for both platforms
await this.sendToIFTTT("twitter_link", {
text: `${socialText} ${relatedUrl}`,
});

await this.sendToIFTTT("bluesky_link", {
text: `${socialText} ${relatedUrl}`,
});
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback IFTTT calls (lines 134-137 and 173-179) don't have error handling. If the fallback also fails, it will cause an unhandled promise rejection. Consider wrapping these fallback calls in try-catch blocks and logging any failures, since they're meant to be backup mechanisms.

Copilot uses AI. Check for mistakes.
Comment on lines +110 to +156
// Fallback to IFTTT
await this.sendToIFTTT("mastodon_post", {
status: `${socialText} ${post.url}`,
guid: post.id,
});
}

// Buffer for Twitter and Bluesky
try {
console.log("🐦 Posting to Buffer (Twitter & Bluesky)...");
const bufferText = ContentProcessor.truncateText(
`${socialText} ${post.url}`,
260
);

const profileIds = [
process.env.BUFFER_TWITTER_PROFILE_ID,
process.env.BUFFER_BLUESKY_PROFILE_ID,
].filter(Boolean);

if (profileIds.length > 0) {
const bufferResults = await this.postToBuffer(bufferText, profileIds);
results.push({
platform: "Buffer (Twitter/Bluesky)",
success: true,
data: bufferResults,
});
console.log("✅ Buffer posts successful");
} else {
console.log("⚠️ No Buffer profile IDs configured");
}
} catch (error) {
console.log("❌ Buffer posts failed:", error.message);
results.push({
platform: "Buffer",
success: false,
error: error.message,
});

// Fallback to IFTTT for both platforms
await this.sendToIFTTT("twitter_post", {
text: `${socialText} ${post.url}`,
});

await this.sendToIFTTT("bluesky_post", {
text: `${socialText} ${post.url}`,
});
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback IFTTT calls (lines 111-114 and 150-156) don't have error handling. If the fallback also fails, it will cause an unhandled promise rejection. Consider wrapping these fallback calls in try-catch blocks and logging any failures, since they're meant to be backup mechanisms.

Copilot uses AI. Check for mistakes.
Comment on lines 155 to 178
exports.handler = async (event, context) => {
if (event.httpMethod !== 'POST') {
return { statusCode: 405, body: 'Method Not Allowed' };
}

const { default: fetch } = await import('node-fetch');

try {
const response = await fetch(
'https://api.github.com/repos/aarongustafson/aaron-gustafson.com/dispatches',
{
method: 'POST',
headers: {
'Authorization': `token ${process.env.GITHUB_TOKEN}`,
'Content-Type': 'application/json',
'User-Agent': 'Netlify-Function'
},
body: JSON.stringify({
event_type: 'netlify-deploy-succeeded',
client_payload: {
site_id: event.headers['x-netlify-site-id'],
deploy_id: event.headers['x-netlify-deploy-id']
}
})
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sample github-dispatch Netlify function sends a repository dispatch event to GitHub using process.env.GITHUB_TOKEN but does not authenticate or verify that incoming requests actually originate from GitHub/Netlify. An attacker can directly call this public function endpoint and trigger arbitrary repository_dispatch events against your repo using your token, potentially abusing any workflows bound to that event. Add authentication to this function (for example, verify a shared secret or GitHub webhook signature before calling fetch, and reject requests that fail validation) so only trusted callers can trigger dispatches.

Copilot uses AI. Check for mistakes.
@aarongustafson aarongustafson merged commit b19d659 into main Dec 7, 2025
5 checks passed
@aarongustafson aarongustafson deleted the zapier-conversion branch December 7, 2025 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants