Skip to content

Commit 79931fd

Browse files
authored
Merge branch 'twilight-rs:main' into main
2 parents 935cd82 + 268052f commit 79931fd

65 files changed

Lines changed: 2175 additions & 544 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/check.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ jobs:
2525
- name: Checkout sources
2626
uses: actions/checkout@v4
2727

28-
- name: Get latest compatible dependencies for MSRV
29-
run: CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS=fallback cargo update
30-
3128
- name: Retrieve rust-version
3229
run: echo rust-version=$(awk '/rust-version/{print $NF}' Cargo.toml | tr -d '"') >> $GITHUB_ENV
3330

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ members = [
1414
"twilight-util",
1515
"twilight-validate",
1616
]
17-
resolver = "2"
17+
resolver = "3"
1818

1919
[workspace.package]
2020
authors = ["Twilight Contributors"]

book/src/chapter_1_crates/section_7_first_party/section_4_util.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ enabled via feature flags.
1212
### Builder
1313

1414
The `builder` feature enables builders for large structs. At the time of
15-
writing, it contains the following builders:
15+
writing, it contains the following:
16+
1617
- [`CommandBuilder`]
1718
- [`EmbedBuilder`]
18-
- [`InteractionResponseData`]
19+
- [`interaction_response`] builders module
20+
- [`message`] component builders module
1921

2022
#### Command example
2123

@@ -145,4 +147,5 @@ let timestamp = user.timestamp();
145147

146148
[`CommandBuilder`]: https://api.twilight.rs/twilight_util/builder/command/struct.CommandBuilder.html
147149
[`EmbedBuilder`]: https://api.twilight.rs/twilight_util/builder/embed/struct.EmbedBuilder.html
148-
[`InteractionResponseDataBuilder`]: https://api.twilight.rs/twilight_util/builder/struct.InteractionResponseDataBuilder.html
150+
[`interaction_response`]: https://api.twilight.rs/twilight_util/builder/interaction_response/index.html
151+
[`message`]: https://api.twilight.rs/twilight_util/builder/message/index.html

examples/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ twilight-http = { path = "../twilight-http" }
2929
twilight-lavalink = { path = "../twilight-lavalink" }
3030
twilight-model = { path = "../twilight-model" }
3131
twilight-standby = { path = "../twilight-standby" }
32+
twilight-util = { features = ["builder"], path = "../twilight-util" }
3233

3334
[[example]]
3435
name = "cache-optimization"

