From 3b5f9153c489ed3a4c4dee4a86ea58a0cf7a1edf Mon Sep 17 00:00:00 2001
From: Simon Lecoq <22963968+lowlighter@users.noreply.github.com>
Date: Tue, 2 Mar 2021 02:28:58 +0100
Subject: [PATCH] Feat plugin tweets medias (#164)
---
source/plugins/tweets/README.md | 1 +
source/plugins/tweets/index.mjs | 34 ++++++++++++---
source/plugins/tweets/metadata.yml | 6 +++
source/templates/classic/partials/tweets.ejs | 16 ++++++-
source/templates/classic/style.css | 45 +++++++++++++++++++-
5 files changed, 94 insertions(+), 8 deletions(-)
diff --git a/source/plugins/tweets/README.md b/source/plugins/tweets/README.md
index 2377cdb1..c20734c9 100644
--- a/source/plugins/tweets/README.md
+++ b/source/plugins/tweets/README.md
@@ -31,6 +31,7 @@ Create an app from your [developer dashboard](https://developer.twitter.com/en/p
# ... other options
plugin_tweets: yes
plugin_tweets_token: ${{ secrets.TWITTER_TOKEN }} # Required
+ plugin_tweets_attachments: yes # Display tweets attachments (images, preview urls, etc.)
plugin_tweets_limit: 2 # Limit to 2 tweets
plugin_tweets_user: .user.twitter # Defaults to your GitHub linked twitter username
```
diff --git a/source/plugins/tweets/index.mjs b/source/plugins/tweets/index.mjs
index 3e53a1c7..f9337685 100644
--- a/source/plugins/tweets/index.mjs
+++ b/source/plugins/tweets/index.mjs
@@ -7,7 +7,7 @@
return null
//Load inputs
- let {limit, user:username} = imports.metadata.plugins.tweets.inputs({data, account, q})
+ let {limit, user:username, attachments} = imports.metadata.plugins.tweets.inputs({data, account, q})
//Load user profile
console.debug(`metrics/compute/${login}/plugins > tweets > loading twitter profile (@${username})`)
@@ -21,7 +21,8 @@
//Load tweets
console.debug(`metrics/compute/${login}/plugins > tweets > querying api`)
- const {data:{data:tweets = []}} = await imports.axios.get(`https://api.twitter.com/2/tweets/search/recent?query=from:${username}&tweet.fields=created_at&expansions=entities.mentions.username`, {headers:{Authorization:`Bearer ${token}`}})
+ const {data:{data:tweets = [], includes:{media = []}}} = await imports.axios.get(`https://api.twitter.com/2/tweets/search/recent?query=from:${username}&tweet.fields=created_at,entities&media.fields=preview_image_url,url,type&expansions=entities.mentions.username,attachments.media_keys`, {headers:{Authorization:`Bearer ${token}`}})
+ const medias = new Map(media.map(({media_key, type, url, preview_image_url}) => [media_key, (type === "photo")||(type === "animated_gif") ? url : type === "video" ? preview_image_url : null]))
//Limit tweets
if (limit > 0) {
@@ -31,8 +32,29 @@
//Format tweets
await Promise.all(tweets.map(async tweet => {
- //Mentions
- tweet.mentions = tweet.entities?.mentions.map(({username}) => username) ?? []
+ //Mentions and urls
+ tweet.mentions = tweet.entities?.mentions?.map(({username}) => username) ?? []
+ tweet.urls = new Map(tweet.entities?.urls?.map(({url, display_url:link}) => [url, link]) ?? [])
+ //Attachments
+ if (attachments) {
+ //Retrieve linked content
+ let linked = null
+ if (tweet.urls.size) {
+ linked = [...tweet.urls.keys()][tweet.urls.size-1]
+ tweet.text = tweet.text.replace(new RegExp(`(?:${linked})$`), "")
+ }
+ //Medias
+ if (tweet.attachments)
+ tweet.attachments = await Promise.all(tweet.attachments.media_keys.filter(key => medias.get(key)).map(key => medias.get(key)).map(async url => ({image:await imports.imgb64(url, {height:-1, width:450})})))
+ else if (linked) {
+ const {result:{ogImage, ogSiteName:website, ogTitle:title, ogDescription:description}} = await imports.opengraph({url:linked})
+ const image = await imports.imgb64(ogImage?.url, {height:-1, width:450, fallback:false})
+ if (image)
+ tweet.attachments = [{image, title, description, website}]
+ }
+ }
+ else
+ tweet.attachments = null
//Format text
console.debug(`metrics/compute/${login}/plugins > tweets > formatting tweet ${tweet.id}`)
tweet.createdAt = `${imports.date(tweet.created_at, {timeStyle:"short", timeZone:data.config.timezone?.name})} on ${imports.date(tweet.created_at, {dateStyle:"short", timeZone:data.config.timezone?.name})}`
@@ -40,13 +62,13 @@
//Escape tags
imports.htmlescape(tweet.text, {"<":true, ">":true})
//Mentions
- .replace(new RegExp(`@(${tweet.mentions.join("|")})`, "gi"), ' @$1 ')
+ .replace(new RegExp(`@(${tweet.mentions.join("|")})`, "gi"), '@$1')
//Hashtags (this regex comes from the twitter source code)
.replace(/(?#$1 ') //eslint-disable-line no-misleading-character-class, prefer-named-capture-group
//Line breaks
.replace(/\n/g, "
")
//Links
- .replace(/https?:[/][/](?t.co[/]\w+)/g, ' $ '), {"&":true})
+ .replace(new RegExp(`${tweet.urls.size ? "" : "noop^"}(${[...tweet.urls.keys()].map(url => `(?:${url})`).join("|")})`, "gi"), (_, url) => `${tweet.urls.get(url)}`), {"&":true})
}))
//Result
diff --git a/source/plugins/tweets/metadata.yml b/source/plugins/tweets/metadata.yml
index 3e207935..efd9b2d6 100644
--- a/source/plugins/tweets/metadata.yml
+++ b/source/plugins/tweets/metadata.yml
@@ -19,6 +19,12 @@ inputs:
type: token
default: ""
+ # Display tweets attachments (images, video previews, etc.)
+ plugin_tweets_attachments:
+ description: Display tweets attchments
+ type: boolean
+ default: no
+
# Number of tweets to display
plugin_tweets_limit:
description: Maximum number of tweets to display
diff --git a/source/templates/classic/partials/tweets.ejs b/source/templates/classic/partials/tweets.ejs
index 238e6189..db809676 100644
--- a/source/templates/classic/partials/tweets.ejs
+++ b/source/templates/classic/partials/tweets.ejs
@@ -25,9 +25,23 @@
<% if (plugins.tweets.profile) { %>
<% if (plugins.tweets.list.length) { %>
- <% for (const {text, createdAt } of plugins.tweets.list) { %>
+ <% for (const {text, createdAt, attachments} of plugins.tweets.list) { %>