#archive-library-discussion

1 messages ยท Page 11 of 1

wild flax
#

it's just so much more annoying to use for people ugh

#

Like I fully agree and we should do it

vagrant holly
#

If you were to add getters the end user wouldn't see much impact, but maintainer/contributor experience would suck a bit more

wild flax
#

but people are gonna give us an earful again for having to do ChannelTypes.GUILD_TEXT and why they cant do "GUILD_TEXT"

vagrant holly
#

Could just resolve that

#

But I'm also a fan of just moving folks to using constants like that anyway, hard-coded strings are bad

wild flax
#

Very hard with such a close-minded userbase that mostly learned JS from bad express tutorials

#

Sure you can just force it onto people, but still

vagrant holly
#

It could be for now that its done in a completely non-breaking way, so there are getters so accessing still returns a string, and resolvers for anywhere a user-provided value is ingested

#

And made more breaking with numbers-only later on

left moat
#

getters use memory too tho cryingmansad

wild flax
#

"Later on" will probably just be the rewrite anyway

#

but yeah, it will be hella annoying for people to do channel.type === ChannelTypes.GUILD_TEXT

#

most of our userbase is js beginners OMEGALUL

#

Honestly, you are right though

#

I should just not care

vagrant holly
#

tbf v13 is so incredibly breaking that a change like this would fit in

wild flax
#

But I do to a certain degree, I don't like death threats from 14yo

vagrant holly
#

i struggle to keep up with master when I update once a week LUL

wild flax
#

vlad wanted to do something like that

#

we still have approx a month or so until threads hit general availability

#

which is when v13 is supposed to hit anyway

vagrant holly
#

I'd like to think this'd have a pretty significant impact on d.js mem usage so would be worth it

#

But I don't actually have anything concrete to confirm that

wild flax
#

Its pretty clear that 700k "GUILD_TEXT" or "GUILD_VOICE" uses a lot more than 1 or 2

#

But not sure if it makes a huge difference

#

id rather be interested in extending cacheable props

#

so you can disable certain props on structs and theyd just return null or smth

vagrant holly
#

'GUILD_TEXT'.length * 2 * 700_000 = 14_000_000, channel type is currently 14mb

#

A number in JS is 8 bytes iirc, so 8 * 700_000 = 5_600_000 5.6mb

#

Seems like a decent saving on a single prop, so across all enums would be p good imo

wild flax
#

but what if you could just do

#
makeCache() {
  return {
    ChannelCache: {
       // amount: 200 -- Defaults to Infinity
       topic: false,
    }
  }
}
vagrant holly
#

Yeah that'd be nice

#

You can just update the prototypes for channel to add a null'ed getter & setter for topic though, so thats less of an issue

wild flax
#

Not as userfriendly honestly

#

but I guess yeah

vagrant holly
#

nod Yeah, for a ux perspective, being able to just disable the prop would be nice

#

Doesn't fix things like type though, cause a lot of folks need those, but they're still strings Sadge

#

Gonna test this on Res for a bit and see if I notice any diff in mem usage:

    // Store channel type as an integer
    Reflect.defineProperty(Channel.prototype, 'type', {
        get: function () { return Constants.ChannelTypes[this._type]; },
        set: function (value) { this._type = Constants.ChannelTypes[value]; },
    });

    // Store overwrites type as an integer
    Reflect.defineProperty(PermissionOverwrites.prototype, 'type', {
        get: function () { return Constants.OverwriteTypes[this._type]; },
        set: function (value) { this._type = Constants.OverwriteTypes[value]; },
    });
wild flax
oak quail
#

@vagrant holly i brought that up recently, Kyra said changing all types to numbers didn't impact memory usage

vagrant holly
#

Dropped about 100mb for me

#

Maybe something about what Discord was sending me happened to change as I rolled out the above, but it would appear to me that it does help

solemn oyster
#

It takes 10 bytes for text, 8 for length

#

Every reference to it is 8 bytes

#

Numbers inside objects are boxed, to prevent memory issues as well as preventing memory expansions in the middle of it, which means they're always doubles, and they always take 8 bytes

#

So the overhead of having this as a string, is a mere 18 bytes

vagrant holly
#

This is assuming that v8 decides to optimise the strings?

#

Aren't strings in most cases stored directly, not optimised in a map with pointers?

#

Oh, hm, the enum they come from is a constant object, so maybe v8 does optimise them, assuming type is always set from those objects

drifting knot
#

what about making snowflakes a bunch of opaque types

#

so that the type of a user ID is distinct from the type of a channel ID

remote wasp
#

on what basis, because they are the same thing

solemn oyster
#

@vagrant holly there's an internal hashmap for strings

#

Even if you don't use the constant, it'll be assigned to the same one, although there's also the one in the .data field from the compiled assembly, which ofc is a constant value

#

This is also the same behaviour in C# and several other languages, specially managed ones that have immutable strings

solemn oyster
drifting knot
#

?

solemn oyster
#

Simply, no

#

If this were C++ or Rust, sure, because RTTI is optional

#

But JS is RTTI heavy, and as such, the overhead of boxing primitives is enormous

drifting knot
#

uh

#

what are you talking about

#

opaque types

#

build time

#

typescript

solemn oyster
#

RTTI = RunTime Type Information

#

aka information that tells what each thing is instance of

#

Each value in JS has a header which tells the type followed by the data of the values

#

Primitives are specially optimised, because they can skip some headers, but they don't work when boxed due to alignment issues that can arise from mutating them with other types

drifting knot
#

are you reading my messages at all

solemn oyster
#

Yes

drifting knot
solemn oyster
#

And it makes no sense

drifting knot
#

okay ๐Ÿ‘ ๐Ÿ‘

solemn oyster
#

So I'm trying to make sense of it

#

You can't just make 2 types resolving to the same one and expect them to be opaque

#

Simply because type is called type alias in TS

#

The only way to make them properly opaque is by adding type-only fields that don't exist, I think those are called brand fields

#

But it can be troublesome, you see

#

In all servers, the everyone role id is the same as the guild's

drifting knot
#

BUILD TIME

#

ONLY TYPESCRIPT

#

I DO NOT CARE ABOUT JS USERS

solemn oyster
#

As well as in old ones, their created-by-default channel also has them

#

Dude are you even reading me?

#

What do you think "type-only fields" mean?

drifting knot
#

that's the only way to do opaque types

#

that is how everyone does it

#

it is a feature of the language

solemn oyster
#

Yes

#

And I'm telling you

drifting knot
#
type Snowflake = `${bigint}`;

type Opaque<T, N extends string> = T & { __opaque: N };

type UserSnowflake = Opaque<Snowflake, "UserSnowflake">;
type ChannelSnowflake = Opaque<Snowflake, "ChannelSnowflake">;

const user = "123" as UserSnowflake;
const channel = "123" as ChannelSnowflake;

console.log(user === user);
console.log(channel === channel);
// @ts-expect-error
console.log(user === channel);

behold, 0 cost typesafety

solemn oyster
#

It's baseless

#

IDs are shared in many places

#

A message ID can be a thread's ID

#

A role ID can be a channel's ID as well as the guild's ID

#

You'd have to typecast it all the time and it's annoying

#

I already know what branded types are, I use them in my bot to define i18next types

drifting knot
#

tell me more about your Discordjs Bot

solemn oyster
#

Outside this channel, probably in DMs

rough arrow
#

I see that discord.js-modules is using tabs. Is there a reason for this switch? Can I read about discussion anywhere?

wild flax
#

Tabs are simply superior

#

If you want a "real" reason read this.

#

But either way, point still stands. Superior.

rough arrow
#

I see, I can't imagine this was the reason that discord.js switched though. Is it just preference among the core developers and this is something that they've wanted to do but can't because it would ruin the history?

copper laurel
#

I mean youre talking to a maintainer of discord.js who just gave you the reason

#

and you think its not the reason because...?

solemn oyster
#

In the topic of memory usage (that IPv4 has brought over), should we look into removing TextBasedChannel#_typings? Its removal has a pretty large memory impact

#

Alternatively, we can do a global WeakMap<ChannelId, Set<UserId>>, which has near to no memory effect for those not using typing intents, if we need it

#

Or a Map and remove it in CHANNEL_DELETE/GUILD_DELETE just like we do with the global channel cache

#

The structure would ideally self-sweep if an entry removal ends up to 0 user IDs, so we don't memory leak

wild flax
#

@outer raven no we don't know, thats exactly why

outer raven
#

I don't see any reason why they would do that though? The row doesn't even have any special properties it's just a place to put components in

solemn oyster
#

It's Discord, they can implement new features without notice, say, padded columns within a row with a different type

#

And we'd have to support 2D arrays as well as different row types, but the former always resolving to the first row type

outer raven
#

Well I assume the nested arrays would always only contain components and not those columns, and Action Rows would always be the default when a nested array is passed

solemn oyster
#

Rows having a type identifier suggests they're going to add new ones in the future

outer raven
#

They have a type identifier just like buttons and select menus, but, unlike channel types, there is no gap between the action row number type and the others

copper laurel
#

having a default to only one type of row is exactly the sort of misleading interface we wanted to remove

#

like the send overloads

outer raven
#

