Help for bot twitch writing in excel sheet

Hello everyone. I'm working on my twitch bot.
My goal is to be able to create racing tournaments on my channel.
For that i create a bot twich to help me record participants and their results from commands in chat.
In this case i'm using node-red-contrib-excelsheets to register results on local files.

First i get all twitch display-name (pseudo) and register them in my excel sheet. (this part is workin well)

Then with a command like this one : !€b2 (with b referring to the race performed and which will give us the correct column to write in. And 2 being the place where the spectator finished his race.)
My bot is supposed to check the excel file to find the pseudo of my viewer who write the command. Then in the same ligne write in the right collumn his place. And currently this is not working. So maybe i push it too far in my function. Can someone take a look pls ?

There is no Node-red erreur while i execute this function. It is just not writing in my excel or corrupt the excel file.

My second issue is the switch node. i want to use this expression to let message in this type going throw but right now it is not working... : /(!€[a-z][1-8])/

Capture d'écran 2024-01-22 190335

Thanks in advance.

So this is the code of the function :

let message = msg.payload.message;

// Vérification si le pseudo est vide
if (pseudo.trim() === "") {
    // Continuer la vérification sans effectuer d'action spécifique
} else {
    function assignPlace(message) {
        let place = -1; // Valeur par défaut si aucun chiffre n'est trouvé dans le message
        // Recherche des chiffres de 1 à 8 dans le message
        for (let i = 1; i <= 8; i++) {
            if (message.includes(i.toString())) {
                place = i; // Si le chiffre est trouvé, la valeur est définie sur ce chiffre
                break; // Sortir de la boucle une fois le chiffre trouvé
            }
        }

        return place;
    }
    
    // Utilisation de la fonction pour assigner une place en fonction du message
    let place = assignPlace(message);

    // Nouvelle fonction pour assigner une colonne en fonction de la lettre
    function assignColonne(lettre) {
        let colonne = lettre.charCodeAt(0) - 95; // Convertit la lettre en code ASCII et soustrait 95 pour obtenir le numéro de colonne
        return colonne;
    }

    // Utilisation de la fonction pour assigner une colonne en fonction de la lettre
    let colonne = assignColonne(message.toLowerCase()); // Convertit la lettre en minuscule avant de l'assigner à la colonne

    // Vérifier si msg.payload est un tableau
    if (!Array.isArray(msg.payload)) {
        // Si ce n'est pas le cas, le convertir en un tableau contenant l'élément actuel de msg.payload
        msg.payload = [
            {
                header: {
                    col1: "Pseudos"
                    // ... ajoutez des en-têtes de colonnes vides jusqu'à la colonne correspondant à la lettre
                    // colX: "Lettre" par exemple
                },
                items: [],
                sheetName: "InscriptionPlay"
            }
        ];
    }

    // Recherche du pseudo dans la feuille de calcul et ajout de la place sur la même ligne
    let foundPseudo = false;
    for (let i = 0; i < msg.payload.length; i++) {
        if (msg.payload[i].items[0] && msg.payload[i].items[0].col1 === pseudo) {
            msg.payload[i].items[0]["col" + colonne] = place; // Assigne la place à la colonne correspondant à la lettre
            foundPseudo = true;
            break; // Sortir de la boucle après avoir trouvé le pseudo
        }
    }

    // Si le pseudo n'a pas été trouvé, l'ajouter à la feuille de calcul
    if (!foundPseudo) {
        msg.payload.push({
            header: {
                col1: "Pseudos"
                // ... ajoutez des en-têtes de colonnes vides jusqu'à la colonne correspondant à la lettre
                // colX: "Lettre" par exemple
            },
            items: [
                {
                    col1: pseudo,
                    // ... ajoutez des colonnes vides jusqu'à la colonne correspondant à la lettre "x"
                    ["col" + colonne]: place
                }
            ],
            sheetName: "InscriptionPlay"
        });
    }

    msg.filepath = "thecorrectfilepath.xlsx";

    return msg;
}
type or paste code here

this is an overview of my flow.

Sorry i share the wrong code. You will find the right one here :

let pseudo = msg.payload.userstate["display-name"];
let message = msg.payload.message;

