Finding multiple whole words with .split and .includes with Javascript - javascript

I'm using javascript and html to develop a simple chatbot. The code below works and uses .split to check for a whole word, however, this means that anything entered longer than one word such as "how are you?" no longer works. How can change this so that it'll allow multiple words but still check for whole words like "hi" so that they aren't picked up in bigger words such as "high" etc.
Any help would be much appreciated! Thanks!
var know = {
<!--General Phrases-->
"Hi": "Hello! &#128075",
"Hey": "Hello! &#128075",
"Hello":"Hello &#128075 How can I help?",
"how are you":"Not bad, thanks!",
"Bye":"Have a nice day!",
"Goodbye":"See you later!",
<!--Directory-->
"Help": `You can find help by searching below or by clicking here`,
"contact": `You can contact us by clicking here`,
"About": `You can find our About Us page by clicking here`
};
function goo() {
var userBox = document.getElementById('userBox');
var userInput = userBox.value;
var chatLog = document.getElementById('chatLog');
var chatLogContent = "";
if (!userInput) {
chatLogContent = ''
}
var hasKeyword = false;
for (var key in know) {
if (userInput.toLowerCase()
.replace(/[.,\/#!$%\^&\*;:{}=\-_`~ ()]/g,"")
.split(/\s+/)
.includes(key.toLowerCase())) {
hasKeyword = true;
break;
} else {
hasKeyword = false;
}
}
if (hasKeyword) {
chatLogContent += know[key] + "<br>"; //or use know.key
} else {
chatLogContent += "No results found. Please enter another search term below.<br>";
}
var server = document.createElement('div');
server.setAttribute('class', 'server');
server.innerHTML = chatLogContent;
document.getElementById('chatLog').innerHTML = '';
chatLog.appendChild(server);
}

You can use the regex:
var yourText = "how are you";
var youKeyRegexEscaped = "how are you"
yourText.match(new RegExp('(^|\\s)'+youKeyRegexEscaped+'(\\s|$)'))
Rexgex explanation:
(^|\s) -> begining of the string or space
(\s|$) -> space or end of string
To escape the key, just look at Is there a RegExp.escape function in JavaScript?

Related

Can you help me to simplify a JavaScript code in a few lines?

I have a JavaScript code that gets the lyrics of a song that is currently playing through an API.
Sometimes (not always) the lyric returns the title at the beginning which is what I want to remove.
Sometimes the title at the beginning is in uppercase, other times in uppercase and lowercase.
Example:
SWEET CHILD O' MINE
She's got a smile that it seems to me
Reminds me of childhood memories
Where everything was as fresh as the bright blue sky
Now and then when I see her face
She takes me away to that special place
And if I stare too long, I'd probably break down and cry
........
I have created a somewhat cumbersome code that removes the title from the beginning with success.
I want to see if they help me simplify that code in fewer lines.
I will share the part of the code that interests us to facilitate the help, if you want all the code I have no problem sharing it.
currentSong contains the title of the song that is playing
lyric contains full lyric obtained with the API
this.refreshLyric = function(currentSong, currentArtist) {
//another code that does not interest
//...
//...
//lyric variable contains the complete lyrics of a song obtained through an API
var lyric = data.mus[0].text;
//divide the string CurrentSong (contains the song title) into parts
let splitCurrenSong = currentSong.split(' ');
//I get the length of the array
let largeCurrentSong = splitCurrenSong.length;
//divide the string lyric into parts
let splitLyric = lyric.split(' ');
//I get the first elements of the lyric array with the length limit of largeCurrentSong
let pieceLyric = splitLyric.slice(0, largeCurrentSong);
//I get all elements of the splitCurrenSong array
let pieceSong = splitCurrenSong.slice(0, largeCurrentSong);
//join arrays
let joinLyric = pieceLyric.join(' ');
let joinSong = pieceSong.join(' ');
//I check if the chunk of the joinLyric string matches the same chunk of joinSong
if (joinLyric.toLocaleLowerCase() == joinSong.toLocaleLowerCase()) {
//remove the matching items
splitLyric.splice(0, largeCurrentSong);
//put the resulting join array into a variable
lyrics = splitLyric.join(' ');
//remove the spaces from the beginning and end of lyrics
lyric = lyrics.trim()
}
//another code that does not interest
//...
//...
}
EDIT: to reply to #iamaword
As shown in the screenshot of the API return:
text: contains the lyrics of the complete song
name: song title
I can perfectly get the name of the song with this line:
var nameSong = data.mus[0].name
But I don't think it's necessary since I get the name of the song from the currentSong variable, which is the one sent in the GET command to get the lyric.
FINAL EDIT: credits to #CerebralFart
full code:
this.refreshLyric = function(currentSong, currentArtist) {
var proxy_URL = PROXYURL;
var vagalume_api = 'https://api.vagalume.com.br/search.php?';
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4) {
if (this.status === 200) {
var data = JSON.parse(this.responseText);
if (data.type === 'exact' || data.type === 'aprox') {
var lyric = normalizeText(data);
document.getElementById('lyric').innerHTML = lyric.replace(/\n/g, '<br />');
var openLyric = document.getElementsByClassName('lyrics')[0];
openLyric.style.opacity = "1";
openLyric.setAttribute('data-toggle', 'modal');
var powered = "Vagalume"
var URL_lyric = 'https://www.vagalume.com.br';
//Powered by image src...
const parent = document.querySelector('.chartlyrics');
parent.innerHTML = '';
var img = document.createElement("img");
img.src = "img/103-fundo-escuro.jpg"
img.setAttribute('class', "")
parent.appendChild(img);
parent.appendChild(document.createElement('br'));
parent.append('Powered by ');
// Powered by link a href...
document.getElementById('powered_by').innerHTML = ''
var a = document.getElementById('powered_by')
.appendChild(document.createElement("a"));
a.href = URL_lyric;
a.target = "_blank";
a.rel = "noopener noreferrer";
a.textContent = powered;
} else {
var page = new Page();
page.refreshLyric2(currentSong, currentArtist);
}
} else {
var page = new Page();
page.refreshLyric2(currentSong, currentArtist);
}
}
}
xhttp.open('GET', proxy_URL + vagalume_api + API_KEY + '&art=' + currentArtist + '&mus=' + currentSong.toLowerCase(), true);
xhttp.send()
}
function normalizeText(response){
// First unpack the data, get the right name and text values
let {mus:[{name, text}]} = response;
// Now get the part of the text that might be the title
let titleLength = name.length;
let maybeTitle = text.substring(0, titleLength);
// Compare the two titles and trim if they match
if (name.toLowerCase() === maybeTitle.toLowerCase() && exceptions.includes(maybeTitle.toLowerCase()) == false){
text = text.substring(titleLength)
}
//Remove any leading or trailing whitespace and return
return text.trim();
}
//song names excepted from being removed in lowercase ['one song', 'two song', etc..]
const exceptions = ['sweet emotion'];
I have created a list of song names excepted from being removed, in case any of them mention the song title as part of the lyric.
This is the case for example of the well-known Aerosmith - Sweet Emotion song.
Sweet emotion
Sweet emotion
You talk about things and nobody cares
You're wearing other things that nobody wears
You're calling my name but you gotta make clear
I can't say baby where I'll be in a year
.....
I added a new condition to the normalizeText function to check if the name of the song to be removed is not within the exceptions.
// Compare the two titles and trim if they match
if (name.toLowerCase() === maybeTitle.toLowerCase() && exceptions.includes(maybeTitle.toLowerCase()) == false){
And I created a constant exceptions where the name of the songs in lowercase must be added manually separated by commas.
//song names excepted from being removed in lowercase ['one song', 'two song', etc..]
const exceptions = ['sweet emotion'];
There are a few ways in which your code can be cleaned up, mainly in how you unpack the data and in how you compare the two strings.
function normalizeText(response){
// First unpack the data, get the right name and text values
let {mus:[{name, text}]} = response;
// Now get the part of the text that might be the title
let titleLength = name.length;
let maybeTitle = text.substring(0, titleLength);
// Compare the two titles and trim if they match
if (name.toLowerCase() === maybeTitle.toLowerCase()){
text = text.substring(titleLength)
}
//Remove any leading or trailing whitespace and return
return text.trim();
}
EDIT: sintax error toLowerCase added ()

Passing a string through an array looking for regEx

I'm currently making a chatbox in JQuery. I've been using indexOf but I think it might be more efficient to use regExp.
my current code is
function ai(message){
if (username.length<3){
username = message;
send_message("Nice, to meet you " + username + ", how are you doing?");
}
if(message.indexOf("how are you?")>=0) {
send_message("I'm feeling great!");
}
if(message.indexOf("weather")>=0 ){
send_message("In England it is shitty");
}
var n = message.search(/\b(cat|cats|kitten|feline)\b/i);
if (n !== -1) {
send_message("i hate cats");
}
else {
for (i=0; i <= botChat.length; i++) {
var re = new RegExp (botChat[i][0], 'i');
if (re.test(message)) {
var length = botChat[i].length - 1;
var index = Math.ceil( length * Math.random());
var reply = (botChat[i][index]);
send_message(reply);
}
}
}
}
and a typical line from my array is
new Array ("I need (.*)\." , "Why do you need $1?", "Would it really help you to get $1?" , "Are you sure you need $1?"),
i'm trying to demonstrate the ways of creating a chatbot. The first four responses work perfectly
it takes a name, comments on the weather and can search for cats. What it can't do is perform the loop. Has anyone any suggestions?

Give my javascript array access to includes()

Before everything, I have been making a word filter program for my discord.js bot so excuse me for the bad words!
Since you can't add extra parameters in includes() I decided to make a var line:
var filteredwords = ['asshole', 'fuck']
But now I want to place these words (further more will be added) in the following code line:
if (message.content.includes('asshole'));
So instead of 'asshole' I want to place the array? How can I do that? Since I'm a beginner in JS I could not understand the other topics with a similar question. It would be fine if you explain it in noob language. :)
If useful, this is my full code:
const Discord = require('discord.js');
const client = new Discord.Client();
var filteredwords = ['asshole', 'fuck']
function commandIs(str, msg) {
return msg.content.toLowerCase().startsWith('--' + str);
}
client.on('ready', () => {
console.log('The bot is started succesfully')
});
client.on('message', message => {
if (commandIs('trump', message)) {
message.reply('He is the president of the United States of America!');
}
if (commandIs('putin', message)) {
message.reply('He is the president of Russia!');
}
if (commandIs('spacetaco', message)) {
message.reply('He is the first user who joined Arcanews!');
}
if (message.content.includes('asshole')); {
message.reply('Do not swear please');
message.delete();
var colors = require('colors/safe');
console.log(colors.red(`The following message got deleted:
${message.content}`));
}
});
if(filterwords.some(badword=>message.content.includes(badword))){
alert("BAD!");
}
Array.prototype.some iterates over the array and returns true if one of the given function called with the array elem as argument is true, therefore if it contains at least one bad word...
Because of the "includes" method you used, I think the type of "message.content" is an array.
Therefore, the problem can be regard as compare two arrays. You can simply apply two loops for checking, or you can use reduce method for checking.
var messageA = {
content: "Here is a asshole in it.".split(" "),
};
var messageB = {
content: "Here is a hole in it.".split(" "),
};
var filteredwords = ['asshole', 'fuck'];
var isValidA = messageA.content.reduce(function(pre, cur) {
if (pre) return !!filteredwords.indexOf(cur);
return pre;
}, true);
var isValidB = messageB.content.reduce(function(pre, cur) {
if (pre) return !!filteredwords.indexOf(cur);
return pre;
}, true);
console.log(isValidA); // false
console.log(isValidB); // true
You can use regex matching here. A much faster implementation
var filteredwords = ['bad-word', 'another-bad-word']
var message = 'Hello this string has a bad-word in it'; // Use your messsage.content here
// In a regex - '\b' start or end of a word
// " .join('\\b|\\b') " - gives me a regex string with each of the values from filteredwords array concatinated to for a 'OR' expression
// Here is th regex exp taht is generated `\basshole\b|\bfuck\b`
if( (new RegExp( '\\b' + filteredwords.join('\\b|\\b') + '\\b') ).test(message) ){
alert('match'); // Here the word were matched
}else{
alert('no match'); // Wooho, a safe message
}
Pro tip :
RegEx solution stands out in a way that you do the match that are case-insensitive and for bad-words that appear as a part of another word eg 'dambad-Word' would give a match for bad-word
EDIT: Updating answer to the full-code posted by const Discord = require('discord.js');
const client = new Discord.Client();
var filteredwords = ['asshole', 'fuck']
function commandIs(str, msg) {
return msg.content.toLowerCase().startsWith('--' + str);
}
client.on('ready', () => {
console.log('The bot is started succesfully')
});
client.on('message', message => {
if (commandIs('trump', message)) {
message.reply('He is the president of the United States of America!');
}
if (commandIs('putin', message)) {
message.reply('He is the president of Russia!');
}
if (commandIs('spacetaco', message)) {
message.reply('He is the first user who joined Arcanews!');
}
if ( (new RegExp('\\b' + filteredwords.join('\\b|\\b') + '\\b')).test(message.content ) ) {
message.reply('Do not swear please');
message.delete();
var colors = require('colors/safe');
console.log(colors.red(`The following message got deleted:${message.content}`));
}
});
If you want to do the matching for not whole words like say curseWord should be detected in the sentence hello there youcurseword (case insensitive as well), you can replace the last IF condition with :
// We can get rid of '\b' to make the search not limited to whole-word matching
if ((new RegExp(filteredwords.join('|'))).test(message.content))
// We can use 'i' flag to make the matching case insensitive
if ((new RegExp(filteredwords.join('|') , 'i')).test(message.content))

Add URL to specific word across entire website

I want to be able to link any word of my choice to a specific URL for example:
I want the word "goat" to link to "http://goat.com" across the entire website. So all "goat"/s will link to that URL right across the website.
I am using wordpress and I have not yet found a plugin to do this. If I can get a solution to this I would most likely create a plugin for this functionality.
I know how to target one word on a single page. But I would like it to be across all the pages and all the words in those pages( I used JavaScript for this).
Something like this may work for you.
function replaceWithUri(textToReplace, element){
element.innerHTML = element.innerHTML.replace(textToReplace, '<a href="http://www.' + textToReplace + '.com" >' + textToReplace + '</a>');
}
replaceWithUri('goat', document.getElementsByTagName('body')[0]);
Here's a crappy solution but it's better than nothing:
I found some code here which searches for a world across the whole page so I copy pasted that and modified it.
The replaceWord variable cannot contain the same string as word, otherwise it'll loop infinitely.
var word = " goat",
replaceWord = " <a href = 'http://goat.com'>goat</a>",
queue = [document.body],
curr
;
while (curr = queue.pop()) {
if (!curr.textContent.match(word)) continue;
for (var i = 0; i < curr.childNodes.length; ++i) {
switch (curr.childNodes[i].nodeType) {
case Node.TEXT_NODE : // 3
if (curr.childNodes[i].textContent.match(word)) {
curr.innerHTML = curr.innerHTML.replace(word,replaceWord);
}
break;
case Node.ELEMENT_NODE : // 1
queue.push(curr.childNodes[i]);
break;
}
}
}
Hello goat
<div>Look a goat</div>
This might be a bit resource intensive and replaceWord cannot contain the same string as word, otherwise it'll loop forever.
document.onload = function() {
var word = " goat",
replaceWord = " <a href = 'http://goat.com'>goat</a>";
while(document.body.innerHTML.indexOf(word) !== -1) {
document.body.innerHTML = document.body.innerHTML.replace(word,replaceWord);
}
}
Hello goat
<div>Look a goat</div>

Is it possible to highlight all words on a web page without destroying the layout?

I've written an extension for firefox which highlights all words on a web page (excluding some words in a given list).
What i've noticed is that (besides that my extension is terribly slow) some web pages get "destroyed", more specifically the layout gets destroyed (particularly websites with overlay advertising or fancy drop-down menus).
My code wraps <span> tags around every "word", or to be precise around every token, because i'm splitting the text nodes with a whitespace as seperator.
So is it possible anyway to realize this task without destroying the page's layout?
I'm iterating over all text nodes, split them, and iterate over every token.
When the token is in my list, i don't highlight it, else i wrap the <span> tag around it.
So any suggestions how this could be done faster would be helpful, too.
Here are some screenshots for a correctly highlighted and a not correctly highlighted web page:
right:
en.wikipedia.org before highlighting,
en.wikipedia.org after highlighting.
wrong:
developer.mozilla.org before highlighting,
developer.mozilla.org after highlighting.
OK. Study this code. It searches for all instances of "is" and highlights if it is not surrounded by word characters. Put this in your scratchpad while this tab is focused. You will see that words like "List" and other words containing "Is" are no highlighted, but all the "Is"'s are.
I basically made an addon here for you. You can now release this as an addon called RegEx FindBar and take all the credit....
var doc = gBrowser.contentDocument;
var ctrler = _getSelectionController(doc.defaultView);
var searchRange = doc.createRange();
searchRange.selectNodeContents(doc.documentElement);
let startPt = searchRange.cloneRange();
startPt.collapse(true);
let endPt = searchRange.cloneRange();
endPt.collapse(false);
let retRane = null;
let finder = Cc["#mozilla.org/embedcomp/rangefind;1"].createInstance().QueryInterface(Ci.nsIFind);
finder.caseSensitive = false;
var i = 0;
while (retRange = finder.Find('is', searchRange, startPt, endPt)) {
i++;
var stCont = retRange.startContainer;
var endCont = retRange.endContainer;
console.log('retRange(' + i + ') = ', retRange);
console.log('var txt = retRange.commonAncestorContainer.data',retRange.commonAncestorContainer.data);
//now test if one posiion before startOffset and one position after endOffset are WORD characters
var isOneCharBeforeStCharWordChar; //var that holds if the character before the start character is a word character
if (retRange.startOffset == 0) {
//no characters befor this characte so obviously not a word char
isOneCharBeforeStCharWordChar = false;
} else {
var oneCharBeforeStChar = stCont.data.substr(retRange.startOffset-1,1);
if (/\w/.test(oneCharBeforeStChar)) {
isOneCharBeforeStCharWordChar = true;
} else {
isOneCharBeforeStCharWordChar = false;
}
console.log('oneCharBeforeStChar',oneCharBeforeStChar);
}
var isOneCharAfterEndCharWordChar; //var that holds if the character before the start character is a word character
if (retRange.endOffset == endCont.length - 1) {
//no characters after this characte so obviously not a word char
isOneCharAfterEndCharWordChar = false;
} else {
var oneCharAferEndChar = endCont.data.substr(retRange.endOffset,1); //no need to subtract 1 from endOffset, it takes into account substr 2nd arg is length and is treated like length I THINK
if (/\w/.test(oneCharAferEndChar)) {
isOneCharAfterEndCharWordChar = true;
} else {
isOneCharAfterEndCharWordChar = false;
}
console.log('oneCharAferEndChar',oneCharAferEndChar);
}
if (isOneCharBeforeStCharWordChar == false && isOneCharAfterEndCharWordChar == false) {
//highlight it as surrounding characters are no word characters
_highlightRange(retRange, ctrler);
console.log('highlighted it as it was not surrounded by word charactes');
} else {
console.log('NOT hilte it as it was not surrounded by word charactes');
}
//break;
startPt = retRange.cloneRange();
startPt.collapse(false);
}
/*********************/
function _getEditableNode(aNode) {
while (aNode) {
if (aNode instanceof Ci.nsIDOMNSEditableElement)
return aNode.editor ? aNode : null;
aNode = aNode.parentNode;
}
return null;
}
function _highlightRange(aRange, aController) {
let node = aRange.startContainer;
let controller = aController;
let editableNode = this._getEditableNode(node);
if (editableNode)
controller = editableNode.editor.selectionController;
let findSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
findSelection.addRange(aRange);
if (editableNode) {
// Highlighting added, so cache this editor, and hook up listeners
// to ensure we deal properly with edits within the highlighting
if (!this._editors) {
this._editors = [];
this._stateListeners = [];
}
let existingIndex = this._editors.indexOf(editableNode.editor);
if (existingIndex == -1) {
let x = this._editors.length;
this._editors[x] = editableNode.editor;
this._stateListeners[x] = this._createStateListener();
this._editors[x].addEditActionListener(this);
this._editors[x].addDocumentStateListener(this._stateListeners[x]);
}
}
}
function _getSelectionController(aWindow) {
// display: none iframes don't have a selection controller, see bug 493658
if (!aWindow.innerWidth || !aWindow.innerHeight)
return null;
// Yuck. See bug 138068.
let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsISelectionDisplay)
.QueryInterface(Ci.nsISelectionController);
return controller;
}
Oh edit my solution out, will update with proper solution, I see you want to highlight all words
This is the code how firefox highlights stuff without changing document: Finder.jsm - _highlight function. You will have to copy this and use it for the whole document, if you need help let me know and I'll do it.
Here was my solution to highlight all matches of single word: https://stackoverflow.com/a/22206366/1828637
Here man this is how you are going to highlight the whole document, I didn't finish the snippet but this is the start of it: Gist - HighlightTextInDocument
Here's the copy paste answer to highlight everything in the document. As you learn more about it share with us, like how you can highlight with a different color, right now its all pink O_O
function _getEditableNode(aNode) {
while (aNode) {
if (aNode instanceof Ci.nsIDOMNSEditableElement)
return aNode.editor ? aNode : null;
aNode = aNode.parentNode;
}
return null;
}
function _highlightRange(aRange, aController) {
let node = aRange.startContainer;
let controller = aController;
let editableNode = this._getEditableNode(node);
if (editableNode)
controller = editableNode.editor.selectionController;
let findSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
findSelection.addRange(aRange);
if (editableNode) {
// Highlighting added, so cache this editor, and hook up listeners
// to ensure we deal properly with edits within the highlighting
if (!this._editors) {
this._editors = [];
this._stateListeners = [];
}
let existingIndex = this._editors.indexOf(editableNode.editor);
if (existingIndex == -1) {
let x = this._editors.length;
this._editors[x] = editableNode.editor;
this._stateListeners[x] = this._createStateListener();
this._editors[x].addEditActionListener(this);
this._editors[x].addDocumentStateListener(this._stateListeners[x]);
}
}
}
function _getSelectionController(aWindow) {
// display: none iframes don't have a selection controller, see bug 493658
if (!aWindow.innerWidth || !aWindow.innerHeight)
return null;
// Yuck. See bug 138068.
let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsISelectionDisplay)
.QueryInterface(Ci.nsISelectionController);
return controller;
}
var doc = gBrowser.contentDocument;
var searchRange = doc.createRange();
searchRange.selectNodeContents(doc.documentElement);
_highlightRange(searchRange,_getSelectionController(gBrowser.contentWindow))
#jervis, I can't make a comment on your comment under #Noitidart code as I don't have 50rep yet. So I have to post here.
Re:
I did it with 'gFindBar._highlightDoc(true, word)' now. I'm using firefox 17, so i dont know if gFindBar is state of the art. – jervis 40 mins ago
But I tested his code and and it works.
Don't use gFindBar.
Copy it and then paste it into your Scratchpad.
Why are you using gFindBar._highlightDoc(true, word) ? I thoght you wanted to highlight everything in the document? Where did you get _highlightDoc from? I don't see that anywhere in #Noitidart's code.
Regading yoru comment on iterate all words and use gFindBar._highlightDoc:
I did it with 'gFindBar._highlightDoc(true, word)' now. I'm using firefox 17, so i dont know if gFindBar is state of the art. – jervis 39 mins ago
Dude why do that.... I saw #Noitidart posted a per word solution on the linked topic: gBrowser.tabContainer.childNodes[0].linkedBrowser.finder.highlight(true, 'YOUR_WORD_HERE'); that is extremely easy, one line and no need to create text nodes spans or anything. You have to run this code on each tab you want to highlight in.

Categories