Last active
December 25, 2022 16:13
-
-
Save Swizec/806c01c51a898b3404803fc9a9a5d11b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const official = require("gatsby-remark-embedder/dist/transformers/Twitter") | |
const Twitter = require("twitter-lite") | |
// Memoized twitter client instance | |
let twitterClient = null | |
async function getAppClient() { | |
if (twitterClient) { | |
return twitterClient | |
} | |
const user = new Twitter({ | |
consumer_key: process.env.TWITTER_CONSUMER_KEY, | |
consumer_secret: process.env.TWITTER_CONSUMER_SECRET, | |
}) | |
const response = await user.getBearerToken() | |
twitterClient = new Twitter({ | |
version: "2", | |
extension: false, | |
bearer_token: response.access_token, | |
}) | |
return twitterClient | |
} | |
// fetch tweet from API | |
async function getTweet(tweetId) { | |
const client = await getAppClient() | |
try { | |
const tweet = await client.get(`tweets/${tweetId}`, { | |
"tweet.fields": "public_metrics,created_at", | |
expansions: "author_id,attachments.media_keys", | |
"user.fields": "name,username,url,profile_image_url", | |
"media.fields": "preview_image_url,url", | |
}) | |
return tweet | |
} catch (e) { | |
console.log(e) | |
} | |
return null | |
} | |
function buildMediaList(media) { | |
const width = media.length > 1 ? "50%" : "100%" | |
return media | |
.map( | |
(media) => | |
`<img src="${ | |
media.preview_image_url || media.url | |
}" width="${width}" loading="lazy" alt="Tweet media" />` | |
) | |
.join("") | |
} | |
const likesSVG = `<svg viewBox="0 0 24 24" class="r-m0bqgq r-4qtqp9 r-yyyyoo r-1xvli5t r-dnmrzs r-bnwqim r-1plcrui r-lrvibr" style=""><g><path d="M12 21.638h-.014C9.403 21.59 1.95 14.856 1.95 8.478c0-3.064 2.525-5.754 5.403-5.754 2.29 0 3.83 1.58 4.646 2.73.814-1.148 2.354-2.73 4.645-2.73 2.88 0 5.404 2.69 5.404 5.755 0 6.376-7.454 13.11-10.037 13.157H12zM7.354 4.225c-2.08 0-3.903 1.988-3.903 4.255 0 5.74 7.034 11.596 8.55 11.658 1.518-.062 8.55-5.917 8.55-11.658 0-2.267-1.823-4.255-3.903-4.255-2.528 0-3.94 2.936-3.952 2.965-.23.562-1.156.562-1.387 0-.014-.03-1.425-2.965-3.954-2.965z"></path></g></svg>` | |
const repliesSVG = `<svg viewBox="0 0 24 24" class="r-m0bqgq r-4qtqp9 r-yyyyoo r-1xvli5t r-dnmrzs r-bnwqim r-1plcrui r-lrvibr"><g><path d="M14.046 2.242l-4.148-.01h-.002c-4.374 0-7.8 3.427-7.8 7.802 0 4.098 3.186 7.206 7.465 7.37v3.828c0 .108.044.286.12.403.142.225.384.347.632.347.138 0 .277-.038.402-.118.264-.168 6.473-4.14 8.088-5.506 1.902-1.61 3.04-3.97 3.043-6.312v-.017c-.006-4.367-3.43-7.787-7.8-7.788zm3.787 12.972c-1.134.96-4.862 3.405-6.772 4.643V16.67c0-.414-.335-.75-.75-.75h-.396c-3.66 0-6.318-2.476-6.318-5.886 0-3.534 2.768-6.302 6.3-6.302l4.147.01h.002c3.532 0 6.3 2.766 6.302 6.296-.003 1.91-.942 3.844-2.514 5.176z"></path></g></svg>` | |
function buildTweetHTML(tweet) { | |
const author = tweet.includes.users.find( | |
(user) => user.id === tweet.data.author_id | |
) | |
const tweetURL = `https://twitter.com/${author.username}/status/${tweet.data.id}` | |
const authorHTML = `<a class="author" href="${author.url}"><img src="${author.profile_image_url}" loading="lazy" alt="${author.name} avatar" /><b>${author.name}</b>@${author.username}</a>` | |
const tweetHTML = `<blockquote>${tweet.data.text.replace( | |
/https:\/\/t.co\/(\w+)/, | |
"" | |
)}</blockquote>` | |
const mediaHTML = tweet.includes.media | |
? `<div class="media">${buildMediaList(tweet.includes.media)}</div>` | |
: "" | |
const createdAtHTML = `<div class="time"><a href="${tweetURL}">${new Date( | |
tweet.data.created_at | |
).toLocaleTimeString()} – ${new Date( | |
tweet.data.created_at | |
).toLocaleDateString()}</a></div>` | |
const likeIntent = `https://twitter.com/intent/like?tweet_id=${tweet.data.id}` | |
const replyIntent = tweetURL | |
const statsHTML = `<div class="stats"><a href="${likeIntent}" class="like">${likesSVG}${tweet.data.public_metrics.like_count}</a> <a href="${replyIntent}" class="reply">${repliesSVG}${tweet.data.public_metrics.reply_count}</a></div>` | |
return `<div><div class="static-tweet-embed"> | |
${authorHTML} | |
${tweetHTML} | |
${mediaHTML} | |
${createdAtHTML} | |
${statsHTML} | |
</div></div>` | |
} | |
async function getHTML(url) { | |
const twitterUrl = url.replace("events", "moments") | |
const tweetId = twitterUrl.split("/").pop() | |
const tweet = await getTweet(tweetId) | |
if (!tweet) { | |
console.log("TWEET NOT FOUND", twitterUrl, tweetId) | |
return "" | |
} | |
return buildTweetHTML(tweet) | |
} | |
module.exports = { | |
getHTML, | |
shouldTransform: official.shouldTransform, | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
div.static-tweet-embed { | |
display: flex; | |
flex-direction: column; | |
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | |
max-width: 550px; | |
width: 100%; | |
margin-top: 10px; | |
margin-bottom: 10px; | |
border: 1px solid rgb(196, 207, 214); | |
border-radius: 12px; | |
padding: 12px 16px 4px 16px; | |
} | |
div.static-tweet-embed blockquote { | |
margin: 0; | |
font-size: 20px; | |
padding: 0px; | |
border: 0px; | |
color: rgb(15, 20, 25); | |
line-height: 24px; | |
} | |
div.static-tweet-embed .author { | |
color: rgb(91, 112, 131); | |
font-size: 15px; | |
line-height: 20px; | |
text-decoration: none; | |
padding-bottom: 12px; | |
} | |
div.static-tweet-embed .author b { | |
display: block; | |
font-weight: 700; | |
font-size: 14px; | |
color: rgb(15, 20, 25); | |
} | |
div.static-tweet-embed .author b:hover { | |
text-decoration: underline; | |
} | |
div.static-tweet-embed .author img { | |
float: left; | |
width: 48px; | |
height: 48px; | |
border-radius: 100%; | |
margin-right: 4px; | |
margin-bottom: 0px; | |
} | |
div.static-tweet-embed .media { | |
display: flex; | |
flex-wrap: wrap; | |
flex-direction: row; | |
align-content: center; | |
align-items: center; | |
width: 100%; | |
max-height: 366px; | |
margin-top: 12px; | |
border-radius: 12px; | |
overflow: hidden; | |
} | |
div.static-tweet-embed .media img { | |
margin-bottom: 0px; | |
} | |
div.static-tweet-embed .time a { | |
color: rgb(91, 112, 131); | |
font-size: 15px; | |
line-height: 32px; | |
height: 32px; | |
text-decoration: none; | |
} | |
div.static-tweet-embed .time a:hover { | |
color: rgb(91, 112, 131); | |
font-size: 15px; | |
line-height: 32px; | |
height: 32px; | |
text-decoration: underline; | |
} | |
div.static-tweet-embed .stats { | |
display: flex; | |
align-items: center; | |
color: rgb(91, 112, 131); | |
} | |
div.static-tweet-embed .stats svg { | |
height: 20px; | |
width: 20px; | |
margin-right: 10px; | |
} | |
div.static-tweet-embed .stats svg path { | |
fill: rgb(91, 112, 131) | |
} | |
div.static-tweet-embed .stats svg:first-child { | |
padding-left: 0px; | |
} | |
div.static-tweet-embed .stats a { | |
display: flex; | |
align-items: center; | |
margin-right: 20px; | |
color: inherit; | |
text-decoration: none; | |
} | |
div.static-tweet-embed .stats a.like:hover { | |
color: rgb(224, 36, 94); | |
text-decoration: underline; | |
} | |
div.static-tweet-embed .stats a.like:hover svg path { | |
fill: rgb(224, 36, 94); | |
} | |
div.static-tweet-embed .stats a.reply:hover { | |
color: rgb(29, 161, 242); | |
text-decoration: underline; | |
} | |
div.static-tweet-embed .stats a.reply:hover svg path { | |
fill: rgb(29, 161, 242); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Silly question, but how do I use this? I have a static website (built with Jekyll) and would like to export some HTML that I can then pull in. Any clues?