examples/gateway-reshard.rs

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::{
2-
env,
2+
env, iter,
33
sync::{
44
Arc,
55
atomic::{AtomicBool, Ordering},
@@ -12,9 +12,7 @@ use tokio::{
1212
};
1313
use tokio_stream::StreamExt as _;
1414
use tokio_util::sync::CancellationToken;
15-
use twilight_gateway::{
16-
Config, ConfigBuilder, EventTypeFlags, Intents, Shard, ShardId, StreamExt as _,
17-
};
15+
use twilight_gateway::{Config, EventTypeFlags, Intents, Shard, StreamExt as _};
1816
use twilight_http::Client;
1917

2018
#[tokio::main]
@@ -29,10 +27,12 @@ async fn main() -> anyhow::Result<()> {
2927
let token = env::var("DISCORD_TOKEN")?;
3028
let client = Client::new(token.clone());
3129
let config = Config::new(token, Intents::GUILDS);
32-
let config_callback = |_, builder: ConfigBuilder| builder.build();
3330

34-
let mut shards = twilight_gateway::create_recommended(&client, config.clone(), config_callback)
35-
.await?
31+
let info = client.gateway().authed().await?.model().await?;
32+
33+
let mut shards = twilight_gateway::bucket(0, 1, info.shards)
34+
.zip(iter::repeat_n(config.clone(), info.shards as usize))
35+
.map(|(shard_id, config)| Shard::with_config(shard_id, config))
3636
.collect::<Vec<_>>();
3737

3838
loop {
@@ -42,7 +42,7 @@ async fn main() -> anyhow::Result<()> {
4242
set.spawn(runner(shard));
4343
}
4444

45-
shards = reshard(&client, config.clone(), config_callback).await?;
45+
shards = reshard(&client, config.clone()).await?;
4646
}
4747
}
4848

@@ -62,21 +62,18 @@ async fn runner(mut shard: Shard) {
6262
}
6363

6464
// Instrument to differentiate between the logs produced here and in `runner`.
65-
async fn reshard(
66-
client: &Client,
67-
config: Config,
68-
config_callback: impl Fn(ShardId, ConfigBuilder) -> Config,
69-
) -> anyhow::Result<Vec<Shard>> {
65+
async fn reshard(client: &Client, config: Config) -> anyhow::Result<Vec<Shard>> {
7066
// Reshard every eight hours. This is an arbitrary number.
7167
const RESHARD_DURATION: Duration = Duration::from_secs(60 * 60 * 8);
7268

7369
time::sleep(RESHARD_DURATION).await;
7470

7571
let info = client.gateway().authed().await?.model().await?;
7672

77-
let shards =
78-
twilight_gateway::create_iterator(0..info.shards, info.shards, config, config_callback)
79-
.collect::<Vec<_>>();
73+
let shards = twilight_gateway::bucket(0, 1, info.shards)
74+
.zip(iter::repeat_n(config, info.shards as usize))
75+
.map(|(shard_id, config)| Shard::with_config(shard_id, config))
76+
.collect::<Vec<_>>();
8077

8178
let expected_duration = estimate_identifed(
8279
info.shards,

examples/model-webhook-slash.rs

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@ use once_cell::sync::Lazy;
1414
use std::{future::Future, net::SocketAddr};
1515
use tokio::net::TcpListener;
1616
use twilight_model::{
17-
application::interaction::{
18-
Interaction, InteractionData, InteractionType, application_command::CommandData,
19-
},
20-
http::interaction::{InteractionResponse, InteractionResponseData, InteractionResponseType},
17+
application::interaction::{Interaction, InteractionType, application_command::CommandData},
18+
http::interaction::{InteractionResponse, InteractionResponseType},
2119
};
20+
use twilight_util::builder::interaction_response::ChannelMessageBuilder;
2221

2322
/// Public key given from Discord.
2423
static PUB_KEY: Lazy<VerifyingKey> = Lazy::new(|| {
@@ -114,11 +113,7 @@ where
114113
// Respond to a slash command.
115114
InteractionType::ApplicationCommand => {
116115
// Run the handler to gain a response.
117-
let data = match interaction.data {
118-
Some(InteractionData::ApplicationCommand(data)) => Some(data),
119-
_ => None,
120-
}
121-
.expect("`InteractionType::ApplicationCommand` has data");
116+
let data = interaction.data.unwrap().try_into().unwrap();
122117
let response = f(data).await?;
123118

124119
// Serialize the response and return it back to Discord.
@@ -148,24 +143,14 @@ async fn handler(data: Box<CommandData>) -> anyhow::Result<InteractionResponse>
148143

149144
/// Example of a handler that returns the formatted version of the interaction.
150145
async fn debug(data: Box<CommandData>) -> anyhow::Result<InteractionResponse> {
151-
Ok(InteractionResponse {
152-
kind: InteractionResponseType::ChannelMessageWithSource,
153-
data: Some(InteractionResponseData {
154-
content: Some(format!("```rust\n{data:?}\n```")),
155-
..Default::default()
156-
}),
157-
})
146+
Ok(ChannelMessageBuilder::new()
147+
.content(format!("```rust\n{data:?}\n```"))
148+
.build())
158149
}
159150

160151
/// Example of interaction that responds with a message saying "Vroom vroom".
161152
async fn vroom(_: Box<CommandData>) -> anyhow::Result<InteractionResponse> {
162-
Ok(InteractionResponse {
163-
kind: InteractionResponseType::ChannelMessageWithSource,
164-
data: Some(InteractionResponseData {
165-
content: Some("Vroom vroom".to_owned()),
166-
..Default::default()
167-
}),
168-
})
153+
Ok(ChannelMessageBuilder::new().content("Vroom vroom").build())
169154
}
170155

171156
#[tokio::main]

twilight-cache-inmemory/src/event/interaction.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ mod tests {
337337
message: None,
338338
token: "token".into(),
339339
user: None,
340+
attachment_size_limit: 0,
340341
}));
341342

342343
{

twilight-gateway/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,8 @@ anyhow = { default-features = false, features = ["std"], version = "1" }
4141
rustls = { default-features = false, features = ["ring"], version = "0.23" }
4242
serde_test = { default-features = false, version = "1.0.136" }
4343
static_assertions = { default-features = false, version = "1" }
44-
tokio = { default-features = false, features = ["macros", "rt-multi-thread", "signal", "test-util"], version = "1.12" }
44+
tokio = { default-features = false, features = ["macros", "test-util"], version = "1.12" }
4545
tokio-stream = { default-features = false, version = "0.1" }
46-
tokio-util = { default-features = false, features = ["rt"], version = "0.7" }
4746
tracing-subscriber = { default-features = false, features = ["fmt", "tracing-log"], version = "0.3" }
4847

4948
[features]

twilight-gateway/README.md

Lines changed: 34 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,24 @@ connection to Discord's gateway. Much of its functionality can be configured,
1111
and it's used to receive gateway events or raw Websocket messages, useful for
1212
load balancing and microservices.
1313

14-
Multiple shards may easily be created at once, with a per shard config created
15-
from a `Fn(ShardId, ConfigBuilder) -> Config` closure, with the help of the
16-
`create_` set of functions. These functions will reuse shards' TLS context and
17-
[session queue][queue], something otherwise achieved by cloning an existing
18-
[`Config`].
14+
Multiple shards may be created with the `bucket` function, and optionally with a
15+
per-shard config.
16+
17+
```rust
18+
use twilight_gateway::{Config, Shard};
19+
20+
fn shared(config: Config, shards: u32) -> impl Iterator<Item = Shard> {
21+
unique(std::iter::repeat_n(config, shards as usize))
22+
}
23+
24+
fn unique(iter: impl ExactSizeIterator<Item = Config>) -> impl Iterator<Item = Shard> {
25+
let bucket_id = 0;
26+
let buckets = 1;
27+
let shards = iter.len() as u32;
28+
iter.zip(twilight_gateway::bucket(bucket_id, buckets, shards))
29+
.map(|(config, shard_id)| Shard::with_config(shard_id, config))
30+
}
31+
```
1932

2033
## Features
2134

@@ -33,145 +46,43 @@ from a `Fn(ShardId, ConfigBuilder) -> Config` closure, with the help of the
3346
* `zlib`: Zlib transport compression using [`zlib-rs`][^1]
3447
* `zstd` (*default*): Zstandard transport compression using [`zstd-sys`]
3548

36-
## Example
49+
## Examples
3750

38-
Create the recommended number of shards and loop over their guild messages:
51+
Create a shard and loop over guild events:
3952

4053
```rust,no_run
41-
mod context {
42-
use std::{ops::Deref, sync::OnceLock};
43-
use twilight_http::Client;
44-
45-
pub static CONTEXT: Handle = Handle(OnceLock::new());
46-
47-
#[derive(Debug)]
48-
pub struct Context {
49-
pub http: Client,
50-
}
51-
52-
pub fn initialize(http: Client) {
53-
let context = Context { http };
54-
assert!(CONTEXT.0.set(context).is_ok());
55-
}
56-
57-
pub struct Handle(OnceLock<Context>);
58-
impl Deref for Handle {
59-
type Target = Context;
60-
61-
fn deref(&self) -> &Self::Target {
62-
self.0.get().unwrap()
63-
}
64-
}
65-
}
66-
67-
use context::CONTEXT;
68-
use std::{env, pin::pin};
69-
use tokio::signal;
70-
use tokio_util::task::TaskTracker;
71-
use twilight_gateway::{
72-
CloseFrame, Config, Event, EventTypeFlags, Intents, MessageSender, Shard, StreamExt as _,
73-
};
74-
use twilight_http::Client;
75-
use twilight_model::gateway::payload::{incoming::MessageCreate, outgoing::UpdateVoiceState};
76-
77-
const EVENT_TYPES: EventTypeFlags = EventTypeFlags::MESSAGE_CREATE;
78-
const INTENTS: Intents = Intents::GUILD_MESSAGES.union(Intents::MESSAGE_CONTENT);
54+
use std::env;
55+
use twilight_gateway::{EventTypeFlags, Intents, Shard, ShardId, StreamExt as _};
7956
80-
#[tokio::main]
57+
#[tokio::main(flavor = "current_thread")]
8158
async fn main() -> anyhow::Result<()> {
8259
// Initialize the tracing subscriber.
8360
tracing_subscriber::fmt::init();
8461
8562
// Select rustls backend
8663
rustls::crypto::ring::default_provider().install_default().unwrap();
8764
88-
let token = env::var("DISCORD_TOKEN")?;
89-
90-
let config = Config::new(token.clone(), INTENTS);
91-
let http = Client::new(token);
92-
let shards =
93-
twilight_gateway::create_recommended(&http, config, |_, builder| builder.build()).await?;
94-
context::initialize(http);
95-
96-
let tracker = TaskTracker::new();
97-
for shard in shards {
98-
tracker.spawn(dispatcher(shard));
99-
}
100-
tracker.close();
101-
tracker.wait().await;
102-
103-
Ok(())
104-
}
65+
let token = env::var("TOKEN")?;
10566
106-
#[tracing::instrument(fields(shard = %shard.id()), skip_all)]
107-
async fn dispatcher(mut shard: Shard) {
108-
let mut ctrl_c = pin!(signal::ctrl_c());
109-
let mut shutdown = false;
110-
let tracker = TaskTracker::new();
111-
loop {
112-
tokio::select! {
113-
// Do not poll ctrl_c after it's completed.
114-
_ = &mut ctrl_c, if !shutdown => {
115-
// Cleanly shut down once we receive the echo close frame.
116-
shard.close(CloseFrame::NORMAL);
117-
shutdown = true;
118-
},
119-
Some(item) = shard.next_event(EVENT_TYPES) => {
120-
let event = match item {
121-
Ok(event) => event,
122-
Err(source) => {
123-
tracing::warn!(?source, "error receiving event");
124-
continue;
125-
}
126-
};
127-
128-
let handler = match event {
129-
// Clean shutdown exit condition.
130-
Event::GatewayClose(_) if shutdown => break,
131-
Event::MessageCreate(e) => message(e, shard.sender()),
132-
_ => continue,
133-
};
134-
135-
tracker.spawn(async move {
136-
if let Err(source) = handler.await {
137-
tracing::warn!(?source, "error handling event");
138-
}
139-
});
140-
}
141-
}
142-
}
67+
// Initialize the first and only shard in use by a bot.
68+
let mut shard = Shard::new(ShardId::ONE, token, Intents::GUILDS);
14369
144-
tracker.close();
145-
tracker.wait().await;
146-
}
70+
tracing::info!("started shard");
14771
148-
#[tracing::instrument(fields(id = %event.id), skip_all)]
149-
async fn message(event: Box<MessageCreate>, sender: MessageSender) -> anyhow::Result<()> {
150-
match &*event.content {
151-
"!join" if event.guild_id.is_some() => {
152-
sender.command(&UpdateVoiceState::new(
153-
event.guild_id.unwrap(),
154-
Some(event.channel_id),
155-
false,
156-
false,
157-
))?;
158-
}
159-
"!ping" => {
160-
CONTEXT
161-
.http
162-
.create_message(event.channel_id)
163-
.content("Pong!")
164-
.await?;
72+
while let Some(event) = shard.next_event(EventTypeFlags::all()).await {
73+
match event {
74+
Ok(event) => tracing::info!(?event, "received event"),
75+
Err(source) => tracing::warn!(?source, "failed to receive event"),
16576
}
166-
_ => {}
16777
}
16878
16979
Ok(())
17080
}
17181
```
17282

17383
There are a few additional examples located in the
174-
[repository][github examples link].
84+
[repository][github examples link]. Check out our [template] to get started
85+
quickly.
17586

17687
[^1]: Except for the s390x arch, where [`zlib-ng-sys`] is used instead.
17788

@@ -194,3 +105,4 @@ There are a few additional examples located in the
194105
[license badge]: https://img.shields.io/badge/license-ISC-blue.svg?style=for-the-badge&logo=pastebin
195106
[license link]: https://github.com/twilight-rs/twilight/blob/main/LICENSE.md
196107
[rust badge]: https://img.shields.io/badge/rust-1.79+-93450a.svg?style=for-the-badge&logo=rust
108+
[template]: https://github.com/twilight-rs/template

twilight-gateway/src/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ impl<Q> ConfigBuilder<Q> {
274274
/// presence::{ActivityType, MinimalActivity, Status},
275275
/// };
276276
///
277-
/// # #[tokio::main]
277+
/// # #[tokio::main(flavor = "current_thread")]
278278
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
279279
/// let config = ConfigBuilder::new(env::var("DISCORD_TOKEN")?, Intents::empty())
280280
/// .presence(UpdatePresencePayload::new(

0 commit comments

Comments
 (0)