Skip to content

Commit

Permalink
Add locale providers
Browse files Browse the repository at this point in the history
  • Loading branch information
hypergonial committed Jan 2, 2024
1 parent d08f7e2 commit 952e572
Show file tree
Hide file tree
Showing 14 changed files with 657 additions and 8 deletions.
14 changes: 14 additions & 0 deletions arc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@
from .events import ArcEvent, CommandErrorEvent
from .extension import loader, unloader
from .internal.about import __author__, __author_email__, __license__, __maintainer__, __url__, __version__
from .locale import (
CommandLocaleRequest,
CustomLocaleRequest,
LocaleRequest,
LocaleRequestType,
LocaleResponse,
OptionLocaleRequest,
)
from .plugin import GatewayPluginBase, PluginBase, RESTPluginBase
from .utils import bot_has_permissions, dm_only, guild_only, has_permissions, owner_only

Expand Down Expand Up @@ -96,6 +104,12 @@
"RESTPlugin",
"GatewayPlugin",
"HookResult",
"LocaleRequest",
"LocaleRequestType",
"LocaleResponse",
"CustomLocaleRequest",
"CommandLocaleRequest",
"OptionLocaleRequest",
"abc",
"command",
"with_hook",
Expand Down
124 changes: 121 additions & 3 deletions arc/abc/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,19 @@
from arc.context import AutodeferMode, Context
from arc.errors import ExtensionLoadError, ExtensionUnloadError
from arc.internal.sync import _sync_commands
from arc.internal.types import AppT, BuilderT, ErrorHandlerCallbackT, HookT, LifeCycleHookT, PostHookT, ResponseBuilderT
from arc.internal.types import (
AppT,
BuilderT,
CommandLocaleRequestT,
CustomLocaleRequestT,
ErrorHandlerCallbackT,
HookT,
LifeCycleHookT,
OptionLocaleRequestT,
PostHookT,
ResponseBuilderT,
)
from arc.locale import CommandLocaleRequest, LocaleResponse, OptionLocaleRequest

if t.TYPE_CHECKING:
import typing_extensions as te
Expand Down Expand Up @@ -70,27 +82,41 @@ class Client(t.Generic[AppT], abc.ABC):
"_error_handler",
"_startup_hook",
"_shutdown_hook",
"_provided_locales",
"_command_locale_provider",
"_option_locale_provider",
"_custom_locale_provider",
)

def __init__(
self, app: AppT, *, default_enabled_guilds: t.Sequence[hikari.Snowflake] | None = None, autosync: bool = True
self,
app: AppT,
*,
default_enabled_guilds: t.Sequence[hikari.Snowflake] | None = None,
autosync: bool = True,
provided_locales: t.Sequence[hikari.Locale] | None = None,
) -> None:
self._app = app
self._default_enabled_guilds = default_enabled_guilds
self._autosync = autosync
self._provided_locales: t.Sequence[hikari.Locale] | None = provided_locales

self._application: hikari.Application | None = None
self._slash_commands: dict[str, SlashCommandLike[te.Self]] = {}
self._message_commands: dict[str, MessageCommand[te.Self]] = {}
self._user_commands: dict[str, UserCommand[te.Self]] = {}
self._injector: alluka.Client = alluka.Client()
self._plugins: dict[str, PluginBase[te.Self]] = {}
self._loaded_extensions: list[str] = []
self._autosync = autosync
self._hooks: list[HookT[te.Self]] = []
self._post_hooks: list[PostHookT[te.Self]] = []
self._owner_ids: list[hikari.Snowflake] = []
self._error_handler: ErrorHandlerCallbackT[te.Self] | None = None
self._startup_hook: LifeCycleHookT[te.Self] | None = None
self._shutdown_hook: LifeCycleHookT[te.Self] | None = None
self._command_locale_provider: CommandLocaleRequestT | None = None
self._option_locale_provider: OptionLocaleRequestT | None = None
self._custom_locale_provider: CustomLocaleRequestT | None = None

