baconjs: throttle consecutive events with criteria - node.js

I'm coding a messaging app with Node.js and I need to detect when the same user sends N consecutive messages in a group (to avoid spammers). I'm using a bacon.js Bus where I push the incoming messages from all users.
A message looks like this:
{
"text": "My message",
"user": { "id": 1, name: "Pep" }
}
And this is my working code so far:
const Bacon = require('baconjs')
const bus = new Bacon.Bus();
const CONSECUTIVE_MESSAGES = 5;
bus.slidingWindow(CONSECUTIVE_MESSAGES)
.filter((messages) => {
return messages.length === MAX_CONSECUTIVE_MESSAGES &&
_.uniqBy(messages, 'user.id').length === 1;
})
.onValue((messages) => {
console.log(`User ${__.last(messages).user.id}`);
});
// ... on every message
bus.push(message);
It creates a sliding window, to keep only the number on consecutive messages I want to detect. On every event, it filters the array to let the data flow to the next step only if all the messages in the window belong to the same user. Last, in the onValue, it takes the last message to get the user id.
The code looks quite dirty/complex to me:
The filter doesn't look very natural with streams. Is there a better way to emit an event when N consecutive events match some criteria? .
Is there a better way to receive just a single event with the user (instead of an array of messages) in the onValue function.
It doesn't really throttle. If a user sends N messages in one year, he or she shouldn't be detected. The stream should forget old events somehow.
Any ideas to improve it? I'm open to migrating it to rxjs if that helps.

Maybe start with
latestMsgsP = bus.slidingWindow(CONSECUTIVE_MESSAGES)
.map(msgs => msgs.filter(msg => msgAge(msg) < AGE_LIMIT))
See if we should be blockking someone
let blockedUserIdP = latestMsgsP.map(getUserToBlock)
Where you can use something shamelessly imperative such as
function getUserToBlock(msgs) {
if (msgs.length < CONSECUTIVE_MESSAGES) return
let prevUserId;
for (var i = 0; i < msgs.length; i++) {
let userId = msgs[i].user.id
if (prevUserId && prevUserId != userId) return
prevUserId = userId
}
return prevUserId
}

Consider mapping the property you’re interested in as early as possible, then the rest of the stream can be simpler. Also, equality checks on every item in the sliding window won’t scale well as you increase the threshold. Consider using scan instead, so you simply keep a count which resets when the current and previous values don’t match.
bus
.map('.user.id')
.scan([0], ([n, a], b) => [a === b ? n + 1 : 1, b])
.filter(([n]) => n >= MAX_CONSECUTIVE_MESSAGES)
.onValue(([count, userId]) => void console.log(`User ${userId}`));

Related

Dynamic Table updates "too late" ReactJS

