(This tutorial is also available in portuguese)
Open the terminal on your operational system (Windowers can use GIT Bash) and see a black screen.
The flashing cursor on the command line shows that you are in the game. You can move between classes at will, but your experience in each will vary. The Javascripter class is quite on target today and this guide will be based on it.
First steps
There are different ways to use your Javascript skills. We will approach the one that grants some basic equipment by simply casting npm init into a folder.
To enable save mode, use git init once and git commit -am "save" to save. It's a good practice that instead of using the word save
you would use a brief semantic message of your progress.
With save mode enabled, your secrets can be exposed to enemies and to protect them you can use dotenv. Create a .env
file with value = "key"
and add it to a.gitignore
file. Then access them with process.get.INFO
.
Evolutions and Combos
Your basic skill will be node script.js and soon it can be improved to nodemon script.js, allowing for a better flow of your spells.
A major evolution would be using nodemon --exec babel-node script.js to allow the use of up-to-date spells and keep track of the current skills meta.
npm install nodemon --save-dev
npm install @babel/core @babel/node @babel/preset-env --save-dev
// Create .babelrc file and insert:
{
"presets": [
"@babel/preset-env"
]
}
node script.js
nodemon script.js
nodemon --exec babel-node script.js
// Add to package.json:
"scripts": {
"dev": "nodemon --exec babel-node index.js"
},
npm run dev
The text editor will allow the handling of the script.js and create different results according to what you want to do. I recommend VSCode with built-in file browsing, text editor and terminal all together, along with several others advantages.
Quests and other objectives will require different resources, such as express / koa to create routes and open ports within your domain. and react / vue to generate interfaces and visual entities.
statusReport
In this campaign, we will create a Node application that checks the status of a Habitica character and posts a tweet with a summary of the situation. This process should happen every time an endpoint is accessed.
Hereafter it is assumed that you are already prepared with the above upgrades. You can also track quest progress through the commit history of this campaign.
Quest # 1: Get Habitica information
We will invoke a utility spell with npm install axios
that will access the Habitica domain and give us information about a given character. The character ID is stored in the environment variable in .env
accessed withprocess.env.HABITICA_USERID
.
import 'dotenv/config'
import axios from 'axios'
const getStats = async (userid) => {
try {
const response = await axios.get(`https://habitica.com/api/v3/members/${userid}`)
return response.data.data.stats
} catch (error) {
console.log(error)
}
}
const reportStatus = async () => {
try {
const stats = await getStats(process.env.HABITICA_USERID)
console.log(stats)
} catch (error) {
console.log(error)
}
}
reportStatus()
Here we realize the need of Async / Await with Try / Catch in asynchronous requests.
Quest # 2: Generate message based on stats
This step requires just a little javascripter manipulation. A simple way to exemplify the idea is as follows:
// ...
const selectMessage = ({ hp = 0, maxHealth = 0, exp = 0, toNextLevel = 0 }) => {
const status = `[HP: ${hp}/${maxHealth}] [EXP: ${exp.toFixed()}/${toNextLevel}]`
if (hp <= maxHealth * 0.3) {
return `I'm almost dying, help! ${status}`
}
// Could also be:
// if (hp <= maxHealth * 0.3) return `I'm almost dying, help! ${status}`
if (exp >= toNextLevel * 0.7) {
return `I'm almost leveling up! ${status}`
}
return `Things are fine for now. ${status}`
}
const reportStatus = async () => {
try {
const stats = await getStats(process.env.HABITICA_USERID)
const message = selectMessage(stats)
console.log(message)
} catch (error) {
console.log(error)
}
}
reportStatus()
At this point we can identify some peculiarities like Template Literals in strings and Object Destructuring in the selectMessage()
parameters.
Quest # 3: Post to twitter
Here the difficulty begins to increase and in this solution you will need to register in the domain of Twitter wizards to get secret tokens. These tokens will be used in conjunction with the OAuth method to send messages to the domain.
// ...
import OAuth from 'oauth'
// ...
const reportTwitter = async (message) => {
const oauth = new OAuth.OAuth(
'https://api.twitter.com/oauth/request_token',
'https://api.twitter.com/oauth/access_token',
process.env.TWITTER_CONSUMER_APIKEY,
process.env.TWITTER_CONSUMER_APISECRETKEY,
'1.0A',
null,
'HMAC-SHA1'
);
return oauth.post(
'https://api.twitter.com/1.1/statuses/update.json',
process.env.TWITTER_ACCESS_TOKEN,
process.env.TWITTER_ACCESS_SECRETTOKEN,
{ status: message },
'application/x-www-form-urlencoded',
function callback(error, data, res) {
if (error) {
throw new Error(error.data)
};
const jsonData = JSON.parse(data)
const { user: { screen_name }, text } = jsonData
console.log(`Tweet created! @${screen_name}: ${text}`)
return true
});
}
const reportStatus = async () => {
try {
const stats = await getStats(process.env.HABITICA_USERID)
const message = selectMessage(stats)
return reportTwitter(message)
} catch (error) {
console.log(error)
}
}
reportStatus()
More secrets are being stored in .env
file, JSON.parse shows its face and Object Destructuring appears again and it's applied to the jsonData.
Quest # 4: Trigger Endpoint
Our mission is almost done and here are some interesting things happening.
We are using Koa to prepare the api endpoint that will trigger and return the report result.
//..
import Koa from 'koa';
//...
const reportTwitter = async (message) => {
//...
console.log(`Posting tweet with message: ${message}`)
return new Promise((resolve, reject) => oauth.post(
'https://api.twitter.com/1.1/statuses/update.json',
process.env.TWITTER_ACCESS_TOKEN,
process.env.TWITTER_ACCESS_SECRETTOKEN,
{ status: message },
'application/x-www-form-urlencoded',
function callback(error, data, res) {
if (error) {
const errorMessage = error.data
console.log('Error: could not post tweet ', errorMessage)
return resolve(errorMessage)
};
const jsonData = JSON.parse(data)
const { user: { screen_name }, text } = jsonData
const successMessage = `Tweet created! @${screen_name}: ${text}`
console.log(successMessage)
return resolve(successMessage)
}));
}
const reportStatus = async () => {
try {
const stats = await getStats(process.env.HABITICA_USERID)
const message = selectMessage(stats)
const response = await reportTwitter(message)
return response
} catch (error) {
console.log(error)
}
}
const app = new Koa();
app.use(async (ctx) => {
const message = await reportStatus()
ctx.response.body = message
});
app.listen(3000);
And if we take a closer look, we see that the reportTwitter()
function now returns a Promise.
This had to be done because oauth.post()
does not return a Promise by default and we need this to display the return in ctx.response.body
.
Note that the function is not rejected() in error, but resolved() to display the error message on screen (ctx).
Quest # 5: Deploy
As a final step in this mission, we will raise our creation to the clouds.
We will use the Now utility tool by installing it globally with npm install -g now
, creating an account by typingnow
and adding our secrets securely on our account with
now secrets add habitica-userid <userid>
now secrets add twitter-consumer-apikey <key>
now secrets add twitter-consumer-apisecretkey <key>
now secrets add twitter-access-token <token>
now secrets add twitter-access-secrettoken <token>
And with a few more settings in now.json...
{
"version": 2,
"builds": [
{
"src": "index.js",
"use": "@now/node-server"
}
],
"env": {
"HABITICA_USERID": "@habitica-userid",
"TWITTER_CONSUMER_APIKEY": "@twitter-consumer-apikey",
"TWITTER_CONSUMER_APISECRETKEY": "@twitter-consumer-apisecretkey",
"TWITTER_ACCESS_TOKEN": "@twitter-access-token",
"TWITTER_ACCESS_SECRETTOKEN": "@twitter-access-secrettoken"
}
}
Summon now
on the command line and mission accomplished.
Is chronomancy difficult?
The initial idea for this report was it to happen every day at a specific time and it was easily achieved using a simple node-cron:
import cron from 'node-cron'
cron.schedule('30 19 * * *', () => reportStatus())
But Heroku and Now applications goes to sleeping and things get a lot more complicated.
Next campaign?
A good continuation of this campaign would involve doing tests, refactoring, organizing into files, turning it into a Docker container and deploying it on AWS.
What do you think? Would you like more tutorials like this? Leave a message in the comments o/
Top comments (0)