Discord <#!userid> vs <#userid> - node.js

so I'm creating a bot using Node.JS / Discord.JS and I have a question.
On some servers, when you mention a user, it returns in the console as <#!userid> and on other it returns as <#userid>.
My bot has a simple points / level system, and it saves in a JSON file as <#!userid>, so on some servers when trying to look at a users points by mentioning them will work, and on others it won't.
Does anyone have any idea how to fix this? I've tried to find an answer many times, and I don't want to have it save twice, once as <#!userid> and then <#userid>. If this is the only way to fix it then I understand.
Thanks for your help!

The exclamation mark in the <#!userID> means they have a nickname set in that server. Using it without the exclamation mark is more reliable as it works anywhere. Furthermore, you should save users with their id, not the whole mention (the "<#userid>"). Parse out the extra symbols using regex.
var user = "<#!123456789>" //Just assuming that's their user id.
var userID = user.replace(/[<#!>]/g, '');
Which would give us 123456789. Their user id. Of course, you can easily obtain the user object (you most likely would to get their username) in two ways, if they're in the server where you're using the command, you can just
var member = message.guild.member(userID);
OR if they're not in the server and you still want to access their user object, then;
client.fetchUser(userID)
.then(user => {
//Do some stuff with the user object.
}, rejection => {
//Handle the error in case one happens (that is, it could not find the user.)
});
You can ALSO simply access the member object directly from the tag (if they tagged them in the message).
var member = message.mentions.members.first();
And just like that, without any regex, you can get the full member object and save their id.
var memberID = member.id;

Related

Context Management in Bot Framework - Node JS

in the main dialog, I have used this to get the details of the user activity and session ID stepContext.context._activity
and I am able to store the data in JSON format,
I want to know how to use the previous text of the user for the next query if there is no entity present in that and it should work for multiple users, not for a single user.
The location of the user's input varies depending on the option provided to them. Simple text input is available in the stepContext.context.activity.text field. However, the user's returned value could, for example, exist in:
stepContext.result, if a hero card is used
stepContext.options, if a value is forwarded on from a previous step or action
stepContext.context.activity.text, if a suggested action is used
stepContext.context.activity.value, if a postBack is returned
There are a few other possibilities. Referencing the docs is a good place to start as are the BotBuilder-Samples for getting a grasp on all the possibilities. Don't be averse to inspecting the stepContext to see how it is populated and with what.
async firstStep(stepContext) {
await stepContext.context.sendActivity('Type something...');
return { status: DialogTurnStatus.waiting };
}
async secondStep(stepContext) {
const result = stepContext.context.activity.text;
return await stepContext.context.sendActivity(`You said: ${ result }`);
Hope of help!

Preventing a discord bot from executing a command in a user prompt

I need some help/suggestion to prevent a discord bot from executing a command in a user prompt.
My bot currently has a feature that prompts the user a question, and it'll add whatever the user's answer is to the database. The problem that I'm trying to solve is, currently the user's able to enter a bot command as an answer, and the bot would both execute that command and take that as an answer to add to the database.
A very quick example to show how problematic this can get:
User: ?question
Bot: Cats or Dogs?
User: ?question
Bot: "?question" have been added to the database
Bot: Cats or Dogs?
I don't have a problem with the bot adding the command to the database, because that's what the user entered (it might be relevant for the user to enter a bot command there), but I don't want the bot to execute that command.
Right now I have 2 vague ideas to solve this (I don't know whether or not any of this will be valid):
I need to turn the user's answer into an "answer" type variable where the bot can't use it to search for commands, but can still use it to upload to the database and fetch from it and display in a list of answers. Although I don't know if this can be executed before the bot starts to search for the command.
I need to somehow change how this system of question & answer works.
Note: Currently my bot detects a command by slicing the first bit of the user's message .slice(config.prefix.length)
Any help or suggestion will be much appreciated.
Thanks in advance!
Here's some answers to your question:
The bot takes in a command as an answer.
If you ever change your mind, you can disallow this by simply allowing only a few selected responses ("cat" or "dog") or disallowing commands that start with a ?.
The command is executed if a command was used as an answer.
This is because you check if the message is a command down the line, somewhere in your code. Not only does the bot recognize the user's message as a valid answer, but it also recognizes that it fulfills the prefix you set and thus it runs the command.If you have a flag that indicates that the user's next response is an answer to the Q&A question, then you can check if that flag is active before executing the command. For example:
// Assume that the user's ID is in this array after they
// requested to answer a Q&A question.
var usersAwaitingResponse = [];
...
client.on("message", function(message) {
if (usersAwaitingResponse.includes(message.author.id)) {
// take in the answer and then end the function by calling "return;"
} else {
// check if the message was a command and act accordingly
}
});
Prefix Checking
Lastly, I recommend that you check if a message is a command by using .startsWith(config.prefix) on the message text. If you want the user to only input allowed characters after the prefix, then you can use a regular expression. Either method saves time and memory rather than slicing the string. An example of those can be seen below:
// using "startsWith"
function checkIfCommand(message) {
return message.content.startsWith(config.prefix);
}
// using a regular expression
function checkIfCommand(message) {
/**
* If all the matches are fulfilled, the test passes:
*
* ^ - The match should be at the beginning of the string.
* ${config.prefix} - The prefix (interpolated into the string).
* [A-Z0-9]+ - match as much characters that are A to Z or 0 to 9
* \s? - Match a whitespace character (if there is one)
* "gi" - global, case insensitive
**/
return new RegExp(`^${config.prefix}[A-Z]+\s?`, "gi").test(message.content);
}

Strapi & react-admin : I'd like to set 'Content-Range' header dynamically when any fetchAll query fires

I'm still a novice web developer, so please bear with me if I miss something fundamental !
I'm creating a backoffice for a Strapi backend, using react-admin.
React-admin library uses a 'data provider' to link itself with an API. Luckily someone already wrote a data provider for Strapi. I had no problem with step 1 and 2 of this README, and I can authenticate to Strapi within my React app.
I now want to fetch and display my Strapi data, starting with Users. In order to do that, quoting Step 3 of this readme : 'In controllers I need to set the Content-Range header with the total number of results to build the pagination'.
So far I tried to do this in my User controller, with no success.
What I try to achieve:
First, I'd like it to simply work with the ctx.set('Content-Range', ...) hard-coded in the controller like aforementioned Step 3.
Second, I've thought it would be very dirty to c/p this logic in every controller (not to mention in any future controllers), instead of having some callback function dynamically appending the Content-Range header to any fetchAll request. Ultimately that's what I aim for, because with ~40 Strapi objects to administrate already and plenty more to come, it has to scale.
Technical infos
node -v: 11.13.0
npm -v: 6.7.0
strapi version: 3.0.0-alpha.25.2
uname -r output: Linux 4.14.106-97.85.amzn2.x86_64
DB: mySQL v2.16
So far I've tried accessing the count() method of User model like aforementioned step3, but my controller doesn't look like the example as I'm working with users-permissions plugin.
This is the action I've tried to edit (located in project/plugins/users-permissions/controllers/User.js)
find: async (ctx) => {
let data = await strapi.plugins['users-permissions'].services.user.fetchAll(ctx.query);
data.reduce((acc, user) => {
acc.push(_.omit(user.toJSON ? user.toJSON() : user, ['password', 'resetPasswordToken']));
return acc;
}, []);
// Send 200 `ok`
ctx.send(data);
},
From what I've gathered on Strapi documentation (here and also here), context is a sort of wrapper object. I only worked with Express-generated APIs before, so I understood this snippet as 'use fetchAll method of the User model object, with ctx.query as an argument', but I had no luck logging this ctx.query. And as I can't log stuff, I'm kinda blocked.
In my exploration, I naively tried to log the full ctx object and work from there:
// Send 200 `ok`
ctx.send(data);
strapi.log.info(ctx.query, ' were query');
strapi.log.info(ctx.request, 'were request');
strapi.log.info(ctx.response, 'were response');
strapi.log.info(ctx.res, 'were res');
strapi.log.info(ctx.req, 'were req');
strapi.log.info(ctx, 'is full context')
},
Unfortunately, I fear I miss something obvious, as it gives me no input at all. Making a fetchAll request from my React app with these console.logs print this in my terminal:
[2019-09-19T12:43:03.409Z] info were query
[2019-09-19T12:43:03.410Z] info were request
[2019-09-19T12:43:03.418Z] info were response
[2019-09-19T12:43:03.419Z] info were res
[2019-09-19T12:43:03.419Z] info were req
[2019-09-19T12:43:03.419Z] info is full context
[2019-09-19T12:43:03.435Z] debug GET /users?_sort=id:DESC&_start=0&_limit=10& (74 ms)
While in my frontend I get the good ol' The Content-Range header is missing in the HTTP Response message I'm trying to solve.
After writing this wall of text I realize the logging issue is separated from my original problem, but if I was able to at least log ctx properly, maybe I'd be able to find the solution myself.
Trying to summarize:
Actual problem is, how do I set my Content-Range properly in my strapi controller ? (partially answered cf. edit 3)
Collateral problem n°1: Can't even log ctx object (cf. edit 2)
Collateral problem n°2: Once I figure out the actual problem, is it feasible to address it dynamically (basically some callback function for index/fetchAll routes, in which the model is a variable, on which I'd call the appropriate count() method, and finally append the result to my response header)? I'm not asking for the code here, just if you think it's feasible and/or know a more elegant way.
Thank you for reading through and excuse me if it was confuse; I wasn't sure which infos would be relevant, so I thought the more the better.
/edit1: forgot to mention, in my controller I also tried to log strapi.plugins['users-permissions'].services.user object to see if it actually has a count() method but got no luck with that either. Also tried the original snippet (Step 3 of aforementioned README), but failed as expected as afaik I don't see the User model being imported anywhere (the only import in User.js being lodash)
/edit2: About the logs, my bad, I just misunderstood the documentation. I now do:
ctx.send(data);
strapi.log.info('ctx should be : ', {ctx});
strapi.log.info('ctx.req = ', {...ctx.req});
strapi.log.info('ctx.res = ', {...ctx.res});
strapi.log.info('ctx.request = ', {...ctx.request});
ctrapi.log.info('ctx.response = ', {...ctx.response});
Ctx logs this way; also it seems that it needs the spread operator to display nested objects ({ctx.req} crash the server, {...ctx.req} is okay). Cool, because it narrows the question to what's interesting.
/edit3: As expected, having logs helps big time. I've managed to display my users (although in the dirty way). Couldn't find any count() method, but watching the data object that is passed to ctx.send(), it's equivalent to your typical 'res.data' i.e a pure JSON with my user list. So a simple .length did the trick:
let data = await strapi.plugins['users-permissions'].services.user.fetchAll(ctx.query);
data.reduce((acc, user) => {
acc.push(_.omit(user.toJSON ? user.toJSON() : user, ['password', 'resetPasswordToken']));
return acc;
}, []);
ctx.set('Content-Range', data.length) // <-- it did the trick
// Send 200 `ok`
ctx.send(data);
Now starting to work on the hard part: the dynamic callback function that will do that for any index/fetchAll call. Will update once I figure it out
I'm using React Admin and Strapi together and installed ra-strapi-provider.
A little boring to paste Content-Range header into all of my controllers, so I searched for a better solution. Then I've found middleware concept and created one that fits my needs. It's probably not the best solution, but do its job well:
const _ = require("lodash");
module.exports = strapi => {
return {
// can also be async
initialize() {
strapi.app.use(async (ctx, next) => {
await next();
if (_.isArray(ctx.response.body))
ctx.set("Content-Range", ctx.response.body.length);
});
}
};
};
I hope it helps
For people still landing on this page:
Strapi has been updated from #alpha to #beta. Care, as some of the code in my OP is no longer valid; also some of their documentation is not up to date.
I failed to find a "clever" way to solve this problem; in the end I copy/pasted the ctx.set('Content-Range', data.length) bit in all relevant controllers and it just worked.
If somebody comes with a clever solution for that problem I'll happily accept his answer. With the current Strapi version I don't think it's doable with policies or lifecycle callbacks.
The "quick & easy fix" is still to customize each relevant Strapi controller.
With strapi#beta you don't have direct access to controller's code: you'll first need to "rewrite" one with the help of this doc. Then add the ctx.set('Content-Range', data.length) bit. Test it properly with RA, so for the other controllers, you'll just have to create the folder, name the file, copy/paste your code + "Search & Replace" on model name.
The "longer & cleaner fix" would be to dive into the react-admin source code and refactorize so the lack of "Content-Range" header doesn't break pagination.
You'll now have to maintain your own react-admin fork, so make sure you're already committed into this library and have A LOT of tables to manage through it (so much that customizing every Strapi controller will be too tedious).
Before forking RA, please remember all the stuff you can do with the Strapi backoffice alone (including embedding your custom React app into it) and ensure it will be worth the trouble.

How to mention the author of a message in another message

I am using repl.it to develop a bot. I am trying to make a command that makes the bot behave like this:
Someone: !slap #someoneelse
Bot: #Someone slapped #someoneelse
How can I get the bot to mention #someone without using ID? Multiple people will use the command and I can't just use ID since it will only work with one person. I haven't found anything that helped me, and the documentation was no help either. Hopefully, I can get help! Thank you.
Users and members have a .toString() method that is automatically called every time they are concatenated with a string: that means that if you type "Hey " + message.author you will get "Hey #author"
That's how I would do the command:
// First store the mentioned user (it will be undefiend if there's none, check that)
let mentionedUser = message.mentions.users.first();
// Reply by directly putting the User objects in the string:
message.channel.send(`${message.author} slapped ${mentionedUser}`);

wit.ai runActions how to handle context in follow-up message

I'm using node-wit to develop a chatbot application.
This is working fine mostly, but I've run into a problem with the use of the context.
I'm using the runActions api :
this.witClient.runActions(customer._key, messageText, witContext).then((newContext => {}
)).catch(reject);
I have defined a number of actions, which set the context.
This is working fine, as long the context is taking place over one message.
For example, if I were to call an action called addProduct :
addProduct({sessionId, context, text, entities}) {
return new Promise((resolve, reject) => {
context.product = `myNewProduct';
resolve(context);
});
},
It will then show a message using the 'product' context key.
However, when I try to use it over 2 messages, it seems to have lost the context ( for example, when asking a multiple choice question, and then handling that response ).
If I understand how it's working correctly, then node-wit doesn't keep the context beyond messages ( I assumed this at first because I'm passing a session key ).
A solution I see is to store the resulting context ( newContext in this case) in a session/user specific way, and then restore it and pass it again when the user is sending his new message.
Meaning, something like this :
witContext = getContextFromSession();
this.witClient.runActions(customer._key, messageText, witContext).then((newContext => { setContextInSession(newContext) }
)).catch(reject);
Would this be the correct way of handling it ?
Off course you have to store your context state, you decide how to store it. But, take into account what is the most efficient way if you're gonna have a lot of users, and your reasources available.
As you can see in the official example for nodeJs, there's a method named findOrCreateSession on https://github.com/wit-ai/node-wit/blob/master/examples/messenger.js they get the session before the wit actions are called.
In my particular case, I am storing it in the database, so I get the session before the action is called, so I can send the context, then in the actions I query the session again to modify the resulting context and store it again, try the best implementation for your needs.

Resources