// Vérification si le pseudo est vide
if (pseudo.trim() === "") {
    // Continuer la vérification sans effectuer d'action spécifique
} else {
    function assignPlace(message) {
        let place = -1; // Valeur par défaut si aucun chiffre n'est trouvé dans le message
        // Recherche des chiffres de 1 à 8 dans le message
        for (let i = 1; i <= 8; i++) {
            if (message.includes(i.toString())) {
                place = i; // Si le chiffre est trouvé, la valeur est définie sur ce chiffre
                break; // Sortir de la boucle une fois le chiffre trouvé
            }
        }

        return place;
    }
    
    // Utilisation de la fonction pour assigner une place en fonction du message
    let place = assignPlace(message);

    // Nouvelle fonction pour assigner une colonne en fonction de la lettre
    function assignColonne(lettre) {
        let colonne = lettre.charCodeAt(0) - 95; // Convertit la lettre en code ASCII et soustrait 95 pour obtenir le numéro de colonne
        return colonne;
    }

    // Utilisation de la fonction pour assigner une colonne en fonction de la lettre
    let colonne = assignColonne(message.toLowerCase()); // Convertit la lettre en minuscule avant de l'assigner à la colonne

    // Vérifier si msg.payload est un tableau
    if (!Array.isArray(msg.payload)) {
        // Si ce n'est pas le cas, le convertir en un tableau contenant l'élément actuel de msg.payload
        msg.payload = [
            {
                header: {
                    col1: "Pseudos"
                    // ... ajoutez des en-têtes de colonnes vides jusqu'à la colonne correspondant à la lettre
                    // colX: "Lettre" par exemple
                },
                items: [],
                sheetName: "InscriptionPlay"
            }
        ];
    }

    // Recherche du pseudo dans la feuille de calcul et ajout de la place sur la même ligne
    let foundPseudo = false;
    for (let i = 0; i < msg.payload.length; i++) {
        if (msg.payload[i].items[0] && msg.payload[i].items[0].col1 === pseudo) {
            msg.payload[i].items[0]["col" + colonne] = place; // Assigne la place à la colonne correspondant à la lettre
            foundPseudo = true;
            break; // Sortir de la boucle après avoir trouvé le pseudo
        }
    }

    // Si le pseudo n'a pas été trouvé, l'ajouter à la feuille de calcul
    if (!foundPseudo) {
        msg.payload.push({
            header: {
                col1: "Pseudos"
                // ... ajoutez des en-têtes de colonnes vides jusqu'à la colonne correspondant à la lettre
                // colX: "Lettre" par exemple
            },
            items: [
                {
                    col1: pseudo,
                    // ... ajoutez des colonnes vides jusqu'à la colonne correspondant à la lettre "x"
                    ["col" + colonne]: place
                }
            ],
            sheetName: "InscriptionPlay"
        });
    }

    msg.filepath = "thecorrectfilepath.xlsx";

    return msg;
}

Hi @Kabrut - Welcome to the forums

Sorry, but i'll start with saying I haven't gone down your code in detail - so my apologies if I am missing something critical.

But what I am seeing is excel, files, columns, lower case, array index's and a lot of 'noisy' code.
Have you not considered using an SQLite database?

Granted, you will need to understand SQL if not already , but if you do, any reason you haven't?

Using excel files seem chaotic?

Again, My apology if there is a reason for this approach.

Hello thank you very much!

My choices are due to the fact that I am a beginner in programming and I know nothing about databases.
So I suspect that there is probably some excess in my code. This is one reason why I need an outside opinion.

So I tried to make it "simple" by sending the information to an Excel table.
I chose to send the information to Excel because it is a tool that I know. And to "simply" add values ​​I think it's the simplest for me, if I then want to format my tournament results.

It is ok to ask. :wink:

1 Like

Cool!

And I hope you take it as constructive criticism :smiley:

This is what my approach will be, and then you can build on it.
I should note: I know nothing about twitch - so you will have a much easier time then I (and likely a few of us) :smile:

So...

Note : This is just an idea on how to simply the approach,
and will help you explore JS at the same time. :nerd_face:

You can do a lot of this without using function nodes, but I think the function approach will help you understand Javascript (which is the basis of all the other nodes)

Node RED has a context mechanism, which you can store values/tables etc.

  1. Create the storage area and call it tournament
    This creates on object in global context. Do this only once for each tournament
const myStorageObject = {
      "players": [],
      "races": []
}
global.set("tournament", myStorageObject)
  1. Whenever your bot receives a player result
/* Load our global state */
const tournament = global.get("tournament");

/* Player Name, Race Name, Place */
const playerName = msg.payload.userstate["display-name"]
const someRaceName = msg.payload.userstate["race-name"]
const place = msg.payload.userstate["place"]


/* Add Player */
if (!tournament.players.find((P) => P === playerName)) {
    tournament.players.push(playerName)
}

/* Add Race */
if (!tournament.races.find((R) => R.name === someRaceName)) {
    tournament.races.push({
        "name": someRaceName,
        "players": []
    })
}

/* Get Race */
const race = tournament.races.find((R) => R.name === someRaceName))

/* Only allow players to add their result for each race once! */
if (!race.players.find((P) => P.player === playerName)) {
    race.players.push({
        "player": playerName,
        "place": place
    })
}

once this has filled with data - you can use it to calculate the winners of the tournament so on and so forth.

you can view this tournament object in the context tab in the Editor, and use it for other things - such as creating the final excel sheet after the tournament if you wanted it as a file

I hope this helps you with your project :smile:

1 Like

Thank you really much for this approach !

Effectively, I already used a global context to be able to register the participants and to be able to carry out random draws.

But I hadn't thought of using context to add other information.

Thank you for this point of view, I will try to explore this approach. :grin:

1 Like

