Skip to content

Commit fec3b5c

Browse files
author
Eviee Py
committed
Add case_insensitive commands
1 parent efb9c26 commit fec3b5c

File tree

3 files changed

+62
-4
lines changed

3 files changed

+62
-4
lines changed

twitchio/ext/commands/bot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@
4646
from twitchio.eventsub.subscriptions import SubscriptionPayload
4747
from twitchio.models.eventsub_ import ChatMessage
4848
from twitchio.types_.eventsub import SubscriptionResponse
49-
from twitchio.types_.options import ClientOptions
5049
from twitchio.user import PartialUser
5150

5251
from .components import Component
52+
from .types_ import BotOptions
5353

5454
PrefixT: TypeAlias = str | Iterable[str] | Callable[["Bot", "ChatMessage"], Coroutine[Any, Any, str | Iterable[str]]]
5555

@@ -162,7 +162,7 @@ def __init__(
162162
bot_id: str,
163163
owner_id: str | None = None,
164164
prefix: PrefixT,
165-
**options: Unpack[ClientOptions],
165+
**options: Unpack[BotOptions],
166166
) -> None:
167167
super().__init__(
168168
client_id=client_id,

twitchio/ext/commands/core.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import inspect
3030
from collections.abc import Callable, Coroutine, Generator
3131
from types import MappingProxyType, UnionType
32-
from typing import TYPE_CHECKING, Any, Concatenate, Generic, ParamSpec, TypeAlias, TypeVar, Union, Unpack
32+
from typing import TYPE_CHECKING, Any, Concatenate, Generic, ParamSpec, TypeAlias, TypeVar, Union, Unpack, overload
3333

3434
from twitchio.utils import MISSING
3535

@@ -67,6 +67,9 @@
6767
Coro: TypeAlias = Coroutine[Any, Any, None]
6868
CoroC: TypeAlias = Coroutine[Any, Any, bool]
6969

70+
DT = TypeVar("DT")
71+
VT = TypeVar("VT")
72+
7073

7174
class CommandErrorPayload:
7275
"""Payload received in the :func:`~twitchio.event_command_error` event.
@@ -395,9 +398,17 @@ def after_invoke(self) -> None: ...
395398

396399
class Mixin(Generic[Component_T]):
397400
def __init__(self, *args: Any, **kwargs: Any) -> None:
398-
self._commands: dict[str, Command[Component_T, ...]] = {}
401+
case_: bool = kwargs.pop("case_insensitive", False)
402+
self._case_insensitive: bool = case_
403+
self._commands: dict[str, Command[Component_T, ...]] = {} if not case_ else _CaseInsensitiveDict()
404+
399405
super().__init__(*args, **kwargs)
400406

407+
@property
408+
def case_insensitive(self) -> bool:
409+
"""Property returning a bool indicating whether this Mixin is using case insensitive commands."""
410+
return self._case_insensitive
411+
401412
def add_command(self, command: Command[Component_T, ...], /) -> None:
402413
"""Add a :class:`~.commands.Command` object to the mixin.
403414
@@ -986,3 +997,44 @@ def predicate(context: Context) -> bool:
986997
return chatter.moderator or chatter.vip
987998

988999
return guard(predicate)
1000+
1001+
1002+
class _CaseInsensitiveDict(dict[str, VT]):
1003+
def __contains__(self, key: object) -> bool:
1004+
return super().__contains__(key.casefold()) if isinstance(key, str) else False
1005+
1006+
def __delitem__(self, key: str) -> None:
1007+
return super().__delitem__(key.casefold())
1008+
1009+
def __getitem__(self, key: str) -> VT:
1010+
return super().__getitem__(key.casefold())
1011+
1012+
@overload
1013+
def get(self, key: str, /) -> VT | None: ...
1014+
1015+
@overload
1016+
def get(self, key: str, default: VT, /) -> VT: ...
1017+
1018+
@overload
1019+
def get(self, key: str, default: DT, /) -> VT | DT: ...
1020+
1021+
def get(self, key: str, default: DT = None) -> VT | DT:
1022+
return super().get(key.casefold(), default)
1023+
1024+
@overload
1025+
def pop(self, key: str, /) -> VT: ...
1026+
1027+
@overload
1028+
def pop(self, key: str, default: VT, /) -> VT: ...
1029+
1030+
@overload
1031+
def pop(self, key: str, default: DT, /) -> VT | DT: ...
1032+
1033+
def pop(self, key: str, default: DT = MISSING) -> VT | DT:
1034+
if default is MISSING:
1035+
return super().pop(key.casefold())
1036+
1037+
return super().pop(key.casefold(), default)
1038+
1039+
def __setitem__(self, key: str, value: VT) -> None:
1040+
super().__setitem__(key.casefold(), value)

twitchio/ext/commands/types_.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
from typing import TYPE_CHECKING, Any, TypedDict, TypeVar
2626

27+
from twitchio.types_.options import ClientOptions
28+
2729

2830
if TYPE_CHECKING:
2931
from .components import Component
@@ -40,3 +42,7 @@ class CommandOptions(TypedDict, total=False):
4042
class ComponentOptions(TypedDict, total=False):
4143
name: str | None
4244
extras: dict[Any, Any]
45+
46+
47+
class BotOptions(ClientOptions, total=False):
48+
case_insensitive: bool

0 commit comments

Comments
 (0)