my problem is, that I have a table which should update everytime when the user chooses something from a dropdown component. The problem now is that my table updates "too late" in the frontend. So when the user chooses an option for the first time nothing will happen. Then when the user chooses an option for the second time from the dropdown component, the table will show the data from the option he has picked before. If the user chooses an option for the 3rd time, the table will show the data from the second one and so on.
So how can I fix this? I work with ReactJS and Semantic UI
My Code:
This renders the Row for the existing data
renderTableData() {
return this.state.songs.map((song, index) => {
const { id, nr, songname, link } = song
return (
<Table.Row key={id}>
<Table.Cell>{nr}</Table.Cell>
<Table.Cell>{songname}</Table.Cell>
<Table.Cell>{link}</Table.Cell>
</Table.Row>
)
})
}
The Code in the main render() function of React (Its shown correctly, expect that the data is "outdated":
`<Table>
<Table.Header>
<Table.Row>
<Table.HeaderCell width={1}>Nr</Table.HeaderCell>
<Table.HeaderCell width={2}>Songname</Table.HeaderCell>
<Table.HeaderCell width={1}>Link</Table.HeaderCell>
</Table.Row>
</Table.Header>
{this.renderTableData()}
</Table>`
The code when the option from the dropdown gets changed:
onChangeDropdown(e) {
this.setState({game: e.target.textContent}, ()=>{
this.state.songs.length = 0;
for(var i = 0; i< this.state.musicData.length;i++){
if(this.state.musicData[i].game == this.state.game){
for(var j = 0; j<this.state.musicData[i].songs.length;j++){
this.state.songs.push({id: j+1, nr: j+1, songname: this.state.musicData[i].songs[j].name, link: this.state.musicData[i].songs[j].link})
}
break;
}
}
this.renderTableData()
})
}
The game variable in this.setState is correct and also the for-loop works as expected when the user changes the dropdown option, I already checked it with the debugger
I hope you can help me out there, ty
is not that is updating too late, is that you are mutating the state without using setState so React doesn't know what changed, you should refactor your code to always use setState to update the state, not push, something like this:
onChangeDropdown(e) {
this.setState((currentState) => {
const newSongs = [];
const game = e.target.textContent;
musicData.forEach((data) => {
if (data.game === game) {
musicData.songs.forEach((song, index) => {
newSongs.push({
id: index + 1,
nr: index + 1,
songname: song.name,
link: song.link,
});
});
}
});
return {
...currentState,
game,
songs: newSongs,
};
});
}
I changed your for loops to use forEach, less complexity, easier to read
Here is what I did:
create a empty array to store the selected songs (newSongs)
loop all the music data and then loop all the songs inside each item in music data
add the songs from the selected game into newSongs
return newSongs + game to update the selected game, ...currentState is to preserve the other parts of the state between changes
So every time the dropodown changes, I create a new array and run the logic
The setState callback can return an object to replace whole state, so before that you can do any calculation you need to.
Updating the state in React is asyncronous, that's one of the reasons you can't mutate the state directly and need to use setState any time you need to update it

How to use a loop inside message.react() inside async function discord.js

I am making a message that allows the user to target another user via a Discord reaction for a game I'm making compatible with Discord. I am trying to make this work for any number of players without specifying each possible amount of players. I have been using a loop to try and make this work. I just want the bot to add a reaction of 1, 2 and 3 as options for a game that has 3 total players (or users) and display the correct 1, 2 or 3 emoji I have specified in reaction_numbers below. (Those emoji's are just blue squares with the number in them that I know work with Discord reactions)
I get an error of (node:10988) UnhandledPromiseRejectionWarning: TypeError: Emoji must be a string or Emoji/ReactionEmoji
var reaction_numbers = ["\u0030\u20E3","\u0031\u20E3","\u0032\u20E3","\u0033\u20E3","\u0034\u20E3","\u0035\u20E3", "\u0036\u20E3","\u0037\u20E3","\u0038\u20E3","\u0039\u20E3"]
var PlayerListMessage = [] <<< Gets list of players and arranges them
for (let i = 0; i < playerUserArray.length; i++) {
PlayerListMessage.push(`${i+1}: ${playerUserArray[i]}\n`)
}
async function QuestionPlayerToTarget(){
let msg = await message.author.send(`Which player activated the card you would like to negate?\n${PlayerListMessage.join("")}\nPlease select only one player.`)
for (var i of playerUserArray){
await msg.react(reaction_numbers[i+(1)]) <<< Error happens here.
}
const filter = (reaction, user) => {
return [reaction_numbers[1], reaction_numbers[2], reaction_numbers[3], reaction_numbers[4], reaction_numbers[5], reaction_numbers[6]].includes(reaction.emoji.name) && user.id === message.author.id;
};
const reply = await msg.awaitReactions(filter, { max: 1 })
.catch(console.error);
const targetPlayer = reply.first()
return targetPlayer
}
var targetPlayer = await QuestionPlayerToTarget()
console.log(targetPlayer)
Any ideas on how to make this loop add reactions for the exact number of players in the game? Thanks in advance for the help!
Next time please comment with # not <<<
As the error says: Emoji must be a string or Emoji/ReactionEmoji > string or emoji.
How to use msg.react()
So you can either use msg.react("emoji") or msg.react(msg.guild.emojis.get("emojiid")).
The first option is for already existing emojis, like numbers in blue squares.
So in Discord you can put a \ before you post an emoji and you are getting this emoji as string.
The second option is for guild-emojis, emojis, which are only available in this guild.
In your Case
use http://getemoji.com/ and search for "one" and you'll get 1️⃣. You can use this symbol for msg.react("1️⃣").
So just put this in the list reaction_numbers and so on...
I wasn't too far off in my attempts. I ended up changing the loop type from a "for of" loop to a standard for loop and it worked as expected. Code changes as follows.
for (let i = 1; i < playerUserArray.length; i++) {
await msg.react(reaction_numbers[i])
}
var reaction_numbers = ["\u0031\u20E3","\u0032\u20E3","\u0033\u20E3","\u0034\u20E3","\u0035\u20E3", "\u0036\u20E3","\u0037\u20E3","\u0038\u20E3","\u0039\u20E3", "\u0030\u20E3"]
I also took the first value of reaction_numbers and put it at the end of the list in order to make index value 0 = emoji of the number 1 with the blue square.

