-
Notifications
You must be signed in to change notification settings - Fork 18
Zapier conversion #130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Zapier conversion #130
Conversation
✅ Deploy Preview for aaron-gustafson ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this 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_textfield
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.
| async ensureCacheDir() { | ||
| try { | ||
| await fs.mkdir(this.cacheDir, { recursive: true }); | ||
| } catch (error) { | ||
| // Directory already exists or other error | ||
| } |
Copilot
AI
Dec 7, 2025
There was a problem hiding this comment.
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.
netlify.toml
Outdated
| package = "netlify-plugin-github-dispatch" | ||
| [plugins.inputs] | ||
| repo = "aarongustafson/aaron-gustafson.com" | ||
| token = "${GITHUB_ACCESS_TOKEN}" |
Copilot
AI
Dec 7, 2025
There was a problem hiding this comment.
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.
| token = "${GITHUB_ACCESS_TOKEN}" | |
| token = "${GITHUB_TOKEN}" |
| const relatedUrl = link.external_url || link.url; | ||
|
|
||
| // Generate screenshot URL | ||
| const screenshotUrl = ContentProcessor.createScreenshotUrl(relatedUrl); |
Copilot
AI
Dec 7, 2025
There was a problem hiding this comment.
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.
| run: | | ||
| npm install | ||
| # Install additional syndication dependencies | ||
| npm install axios cheerio html-to-text dotenv |
Copilot
AI
Dec 7, 2025
There was a problem hiding this comment.
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.
| npm install axios cheerio html-to-text dotenv |
| 3. Or use alternative services like: | ||
| - [URLBox](https://urlbox.io/) | ||
| - [Bannerbear](https://www.bannerbear.com/) | ||
| - [Htmlcsstoimage](https://htmlcsstoimage.com/) |
Copilot
AI
Dec 7, 2025
There was a problem hiding this comment.
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.
| - [Htmlcsstoimage](https://htmlcsstoimage.com/) | |
| - [HTML/CSS to Image](https://htmlcsstoimage.com/) |
| // 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}`, | ||
| }); |
Copilot
AI
Dec 7, 2025
There was a problem hiding this comment.
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.
| // 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}`, | ||
| }); |
Copilot
AI
Dec 7, 2025
There was a problem hiding this comment.
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.
| 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'] | ||
| } | ||
| }) |
Copilot
AI
Dec 7, 2025
There was a problem hiding this comment.
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.

No description provided.