Retrieve multiple values from nested data structures within API Javascript - javascript

I reformatted this question to correctly display the issue and show my previous attempts to get my desired results.
Below is an NBA API response from rapid.api This specific response (https://api-nba-v1.p.rapidapi.com/games/live/) spits out the current NBA Games that are live/ongoing right now.
I'm using this API and various responses in other, straightforward ways to retrieve NBA information. My script is designed for Discord.
In this Discord Server I have, we make channels for every NBA game so that users can chat about it. I have been trying to make a score command showing the current game score.
My issue/goal:
I've been struggling to find a way to;
Match the channel name via the nickName of a team (an example of a game channel name would be: lakers-vs-nets), which will allow me to make sure that I get the score for the correct game
Retrieve the score from both the home and away teams
Print the score from both the home and away teams.
I'm unfamiliar with APIs and trying to get better with them, and I have learned more creative ways to use Javascript. So any help and explanations with this issue would be greatly appreciated; thank you.
"api":{
"status":200
"message":"GET games/live"
"results":4
"filters":[
0:"seasonYear"
1:"league"
2:"gameId"
3:"teamId"
4:"date"
5:"live"
]
"games": [
0: {
"seasonYear":"2021"
"league":"standard"
"gameId":"10187"
"startTimeUTC":"2022-01-13T00:30:00.000Z"
"endTimeUTC":""
"arena":"Madison Square Garden"
"city":"New York"
"country":"USA"
"clock":"1:35"
"gameDuration":"2:05"
"currentPeriod":"4/4"
"halftime":"0"
"EndOfPeriod":"0"
"seasonStage":"2"
"statusShortGame":"2"
"statusGame":"In Play"
"vTeam":{
"teamId":"8"
"shortName":"DAL"
"fullName":"Dallas Mavericks"
"nickName":"Mavericks"
"logo":"https://upload.wikimedia.org/wikipedia/fr/thumb/b/b8/Mavericks_de_Dallas_logo.svg/150px-Mavericks_de_Dallas_logo.svg.png"
"score": {
"points":"82"
}
}
"hTeam":{
"teamId":"24"
"shortName":"NYK"
"fullName":"New York Knicks"
"nickName":"Knicks"
"logo":"https://upload.wikimedia.org/wikipedia/fr/d/dc/NY_Knicks_Logo_2011.png"
"score":{
"points":"121"
}
}
1: {
"seasonYear":"2021"
"league":"standard"
"gameId":"10189"
"startTimeUTC":"2022-01-13T02:00:00.000Z"
"endTimeUTC":""
"arena":"Vivint Arena"
"city":"Salt Lake City"
"country":"USA"
"clock":"8:08"
"gameDuration":"1:46"
"currentPeriod":"4/4"
"halftime":"0"
"EndOfPeriod":"0"
"seasonStage":"2"
"statusShortGame":"2"
"statusGame":"In Play"
"vTeam":{...}
"hTeam":{...}
]
}
}
vTeam and hTeam are collapsed here to condense the code but it should give you an idea of the response, as it is nearly identical to the one prior, just different teams, score etc.
Here's some code I have tried so far:
function iterationObject(obj) {
for(prop in obj) {
// If Object
if (typeof(obj[prop]) == "object"){
// Push
iterationObject(obj[prop]);
} else {
/* This only seems to work if I run solely search for the keys and not the values. So for example, this would work:
if (prop == "nickName" || prop == "shortName"){
console.log(prop + ': ', obj[prop])
}
^ This would work and print the values from anything matching nickName and shortName keys (so in this case, it would print every nickName and shortName it found.*/
if (prop == "nickName" && obj[prop] == "Knicks"){
console.log(prop + ': ', obj[prop])
// ^ This is the last thing that I have tried, but has been returning undefined.
}
}
}
}
case 'gamescore':
var gamescoreurl = "https://api-nba-v1.p.rapidapi.com/games/live/";
axios.get(gamescoreurl, {
headers: {
"x-rapidapi-key": apikey,
"x-rapidapi-host": apihost
}
}).then(response=> {
/* Something I tried with .find, only seems to search surface level with the response. What I mean by that is,
I can dive into games API response, but I can't go games.hTeam with .find
var firstchannelpart = message.channel.name
var gamechannelfinder = firstchannelpart.split("-")[0];
var gameapiresp= response.data.api.games
const hscore = gameapiresp.find(el => {
return el.nickName== gamechannelfinder
})
console.log(hscore)
*/
// My latest attempt, which works retrieving specific values that match using prop as a var in iterationObject. Also, the item variable returns as undefined here, but iterationObject will print from the console what it is supposed to.
tidobj.filter(item => {
iterationObject(item)
})})
break;
You can see I put in some comments, those are solely for this thread to help understand my previous attempts, but I feel that one of them might be just on the brink of being right.

With the API Results listed above you could do something like,
// Set up our sample vars
let channelName = "Mavericks-vs-Knicks";
let gamescoreurl = "https://api-nba-v1.p.rapidapi.com/games/live/";
// Make Axios Call
axios.get(gamescoreurl, {
headers: {
"x-rapidapi-key": apikey,
"x-rapidapi-host": apihost
}
}).then(results => {
// get array of team names to pass to 'LogGameScores'
let teamNickNames = GetTeamsFromChannelName(channelName);
// Log the game scores
LogGameScores(teamNickNames, results?.games);
})
This is a simple function to get the team names from the channel name.
function GetTeamsFromChannelName(name = "channelName") {
return name.split("-vs-"); // Split the team Nick Names from the channel name
}
And the a method for getting / logging the scores
function LogGameScores(teams = [], games = null) {
// verify params and log errors found
if (games == null)
return console.log("An error occured while trying to log games scores");
if (teams.length < 2)
return console.log("An error occured while trying to log games scores");
try {
// filter the results to just the game or games representing the teams
// we want from the returned results
let game = games.filter((el) => {
return (
// Check for the game that matches both teams. You are likely able
// to match by only one team name but just in case
// Check for the teams name in the visiting team section
(el.vTeam.nickName.toLowerCase() == teams[0].toLowerCase()
|| el.vTeam.nickName.toLowerCase() == teams[1].toLowerCase())
// Check for the teams name in the home teams section
&& (el.hTeam.nickName.toLowerCase() == teams[0].toLowerCase()
|| el.hTeam.nickName.toLowerCase() == teams[1].toLowerCase())
);
});
// log the scores for the home team and visiting team to the console
game.forEach((el) => {
console.log(`(Home) ${el.hTeam.nickName}: ${el.hTeam.score.points}`);
console.log(`(Visitor) ${el.vTeam.nickName}: ${el.vTeam.score.points}`);
});
} catch (error) {
console.log({ error, teams, games});
}
}

Related

Check Javascript nested obj value exists with unknown key

The following is used to build up a object array:
var users = {};
var user = {};
user[socket.id] = data.username;
if(users[data.roomname]){
// Room already exists - check user already exists
// if data.username does not value exist is users then:
users[data.roomname].push(user);
}
else{
// New room
users[data.roomname] = [user];
}
Over a few iterations we get something like this:
console.log ( 'Users: ', users );
users { RoomABC:
[ { YidwzgUHPHEGkQIPAAAD: 'Mr Chipps' },
{ 'JG-gtBMyPm0C1Hi1AAAF': 'Mr T' },
{ '2JFGMEdPbgjTgLGVAAAH': 'Mr Chipps' }, ] }
The issue is trying to ensure that each username is unique, so Mr Chipps should not be added again if that name already exists.
The examples I have seen Assume the keys are known. I have tried a number of things including some, indexOf but I am not able to get a simple 'does UserX already exist' to work.
The following is the latest block of code I tried to only add the user if not already present in the obj array. This works, but it seems very clunky to me; nested loops to get at the correct level to check the value and set a counter if a match found, then check the counter to decide if a match was found or not:
if(users[data.roomname]){
// Room already exists - check user already exists
let found = 0;
// Nested loop - seems a little clunky but it works
Object.keys(users[data.roomname]).forEach(key => {
Object.keys(users[data.roomname][key]).forEach(key2 => {
if ( users[data.roomname][key][key2] === data.username ) {
found++;
}
});
});
if ( found == 0 ) {
users[data.roomname].push(user);
}
}
I keep thinking surely there is neat one-liner that can do this check for the existence but I cant get any to work.
You could check the values instead of using the keys and exit early if a name is found
if (users[data.roomname]) {
if (!Object.values(users[data.roomname]).some(v => Object.values(v).some(n => n === data.username))) {
users[data.roomname].push(user);
}
}

Making a Discord Bot in JavaScript: Attempting to make a game of wordchain between two players

The user that uses a particular command 'shiritori' and tags another user will be player1. The tagged user will be player2. I have already downloaded a JSON file with most dictionary words so firstly and I have tested it here and it appeared to be successful:
let usedWords = []
let points = 0
function shiritoriCommand(arguments, receivedMessage) {
let word = receivedMessage.content.substr(11)
fs.readFile('./words_dictionary.json', 'utf8', (err, jsonString) => {
if (err) {
console.log("Error reading file from disk:", err)
return
}
try {
const dictionary = JSON.parse(jsonString)
if (word in dictionary && !(usedWords.includes(word)) && word.length >= 4) {
points = points + word.length
receivedMessage.channel.send('The word is in the dictionary! You have a total of ' + points + ' points!')
usedWords.push(word)
} else {
receivedMessage.channel.send('Either the word is not in the dictionary, it is too short or it has already been used. You gain no points.')
}
} catch(err) {
console.log('Error parsing JSON string:', err)
}
})
}
The current program takes in the received message and separates the word with the substr(). Then it reads the dictionary to see if the word is found in there. If it is, it pushes the word into the array of used words so it cannot be used again to gain points. The points are the word length (which has to be 4 or more otherwise it will be disregarded.) The total is shown when a valid word is used.
However, I am finding it challenging to incorporate 2 players into it. I was inspired by the Pokecord duel, how does one differentiate between the two players' words and what exactly must be done? I initially arranged it like this:
let player1 = receivedMessage.author
let player2 = receivedMessage.mentions.members.first()
On top of that, I want each player to have a 15 second timeslot. The game stops when either player has 200 points. Now I can manage that with a while loop:
points1 = 0
points2 = 0
while (points1 <= 200 || points2 <= 200) {
/* Do I use set interval and duplicate the first function for each player
and assign their respective points */
}
If they do not answer by then, then the turn goes to the next player. I have no clue how to combine all of this to make a valid game.
You'll need to figure out a way to keep track of which player's turn it is. In this case you could use a boolean, as there are only two players. If you want the game to be more extensible, you'll obviously need to use a different logical check.
In this case, if
(player1Turn === true) {
// do some stuff
}
Of course this is just one way to handle it

Discord.js replace ID inside array with the nickname of the user matching that ID?

if (message.content === '.mutelist') {
var muteIdsList = muteIds.slice();
var memberList = message.guild.members.array();
var convertList = muteIdsList.indexOf(`${memberList}`);
console.log(`${memberList}`);
if (convertList !== -1) {
muteIdsList[convertList] = `${guild.member.nickname}`
console.log('Match!')
}
message.channel.send(`${muteIdsList}`)
.catch(function(err) {
if (muteIds.length >= 0) {
message.channel.send('Nobody is being muted at this time!');
}
else {
message.channel.send('There was an unknown error that occured, try running the command again!');
}
})
}
Nothing seems to be working so far, memberList does what I want my by fetching all the user ID's in the guild, but displays them as <#ID> instead of just the plain ID and so it doesn't match any items inside the array I have. What I want to happen is, I made a copy of the array i'm using to hold ID's for people to be muted, then taking all the ID's in the guild, run through the copied array and find all matches, then replace the ID's inside the copied array with the nickname of the user matching that specific ID.
You can access the members to nicknames mapping by using message.guild.members.
var muteIdsList = muteIds.slice(); // [1]
var memberList = message.guild.members; // {1: nickname1, 2: nickname2}
var mutedNicknameList = muteIdsList.map(x => memberList[x]); // [nickname1]

Proper way to manage paging and search/filter on a REST API made with Node and Express

I am playing around with Node and Express. My current problem is not "how to do things", as I have my paging and filtering/searching working in my "mini API" that I have made from scratch and that I am playing with. My question is more about "good practices" and "proper way" of doing things.
I will put some snippets of code below, which I am sure will bring some critics. The API is memory based, no database involved. I have an array of hard-coded users that I am pulling data from and pushing data in.
Below is my code (as you can see, I have also implemented basic authentication using passport):
//This array contains all my user data...
var users = [
{
"id": "1",
"firstName": "john",
"lastName": "doe"
}
];
//This is the route I have configured in order to retrieve all users.
//I am retrieving the users with the getUsers() function and then returning it.
//in the response object.
router.get('/users', passport.authenticate('basic', { session: false }),
function(req, res, next) {
var result = users.getUsers(req);
res.status(200).json({ users: result });
});
//This method will get the page and items parameters and will try to parse
//them. After that, it will call the search function that will filter the data
//Finally, I am passing the result array, page param and items param to the
//sliceUsers() function that will take care of slicing the result array depending
//on the values of page and items.
exports.getUsers = function(req) {
console.log(req.query);
var page = req.query.page;
items = req.query.items;
page = page !== 'undefined' ? parseInt(page, 10) : undefined;
items = items !== 'undefined' ? parseInt(items, 10) : undefined;
//The search method will filter the data
var searchResults = exports.search(req.query);
//Then, I call sliceUsers(), passing the filtered data, page and items parameters
return exports.sliceUsers(searchResults , page, items);
}
//This method will slice the array to return the page and # of items specified
//The "data" array that is passed as the first parameters is the array that contains
//the data that have already been filtered.
exports.sliceUsers= function(data, page, items) {
page = (page < 1 ? 1 : page) || 1;
items = (items < 1 ? 5 : items) || 5;
console.log('page', page, 'items', items);
var indexStart, indexEnd;
indexStart = (page - 1) * items;
indexEnd = indexStart + items;
return data.slice(indexStart, indexEnd);
};
//Those 2 methods take care of filtering
exports.search = function(query) {
return users.filter(search(query));
}
function search(query) {
console.log('search function');
return function(element) {
for(var i in query) {
//Please note here how I am checking the the parameter I am currently
//checking is NOT 'page' nor 'items'
if(query[i] != element[i] && i !== 'page' && i !== 'items') {
return false;
}
}
return true;
}
}
A few questions arise here:
Is the way I am dealing with filter/search and THEN, dealing with paging the right way?
In the search() function, when I am looping over req.query, I know that the way I check if the current param is different than 'page' or 'items' is not very efficient, but I don't know I could do that differently.
My goal here is to learn node and express and get better at javascript, what would you advice me to do next in order to pursue that goal? Any resources greatly appreciated, as the only stuff I found on APIs are basic operations that don't really deal with search/filtering. When I do find those, it's never in addition to paging for example. I have never found a complete example.
I have heard that underscore could help me do the filtering, but once again, did not really find any good example, any snippets somewhere?
Any critic GREATLY appreciated.
P.S: I apologize in advance for any grammatical error in this question.
There's a standard called OData for thing like filtering, searching, selecting and of course REST. There are few options for using it at the moment. For node backend part it's node-odata. For more, see here: http://www.odata.org/libraries/

Selecting n elements belonging to a user in a MapReduce for CouchDB

Please bear with me, I'm pretty new to the whole CouchDb stuff.
The db looks like:
** item ** count ** user **
A 20 bob
B 30 bob
C 10 bob
D 15 john
I want to write a MapReduce that selects all the items belonging to bob and only return the top 2, sorted. so it should return [{item:"B",count:"30"},{item:"A",count:"20}]
I'm not sure how this can be done? Seems like I have to emit(doc.item, doc.count), but how do I know if the user owns the doc? How do I run another MapReduce to select the top elements?
One solution would be to write your view to use a complex key, such as:
function (doc) {
emit([doc.user, doc.count], doc.item);
}
If you add descending=true to your query string, that would give you a view result like:
{"total_rows":4,"offset":0,"rows":[
{"id":"53f359b7cd360da296dd9aab3d0029bd","key":["john",15],"value":"D"},
{"id":"53f359b7cd360da296dd9aab3d001a0e","key":["bob",30],"value":"B"},
{"id":"53f359b7cd360da296dd9aab3d000fec","key":["bob",20],"value":"A"},
{"id":"53f359b7cd360da296dd9aab3d002668","key":["bob",10],"value":"C"}
]}
It's sorted already by user, then count. (with the item type as the value)
Then you can use a _list function to do the rest. The code below basically loops through the view, and returns the top 2 results for each user. If you specify user=bob in the query string, you'll only get the results for bob.
function (head, req) {
// specify that we're sending JSON as our response
provides('json', function () {
var results = [],
result, user, count, row;
while (row = getRow()) {
// if the user doesn't match the last iteration, reset our counter
if (user != row.key[0]) {
user = row.key[0];
count = 0;
}
// we only need the top 2
if (count++ >= 2) {
continue;
}
// start building a result object
result = {
item: row.value,
count: row.key[1]
};
// if we provide user=?
if (req.query.user) {
// check to see if it matches the current user
if (req.query.user === user) {
// if so, add it to the results
results.push(result);
}
// by default, we'll return the top 2 for every user
} else {
// add the user key to the result object
result.user = row.key[0];
// and add it to the result set
results.push(result);
}
}
// send outside the loop, since it needs to be sent as valid JSON
send(JSON.stringify(results));
});
}
If you put user and count in the key of the view, you can use startkey=["bob",""] and endkey=["bob"] to select the user, and descending=true and limit=2 to get the top two items.
I tried the following map function:
function(doc) {
if(doc.user && doc.count && doc.item) {
emit([doc.user, doc.count], doc);
}
}
with the query string ?startkey=["bob",""]&endkey=["bob"]&descending=true&limit=2 it returns:
{"total_rows":4,"offset":1,"rows":[
{"id":"item_B_bob","key":["bob",30],"value":{"_id":"item_B_bob","_rev":"1-b23bd22fb719c7d59b045bce0932df8c","item":"B","count":30,"user":"bob"}},
{"id":"item_A_bob","key":["bob",20],"value":{"_id":"item_A_bob","_rev":"2-515bca46eab383cfeaaa2a101d180291","item":"A","count":20,"user":"bob"}}
]}
Please note:
startkey and endkey are reversed because descending=true.
["bob",""] is a key greater then ["bob", ANY NUMBER] as specified in view collation.

Categories

Resources