But why remove things that make people's lives easier? I have a system rn that I genuinely don't think I can use action rows for and the nested arrays were the perfect solution

copper laurel
#

For all of the reasons listed above

wild flax
#

Why are people like this

copper laurel
#

Because Discord might introduce new types of rows
Because we might have to support 2D arrays and cant if theyre being interpreted as rows
Because we don't want two different interfaces for different types of rows

wild flax
#

And yes, you can easily have your system work with non-nested arrays

#

Because you can literally just replicate what we do when you pass a nested array

copper laurel
#

a raw object is barely more code

outer raven
copper laurel
#

because we cant make breaking changes later

#

dont want to have to do a v14 just for one row type

outer raven
#

can't you just do a 13.x?

copper laurel
#

not to remove something

#

do you understand semver?

solemn oyster
#

Removing features = semver major = v13 -> v14

amber basalt
#

What is semver?

solemn oyster
outer raven
#

Is v14 gonna be the TS rewrite or is it still gonna be on js

solemn oyster
outer raven
#

Alright then

copper laurel
#
- components: [[button]]
+ components: [{ type: 1, components: [button] }]`
outer raven
#

I have an array of arrays of buttons though, and I can't possibly predict how many buttons I have in total
Can you at least provide an alternative in the PR description?

vagrant holly
copper laurel
#

if you want me to copy and paste that, sure

outer raven
copper laurel
#

why dont you show what youre doing now, in buttons channel, and I'll give alternative

outer raven
#

sure

copper laurel
#

The alternative is literally just doing what the library does now internally with a nested array really, which is mapping it to components

remote wasp
#

What was the reason for not removing interaction event instead of deprecating it. We had no reason to keep support for something that wasn't even released yet ๐Ÿค”

ornate topaz
#

pretty sure it wasn't just a niche no one knew about. we literally made channels for v13 support

remote wasp
#

Yes a lot of people would have been affected from it but interaction were added on master and were changed on master, so there is like no need to keep the bloat around when we already are near a major release and have already broken some really big parts of the lib

ornate topaz
#

and the "bloat" is singular boolean variable?

remote wasp
#

it's not how small or big it is, if we can break stuff that has been here for a long time then why keep support for something that had been just added to the lib.

copper laurel
#

just because of the scale we expect its been used at

#

and there isnt much harm in that existing

remote wasp
#

I understand where you're coming from but this all seems like a problem we are pushing over to the future. We have a chance to rename both the events as people will likely rewrite their bot for v13.

copper laurel
#

it isnt a problem though

#

it wasnt a problem for 12 versions and it isnt now

solemn oyster
#

True, @dev releases have around 5000 downloads a week

remote wasp
#

It will be a problem if we decide to remove the deprecated events somewhere in future. I'm just saying that we have a chance of making breaking changes then why not break this too. It's not like users can get anymore angry, given that we already broke a lot

copper laurel
#

we'd remove them in v14

#

thats what deprecations are

remote wasp
#

If that's what the majority decision is then okay ๐Ÿ‘

wild flax
#

It will be a problem if we decide to remove the deprecated events somewhere in future.
I don't understand lol

#

How

tacit crypt
#

Right now you get a warning if you listen to the deprecated events, just like you get when you used a deprecated function in v11 that was removed or replaced in v12. In the future, they'll probably (I'd assume so) be removed, so you have a gap of time for people to update their code

remote wasp
#

I was curious about the decision so I asked, I'm okay with the change now because it seems like everyone supports it and it wasn't something that just made it's way into the master (for eg inGuild typeguard). I would like to make myself clear tho, I was thinking that we deprecate things when we introduce a replacement without a major release and then remove it on the next major release. I get it now.

For crawl's question, I meant it would be a problem if we remove it in the next version and someone who still uses the deprecated events will have to make change but that's their problem. Yeah that's it.

wild flax
#

Yeah but considering they get a warning in the console even

rough arrow
# copper laurel and you think its not the reason because...?

Don't get me wrong, using tabs to accommodate for the visually impaired is a pretty good reason for one to use tabs. But was that one of the arguments brought up when this was discussed and ultimately decided?

I really just want to know what lead up to this decision so that I can weigh the benefits myself and make better decisions for my own projects.

ornate topaz
#

the reason that was given doesn't really have any counter argument

#

or maybe you have some arguments against tabs

#

and by the way, yes. that reddit post was brought up few times before. it's not like it was random and crawl just now searched up something to give you.

rough arrow
rough arrow
#

Thank you!

outer raven
#

I think it would be quite beneficial to a lot of users if the messageReactionAdd, messageReactionRemove and typingStart events sent a GuildMember object along with the user object that is already sent so that they can more easily manipulate the member. Of course this would be nullable since the member may not be cached or the event may not be triggered in a guild. What do you guys think?

tired valve
#

If you want a member grab it from a guild

outer raven
#

The current event is already getting the user from a member most of the times so why not just send the member as well?

ornate topaz
#

"along user"? sounds pointless when you can do member.user

#

and then you realize that all those events can be received in DMs

outer raven
ornate topaz
#

does the library even emit any empty data for current state in any place?

#

it would seem weird that you sometimes do get member, sometimes you don't.
with current way you are going to get an error at message.guild if you are going to carelessly assume it was in a guild and get/resolve anyway

outer raven
ornate topaz
#

what

strange igloo
#

I think they meant to have that kind of signature for typingStart handler
(TextChannel, User, ?GuildMember)
Or
(TextChannel, (User | GuildMember))

ornate topaz
#

the latter is just going to be confusing, since you have a parameter that can have 2 completely different from each other objects

#

the former i think falls onto what i already asked

#

does the library at any place states that it can emit data that can be missing (and explicitly stating that it's not old state in *Update, which is cache dependant)

strange igloo
#

I don't really agree with that at all tbh
That job could be done by the end user with a single line

const member = channel.guild?.members.resolve(user.id) ?? null```
wild flax
#

resolve already defaults to null upsidemmlol

#

if the concern is cache though, you want to fetch anyway

strange igloo
#

If guild not here it would set it to undefined (?)

ornate topaz
#

regardless, !member

#

but in your own variable, obtained from objects that are always going to be given

oak quail
#

since the member may not be cached
well this isnt an issue for message reaction add or typing start

#

discord never sends a user object in those, it only sends user_id and the member if it's in a guild

#

djs not providing the member object is inconsistent with the gw, and the fact that it caches the member when those events are received is hidden behavior

copper laurel
#

