Skip to content

Commit

Permalink
🎉 Add bot project
Browse files Browse the repository at this point in the history
  • Loading branch information
Toti Muñoz committed Apr 22, 2021
1 parent ac87734 commit 83b9863
Show file tree
Hide file tree
Showing 14 changed files with 217 additions and 1 deletion.
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Consumers keys
CONSUMER_KEY = YOUR_CONSUMER_KEY
CONSUMER_SECRET = YOUR_CONSUMER_SECRET

# Access tokens
ACCESS_TOKEN = YOUR_ACCESS_TOKEN
ACCESS_TOKEN_SECRET = YOUR_ACCESS_TOKEN_SECRET

# Server URL (Only if you need to ping it to keep it alive)
SERVER_URL = YOUR_SERVER_URL
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ typings/

# dotenv environment variables file
.env
.env.dev
.env.test

# parcel-bundler cache (https://parceljs.org/)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# twitter-crypto-bot
# Twitter Crypto Bot
This is a Twitter bot that tweets about cryptocurrencies prices every certain amount of minutes
3 changes: 3 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const startBot = require("./src/startBot");

startBot('BTC', 15);
32 changes: 32 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "twitter-crypto-bot",
"version": "0.0.1",
"description": "This is a Twitter bot that tweets about cryptocurrencies prices every certain amount of minutes",
"main": "index.js",
"scripts": {
"start": "node index.js",
"prod": "npm start prod",
"dev": "npm start dev"
},
"dependencies": {
"dotenv": "^8.2.0",
"express": "^4.17.1",
"node-fetch": "^2.6.1",
"twit": "^2.2.11"
},
"repository": {
"type": "git",
"url": "git+https://github.com/totigm/twitter-crypto-bot.git"
},
"keywords": [
"crypto",
"twitter",
"bot"
],
"author": "Toti Muñoz",
"license": "ISC",
"bugs": {
"url": "https://github.com/totigm/twitter-crypto-bot/issues"
},
"homepage": "https://github.com/totigm/twitter-crypto-bot#readme"
}
1 change: 1 addition & 0 deletions procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: yarn start
12 changes: 12 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const dotenv = require("dotenv");
const NODE_ENV = process.argv.find((value) => value === "prod") ?? "dev";
const path = `.env${NODE_ENV === "prod" ? "" : ".dev"}`;

dotenv.config({
path,
});

process.env.NODE_ENV = NODE_ENV;

const config = process.env;
module.exports = config;
36 changes: 36 additions & 0 deletions src/createUpdatedMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const getPriceData = require("./getPriceData");
const config = require("./config");
const utils = require("./utils");

const getComparisonMessage = (intro, change, percent) =>
`${change > 0 ? "🟢" : "🔴"} ${intro} the price has ${
change > 0 ? "increased" : "dropped"
} by $${Math.abs(change)} (${percent}%).\n`;

const getHashtags = () => "#Bitcoin #BTC";

const createMessage = (coin, priceData, lastTweetPrice = 0) => {
let message = `The $${coin} price is at $${priceData.price} right now.\n`;
if (lastTweetPrice != 0)
message += getComparisonMessage(
"Compared to the last tweet,",
utils.getTwoDecimalsFloat(priceData.price - lastTweetPrice),
utils.getPercentage(priceData.price, lastTweetPrice)
);
message += getComparisonMessage(
"In the last 24 hours",
priceData.dayChange,
priceData.dayChangePercent
);
message += "\n" + getHashtags();
if (config.NODE_ENV === "dev") message = message.replaceAll(/\$|#/g, "[DEV]")
return message;
};

const createUpdatedMessage = async (coin, lastTweetPrice) => {
const priceData = await getPriceData(coin);
const message = createMessage(coin, priceData, lastTweetPrice);
return { message, price: priceData.price };
};

module.exports = createUpdatedMessage;
25 changes: 25 additions & 0 deletions src/getPriceData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const fetch = require("node-fetch");
const utils = require("./utils");

const getPriceData = async (coin) => {
const {
lastPrice: price,
priceChange: dayChange,
priceChangePercent: dayChangePercent,
} = await fetch(
`https://api.binance.com/api/v3/ticker/24hr?symbol=${coin}USDT`
).then((res) => res.json());

const priceData = {
price,
dayChange,
dayChangePercent,
};

for (let property in priceData)
priceData[property] = utils.getTwoDecimalsFloat(priceData[property]);

return priceData;
};

module.exports = getPriceData;
13 changes: 13 additions & 0 deletions src/keepAlive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const fetch = require("node-fetch");
const utils = require("./utils");

const pingServer = (serverUrl) =>
fetch(serverUrl)
.then((res) => res.text())
.then(console.log("Alive at", new Date()));

// Heroku's server goes down after 30 minutes without incoming requests. This method is a workaround to keep it up.
const keepAlive = (serverUrl, minutes = 10) =>
setInterval(() => pingServer(serverUrl), utils.minutesToMs(minutes));

module.exports = keepAlive;
10 changes: 10 additions & 0 deletions src/startBot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const startServer = require("./startServer");
const startTwitterBot = require("./startTwitterBot");

const startBot = (coin, invervalMinutes = 15) => {
coin = coin.toUpperCase();
startServer(coin);
startTwitterBot(coin, invervalMinutes);
};

module.exports = startBot;
29 changes: 29 additions & 0 deletions src/startServer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const createUpdatedMessage = require("./createUpdatedMessage");
const keepAlive = require("./keepAlive");
const config = require("./config");

const express = require("express");
const app = express();

const startServer = (coin) => {
app.set("port", process.env.PORT || 5000);

const port = app.get("port");

app.listen(port, () => {
console.log(
"App is running, server is listening on port",
port
);
});

app.get("/", async (req, res) => {
const messageData = await createUpdatedMessage(coin);
res.send(messageData.message);
});

const serverUrl = config.SERVER_URL;
if (serverUrl) keepAlive(serverUrl, 10);
};

module.exports = startServer;
36 changes: 36 additions & 0 deletions src/startTwitterBot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const Twit = require("twit");
const utils = require("./utils");
const config = require("./config");
const createUpdatedMessage = require("./createUpdatedMessage");

let lastPrice;

const tweet = async (coin, Twitter) => {
const messageData = await createUpdatedMessage(coin, lastPrice);

lastPrice = messageData.price;

Twitter.post(
"statuses/update",
{ status: messageData.message },
(err, data, response) => {
console.log(data);
}
);
};

const startTwitterBot = (coin, intervalMinutes) => {
const Twitter = new Twit({
consumer_key: config.CONSUMER_KEY,
consumer_secret: config.CONSUMER_SECRET,
access_token: config.ACCESS_TOKEN,
access_token_secret: config.ACCESS_TOKEN_SECRET,
});

const intervalMs = utils.minutesToMs(intervalMinutes);

tweet(coin, Twitter);
setInterval(() => tweet(coin, Twitter), intervalMs);
};

module.exports = startTwitterBot;
8 changes: 8 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const getTwoDecimalsFloat = (number) => parseFloat(number).toFixed(2);

const getPercentage = (newPrice, oldPrice) =>
getTwoDecimalsFloat((newPrice / oldPrice - 1) * 100);

const minutesToMs = (minutes) => minutes * 60 * 1000;

module.exports = { getPercentage, getTwoDecimalsFloat, minutesToMs };

0 comments on commit 83b9863

Please sign in to comment.