Skip to content
This repository was archived by the owner on Feb 10, 2026. It is now read-only.
Closed
5 changes: 5 additions & 0 deletions grammers-client/src/peer/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ impl Channel {
None => None,
}
}

/// Return whether this channel requires join requests.
pub fn requires_join_request(&self) -> bool {
self.raw.join_request
}
}

impl TryFrom<Channel> for ChannelKind {
Expand Down
12 changes: 12 additions & 0 deletions grammers-client/src/peer/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,18 @@ impl Group {
C::Channel(_) | C::ChannelForbidden(_) => true,
}
}

/// Return whether this group requires join requests.
pub fn requires_join_request(&self) -> bool {
use tl::enums::Chat;

match &self.raw {
Chat::Empty(_) | Chat::Chat(_) | Chat::Forbidden(_) | Chat::ChannelForbidden(_) => {
false
}
Chat::Channel(channel) => channel.join_request,
}
}
}

impl From<Group> for PeerInfo {
Expand Down
3 changes: 2 additions & 1 deletion grammers-mtsender/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ categories = ["api-bindings", "network-programming"]
edition = "2024"

[features]
proxy = ["tokio-socks", "hickory-resolver", "url"]
proxy = ["tokio-socks", "hickory-resolver", "url", "base64"]

[dependencies]
bytes = "1.10.1"
Expand All @@ -29,6 +29,7 @@ tokio = { version = "1.47.1", default-features = false, features = ["io-util", "
tokio-socks = { version = "0.5.2", optional = true }
hickory-resolver = { version = "0.25.2", optional = true }
url = { version = "2.5.7", optional = true }
base64 = { version = "0.22", optional = true }

[dev-dependencies]
simple_logger = { version = "5.0.0", default-features = false, features = ["colors"] }
Expand Down
87 changes: 87 additions & 0 deletions grammers-mtsender/src/net/tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,93 @@ impl NetStream {
))
}
}
"socks4" => {
use tokio::io::{AsyncReadExt, AsyncWriteExt};

let mut stream = TcpStream::connect(socks_addr).await?;

// SOCKS4 CONNECT: VER(04) CMD(01) DSTPORT(2B) DSTIP(4B) USERID NULL
let ip = match addr.ip() {
std::net::IpAddr::V4(v4) => v4,
_ => {
return Err(io::Error::new(
ErrorKind::InvalidInput,
"SOCKS4 does not support IPv6",
))
}
};
let mut req = vec![0x04, 0x01];
req.extend_from_slice(&addr.port().to_be_bytes());
req.extend_from_slice(&ip.octets());
// userid(可选,用 username)
req.extend_from_slice(username.as_bytes());
req.push(0x00); // null terminator

stream.write_all(&req).await?;

// 响应 8 字节: VN(00) CD DSTPORT(2B) DSTIP(4B)
let mut resp = [0u8; 8];
stream.read_exact(&mut resp).await?;

if resp[1] != 0x5A {
return Err(io::Error::new(
ErrorKind::ConnectionAborted,
format!("SOCKS4 CONNECT rejected (code: 0x{:02X})", resp[1]),
));
}

Ok(NetStream::Tcp(stream))
}
"http" | "https" => {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use base64::Engine;

let mut stream = TcpStream::connect(socks_addr).await?;

let target = format!("{}:{}", addr.ip(), addr.port());
let mut request = format!("CONNECT {} HTTP/1.1\r\nHost: {}\r\n", target, target);

if !username.is_empty() {
let creds = format!("{}:{}", username, password);
let encoded = base64::engine::general_purpose::STANDARD
.encode(creds.as_bytes());
request.push_str(&format!("Proxy-Authorization: Basic {}\r\n", encoded));
}
request.push_str("\r\n");
stream.write_all(request.as_bytes()).await?;

// 逐字节读响应头直到 \r\n\r\n
let mut buf = Vec::with_capacity(512);
let mut byte = [0u8; 1];
loop {
stream.read_exact(&mut byte).await?;
buf.push(byte[0]);
if buf.ends_with(b"\r\n\r\n") {
break;
}
if buf.len() > 4096 {
return Err(io::Error::new(
ErrorKind::InvalidData,
"HTTP CONNECT response too large",
));
}
}

let response = String::from_utf8_lossy(&buf);
let status_line = response.lines().next().ok_or_else(|| {
io::Error::new(ErrorKind::InvalidData, "HTTP CONNECT response has no status line")
})?;
let parts: Vec<&str> = status_line.splitn(3, ' ').collect();
if parts.len() < 2 || parts[1] != "200" {
return Err(io::Error::new(
ErrorKind::ConnectionAborted,
format!("HTTP CONNECT failed: {}", status_line),
));
}

// 隧道建立后底层 TCP 等同直连
Ok(NetStream::Tcp(stream))
}
scheme => Err(io::Error::new(
ErrorKind::ConnectionAborted,
format!("proxy scheme not supported: {}", scheme),
Expand Down
Loading