I am making a Twitch bot in node.js and I have ran into a few weird issues.
I have a JSON file system set up, which can be manipulated via commands in the chat. However, for some reason, the JSON file's formatting messes up terribly upon inputting wrong data (such as not defining the user or giving letters/characters instead of a number). A well formatted JSON file will turn into a one-line monster and will often totally mess up the file contents when inputting even more wrong stuff.
The weird part is, that I have set up a detection system for wrong input which should return an error message and ignore writing to the JSON file, however that does not work for some reason (Error message appears, JSON file still changes).
I am a pretty novice programmer so go easy If I may ask.
if (userName === "xyz1" || userName === "xyz2") { // censored for privacy
if (!args[0] || !args[1]) return client.say(channelName, 'You did not specify the user or the value)!');
const user = args[0].toLowerCase();
const giveRaw = args[1];
if (!giveRaw.match(/^[0-9]+$/)) return client.say(channelName, 'Your amount cannot contain letters/symbols/decimals!');
const giveInt = parseInt(giveRaw);
if (pointsjson.hasOwnProperty(user)) {
pointsjson[user] = {
points: pointsjson[user].points + giveInt
}
fs.writeFile('./system/points.json', JSON.stringify(pointsjson, null, 10), err => { // write to json
if (err) throw err;
});
client.say(channelName, `Added ${giveInt} coins to ${user}'s balance.`);
} else if (pointsjson.hasOwnProperty(user) === false) {
pointsjson[user] = {
points: giveInt
}
fs.writeFile("./system/points.json", JSON.stringify(pointsjson, null, 10), err => { // write to json
if (err) throw err;
});
client.say(channelName, `Added ${giveInt} coins to ${user}'s balance. (User has no record in database, added points anyways, did you input the name correctly?)`);
}
} else if (userName !== 'xyz1' && userName !== 'xyz2') return client.say(channelName, "Can't do that broski! (Developer only command.)");
Solved the problem. Apparently I messed up JSON.stringify() somewhere in my code, which got triggered every time a message was sent. I feel so dumb.
Related
So, this code block is part of a code that player A (challenger) issues a challenge to player B (target) for a game. The bot sends a private message to player B to tell them they were challenged and to ask if they accept or decline the challenge.
The following code doesn't seem to respond to what player B replies with.
if (message.channel.id === '541736552582086656') return target.send("Do you accept the challenge? Please reply with 'accept' or 'deny'.")
.then((newmsg) => {
newmsg.channel.awaitMessages(response => response.content, {
max: 1,
time: 150000,
errors: ['time'],
}).then((collected) => {
if (collected === 'accept') {
newmsg.channel.send("You have ***accepted*** the challenge. Please wait while your battlefield is made...");
} else if (collected === 'deny') {
newmsg.channel.send("You have ***denied*** the challenge.")
}
}).catch(() => {
newmsg.channel.send('Please Accept or Deny the challenge.');
});
});
}
Prior to this code block, I set up logging a message to a channel on the server, sending the challenger and the target the challenge info. The bot successfully contacts the target that they were challenged via pm, but the reply (even when replying with "accept" will still think it was declined.
Thanks for any and all help!
Expanding upon #Stock Overflaw's answer, awaitMessages will always return a collection of fetched messages, meaning collected === 'accepted' won't work. You're checking if a collection object is the same as a string.
What you need is to grab the first (and in your case only) message from the collection and check its content against a string. Below you will find your .then(...) statement re-written. Give it a go and let me know what the result is.
P.s. your collection filter won't work as you might expect. The filter only checks if a message will be added to the collection or not. Since your 'filter' is response => response.content, it will only check if the response.content is not empty, null or undefined
.then((collected) => {
// Grabs the first (and only) message from the collection.
const reply = collected.first();
if (reply.content === 'accept'){
reply.channel.send("You have ***accepted*** the challenge. Please wait while your battlefield is made...");
} else if (reply.content === 'deny') {
reply.channel.send("You have ***denied*** the challenge.")
} else {
reply.channel.send("Your response wasn't valid.");
/*
* Code here to handle invalid responses
*/
}
})
I'm trying to find out if its possible to get the time/information of users last activity retrospectively using discord.js
Say I have something like
client.guilds.find('id', 'SERVER ID').fetchMembers().then(members => {
const role = members.roles.find('name', 'Newbies')
role.members.forEach(member => {
console.log(member.user.lastMessage) // null
})
})
Unless the member has posted, since the client is listening, the lastMessage is always null.
Is there a way to find the last activity? or a workaround, like a query to return all the users messages, which I can then take the most recent one from?
Effectively I want to know what date/time the user last posted so we can monitor non-contributing accounts.
Thanks
After looking thought the documentation I didn't find something neither so I came up with a manual search function.
Basically, it will scan every channels until finding a message from X user, or the end of the messages in the channel. It then compare the last messages of the users from every channels and print the last one.
It can be very long if the user hasn't write since a long time. Of course, you have to check lastMessage before trying this.
I would add a time limit maybe. Because if you have thousand of messages, the function will run eternally.
You can stop the function if the last message found is in the accepted time to not be kick/do whatever.
I made the search stop if the first message found in the pack of fetched messaged is older than the ban limit, however, if the first message is not older, remember that it means for the other, so we still need to check them (it can be avoided by checking the last message of the pack as well).
async function fetchMessageUser(chan, id, res) {
let option = {};
if (typeof res !== 'undefined'){
option = {before: res.id};
}
return await chan.fetchMessages(option)
.then(async msgs => {
if (msgs.size === 0){
return {continue: false, found: false};
};
if ((Date.now() - (msgs.first().createdTimestamp)) > 86400000 ) { // 1 day
return {continue: false, found: false};
}
let msgByAuthor = msgs.find(msg => {
return msg.author.id === id;
});
if (msgByAuthor === null){
return {continue: true, id: msgs.last().id};
} else {
return {continue: false, found: true, timestamp: msgByAuthor.createdTimestamp};
}
})
.catch(err => console.log('ERR>>', err));
}
client.on('message', async (msg) => {
let timestamp = [];
for (let [id, chan] of msg.guild.channels){
if (chan.type !== 'text'){ continue; }
let id = '587692527826763788'; // id of the user, here a non verified account
let res;
do {
res = await fetchMessageUser(chan, id, res);
} while(res.continue);
if (res.found) {
timestamp.push(res.timestamp);
}
}
console.log(timestamp);
let first = timestamp.sort((a,b) => (b-a))[0];
console.log(new Date(first));
});
A better variant would be to run it for an array of users, checking all 50 last messages from every channel, and associating each users with his most recent messages if he wrote one, and doing this until all the messages in all the channels are too old to avoid a kick/whatever. And then do something for all the users who don't have an associated messages.
I think what you need is one of Discord's built in features, namely: pruning. This feature will grab inactive members and lets you kick them. Luckily, discord.js has an API call for it and even lets you get the number of members first without actually kicking them by setting the dry parameter to true. The feature will also allow you to specify the amount of days a user has to be inactive.
Have a look at the docs: https://discord.js.org/#/docs/main/stable/class/Guild?scrollTo=pruneMembers
Hope that helps out!
Background
The JSON file is being generated from a mongoDB with mongoose ODM. However it difficult for me to add a mongoose query to the if statement as I get alot of errors.
The JSON Approach
Using this approach, I cache the mongoDB collection to a JSON file, the attempt to read off the ids.
I have a mods.json file which looks like this (Array Of Objects):
[
{
"userid": "58258161"
},
{
"userid": "135207785"
},
{
"userid": "268339963"
},
{
"userid": "210152609"
}
]
The JSON file above contains a list of users who are allowed to execute a certain Telegram Bot command.
var config = require('./config.json');
var mods = require('./mods.json');
bot.onText(/\/test/i, function (msg) {
var fromId = msg.from.id;
var chatId = msg.chat.id;
for (index in mods) {
if (fromId == config.owner || mods[index].userid) {
console.log('You Are A Mod!');
} else {
console.log('Not A Mod');
bot.sendMessage(chatId, 'You are not a mod');
}
}
});
A brief explanation of how the above is supposed to work:
When the bot receives a "/test" command it is supposed to check if the message is from the owner whose id is stored in the config.json file or any moderator whose unique id is stored in the mods.json file.
The problem
The following code checks through every single id to confirm whether the message is from a mod. And subsequently spits out multiple messages of 'You are not a mod'. Which would be the best method to confirm the user is a mod in the conditional statement 'if'.
The Mongo Approach
When querying with moongose a callback is required, I dont know exactly how to create a if conditional in such a scenario. However this method would obviously be better in such a situation.
You could use Array.some instead, to check if the ID is in the array, that way the message won't be outputted on every iteration, only when it's done checking
var isMod = fromId == config.owner || mods.some(x => fromId == x.userid);
if ( isMod ) {
console.log('You Are A Mod!');
} else {
console.log('Not A Mod');
bot.sendMessage(chatId, 'You are not a mod');
}
Preferably you'd construct a query to ask the database instead, something like
model.find({'mods.userid': config.owner}, function (err, docs) {...
I'm performing server-side validation in the "Accounts.onCreateUser" function so that I can pass the options object as well. I wasn't able to figure out how to do this with the validate user function.
First, I'm totally open for correct if I'm going the wrong direction so please advise.
I can't figure out how to validate password length server-side. Is it because it's already converted prior to the creation? When testing, if I enter in a single character for password it doesn't throw an error.
Accounts.onCreateUser(function (options, user) {
if (options.profile) {
user.profile = options.profile;
user.profile.user_status = "new user";
}
// Error checking
var errors = "";
if (user.username.length === 0) {
errors = errors + '<li>Email is required</li>';
}
if (user.username.length !== 0 && user.username.length < 4) {
errors = errors + '<li>Email too short</li>';
}
if (user.profile.firstname.length === 0) {
errors = errors + '<li>First name is required</li>';
}
if (user.profile.firstname.length !== 0 && user.profile.firstname.length < 2) {
errors = errors + '<li>First name is too short</li>';
}
if (user.profile.lastname.length === 0) {
errors = errors + '<li>Last name is required</li>';
}
if (user.profile.lastname.length !== 0 && user.profile.lastname.length < 2) {
errors = errors + '<li>Last name is too short</li>';
}
if (user.services.password.length === 0) {
errors = errors + '<li>Please enter a password</li>';
}
if (user.services.password.length < 7) {
errors = errors + '<li>Password requires 7 or more characters</li>';
}
if (errors) {
throw new Meteor.Error(403, errors);
} else {
return user;
}
});
I'm not using Accounts-ui. Trying to roll out my own... Being completely new with Meteor it has been a bit of a battle trying to understand account creation and verification. If there's a way to do this with ValidateNewUser function should I be using that instead?
Thank you for all your help.
I've figured out the best manner to perform this. Hope this will help others.
I'm using a method on server side to validate and returning error if there is one. Then proceeding with the Account Creation.
Meteor.call('Validate_Registration', email, password, cpassword, firstname, lastname, terms, function(error) {
if (error) {
error = error.reason;
$('#Error-Block').fadeIn().children('ul').html(error);
console.log(error);
} else {
Accounts.createUser({
username: email,
email: email,
password: password,
profile: {
firstname: firstname,
lastname: lastname
}
}, function(error) {
if (error) {
error = error.reason;
$('#Error-Block').fadeIn().children('ul').html(error);
} else {
var uid = Accounts.connection.userId();
Meteor.call('Verify_Email', uid, email);
Router.go('/email-instructions');
}
});
}
});
The only thing I'm unsure of at this point is if it's correct to use:
var uid = Accounts.connection.userId();
This seems to be local to the current user only, and is stored in local storage to the user.
Accounts-password uses SRP, which is a bit complicated so I won't describe it fully here. The actual check of the hashed tokens happens around here Basically, the password does not arrive at the server as a plain text string therefore you will not be able to enforce password policy on the server, while using SRP.
Also notably around here there is a DDP only "plaintext" login option for those who (understandably) don't want to implement SRP on their own. As advertised, it should only be used if the user is connected w/ SSL. I would probably start there.
In the meantime, you can at least do some client side enforcing until you can roll your server-side login handler.
You may also want to check out this meteorhacks article for a custom login handler tutorial.
According to the documentation, the password "is not sent in plain text over the wire", so the password string you're looking at on the server side is not the same as what the user typed in.
EDIT: At least, that's what I think.
EDIT2: Found a comment in another question that confirms it.
I am currently involved helping out on a project which involves using the Geddy js framework, which it is my first time using. I am currently trying to fix the create method inside a model for users. Here is the code below:
this.create = function (req, resp, params) {
var self = this
, user = geddy.model.User.create(params);
//need to ensure that the user agrees with the terms and conditions.
// Non-blocking uniqueness checks are hard
geddy.model.User.first({username: user.username}, function(err, data) {
if (data) {
params.errors = {
username: 'This username is already in use.'
};
//self.transfer('add');
}
else {
if (user.isValid()) {
user.password = cryptPass(user.password);
user.suburb = "";
user.state = "";
user.postcode = "";
}
user.save(function(err, data) {
if (err) {
params.errors = err;
self.transfer('add');
}
else {
// setup e-mail data with unicode symbols
var mailOptions = {
from: "App ✔ <hello#app.com>", // sender address
to: user.email, // list of receivers
subject: user.username + " Thank you for Signing Up ✔", // Subject line
text: "Please log in and start shopping! ✔", // plaintext body
html: "<b>Please log in and start shopping!✔</b>" // html body
}
smtpTransport.sendMail(mailOptions, function(error, response){
if(error){
console.log(error);
}else{
console.log("Message sent: " + response.message);
}
// if you don't want to use this transport object anymore, uncomment following line
smtpTransport.close(); // shut down the connection pool, no more messages
});
self.redirect({controller: self.name});
}
});
}
});
};
If you look in the code there is apparently a check to see if the so-called user is valid like so: if (user.isValid()) {
user.password = cryptPass(user.password);
user.suburb = "";
user.state = "";
user.postcode = "";
}
The proceeds on to 'save' regardless whether or not the user is valid. I'm thinking why is the code this way? It sounds nonsensical. I asked the original developer who was on the project about it and he said the model was apparently generated when he created the project.
So in bit of a confused state, if anyone can tell me why the save method is outside the if statement in the first place? Is it something the original creators of Geddy intended? or is really nonsensical and I should change it?
Thanks.
Geddy's save() call will error out if the data is invalid (unless force flag is set, which it isn't). It uses the same isValid() call actually. So, looks like what you have here is just someone's way to have a single error handler for all the error cases.
For user.password being set with crypted data only if the data looks valid, I'm guessing this is simply to make 'must be set' type of validation to work. Chances are that even with an empty password, the crypted string would be otherwise counted as set.