This has been discussed before and decided that we didn't want either of these interfaces
client.on("messageReactionAdd", (reaction, user, member or null) =>
client.on("messageReactionAdd", (reaction, user or member) =>

#

discord.js caches the member when data is received, so reaction.message.guild.members.cache.get(user.id) should always work if sent in a guild, intents etc

boreal vapor
#

The UserFlags "EARLY_VERIFIED_BOT_DEVELOPER" reports to the Djs as "EARLY_VERIFIED_DEVELOPER"

I tested with two users with this flag (The second command isn't there because I used Edit), maybe this is better suited to the #archive-site-discussion, srry if is

oak quail
#

thats because youre on v12

#

its EARLY_VERIFIED_BOT_DEVELOPER in v13

boreal vapor
oak quail
#

well v12 is done, it wont be changed

real jetty
#

Why was sending a MessageEmbed by itself removed from .send in favor of using an array in messageoptions? Seems like a weird choice considering more likely then not you're only sending one embed at a time.

wild flax
real jetty
#

interesting, thanks for the read

rough arrow
#

I am not sure what to do with my PR
https://github.com/discordjs/discord.js/pull/6085

Looking into the source of MessageManager, the resolve method looks like this ```js
resolve(idOrInstance) {
if (idOrInstance instanceof this.holds) return idOrInstance;
if (typeof idOrInstance === 'string') return this.cache.get(idOrInstance) ?? null;
return null;
}

There's no way to detect an API message, because it is simply an object (not an ES6 class). The only way to know is to see if it has an `id` property and then try to use that, but that has side-effects of accepting anything with an id.

So I guess I'll just add move it to this union (instead of inside MessageResolvable)? ```js
deleteMessage(message: MessageResolvable| APIMessage | '@original'): Promise<void>; 
remote wasp
#

You already know the answer, do what you suggested in your last comment on the PR i.e. change the typings for deleteMessages and add APIMessage to it.

Changing MessageResolvable will create ripple effect for just a niche change.

#

The Webhook#send returns APIMessage along with MessageResolvable, you let deleteMessage take APIMessage along with the same

bitter peak
#

Used like so: (/command single <name?: string>)

fallow crater
#

maybe it could be implemented in @discordjs/builders?

bitter peak
#

@tacit crypt what do you think about this ^?

tacit crypt
#

Dunno... builders are more... take user-friendly input and emit raw API data. But I'm not opposed to utilities like that. Thoughts @wild flax @solemn oyster?

wild flax
#

hmm

#

I don't mind transformers

#

the only requirements would be:

  • Takes interaction.options and returns a completely new collection (no mutation)
  • Using ow for validation, since we already do that in builders
#

The first point might be irrelevant anyway, since all it does, as I see it, is parsing it

bitter peak
#

Yeah, the way I have it currently, it just provides methods for parsing the options into native data, and subcommands into another resolver class

#

Is that or a transformer of some kind preferable?

wild flax
#

Nah, I mean thatd be fine

#

I just wouldnt know what to call it or where to put it

bitter peak
#

oops sorry for ping person

#

discord.js@dev*

wild flax
#

djs types are required?

bitter peak
#

CommandInteractionOption

#

and User etc.

wild flax
#

hmm

bitter peak
#

Maybe this would belong in the main lib instead?

wild flax
#

probably so yeah

bitter peak
#

Should it replace CommandInteraction#options?

#

Where the current raw collection would be something like rawOptions

wild flax
#

Neither are raw

bitter peak
#

Hmm, maybe optionsCollection or something then?

#

Or make it a property of the resolver

wild flax
#

I think it should be opt-in if anything

bitter peak
#

alright, should it be like, manually constructed or should it be a property?

wild flax
#

I don't necessarily wanna decide this alone so uh

#

cc @copper laurel @vivid field

copper laurel
#

Does Discord always resolve the data that reliably to be throwing if its not there?

#

This wrapper is essentially just allowing us to get a properly typed value from the options collections right?

#

So intead of .get("name").value: string | number | boolean etc, we'd use .channel("name"): GuildChannel | null

bitter peak
#

correct

#

Also has support for subcommands and subcommand groups

copper laurel
#

So, for the most part I think I like this, but I also don't really like the fact that it's a Collection at all currently, I'm not sure what benefit changing it to that in the first place provided other than .get("name") being ever so slightly easier than .find(o => o.name === "name")

bitter peak
#

It also doesn't really make sense to me to be a collection

copper laurel
#

So for me personally, I think this is a far better implementation than the options collection, except for the fact that it doesnt provide access to things like the raw values that I can see (unless you went via resolver.options.get?)

wild flax
#

yeah I wouldn't like being forced the validation on me

#

I have a transformer for the current collection that just .get() values, I don't care if they are nulled

copper laurel
#

My other concern is the method names, while this does seem like the simplest naming convention I'm pretty hesitant to have methods that are name the same thing as properties all over the rest of the lib, like .user(), .channel() etc

#

We got rid of guild.member() and people used to use that incorrectly all the time

#

Definitely support the concept though

bitter peak
#

My idea would be to have
CommandInteraction#options: CommandOptionResolver
CommandOptionResolver#data: Collection<string, CommandInteractionOption> -- (or CommandInteractionOption[])

So for most users, they could use interaction.options.channel("target").
And for users that need the raw collection/array, use interaction.options.data.

Also, how about get*() instead? getUser(), getSubCommand(), getString(), etc.?

copper laurel
#

probably yeah

wild flax
#

I'd still like to keep a generic get()

copper laurel
#

Its a little wordier but it makes them clear that theyre methods and not props

wild flax
#

that does no validation

copper laurel
#

Wouldnt that be interaction.options.data.get

bitter peak
#

^

copper laurel
#

or do you want to promote it up

wild flax
#

I wouldn't want to expose raw data at all

#

we don't do that anywhere in the lib rn

copper laurel
#

Well that "raw" data is still the parsed collection

bitter peak
#

So have CommandOptionResolver#get(name: string): CommandInteractionOption | null?

copper laurel
#

actually

#

Doesnt this already do that

wild flax
#

yah

bitter peak
#

oh yeah

copper laurel
#

cool

bitter peak
#

forgot I added that lmao

wild flax
#

id like to keep that

copper laurel
#

as long as required defaults to false

vivid field
#

I really like the concept too, but I don't really get the purpose of validating the data again. Shouldn't you as the bot owner know which option has which type?

wild flax
#

yeah but TS

#

people would (like me) have to write their own type parser or smth

#

or constantly cast

#

which is really a sub-par experience lol

vivid field
#

Sure we can keep the methods, but e.g. the required parameter seems unnecessary to me (or maybe as a type parameter)

copper laurel
#

People like me would have to steal Crawl's type parser

bitter peak
#

A lot of command options are required, so having to check in code that it isn't null gets annoying fast

#

It's a lot easier to just add a , true) to when you get the option

#

imo at least

#

It can also help devs catch bugs where their command code doesn't match their command option data

vivid field
#

Yea agreed, but I think a type param would be better suited here

bitter peak
#

Instead of just getting null, they would get an error

#

hmm

#

I've run into the issue a few times where I update my command's ApplicationCommandData, but forget to change something in my actual execution code. The errors are usually really annoying to track down because I would just get back null when I tried to access an invalid option. With the required parameter, it would help catch those errors better, in addition to the inferred types without adding another generic.

#

I'd argue that trying to get an invalid option should be an error, and it's better for the library to handle that error than for the user to. There's no use case at all for fetching an invalid option.

vivid field
#

But if you get cannot read property "user" of null or whatever, shouldn't you know what's going on ๐Ÿค”

#

Well anyway, I'd say you can implement it and then it can still be changed afterwards

bitter peak
#

(just added the property parameter to _getTypedOption, it would be added to all the calls as well as "value", "channel", "options" etc.)

wild flax
#

maybe im too tired but

  get(name, required = false) {
    const option = this.options.find(opt => opt.name === name);
    if (option) {
      return option.value;
    } else if (required) {
      throw new Error(`Missing required option "${name}"`);
    } else {
      return null;
    }
  }

get(null, true) will throw?

bitter peak
#

yes

wild flax
#

ah right, makes sense

#

I was reading that in a very dumb way OMEGALUL

#

yeah the private method is ok

#

reduces code duplication

vivid field
#

Just a few minor nits but the concept LGTM

#

Actually couldn't this extend BaseManager instead of being its own structure?

#

Or are those meant to be for api methods only

copper laurel
wild flax
#

ah yeah

#

it should probably do that

copper laurel
#

I really dont see how this aligns with a Manager

#

Otherwise yeah I think so

loud jayBOT
#

pr_draft #6107 in discordjs/discord.js by shinotheshino created 56 seconds ago (review required)
refactor(CommandInteraction): create CommandInteractionOptionResolver
๐Ÿ“ฅ npm i shinotheshino/discord.js#commandoptionresolver

bitter peak
#

lmk what changes are needed :]

#

@wild flax @vivid field @copper laurel

vivid field
#

I'll look at it tomorrow ๐Ÿ‘

bitter peak
wild flax
#

@bitter peak can you fixup the spacing in the typings file

#

we don't usually space out props/methods like that

bitter peak
#

prettier did that sorry

wild flax
#

prettier surely didn't add newlines

#

:P

bitter peak
#

op lemme check

wild flax
#

don't really, usually, need to separate privates from publics like that

#

and they are usually at the very top in other typings

#

its a bit inconsistent, but thats better anyway

bitter peak
#

so... this?

wild flax
#

yep yep

bitter peak
#

kk

sacred river
#

possibly add a way to add permissions on slash command creation if possible

zenith oracle
#

Iirc the API doesn't support this yet

copper laurel
#

Yeah, Discord has it as separate APIs, because it doesn't really make sense as one. At least not for globals

short crescent
#

Hello everyone. I thought for a long time about the fact that many of my favorite structures were removed. I had an idea of โ€‹โ€‹how this could be altered normally so that it would be convenient and understandable to use the extension of structures. Inside the library there are managers for each available data type. But the problem is that the GuildManager does not accept the holds parameter and is substituted inside. If we had access to replace it, then we could create classes inside the cache with the required fields and methods. Also, adding your executable method inside _add will allow you to make a query to the database for further data query. Maybe I'm wrong, but I would like to express my thought here

#
// https://github.com/discordjs/discord.js/blob/master/src/managers/GuildManager.js

// example
class MyGuild extends Guild {
  cool = true;
}

class MyGuildManager extends GuildManager {
  constructor(client, holds, iterable) {
    super(client, holds, iterable)
  }
}

class MyClient extends Client {
  constructor() {
    super(options)
    this.guilds = new MyGuildManager(this, MyGuild)
  }
}
neat bolt
#

no dont

#

i have so many things i want to say

#

this runs into the same problems and more as the original problem

#

and please separate your database code from your discord code why do you want these things to be coupled so badly i dont understand

#

its like you intentionally want to write unmaintainable code

short crescent
#

But on the other hand, why do we need to separate the data that logically relate to the structure and move it into a separate entity, or do you have options for how to do it more correctly and better?

neat bolt
#
  1. because it makes your code more maintainable
  2. it makes your code more readable
  3. it makes everyone's code more predictable
#

as for how, literally just put that code somewhere else; in a separate class, as separate functions, whatever

#

these stuff are basic software design principles

short crescent
#

That is, create many separate functions in which, as an argument, always pass the required structure by reference? I donโ€™t think this is the best thing to think of

neat bolt
#

it is

#

people who use Structures before and wish to continue it are too attached to the idea that just because its related it has to follow with a dot like foo.bar

#

that . is not magic

#

it doesnt make your code better just because its a dot

short crescent
#

Okay, I agree with that

neat bolt
#

please look up these terms to understand where you're going wrong: coupling, single responsibility, open-closed principle
and look up these terms for what you can do: dependency inversion & injection, repository pattern, functions

short crescent
#

Yep, i know

#

The extension approach itself is not good practice in general

#

Do you have examples of how someone has moved away from structures to another approach that is more correct in your opinion in order to study

neat bolt
#

see yuudachi/v3 @rich igloo

#

crawl never used structures in the first place

#

also its agpl so follow the license if youre gonna use its code

short crescent
#

Thanks for the answer. Yes, definitely

outer raven
#

Can someone tag shinotheshino please

#

Wanna ask them a question about their comment on their pr

zenith oracle
#

@bitter peak โ†‘โ†“

outer raven
copper laurel
#

Yeah, I dont think that's the same thing

#

Theres no reason for required but there's also kinda no reason for name either
I dont really think this resolver is quite the right approach for sub-commands, at least not the one #5880 was going for

#

interaction.options.getSubCommand() should return the name of the sub-command used

#

Or do what #5880 was going to do (I really shouldnt have shut that one down) and have that already parsed

outer raven
#

Yeah it shouldnโ€™t have any parameters at all and find the subcommand by the type

bitter peak
#

Hmmm, @outer raven @copper laurel I feel like the way it is makes for a much better control flow when being used (see the example). Maybe name could be optional, to allow for both ways? If name is set, behave as it currently does; if it isn't set, get the current subcommand (required). Thoughts?

copper laurel
#

I don't quite understand what flow you'd have where you'd want to try to get each subcommand by name vs just... getting the one selected. It's more like string choices.

#

Can you explain a bit more?

#

In fairness, I think Discords implementation of it is kinda shit, and I think the fact we left it in options is dumb. #5880 was actually good, I misread it originally

outer raven
bitter peak
#

I think it's just preference tbh; although handling groups seems like it's nicer with the way it currently is

copper laurel
#

etc

#

Especially for anything more than two sub commands

#

Or in this case since it is only two, you dont need to get both, you can if/else on either one of them

bitter peak
#

Well, first off you'd have to wrap the cases in braces if you want to have block-scoped variables, so switch isn't really a great option

copper laurel
#

sure

#

i guess

ornate topaz
#

that seems like a unrelated reason

copper laurel
#

youd be putting the ifs in braces too though

#

So that just seems like a style preference, nothing wrong with braced cases

ruby terrace
#

You donโ€™t have to use a switch, some people might break those out into completely separate functions even

copper laurel
#

Im just really struggling to see why anyone would want to have to get every possible subcommand and then check which one is defined

#

Sorry ๐Ÿ˜„

#

I feel like I must be missing something good in that design

ruby terrace
#

Especially since that requires a call to the api if you donโ€™t already have the command in cache

bitter peak
#

Well in that case would we pull the options up to the top level, or would we return a second resolver from getSubCommand()

copper laurel
#

cause i dont see it

#

Im okay with either - #5880 was top level

#

and I was kinda an ass about it and I was wrong

bitter peak
#

Top-level seems a bit more useful to me. Although, can't you have multiple layers of subcommand groups?

copper laurel
#

nope

#

You can have top level subcommands, or subcommand groups
If you have top level subcommand groups, second level is subcommands

#

Max 3 levels, group > subcommand > options

#

I have no idea what the use case for groups is tbh

ruby terrace
#

Stuff like this

copper laurel
#

Kinda off-topic for this channel though

outer raven
ruby terrace
#

Wow itโ€™d help if I sent the screenshot dead

copper laurel
#

eh, that could also be new remove-embed

#

i guess though

#

or whatever passes validation

ruby terrace
#

Thereโ€™s a lot more options, just canโ€™t show cause mobile

copper laurel
#

Off-topic, lets stop here

bitter peak
#

Before I push, does this seem good?

copper laurel
#

Im not sure why we need the getters

bitter peak
#

To align with the rest of the class and provide the error message

copper laurel
#

ahh you abstracted away options too

#

Yeah I think so

bitter peak
#

minus the typo on line 43 oops dogekek

copper laurel
bitter peak
#

yes I think so

copper laurel
#

and so... no options either?

ruby terrace
#

No, itโ€™s a subcommand

copper laurel
#

you can have a toplevel subcommand with a toplevel group?

ruby terrace
#

Canโ€™t shave a group without a subcommand, wonโ€™t even show up

#

Yes, you can mix and match

copper laurel
#

god damnit discord

ruby terrace
#

People always get confused when I say that lmao

copper laurel
#

Okay so.... _subcommand could be defined and _group not

#

But if _group is defined then _subcommand has to be defined, and in that group

ruby terrace
#

Yeah

copper laurel
#

/command bar foo
/command foo
These are both foo subcommand?

bitter peak
#

Hmm, that makes throwing an error in getSubCommandGroup kinda risky

copper laurel
#

but one has null group

ruby terrace
#

Correct

copper laurel
#

ughghufgubfhdjgk yeah that needs required then

ruby terrace
#

You cannot have a subcommand with the same name as a group IIRC

copper laurel
#

to control the throw

#

Fuck this design is terrible, why Discord

ruby terrace
#

*at the same level

outer raven
bitter peak
#

Yes

outer raven
#

hm i think it would make sense for those to be getters, you can easily remove the required param from the group function too

vivid field
#

I'd even argue that you can just make subCommand and group normal properties instead of using methods/getters

wild flax
#

Switches make for way better control flow

copper laurel
#

Yeah looks like we did, current debate seems to be it should still be a method at all

wild flax
#

Compared to?

#

a getter?

copper laurel
#

Properties

#

Why getters at all

wild flax
#

I don't know im just asking

#

Why properties

bitter peak
#

the rest of the class is methods, so it doesn't seem right to make this one a property

outer raven
#

the rest of the class works for multiple options, you can only have 1 group and 1 subcommand

zenith oracle
#

Why is APIInteractionGuildMember#permissions a Snowflake instead of Readonly<Permissions> like a normal GuildMember? Can I use new Permissions(<APIInteractionGuildMember>.permissions) to get the correct Permissions object?

vivid field
#

Because discord only sends the bitfield, discord.js then constructs a Permissions object out of that

zenith oracle
#

Well, yes about the first part, but if we have an interaction (let's assume a slash command), we may want to check the permissions of the member of the command using interaction.member.permissions.has() for example, but we can't since the .permissions can also returns a Snowflake...

vivid field
#

Then you can construct a Permissions object manually, sure

tacit crypt
#

Anything prefixed with API is usually a type from discord-api-types, which represents raw Discord data

zenith oracle
outer raven
#

@tacit crypt idk if you read the convo but the subcommand method isnโ€™t the same because you donโ€™t need either of the params

#

It should even be a property imo

tacit crypt
#

I mean from what I saw in the code, right now they're glorified getters

outer raven
#

Yeah, thatโ€™s what it should be

tacit crypt
#

neither of them should be functions nor getters

outer raven
#

A getter/property

tacit crypt
#

no getters??

#

why would you make it a getter

outer raven
#

Idrk

#

Idk what the advantages of getters are tbh so I wonโ€™t comment on that

tacit crypt
#
declare const options: APIInteractionDataOptionsIDoNotRememberTheNameOkSueMe;

if (options[0].type === 'SUB_COMMAND_GROUP') {
  this.subCommandGroup = options[0].name;
  const [subCommand] = options[0].options;
  this.subcommand = subCommand.name;
  this._options = subCommand.options;
} else if (options[0].type === 'SUB_COMMAND') {
  this.subCommand = options[0].name;
  this._options = options[0].options;
}
wild flax
void rivet
#

Hey, I'm new here so this might not be the channel for it but I wanted to talk about a certain feature implemented on discord.js (cache check)
So' this is regarding intents, the cache system and fetching
Now, with intents being a thing and discord.js enforcing the idea of specifically adding the ones you need, we can limit which events the client receives, which discord.js' cache depends on to stay up to date, and fetching also checks the cache first I believe
my example would be guild.members.fetch, which (unless explicitly told to skip the cache check ) checks the cache

  async _fetchSingle({ user, cache, force = false }) {
    if (!force) {
      const existing = this.cache.get(user);
      if (existing && !existing.partial) return existing;
    }
```keep this in mind
now a scenario, let's say a client has GUILDS, GUILD_MESSAGES and GUILD_MESSAGE_REACTIONS intents enabled
the (relevant) events they'll receive would be
`messageReactionAdd`, which discord.js will use to cache the GuildMember, at this point in the cache, there is a GuildMember instance for member x (our reactee)
 now note that since there isn't GUILD_MEMBERS, the client won't be sent events like guildMemberUpdate, so if i were to add a role (role1) on x, that wouldn't trigger the event, discord.js wouldn't be able to internally update the cache, right?
now, the fetch, what the fetch does is that it `const existing = this.cache.get(user);` just checks if the cache has the instance, that's enough to make it not call the api, it doesn't check if the data is up to date by my understanding
so in this scenario, I am fetching a member, however, it's not giving me the up to date data , so my question is and what I wanted to talk about was if this cache check feature is useless now or if i'm misunderstanding it of course
zenith oracle
#

Well, there's a force option for a reason. You (usually) don't need to fetch a user if you have it already in the cache, but if scenarios, like the one you showed, occurs, we can use the force option to ignore the cache...

copper laurel
#

Avoiding API calls you dont need to make is never useless, but yes, if you've chosen not to receive events that maintain the state of the cache, then you also need to make sure you're not relying on cache where appropriate

ruby terrace
#

Another option is with custom caching if you don't set GUILD_MEMBERS intent for example you can set GuildMemberManager: () => new LimitedCollection() that way it never caches. I think that would probably be the intended use, not 100% sure though.

void rivet
#

I know the force exists and I have mentioned it before, however,I don't think the users were made aware of this, that even the fetch which's point (i always thought) was to give you the up to date data
in this case, a possible solution may be tweaking cache check to check if the client had the necessary intents passed on
like for this, we need GUILD_MEMBERS, since we want the guildMembersUpdate event, (theoretically that should keep the member cache up to date)
so checking Client.options.intents, if it has the GUILDS and GUILD_MEMBERS intents should do it?

  async _fetchSingle({ user, cache, force = false }) {
    if (!force) {
      const existing = this.cache.get(user);
      if (existing && !existing.partial && this.client.options.intents.has(["GUILDS", "GUILD_MEMBERS"])) return existing;
    }
``` is this a possible workaround to it?
copper garden
#

Pretty sure intents doesnโ€™t apply to fetching a single member

void rivet
void rivet
# void rivet Hey, I'm new here so this might not be the channel for it but I wanted to talk a...

@copper garden the original issue was that with a few certain intents disabled, the cache stores an entry however it's no longer updated (since the cache depends on certain events to stay up to date and not having intents means the client won't get said events)
now the "cache check" only checked for const existing = this.cache.get(user); an entry
and if it found one regardless of if it's outdated, it would return that instead of an api call, yes I am aware you can force and I mentioned that above too
Yes intents may not affect calling the api, but not having them could result in discord.js returning the outdated instance even before reaching to call the api

ruby terrace
#

I get what you're saying about checking intents btw, just not sure if its the right design decision

void rivet
#

that would completely discard caching in this case, right?

ruby terrace
#

yes

void rivet
#

honestly don't know if it's better but what do you think is wrong with checking the intents?

ruby terrace
#

hmm, actually I have an idea, default force to !client.options.intents.has(['GUILDS', 'OTHER_RELEVANT_INTENT']), that way the dev can still pass false or true if they want. The issue I have is the way that things are intertwined, like you need guilds for pretty much everything for example. Figuring out what depends on what isn't really something each manager should have to worry about

void rivet
#

I get that you are trying to say, if we disable the cache, it won't store the data, since at this point if the intents aren't there, this specific cache seems useless

ruby terrace
#

although to be fair, you won't have a guild member manager without a guild, so you don't necessarily need the guilds intent check, plus you could've fetched the guild

void rivet
#

i feel like, this may have unexpected side effects
intents checking there would be safer i feel like

void rivet
wild flax
#

You never get outdated data

#

Data just happens to become outdated without intents

#

If you do not turn on specific intents, itโ€™s your job to make sure to constantly fetch a member IMO

#

I donโ€™t think the library should hardcode intents everywhere now to circumvent that

wild flax
#

Yeah that seems the most sensible approach

void rivet
#

Yeah I agree that intents shouldn't be hardcoded and that was never recommended

#

I am still relatively new to this library when it comes to internal stuff
but judging by what I'm seeing so far, disabling this particular cache completely may be a workaround
However, there is a specific thing I used to use which I feel like is now more complicated
As you know messageReactionAdd only gives you the MessageReaction and the User, however, the API's event actually gives the client a member object( of the reactee)

client.on("messageReactionAdd", (reaction, user) => {
  const member = reaction.message.guild?.members.cache.get(user.id)
})
``` I would've been pretty confident that it's cached since I know the api gives you the member
however here, I need to make the fetch
so here, I am required to make an api request I know could've been avoided
also I would like to ask why this event doesn't have an optional third parameter or something to pass on the member
wild flax
#

Yeah this is what I don't get about this explaination

#

Doesn't this indicate the member gets always freshly cached?

#

So you would only be hitting the problem if you try to use the members cache OUTSIDE of the event

void rivet
#

I am talking about the suggestion that says the lack of required intents would result in the cache being disabled completely

wild flax
#

Well I don't see this as a needed solution

#

I am talking about your original concern

#

If you use

client.on("messageReactionAdd", (reaction, user) => {
  const member = reaction.message.guild?.members.cache.get(user.id)
})

the member will always be up2date in this event. No matter what.

#

If you however start using it in different places, without:
const member = await message.guild?.members.fetch(id, { force: true })

void rivet
wild flax
#

Yeah youll run into issues

#

But I am asking myself, why would you knowingly use the cache (.cache) if you know you won't get GUILD_MEMBER_UPDATEs

void rivet
#

in this case because I know the api is giving me the up to date member in this event

wild flax
#

Yes, but the member in the "event" will always be up2date

#

Since we update the cache when we get a new member from this event

#

That doesn't mean other events wouldnt update it, and since you knowingly didnt enable them..

oak quail
wild flax
#

Yeah just do

void rivet
#

I'm a bit lost here, could you give me an example of what you mean?

wild flax
#

I did above

void rivet
#

I am aware that outside of this event, it's outdated

wild flax
#

Yeah, and inside of it it will always be the latest up2date member

#

and the cache will be updated

#

Since even if it exists, we patch the new data on top of it

void rivet
#

exactly, I know that
but i was talking about the cache being disabled completely, and said that this would be a side effect
if you disable the cache completely, (limit it to 0 entries) this member won't be cached

client.on("messageReactionAdd", (reaction, user) => {
  const member = reaction.message.guild?.members.cache.get(user.id)
})
``` and here, member will be undefined
wild flax
#

Well yes

#

But that is a deliberate decision of yours

#

No?

#

Why would you access a cached instance if you know you disabled it

#

I feel, if anything, we should do more educating on that behaviour, but not change code because of it

void rivet
#

I wouldn't, that was an example, saying that here I am avoiding to call the api since it's definitely not needed
Be right back

wild flax
#

But you just disabled cache?

#

So your decision is to prefer API calls

#

messageReactionAdd is also kind of an odd child here because its the only event that actually gives you a member from the API, but we don't directly expose it

#

But thats a rather interesting design decision

#

If the problem exists anywhere else, I'd happy to discuss this further, but if its only this event I would rather fix the event by possibly returning (reaction, user, member?) =>

#

Even if its a very awkward interface lol

ruby terrace
wild flax
#

But sure, preferably we should fix all of them

copper laurel
#

But there's a valid point about not receiving it or caching it at all I guess

wild flax
#

Might be worth to revisit

#

Esp with disabling cache

copper laurel
#

Fetch is always an option anyway

wild flax
#

Yeah but its kinda pointless to fetch if you technically get the data lol

#

We just don't expose it to you

copper laurel
#

I definitely prefer a third sometimes-there param to inconsistent types on the second

wild flax
#

Yeah, I wouldnt wanna do user/member

void rivet
void rivet
# wild flax Even if its a very awkward interface lol

I am new to typescript/javascript in general I guess so I don't quite get what you mean by an awkward interface
sorry if this is dumb but I would like to know what you mean by that
to me "GuildMember | null" seems fine when compared to presenceUpdate where the first parameter is Presence or null

wild flax
#

The โ€œUser | GuildMember | nullโ€ would be a very awkward one

#

Since it canโ€™t be a member in dms

void rivet
#

Yeah that would be awkward, I thought you were talking about the one with the third parameter

oak quail
#

for user banner color discord returns a hex string ("#123456"), which is inconsistent with embed colors. should djs just return that or also have a getter for the color as a number?

wild flax
#

You could check on the roles

#

We have a displayColor thing there I think

#

And also a hex prop

zenith oracle
#

Roles have a color property in base 10 and hexColor getter in hexadecimal

oak quail
#

roles do the same thing embeds do ^

#

bc discord returns a number for role colors too

wild flax
#

Then we should copy that behavior IMO

oak quail
#

but banner colors are inconsistent for some reason

wild flax
#

And just transform discordโ€™s stuff

#

Or maybe ask in the docs PR

#

Fucking dumb

#

(โ•ฏยฐโ–กยฐ๏ผ‰โ•ฏ๏ธต โ”ปโ”โ”ป

oak quail
#

so would hexColor be the property and color be the getter, or should it store the converted color and add a hexColor getter to un-convert it

#

i'll also comment on the docs pr

void rivet
#

I feel like I made this conversation drag on for a long time, sorry, it's 5 am here I can't think but I'll end it with what i came out with from this
fetch should check client's intents, if it seems like the data might be outdated, skip the cache check, by my understanding, users would expect the latest data, the reason why I am not okay with completely disabling this cache is because i feel like it'll lead to unnecessary api calls like with the event I mentioned
Lastly, the users should be told that the intents don't just disable some events, it also causes cache issues internally. They should know what they're doing and what to expect.
Thanks a lot for this conversation, I explored and learned a lot thanks to all of you, goodnight

wild flax
#

Probably no transform for the raw data

#

and transform into an int via the getter in this case

#

Actually annoying how backwards it is

#

Literally ass

oak quail
#

oh

#

user#fetch(true) is broken lol

#

ig i'll fix that in a different pr so it can get merged faster

raven cloak
#

Have channel type names been made uppercase?

oak quail
#

yes

raven cloak
#

Why so?

#

Consistency?

oak quail
oak quail
#

since u have to fetch the user to get user banner & banner color (its not always on the user object)

#

should I add fetchBanner, fetchBannerURL, fetchBannerColor, and fetchHexBannerColor methods, like the current fetchFlags?

wild flax
#

Wait what

#

Why not just fetch banner that populates all of those

oak quail
#

what should it return tho

#

just the banner hash?

wild flax
#

void mmLol

oak quail
#

isnt that just fetch(true) tho

wild flax
#

I dunno, the patched user?

oak quail
#

i mean

wild flax
#

I don't even know why we don't do that everywhere

oak quail
#

fetchFlags is essentially just fetch(true) that returns .flags

#

i assume theres some situation where user.flags exists but isnt sent

wild flax
#

If we need to fetch something, best case scenario we should just return the patched user

#

cc @tacit crypt @solemn oyster

tacit crypt
#

So fetchBanner() = this.fetch()?

wild flax
#

yeah

#

essentially

#

lol

#

I know its awkward but I just don't like arbitrarily returning data based on the fetch

tacit crypt
#

I mean at that point we would drop fetchFlags and just tell users to call user.fetch if null

wild flax
#

yeah thats even better

tacit crypt
#

What do with emojis/stickers then

oak quail
#

what about them

#

how are they relevant ๐Ÿค”

tacit crypt
#

fetchGuild

wild flax
#

well for all of those

oak quail
#

oh no not this again

wild flax
#

we should really just return the whole instance

#

it feels so awkward and dumb to have 5 methods just to return 5 strings from them

#

we don't do tell people to do .fetchFull() either when its a partial right

#

and then return everything but the id

#

because its always there on a partial

#

lol

oak quail
#

so on the banner properties should I add a "The user must be force fetched" note?

tacit crypt
#

Well, force isn't really needed cuz it defaults to true anyways

#

But the main fun factor is how you'll differentiate from no banner and no fetched banner

oak quail
#

cuz it defaults to true anyways
no it doesnt?

#

afaik its always been default false

tacit crypt
#

Not in the manager function, in the user function

oak quail
#

false there too

#

wait wtf is the point then

tacit crypt
#

I mean it should probably default to true, because partial users

#

@wild flax @solemn oyster ^ thoughts

wild flax
#

yeah imo on instances it should default to fetch(true)

#

but on managers it should be fetch(false)

oak quail
#

should i change that in the fix pr too

wild flax
#

Sure

oak quail
#

done

oak quail
#

made the banner pr

raven cloak
#

Why did presence get moved to member, rather than user

#

Even on Discord API side

#

What made that change

wild flax
#

A presence is never on a user

raven cloak
#

It was in v12

wild flax
#

That was us doing it

unique axle
#

it was always just a getter that tried to retrieve the presence from the member on the first guild it happened to stumble across
however there were edge cases that resulted in this not working and a surplus of the suggestion "use message.member.presence instead of message.author.presence"
presence data is inherently guild based data and we probably should've never started to provide it to the user

wild flax
#

you also get presences when being friends, but since thats not a thing with bots

clever spoke
#

is there any chance djs would add undocumented features?

raven juniper
#

no - see stuff like mobile indicator

clever spoke
#

understandable

real jetty
gusty carbon
#

Would it make sense for the library to add a Message#removeComponents method, similarly to how there is Message#removeAttachments?

copper laurel
#

tbh I thought that was kinda a silly method since all it does is message.edit({ attachments: [] });

rough glacier
#

Yeah, it doesn't really make sense for only this to exist. It's the same thing with removing components. Either we create one for everything (components, embeds, attachments) or none

gusty carbon
#

yea thats what i thought too
but if we keep it we might as well add the components method

clever spoke
#

what if we removed Message#removeAttachments

#

I guess maybe it doesn't make sense because its not causing any harm

real jetty
#

or a singular method taking arguments as to what to remove?

gusty carbon
#

would probably be the best decision, it doesn't really provide much value

gusty carbon
rough glacier
#

Then again, lib enforces using methods rather than constructor objects in guides. In embeds for ex, it's proposed to use emb.addDescription().addField() rather than passing values to embed constructor. It could make sense for a similar approach msg.removeEmbeds().removeComponents()

clever spoke
#

what about something like message.remove(["embeds", "components"]);
like Mega said

gusty carbon
#

that seems kinda janky honestly, imo i would either add both methods or remove removeAttachments

real jetty
copper laurel
#

No

#

Just confirmed - the reason that exists is because you can remove the attachments from other people's messages if you have MANAGE_MESSAGES

#

Much like supressEmbeds also exists

#

We aren't going to add helpers for every other edit of your own messages

gusty carbon
outer raven
#

@oak quail did u change anything in the icon function?

oak quail
#

dynamic wasnt in the typings before

#

that change was copied from the old banner pr

zenith oracle
outer raven
#

alright but can you remove 'default' then? I have no clue what that's supposed to be but it doesn't work

outer raven
#

btw @bitter peak about your command options PR, can you please make all functions return undefined instead of null just like collection.get does?

copper laurel
#

yeah should the default format be webp or something

#

null is more correct for those imo

outer raven
wild flax
#

they arent a collection

#

why would it matter

copper laurel
#

they are methods, not a hashmap

outer raven
#

because undefined is more accurate than null

copper laurel
#

no, i dont think it is

wild flax
#

based on what

outer raven
#

because these options were not specified by the user who ran the command

#

it's not like they were specified as null

wild flax
#

imo null is always more accurate

#

we only use undefined for collections because map does

#

to not completely break the map interface

copper laurel
#

undefined is a dumb type that doesnt exist in a lot of other languages

outer raven
#

but it's way easier to work with undefined types because if you have an optional parameter in a function you can't assign null to that

wild flax
#

?

solemn oyster
#

?

#

We use undefined only when exclusively needed

bitter peak
#

Null is more of an explicit empty type whereas undefined is an implicit one. It's pretty much always better to use null for things like this unless it breaks something else

solemn oyster
#

Such as skipping fields from JSON.stringify

tacit crypt
#

Why are subCommand/subCommandGroups not... properties meguFace

copper laurel
#

they are. private properties lol

tacit crypt
#

Not what I asked

#

They shouldn't be glorified getters

copper laurel
#

oh I know

wild flax
#

I dont see why not

copper laurel
#

I dont know why theyre private and not just properties

outer raven
wild flax
#

thats more a you problem

#

null can be a default value for optional params

outer raven
#

yeah but why do this

function example(param: string | null) {}

when you can just do this

function example(param?: string) {}
wild flax
#

bruh

copper laurel
#

we.... dont

outer raven
#

that doesn't even make sense because you want the param to be optional, not null

outer raven
copper laurel
#

dont write shit functions

#

dont pass variables you havent checked to functions

wild flax
#

function example(param?: string | null) {}

#

is the best of both worlds

#

and youd need to do it anyway

#

since not everything returns undefined

outer raven
#

yeah but the | null there is redundant

wild flax
#

no it isnt

#

if you ever pass a null value the ? wont allow it

outer raven
outer raven
wild flax
#

it doesnt mean the same thing

#

undefined is not null

outer raven
#

then what's the difference

wild flax
#

learn js thats the difference

outer raven
#

I don't get why you guys like the null type so much it's just annoying to work with

copper laurel
#

undefined is not the lack of a value. Its the lack of any definition at all, a property not existing in an object, etc
null is a specific, defined return value denoting that everything was successful, but theres no value found. A function should not be returning undefined, it should return null when it has no value to return because it deliberately, knowingly has no value

#

Because its correct

#

If JS wasn't such a loose language, undefined wouldnt exist. Accessing undefined things would throw errors in a good language like C#

outer raven
copper laurel
#

But this is a command option resolver

#

A resolver function should return null

#

accessing a property that isnt defined yes, should return undefined but thats not what youre doing

#

You dont have to use the resolver if you want the map-like behaviour

#

You're drawing a false equivalency to an option being null, and an option resolver method returning null. They arent the same

outer raven
#

But Discord will never allow you to pass null to any type of option, it only lets you not pass it at all and the API doesn't pass anything as null either, so why change that behavior?
You could simply try to find the option in the array and return that regardless of the outcome (meaning that you wouldn't be explicitly returning undefined, it's just what the find function returns if that makes sense)

wild flax
#

What has that to do with sending something to discord when we are transforming received data?

#

We strife to write good and maintainable code, not some mess.

#

undefined is a mess that should not exist in the first place, the only reason we use it is because of quirks in the current language

outer raven
#

aight if typescript itself is doing that then sure there's no point to change to undefined

wild flax
#

lol

#

It's almost like we know what we are talking about

bitter peak
#

@outer raven The reason I have methods for getSubCommand and getSubCommandGroup is to perform validation as all the other methods do. e.g. getSubCommand() asserts that there actually is a subcommand, moving the burden of checking away from end-user code.

#

cc @tacit crypt bc you asked too

tacit crypt
#

??

outer raven
#

but if you wanna check if there is a subcommand or a group just check if they're null ?

bitter peak
#

The same could be said for all the get* methods

#

But the rest of the PR follows that assertion logic

outer raven
#

not really cuz there are multiple options and only one subcommand and group

tacit crypt
#

How is that moving the burden of checking from the user, when it now makes it twice as annoying because it throws an error

bitter peak
#

It still serves to validate the user's command data against their runtime code

#

If anything, I think it should be a getter

#

If not a function

outer raven
#

i don't see any advantage in that

tacit crypt
#

it should be a property

#

and let users check themselves

#

there's no reason for it to be a glorified getter

bitter peak
#

But none of the other methods do that, for the sole reason of ensuring the user doesn't have mismatched command definitions and code

tacit crypt
#

the other options are specifically of a type (roles, users, channels, etc)

#

subcommands and subcommand groups are strings and nothing more

#

All you do right now is throw an INCONSISTENT error when someone gets the subcommand / subcommand group

wild flax
#

I think you guys make this out to be a way bigger deal than it is

#

lol

tacit crypt
#

??

wild flax
#

the whole point of us transforming the options is so the user doesnt need to check at all

#

so I agree with shino there

#

otherwise you can just use the get() method

tacit crypt
#

I am strongly disagreeing with glorified getters for sub command (group) but shrug

wild flax
#

which does none of this

#

Yeah I don't think its too big of a deal

tacit crypt
#

I don't see how that's better than just having users check if interaction.options.subCommand === 'name'

#

Especially since I bet you there will be someone doing if/else chains of getSubCommand() instead of caching the variable

wild flax
#

I don't think we ever tried to pre-optimize what users end up doing

#

So that is out of scope for this and rather belongs into the guide or a programming guide in general

#

Calling a function that returns the same value over and over, and how that isnt optimal, is not something we need to teach people.

gaunt lotus
#

Not sure if this is where I should be asking this but I can't seem to import SlashCommandBuilder, I have:
import { SlashCommandBuilder } from '@discordjs/builders';

Says Module '"@discordjs/builders"' has no exported member 'SlashCommandBuilder'. I have @discordjs/builders@0.2.0 installed

wild flax
#

Its not yet published

#

Ill let you know when it is

gaunt lotus
#

Oh ok my bad, thank you

oak quail
#

hm

#

for my stickers pr should i actually make it work for both stickers and sticker_items

#

so it works for old messages and new messages

#

stickers is technically deprecated but its the only way of seeing what stickers old msgs have

oak quail
#

also, couldnt the Base class implement createdAt and createdTimestamp? since it says its for data models that have snowflakes (although looks like some non-snowflake objects incorrectly extend it)

warped crater
#

yeah thats primarily the reason why it doesnt annoyingly enough

#

i had a look at it once but its fairly obnoxious to refactor

slate nacelle
#

We could also update the docstring to reflect what it is.

wild flax
#

is it deprecated in v9 still?

bitter peak
#

Maybe Base can be refactored into Base and SnowflakeBase extends Base?

zenith oracle
#

Why?

tacit crypt
#

@wild flax @bitter peak why don't we make getSubCommandGroup do the exact same as getSubCommand? (aka throw if missing, return otherwise)?

bitter peak
#

I guess yeah that does make sense. What do you think, Crawl?

bitter peak
#

@wild flax

wild flax
#

sure

#

next time make sure to ping me directly instead of waiting :P

oak quail
wild flax
#

man fuck discord

ruby terrace
#

aj did say no more breaking changes for v9 right

oak quail
#

i think someone said theyre removing guild.region in v9 but that doesnt sound right

#

the client still uses it tho bloblul

dry valve
#

there doesn't seem to be any documentation about how context works in broadcastEval, I've spent the past hour searching and the only thing I've found is where the context should be placed, but not how to access it

#

passing a second argument seems to be the solution to this, but why is it not documented?

analog oyster
#

here are a few oddities with GuildInviteManager

  • #cache is typed as Collection<Snowflake, Invite>, while it is actually Collection<string, Invite>

  • it has #resolveId (should perhaps be called resolveCode?), and it just returns back whatever you pass at it as long as you pass a string

    • e.g. if you pass discord.gg/djs, it returns discord.gg/djs, if you pass djs, it returns djs
  • #resolve should support invite urls as suggested by InviteResolvable, but it doesn't. it only supports invite codes

gaunt lotus
#

Why do I need the MANAGE_ROLES permission to edit the permissionOverwrites of a channel when I can create a channel with initial permissionOverwrites without it?

frank turret
tacit crypt
frank turret
#

noted, the second part of the point still stands

loud jayBOT
upper moss
#

isnt this pr a major semver as it renames some of the properties

analog oyster
#

yes, but technically interactions haven't been released yet so shouldn't it be semver: N/A instead

tacit crypt
#

it's still semver major i think meguFace

gusty carbon
#

its labeled as semver minor tho

oak quail
tacit crypt
oak quail
#

they probably won't but I can ask

tacit crypt
#

They should

#

So please do ask ๐Ÿ™

ruby terrace
#

I would guess they won't, based on the fact that old crossposts no longer return guild_id which is supposed to be guaranteed

rough glacier
#

current CommandInteractionOptionResolver implementation leaves subcommand options as a Collection and a tiny bit fuzzy to access them. Shouldn't they be adapted similarly?

bitter peak
#

getSubCommand() is just a getter for the name, which is resolved at construction time

junior pumice
#

I've got an idea:
What if you could insert a filter function in the cachefactory to tell it that it should only cache what matches the filter

So (c) => c.isText() to only cache text based channels

short crescent
copper laurel
#

message.edit({ embeds: [embed] }) for example - you dont want this to change the content (which is undefined)

#

null is the correct way to specify something as intentionally having no value, hence editing out the content. Undefined is only the lack of its presence in the object

rough glacier
# bitter peak What do you mean?

well right now you can only access subcommand options as <interaction>.options.get(subcommandName).options if im not mistaken, and you get Collection<Snowflake, CommandInteractionOptionData[]> instead of another CommandInteractionOptionResolver. Maybe an interaction.options.getSubCommandOptions() is too much, but different types between first and second options layer is weird

bitter peak
# rough glacier well right now you can only access subcommand options as `<interaction>.options....

That's intended.
CommandInteractionOptionResolver#get() is intended to return unparsed option data. It's up to you to parse the subcommand options if you do it that way. The preferred way to use it is like this:

declare const interaction: CommandInteraction;

const subcommand = interaction.options.getSubCommand();
switch (subcommand) {
  case 'foo': {//   /command foo
    await interaction.reply('foo!');
    break;
  }
  case 'bar': {//   /command bar <baz:string>
    const baz = interaction.options.getString('baz', true);
    await interaction.reply(`baz: ${baz.toUpperCase()}`);
    break;
  }
  default: throw new Error(`Unknown subcommand: ${subcommand}`);
}
#

CommandInteractionOptionResolver#getSubCommand() returns the subcommand name as a string

vivid field
copper laurel
rough glacier
bitter peak
#

Since the purpose of get() is to return "raw" data

bitter peak
vivid field
#

\๐Ÿ‘

junior pumice
copper laurel
#

No I mean, it doesnt need a PR

junior pumice
#

Uh

#

Oh hmmmm

#

Can I?

#

I thought the cache classes are just object names

copper laurel
#
const FilteredCollection extends Collection {
  set(key, value) {
    if(filter) {
      super.set(key, value);
    } else {
      // idk, something
    }
  }
}

const client = new Client({
  makeCache: (manager) => {
    if (manager.name === 'ManagerToFilter') return new FilteredCollection(filter);
  }
});```
#

Thats a really rough example of what you could do

junior pumice
#

Hmmm

#

I thought about something like this

[...].makeCache({
MessagesManager: {limit: 200, (f) => !f.author.bot},
EmojiManager: {limit: 1000, (f) => f.animated}
})```
#

Idk the exact names

mighty matrix
bitter peak
#

wtf

copper laurel
#

Nobody online can right now

real jetty
real jetty
# zenith oracle Wdym?

In reply options you have a property named fetchReply to fetch the reply immediately :

// First way
const reply = interaction.reply({ content: "test", fetchReply: true })

// Second way
interaction.reply({ content: "test" })
const reply = await interaction.fetchReply()
zenith oracle
#

Ah yeah, you're right, this option doesn't seem to be in the docs

bitter peak
#

regarding #6143, I'm not too sure what a good solution there would be; interaction.options.options definitely doesn't sound too great. While their issue could be solved with a loop/helper function in their code, it seems like a use-case that would be useful to support. Maybe CommandInteraction#optionsArray: CommandInteractionOption[]?

real jetty
#

Another thing that can be cool is to add the same thing for ClientApplication that for Client, something like :

type If<T extends boolean, A, B = null> = T extends true ? A : T extends false ? B : A | B;

export class ClientApplication<Fetched extends boolean = boolean> extends Application {
  ...
  public owner: If<Fetched, User | Team>;
  public isFetched(): this is ClientApplication<true>
  ...
}
bitter peak
#

@wild flax @tacit crypt thoughts on the issue above ^?

loud jayBOT
gaunt lotus
#

Would it be possible to have getSubCommandGroup() return null/undefined instead of throwing an error when the command issued doesn't have a sub command group? When checking if a received command interaction has one I would do:

if(<CommandInteractionOptionResolver>.getSubCommandGroup()) {
    (...)
}

instead of something like:

let subCommandGroup: string;
try {
    subCommandGroup = <CommandInteractionOptionResolver>.getSubCommandGroup();
}
catch {}

if(subCommandGroup) {
    (...)
}

I'm using this to run certain code for specific sub command groups

wild flax
#

That isnโ€™t really the intended use case for it

#

Also thereโ€™s always the generic get method that doesnโ€™t throw

zenith oracle
#

I think the option resolver should have a public collection property, so that users can still use their own ways to handle options, like the issue mentioned above. What if I want to filter or map the options? I actually can't. It's like a collection with a really advanced get methods...

gaunt lotus
tacit crypt
loud jayBOT
bitter peak
#

@tacit crypt

tacit crypt
#

Could just turn _options into a Collection like before

#

wait, right, options.options

#

uhhhh

ruby terrace
#

.raw?

opaque vessel
#

Might I just suggest .data or .raw?

#

.data as in, the data supplied to us
Or .raw if you really wanna go that route!

bitter peak
#

I was thinking like interaction.optionsArray

tacit crypt
#

It's not really raw tho

#

It's already processed by us

ruby terrace
#

eh, true

real jetty
#

Or instead name it like a property we could name it like a function / getter :
interaction.options.toCollection -> return _options into a Collection like before
interaction.options.toArray -> return _options into an Array like now

We will avoid interaction.options._options and give the possibility to DJs user to use what they want

zenith oracle
#

I agree, it would be really useful

bitter peak
#

I like the idea of toArray() since we could return a copy of the array

opaque vessel
#

I also wanna suggest a rename of the methods? Right now, they are .get(), .getBoolean(), .getChannel() etc. but imo this feels kinda weird since discord.js relies a lot on Collections and using .get() returns undefined but .get() here returns null. I know they're not the same but it's still... weird to me.

What about renaming the methods to.resolve(), .resolveBoolean(), .resolveChannel() etc.? The managers across discord.js use .resolve() and also return null which is what the current methods do on CommandInteractionOptionResolver. Also there's "resolver" in the name so it kinda matches up.

Thoughts?

wild flax
#

Not a friend

#

Itโ€™s not a collection

ornate topaz
#

it's the Collections that are odd ones here and return undefined

#

and it's like that because Collections extend Maps by adding Array methods, and both Maps and Arrays return undefined. We just mimic those in Collections

wild flax
#

Right ok theres one issue @bitter peak

bitter peak
#

๐Ÿ‘€

wild flax
#

Def need a way to access all the options before they get transformed or anything

#

I used to do something along the lines of const args = [...interaction.options.values()];

#

which is obviously not possible right now

#

I can do _options for now I guess

#

But since its private, it will obv throw in TS

bitter peak
#

okay, which do you prefer:

  1. interaction.options.toArray(): CommandInteractionOption[]
  2. interaction.optionsArray: ReadonlyArray<CommandInteractionOption>
#

or?

wild flax
#

just make options public

#

probably

bitter peak
#

but that looks bad

#

interaction.options.options

wild flax
#

call it data then

#

and we are good

bitter peak
wild flax
#

yeah but you know what im trying to say lol

bitter peak
#

ye ye

wild flax
#

theres just no real way yet

#

id be fine with interaction.options.data

bitter peak
#

alright I'll add that as a ReadonlyArray

wild flax
#

since data will probably be our key moving forward for raw data on structures

real jetty
#

In DataManager class we have this :

export abstract class DataManager<K, Holds, R> extends BaseManager {
  public constructor(client: Client, holds: Constructable<Holds>);
  public readonly holds: Constructable<Holds>;
  public readonly cache: Collection<K, Holds>;
  public resolve(resolvable: Holds): Holds;
  public resolve(resolvable: R): Holds | null;
  public resolveId(resolvable: Holds): K;
  public resolveId(resolvable: R): K | null;
  public valueOf(): Collection<K, Holds>;
}

But when you pass a snowflake to resolveId (for example with ChannelManager), TS consider that you are on this function: public resolveId(resolvable: R): K | null; instead of public resolveId(resolvable: Holds): K;... so TS consider that the result can be null. So I wonder if it would be possible to change the typing to include the snowflake type in the first function like this : public resolveId(resolvable: Holds | K): K; so resolveId(<Snowflake>) return Snowflake and not Snowflake | null

solemn oyster
#

It should be changed tots public resolveId(resolvable: K | Holds): K; public resolveId(resolvable: R): K | null;
Although we'll have to check for the extended managers, specifically emoji ones

real jetty
oak quail
#

@tacit crypt they said to use (data.sticker_items ?? data.stickers)

tacit crypt
#

CC @wild flax

#

also Bruh

wild flax
#

for real?

#

wait

#

but it isnt backwards compatible is it?

oak quail
#

it is

wild flax
#

so you dont even need to change anything but do that?

oak quail
#

sticker_items has partial sticker objects

#

yeah

wild flax
#

then sure

#

@oak quail did you add the new error codes?

oak quail
#

doing rn

wild flax
#

๐Ÿ‘

oak quail
#

and i'll do the docs link thing

wild flax
#

@oak quail youll need to like rebase or something

#

the lock file is out of sync

#

actually hold on

oak quail
wild flax
#

yeah

oak quail
#

i dont have the new npm ver

wild flax
#

I should have thought first

#

done

oak quail
#

๐ŸŽ‰

wild flax
#

really close to release now

real jetty
oak quail
#

why are deferred, ephemeral, replied, and webhook on CommandInteraction and MessageComponentInteraction instead of on Interaction ๐Ÿค”

copper laurel
#

Future proofing, there might be a type of interaction that doesn't use those response types

loud jayBOT
#

pr_open #6152 in discordjs/discord.js by Apokalypt opened <t:1626773214:R> (review required)
types(DataManager): add 'K' to type parameter of 'resolveId'
๐Ÿ“ฅ npm i Apokalypt/discord.js-1#master

real jetty
#

I hope I didn't make a mistake and did it right XD

winged dust
#

#archive-voice from v0.5.1 to 0.5.4 newState.channelId changed to newState.channelID (Capital D) which is the opposite of what happened to discord V13. Is this right?

tacit crypt
#

Boop @fringe temple

fringe temple
#

In JoinConfig, everything is cased as Id

real jetty
#

In Sticker.js we have :

  async fetchUser() {
    if (this.partial) await this.fetch();
    if (!this.guildID) throw new Error('NOT_GUILD_STICKER');

    const data = await this.client.api.guilds(this.guildId).stickers(this.id).get();
    this._patch(data);
    return this.user;
  }

!this.guildID will always be true because the variable has got renamed to guildId no ?

winged dust
fringe temple
#

That's unrelated to the new voice library, @tacit crypt ^

tacit crypt
#

ah

real jetty
wild flax
#

Sure

loud jayBOT
honest barn
#

my first pr here, tell me if i have to change anything

winged dust
real jetty
honest barn
#

just found it, it's just one file so i can change that quickly

#

one sec

#

nvm its better to just make a new pr

honest barn
fringe temple
#

@honest barn if there are other places in the voice repo to make this change, feel free to do it in the same PR ๐Ÿ™‚

#

i will review it later today

fringe temple
honest barn
#

i searched for id on the repo to see any mentions of ID

#

the only one was in Networking.js other than in the examples

honest barn
loud jayBOT
#

pr_open #156 in discordjs/voice by SuperchupuDev opened <t:1626786134:R> (review required)
refactor(ConnectionOptions): change xID to xId
๐Ÿ“ฅ npm i SuperchupuDev/voice#refactor-connectionoptions

bitter peak
#

Will ReadonlyArray<T> work in a JSDoc comment?

diff --git a/src/structures/CommandInteractionOptionResolver.js b/src/structures/CommandInteractionOptionResolver.js
index ca0b1587..7d93b6cd 100644
--- a/src/structures/CommandInteractionOptionResolver.js
+++ b/src/structures/CommandInteractionOptionResolver.js
@@ -45,6 +45,15 @@ class CommandInteractionOptionResolver {
     }
   }
 
+  /**
+   * The array of `CommandInteractionOptions` managed by this resolver.
+   * @type {ReadonlyArray<CommandInteractionOption>}
+   * @readonly
+   */
+  get data() {
+    return this._options;
+  }
+
   /**
    * Gets an option by its name.
    * @param {string} name The name of the option.
#

And, should this be return this._options.slice()? Or assigned in the constructor?

zenith oracle
#

Why readonlyarray?

#

You already have the readonly tag

winged dust
strange igloo
honest barn
#

on my last pr in voice this workflow failed, do i need to change anything?

strange igloo
# honest barn on my last pr in voice this workflow failed, do i need to change anything?

You can look at what went wrong with the Details

src/VoiceConnection.ts:337:5 - error TS2345: Argument of type '{ endpoint: string; serverID: `${bigint}`; token: string; sessionID: string; userID: `${bigint}`; }' is not assignable to parameter of type 'ConnectionOptions'.
  Object literal may only specify known properties, but 'serverID' does not exist in type 'ConnectionOptions'. Did you mean to write 'serverId'?

337     serverID: server.guild_id,
honest barn
#

oh im on mobile i didnt see it

#

ill fix it later fixed

winged dust
bitter peak
bitter peak
zenith oracle
#

What if we clone the array instead?

#

Like return [...this._options]

honest barn
#

fixed

zenith oracle
#

๐Ÿ‘