Using context will be the database if you will for your bot.

  • Command to return the current rankings in the tournament.
  • Command for each player to be able to check their last race results (or a chosen race they want to lookup) - and total points

etc etc

You know better what is off interest here :smiley: , but using context will be much easier.
But please don't let me interfere with it - its just the way I will go myself

1 Like

For a first bot i think your approach is easier.
For now i want it to work.

But i will think about how to go further too. And what action i want to add :wink:

I really thank you because I knew that, for a first attempt, I had a complex approach and that there was a simpler one.

But when you start down a path it's always difficult to step back. Especially when you're a beginner. :hugs:

1 Like

Hello everyone. Please check this update.
I've been deeper on Marcus'point of view so i am now using the context to do my things. It is simpler but i just have 1 little problem.

I have now this context set up :

const myStorageObject = {
    "players": [],
    "races": [],
    "resultats": []
}
global.set("tournament", myStorageObject)
return msg;
/* Load our global state */
const tournament = global.get("tournament");

/* pseudo */
const pseudo = msg.payload.userstate["display-name"]

/* Add Player */
if (!tournament.players.find((P) => P === pseudo)) {
    tournament.players.push(pseudo)
}

return msg;

So now i have all my players register in my tournament

But my command to add all results and races is not working... someRaceName, place and pseudo are not added to the context.
The message send by the player is like that : "!a2"
where "a" is the name of the race and "2" the result of the player. (the name of the race can be a letter between a and z and the result can be a number between 1 and 8.

I'm using a regular expression but i don't know if it is good or not or if it's something else that is wrong in my code.

/* Load our global state */
const tournament = global.get("tournament");

/* Player Name, Race Name, Place */
const pseudo = msg.payload.userstate["display-name"]
const message = msg.payload.message;

const regexRaceName = /^![a-z].*$/;
const regexPlace = /^![a-z][1-8]$/;

/* Vérifier si le message contient une seule lettre pour someRaceName et un chiffre entre 1 et 8 pour place */
if (regexRaceName.test(message[1]) && regexPlace.test(message[2])) {
    const someRaceName = message[1];
    const place = parseInt(message[2]); // Convertir le chiffre en entier

/* Add Player */
if (!tournament.players.find((P) => P === pseudo)) {
    tournament.players.push(pseudo)
}

/* Add Race */
if (!tournament.races.find((R) => R.name === someRaceName)) {
    tournament.races.push({
        "name": someRaceName,
        "players": []
    })
}

/* Get Race */
const race = tournament.races.find((R) => R.name === someRaceName)

/* Only allow players to add their result for each race once! */
if (!race.players.find((P) => P.player === pseudo)) {
    race.players.push({
        "player": pseudo,
        "place": place
    })
}
}
return msg;

I will continue my investigations. I will update this topic if i found something.
If someone have an idea about what's wrong i really thank you in advance.
have a nice day! :grin:

Hi @Kabrut

Glad I didn't scare you off :wink:

What would help here I think, is a complete example of the payload that you receive and is consumed by this script?

My regex is....... none, but plenty here who are gods at it!
having an example will help a lot - feel free to replace real names of course

EDIT


And just in case, ensure this is only created once (for each tournament)
else it will be cleared/recreated each time a message is received.

const myStorageObject = {
    "players": [],
    "races": [],
    "resultats": []
}
global.set("tournament", myStorageObject)
return msg;

Hi Marcus.
Thank you to be there.

The payload is set up like this for twitch :
image_2024-01-25_211853005

I hope it will help

I already try to change the expression for this :

const regexRaceName = /^"![a-z].*$/;
const regexPlace = /^"![a-z][1-8]"$/;

but nothing too

My regex is also close to the void :sweat_smile:

yes I have already experienced the context which resets with each new pseudo and I have already fixed the problem.:grin:
It is set up on a separate node with a separate command. and it works. I have several registrants who arrive in my context like intended.

1 Like

Ok would this work?

const validMessageRegex = /^i[a-z][1-8]$/
if (validMessageRegex.test(message)) {
   ....Your Code
}

This was in no way suggested by ChatGPT :wink:

EDIT


Also to see where it might be stopping...

add node.warn("Hello, Wold") in places in code - they will appear in the debug pane

yes i found it too. That was like always very "obvious".
the first regex was good.

Now that's the correct code :

if (regexRaceName.test(message) && regexPlace.test(message)) {
    const someRaceName = message[1];
    const place = parseInt(message[2]);

the problem was that i called the full regex to check if it feet only with the second or 3rd character. But of course the code was searching the full 3 character to fit with just 1 so it doesn't work :crazy_face:

but now it's working.

Once again thank you to be there :wink:
You really help !

1 Like

I don't think you need 2 tests.

const validMessageRegex = /^i[a-z][1-8]$/
validMessageRegex.test(message)

will match "iAN"

where A can be a-z and N can be 1-8

a single test should validate the whole format (I think)

but glad your getting there :smiley:

Yes, Effectively, just one check is needed.
Thank you it will simplify a bit the code. :wink: