filter map result with duplicate keys - javascript

I have a command handler that uses a map with commands assigned from user command folders and a general comm folder. User can add their own commands. the map filters using name inside the file which is also how the call them. How do I make a comparison if there is more than one with the same name? Afaik maps can't do this and fear I have to redo it all.
At first I was trying to assign every user it's own map
client.User1.get(command, argument);
client.User2.get(command argument);
was this actually the right way to do it? I am using a .JSON with enabled command names for initial comparison. Below some of the code, as you ccan probably tell i'm not that experienced.
const assignedChannels = client.opts.channels.slice(0);
assignedChannels.unshift('#basiccommands');
const rootForCommands = "./commands";
client.commandCall = new Map();
for (const channelspecificDir of assignedChannels) {
const commandFiles = fs.readdirSync(`${rootForCommands}/${channelspecificDir}`);
for (const file of commandFiles) {
const commandCollection = require(`${rootForCommands}/${channelspecificDir}/${file}`);
//sets a new item in the collection.
//Key of map as command name and the value the function to send message and argument true or false
client.commandCall.set(commandCollection.name, commandCollection);
const currentProfile = JSON.parse(fs.readFileSync(`./userProfiles/${channel}.json`, 'utf8'));
if (!currentProfile.all_commands.includes(commandPre)){
return console.log("Command does not exist");
}
try {
client.commandCall.get(commandPre, argU).execute(channel, argU);
} catch (error) {
console.log('command broke');
console.error(error);
}
My prevoious method was to assign user specific folder and general command folder to a map and at the end of each iteration change the name of the map object. But that does not work for some reason.

Actually, you can use symbols as keys for maps
const map = new Map()
map.set(Symbol(‘the same name’), value1)
map.set(Symbol(‘the same name’), value2)
You can use any values for Map’s keys, even objects and functions
map.set({}, value3)
map.set({}, value4)
In all previous expressions we get different values (links) for Map’s keys.
BTW. Be aware with const some = require(‘some_file_name’).
require() caches a value for a file name. So, if your files may be updated during a run time and you need to read these updates you shouldn’t use ‘require()’. It’s good only for importing modules and static values (e.g. configs)

Related

Dexie JS (Indexed DB): Using get( ) in addEventListener returning undefined

I've been working on chrome extention project.
What I am trying to do is store the input value and pull it out when the specific button is pressed.
Below is the part of js code:
import { Dexie } from '../node_modules/dexie/dist/dexie.mjs';
var DBName = 'test';
buttonA.addEventListener('click', () => {
const inp = document.getElementById("inp");
const db = new Dexie(DBName);
db.version(2).stores({
friend: '++id, name'
});
db.friend.add({
name: inp.value
})
});
buttonB.addEventListener('click', () => {
const db = new Dexie(DBName);
const ch = db.friend;
console.log("Checking the value in DB: " + ch);
});
When I try it, it stores whatever input to indexed DB after clicking buttonA (confirmed by using Chrome Developer Tool), however when it comes to clicking on buttonB, the log tells that ch is undefined (same for db.friend.name or db.name as well).
Because of this, even when I use get(), it returns me an error since I am accessing on undefined variable.
Could someone help figuring out why the program does not access properly to an indexed DB that exists when I click on a buttonB?
Thank you.
Problems:
The second instance of Dexie does not declare which tables there are, so db.friend is unknown.
Your code creates a new Dexie for every click. It would be much better and faster to reuse a single Dexie instance. If you create a new Dexie instance everytime, you must also close it after you to avoid resource leakage.
Recommendation:
Declare a singleton Dexie instance with version().stores(...) so it populates the 'friend' property for you.
Preferably this code could be in it's own module (such as 'db.js').
Use that single Dexie instance from any place where you need to store or read from the db.

Javascript find and map values based on scheme throwing undefined

I'm working with Javascript to build a mapping function, that, given a scheme should find the mapper's object's value within an application variable.
My attempt at doing this results in an undefined error and I'm trying to figure out what I need to change to make it work correctly, I've put together a JS fiddle that can be found here as well.
const application = {
state: {
outgoings: {
credit: 0,
food: 0
}
}
}
const mapper = {
"AppFoodSpend": "outgoings.food",
"AppCreditSpend": "outgoings.credit"
}
for (const field in mapper) {
console.log(application.state[mapper[field]])
// expected results should be credit 0 and food 0
}
For further context, I have an object called application which contains some fields, they may not always be in the same order. My mapper object contains some key/value pairs, and it's the value that I'd like to try and find in application. for for instance, my console log should be retrieving something like:
application.state.outgoings.food
application.state.outgoings.credit
They may not always be in this order, so I need to (on each iteration of the mapper) find the key in my application variable.
What do I need to change here?
Strings like 'outgoings.food' are not valid to navigate a data structure. You could use some "lenses" lib or write something like this...
const mapper = {
"AppFoodSpend": ["outgoings", "food"],
"AppCreditSpend": ["outgoings", "credit"]
}
for (const field in mapper) {
console.log(
mapper[field].reduce(
(ctx, path) => ctx[path],
application.state
)
)
}

Is there a better way to modify a nested item in a JavaScript object?

I know that eval is bad and all but I can't really find any better way of doing this.
I receive an input string (from a command prompt) and I need to modify some value of a nested item inside a stored JSON object representing a file system.
Here it is in more detail:
I am making an edit command for my command prompt, and its form is like this:
edit <filename>
My code receives <filename> and looks in a file system for it, to check its existence.
Here is an example file system (in JSON, Linux based):
{
"home": {
"default": {
"documents": {},
"tst": "This is a file"
}
}
"etc": {}
"tmp": {}
}
So if the user's PWD (present working directory) is /home, and they enter default/tst, they should be editing /home/default/tst.
This is how I tried:
var wdir=JSON.parse(localStorage.getItem("filesys")); // Whole dir (wdir) - Obtain the stored file system
var cdir=wdir; // Current dir (cdir)
var i; // loop var
var l=f.length; // f is something like ["default","tst"]
var runstr="wdir"; // The thing to eval
for(i=0;i<l-1;i++){ // Go through everything except the last (the last is the file)
if(f[i] in cdir){ // If it exists in the current dir
cdir=cdir[f[i]]; // Go into a directory and modify cdir, not wdir
runstr+="['"+f[i]+"']"; // Add more to runstr
}
}
cdir[n]=t.value; // t.value is the new file content
eval(runstr+'=cdir;'); // run runstr to modify value
localStorage.setItem("filesys",JSON.stringify(wdir)); // Save the file system
Side note: this works, I'm just looking for a better solution
I've heard that eval is 'evil' and also slow as the JS interpreter has to start again. Since I'm using it for such a simple task I thought that there must be another way.
What better way can I set the value of a key that could be nested inside objects inside objects?
If you need any more information, you can ask in the comments and I will add it to my question.
Fun to code, the below snippet should do the trick ! You might need to add some additional conditions in order to prevent your user to override a folder !
I used the spread operator to do a "deep copy" of the object, if you want to directly alter the object passed to the function you can remove the first line and replace deepCopy by obj in the reducer. :)
const setObjectPropertyAtPath = (obj, path, value) => {
// we do a deep copy of the object because we want to alter this copy and not directly the object passed to the // function. Doing it provide us a way to return the modified object instead of direcly altering the original // object
const deepCopy = {...obj}
path.reduce((acc, cur, level) => {
if(level === path.length - 1) {
acc[cur] = value;
return value;
}
return acc[cur];
}, deepCopy)
return deepCopy;
}
const fs = {
"home": {
"default": {
"documents": {},
"tst": "This is a file"
}
},
"etc": {},
"tmp": {}
}
const updatedFs = setObjectPropertyAtPath(fs,["home", "default", "tst"], "foo")
console.log(updatedFs)

Setting Socket.io room variables

I would like to store some information in the socket room variables, but am getting the following error: UnhandledPromiseRejectionWarning: TypeError: Cannot set property 'host' of undefined
This is my code:
io.on('connection', (socket, next) => {
socket.on('create game', async () => {
console.log('Creating game...');
socket.username = await generateUsername();
socket.roomId = generateId();
socket.join(socket.roomId);
io.sockets.adapter.rooms[socket.roomId].host = socket.username;
io.sockets.adapter.rooms[socket.roomId].isOpen = true;
io.sockets.adapter.rooms[socket.roomId].players = [socket.username];
console.log('Game created! ID: ', socket.roomId);
});
}
If I try to log socket.roomId it would return something like rBAhx0. And when I log io.sockets.adapter.rooms, I get the following:
Map {
'PX_o3Di9sp_xsD6oAAAB' => Set { 'PX_o3Di9sp_xsD6oAAAB' },
'rBAhx0' => Set { 'PX_o3Di9sp_xsD6oAAAB' }
}
However, when I try to log io.sockets.adapter.rooms[socket.roomId], it returns undefined. How can I fix this?
Socket.io made some breaking changes in the newest version and rooms cannot be accessed like they used to. It changed a lot of objects and arrays into Maps and Sets, you can see it in the logs you've posted.
Set objects are collections of values. You can iterate through the elements of a set in insertion order. A value in the Set may only occur once; it is unique in the Set's collection.
The Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value.
Accessing properties of a Map works differently than accessing properties of a normal Object. Example:
const myMap = new Map();
myMap.set("foo", "bar");
console.log(myMap["foo"]) // undefined
console.log(myMap.get("foo")) // bar
Same applies to Sets, however in your case querying this Set in particular is probably a wrong approach, as this set only holds a collection of ids, and not actual room objects. Even if you were to get a value out of the Set, you could not access it's properties (host, isOpen and players) since it is only a string.
The version 3.0 made accessing the list of all rooms directly impossible I'm afraid. However the adapter now has a property socketRooms, which can be used in place of it.
In order to access rooms of a socket easier, you should access them like so:
io.sockets.adapter.socketRooms(socketId);
However that would still just return a list of strings.
The simplest solution to this problem would be to create an external variable outside of the connection scope.
const rooms = {};
io.on('connection', (socket, next) => {
socket.on('create game', async () => {
console.log('Creating game...');
socket.username = await generateUsername();
socket.roomId = generateId();
socket.join(socket.roomId);
if (!rooms[socket.roomId]) rooms[socket.roomId] = {};
rooms[socket.roomId].host = socket.username;
rooms[socket.roomId].isOpen = true;
rooms[socket.roomId].players = [socket.username];
console.log('Game created! ID: ', socket.roomId);
});
}
As you can see in your own log, io.sockets.adapter.rooms is a Map object (in the later versions of socket.io), not a plain object. That means that a room is accessed with .get(), not with plain indexing such as what you are using:
io.sockets.adapter.rooms[socket.roomId].host = socket.username;
That simply won't work because there is no plain socket.roomId property on the Map.
If you wanted to get the room object, you would have to do this:
let roomObj = io.sockets.adapter.rooms.get(socket.roomId);
roomObj.host = socket.username;
In my opinion, I wouldn't mess with the room objects directly. We already know that socket.io has changed their design before, rendering any code that tried to use them as broken and in need of redesign. Instead, I'd probably just create my own data structure where I stored my own data and then I won't be dependent upon the whims of how socket.io changes their design. Accessing the room objects directly is not a committed design or committed API of socket.io. They can change how it works whenever they want and have, indeed, changed it recently.
After the breaking update from Socket.io of changing Array to Map.
A simple solution would be changing:
io.sockets.adapter.rooms[socket.roomId].host = socket.username;
to:
io.sockets.adapter.get(socket.roomId).host = socket.username;

How to loop through a Cherrio inside an async function and populate an outside variable?

I need to create an API that web scraps GitHub's repos getting the following data:
File name;
File extension;
File size (bytes, kbytes, mbytes, etc);
File number of lines;
I'm using Node with TypeScript so, to get the most out of it, I decided to create an interface called FileInterface, that has the four attributes mentioned above.
And of course, the variable is an array of that interface:
let files: FileInterface[] = [];
Let's take my own repo to use as an example: https://github.com/raphaelalvarenga/git-hub-web-scraping
So far so good.
I'm already pointing to the HTML's files section with request-promise dependency and storing them in a Cheerio variable so I can traverse through the "tr" tags to create a loop. As you might think, those "tr" tags represent each files/folders inside of a "table" tag (if you inspect the page, it can easily be found). The loop will fill a temp variable called:
let tempFile: FileInterface;
And at the end of every cycle of the loop, the array will be populated:
files.push(tempFile);
In GitHub repo's initial page, we can find the file names and their extension. But the size and total of lines, we can't. They are found when clicking on them to redirect to the file page. Let's say we clicked in README.md:
Ok, now we can see README.md has 2 lines and 91 Bytes.
My problem is, since this will take a long time, it needs to be an async function. But I can't handle the loop in Cheerio content inside the async function.
Things that I've tried:
Using map and each methods to loop through it and push in the array files;
Using await before the loop. I knew this one wouldn't actually work since it's just a loop that doesn't return anything;
The last thing I tried and believed that would work is Promise. But TypeScript accuses Promises return the "Promise unknown" type and I'm not allowed to populate the result in files arrays, since the types "unknown" and "FilesInterface[]" are not equal.
Below I'll put the code I created so far. I'll upload the repo in case you want to download and test (the link is at the beginning of this post), but I need to warn that this code is in the branch "repo-request-bad-loop". It's not in the master. Don't forget because the master branch doesn't have any of this that I mentioned =)
I'm making a request in Insomnia to the route "/" and passing this object:
{
"action": "getRepoData",
"url": "https://github.com/raphaelalvarenga/git-hub-web-scraping"
}
index-controller.ts file:
As you can see, it calls the getRowData file, the problematic one. And here it is.
getRowData.ts file:
I will try to help you, although I do not know typescript. I redid the getRowData function a bit and now it works for me:
import cheerio from "cheerio";
import FileInterface from "../interfaces/file-interface";
import getFileRemainingData from "../routines/getFileRemaningData";
const getRowData = async (html: string): Promise<FileInterface[]> => {
const $ = cheerio.load(html);
const promises: any[] = $('.files .js-navigation-item').map(async (i: number, item: CheerioElement) => {
const tempFile: FileInterface = {name: "", extension: "", size: "", totalLines: ""};
const svgClasses = $(item).find(".icon > svg").attr("class");
const isFile = svgClasses?.split(" ")[1] === "octicon-file";
if (isFile) {
// Get the file name
const content: Cheerio = $(item).find("td.content a");
tempFile.name = content.text();
// Get the extension. In case the name is such as ".gitignore", the whole name will be considered
const [filename, extension] = tempFile.name.split(".");
tempFile.extension = filename === "" ? tempFile.name : extension;
// Get the total lines and the size. A new request to the file screen will be needed
const relativeLink = content.attr("href")
const FILEURL = `https://github.com${relativeLink}`;
const fileRemainingData: {totalLines: string, size: string} = await getFileRemainingData(FILEURL, tempFile);
tempFile.totalLines = fileRemainingData.totalLines;
tempFile.size = fileRemainingData.size;
} else {
// is not file
}
return tempFile;
}).get();
const files: FileInterface[] = await Promise.all(promises);
return files;
}
export default getRowData;

Categories

Resources