How to split text depending on word count

I am trying to make a lyric project using discord.js, cheerio and the website called genius.com.
I have successfully found a way to scrape the lyrics from the website, I am onto the part where I need to split it because discord has a max word limit of 2000.
I can check how many characters/words are in the overall lyrics by doing lyrics.length, I just need to find a way to split the string and send both, in the future I might implement richEmbeds to make it more stylish but for now I'm focusing on the basics.
var request = require('request');
var cheerio = require('cheerio');
/*
This is a project for my discord bot, the reason for the 2000 word limit is because
discords character limit is currently set to 2000, this means that i will have to add
a function to split the lyrics and send each part
*/
//Define the URL that we are going to be scraping the data from
var UR_L = "https://genius.com/Josh-a-and-jake-hill-not-afraid-of-dying-lyrics";
//send a request to the website and return the contents of the website
request(UR_L, function(err, resp, body) {
//load the website using cheerio
$ = cheerio.load(body);
//define lyrics as the selector to text form
var lyrics = $('p').text();
if (lyrics.length > "2000" && lyrics.length < "4000") {
} else if (lyrics.length > "4000" && lyrics.length < "6000") {
} else {
//send the lyrics as one message
}
})
You can find a live version running here on repl.it.
You don't need to use any fancy function, that function is already built in discord.js: you can attach some options to a message, and MessageOptions.split is what you're searching for. When you want to send the text, do it like this:
channel.send(lyrics, { split: true });
If lyrics.length is greater that the limit, discord.js will cut your messages and send them one after the other, making it seem like it's only one.
channel is the TextChannel you want to send the messages to.
Discord has a 2000 characters limit not a 2000 words limit.
One solution to your problem could be this:
// This will result in an array with strings of max 2000 length
const lyricsArr = lyrics.match(/.{1,2000}/g);
lyricsArr.forEach(chunk => sendMessage(chunk))
Given the async nature of sending messages, you might want to look into modules like p-iteration to ensure the chunks arrive in the correct order.
That being said, there exists APIs for getting lyrics of songs, which I would recommend instead of scraping. See apiseeds lyrics API as an example.
UPDATE
const lyrics = 'These are my lyrics';
const lyricsArr = lyrics.match(/.{1,8}/g);
console.log(lyricsArr); // [ 'These ar', 'e my lyr', 'ics' ]
lyricsArr.forEach((chunk, i) => {
// Break if this is the last chunk.
if (i == lyricsArr.length -1) {
return;
}
// If last character is not a space, we split a word in two.
// Add additional non-wordbreaking symbols between the slashes (in the regex) if needed.
if (!chunk[chunk.length - 1].match(/[ ,.!]/)) {
const lastWord = chunk.match(/\s([^ .]+)$/)
lyricsArr[i + 1] = lastWord[1] + lyricsArr[i + 1];
lyricsArr[i] = lyricsArr[i].split(/\s[^ .]*$/)[0];
}
})
console.log(lyricsArr) // [ 'These', 'are my', 'lyrics' ]
Updated as per the comments.
This is some crude code that i did not spend much time on, but it does the job.
Some info when using this approach:
You need to add any symbols that should not be considered wordbreaking to the regex in the second if
This has not been tested thoroughly, so use at your own risk.
It will definitely break if you have a word in the lyrics longer than the chunk size. Since this is around 2000, I imagine it will not be problem.
This will no longer ensure that the chunk length is below the limit, so change the limit to around 1900 to be safe
You can use .split( ) Javascript function.
word_list = lyrics.split(" ")
And word_list.length to access the number of words in your message and word_list[0] to select the first word for instance.

Running self-feeding channels in Perl 6