@property
@abc.abstractmethod
Expand Down Expand Up @@ -303,6 +329,20 @@ async def on_command_interaction(self, interaction: hikari.CommandInteraction) -
f" Did you forget to respond?"
)

def _provide_command_locale(self, request: CommandLocaleRequest) -> LocaleResponse:
"""Provide a locale for a command."""
if self._command_locale_provider is None:
return LocaleResponse(name=request.command.name, description=getattr(request.command, "description", None))

return self._command_locale_provider(request)

def _provide_option_locale(self, request: OptionLocaleRequest) -> LocaleResponse:
"""Provide a locale for an option."""
if self._option_locale_provider is None:
return LocaleResponse(name=request.option.name, description=request.option.description)

return self._option_locale_provider(request)

async def on_autocomplete_interaction(
self, interaction: hikari.AutocompleteInteraction
) -> hikari.api.InteractionAutocompleteBuilder | None:
Expand Down Expand Up @@ -605,6 +645,84 @@ async def shutdown_hook(client: arc.GatewayClient) -> None:
"""
self._shutdown_hook = handler

def set_command_locale_provider(self, provider: CommandLocaleRequestT) -> None:
"""Decorator to set the command locale provider for this client.
This will be called for each command for each locale.
Parameters
----------
provider : CommandLocaleRequestT
The command locale provider to set.
Usage
-----
```py
@client.set_command_locale_provider
def command_locale_provider(request: arc.CommandLocaleRequest) -> arc.LocaleResponse:
...
```
Or, as a function:
```py
client.set_command_locale_provider(command_locale_provider)
```
"""
self._command_locale_provider = provider

def set_option_locale_provider(self, provider: OptionLocaleRequestT) -> None:
"""Decorator to set the option locale provider for this client.
This will be called for each option of each command for each locale.
Parameters
----------
provider : OptionLocaleRequestT
The option locale provider to set.
Usage
-----
```py
@client.set_option_locale_provider
def option_locale_provider(request: arc.OptionLocaleRequest) -> arc.LocaleResponse:
...
```
Or, as a function:
```py
client.set_option_locale_provider(option_locale_provider)
```
"""
self._option_locale_provider = provider

def set_custom_locale_provider(self, provider: CustomLocaleRequestT) -> None:
"""Decorator to set the custom locale provider for this client.
This will be called for each custom locale request performed via [`Context.loc()`][arc.context.base.Context.loc].
Parameters
----------
provider : CustomLocaleRequestT
The custom locale provider to set.
Usage
-----
```py
@client.set_custom_locale_provider
def custom_locale_provider(request: arc.CustomLocaleRequest) -> str:
...
```
Or, as a function:
```py
client.set_custom_locale_provider(custom_locale_provider)
```
"""
self._custom_locale_provider = provider

def load_extension(self, path: str) -> te.Self:
"""Load a python module with path `path` as an extension.
This will import the module, and call it's loader function.
Expand Down
20 changes: 20 additions & 0 deletions arc/abc/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
PostHookT,
ResponseBuilderT,
)
from arc.locale import CommandLocaleRequest

if t.TYPE_CHECKING:
from arc.abc.plugin import PluginBase
Expand Down Expand Up @@ -323,6 +324,25 @@ def _plugin_include_hook(self, plugin: PluginBase[ClientT]) -> None:
self._plugin = plugin
self._plugin._add_command(self)

def _request_command_locale(self) -> None:
"""Request the locale for this command."""
if self.name_localizations or self._client is None:
return

if not self._client._provided_locales or not self._client._command_locale_provider:
return

name_locales: dict[hikari.Locale, str] = {}

for locale in self._client._provided_locales:
request = CommandLocaleRequest(self, locale, self.name)
resp = self._client._command_locale_provider(request)

if resp.name is not None:
name_locales[locale] = resp.name

self._name_localizations = name_locales

