mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
fix(discord): honor wildcard '*' in ignored_channels and free_response_channels
Follow-up to the allowed_channels wildcard fix in the preceding commit. The same '*' literal trap affected two other Discord channel config lists: - DISCORD_IGNORED_CHANNELS: '*' was stored as the literal string in the ignored set, and the intersection check never matched real channel IDs, so '*' was a no-op instead of silencing every channel. - DISCORD_FREE_RESPONSE_CHANNELS: same shape — '*' never matched, so the bot still required a mention everywhere. Add a '*' short-circuit to both checks, matching the allowed_channels semantics. Extend tests/gateway/test_discord_allowed_channels.py with regression coverage for all three lists. Refs: #14920
This commit is contained in:
parent
8598746e86
commit
8a1e247c6c
2 changed files with 74 additions and 9 deletions
|
|
@ -2719,7 +2719,12 @@ class DiscordAdapter(BasePlatformAdapter):
|
||||||
return os.getenv("DISCORD_REQUIRE_MENTION", "true").lower() not in ("false", "0", "no", "off")
|
return os.getenv("DISCORD_REQUIRE_MENTION", "true").lower() not in ("false", "0", "no", "off")
|
||||||
|
|
||||||
def _discord_free_response_channels(self) -> set:
|
def _discord_free_response_channels(self) -> set:
|
||||||
"""Return Discord channel IDs where no bot mention is required."""
|
"""Return Discord channel IDs where no bot mention is required.
|
||||||
|
|
||||||
|
A single ``"*"`` entry (either from a list or a comma-separated
|
||||||
|
string) is preserved in the returned set so callers can short-circuit
|
||||||
|
on wildcard membership, consistent with ``allowed_channels``.
|
||||||
|
"""
|
||||||
raw = self.config.extra.get("free_response_channels")
|
raw = self.config.extra.get("free_response_channels")
|
||||||
if raw is None:
|
if raw is None:
|
||||||
raw = os.getenv("DISCORD_FREE_RESPONSE_CHANNELS", "")
|
raw = os.getenv("DISCORD_FREE_RESPONSE_CHANNELS", "")
|
||||||
|
|
@ -3219,7 +3224,7 @@ class DiscordAdapter(BasePlatformAdapter):
|
||||||
# Check ignored channels - never respond even when mentioned
|
# Check ignored channels - never respond even when mentioned
|
||||||
ignored_channels_raw = os.getenv("DISCORD_IGNORED_CHANNELS", "")
|
ignored_channels_raw = os.getenv("DISCORD_IGNORED_CHANNELS", "")
|
||||||
ignored_channels = {ch.strip() for ch in ignored_channels_raw.split(",") if ch.strip()}
|
ignored_channels = {ch.strip() for ch in ignored_channels_raw.split(",") if ch.strip()}
|
||||||
if channel_ids & ignored_channels:
|
if "*" in ignored_channels or (channel_ids & ignored_channels):
|
||||||
logger.debug("[%s] Ignoring message in ignored channel: %s", self.name, channel_ids)
|
logger.debug("[%s] Ignoring message in ignored channel: %s", self.name, channel_ids)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -3233,7 +3238,11 @@ class DiscordAdapter(BasePlatformAdapter):
|
||||||
voice_linked_ids = {str(ch_id) for ch_id in self._voice_text_channels.values()}
|
voice_linked_ids = {str(ch_id) for ch_id in self._voice_text_channels.values()}
|
||||||
current_channel_id = str(message.channel.id)
|
current_channel_id = str(message.channel.id)
|
||||||
is_voice_linked_channel = current_channel_id in voice_linked_ids
|
is_voice_linked_channel = current_channel_id in voice_linked_ids
|
||||||
is_free_channel = bool(channel_ids & free_channels) or is_voice_linked_channel
|
is_free_channel = (
|
||||||
|
"*" in free_channels
|
||||||
|
or bool(channel_ids & free_channels)
|
||||||
|
or is_voice_linked_channel
|
||||||
|
)
|
||||||
|
|
||||||
# Skip the mention check if the message is in a thread where
|
# Skip the mention check if the message is in a thread where
|
||||||
# the bot has previously participated (auto-created or replied in).
|
# the bot has previously participated (auto-created or replied in).
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
"""Regression guard for #14920: DISCORD_ALLOWED_CHANNELS="*" should allow all channels.
|
"""Regression guard for #14920: wildcard "*" in Discord channel config lists.
|
||||||
|
|
||||||
Setting allowed_channels: "*" in config (or DISCORD_ALLOWED_CHANNELS="*" as env var)
|
Setting ``allowed_channels: "*"``, ``free_response_channels: "*"``, or
|
||||||
must behave as a wildcard — i.e. the bot responds in every channel. Previously the
|
``ignored_channels: "*"`` in config (or their ``DISCORD_*_CHANNELS`` env var
|
||||||
literal string "*" was placed into the set and compared against numeric channel IDs via
|
equivalents) must behave as a wildcard — i.e. the bot responds in every
|
||||||
set-intersection, which always produced an empty set, causing every message to be
|
channel (or is silenced in every channel, for the ignored list). Previously
|
||||||
silently dropped.
|
the literal string "*" was placed into a set and compared against numeric
|
||||||
|
channel IDs via set-intersection, which always produced an empty set and
|
||||||
|
caused every message to be silently dropped (for ``allowed_channels``) or
|
||||||
|
every ``free_response`` / ``ignored`` check to fail open.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
@ -20,6 +23,22 @@ def _channel_is_allowed(channel_id: str, allowed_channels_raw: str) -> bool:
|
||||||
return bool({channel_id} & allowed_channels)
|
return bool({channel_id} & allowed_channels)
|
||||||
|
|
||||||
|
|
||||||
|
def _channel_is_ignored(channel_id: str, ignored_channels_raw: str) -> bool:
|
||||||
|
"""Replicate the ignored-channel check from discord.py on_message."""
|
||||||
|
ignored_channels = {
|
||||||
|
ch.strip() for ch in ignored_channels_raw.split(",") if ch.strip()
|
||||||
|
}
|
||||||
|
return "*" in ignored_channels or bool({channel_id} & ignored_channels)
|
||||||
|
|
||||||
|
|
||||||
|
def _channel_is_free_response(channel_id: str, free_channels_raw: str) -> bool:
|
||||||
|
"""Replicate the free-response-channel check from discord.py on_message."""
|
||||||
|
free_channels = {
|
||||||
|
ch.strip() for ch in free_channels_raw.split(",") if ch.strip()
|
||||||
|
}
|
||||||
|
return "*" in free_channels or bool({channel_id} & free_channels)
|
||||||
|
|
||||||
|
|
||||||
class TestDiscordAllowedChannelsWildcard(unittest.TestCase):
|
class TestDiscordAllowedChannelsWildcard(unittest.TestCase):
|
||||||
"""Wildcard and channel-list behaviour for DISCORD_ALLOWED_CHANNELS."""
|
"""Wildcard and channel-list behaviour for DISCORD_ALLOWED_CHANNELS."""
|
||||||
|
|
||||||
|
|
@ -46,3 +65,40 @@ class TestDiscordAllowedChannelsWildcard(unittest.TestCase):
|
||||||
def test_whitespace_only_entry_ignored(self):
|
def test_whitespace_only_entry_ignored(self):
|
||||||
"""Entries that are only whitespace are stripped and ignored."""
|
"""Entries that are only whitespace are stripped and ignored."""
|
||||||
self.assertFalse(_channel_is_allowed("1234567890", " , "))
|
self.assertFalse(_channel_is_allowed("1234567890", " , "))
|
||||||
|
|
||||||
|
|
||||||
|
class TestDiscordIgnoredChannelsWildcard(unittest.TestCase):
|
||||||
|
"""Wildcard and channel-list behaviour for DISCORD_IGNORED_CHANNELS."""
|
||||||
|
|
||||||
|
def test_wildcard_silences_every_channel(self):
|
||||||
|
"""'*' in ignored_channels silences the bot everywhere."""
|
||||||
|
self.assertTrue(_channel_is_ignored("1234567890", "*"))
|
||||||
|
|
||||||
|
def test_empty_ignored_list_silences_nothing(self):
|
||||||
|
self.assertFalse(_channel_is_ignored("1234567890", ""))
|
||||||
|
|
||||||
|
def test_exact_match_is_ignored(self):
|
||||||
|
self.assertTrue(_channel_is_ignored("111", "111,222"))
|
||||||
|
|
||||||
|
def test_non_match_not_ignored(self):
|
||||||
|
self.assertFalse(_channel_is_ignored("333", "111,222"))
|
||||||
|
|
||||||
|
|
||||||
|
class TestDiscordFreeResponseChannelsWildcard(unittest.TestCase):
|
||||||
|
"""Wildcard and channel-list behaviour for DISCORD_FREE_RESPONSE_CHANNELS."""
|
||||||
|
|
||||||
|
def test_wildcard_makes_every_channel_free_response(self):
|
||||||
|
"""'*' in free_response_channels exempts every channel from mention-required."""
|
||||||
|
self.assertTrue(_channel_is_free_response("1234567890", "*"))
|
||||||
|
|
||||||
|
def test_wildcard_in_list_applies_everywhere(self):
|
||||||
|
self.assertTrue(_channel_is_free_response("9999999999", "111,*,222"))
|
||||||
|
|
||||||
|
def test_exact_match_is_free_response(self):
|
||||||
|
self.assertTrue(_channel_is_free_response("111", "111,222"))
|
||||||
|
|
||||||
|
def test_non_match_not_free_response(self):
|
||||||
|
self.assertFalse(_channel_is_free_response("333", "111,222"))
|
||||||
|
|
||||||
|
def test_empty_list_no_free_response(self):
|
||||||
|
self.assertFalse(_channel_is_free_response("111", ""))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue