discord.py icon indicating copy to clipboard operation
discord.py copied to clipboard

channel.position often reflects creation order instead of sort order

Open EclipsedSolari opened this issue 6 years ago • 7 comments

Summary

This one's so bizarre it might actually be a Discord bug.

New channels often have channel.position set according to creation order rather than sort order. Restarting the bot does not change channel position order. Manually dragging any channel in the Discord client resets all channels' positions to correctly indicate sort order.

Reproduction Steps

import discord
import random

client = discord.Client()
new_index = 1

@client.event
async def on_message(message):
    global new_index

    if message.author == client.user:
        return

    if message.content.startswith('/create'):
        channel_name = f"test{new_index}"
        new_index = new_index + 1

        # create a new channel at a random position in the server
        server = message.channel.guild
        position = random.randint(0, len(server.channels))
        new_channel = await server.create_text_channel(name=channel_name, position=position)
        await new_channel.send(f"I have position {new_channel.position}!")

    if message.content.startswith('/position'):
        for channel in message.channel_mentions:
            await message.channel.send(f"Channel {channel.name} has position {channel.position}.")

client.run('token')

Use /create to create a channel at a random position within the server and immediately report its position. I'm getting results like "I have position 23!" for a channel with visual position 10.

Use /position to check the position of a manually created channel. As long as the channel is freshly created, it demonstrates the issue just as well as a bot-created channel.

Without changing any channels, restart the bot and query /position again. Channel positions don't change.

Manually reorder any channel in the server and query /position again. All channel positions magically snap into sort order.

Expected Results

I expect channel.position to indicate the channel's sort position, as stated in both discord.py's documentation and discord's own documentation.

Actual Results

channel.position for newly created channels reflects creation order rather than sort order.

image

Checklist

  • [X] I have searched the open issues for duplicates.
  • [X] I have shown the entire traceback, if possible.
  • [X] I have removed my token from display, if visible.

System Information

  • Python v3.7.5-final
  • discord.py v1.2.4-final
  • aiohttp v3.5.4
  • websockets v6.0
  • system info: Windows 10 10.0.18362

EclipsedSolari avatar Oct 24 '19 02:10 EclipsedSolari

Are the channels still in order relative to each other?

khazhyk avatar Oct 24 '19 02:10 khazhyk

It looks like the channels do keep relative order, yes. In the demo script above, if /create creates two channels, and the second is created visually above the first, like this:

#test2
#test1

then #test2 gets a lower channel.position than #test1 despite being created after it. However, both channel.position values remain incorrect based on where the channels are actually sorted.

EclipsedSolari avatar Oct 24 '19 03:10 EclipsedSolari

This is WAI, discord probably isn't going to change it any time soon. channel.position is used for sorting order only, and shouldn't be relied on to determine the "nth" channel - it's possible e.g. for multiple channels in the server to have the same position, e.g., etc.

Might be a handy feature to have a order property? but you'd likely need to order the channels anyways to figure that out, since it's stored as a dict, so just sort the channels and enumerate?

khazhyk avatar Oct 24 '19 04:10 khazhyk

Okay, so to accomplish sorting the channels (my original goal), I need to stop looking at channel.position as an absolute value and start looking at it as a relative value? E.g. if channel1.position < channel2.position and channel1.category_id == channel2.category_id, channel1 will visually sort above channel2?

I don't believe this is intuitive, so a note in the documentation might be helpful, but I think it still allows a form of channel sorting as long as I'm careful about it. Thank you for the clarification!

EclipsedSolari avatar Oct 24 '19 04:10 EclipsedSolari

I guess an __lt__ and friends for GuildChannel might make sense. You can get the sorted GUI order of channels using Guild.text_channels, Guild.voice_channels, or Guild.by_category.

Rapptz avatar Oct 24 '19 04:10 Rapptz

The documentation should be fixed, I think. For example, the reference for TextChannel says "position - The position in the channel list. This is a number that starts at 0. e.g. the top channel is position 0". That makes it sound like an index into the list, which it most definitely is not. (And that's even setting aside the way channels are sorted within categories.)

Not that for example discord.js makes a distinction between "rawPosition" (the number given by Discord) and "position" (the actual index position in a list), though their documentation is also not super clear.

Here is my best understanding of channel positions (as with everything about the discord API, there's a ton of misinformation out there):

  • As far as the server is concerned, "position" is just an arbitrary int that gets carried with channel metadata. There's no checking that they're consecutive or don't collide or anything.
  • The Discord client sorts channels for display:
    • first by category
    • then by type (text first, then voice) within the category
    • then by the position value (lowest first) within the category and type
    • then by raw channel ID (earliest first) when category, type, and position value are all the same
  • When the Discord client creates a new channel, it assigns a position value that's 1 + the highest value within the category/type group.
  • When you drag a channel around in the Discord client, in addition to possibly reassigning the category, it assigns a position value that's 1 + the position of the channel just above where it was dragged. All channels lower in the group are renumbered to consecutive numbers after that.
  • Note that all of this might be happening from multiple clients at once.

Deleting or recategorizing channels can easily leave "holes" in the channel numbering. The client will try to avoid dup numbers, but simultaneous updates (race conditions) can lead to them, too. So one should most definitely not assume that the position value is an absolute index within the group.

The discord.py library reports these raw, potentially holey position values as received from the Discord server. When a library user sets the position, the library does this kinda fancy thing which places the channel in the list at that position (relative to other position values), and then bulk-renumbers all the channels in the same category/type batch into a consecutive series.

(Let me know if you'd like a PR...!)

egnor avatar Oct 13 '20 03:10 egnor

You are correct, this is the explanation I give people when they ask in the help channels whenever something comes up. Documenting this better in the actual documentation site has been a goal for some time but care needs to be taken since it'd be its own article detailing other types of positions as well (such as role positions). That's why this issue is still open, because it's a documentation request that we haven't really gotten to yet.

Rapptz avatar Oct 17 '20 02:10 Rapptz