Feat plugin tweets medias (#164)
This commit is contained in:
1 parent
46ef6a1532
commit
3b5f9153c4
5 files changed
+94
-8
No files matched your search
@@ -31,6 +31,7 @@ Create an app from your [developer dashboard](https://developer.twitter.com/en/p
|
|||||||
# ... other options
|
# ... other options
|
||||||
plugin_tweets: yes
|
plugin_tweets: yes
|
||||||
plugin_tweets_token: ${{ secrets.TWITTER_TOKEN }} # Required
|
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_limit: 2 # Limit to 2 tweets
|
||||||
plugin_tweets_user: .user.twitter # Defaults to your GitHub linked twitter username
|
plugin_tweets_user: .user.twitter # Defaults to your GitHub linked twitter username
|
||||||
```
|
```
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//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
|
//Load user profile
|
||||||
console.debug(`metrics/compute/${login}/plugins > tweets > loading twitter profile (@${username})`)
|
console.debug(`metrics/compute/${login}/plugins > tweets > loading twitter profile (@${username})`)
|
||||||
@@ -21,7 +21,8 @@
|
|||||||
|
|
||||||
//Load tweets
|
//Load tweets
|
||||||
console.debug(`metrics/compute/${login}/plugins > tweets > querying api`)
|
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
|
//Limit tweets
|
||||||
if (limit > 0) {
|
if (limit > 0) {
|
||||||
@@ -31,8 +32,29 @@
|
|||||||
|
|
||||||
//Format tweets
|
//Format tweets
|
||||||
await Promise.all(tweets.map(async tweet => {
|
await Promise.all(tweets.map(async tweet => {
|
||||||
//Mentions
|
//Mentions and urls
|
||||||
tweet.mentions = tweet.entities?.mentions.map(({username}) => username) ?? []
|
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
|
//Format text
|
||||||
console.debug(`metrics/compute/${login}/plugins > tweets > formatting tweet ${tweet.id}`)
|
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})}`
|
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
|
//Escape tags
|
||||||
imports.htmlescape(tweet.text, {"<":true, ">":true})
|
imports.htmlescape(tweet.text, {"<":true, ">":true})
|
||||||
//Mentions
|
//Mentions
|
||||||
.replace(new RegExp(`@(${tweet.mentions.join("|")})`, "gi"), ' <span class="mention">@$1</span> ')
|
.replace(new RegExp(`@(${tweet.mentions.join("|")})`, "gi"), '<span class="mention">@$1</span>')
|
||||||
//Hashtags (this regex comes from the twitter source code)
|
//Hashtags (this regex comes from the twitter source code)
|
||||||
.replace(/(?<!&)[#|#]([a-z0-9_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f]*[a-z_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f][a-z0-9_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f]*)/gi, ' <span class="hashtag">#$1</span> ') //eslint-disable-line no-misleading-character-class, prefer-named-capture-group
|
.replace(/(?<!&)[#|#]([a-z0-9_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f]*[a-z_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f][a-z0-9_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f]*)/gi, ' <span class="hashtag">#$1</span> ') //eslint-disable-line no-misleading-character-class, prefer-named-capture-group
|
||||||
//Line breaks
|
//Line breaks
|
||||||
.replace(/\n/g, "<br/>")
|
.replace(/\n/g, "<br/>")
|
||||||
//Links
|
//Links
|
||||||
.replace(/https?:[/][/](?<link>t.co[/]\w+)/g, ' <span class="link">$<link></span> '), {"&":true})
|
.replace(new RegExp(`${tweet.urls.size ? "" : "noop^"}(${[...tweet.urls.keys()].map(url => `(?:${url})`).join("|")})`, "gi"), (_, url) => `<span class="link">${tweet.urls.get(url)}<link></span>`), {"&":true})
|
||||||
}))
|
}))
|
||||||
|
|
||||||
//Result
|
//Result
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ inputs:
|
|||||||
type: token
|
type: token
|
||||||
default: ""
|
default: ""
|
||||||
|
|
||||||
|
# Display tweets attachments (images, video previews, etc.)
|
||||||
|
plugin_tweets_attachments:
|
||||||
|
description: Display tweets attchments
|
||||||
|
type: boolean
|
||||||
|
default: no
|
||||||
|
|
||||||
# Number of tweets to display
|
# Number of tweets to display
|
||||||
plugin_tweets_limit:
|
plugin_tweets_limit:
|
||||||
description: Maximum number of tweets to display
|
description: Maximum number of tweets to display
|
||||||
|
|||||||
@@ -25,9 +25,23 @@
|
|||||||
</div>
|
</div>
|
||||||
<% if (plugins.tweets.profile) { %>
|
<% if (plugins.tweets.profile) { %>
|
||||||
<% if (plugins.tweets.list.length) { %>
|
<% if (plugins.tweets.list.length) { %>
|
||||||
<% for (const {text, createdAt } of plugins.tweets.list) { %>
|
<% for (const {text, createdAt, attachments} of plugins.tweets.list) { %>
|
||||||
<div class="tweet">
|
<div class="tweet">
|
||||||
<%- text %>
|
<%- text %>
|
||||||
|
<% if (attachments) { %>
|
||||||
|
<div class="attachments">
|
||||||
|
<% for (const {image, title, description, website} of attachments) { %>
|
||||||
|
<div style="background-image: url('<%= image %>');">
|
||||||
|
<% if (title) { %>
|
||||||
|
<div class="infos">
|
||||||
|
<div class="title"><%= title %></div>
|
||||||
|
<div class="description"><%= description %></div>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
<div class="date"><%= createdAt %></div>
|
<div class="date"><%= createdAt %></div>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|||||||
@@ -326,7 +326,6 @@
|
|||||||
|
|
||||||
.tweet .mention, .tweet .link, .tweet .hashtag {
|
.tweet .mention, .tweet .link, .tweet .hashtag {
|
||||||
color: #0366d6;
|
color: #0366d6;
|
||||||
margin: 0 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tweet .date {
|
.tweet .date {
|
||||||
@@ -335,6 +334,50 @@
|
|||||||
color: #666666;
|
color: #666666;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tweet .attachments {
|
||||||
|
display: flex;
|
||||||
|
width: 450px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet .attachments > div {
|
||||||
|
flex: 1 1 0;
|
||||||
|
width: 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
|
height: 200px;
|
||||||
|
margin: 2px;
|
||||||
|
box-shadow: 0px 0px 1px #777777A0;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet .attachments .infos {
|
||||||
|
background-color: #000000D0;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet .attachments .infos > div {
|
||||||
|
margin: 4px 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet .attachments .infos .title {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet .attachments .infos .description {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
/* Charts and graphs */
|
/* Charts and graphs */
|
||||||
.chart {
|
.chart {
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
|
|||||||
Reference in new issue
Block a user