/**
* Made by Monkey#8028
* JSON as persistent storage example module
* Queues the writeFile operation
* - Way less data corruption
* - No sync I/O used
* - No unnecessary eventloop blocking
*/
const { writeFile } = require('fs');
module.exports = (path, onError) =>{
const data = require(path);
let queue = Promise.resolve();
const write = () => {
queue = queue.then(() => new Promise(res =>
writeFile(path, JSON.stringify(data, null, 2), err => {
if(!err) return res();
if(onError) return onError(err);
throw err;
})
))
}
return new Proxy(data, {
set: (obj, prop, value) => {
obj[prop] = value;
write();
}
})
}
Above is the storage.js file, you can just copy/paste it. Below is example usage:
const storage = require('./storage.js');
client.warns = storage(`${__dirname}/warnings.json`);
// Just modify client.warns like you always do
// Only you don't need to save it using writeFile
// Since that's already done for you!
// Add a warning
if(!client.warns[member.id]) {
client.warns[member.id] = 0;
}
client.warns[member.id]++;
console.log(client.warns);
// Optionally specify how you would like to handle possible errors that occur
client.warns = storage(`${__dirname}/warnings.json`, err => {
console.error('Whoops! An error occured while trying to save warnings.json\n', err);
})
/**
* Example by Monkey#8028
* Simple Discord bot for fortnite stats command
* Make sure you have an config.json file that looks like
* { prefix: string, token: string, fortniteKey: string }
* Generate an API key here https://fortnitetracker.com/site-api
*/
class Fortnite {
/**
* Fortnite class
* @param {string} apiKey
*/
constructor(apiKey) {
// To prevent API spam, make sure to do one request per 2 seconds tops
// A queue will be needed to do that
this.queue = Promise.resolve();
// The fetch options must include the header
this.options = {
headers: { "TRN-Api-Key": apiKey }
}
}
/**
* Small helper function to wait t ms
* @param {number} t Time after which the promise is resolved
* @returns {Promise<void>}
*/
wait(t) { return new Promise(r => setTimeout(r, t)) }
/**
* Fetch the stats by player name
* @param {string} name
* @param {string?} platform Default to pc
* @returns {Promise<Object>}
*/
stats(name, platform = 'pc') {
return new Promise(resolve => {
// Update the queue
this.queue = this.queue
// Fetch the API with the entered name and platform
.then(() => fetch(`https://api.fortnitetracker.com/v1/profile/${platform}/${name}`, this.options))
// Get the JSON response body
.then(res => res.json())
// Resolve the data.lifeTimeStats, and let the queue wait 2 seconds to prevent API spam
.then(data => (resolve(data.lifeTimeStats), this.wait(2000)))
})
}
}
// Load the prefix, Discord bot token, and fortnite apiKey from the config.json file
const { prefix, token, fortniteKey } = require('./config.json');
// Require the Client and RichEmbed from discord.js (we don't need anything else right now)
const { Client, RichEmbed } = require('discord.js');
// Require node-fetch to perform the API requests
const fetch = require('node-fetch');
// initiate a D.JS Client
const client = new Client({ disableEveryone: true });
// initiate a Fortnite class instance
const ft = new Fortnite(fortniteKey);
// NodeJS process listeners
process.on('unhandledRejection', console.error)
process.on('warning', console.warn)
// D.JS Client listeners
client.on('ready', () => console.log(`${client.user.tag} is ready!`));
client.on('error', () => {}); // WS Errors can just be ignored
client.on('reconnecting', () => console.log('Reconnecting WS...'));
client.on('disconnect', () => {
console.log('Disconnected, trying to restart...');
process.exit();
});
client.on("message", async message => {
// If the message is coming froma bot, return
// If a message does not start with the right prefix, return
if(message.author.bot || !message.content.startsWith(prefix)) return;
// Parse the args and command from the message.content
const args = message.content.slice(prefix.length).trim().split(/ +/);
const cmd = args.shift().toLowerCase();
// If the command equals 'fortnite', get the stats
if(cmd === 'fortnite') {
// The player name will be the first command argument. If missing, his own Discord username
const name = args[0] || message.author.username;
// Platform is the second argument, which defaults to 'pc' if missing
const platform = args[1] || 'pc';
// Fetch the stats and log the error in case an error happend
const stats = await ft.stats(name, platform).catch(console.error);
// If an error did happen, let the user know
if(!stats) return message.reply('Failed to load your stats!');
// Create a new RichEmbed instance, to build the stats embed
const embed = new RichEmbed()
.setTitle(`Fortnite stats of ${name}`)
.setColor('BLUE')
.setFooter('Source: fortnitetracker.com')
.setTimestamp()
// For each stat, add an inline field to the embed
for(const { key, value } of stats) {
embed.addField(key, value, true);
}
// Send the embed
message.channel.send(embed);
}
})
// Make sure you login your bot
client.login(token);
// D.JS Client listeners
client.on('error', () => {}); // WS Errors can just be ignored
client.on('reconnecting', () => console.log('Reconnecting WS...'));
client.on('disconnect', () => {
console.log('Disconnected, trying to restart...');
process.exit();
});
// NodeJS process listeners
process.on('unhandledRejection', console.error)
process.on('warning', console.warn)
Cooldown example
// Magic cooldown function
const cooldown = time => {
const set = new Set();
return ({ guild, author }) => {
const key = `${guild.id}-${author.id}`;
if(set.has(key)) return true;
set.add(key);
setTimeout(() => set.delete(key), time);
return false;
}
}
// Initiate cooldowns above of the message event
const cooldowns = {
help: cooldown(60 * 1000), // Cooldown of 1 minute for the help command
coins: cooldown(24 * 60 * 60 * 1000), // Cooldown of 24 hours for the coins command
}
client.on('message', message => {
// Always return on bots, let's disable DMs and make sure the message starts with the prefix
if(message.author.bot || !message.guild || !message.content.startsWith(prefix)) return;
// Define args and cmd in a proper way
const args = message.content.slice(prefix.length).trim().split(/\s+/);
const cmd = args.shift().toLowerCase();
if(cmd === 'help') {
if(cooldowns.help(message)) return message.reply('You gotta wait');
// The actuall help command
return message.reply('Here is the help command');
}
if(cmd === 'coins') {
if(cooldowns.coins(message)) return message.reply('You gotta wait');
// The actuall coins command
return message.reply('Here are some coins');
}
})
Note: This cooldown example is not persistent. That means that all the cooldowns reset after a bot restart. For relative short cooldowns, this is fine, but for longer cooldowns, you might want to make them persistent using a database.
/*
* Creating a D.JS docs command
* Example by Monkey#8028
*/
// Make sure you require node-fetch, because we will have to make an API request
const fetch = require('node-fetch');
// Define what the search query is, most likely, that would be args.join(' ')
const query = args.join(' ');
// You can construct a url from that, using this REST API
const url = `https://djsdocs.sorta.moe/v2/embed?src=stable&q=${encodeURIComponent(query)}`;
// We need to fetch that url
fetch(url)
// Get the JSON response
.then(res => res.json())
.then(embed => {
// The request was make successfull, now let's see if there was a result found
if(embed && !embed.error) {
// Yes there was, let's send it!
message.channel.send({ embed });
} else {
// Nope, no results found, let's let the user know
message.reply(`I don't know mate, but "${query}" doesn't make any sense!`);
}
})
.catch(e => {
// Whoops, some error occured, let's log it and notify the user
console.error(e);
message.reply('Darn it! I failed!');
})
/**
* Example calculator in NodeJS
* Solves equations like 12-3*2/2-9
* Only supports * / + -
* Does not support brackets
*/
require('readline').createInterface({
input: process.stdin,
output: process.stdout
}).on('line', line => console.log(solve(tokenize(line))));
function tokenize(input) {
for(const [a, b] of [['+', '-'], ['*', '/']]) {
const indexA = input.lastIndexOf(a);
const indexB = input.lastIndexOf(b);
if(indexA !== -1 || indexB !== -1) {
const index = indexA !== -1 && indexB !== -1 ?
Math.min(indexA, indexB):
Math.max(indexA, indexB);
return [
input.charAt(index),
tokenize(input.slice(0, index)),
tokenize(input.slice(index + 1))
]
}
}
return input;
}
function solve(tree) {
if(typeof tree === 'string') return Number(tree);
if(tree[0] === '*') return solve(tree[1]) * solve(tree[2]);
if(tree[0] === '/') return solve(tree[1]) / solve(tree[2]);
if(tree[0] === '+') return solve(tree[1]) + solve(tree[2]);
if(tree[0] === '-') return solve(tree[1]) - solve(tree[2]);
}
require('readline').createInterface({
input: process.stdin,
output: process.stdout
}).on('line', line => {
// Send the input line to a channel
})
=> is the arrow function
// function statement
function sum(a, b) {
return a + b;
}
// With return keyword
const sum = (a, b) => {
return a + b;
}
// without return keyword, onelined
const sum = (a, b) => a + b;
Now there are differences when it comes down to for example hoisting and this, but in general, those two types of functions do the exact same Example doubling a number:
function double(x) { return 2 * x }
[1,2,3].map(double);
const double = x => 2 * x;
[1,2,3].map(double);
[1,2,3].map(function double(x) { return 2 * x });
[1,2,3].map(x => 2 * x);
If the length is more than 50, you would not like to paste all the words below each other, but you'd like to "wrap" the text. Here is a proper example:
function setMaxLength(input, len) {
let i = 0;
return input.split(' ').reduce((acc, val) => {
i += val.length;
return acc + (i > len ? (i = val.length, '\n' + val) : ' ' + val);
})
}
So you can use it like this:
const input = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
console.log(setMaxLength(input, 50));
This will log
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui
officia deserunt mollit anim id est laborum.
The official guide
An idiot's guide
Basic JS and NodeJS
CodeCademy online course
Eloquent Javascript, free book
Some Node
Promises/async
- Evie's written guide
- MDN (promise)
- MDN (async/await)
By Monkey#8028 and Connor#4767
Firstly, Free hosting isn't reliable at all, they have some or other kind of drawbacks or they don't have a proper support.
Lists of free hosting
- Glitch - Guide
- Heroku - Guide
- Google Cloud - Video Guide
- Amazon Aws - Video Guide
- Microsoft Azure - Video Guide
We have got these far, you know some more? lets us know.
Note: Google Cloud and Amazon aws require you to give your credit card details for verification and Microsoft Azure needs you to have a github student pack. Bonus: If you have a credit card, then Github student pack gives you some more VPS make sure check them out.
Ah, yes who doesn't want free hosting right? Well there are some problems with those
- you can't store data or they are limited
- They are not safe? Might lead to a ban if you are using some kind of API?
- Limited usage
- Frequent downtime
- No proper support
So considering glitch, there is a lot of case of API bans lately for discord thought, your project stays alive only for 5 minutes. In order to keep it up you need to ping it every 5 min, Talking about Heroku, well Heroku seemed to work fine until March 2019, the worker started to sleep, which took down like 1000+ discord bots , Heroku gives you 500 hours which is not enough you need a minimum of 720 hours per month to keep you discord bot active, talking about Google cloud and Amazon aws they are quite good and they have good support but you would need a credit card why so ? Well Google cloud and Amazon aws don't care how much resources you use its left to you but if you cross that limit then you might have to pay the bills or your account would get terminated, And Microsoft Azure well I haven't tried myself so I can't say much about it.
Hey, I found a discord.js handler gist that worked for my bot but now that I need it I can't find it