Skip to content

Instantly share code, notes, and snippets.

@bridgestew
Last active February 10, 2019 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bridgestew/989fb2ed7bc5b91f738fcd7628aa7f5c to your computer and use it in GitHub Desktop.
Save bridgestew/989fb2ed7bc5b91f738fcd7628aa7f5c to your computer and use it in GitHub Desktop.
Notes-to-Twitter
// package.json shown below to double check nothing is missing
import fetch from 'node-fetch'
import dotenv from 'dotenv'
import Twitter from 'twitter'
import { AllHtmlEntities as Entities } from 'html-entities'
dotenv.config()
// URL of notes JSON feed
// output of file shown below
const NOTES_URL = 'https://www.bridgestew.com/notes.json'
// Configure Twitter API Client
// Have double checked values in .env for accuracy
const twitter = new Twitter({
consumer_key: process.env.TWITTER_CONSUMER_KEY,
consumer_secret: process.env.TWITTER_CONSUMER_SECRET,
access_token_key: process.env.TWITTER_ACCESS_TOKEN_KEY,
access_token_secret: process.env.TWITTER_ACCESS_TOKEN_SECRET
})
// Helper Function to return unknown errors
const handleError = err => ({
statusCode: 422,
body: String(err)
})
// Helper Function to return function status
const status = (code, msg) => {
console.log(msg)
return {
statusCode: code,
body: msg
}
}
// Check exisiting notes
const processNotes = async notes => {
if (!notes.length) {
return status(404, 'No notes found to process.')
}
// assume the last note is not yet syndicated
const latestNote = notes[0]
if (!latestNote.syndicate) {
return status(
400,
'Latest note has disabled syndication. No action taken.'
)
}
try {
// check twitter for any tweets containing note URL.
// if there are none, publish it.
const q = await twitter.get('search/tweets', { q: latestNote.url })
if (q.statuses && q.statuses.length === 0) {
return publishNote(latestNote)
} else {
return status(
400,
'Latest note was already syndicated. No action taken.'
)
}
} catch (err) {
return handleError(err)
}
}
// Prepare the content string for tweet format
const prepareStatusText = note => {
const maxLength = 280 - 3 - 1 - 23 - 20
const entities = new Entities()
// strip html tags and decode entities
let text = note.content.trim().replace(/<[^>]+>/g, '')
text = entities.decode(text)
// truncate note text if its too long for a tweet.
if (text.length > maxLength) {
text = text.substring(0, maxLength) + '...'
}
// include the note url at the end;
text = text + ' ' + note.url
return text
}
// Push a new note to Twitter
const publishNote = async note => {
try {
const statusText = prepareStatusText(note)
const tweet = await twitter.post('statuses/update', {
status: statusText
})
if (tweet) {
return status(
200,
`Note ${note.date} successfully posted to Twitter.`
)
} else {
return status(422, 'Error posting to Twitter API.')
}
} catch (err) {
return handleError(err)
}
}
// Main Lambda Function Handler
exports.handler = async () => {
// Fetch the list of published notes to work on,
// then process them to check if an action is necessary
return fetch(NOTES_URL)
.then(response => response.json())
.then(processNotes)
.catch(handleError)
}
[build]
publish = "_site"
command = "npm build trigger"
functions = "lambda"
[
{
"id": 1,
"date": "Fri Feb 08 2019 17:45:05 GMT-0500 (Eastern Standard Time)",
"url": "https://www.bridgestew.com/notes/2019-02-08-22-45/",
"content": "I've been trying to follow along with Max's code examples for how to get webmention sends working with Twitter. Still working out kinks.",
"syndicate": true
}
]
// The pertinent parts
{
"scripts": {
"serve": "gulp watch & eleventy --serve",
"build": "npx eleventy",
"watch": "npx eleventy --watch",
"debug": "DEBUG=* npx eleventy",
"trigger": "eleventy & npm run build:lambda",
"build:lambda": "netlify-lambda build functions"
},
"devDependencies": {
"@11ty/eleventy": "^0.7.1",
"@11ty/eleventy-plugin-rss": "^1.0.3",
"@11ty/eleventy-plugin-syntaxhighlight": "^2.0.0",
"acorn": "^6.0.7",
"eleventy-plugin-reading-time": "^0.0.1",
"luxon": "^1.9.0",
"markdown-it": "^8.4.1",
"markdown-it-abbr": "^1.0.4",
"markdown-it-anchor": "^5.0.2",
"markdown-it-attrs": "^2.3.2"
},
"dependencies": {
"clean-css": "^4.2.1",
"dotenv": "^6.2.0",
"gulp": "^4.0.0",
"gulp-concat": "^2.6.1",
"gulp-sass": "^4.0.1",
"gulp-uglify": "^3.0.1",
"html-entities": "^1.2.1",
"html-minifier": "^3.5.21",
"netlify-lambda": "^1.3.3",
"node-fetch": "^2.3.0",
"sanitize-html": "^1.20.0",
"send-webmention": "^2.0.0",
"sitemap-generator": "^8.3.2",
"twitter": "^1.7.1",
"uglify-js": "^3.4.9"
}
}
@maxboeck
Copy link

tried running your function in my local setup and it seems to work.
Could you try to expand the handleError function to:

const handleError = err => {
    console.error(err)
    const message = Array.isArray(err) ? err[0].message : err.message
    return {
        statusCode: 422,
        body: String(message)
    }
}

that should give you a more verbose output on Netlify's function console.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment