twitch_irc/lib.rs
1#![warn(missing_docs)]
2//! This is a client library to interface with [Twitch](https://www.twitch.tv/) chat.
3//!
4//! This library is async and runs using the `tokio` runtime.
5//!
6//! # Getting started
7//!
8//! The central feature of this library is the `TwitchIRCClient` which connects to Twitch IRC
9//! for you using a pool of connections and handles all the important bits. Here is a minimal
10//! example to get you started:
11//!
12//! ```no_run
13//! use twitch_irc::login::StaticLoginCredentials;
14//! use twitch_irc::ClientConfig;
15//! use twitch_irc::SecureTCPTransport;
16//! use twitch_irc::TwitchIRCClient;
17//!
18//! #[tokio::main]
19//! pub async fn main() {
20//! // default configuration is to join chat as anonymous.
21//! let config = ClientConfig::default();
22//! let (mut incoming_messages, client) =
23//! TwitchIRCClient::<SecureTCPTransport, StaticLoginCredentials>::new(config);
24//!
25//! // first thing you should do: start consuming incoming messages,
26//! // otherwise they will back up.
27//! let join_handle = tokio::spawn(async move {
28//! while let Some(message) = incoming_messages.recv().await {
29//! println!("Received message: {:?}", message);
30//! }
31//! });
32//!
33//! // join a channel
34//! // This function only returns an error if the passed channel login name is malformed,
35//! // so in this simple case where the channel name is hardcoded we can ignore the potential
36//! // error with `unwrap`.
37//! client.join("sodapoppin".to_owned()).unwrap();
38//!
39//! // keep the tokio executor alive.
40//! // If you return instead of waiting the background task will exit.
41//! join_handle.await.unwrap();
42//! }
43//! ```
44//!
45//! The above example connects to chat anonymously and listens to messages coming to the channel `sodapoppin`.
46//!
47//! # Features
48//!
49//! * Simple API
50//! * Integrated connection pool, new connections will be made based on your application's demand
51//! (based on amount of channels joined as well as number of outgoing messages)
52//! * Automatic reconnect of failed connections, automatically re-joins channels
53//! * Rate limiting of new connections
54//! * Support for refreshing login tokens, see below
55//! * Fully parses all message types (see [`ServerMessage`](message/enum.ServerMessage.html)
56//! for all supported types)
57//! * Can connect using all protocol types supported by Twitch
58//! * Supports Rustls as well as Native TLS
59//! * No unsafe code
60//! * Feature flags to reduce compile time and binary size
61//!
62//! # Send messages
63//!
64//! To send messages, use the `TwitchIRCClient` handle you get from `TwitchIRCClient::new`.
65//!
66//! ```no_run
67//! # use twitch_irc::login::StaticLoginCredentials;
68//! # use twitch_irc::ClientConfig;
69//! # use twitch_irc::SecureTCPTransport;
70//! # use twitch_irc::TwitchIRCClient;
71//! #
72//! # #[tokio::main]
73//! # async fn main() {
74//! # let config = ClientConfig::default();
75//! # let (mut incoming_messages, client) = TwitchIRCClient::<SecureTCPTransport, StaticLoginCredentials>::new(config);
76//! client.say("a_channel".to_owned(), "Hello world!".to_owned()).await.unwrap();
77//! # }
78//! ```
79//!
80//! The `TwitchIRCClient` handle can also be cloned and then used from multiple threads.
81//!
82//! See the documentation on [`TwitchIRCClient`](struct.TwitchIRCClient.html)
83//! for the possible methods.
84//!
85//! # Receive and handle messages
86//!
87//! Incoming messages are [`ServerMessage`](message/enum.ServerMessage.html)s. You can use a match
88//! block to differentiate between the possible server messages:
89//!
90//! ```no_run
91//! # use twitch_irc::message::ServerMessage;
92//! # use tokio::sync::mpsc;
93//! #
94//! # #[tokio::main]
95//! # async fn main() {
96//! # let mut incoming_messages: mpsc::UnboundedReceiver<ServerMessage> = unimplemented!();
97//! while let Some(message) = incoming_messages.recv().await {
98//! match message {
99//! ServerMessage::Privmsg(msg) => {
100//! println!("(#{}) {}: {}", msg.channel_login, msg.sender.name, msg.message_text);
101//! },
102//! ServerMessage::Whisper(msg) => {
103//! println!("(w) {}: {}", msg.sender.name, msg.message_text);
104//! },
105//! _ => {}
106//! }
107//! }
108//! # }
109//! ```
110//!
111//! # Logging in
112//!
113//! `twitch_irc` ships with [`StaticLoginCredentials`](login/struct.StaticLoginCredentials.html)
114//! and [`RefreshingLoginCredentials`](login/struct.RefreshingLoginCredentials.html).
115//!
116//! For simple cases, `StaticLoginCredentials` fulfills all needs:
117//!
118//! ```
119//! use twitch_irc::login::StaticLoginCredentials;
120//! use twitch_irc::ClientConfig;
121//!
122//! let login_name = "your_bot_name".to_owned();
123//! let oauth_token = "u0i05p6kbswa1w72wu1h1skio3o20t".to_owned();
124//!
125//! let config = ClientConfig::new_simple(
126//! StaticLoginCredentials::new(login_name, Some(oauth_token))
127//! );
128//! ```
129//!
130//! However for most applications it is strongly recommended to have your login token automatically
131//! refreshed when it expires. For this, enable one of the `refreshing-token` feature flags
132//! (see [Feature flags](#feature-flags)), and use
133//! [`RefreshingLoginCredentials`](login/struct.RefreshingLoginCredentials.html), for example
134//! like this:
135//!
136//! ```no_run
137//! # #[cfg(feature = "refreshing-login")] {
138//! use async_trait::async_trait;
139//! use twitch_irc::login::{RefreshingLoginCredentials, TokenStorage, UserAccessToken};
140//! use twitch_irc::ClientConfig;
141//!
142//! #[derive(Debug)]
143//! struct CustomTokenStorage {
144//! // fields...
145//! }
146//!
147//! #[async_trait]
148//! impl TokenStorage for CustomTokenStorage {
149//! type LoadError = std::io::Error; // or some other error
150//! type UpdateError = std::io::Error;
151//!
152//! async fn load_token(&mut self) -> Result<UserAccessToken, Self::LoadError> {
153//! // Load the currently stored token from the storage.
154//! Ok(UserAccessToken {
155//! access_token: todo!(),
156//! refresh_token: todo!(),
157//! created_at: todo!(),
158//! expires_at: todo!()
159//! })
160//! }
161//!
162//! async fn update_token(&mut self, token: &UserAccessToken) -> Result<(), Self::UpdateError> {
163//! // Called after the token was updated successfully, to save the new token.
164//! // After `update_token()` completes, the `load_token()` method should then return
165//! // that token for future invocations
166//! todo!()
167//! }
168//! }
169//!
170//! // these credentials can be generated for your app at https://dev.twitch.tv/console/apps
171//! // the bot's username will be fetched based on your access token
172//! let client_id = "rrbau1x7hl2ssz78nd2l32ns9jrx2w".to_owned();
173//! let client_secret = "m6nuam2b2zgn2fw8actt8hwdummz1g".to_owned();
174//! let storage = CustomTokenStorage { /* ... */ };
175//!
176//! let credentials = RefreshingLoginCredentials::new(client_id, client_secret, storage);
177//! // It is also possible to use the same credentials in other places
178//! // such as API calls by cloning them.
179//! let config = ClientConfig::new_simple(credentials);
180//! // then create your client and use it
181//! # }
182//! ```
183//!
184//! `RefreshingLoginCredentials` needs an implementation of `TokenStorage` that depends
185//! on your application, to retrieve the token or update it. For example, you might put the token
186//! in a config file you overwrite, some extra file for secrets, or a database.
187//!
188//! In order to get started with `RefreshingLoginCredentials`, you need to have initial access
189//! and refresh tokens present in your storage. You can fetch these tokens using the
190//! [OAuth authorization code flow](https://dev.twitch.tv/docs/authentication/getting-tokens-oauth#oauth-authorization-code-flow).
191//! There is also a [`GetAccessTokenResponse`](crate::login::GetAccessTokenResponse) helper struct
192//! that allows you to decode the `POST /oauth2/token` response as part of the authorization process.
193//! See the documentation on that type for details on usage and how to convert the decoded response
194//! to a `UserAccessToken` that you can then write to your `TokenStorage`.
195//!
196//! # Close the client
197//!
198//! To close the client, drop all clones of the `TwitchIRCClient` handle. The client will shut down
199//! and end the stream of incoming messages once all processing is done.
200//!
201//! # Feature flags
202//!
203//! This library has these optional feature toggles:
204//! * **`transport-tcp`** enables `PlainTCPTransport`, to connect using a plain-text TLS socket
205//! using the normal IRC protocol.
206//! * `transport-tcp-native-tls` enables `SecureTCPTransport` which will then use OS-native
207//! TLS functionality to make a secure connection. Root certificates are the ones configured
208//! in the operating system.
209//! * `transport-tcp-rustls-native-roots` enables `SecureTCPTransport` using [Rustls][rustls]
210//! as the TLS implementation, but will use the root certificates configured in the
211//! operating system.
212//! * `transport-tcp-rustls-webpki-roots` enables `SecureTCPTransport` using [Rustls][rustls]
213//! as the TLS implementation, and will statically embed the current
214//! [Mozilla root certificates][mozilla-roots] as the trusted root certificates.
215//! * **`transport-ws`** enables `PlainWSTransport` to connect using the Twitch-specific websocket
216//! method. (Plain-text)
217//! * `transport-ws-native-tls` further enables `SecureWSTransport` which will then use OS-native
218//! TLS functionality to make a secure connection. Root certificates are the ones configured
219//! in the operating system.
220//! * `transport-ws-rustls-native-roots` enables `SecureWSTransport` using [Rustls][rustls]
221//! as the TLS implementation, but will use the root certificates configured in the
222//! operating system.
223//! * `transport-ws-rustls-webpki-roots` enables `SecureWSTransport` using [Rustls][rustls]
224//! as the TLS implementation, and will statically embed the current
225//! [Mozilla root certificates][mozilla-roots] as the trusted root certificates.
226//! * Three different feature flags are provided to enable the
227//! [`RefreshingLoginCredentials`](crate::login::RefreshingLoginCredentials):
228//! * `refreshing-token-native-tls` enables this feature using the OS-native TLS functionality
229//! to make secure connections. Root certificates are the ones configured
230//! in the operating system.
231//! * `refreshing-token-rustls-native-roots` enables this feature using
232//! [Rustls][rustls] as the TLS implementation, but will use the root certificates configured
233//! in the operating system.
234//! * `refreshing-token-rustls-webpki-roots` enables this feature using [Rustls][rustls]
235//! as the TLS implementation, and will statically embed the current [Mozilla root
236//! certificates][mozilla-roots] as the trusted root certificates.
237//! * **`metrics-collection`** enables a set of metrics to be exported from the client. See the
238//! documentation on `ClientConfig` for details.
239//! * **`with-serde`** pulls in `serde` v1.0 and adds `#[derive(Serialize, Deserialize)]` to many
240//! structs. This feature flag is automatically enabled when using any of the `refreshing-token`
241//! feature flags.
242//!
243//! By default, `transport-tcp` and `transport-tcp-native-tls` are enabled.
244//!
245//! [rustls]: https://github.com/ctz/rustls
246//! [mozilla-roots]: https://github.com/ctz/webpki-roots
247
248pub mod client;
249mod config;
250mod connection;
251mod error;
252pub mod login;
253pub mod message;
254#[cfg(feature = "metrics-collection")]
255mod metrics;
256pub mod transport;
257pub mod validate;
258
259pub use client::TwitchIRCClient;
260pub use config::ClientConfig;
261#[cfg(feature = "metrics-collection")]
262pub use config::MetricsConfig;
263pub use error::Error;
264
265#[cfg(feature = "transport-tcp")]
266pub use transport::tcp::PlainTCPTransport;
267#[cfg(all(
268 feature = "transport-tcp",
269 any(
270 feature = "transport-tcp-native-tls",
271 feature = "transport-tcp-rustls-native-roots",
272 feature = "transport-tcp-rustls-webpki-roots"
273 )
274))]
275pub use transport::tcp::SecureTCPTransport;
276
277#[cfg(feature = "transport-ws")]
278pub use transport::websocket::PlainWSTransport;
279#[cfg(all(
280 feature = "transport-ws",
281 any(
282 feature = "transport-ws-native-tls",
283 feature = "transport-ws-rustls-native-roots",
284 feature = "transport-ws-rustls-webpki-roots",
285 )
286))]
287pub use transport::websocket::SecureWSTransport;