Skip to content

Commit a66391c

Browse files
author
Juliano Rafael
committed
🎉 Initial commit
0 parents  commit a66391c

8 files changed

+1401
-0
lines changed

.gitignore

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# package directories
2+
node_modules
3+
jspm_packages
4+
5+
# Serverless directories
6+
.serverless
7+
8+
# Environment variables
9+
env.yml

README.md

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# TheReactDev
2+
3+
The function that feeds @TheReactDev twitter.
4+
5+
This project was inspired by
6+
[this article](https://dev.to/danielelkington/a-bot-that-tweets-new-dev-articles-about-vue-4p5a).
7+
8+
This function depends on a few environment variables:
9+
10+
- `dev_tag`: the dev.to tag to which the crawler will get the latest articles.
11+
- `twitter_bot_screen_name`: the name of the account that will tweet the
12+
articles.
13+
- Twitter keys:
14+
- `twitter_consumer_key`
15+
- `twitter_consumer_secret`
16+
- `twitter_access_token`
17+
- `twitter_access_token_secret`
18+
- `telegram_bot_token`: the telegram bot who's going to log messages for
19+
maintenance/debug purposes
20+
- `telegram_chat_id`: the channel where the bot will send messages
21+
22+
You just need to create a `env.yml` file and put those values in, like this:
23+
24+
```yml
25+
dev_tag: "react"
26+
twitter_bot_screen_name: "NameOfTheTwitterAccount"
27+
twitter_consumer_key: "..."
28+
twitter_consumer_secret: "..."
29+
twitter_access_token: "..."
30+
twitter_access_token_secret: "..."
31+
telegram_bot_token: "..."
32+
telegram_chat_id: "..."
33+
```
34+
35+
It's worth noting that I used telegram here because it was simple and it
36+
probably is the fastest way I can see a message in case things go wrong.
37+
Probably sentry.io would be more
38+
39+
I started using azure, like
40+
[twitter-vue-dev](http://github.com/danielelkington/twitter-vue-dev/), but after
41+
a few hours of headaches I'm switching to the serverless framework with aws.

getLatestArticles.js

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const Xray = require("x-ray")
2+
const URL = require("url").URL
3+
4+
const X = Xray({
5+
filters: {
6+
trim: value => value.trim(),
7+
parseName: value => value.split("・")[0]
8+
}
9+
})
10+
11+
module.exports = async tag => {
12+
const articles = await X(
13+
`https://dev.to/t/${tag}/latest`,
14+
"#substories .single-article",
15+
[
16+
{
17+
title: ".index-article-link .content h3 | trim",
18+
link: ".index-article-link@href",
19+
tags: [".tags .tag"],
20+
author: {
21+
name: "h4 a | parseName",
22+
link: ".small-pic-link-wrapper@href"
23+
}
24+
}
25+
]
26+
).then(articles => articles.filter(article => article.title))
27+
28+
// get twitter handle
29+
for (article of articles) {
30+
const socialLinks = await X(article.author.link, [
31+
".profile-details .social a@href"
32+
])
33+
const twitter = socialLinks.find(url => url.includes("twitter.com/"))
34+
if (twitter) {
35+
const twitterURL = new URL(twitter)
36+
article.author.twitterHandle = `@${twitterURL.pathname.substring(1)}`
37+
}
38+
}
39+
40+
return articles
41+
}

handler.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"use strict"
2+
3+
const axios = require("axios")
4+
5+
const getLatestDevArticles = require("./getLatestArticles")
6+
const { getRecentTweets, tweet, isPublished, format } = require("./twitter")
7+
8+
const sendToTelegram = async text =>
9+
axios.post(
10+
`https://api.telegram.org/bot${process.env.telegram_bot_token}/sendMessage`,
11+
{ chat_id: process.env.telegram_chat_id, text }
12+
)
13+
14+
module.exports.scrapArticlesAndTweet = async () => {
15+
const articles = await getLatestDevArticles(process.env.dev_tag)
16+
const recentTweets = await getRecentTweets(
17+
process.env.twitter_bot_screen_name
18+
)
19+
20+
for (article of articles) {
21+
if (!isPublished(article, recentTweets)) {
22+
const data = await tweet(format(article))
23+
const log = `Tweeted ${article.title} on ${
24+
data.id
25+
} at ${new Date().toISOString()}`
26+
sendToTelegram(log)
27+
}
28+
}
29+
}

package.json

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"dependencies": {
3+
"axios": "^0.18.0",
4+
"twit": "^2.2.11",
5+
"x-ray": "^2.3.3"
6+
}
7+
}

serverless.yml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
service: TheReactDev
2+
3+
provider:
4+
name: aws
5+
runtime: nodejs8.10
6+
environment: ${file(env.yml)}
7+
8+
functions:
9+
scrapArticlesAndTweet:
10+
handler: handler.scrapArticlesAndTweet
11+
events:
12+
- schedule: rate(2 minutes)

twitter.js

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const Twit = require("twit")
2+
const T = Twit({
3+
consumer_key: process.env.twitter_consumer_key,
4+
consumer_secret: process.env.twitter_consumer_secret,
5+
access_token: process.env.twitter_access_token,
6+
access_token_secret: process.env.twitter_access_token_secret
7+
})
8+
9+
module.exports = {
10+
getRecentTweets: async () =>
11+
T.get("statuses/user_timeline", {
12+
screen_name: process.env.twitter_bot_screen_name,
13+
trim_user: true,
14+
tweet_mode: "extended"
15+
}).then(res => res.data),
16+
tweet: async status => T.post("statuses/update", { status }),
17+
isPublished: (article, tweets) => {
18+
const tweet = tweets
19+
.map(tweet => tweet.entities.urls[0])
20+
.find(entity => entity.expanded_url === article.link)
21+
return !!tweet
22+
},
23+
format: article =>
24+
`"${article.title}" by ${article.author.twitterHandle ||
25+
article.author.name}\n#DEVcommunity ${article.tags.join(" ")}\n${
26+
article.link
27+
}`
28+
}

0 commit comments

Comments
 (0)