I have a problem with discord.js: I want to set a position to a channel :
const chanName = message.channel.name;
let categoryId = message.channel.parentID
let position = message.channel.position
console.dir(position)
const catego = message.guild.channels.cache.find(c => c.id == categoryId && c.type == "category")
message.channel.delete().catch();
const chan = message.guild.channels.create(chanName, {type: 'text'}).then((channel) => channel.setParent(catego) && channel.setPosition(position) && channel.send(`Succesfully nuked \`${chanName}\`\nhttps://imgur.com/LIyGeCR`))
chan
but when i execute the command the channel is not set at the position.
I would also like to save the permissions to put them back after
The issue is in the then callback:
channel.setParent(catego) && channel.setPosition(position) && channel.send(`Succesfully nuked \`${chanName}\`\nhttps://imgur.com/LIyGeCR`)
&& is the logical AND operator. channels.setParent(catego) returns a Promise, which is truthy because it is an object. Because && short-circuits, it does not evaluate the other expressions channel.setPosition(...) and channel.send(...) because the expression will evaluate to that Promise regardless.
You want to execute multiple statements instead:
const chan = message.guild.channels.create(chanName, {type: 'text'}).then(channel => {
channel.setParent(catego)
channel.setPosition(position)
channel.send(`Succesfully nuked \`${chanName}\`\nhttps://imgur.com/LIyGeCR`)
})
It's important to remember that channel => ... is just an ordinary arrow function, and the following are all equivalent:
const f1 = channel => channel.setParent(catego)
const f2 = channel => {
return channel.setParent(catego)
}
const f3 = function (channel) {
return channel.setParent(catego)
}
function f4(channel) {
return channel.setParent(catego)
}
However, in this case, you can set the parent and position of the channel while creating it:
const chan = message.guild.channels.create(chanName, {
type: 'text',
parent: catego,
position
})
.then(channel =>
channel.send(`Succesfully nuked \`${chanName}\`\nhttps://imgur.com/LIyGeCR`)
)
You can see the other options in the documentation for GuildChannelManager#create.
Related
I am trying to implement a filter which allows me to filter users based on the distance between a location and their address. Data is provided to the table using useMemo, basically like this:
const data = useMemo(
() =>
contacts.filter(contact => {
var shouldReturn = true;
clientFilter.map((filter, i) => {
if (filter.condition === 'max_10km') {
const originAddress = `${contact['street']} ${contact['number']}, ${contact['zip']} ${contact['city']}, ${contact['country']}`;
calculateDistance(originAddress, filter.value, function(distance) {
console.log('distance is calculated: ', distance);
if (distance > 10000) {
console.log('distance is > for', contact['name']);
shouldReturn = false;
}
});
}
});
return shouldReturn;
}),
[clientFilter]
);
This works fine, in console the results return as I expect them to be. However, my table doesn't update. I suspect it is because the result of the API calls are async, and thus the table is re-rendered before the results are in.
I have tried updating the data using useEffect, but this brings me in a loop which constant re-renders, and thus exceeding the maximum (Maximum update depth exceeded.).
How should I go about this? Should I try async functions? If so, how can I wait to update my data until all promises are resolved?
EDIT 14 NOV
So, I have been looking further into this today. I have managed to switch the filtering to useEffect() instead of useMemo(), so currently, it looks like this:
const [filteredContacts, setFilteredContacts] = useState(contacts);
const data = useMemo(() => filteredContacts, [filteredContacts]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
console.log('Log: In useEffect()');
if (!isLoading) {
setIsLoading(true);
(async () => {
console.log('Log: State has changed, filtering will start');
filterContacts().then(() => setIsLoading(false));
})();
}
}, [clientFilter]);
async function filterContacts() {
setFilteredContacts(
contacts.filter(contact => {
var shouldReturn = true;
clientFilter.map((filter, i) => {
if (
filter.condition === 'equal' &&
contact[filter.field] != filter.value &&
shouldReturn
) {
shouldReturn = false;
}
if (filter.condition === 'max_10km' && shouldReturn) {
const originAddress = `${contact['street']} ${contact['number']}, ${contact['zip']} ${contact['city']}, ${contact['country']}`;
calculateDistance(originAddress, filter.value, async function(
distance
) {
console.log('Log: Distance is calculated: ', distance);
if (distance > 10000) {
console.log(
'Log: Distance is further away for',
contact['title']
);
shouldReturn = false;
}
});
}
});
console.log('Log: About to return shouldReturn value');
return shouldReturn;
})
);
}
Now, this works for my other filters, but the async distance calculation still runs after the return of shouldReturn has been done. So my logs look like this (I have 16 contacts/users currently):
Log: In useEffect()
Log: State has changed, filtering will start
(16) Log: About to return shouldReturn value
Log: Distance is calculated: 1324
Log: Distance is calculated: 4326
...
So basically, it still ignores the async state of my function calculateDistance. Any ideas?
EDIT 15/11
Might be useful as well, this is my calculateDistance() function:
function calculateDistance(origin, destination, callback) {
const google = window.google;
const directionsService = new google.maps.DirectionsService();
directionsService.route(
{
origin: origin,
destination: destination,
travelMode: google.maps.TravelMode.DRIVING
},
(result, status) => {
if (status === google.maps.DirectionsStatus.OK) {
callback(result.routes[0].legs[0].distance.value);
} else {
console.error('Google Maps API error: ', status);
callback(null);
}
}
);
}
EDIT 18/11
After #Secret's suggestion, I changed the code to:
const shouldRemoveContact = await(filters, contact) = () => {
for (const filter of filters) {
if (
filter.condition === 'equal' &&
contact[filter.field] != filter.value
) {
return true
}
if (filter.condition === 'max_10km') {
const originAddress = `${contact['street']} ${contact['number']}, ${contact['zip']} ${contact['city']}, ${contact['country']}`
// please update calculateDistance to return a promise
const distance = await calculateDistance(originAddress, filter.value)
return distance > 10000
}
return false
}
}
async function filterContacts (filters, contact) {
// for every contact, run them to shouldRemoveContact
// since shouldRemoveContact is async, we use Promise.all
// to wait for the array of removeables to be ready
const shouldRemove = await Promise.all(
contact.map(c => shouldRemoveContact(filters, c))
)
// use shouldRemove to check if contact should be removed
// and voila!
return contacts.filter((c, i) => !shouldRemove[i])
}
This results in:
Syntax error: Unexpected reserved word 'await'.
on the line:
const shouldRemoveContact = await(filters, contact) = () => {
You are correct in your mistake - your calculateDistance is async and therefore you can't filter your data properly. Essentially, your code can be boiled down to:
setFilteredContacts(
contacts.filter(contact => {
var shouldReturn = true;
// THIS IS WHERE CALCULATE IS
// but it doesn't do anything because it is async
// so effectively, it does nothing like a comment
console.log('Log: About to return shouldReturn value');
return shouldReturn;
})
);
As you can see, your contacts will never be filtered out as shouldReturn will always return true - it never changes in the middle of that function!
What you can do is calculate the list of filterables beforehand, and then use that to run your filter. Something like this (psuedocode):
// given a contact and a list of filters
// asynchronously return if it should or should not be filtered
const shouldRemoveContact = async () => {}
// THEN, in the filterContacts part:
// let's generate an array of calls to shouldRemoveContact
// i.e. [true, true, false, true], where `true` means it should be removed:
// note that we use await Promise.all here, waiting for all the data to be finished.
const shouldRemove = await Promise.all(
contact.map(c => shouldRemoveContact(filters, c))
)
// we then simply shouldRemove to filter it all out
return contacts.filter((c, i) => !shouldRemove[i])
All together:
// given a list of filters and one contact
// asynchronously returns true or false depending on wheter or not
// the contact should be removed
const shouldRemoveContact = async (filters, contact) => {
for (const filter of filters) {
if (
filter.condition === 'equal' &&
contact[filter.field] != filter.value
) {
return true
}
if (filter.condition === 'max_10km') {
const originAddress = `${contact['street']} ${contact['number']}, ${contact['zip']} ${contact['city']}, ${contact['country']}`
// please update calculateDistance to return a promise
const distance = await calculateDistance(originAddress, filter.value)
return distance > 10000
, async function(
distance
) {
console.log('Log: Distance is calculated: ', distance);
if (distance > 10000) {
console.log(
'Log: Distance is further away for',
contact['title']
);
shouldReturn = false;
}
});
}
}
return false
}
async function filterContacts (filters, contact) {
// for every contact, run them to shouldRemoveContact
// since shouldRemoveContact is async, we use Promise.all
// to wait for the array of removeables to be ready
const shouldRemove = await Promise.all(
contact.map(c => shouldRemoveContact(filters, c))
)
// use shouldRemove to check if contact should be removed
// and voila!
return contacts.filter((c, i) => !shouldRemove[i])
}
I am writing a discord bot to ping a user at a set interval. And I want to know if there is a way to compile this to not have to copy and paste the entire script to have the same thing happen for other users.
client.on('message', message => {
if(message.content === '&ping zach forever') {
setInterval(() => {
var yourchannel = client.channels.cache.get('704506336339034115');
yourchannel.send('<#UserID>');
}, "5000");
}});
client.on('message', message => {
if(message.content === '&ping maxx forever') {
setInterval(() => {
var yourchannel = client.channels.cache.get('704506336339034115');
yourchannel.send('<#UserID>');
}, "5000");
}});
I have no experience with discord bots in particular, but as general JavaScript advice:
const users = ["zach", "maxx"];
client.on('message', message => {
const splits = message.content.split(' ');
if (splits.length === 3 && splits[0] === '&ping' && splits[2] === 'forever' && users.includes(splits[1])) {
setInterval(() => {
var yourchannel = client.channels.cache.get('704506336339034115');
yourchannel.send('<#UserID>');
}, "5000");
}
});
Simply expand the users array with the required names. You can also modify the array at runtime with .push() and .splice(). If you'd like the bot to trigger even if there is text after the command, check for splits.length >= 3 instead.
PS. As a general rule, using var is frowned upon these days. Use const for values only assigned once, and let otherwise.
Edit:
Here's a bit more fleshed out example, that includes hardcoded user ID's for each name, applies the duration (in seconds), and also includes a &stop command to stop pinging a certain person. (Code is not tested to run correctly, since I don't have a discord bot account, and don't want to make one either)
const users = new Map(Object.entries({
"zach": "UserID1",
"maxx": "UserID2"
}));
const active = new Map();
client.on('message', message => {
const splits = message.content.split(' ');
if (splits.length === 3 && splits[0] === '&ping' && users.has(splits[1])) {
const channel = client.channels.cache.get('704506336339034115');
const message = `<#${users.get(splits[1])}>`;
const time = Number(splits[2]);
if (isNaN(time) && splits[2] !== 'forever') {
// Passed a time that wasn't a number - possibly send back an error message
} else {
const id = setInterval(() => channel.send(message), 5000);
active.set(splits[1], id);
if (!isNaN(time)) setTimeout(() => clearInterval(id), time * 1000);
}
}
if (splits.length === 2 && splits[0] === '&stop') {
const id = active.get(splits[1]);
if (id) {
clearInterval(id);
active.delete(splits[1]);
}
}
});
Despite looking and following numerous answers here at stackoverflow,I have still failed to refactor this code to abide by the ESLint no-loop-func.
I keep getting the following warning, despite my efforts to refactor the code:
Compiled with warnings.
Function declared in a loop contains unsafe references to variable(s) 'lastResult', 'biologyBooks', 'page' no-loop-func
Here's the code:
import React from 'react';
import { apiFullCall } from '../../apiHelper';
const MyComponent = props => {
const [state, setState] = React.useState({ total: 0, biologyBooksByAuthor: [] });
let isLoaded = React.useRef(true);
const token = sessionStorage.getItem('token');
const authorID = sessionStorage.getItem('author_id');
const getBooks = async() => { // fetch items
let page = 1;
let scienceBooks, biologyBooks;
// create empty arrays to store book objects for each loop
let scienceBooks = biologyBooks = [];
// create a lastResult object to help check if there is a next page
let lastResult = { next: null };
do { // the looping - this is what I have failed to refactor
try {
await apiFullCall( // Make API calls over paginated records
'',
token,
'get',
`books/?author_id=1&page=${page}`
).then(res => {
if (res) {
const { status, body } = res;
if (status === 200 || status === 201) {
lastResult = body; // assign lastResult to pick "next"
body &&
body.results &&
body.results.map(eachBook => { // we map() over the returned "results" array
// the author with queried "author_id" writes science books;
// so we add each book (an object) into the science category
scienceBooks.push(eachBook);
// We then filter the author's biology books (from other science books)
biologyBooks = scienceBooks.filter(
({ is_biology }) =>
typeof(is_biology) === "boolean" && is_biology === true
);
return null;
}
);
// increment the page with 1 on each loop
page++;
}
}
}).catch(error => console.error('Error while fetching data:', error));
} catch (err) { console.error(`Oops, something went wrong ${err}`); }
// keep running until there's no next page
} while (lastResult.next !== null);
// update the state
setState(prevState => ({
...prevState, total: scienceBooks.length, biologyBooksByAuthor: biologyBooks,
}));
};
React.useEffect(() => { // fetch science books by author (logged in)
if (isLoaded && authorID) {
getBooks();
};
return function cleanup() {...}; // clean up API call, on unmount
}, [isLoaded, authorID]);
return (
// render the JSX code
);
}
Please note that I actually declared the said variables lastResult, biologyBooks and page outside the "do-while".
Any help or clues will be greatly appreciated.
The function the warning is referring to is the .then callback, if you're using async/await stick to it, try removing the .then part by assigning the result to a variable instead and remove the unnecessary .map, you can concatenate previous results with spread operator or .concat.
import React from 'react';
import { apiFullCall } from '../../apiHelper';
const MyComponent = props => {
const [state, setState] = React.useState({
total: 0,
scienceBooksByAuthor: [],
});
const isLoaded = React.useRef(true);
const token = sessionStorage.getItem('token');
const authorID = sessionStorage.getItem('author_id');
const getBooks = async () => {
// fetch items
let page = 1;
let scienceBooks = [];
// create a lastResult object to help check if there is a next page
let lastResult = { next: null };
do {
// the looping - this is what I have failed to refactor
try {
const res = await apiFullCall(
// Make API calls over paginated records
'',
token,
'get',
`books/?author_id=1&page=${page}`,
);
if (res) {
const { status, body } = res;
if (status === 200 || status === 201) {
lastResult = body; // assign lastResult to pick "next"
// concatenate new results
scienceBooks = [
...scienceBooks,
...((lastResult && lastResult.results) || []),
];
// increment the page with 1 on each loop
page += 1;
}
}
} catch (err) {
console.error(`Oops, something went wrong ${err}`);
}
// keep running until there's no next page
} while (lastResult.next !== null);
const biologyBooks = scienceBooks.filter(
({ is_biology }) =>
typeof is_biology === 'boolean' && is_biology === true,
);
// update the state
setState(prevState => ({
...prevState,
total: scienceBooks.length,
scienceBooksByAuthor: scienceBooks,
}));
};
React.useEffect(() => {
// fetch science books by author (logged in)
if (isLoaded && authorID) {
getBooks();
}
return function cleanup() {...}; // clean up API call, on unmount
}, [isLoaded, authorID]);
return (
// render the JSX code
);
};
A button calls the function signAllBrowsed, which contains two other functions:
loadSafetyLetters is a hook that makes a database call for some data and sets it in context
signAll is a hook that tries to access data in context to do something with it
Context is getting set properly, but when signAll accesses it, the data is not updated. Is there a way to access the updated context without directly passing it to the 2nd function? Or is there a way to call a callback once context is updated and accessible? Seems the updated context is only available after a re-render.
The component containing signAllBrowsed and the 2 hooks are siblings.
code in above image:
setModalVisible(true)
const logHeader = 'SafetyLetterHome::SignAllBrowsed'
try {
const response = await loadSafetyLetters(false) // before beginning sign all, get a fresh list of letters from db
if (Configs.SHOW_REQUEST_LOGS) console.log(`${logHeader} response`, response)
if (response === 'no api error') {
await signAll()
navigation.navigate('SafetyLetterSign')
}
} catch (error) {
const errorMessage = error.status && error.status.message ? error.status.message : error
Alert.alert('Database Error', errorMessage)
console.log(`${logHeader}`, errorMessage)
}
}
loadSafetyLetters calls the loadLetters hook:
const [getLetters] = useGetLetters()
const [sortLetters] = useSortLetters()
const [hasAPIError] = useHasAPIError()
const navigation = useNavigation()
const { setModalVisible, setShowSignAll, setSortedLetters, setUnsortedLetters } = useContext(SafetyContext)
const loadLetters = async (sort = true) => {
try {
const response = await getLetters()
const logHeader = 'SafetyHome::loadLetters'
const errorMessage = 'The following error occurred when trying to load letters:'
if (Configs.SHOW_REQUEST_LOGS) console.log(`${logHeader} response`, response)
const error = hasAPIError(response, logHeader, errorMessage)
if (error) return error
const { data } = response.data.payload
let unsortedLetters = []
if (data !== null && data.length > 0) {
data.map((item) => {
// grab only unsigned letters
if (
item.assignmentStatus === SafetySources.PENDING ||
item.assignmentStatus === SafetySources.BROWSED ||
item.assignmentStatus === SafetySources.QUESTIONS_COMPLETE
) {
unsortedLetters.push({
safetyLetterId: item.safetyLetterId,
title: item.title,
assignmentStatus: item.assignmentStatus,
filePath: item.filePath,
embeddableToken: item.embeddableToken,
sponsorId: item.sponsorId,
letterDate: item.letterDate,
form16: item.form16Enabled === '1' ? true : false,
sponsorName: item.sponsorName,
type: item.letterType,
sortOrder: item.sortOrder, // dear doctor; sortOrder === 1
})
}
})
}
if (unsortedLetters.length > 0) {
let bletters = unsortedLetters.filter((letter) => letter.assignmentStatus === SafetySources.BROWSED || letter.assignmentStatus === SafetySources.QUESTIONS_COMPLETE)
console.log('useLoadLetters; setting fresh pull of letters in context, including ', bletters.length, ' browsed letters')
setUnsortedLetters(unsortedLetters) // set in context
setShowSignAll( // show/hide sign all button
unsortedLetters.some((letter) =>
letter.assignmentStatus === SafetySources.BROWSED ||
letter.assignmentStatus === SafetySources.QUESTIONS_COMPLETE,
))
}
if (sort) {
if (unsortedLetters.length > 0) {
let sortedLetters = sortLetters(unsortedLetters) // sort letters with hook
setSortedLetters(sortedLetters) // set in context
}
}
} catch (error) {
console.log('SafetyHome::loadLetters ', error)
const errorMessage = error.status && error.status.message ? error.status.message : error
Alert.alert(
'Error Loading Letters',
`A database error has occurred. Please try again. (${errorMessage})`,
)
navigation.navigate('Home')
} finally {
setModalVisible(false)
}
}
return [loadLetters]
}
signAll hook:
const { state: { unsortedLetters },
setF16Browsed,
setQcAndBrowsed,
setModalVisible,
setSelectedLetter
} = useContext(SafetyContext)
const signAll = async () => {
let qcAndBrowsed = [] // set letter groups in context
let f16Browsed = []
unsortedLetters.forEach((letter) => {
if (
letter.assignmentStatus === SafetySources.BROWSED ||
letter.assignmentStatus === SafetySources.QUESTIONS_COMPLETE
) {
if (
letter.form16 &&
letter.assignmentStatus !== SafetySources.QUESTIONS_COMPLETE
) {
f16Browsed.push(letter)
} else {
qcAndBrowsed.push(letter)
}
}
})
setQcAndBrowsed(qcAndBrowsed)
setF16Browsed(f16Browsed)
// begin sign all with first f16 letter
if (f16Browsed.length > 0) {
setSelectedLetter(f16Browsed[0])
} else {
setSelectedLetter(null) // clear any previous viewed letter
}
setModalVisible(false)
}
return [signAll]
}
I want to skip all null value from array in React
This is a part of my code.
I've added filter before map function but it looks not working.
componentDidMount(){
client.getEntries({content_type:'formRequest'}).then(response => {
this.setState({qoutes: response.items});
}).catch(e => {
console.log(e);
});
}
render(){
const user = "name";
const qoutes = this.state.qoutes
.filter(i => i !== null)
.map((qoute, i) =>
user === qoute.fields.user || user === qoute.fields.company ?
<QoutesListItem id={i} key={i} qoute={qoute} />
: null)
return(<div>{qoutes}<div>)
I tried to make function like this, but it does not work as well
const qoutes = this.state.qoutes.filter((qoutes)=>{
if(qoutes !== null ) {
return qoutes;
}}).map((qoute, i) =>
How can I avoid null value and get available data (in my case only three objects)
Filter the results again to remove the null elements from the second map:
render(){
const user = "name";
const qoutes = this.state.qoutes
.filter(q => !!q)
.map((qoute, i) =>
user === qoute.fields.user || user === qoute.fields.company ?
<QoutesListItem id={i} key={i} qoute={qoute} />
:
null)
.filter(q => !!q) // Filter falsy values
return(<div>{qoutes}<div>)
Do both conditional checks in a single reduce pass. If the quote is truthy and the fields has a match user or company for user, push the JSX into result array.
const qoutes = this.state.qoutes.reduce(
(quotes, quote, i) => {
if (
quote &&
quote.fields &&
(user === quote.fields.user || user === quote.fields.company)
) {
quotes.push(<QoutesListItem id={i} key={i} qoute={quote} />);
}
return quotes;
},
[] // initial empty quotes result array
);
const quotes = [null, null, null, {
fields: {
user: 'name'
}
}, null, {
fields: {
company: 'name'
}
}];
const user = "name";
const quotesRes = quotes.reduce(
(quotes, quote, i) => {
if (
quote &&
quote.fields &&
(user === quote.fields.user || user === quote.fields.company)
) {
quotes.push(`<QoutesListItem id={${i}} key={${i}} qoute={${quote.fields.user || quote.fields.company}} />`);
}
return quotes;
}, [] // initial empty quotes result array
);
console.log(quotesRes);
FYI, anytime you see map/filter or filter/map should be a good indication to use a reduce function.