async def _handle_pre_hooks(self, command: CallableCommandProto[ClientT], ctx: Context[ClientT]) -> bool:
"""Handle all pre-execution hooks for a command.
Expand Down
26 changes: 26 additions & 0 deletions arc/abc/option.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@
import hikari

from arc.internal.types import AutocompleteCallbackT, ChoiceT, ClientT, ParamsT
from arc.locale import OptionLocaleRequest

if t.TYPE_CHECKING:
import typing_extensions as te

from arc.abc.client import Client
from arc.abc.command import CommandProto

__all__ = ("Option", "OptionParams", "OptionWithChoices", "OptionWithChoicesParams", "OptionBase", "CommandOptionBase")

T = t.TypeVar("T")
Expand Down Expand Up @@ -167,6 +171,28 @@ def to_command_option(self) -> hikari.CommandOption:
"""Convert this option to a hikari.CommandOption."""
return hikari.CommandOption(**self._to_dict())

def _request_option_locale(self, client: Client[t.Any], command: CommandProto) -> None:
"""Request the option's name and description in different locales."""
if self.name_localizations or self.description_localizations:
return

if not client._provided_locales or not client._option_locale_provider:
return

name_locales: dict[hikari.Locale, str] = {}
desc_locales: dict[hikari.Locale, str] = {}

for locale in client._provided_locales:
request = OptionLocaleRequest(command, locale, self.name, self.description, self)
resp = client._option_locale_provider(request)

if resp.name is not None and resp.description is not None:
name_locales[locale] = resp.name
desc_locales[locale] = resp.description

self.name_localizations = name_locales
self.description_localizations = desc_locales


@attr.define(slots=True, kw_only=True)
class CommandOptionBase(OptionBase[T], t.Generic[T, ClientT, ParamsT]):
Expand Down
14 changes: 12 additions & 2 deletions arc/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class GatewayClient(Client[hikari.GatewayBotAware]):
The guilds that slash commands will be registered in by default, by default None
autosync : bool, optional
Whether to automatically sync commands on startup, by default True
provided_locales : t.Sequence[hikari.Locale] | None, optional
The locales that will be provided to the client by locale provider callbacks, by default None
Usage
-----
Expand All @@ -59,8 +61,11 @@ def __init__(
*,
default_enabled_guilds: t.Sequence[hikari.Snowflake] | None = None,
autosync: bool = True,
provided_locales: t.Sequence[hikari.Locale] | None = None,
) -> None:
super().__init__(app, default_enabled_guilds=default_enabled_guilds, autosync=autosync)
super().__init__(
app, default_enabled_guilds=default_enabled_guilds, autosync=autosync, provided_locales=provided_locales
)
self.app.event_manager.subscribe(hikari.StartedEvent, self._on_gatewaybot_startup)
self.app.event_manager.subscribe(hikari.StoppingEvent, self._on_gatewaybot_shutdown)
self.app.event_manager.subscribe(hikari.InteractionCreateEvent, self._on_gatewaybot_interaction_create)
Expand Down Expand Up @@ -129,6 +134,8 @@ class RESTClient(Client[hikari.RESTBotAware]):
The guilds that slash commands will be registered in by default, by default None
autosync : bool, optional
Whether to automatically sync commands on startup, by default True
provided_locales : t.Sequence[hikari.Locale] | None, optional
The locales that will be provided to the client by locale provider callbacks, by default None
Usage
Expand All @@ -155,8 +162,11 @@ def __init__(
*,
default_enabled_guilds: t.Sequence[hikari.Snowflake] | None = None,
autosync: bool = True,
provided_locales: t.Sequence[hikari.Locale] | None = None,
) -> None:
super().__init__(app, default_enabled_guilds=default_enabled_guilds, autosync=autosync)
super().__init__(
app, default_enabled_guilds=default_enabled_guilds, autosync=autosync, provided_locales=provided_locales
)
self.app.add_startup_callback(self._on_restbot_startup)
self.app.add_shutdown_callback(self._on_restbot_shutdown)
self.app.interaction_server.set_listener(
Expand Down
Loading

0 comments on commit 952e572

Please sign in to comment.