mirror of
https://github.com/mat-1/azalea.git
synced 2024-09-19 14:42:32 +00:00
socks5 support (#113)
This commit is contained in:
parent
fa96af786b
commit
353eda21ac
9 changed files with 240 additions and 64 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
@ -122,6 +122,12 @@ version = "1.0.79"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
||||
|
||||
[[package]]
|
||||
name = "as-any"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b8a30a44e99a1c83ccb2a6298c563c888952a1c9134953db26876528f84c93a"
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "2.2.0"
|
||||
|
@ -353,6 +359,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"simdnbt",
|
||||
"socks5-impl",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
@ -495,6 +502,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"simdnbt",
|
||||
"socks5-impl",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
|
@ -2533,6 +2541,20 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socks5-impl"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dfc11441196e51be4f48c72b075e7fff394a3c6a43f93420f907a2708079b27"
|
||||
dependencies = [
|
||||
"as-any",
|
||||
"async-trait",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
|
|
|
@ -43,6 +43,7 @@ azalea-entity = { version = "0.9.0", path = "../azalea-entity" }
|
|||
serde_json = "1.0.113"
|
||||
serde = "1.0.196"
|
||||
minecraft_folder_path = "0.1.2"
|
||||
socks5-impl = "0.5.6"
|
||||
|
||||
[features]
|
||||
default = ["log"]
|
||||
|
|
|
@ -34,7 +34,7 @@ use azalea_entity::{
|
|||
};
|
||||
use azalea_physics::PhysicsPlugin;
|
||||
use azalea_protocol::{
|
||||
connect::{Connection, ConnectionError},
|
||||
connect::{Connection, ConnectionError, Proxy},
|
||||
packets::{
|
||||
configuration::{
|
||||
serverbound_client_information_packet::ClientInformation,
|
||||
|
@ -183,6 +183,7 @@ impl Client {
|
|||
pub async fn join(
|
||||
account: &Account,
|
||||
address: impl TryInto<ServerAddress>,
|
||||
proxy: Option<Proxy>,
|
||||
) -> Result<(Self, mpsc::UnboundedReceiver<Event>), JoinError> {
|
||||
let address: ServerAddress = address.try_into().map_err(|_| JoinError::InvalidAddress)?;
|
||||
let resolved_address = resolver::resolve_address(&address).await?;
|
||||
|
@ -200,6 +201,7 @@ impl Client {
|
|||
account,
|
||||
&address,
|
||||
&resolved_address,
|
||||
proxy,
|
||||
run_schedule_sender,
|
||||
)
|
||||
.await
|
||||
|
@ -212,6 +214,7 @@ impl Client {
|
|||
account: &Account,
|
||||
address: &ServerAddress,
|
||||
resolved_address: &SocketAddr,
|
||||
proxy: Option<Proxy>,
|
||||
run_schedule_sender: mpsc::UnboundedSender<()>,
|
||||
) -> Result<(Self, mpsc::UnboundedReceiver<Event>), JoinError> {
|
||||
// check if an entity with our uuid already exists in the ecs and if so then
|
||||
|
@ -239,7 +242,11 @@ impl Client {
|
|||
entity
|
||||
};
|
||||
|
||||
let conn = Connection::new(resolved_address).await?;
|
||||
let conn = if let Some(proxy) = proxy {
|
||||
Connection::new_with_proxy(resolved_address, proxy).await?
|
||||
} else {
|
||||
Connection::new(resolved_address).await?
|
||||
};
|
||||
let (conn, game_profile) =
|
||||
Self::handshake(ecs_lock.clone(), entity, conn, account, address).await?;
|
||||
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
//! Ping Minecraft servers.
|
||||
|
||||
use azalea_protocol::{
|
||||
connect::{Connection, ConnectionError},
|
||||
connect::{Connection, ConnectionError, Proxy},
|
||||
packets::{
|
||||
handshaking::client_intention_packet::ClientIntentionPacket,
|
||||
handshaking::{
|
||||
client_intention_packet::ClientIntentionPacket, ClientboundHandshakePacket,
|
||||
ServerboundHandshakePacket,
|
||||
},
|
||||
status::{
|
||||
clientbound_status_response_packet::ClientboundStatusResponsePacket,
|
||||
serverbound_status_request_packet::ServerboundStatusRequestPacket,
|
||||
|
@ -47,11 +50,29 @@ pub async fn ping_server(
|
|||
address: impl TryInto<ServerAddress>,
|
||||
) -> Result<ClientboundStatusResponsePacket, PingError> {
|
||||
let address: ServerAddress = address.try_into().map_err(|_| PingError::InvalidAddress)?;
|
||||
|
||||
let resolved_address = resolver::resolve_address(&address).await?;
|
||||
let conn = Connection::new(&resolved_address).await?;
|
||||
ping_server_with_connection(address, conn).await
|
||||
}
|
||||
|
||||
let mut conn = Connection::new(&resolved_address).await?;
|
||||
/// Ping a Minecraft server through a Socks5 proxy.
|
||||
pub async fn ping_server_with_proxy(
|
||||
address: impl TryInto<ServerAddress>,
|
||||
proxy: Proxy,
|
||||
) -> Result<ClientboundStatusResponsePacket, PingError> {
|
||||
let address: ServerAddress = address.try_into().map_err(|_| PingError::InvalidAddress)?;
|
||||
let resolved_address = resolver::resolve_address(&address).await?;
|
||||
let conn = Connection::new_with_proxy(&resolved_address, proxy).await?;
|
||||
ping_server_with_connection(address, conn).await
|
||||
}
|
||||
|
||||
/// Ping a Minecraft server after we've already created a [`Connection`]. The
|
||||
/// `Connection` must still be in the handshake state (which is the state it's
|
||||
/// in immediately after it's created).
|
||||
pub async fn ping_server_with_connection(
|
||||
address: ServerAddress,
|
||||
mut conn: Connection<ClientboundHandshakePacket, ServerboundHandshakePacket>,
|
||||
) -> Result<ClientboundStatusResponsePacket, PingError> {
|
||||
// send the client intention packet and switch to the status state
|
||||
conn.write(
|
||||
ClientIntentionPacket {
|
||||
|
|
|
@ -48,6 +48,8 @@ trust-dns-resolver = { version = "^0.23.2", default-features = false, features =
|
|||
uuid = "1.7.0"
|
||||
log = "0.4.20"
|
||||
|
||||
socks5-impl = "0.5.6"
|
||||
|
||||
[features]
|
||||
connecting = []
|
||||
default = ["packets"]
|
||||
|
|
|
@ -20,7 +20,7 @@ use std::io::Cursor;
|
|||
use std::marker::PhantomData;
|
||||
use std::net::SocketAddr;
|
||||
use thiserror::Error;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::io::{AsyncWriteExt, BufStream};
|
||||
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf, ReuniteError};
|
||||
use tokio::net::TcpStream;
|
||||
use tracing::{error, info};
|
||||
|
@ -257,6 +257,20 @@ pub enum ConnectionError {
|
|||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
use socks5_impl::protocol::UserKey;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Proxy {
|
||||
pub addr: SocketAddr,
|
||||
pub auth: Option<UserKey>,
|
||||
}
|
||||
|
||||
impl Proxy {
|
||||
pub fn new(addr: SocketAddr, auth: Option<UserKey>) -> Self {
|
||||
Self { addr, auth }
|
||||
}
|
||||
}
|
||||
|
||||
impl Connection<ClientboundHandshakePacket, ServerboundHandshakePacket> {
|
||||
/// Create a new connection to the given address.
|
||||
pub async fn new(address: &SocketAddr) -> Result<Self, ConnectionError> {
|
||||
|
@ -265,6 +279,28 @@ impl Connection<ClientboundHandshakePacket, ServerboundHandshakePacket> {
|
|||
// enable tcp_nodelay
|
||||
stream.set_nodelay(true)?;
|
||||
|
||||
Self::new_from_stream(stream).await
|
||||
}
|
||||
|
||||
/// Create a new connection to the given address and Socks5 proxy. If you're
|
||||
/// not using a proxy, use [`Self::new`] instead.
|
||||
pub async fn new_with_proxy(
|
||||
address: &SocketAddr,
|
||||
proxy: Proxy,
|
||||
) -> Result<Self, ConnectionError> {
|
||||
let proxy_stream = TcpStream::connect(proxy.addr).await?;
|
||||
let mut stream = BufStream::new(proxy_stream);
|
||||
|
||||
let _ = socks5_impl::client::connect(&mut stream, address, proxy.auth)
|
||||
.await
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
|
||||
|
||||
Self::new_from_stream(stream.into_inner()).await
|
||||
}
|
||||
|
||||
/// Create a new connection from an existing stream. Useful if you want to
|
||||
/// set custom options on the stream. Otherwise, just use [`Self::new`].
|
||||
pub async fn new_from_stream(stream: TcpStream) -> Result<Self, ConnectionError> {
|
||||
let (read_stream, write_stream) = stream.into_split();
|
||||
|
||||
Ok(Connection {
|
||||
|
|
|
@ -181,10 +181,12 @@ async fn swarm_handle(
|
|||
_state: SwarmState,
|
||||
) -> anyhow::Result<()> {
|
||||
match &event {
|
||||
SwarmEvent::Disconnect(account) => {
|
||||
SwarmEvent::Disconnect(account, join_opts) => {
|
||||
println!("bot got kicked! {}", account.username);
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
swarm.add_and_retry_forever(account, State::default()).await;
|
||||
swarm
|
||||
.add_and_retry_forever_with_opts(account, State::default(), join_opts)
|
||||
.await;
|
||||
}
|
||||
SwarmEvent::Chat(chat) => {
|
||||
if chat.message().to_string() == "The particle was not visible for anybody" {
|
||||
|
|
|
@ -38,6 +38,7 @@ pub use azalea_world as world;
|
|||
pub use bot::*;
|
||||
use ecs::component::Component;
|
||||
use futures::{future::BoxFuture, Future};
|
||||
use protocol::connect::Proxy;
|
||||
use protocol::{resolver::ResolverError, ServerAddress};
|
||||
use swarm::SwarmBuilder;
|
||||
use thiserror::Error;
|
||||
|
@ -185,30 +186,26 @@ where
|
|||
account: Account,
|
||||
address: impl TryInto<ServerAddress>,
|
||||
) -> Result<!, StartError> {
|
||||
self.swarm.accounts = vec![account];
|
||||
self.swarm.accounts = vec![(account, JoinOpts::default())];
|
||||
if self.swarm.states.is_empty() {
|
||||
self.swarm.states = vec![S::default()];
|
||||
}
|
||||
self.swarm.start(address).await
|
||||
}
|
||||
|
||||
/// Do the same as [`Self::start`], but allow passing in a custom resolved
|
||||
/// address. This is useful if the address you're connecting to doesn't
|
||||
/// resolve to anything, like if the server uses the address field to pass
|
||||
/// custom data (like Bungeecord or Forge).
|
||||
pub async fn start_with_custom_resolved_address(
|
||||
/// Do the same as [`Self::start`], but allow passing in custom join
|
||||
/// options.
|
||||
pub async fn start_with_opts(
|
||||
mut self,
|
||||
account: Account,
|
||||
address: impl TryInto<ServerAddress>,
|
||||
resolved_address: SocketAddr,
|
||||
opts: JoinOpts,
|
||||
) -> Result<!, StartError> {
|
||||
self.swarm.accounts = vec![account];
|
||||
self.swarm.accounts = vec![(account, opts.clone())];
|
||||
if self.swarm.states.is_empty() {
|
||||
self.swarm.states = vec![S::default()];
|
||||
}
|
||||
self.swarm
|
||||
.start_with_custom_resolved_address(address, resolved_address)
|
||||
.await
|
||||
self.swarm.start_with_default_opts(address, opts).await
|
||||
}
|
||||
}
|
||||
impl Default for ClientBuilder<NoState> {
|
||||
|
@ -224,3 +221,55 @@ impl Default for ClientBuilder<NoState> {
|
|||
/// [`SwarmBuilder`]: swarm::SwarmBuilder
|
||||
#[derive(Component, Clone, Default)]
|
||||
pub struct NoState;
|
||||
|
||||
/// Optional settings when adding an account to a swarm or client.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct JoinOpts {
|
||||
/// The Socks5 proxy that this bot will use.
|
||||
pub proxy: Option<Proxy>,
|
||||
/// Override the server address that this specific bot will send in the
|
||||
/// handshake packet.
|
||||
pub custom_address: Option<ServerAddress>,
|
||||
/// Override the socket address that this specific bot will use to connect
|
||||
/// to the server.
|
||||
pub custom_resolved_address: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
impl JoinOpts {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn update(&mut self, other: &Self) {
|
||||
if let Some(proxy) = other.proxy.clone() {
|
||||
self.proxy = Some(proxy);
|
||||
}
|
||||
if let Some(custom_address) = other.custom_address.clone() {
|
||||
self.custom_address = Some(custom_address);
|
||||
}
|
||||
if let Some(custom_resolved_address) = other.custom_resolved_address {
|
||||
self.custom_resolved_address = Some(custom_resolved_address);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the proxy that this bot will use.
|
||||
#[must_use]
|
||||
pub fn proxy(mut self, proxy: Proxy) -> Self {
|
||||
self.proxy = Some(proxy);
|
||||
self
|
||||
}
|
||||
/// Set the custom address that this bot will send in the handshake packet.
|
||||
#[must_use]
|
||||
pub fn custom_address(mut self, custom_address: ServerAddress) -> Self {
|
||||
self.custom_address = Some(custom_address);
|
||||
self
|
||||
}
|
||||
/// Set the custom resolved address that this bot will use to connect to the
|
||||
/// server.
|
||||
#[must_use]
|
||||
pub fn custom_resolved_address(mut self, custom_resolved_address: SocketAddr) -> Self {
|
||||
self.custom_resolved_address = Some(custom_resolved_address);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ use std::{collections::HashMap, future::Future, net::SocketAddr, sync::Arc, time
|
|||
use tokio::sync::mpsc;
|
||||
use tracing::error;
|
||||
|
||||
use crate::{BoxHandleFn, DefaultBotPlugins, HandleFn, NoState, StartError};
|
||||
use crate::{BoxHandleFn, DefaultBotPlugins, HandleFn, JoinOpts, NoState, StartError};
|
||||
|
||||
/// A swarm is a way to conveniently control many bots at once, while also
|
||||
/// being able to control bots at an individual level when desired.
|
||||
|
@ -51,8 +51,8 @@ where
|
|||
SS: Default + Send + Sync + Clone + Resource + 'static,
|
||||
{
|
||||
pub(crate) app: App,
|
||||
/// The accounts that are going to join the server.
|
||||
pub(crate) accounts: Vec<Account>,
|
||||
/// The accounts and proxies that are going to join the server.
|
||||
pub(crate) accounts: Vec<(Account, JoinOpts)>,
|
||||
/// The individual bot states. This must be the same length as `accounts`,
|
||||
/// since each bot gets one state.
|
||||
pub(crate) states: Vec<S>,
|
||||
|
@ -257,8 +257,20 @@ where
|
|||
/// Add an account with a custom initial state. Use just
|
||||
/// [`Self::add_account`] to use the Default implementation for the state.
|
||||
#[must_use]
|
||||
pub fn add_account_with_state(mut self, account: Account, state: S) -> Self {
|
||||
self.accounts.push(account);
|
||||
pub fn add_account_with_state(self, account: Account, state: S) -> Self {
|
||||
self.add_account_with_state_and_opts(account, state, JoinOpts::default())
|
||||
}
|
||||
|
||||
/// Same as [`Self::add_account_with_state`], but allow passing in custom
|
||||
/// join options.
|
||||
#[must_use]
|
||||
pub fn add_account_with_state_and_opts(
|
||||
mut self,
|
||||
account: Account,
|
||||
state: S,
|
||||
join_opts: JoinOpts,
|
||||
) -> Self {
|
||||
self.accounts.push((account, join_opts));
|
||||
self.states.push(state);
|
||||
self
|
||||
}
|
||||
|
@ -302,21 +314,16 @@ where
|
|||
Err(_) => return Err(StartError::InvalidAddress),
|
||||
};
|
||||
|
||||
// resolve the address
|
||||
let resolved_address = resolver::resolve_address(&address).await?;
|
||||
|
||||
self.start_with_custom_resolved_address(address, resolved_address)
|
||||
self.start_with_default_opts(address, JoinOpts::default())
|
||||
.await
|
||||
}
|
||||
|
||||
/// Do the same as [`Self::start`], but allow passing in a custom resolved
|
||||
/// address. This is useful if the address you're connecting to doesn't
|
||||
/// resolve to anything, like if the server uses the address field to pass
|
||||
/// custom data (like Bungeecord or Forge).
|
||||
pub async fn start_with_custom_resolved_address(
|
||||
/// Do the same as [`Self::start`], but allow passing in default join
|
||||
/// options for the bots.
|
||||
pub async fn start_with_default_opts(
|
||||
self,
|
||||
address: impl TryInto<ServerAddress>,
|
||||
resolved_address: SocketAddr,
|
||||
default_join_opts: JoinOpts,
|
||||
) -> Result<!, StartError> {
|
||||
assert_eq!(
|
||||
self.accounts.len(),
|
||||
|
@ -325,11 +332,17 @@ where
|
|||
);
|
||||
|
||||
// convert the TryInto<ServerAddress> into a ServerAddress
|
||||
let address: ServerAddress = match address.try_into() {
|
||||
let address = match address.try_into() {
|
||||
Ok(address) => address,
|
||||
Err(_) => return Err(StartError::InvalidAddress),
|
||||
};
|
||||
|
||||
let address: ServerAddress = default_join_opts.custom_address.clone().unwrap_or(address);
|
||||
let resolved_address: SocketAddr = match default_join_opts.custom_resolved_address {
|
||||
Some(resolved_address) => resolved_address,
|
||||
None => resolver::resolve_address(&address).await?,
|
||||
};
|
||||
|
||||
let instance_container = Arc::new(RwLock::new(InstanceContainer::default()));
|
||||
|
||||
// we can't modify the swarm plugins after this
|
||||
|
@ -378,24 +391,27 @@ where
|
|||
tokio::spawn(async move {
|
||||
if let Some(join_delay) = join_delay {
|
||||
// if there's a join delay, then join one by one
|
||||
for (account, state) in accounts.iter().zip(states) {
|
||||
swarm_clone.add_and_retry_forever(account, state).await;
|
||||
for ((account, bot_join_opts), state) in accounts.iter().zip(states) {
|
||||
let mut join_opts = default_join_opts.clone();
|
||||
join_opts.update(bot_join_opts);
|
||||
swarm_clone
|
||||
.add_and_retry_forever_with_opts(account, state, &join_opts)
|
||||
.await;
|
||||
tokio::time::sleep(join_delay).await;
|
||||
}
|
||||
} else {
|
||||
// otherwise, join all at once
|
||||
let swarm_borrow = &swarm_clone;
|
||||
join_all(
|
||||
accounts
|
||||
.iter()
|
||||
.zip(states)
|
||||
.map(move |(account, state)| async {
|
||||
swarm_borrow
|
||||
.clone()
|
||||
.add_and_retry_forever(account, state)
|
||||
.await;
|
||||
}),
|
||||
)
|
||||
join_all(accounts.iter().zip(states).map(
|
||||
|((account, bot_join_opts), state)| async {
|
||||
let mut join_opts = default_join_opts.clone();
|
||||
join_opts.update(bot_join_opts);
|
||||
swarm_borrow
|
||||
.clone()
|
||||
.add_and_retry_forever_with_opts(account, state, &join_opts)
|
||||
.await;
|
||||
},
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
|
@ -460,9 +476,9 @@ pub enum SwarmEvent {
|
|||
Init,
|
||||
/// A bot got disconnected from the server.
|
||||
///
|
||||
/// You can implement an auto-reconnect by calling [`Swarm::add`]
|
||||
/// with the account from this event.
|
||||
Disconnect(Box<Account>),
|
||||
/// You can implement an auto-reconnect by calling [`Swarm::add_with_opts`]
|
||||
/// with the account and options from this event.
|
||||
Disconnect(Box<Account>, JoinOpts),
|
||||
/// At least one bot received a chat message.
|
||||
Chat(ChatPacket),
|
||||
}
|
||||
|
@ -544,31 +560,36 @@ impl Swarm {
|
|||
account: &Account,
|
||||
state: S,
|
||||
) -> Result<Client, JoinError> {
|
||||
let address = self.address.read().clone();
|
||||
let resolved_address = *self.resolved_address.read();
|
||||
|
||||
self.add_with_custom_address(account, state, address, resolved_address)
|
||||
self.add_with_opts(account, state, JoinOpts::default())
|
||||
.await
|
||||
}
|
||||
/// Add a new account to the swarm, using the given host and socket
|
||||
/// address. This is useful if you want bots in the same swarm to connect to
|
||||
/// different addresses. Usually you'll just want [`Self::add`] though.
|
||||
/// Add a new account to the swarm, using custom options. This is useful if
|
||||
/// you want bots in the same swarm to connect to different addresses.
|
||||
/// Usually you'll just want [`Self::add`] though.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an `Err` if the bot could not do a handshake successfully.
|
||||
pub async fn add_with_custom_address<S: Component + Clone>(
|
||||
pub async fn add_with_opts<S: Component + Clone>(
|
||||
&mut self,
|
||||
account: &Account,
|
||||
state: S,
|
||||
address: ServerAddress,
|
||||
resolved_address: SocketAddr,
|
||||
opts: JoinOpts,
|
||||
) -> Result<Client, JoinError> {
|
||||
let address = opts
|
||||
.custom_address
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.address.read().clone());
|
||||
let resolved_address = opts
|
||||
.custom_resolved_address
|
||||
.unwrap_or_else(|| *self.resolved_address.read());
|
||||
|
||||
let (bot, mut rx) = Client::start_client(
|
||||
self.ecs_lock.clone(),
|
||||
account,
|
||||
&address,
|
||||
&resolved_address,
|
||||
opts.proxy.clone(),
|
||||
self.run_schedule_sender.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
@ -597,7 +618,7 @@ impl Swarm {
|
|||
.get_component::<Account>()
|
||||
.expect("bot is missing required Account component");
|
||||
swarm_tx
|
||||
.send(SwarmEvent::Disconnect(Box::new(account)))
|
||||
.send(SwarmEvent::Disconnect(Box::new(account), opts))
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
|
@ -613,10 +634,25 @@ impl Swarm {
|
|||
&mut self,
|
||||
account: &Account,
|
||||
state: S,
|
||||
) -> Client {
|
||||
self.add_and_retry_forever_with_opts(account, state, &JoinOpts::default())
|
||||
.await
|
||||
}
|
||||
|
||||
/// Same as [`Self::add_and_retry_forever`], but allow passing custom join
|
||||
/// options.
|
||||
pub async fn add_and_retry_forever_with_opts<S: Component + Clone>(
|
||||
&mut self,
|
||||
account: &Account,
|
||||
state: S,
|
||||
opts: &JoinOpts,
|
||||
) -> Client {
|
||||
let mut disconnects = 0;
|
||||
loop {
|
||||
match self.add(account, state.clone()).await {
|
||||
match self
|
||||
.add_with_opts(account, state.clone(), opts.clone())
|
||||
.await
|
||||
{
|
||||
Ok(bot) => return bot,
|
||||
Err(e) => {
|
||||
disconnects += 1;
|
||||
|
|
Loading…
Reference in a new issue