Skip to content

Implement !tunnel command#3516

Open
Matiiss wants to merge 1 commit into
python-discord:mainfrom
Matiiss:matiiss-implement-tunnel-command
Open

Implement !tunnel command#3516
Matiiss wants to merge 1 commit into
python-discord:mainfrom
Matiiss:matiiss-implement-tunnel-command

Conversation

@Matiiss
Copy link
Copy Markdown
Member

@Matiiss Matiiss commented Jun 6, 2026

Fixes #3512

@Matiiss Matiiss force-pushed the matiiss-implement-tunnel-command branch 2 times, most recently from 5b4d35e to 9d2c86c Compare June 6, 2026 21:12
@Matiiss Matiiss force-pushed the matiiss-implement-tunnel-command branch from 9d2c86c to 3bf675d Compare June 6, 2026 21:14
@Matiiss
Copy link
Copy Markdown
Member Author

Matiiss commented Jun 6, 2026

I do have a couple of questions though, first, so currently I have made it so that only discord.TextChannel are supported, which means you can't use this command inside those voice channel text channels or threads, but I suppose it'd be fine to allow its usage there, too.

Another thing is that if I change the command method signature to use discord.abc.MessageableChannel, then suddenly !tunnel is being interpreted as a "tag" and it's trying to find a match for it among the other tags, instead of running this command. I'd like some advice on how to make it behave like it does with discord.TextChannel or like make the tag search skip it or something.

Then also, if I use the discord.TextChannel annotation (and presumably it would be similar if I fixed it so that discord.abc.MessageableChannel would work), if I provide a wrong ID (same length, just changed some digit), it doesn't appear to complain at any level, it seems it just sets that argument to None, which, I'm not sure if that's the behavior that I'd like. That is, you either provide a correct argument for the channel or it uses the default behavior without letting you know that you may have messed up.

Should there be checks for whether the given channel is a DM channel or some such? Currently it just seems to treat it as incorrect channel ID (probably because it's not a discord.TextChannel), so it just sets it to None, but if switching to discord.abc.MessageableChannel, that should probably be explicitly checked?

Similarly, what to do about ctx.guild, should that throw some more specific error or should it realistically never be None (which is my assumption, hence the AssertionError)? I used raise AssertionError` mostly for typing purposes here (in places where it also should never be realistically raised anyway).

Is there a simple way to do overloads for this command? So that with one argument it's !tunnel [destination_channel], but then with two it's !tunnel [source_channel] [destination_channel]?

Any shortcuts/convenience functions I should use for say permission checks from the existing code base?

Does this need unit or integration tests?

Copy link
Copy Markdown
Member

@jb3 jb3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice initial implementation, a few feedback comments. Will address your other comments in a second.

Comment thread bot/exts/utils/tunnel.py
Comment on lines +20 to +32
for channel_id in CHANNEL_IDS:
channel = bot.get_channel(channel_id)
if channel is None:
continue

if not isinstance(channel, discord.TextChannel):
raise AssertionError

last_message = channel.last_message
if last_message is None:
continue

self.channel_id_to_timestamp[channel_id] = last_message.created_at.timestamp()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be done in cog load listener, not in the cog init (see other cogs for prior art)

Comment thread bot/exts/utils/tunnel.py
Comment on lines +42 to +43
if ctx.guild is None:
raise AssertionError
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a guild-only check here instead, also maybe worth adding a cooldown decorator as we do to prevent abuse of this feature.

Comment thread bot/exts/utils/tunnel.py
Comment on lines +92 to +102
def get_least_active_channel_id(self, current_channel_id: int) -> int:
"""Gets least active off-topic channel."""
channel_id, _ = min(
[
(channel_id, timestamp)
for channel_id, timestamp in self.channel_id_to_timestamp.items()
if channel_id != current_channel_id
],
key=itemgetter(1),
)
return channel_id
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels cleaner to just do this:

Suggested change
def get_least_active_channel_id(self, current_channel_id: int) -> int:
"""Gets least active off-topic channel."""
channel_id, _ = min(
[
(channel_id, timestamp)
for channel_id, timestamp in self.channel_id_to_timestamp.items()
if channel_id != current_channel_id
],
key=itemgetter(1),
)
return channel_id
def get_least_active_channel_id(self, current_channel_id: int) -> int:
"""Gets least active off-topic channel."""
return min(
(channel for channel in self.channel_id_to_timestamp if channel != current_channel_id),
key=self.channel_id_to_timestamp.get
)

Comment thread bot/exts/utils/tunnel.py
Comment on lines +48 to +49
if not isinstance(least_active_channel, discord.TextChannel):
raise AssertionError
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand why you've got a few AssertionErrors throughout your implementation here, but this is not a pattern we have through the rest of this project and I'm not sure how I feel about implementing it just for this cog.

We have found that trying to do precise typing is almost impossible with how many updates the upstream discord.py types get as well as when new features are released to the Discord API that suddenly then start breaking assertions.

I'd rather see any genuine failures handled but otherwise do not think we should have these in the implementation.

@jb3
Copy link
Copy Markdown
Member

jb3 commented Jun 6, 2026

so currently I have made it so that only discord.TextChannel are supported, which means you can't use this command inside those voice channel text channels or threads, but I suppose it'd be fine to allow its usage there, too.

Sounds fine — with the necessary cooldown added as mentioned so users cannot spam this in places we have less visibility.

Another thing is that if I change the command method signature to use discord.abc.MessageableChannel, then suddenly !tunnel is being interpreted as a "tag" and it's trying to find a match for it among the other tags, instead of running this command. I'd like some advice on how to make it behave like it does with discord.TextChannel or like make the tag search skip it or something.

This sounds weird, do you get the same if you try use GuildChannel instead?

That is, you either provide a correct argument for the channel or it uses the default behavior without letting you know that you may have messed up.

I wonder if using a https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.TextChannelConverter might help?

Should there be checks for whether the given channel is a DM channel or some such? Currently it just seems to treat it as incorrect channel ID (probably because it's not a discord.TextChannel), so it just sets it to None, but if switching to discord.abc.MessageableChannel, that should probably be explicitly checked?

You can use a guild-only decorator to enforce this.

Similarly, what to do about ctx.guild, should that throw some more specific error or should it realistically never be None (which is my assumption, hence the AssertionError)? I used raise AssertionError` mostly for typing purposes here (in places where it also should never be realistically raised anyway).

Same as above.

Is there a simple way to do overloads for this command? So that with one argument it's !tunnel [destination_channel], but then with two it's !tunnel [source_channel] [destination_channel]?

Not easily without doing most of the argument handling yourself.

I was going to suggest that for this initial implementation though we ditch the source channel support. Especially since the user who did the tunneling isn't mentioned in the tunneling messages it seems ripe for abuse (e.g. someone spamming in bot commands with another source & dest).

For now, let's keep things simple and just implement an optional destination channel.

Does this need unit or integration tests?

You can if you want to (and if you remove the source stuff it should be easier), we do have some utilities for mocking Discord components here and it's not too hard to add new bits.

@jb3
Copy link
Copy Markdown
Member

jb3 commented Jun 6, 2026

Also, on the last point about abuse: can we include the user who tunneled on both the source and destination message (username is fine, doesn't need to be a mention), so that we can easily see any problems without having to look through message logs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A !tunnel command for ease of redirecting conversations

2 participants