Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add guides to docs #1522

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Guides

```{toctree}
:titlesonly:
:maxdepth: 1

guides/event
guides/data
guides/intents
guides/interaction
guides/ext
```
62 changes: 62 additions & 0 deletions docs/guides/data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# 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.
hypergonial marked this conversation as resolved.
Show resolved Hide resolved

## 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 --
Copy link
Contributor

Choose a reason for hiding this comment

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

Personally I feel the snip is unnecessary again, but that's just my opinion


# 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`.
hypergonial marked this conversation as resolved.
Show resolved Hide resolved

```{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.
hypergonial marked this conversation as resolved.
Show resolved Hide resolved
```

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.
hypergonial marked this conversation as resolved.
Show resolved Hide resolved
```

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
hypergonial marked this conversation as resolved.
Show resolved Hide resolved
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`.
hypergonial marked this conversation as resolved.
Show resolved Hide resolved

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.
9 changes: 9 additions & 0 deletions docs/guides/event.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Events

```{toctree}
:titlesonly:
:maxdepth: 1

/guides/events/basics
/guides/events/advanced
```
115 changes: 115 additions & 0 deletions docs/guides/events/advanced.md
Original file line number Diff line number Diff line change
@@ -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.
hypergonial marked this conversation as resolved.
Show resolved Hide resolved

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
appropiate response.

```py
# We use random for number-generation and asyncio for exception handling
import asyncio
import random

import hikari

# -- Snip --
hypergonial marked this conversation as resolved.
Show resolved Hide resolved

@bot.listen()
async def guessing_game(event: hikari.MessageCreateEvent) -> None:

hypergonial marked this conversation as resolved.
Show resolved Hide resolved
if not event.is_human:
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 not event.is_human:
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).
hypergonial marked this conversation as resolved.
Show resolved Hide resolved
84 changes: 84 additions & 0 deletions docs/guides/events/basics.md
Original file line number Diff line number Diff line change
@@ -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..
hypergonial marked this conversation as resolved.
Show resolved Hide resolved

## 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.
hypergonial marked this conversation as resolved.
Show resolved Hide resolved

```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}!")
```
Comment on lines +27 to +33
Copy link
Contributor

Choose a reason for hiding this comment

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

Although this is possible, does the guide need to show this?

Suggested change
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}!")
```
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}!")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it is important to note in scenarios where type annotation usage may not be feasible or possible, yes.


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 not event.is_human: # Ignore messages from bots
hypergonial marked this conversation as resolved.
Show resolved Hide resolved
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.
hypergonial marked this conversation as resolved.
Show resolved Hide resolved

```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 --
Copy link
Contributor

Choose a reason for hiding this comment

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

Are the # -- Snip --'s necessary here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd say yes.


bot.subscribe(hikari.MessageCreateEvent, message_listener)
```

You may also use `unsubscribe()` to deregister a listener function from a given event type the same way.
hypergonial marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions docs/guides/ext.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Additional extensions
5 changes: 5 additions & 0 deletions docs/guides/intents.md
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions docs/guides/interaction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Interactions

```{toctree}
:titlesonly:
:maxdepth: 1

/guides/interactions/commands
/guides/interactions/components
```
1 change: 1 addition & 0 deletions docs/guides/interactions/commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Commands
1 change: 1 addition & 0 deletions docs/guides/interactions/components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Components
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
:titlesonly:
:maxdepth: 1

/guide
API Reference </reference/hikari/index>
/changelog/index
```