I have the following problem, I have a function that detects when a message has arrived, and when the message contains the prefix "#" for example "Hello I mean #LOL" it only gets the "LOL". And it works very well indeed, but now I want it to be able to detect not only the "#" prefix but also the "$" prefix for example $ MONEY. The first thing that occurred to me was to literally copy the entire function and declare for example prefixD = "$". The problem is that I have a lot of repeated / duplicated code, and I think it is not good to do it like that, what would be the correct way to do it? I leave my code here (Which works, but it has a lot of duplicate code)
client.on("message", function consigue(msg) {
const prefix = "#";
if (!msg.content.includes(prefix)) return;
const pattern = new RegExp(prefix + "([a-z]+)", "i");
const getMatch = (str) => str.match(pattern)?.[1];
TRADE_OUT = getMatch(msg.content);
if (TRADE_OUT != "") {
// some here
}
});
client.on("message", function consigueD(msg) {
const prefixD = "$";
if (!msg.content.includes(prefixD)) return;
function escapeRegex(string) {
return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}
const pattern = new RegExp(escapeRegex(prefixD) + '([a-z]+)', 'i');
const getMatch = (str) => str.match(pattern)?.[1];
TRADE_OUT = getMatch(msg.content);
if (TRADE_OUT != "") {
// The same some here
}
});
For example I would like to replace that
if (TRADE_OUT != "") {
}
which is repeated in the two functions and is exactly the same code , and everything it has inside as it is done with the functions in the modules, which are declared only as name (); and they already execute all the code they have inside, and you can use it as many times as you want.
What would be the best way to do everything in the same function? I have tried with || , and in many other ways but the truth is I'm not very good with this topic
You could make a factory.
let handlerFactory = flag => {
return msg => {
const prefix = flag
...
}
}
Then you would do
client.on("message", handlerFactory("X"))
where X is either "$" or "#".
EDIT:
If you meant "just the part within the TRADE_OUT-bit is a duplicate" then just take that, put it in a function declared before the handlers and call that from within the TRADE_OUT-bit.
Related
Please excuse how utterly "noobish" I am, I'm trying to learn as I go along but I'm very new.
I have the below code which I'm trying to use for a Discord bot. For the most part it works, however the # "ping" simply returns "#undefined" as opposed to the values I've set in the consts.
Would anyone be so kind as to point me in the right direction on this?
const ping = {
roleID: function() {
return this.role;
}
}
const John = {
role:"abc"
}
const Mary = {
role:"123"
}
function pingSubbed() {
let pingID = message.author.username;
if (pingID == "John") {
ping.roleID.call(John);
}
if (pingID == "Mary") {
ping.roleID.call(Mary);
}
}
yield hook.send(`**${message.author.username}**\n` + " " + messageContents + " " + "#"+pingSubbed());
I'm expecting the function pingSubbed() to determine the username of the person who posts in Discord, for example John, reference the above ping.roleID.call(John) and then take the appropriate role (in this case John = abc) and sending this information as a message itself - #123 - as in the last line "#"+pingSubbed()
You might find a look up table simpler to author and maintain:
function pingSubbed() {
let getId = Function.call.bind(ping.roleID);
return {
John: getId(John),
Mary: getId(Mary),
}[message.author.username] || ("Unknown User:"+message.author.username);
}
This puts a lot less boilerplate (even no quotes) in the way of your raw logic.
Even more jedi would be to make an iterable object containing your users instead of free-floating variables (well const(s)). This drastically simplifies the already-simpler look up table method:
const ping = {
roleID: function() {
return this.role;
}
}
const users={
John : {
role:"abc"
},
Mary: {
role:"123"
}
}
function pingSubbed() {
return ping.roleID.call(users[message.author.username]) ||
("Unknown User:"+message.author.username);
}
that gets it to the point of being almost all data with minimal logic and code to worry about...
The call inside your Object is working well you just forget to return the value that you need from your function
function pingSubbed() {
let pingID = message.author.username;
if (pingID == "John") {
ping.roleID.call(John);
}
if (pingID == "Mary") {
ping.roleID.call(Mary);
}
return pingID // add this line
}
When you use this keyword inside an object, it refers to the object itself.
The reason I say language agnostic is that I would like a small, self contained implementation that determines if the outermost scope in a string containing JavaScript is a function or not. I've looked at the MDN for guidance on the possible forms of declaring functions, but unfortunately wasn't able to find any comprehensive examples for all the ways functions can be defined in JS.
Here's a few different test cases the implementation should be able to handle:
// yes
function (){}
//yes
() => p
//yes
((g) => p)
//yes
(g => p)
//no
(()=>p)()).call()
//no
a = function (){
console.log()
{
//no
g=() => p
I've thought about trying to construct a regex to look for this, but I'm not sure I've covered every case, and I haven't found any sort of corpus of example JS functions, or even if a pure regex approach would be the best way to handle this.
You can use Acorn to parse the Javascript. If parsing is successful, and the body is composed only of a single item, and that item is a FunctionDeclaration or ArrowFunctionExpression, the test passes:
const test = (str) => {
console.log(str);
try {
const { body } = acorn.parse(str);
if (body.length > 1) throw new Error();
const [item] = body;
if (item.type === 'FunctionDeclaration' || (item.type === 'ExpressionStatement' && item.expression.type === 'ArrowFunctionExpression')) {
console.log('Pass');
} else {
console.log('invalid');
}
} catch(e) {
console.log('invalid');
}
};
// function (){}
test(`function foo() {}`);
test(`() => p`);
test(`((g) => p)`);
test(`(g => p)`);
test(`(()=>p)()).call()`);
test(`a = function (){
console.log()
{`);
test(`g=() => p`);
<script src="https://cdnjs.cloudflare.com/ajax/libs/acorn/7.1.1/acorn.min.js"></script>
Im making a program that takes some code via parameter, and transform the code adding some console.logs to the code. This is the program:
const escodegen = require('escodegen');
const espree = require('espree');
const estraverse = require('estraverse');
function addLogging(code) {
const ast = espree.parse(code);
estraverse.traverse(ast, {
enter: function(node, parent) {
if (node.type === 'FunctionDeclaration' ||
node.type === 'FunctionExpression') {
addBeforeCode(node);
}
}
});
return escodegen.generate(ast);
}
function addBeforeCode(node) {
const name = node.id ? node.id.name : '<anonymous function>';
const beforeCode = "console.log('Entering " + name + "()');";
const beforeNodes = espree.parse(beforeCode).body;
node.body.body = beforeNodes.concat(node.body.body);
}
So if we pass this code to the function:
console.log(addLogging(`
function foo(a, b) {
var x = 'blah';
var y = (function () {
return 3;
})();
}
foo(1, 'wut', 3);
`));
This is the output of this program:
function foo(a, b) {
console.log('Entering foo()');
var x = 'blah';
var y = function () {
console.log('Entering <anonymous function>()');
return 3;
}();
}
foo(1, 'wut', 3);
And this is the AST (Abstract Syntax Tree) for that last function passed to addLoggin:
https://astexplorer.net/#/gist/b5826862c47dfb7dbb54cec15079b430/latest
So i wanted to add more information to the console logs like for example the line number we are on. As far as i know, in the ast, the node has a value caled 'start' and 'end' which indicates in which character that node starts and where it ends. How can i use this to get the line number we are on? Seems pretty confusing to me to be honest. I was thinking about doing a split of the file by "\n", so that way i have the total line numbers, but then how can i know i which one im on?
Thank you in advance.
Your idea is fine. First find the offsets in the original code where each line starts. Then compare the start index of the node with those collected indexes to determine the line number.
I will assume here that you want the reported line number to refer to the original code, not the code as it is returned by your function.
So from bottom up, make the following changes. First expect the line number as argument to addBeforeCode:
function addBeforeCode(node, lineNum) {
const name = node.id ? node.id.name : '<anonymous function>';
const beforeCode = `console.log("${lineNum}: Entering ${name}()");`;
const beforeNodes = espree.parse(beforeCode).body;
node.body.body = beforeNodes.concat(node.body.body);
}
Define a function to collect the offsets in the original code that correspond to the starts of the lines:
function getLineOffsets(str) {
const regex = /\r?\n/g;
const offsets = [0];
while (regex.exec(str)) offsets.push(regex.lastIndex);
offsets.push(str.length);
return offsets;
}
NB: If you have support for matchAll, then the above can be written a bit more concise.
Then use the above in your main function:
function addLogging(code) {
const lineStarts = getLineOffsets(code); // <---
let lineNum = 0; // <---
const ast = espree.parse(code);
estraverse.traverse(ast, {
enter: function(node, parent) {
if (node.type === 'FunctionDeclaration' ||
node.type === 'FunctionExpression') {
// Look for the corresponding line number in the source code:
while (lineStarts[lineNum] < node.body.body[0].start) lineNum++;
// Actually we now went one line too far, so pass one less:
addBeforeCode(node, lineNum-1);
}
}
});
return escodegen.generate(ast);
}
Unrelated to your question, but be aware that functions can be arrow functions, which have an expression syntax. So they would not have a block, and you would not be able to inject a console.log in the same way. You might want to make your code capable to deal with that, or alternatively, to skip over those.
I am working on a language that transpiles to javascript and has a similar syntax. However I want to include some new type of block statements. For syntax purposes they are the same as an IfStatement. How can I get esprima or acorn to parse this program MyStatement {a=1;} without throwing an error? Its fine if it calls it an IfStatement. I would prefer not to fork esprima.
It turns out, that the plugin capabilities of acorn are not really documented. It seems like forking acorn would be the easiest route. In this case, it is as simple as searching for occurances of _if and following a similar pattern for _MyStatement.
However it is possible to write a plugin to accomplish what I was trying to do. It seems a bit of a hack, but here is the code. The basic steps are:
To exend Parse and add to the list of keywords that will be recognized by the first pass
Create a TokenType for the new keyword and add it to the Parser.acorn.keywordTypes, extend parseStatement so that it processes the new TokenType
Create a handler for the new TokenType which will add information to the Abstract Syntax Tree as required by the keyword functionality and also consume tokens using commands like this.expect(tt.parenR) to eat a '(' or this.parseExpression() to process an entire expression.
Here is the code:
var program =
`
MyStatement {
MyStatement(true) {
MyStatement() {
var a = 1;
}
}
if (1) {
var c = 0;
}
}
`;
const acorn = require("acorn");
const Parser = acorn.Parser;
const tt = acorn.tokTypes; //used to access standard token types like "("
const TokenType = acorn.TokenType; //used to create new types of Tokens.
//add a new keyword to Acorn.
Parser.acorn.keywordTypes["MyStatement"] = new TokenType("MyStatement",{keyword: "MyStatement"});
//const isIdentifierStart = acorn.isIdentifierStart;
function wordsRegexp(words) {
return new RegExp("^(?:" + words.replace(/ /g, "|") + ")$")
}
var bruceware = function(Parser) {
return class extends Parser {
parse(program) {
console.log("hooking parse.");
//it appears it is necessary to add keywords here also.
var newKeywords = "break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this const class extends export import super";
newKeywords += " MyStatement";
this.keywords = wordsRegexp(newKeywords);
return(super.parse(program));
}
parseStatement(context, topLevel, exports) {
var starttype = this.type;
console.log("!!!hooking parseStatement", starttype);
if (starttype == Parser.acorn.keywordTypes["MyStatement"]) {
console.log("Parse MyStatement");
var node = this.startNode();
return this.parseMyStatement(node);
}
else {
return(super.parseStatement(context, topLevel, exports));
}
}
parseMyStatement(node) {
console.log("parse MyStatement");
this.next();
//In my language, MyStatement doesn't have to have a parameter. It could be called as `MyStatement { ... }`
if (this.type == tt.parenL) {
node.test = this.parseOptionalParenExpression();
}
else {
node.test = 0; //If there is no test, just make it 0 for now (note that this may break code generation later).
}
node.isMyStatement = true; //set a flag so we know that this if a "MyStatement" instead of an if statement.
//process the body of the block just like a normal if statement for now.
// allow function declarations in branches, but only in non-strict mode
node.consequent = this.parseStatement("if");
//node.alternate = this.eat(acornTypes["else"]) ? this.parseStatement("if") : null;
return this.finishNode(node, "IfStatement")
};
//In my language, MyStatement, optionally has a parameter. It can also by called as MyStatement() { ... }
parseOptionalParenExpression() {
this.expect(tt.parenL);
//see what type it is
console.log("Type: ", this.type);
//allow it to be blank.
var val = 0; //for now just make the condition 0. Note that this may break code generation later.
if (this.type == tt.parenR) {
this.expect(tt.parenR);
}
else {
val = this.parseExpression();
this.expect(tt.parenR);
}
return val
};
}
}
process.stdout.write('\033c'); //cls
var result2 = Parser.extend(bruceware).parse(program); //attempt to parse
console.log(JSON.stringify(result2,null,' ')); //show the results.
I need to detect and eval the Javascript code contained in a string.
The following code works, but it only evaluates the first <script>...</script> it founds.
function executeJs(html) {
var scriptFragment = "<script(.+?)>(.+?)<\/script>";
match = new RegExp(scriptFragment, "im");
var matches = html.match(match);
if (matches.length >= 2) {
eval(matches[2]);
}
}
I wonder if there is a method that allows me to iterate and execute all Javascript fragments.
The reason it only takes the first one is because you're missing the g flag. Try this:
function executeJs(html) {
var scriptFragment = '<script(.*?)>(.+?)<\/script>';
var re = new RegExp(scriptFragment, 'gim'), match;
while ((match = re.exec(html)) != null) {
eval(match[2]);
}
}
executeJs('<script>alert("hello")</script>abc<script>alert("world")</script>');
Here is some code that does the same thing in a slightly different way. You can pass the string to the function and it will eval all the script tags and return the cleaned source(without script). There is also a slight difference in the way IE handles it, that is handled in the code as well, you may adapt it to your requirements. Also, the evaluated code has the global context. Hope it helps.
function parseScript(_source)
{
var source = _source;
var scripts = new Array();
// Strip out tags
while(source.indexOf("<script") > -1 || source.indexOf("</script") > -1)
{
var s = source.indexOf("<script");
var s_e = source.indexOf(">", s);
var e = source.indexOf("</script", s);
var e_e = source.indexOf(">", e);
// Add to scripts array
scripts.push(source.substring(s_e+1, e));
// Strip from source
source = source.substring(0, s) + source.substring(e_e+1);
}
// Loop through every script collected and eval it
for(var i=0; i<scripts.length; i++)
{
try
{
//eval(scripts[i]);
if(window.execScript)
{
window.execScript(scripts[i]); // IE
}
else
{
window.setTimeout(scripts[i],0); // Changed this from eval() to setTimeout() to get it in Global scope
}
}
catch(ex)
{
// do what you want here when a script fails
alert("Javascript Handler failed interpretation. Even I am wondering why(?)");
}
}
// Return the cleaned source
return source;
}
Blixt should be right...
You may also take a look at prototype's String.evalScripts function.
http://api.prototypejs.org/language/string.html#evalscripts-instance_method