From fcde840f2b547ff73507adc4cb6af6524eb5e8ce Mon Sep 17 00:00:00 2001 From: HyperGH <46067571+HyperGH@users.noreply.github.com> Date: Sat, 25 Feb 2023 02:06:35 +0100 Subject: [PATCH 1/4] Add event guides --- docs/guide.md | 12 +++ docs/guides/data.md | 4 + docs/guides/event.md | 9 ++ docs/guides/events/advanced.md | 115 +++++++++++++++++++++++++ docs/guides/events/basics.md | 84 ++++++++++++++++++ docs/guides/ext.md | 1 + docs/guides/intents.md | 5 ++ docs/guides/interaction.md | 9 ++ docs/guides/interactions/commands.md | 1 + docs/guides/interactions/components.md | 1 + docs/index.md | 1 + 11 files changed, 242 insertions(+) create mode 100644 docs/guide.md create mode 100644 docs/guides/data.md create mode 100644 docs/guides/event.md create mode 100644 docs/guides/events/advanced.md create mode 100644 docs/guides/events/basics.md create mode 100644 docs/guides/ext.md create mode 100644 docs/guides/intents.md create mode 100644 docs/guides/interaction.md create mode 100644 docs/guides/interactions/commands.md create mode 100644 docs/guides/interactions/components.md diff --git a/docs/guide.md b/docs/guide.md new file mode 100644 index 0000000000..0c7a4fd54b --- /dev/null +++ b/docs/guide.md @@ -0,0 +1,12 @@ +# Guides + +```{toctree} +:titlesonly: +:maxdepth: 1 + +guides/event +guides/data +guides/intents +guides/interaction +guides/ext +``` diff --git a/docs/guides/data.md b/docs/guides/data.md new file mode 100644 index 0000000000..446ba3be1a --- /dev/null +++ b/docs/guides/data.md @@ -0,0 +1,4 @@ +# Obtaining Data + +Cache VS Rest goes here +1 page is enough diff --git a/docs/guides/event.md b/docs/guides/event.md new file mode 100644 index 0000000000..f39b56eea7 --- /dev/null +++ b/docs/guides/event.md @@ -0,0 +1,9 @@ +# Events + +```{toctree} +:titlesonly: +:maxdepth: 1 + +/guides/events/basics +/guides/events/advanced +``` diff --git a/docs/guides/events/advanced.md b/docs/guides/events/advanced.md new file mode 100644 index 0000000000..289ef675ec --- /dev/null +++ b/docs/guides/events/advanced.md @@ -0,0 +1,115 @@ +# Advanced Usage + +```{attention} +This guide only applies to GatewayBot. REST-only applications cannot receive events through the gateway. +``` + +## wait_for + +Sometimes you may want to wait for an event to be received in a procedural manner, then proceed +with execution. + +With `wait_for()` you can block until you receive an event with a given predicate and timeout. + +In the example below, the bot prompts the user to play a number guessing game. +Each iteration, the bot waits for the user to input a number, evaluates it and gives an +appropiate response. + +```py +# We use random for number-generation and asyncio for exception handling +import asyncio +import random + +import hikari + +# -- Snip -- + +@bot.listen() +async def guessing_game(event: hikari.MessageCreateEvent) -> None: + + if event.is_bot: + return + + me = bot.get_me() + + # We only want to respond to messages where the bot is pinged + # Please note that bots by default do not receive message content for messages + # where they are not pinged or DMd, see the intents section for more information! + if me.id not in event.message.user_mentions_ids: + return + + number = random.randint(1, 10) + guess = None + player = event.author + + await event.message.respond("I thought of a number between 1 and 10!\nPlease enter your first guess!") + + while guess != number: + try: + input_event = await bot.wait_for( + hikari.MessageCreateEvent, + # We only want to check for input coming from the player + # We also want to ensure there is content to parse + predicate=lambda e: e.author_id == player.id and e.content is not None, + # Timeout, in seconds + timeout=60 + ) + except asyncio.TimeoutError: + await event.message.respond(f"{player.mention} did not guess the number in time!") + break + + if not input_event.content.isdigit(): + await input_event.message.respond(f"{player.mention}, please enter a valid guess!") + continue + + guess = int(input_event.content) + + if guess < number: + await input_event.message.respond(f"{player.mention}, your guess is too low!") + elif guess > number: + await input_event.message.respond(f"{player.mention}, your guess is too high!") + + await event.message.respond(f"You guessed the number! It was **{number}**!") + +# -- Snip -- +``` + +--- + +## stream + +If you prefer a more functional approach to event handling, you can also use hikari's event streams! + +In the example below, we query the user for their 3 most favorite movies and gather them into a list. + +```py +@bot.listen() +async def favorite_movie_collector(event: hikari.MessageCreateEvent) -> None: + + if event.is_bot: + return + + me = bot.get_me() + + # We only want to respond to messages where the bot is pinged + # Please note that bots by default do not receive message content for messages + # where they are not pinged or DMd, see the intents section for more information! + if me.id not in event.message.user_mentions_ids: + return + + + await event.message.respond("Please enter your 3 favorite movies!") + + with bot.stream(hikari.MessageCreateEvent, timeout=None) as stream: + movies = await ( + stream + .filter(lambda e: e.author_id == event.author.id and bool(event.message.content)) + .limit(3) + .map(lambda e: e.message.content) + .collect(list) + ) + + await event.message.respond(f"Your favorite movies are:```{' '.join(movies)}```") +``` + +For more methods available on hikari's `LazyIterator`, check [this page](https://docs.hikari-py.dev/en/latest/reference/hikari/iterators/#hikari.iterators.LazyIterator). diff --git a/docs/guides/events/basics.md b/docs/guides/events/basics.md new file mode 100644 index 0000000000..6ea3cbbb4c --- /dev/null +++ b/docs/guides/events/basics.md @@ -0,0 +1,84 @@ +# Basics + +```{attention} +This guide only applies to GatewayBot. REST-only applications cannot receive events through the gateway. +``` + +## What are events? + +When your application connects to the Discord Gateway, it will start receiveing information about actions +happening within the guilds your bot is a member of. These pieces of information are called "events", +and inform your bot about actions other users or bots may have triggered. + +An example would be `MessageCreateEvent`, which the bot receives every time a message is sent in a channel +the bot can see. It contains some information about the message, where it was sent, etc.. + +## Listeners + +To execute code when the bot receives an event, we can create a listener. This is an async function that +will be called every time an event of the type we specified is encountered. + +```py +@bot.listen() +async def message_listener(event: hikari.MessageCreateEvent) -> None: + print(f"I have received a message from {event.author} in {event.channel_id}!") +``` + +You may also put the event's type in the decorator instead of a type annotation: + +```py +@bot.listen(hikari.MessageCreateEvent) +async def message_listener(event) -> None: + print(f"I have received a message from {event.author} in {event.channel_id}!") +``` + +Each event type has different attributes, for a list of all event types, see [this page](https://docs.hikari-py.dev/en/latest/reference/hikari/events/). + +--- + +```{attention} +The above example makes the assumption that you do not plan to respond to the message. If you want to do so, you must first check if the message does not +originate from your bot, otherwise you may end up in an infinite loop! +``` + +### Don't + +```py +@bot.listen() +async def message_listener(event: hikari.MessageCreateEvent) -> None: + # This will end up in an infinite loop with the bot responding to itself + await event.message.respond("Hi!") +``` + +### Do + +```py +@bot.listen() +async def message_listener(event: hikari.MessageCreateEvent) -> None: + if event.is_bot: # Ignore messages from bots + return + + await event.message.respond("Hi!") +``` + +--- + +## Subscribing listeners + +It may be undesirable, or even infeasible to use the decorator-syntax above to create a listener in some cases, +such as when trying to programatically register listeners. This is where `subscribe()` comes in. + +```py +bot = hikari.GatewayBot(...) + +# -- Snip -- + +async def message_listener(event: hikari.MessageCreateEvent) -> None: + print(f"I have received a message from {event.author} in {event.channel_id}!") + +# -- Snip -- + +bot.subscribe(hikari.MessageCreateEvent, message_listener) +``` + +You may also use `unsubscribe()` to deregister a listener function from a given event type the same way. diff --git a/docs/guides/ext.md b/docs/guides/ext.md new file mode 100644 index 0000000000..c0e47e6961 --- /dev/null +++ b/docs/guides/ext.md @@ -0,0 +1 @@ +# Additional extensions diff --git a/docs/guides/intents.md b/docs/guides/intents.md new file mode 100644 index 0000000000..2b3b68f83b --- /dev/null +++ b/docs/guides/intents.md @@ -0,0 +1,5 @@ +# Intents + +1 page is enough with brief explainer +priviliged intent enabling guide with screenshots on ddev portal +also link to dapi docs for intents diff --git a/docs/guides/interaction.md b/docs/guides/interaction.md new file mode 100644 index 0000000000..262e56e28b --- /dev/null +++ b/docs/guides/interaction.md @@ -0,0 +1,9 @@ +# Interactions + +```{toctree} +:titlesonly: +:maxdepth: 1 + +/guides/interactions/commands +/guides/interactions/components +``` \ No newline at end of file diff --git a/docs/guides/interactions/commands.md b/docs/guides/interactions/commands.md new file mode 100644 index 0000000000..61c515e7b4 --- /dev/null +++ b/docs/guides/interactions/commands.md @@ -0,0 +1 @@ +# Commands diff --git a/docs/guides/interactions/components.md b/docs/guides/interactions/components.md new file mode 100644 index 0000000000..0eac4eb522 --- /dev/null +++ b/docs/guides/interactions/components.md @@ -0,0 +1 @@ +# Components diff --git a/docs/index.md b/docs/index.md index 62e7c6438d..34470e24d7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,6 +7,7 @@ :titlesonly: :maxdepth: 1 +/guide API Reference /changelog/index ``` From 86f12cabc32e1e1b6fa5672a7dde7855b542f59e Mon Sep 17 00:00:00 2001 From: HyperGH <46067571+HyperGH@users.noreply.github.com> Date: Sat, 25 Feb 2023 02:30:16 +0100 Subject: [PATCH 2/4] Implement some suggestions --- docs/guides/events/advanced.md | 6 +++--- docs/guides/events/basics.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/guides/events/advanced.md b/docs/guides/events/advanced.md index 289ef675ec..128a667c78 100644 --- a/docs/guides/events/advanced.md +++ b/docs/guides/events/advanced.md @@ -12,7 +12,7 @@ with execution. With `wait_for()` you can block until you receive an event with a given predicate and timeout. In the example below, the bot prompts the user to play a number guessing game. -Each iteration, the bot waits for the user to input a number, evaluates it and gives an +Each iteration, the bot waits for the user to input a number, evaluates it, then gives an appropiate response. ```py @@ -27,7 +27,7 @@ import hikari @bot.listen() async def guessing_game(event: hikari.MessageCreateEvent) -> None: - if event.is_bot: + if not event.is_human: return me = bot.get_me() @@ -86,7 +86,7 @@ In the example below, we query the user for their 3 most favorite movies and gat @bot.listen() async def favorite_movie_collector(event: hikari.MessageCreateEvent) -> None: - if event.is_bot: + if not event.is_human: return me = bot.get_me() diff --git a/docs/guides/events/basics.md b/docs/guides/events/basics.md index 6ea3cbbb4c..ef94738b67 100644 --- a/docs/guides/events/basics.md +++ b/docs/guides/events/basics.md @@ -55,7 +55,7 @@ async def message_listener(event: hikari.MessageCreateEvent) -> None: ```py @bot.listen() async def message_listener(event: hikari.MessageCreateEvent) -> None: - if event.is_bot: # Ignore messages from bots + if not event.is_human: # Ignore messages from bots return await event.message.respond("Hi!") From 84d602eb57cc3f1fe96a325ae69f1f1f4d7991d9 Mon Sep 17 00:00:00 2001 From: HyperGH <46067571+HyperGH@users.noreply.github.com> Date: Sun, 5 Mar 2023 13:31:40 +0100 Subject: [PATCH 3/4] More guides --- docs/guides/data.md | 62 ++++++++++++++++++++++++++++++++-- docs/guides/events/advanced.md | 4 +-- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/docs/guides/data.md b/docs/guides/data.md index 446ba3be1a..670ae0477a 100644 --- a/docs/guides/data.md +++ b/docs/guides/data.md @@ -1,4 +1,62 @@ # Obtaining Data -Cache VS Rest goes here -1 page is enough +Many operations you might want to do via your bot may require additional data to complete, for example, information +about users, guilds, channels, and so on. There's two main ways of obtaining this information, which is detailed below. + +## REST + +The first option is to query the Discord API directly for this information, which, while typically guarantees to +provide the most up-to-date information possible, is much slower and consumes API ratelimits. Therefore it is advisable +to avoid calling these often (e.g. per event). + +```py +bot = hikari.GatewayBot(...) +# OR +bot = hikari.RESTBot(...) + +# -- Snip -- + +# Requesting a specific guild's data +guild_id = ... +guild = await bot.rest.fetch_guild(guild_id) +print(f"The guild's name is {guild.name}!") + +``` + +All rest calls can be performed via the bot's `RESTClient` instance, which can be accessed via `bot.rest`. + +```{note} +Please note that the available data may depend on what intents your bot has. For example, you cannot fetch specific members +without the members intent. Please see the intents section of the guide for more on this. +``` + +You can also use the `RESTClient` to perform actions on Discord via your bot, for example send a message, create a new role, +or kick someone. + +```py +channel_id = ... +await bot.rest.create_message(channel_id, "Hello!") +``` + +For all available rest methods, see [this page](https://docs.hikari-py.dev/en/latest/reference/hikari/api/rest/). + +## Cache + +```{note} +The cache is only available if your application uses `GatewayBot` as it's base. +``` + +hikari, by default, caches most objects received in events through the gateway, and also performs a process called "chunking" +on startup, where it populates the bot's cache with guilds, channels (and members, if you have the priviliged intent). Accessing +data this way is much faster, and doesn't consume ratelimits, therefore it is the recommended way of obtaining information. + +```py +guild_id = ... +guild = bot.cache.get_guild(guild_id) +print(f"The guild's name is {guild.name}!") +``` + +All cache calls can be performed via the bot's `Cache` instance, which can be accessed via `bot.cache`. + +To configure what gets cached by hikari, you may pass an instance of :obj:`hikari.impl.config.CacheSettings` to the `cache_settings` keyword-only argument of +the `GatewayBot` upon instantiation. diff --git a/docs/guides/events/advanced.md b/docs/guides/events/advanced.md index 128a667c78..9f6d43ae4f 100644 --- a/docs/guides/events/advanced.md +++ b/docs/guides/events/advanced.md @@ -47,7 +47,7 @@ async def guessing_game(event: hikari.MessageCreateEvent) -> None: while guess != number: try: input_event = await bot.wait_for( - hikari.MessageCreateEvent, + hikari.MessageCreateEvent, # We only want to check for input coming from the player # We also want to ensure there is content to parse predicate=lambda e: e.author_id == player.id and e.content is not None, @@ -68,7 +68,7 @@ async def guessing_game(event: hikari.MessageCreateEvent) -> None: await input_event.message.respond(f"{player.mention}, your guess is too low!") elif guess > number: await input_event.message.respond(f"{player.mention}, your guess is too high!") - + await event.message.respond(f"You guessed the number! It was **{number}**!") # -- Snip -- From f349c7a9300eda5c109db0079820ca19a5880a9f Mon Sep 17 00:00:00 2001 From: Hyper <46067571+HyperGH@users.noreply.github.com> Date: Sun, 5 Mar 2023 14:36:17 +0100 Subject: [PATCH 4/4] Apply suggestions from nova Co-authored-by: Nova <110734810+novanai@users.noreply.github.com> Signed-off-by: Hyper <46067571+HyperGH@users.noreply.github.com> --- docs/guides/data.md | 12 ++++++------ docs/guides/events/advanced.md | 7 +++---- docs/guides/events/basics.md | 10 +++++----- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/guides/data.md b/docs/guides/data.md index 670ae0477a..cacf7a7e0d 100644 --- a/docs/guides/data.md +++ b/docs/guides/data.md @@ -1,7 +1,7 @@ # Obtaining Data Many operations you might want to do via your bot may require additional data to complete, for example, information -about users, guilds, channels, and so on. There's two main ways of obtaining this information, which is detailed below. +about users, guilds, channels, and so on. There's two main ways of obtaining this information, both detailed below. ## REST @@ -23,11 +23,11 @@ print(f"The guild's name is {guild.name}!") ``` -All rest calls can be performed via the bot's `RESTClient` instance, which can be accessed via `bot.rest`. +All rest calls can be performed via the `bot`'s `RESTClient` instance, which can be accessed via `bot.rest`. ```{note} Please note that the available data may depend on what intents your bot has. For example, you cannot fetch specific members -without the members intent. Please see the intents section of the guide for more on this. +without the `GUILD_MEMBERS` intent. Please see the intents section of the guide for more on this. ``` You can also use the `RESTClient` to perform actions on Discord via your bot, for example send a message, create a new role, @@ -43,11 +43,11 @@ For all available rest methods, see [this page](https://docs.hikari-py.dev/en/la ## Cache ```{note} -The cache is only available if your application uses `GatewayBot` as it's base. +The cache is only available if your application uses `GatewayBot` as its base. ``` hikari, by default, caches most objects received in events through the gateway, and also performs a process called "chunking" -on startup, where it populates the bot's cache with guilds, channels (and members, if you have the priviliged intent). Accessing +on startup, where it populates the bot's cache with guilds, channels (and members, if you have that priviliged intent). Accessing data this way is much faster, and doesn't consume ratelimits, therefore it is the recommended way of obtaining information. ```py @@ -56,7 +56,7 @@ guild = bot.cache.get_guild(guild_id) print(f"The guild's name is {guild.name}!") ``` -All cache calls can be performed via the bot's `Cache` instance, which can be accessed via `bot.cache`. +All cache calls can be performed via the `bot`'s `Cache` instance, which can be accessed via `bot.cache`. To configure what gets cached by hikari, you may pass an instance of :obj:`hikari.impl.config.CacheSettings` to the `cache_settings` keyword-only argument of the `GatewayBot` upon instantiation. diff --git a/docs/guides/events/advanced.md b/docs/guides/events/advanced.md index 9f6d43ae4f..44a325e563 100644 --- a/docs/guides/events/advanced.md +++ b/docs/guides/events/advanced.md @@ -9,7 +9,7 @@ This guide only applies to GatewayBot. REST-only applications cannot receive eve Sometimes you may want to wait for an event to be received in a procedural manner, then proceed with execution. -With `wait_for()` you can block until you receive an event with a given predicate and timeout. +With `wait_for()` you can block until you receive an event with a given predicate, or until a timeout is reached. In the example below, the bot prompts the user to play a number guessing game. Each iteration, the bot waits for the user to input a number, evaluates it, then gives an @@ -22,11 +22,10 @@ import random import hikari -# -- Snip -- +bot = hikari.GatewayBot(...) @bot.listen() async def guessing_game(event: hikari.MessageCreateEvent) -> None: - if not event.is_human: return @@ -112,4 +111,4 @@ async def favorite_movie_collector(event: hikari.MessageCreateEvent) -> None: await event.message.respond(f"Your favorite movies are:```{' '.join(movies)}```") ``` -For more methods available on hikari's `LazyIterator`, check [this page](https://docs.hikari-py.dev/en/latest/reference/hikari/iterators/#hikari.iterators.LazyIterator). +`GatewayBot.stream` returns a `LazyIterator`. For more methods available on hikari's `LazyIterator`, check [this page](https://docs.hikari-py.dev/en/latest/reference/hikari/iterators/#hikari.iterators.LazyIterator). diff --git a/docs/guides/events/basics.md b/docs/guides/events/basics.md index ef94738b67..a38d206f57 100644 --- a/docs/guides/events/basics.md +++ b/docs/guides/events/basics.md @@ -11,12 +11,12 @@ happening within the guilds your bot is a member of. These pieces of information and inform your bot about actions other users or bots may have triggered. An example would be `MessageCreateEvent`, which the bot receives every time a message is sent in a channel -the bot can see. It contains some information about the message, where it was sent, etc.. +the bot can see. It contains some information about the message, where it was sent, etc... ## Listeners To execute code when the bot receives an event, we can create a listener. This is an async function that -will be called every time an event of the type we specified is encountered. +will be called every time an event of the type we specified is received. ```py @bot.listen() @@ -55,7 +55,7 @@ async def message_listener(event: hikari.MessageCreateEvent) -> None: ```py @bot.listen() async def message_listener(event: hikari.MessageCreateEvent) -> None: - if not event.is_human: # Ignore messages from bots + if not event.is_human: # Ignore bot and system messages return await event.message.respond("Hi!") @@ -66,7 +66,7 @@ async def message_listener(event: hikari.MessageCreateEvent) -> None: ## Subscribing listeners It may be undesirable, or even infeasible to use the decorator-syntax above to create a listener in some cases, -such as when trying to programatically register listeners. This is where `subscribe()` comes in. +such as when trying to programmatically register listeners. This is where [`subscribe()`](https://docs.hikari-py.dev/en/latest/reference/hikari/impl/bot/#hikari.impl.bot.GatewayBot.subscribe) comes in. ```py bot = hikari.GatewayBot(...) @@ -81,4 +81,4 @@ async def message_listener(event: hikari.MessageCreateEvent) -> None: bot.subscribe(hikari.MessageCreateEvent, message_listener) ``` -You may also use `unsubscribe()` to deregister a listener function from a given event type the same way. +You may also use [`unsubscribe()`](https://docs.hikari-py.dev/en/latest/reference/hikari/impl/bot/#hikari.impl.bot.GatewayBot.unsubscribe) to deregister a listener function from a given event type the same way.