I would like to set up a number of threads operating concurrently on a channel, and every one of those threads should be also feeding the channel. One of the threads would decide when to stop. However, this is the closest I have come to doing that:
use Algorithm::Evolutionary::Simple;
my $length = 32;
my $supplier = Supplier.new;
my $supply = $supplier.Supply;
my $channel-one = $supply.Channel;
my $pairs-supply = $supply.batch( elems => 2 );
my $channel-two = $pairs-supply.Channel;
my $single = start {
react {
whenever $channel-one -> $item {
say "via Channel 1:", max-ones($item);
}
}
}
my $pairs = start {
react {
whenever $channel-two -> #pair {
my #new-chromosome = crossover( #pair[0], #pair[1] );
say "In Channel 2: ", #new-chromosome;
$supplier.emit( #new-chromosome[0]);
$supplier.emit( #new-chromosome[1]);
}
}
}
await (^10).map: -> $r {
start {
sleep $r/100.0;
$supplier.emit( random-chromosome($length) );
}
}
$supplier.done;
This stops after a number of emissions. And it's probably not running concurrently anyway. I am using channels instead of supplies and taps because these are not run concurrently, but asynchronously. I need supplies because I want to have a seudo-channel that takes the elements in pairs, as it's done above; I haven't seen the way of doing that with pure channels.
There is no difference above if I change the supply's emit to channel's send.
So several questions here
Are these react blocks run in different threads? If not, what would be the way of doing that?
Even if they are not, why does it stop even if $pairs is emitting to the channel all the time?
Could I have "batch" channels created automatically from single-item channels?
Update 1: if I eliminate $supplier.done from the end, it will just block. If I create a promise in whenever, one for each read, it just blocks and does nothing.
The answer is here, stripped down to the minimum necessary
my Channel $c .= new;
my Channel $c2 = $c.Supply.batch( elems => 2).Channel;
my Channel $output .= new;
my $count = 0;
$c.send(1) for ^2;
my $more-work = start react whenever $c2 -> #item {
if ( $count++ < 32 ) {
$c.send( #item[1]);
my $sum = sum #item;
$c.send( $sum );
$output.send( $sum );
} else {
$c.close;
}
}
await $more-work;
loop {
if my $item = $output.poll {
$item.say
} else {
$output.close;
}
if $output.closed { last };
}
A second channel that batches the first channel every two elements is used via the creation of a supply from a channel ($c.Supply), batching that supply in batches of two (batch( elems => 2)) and turning it back into a channel. A third channel is created for output.
In order to not exhaust the supply and hang the channel, every second element that is read from the first (and actually, only) channel is put back there. So the second channel that reads in twos is never hanged or waiting for new elements.
An output channel is created for every new element, and an external counter to finish the operation when it's needed; that output channel is read in a non-blocking way, and closed when there's nothing left to read in the last line.
To answer precisely to my original questions:
Yes, they are, only they are stealing elements from each other.
Because the two threads were reading from the same channel. The first one to stumble into an element, reads it.
Yes, by turning channels into supplies, batching them and turning them back into channels. Only bear in mind that they are not copies, they will be sharing the self same elements.

How to bind data to html in angular 2/4 by using thread or async

I have a Component, which have child component. at ngOnInit() I'm calling Web API and get list of data.
Initial point length of the list is 10, But it will have more.
Need to execute some method (task|process|job) in background to take rest of the data 10 by 10 in a loop which would run parallel to other task in background no matter what the user is currently doing, which component he/she is interacting with. And execute that method so that it doesn't block others.
What is the correct way to do this?
Seems like a recursive call to me !
firstResults: any[] = []; // First 10 results to show to your user
results: any[] = []; // All results
currentPosition = 0; // The current position of your last result fetched
getData() {
this.myService.getResults().subscribe(results => {
if(!this.firstResults.length) {
this.firstResults = results
}
this.results.push(...results);
this.currentPosition += results.length;
this.getData();
});
}
I don't imagine you're trying to do. But if the list is not so largger, you can get all the data and "paginate" the array
allData:any[];
page:number=0; //page is 0,1,2,3,4....
paginateData:any[]
this.httpClient.get("url").subscribe(res=>{
allData=res;
paginateData=allData.slice(10*this.page,10*(this.page+1));
}

Resources