feat: Some quality of life improvements

This commit is contained in:
2026-04-17 17:18:24 +02:00
parent b1ff10916b
commit 1559ad1d2f
13 changed files with 318 additions and 232 deletions

View File

@@ -25,6 +25,8 @@ pub struct MatrixConfig {
pub struct OmegleConfig {
#[serde(default)]
pub websocket_url: String,
#[serde(default)]
pub selenium_url: String,
}
impl Config {
@@ -33,11 +35,13 @@ impl Config {
let username = env::var("MATRIX_USERNAME").unwrap_or_default();
let password = env::var("MATRIX_PASSWORD").unwrap_or_default();
let websocket_url = env::var("OMEGLE_WEBSOCKET_URL").unwrap_or_default();
let selenium_url = env::var("SELENIUM_URL").unwrap_or_default();
if !homeserver.is_empty()
|| !username.is_empty()
|| !password.is_empty()
|| !websocket_url.is_empty()
|| !selenium_url.is_empty()
{
return Ok(Config {
matrix: MatrixConfig {
@@ -45,7 +49,10 @@ impl Config {
username,
password,
},
omegle: OmegleConfig { websocket_url },
omegle: OmegleConfig {
websocket_url,
selenium_url,
},
});
}

View File

@@ -33,8 +33,8 @@ pub enum BotCommand {
Connect { msg_id: OwnedEventId },
Match { prefer_same_country: bool, user_id: String, msg_id: OwnedEventId },
Skip { user_id: String, msg_id: OwnedEventId },
Pause,
Disconnect,
Pause { reply_to: OwnedEventId },
Disconnect { reply_to: OwnedEventId },
SendMessage(String),
SendTyping(bool),
}
@@ -78,6 +78,24 @@ async fn edit_status(room: &Room, event_id: OwnedEventId, text: &str, emoji: &st
Ok(resp.event_id)
}
async fn send_reply(room: &Room, event_id: OwnedEventId, text: &str) -> Result<OwnedEventId> {
let mut content = RoomMessageEventContent::text_plain(text);
content.relates_to = Some(Relation::Reply {
in_reply_to: matrix_sdk::ruma::events::relation::InReplyTo::new(event_id),
});
let resp = room.send(content).await?;
Ok(resp.event_id)
}
async fn send_reply_html(room: &Room, event_id: OwnedEventId, text: &str, html: &str) -> Result<OwnedEventId> {
let mut content = RoomMessageEventContent::text_html(text, html);
content.relates_to = Some(Relation::Reply {
in_reply_to: matrix_sdk::ruma::events::relation::InReplyTo::new(event_id),
});
let resp = room.send(content).await?;
Ok(resp.event_id)
}
impl MatrixBot {
pub async fn new(config: Config, db: Arc<Db>) -> Result<Self> {
@@ -134,7 +152,7 @@ impl MatrixBot {
if let MessageType::Text(text) = original.content.msgtype {
let body = text.body.trim();
if body.starts_with('!') {
handle_command(body, &original.sender, &room, &db, &config, &handlers, &client).await;
handle_command(body, &original.sender, &room, &db, &config, &handlers, &client, original.event_id).await;
} else if let Some(tx) = handlers.get(&room_id) {
let _ = tx.send(BotCommand::SendMessage(body.to_string())).await;
}
@@ -175,6 +193,7 @@ async fn handle_command(
config: &Config,
handlers: &Arc<DashMap<String, mpsc::Sender<BotCommand>>>,
client: &Client,
reply_to: OwnedEventId,
) {
let parts: Vec<&str> = body.split_whitespace().collect();
let cmd = parts[0];
@@ -185,24 +204,28 @@ async fn handle_command(
"!help" => {
let help_text = "<b>Available commands:</b><br/>\
<code>!help</code> - Show this help message<br/>\
<code>!connect</code> - Connect to Omgle WebSocket<br/>\
<code>!connect</code> - Connect to Omegle WebSocket<br/>\
<code>!match [--same-country]</code> - Request a new match (uses your interests)<br/>\
<code>!skip</code> - Skip current peer and automatch (uses your interests)<br/>\
<code>!stop</code> - Skip current peer without automatching<br/>\
<code>!disconnect</code> - Disconnect from Omgle WebSocket<br/>\
<code>!disconnect</code> - Disconnect from Omegle WebSocket<br/>\
<code>!autoskip add/remove/list &lt;CC&gt;</code> - Manage your automatic skipping list (global)<br/>\
<code>!interests add/remove/list/clear &lt;interest&gt;</code> - Manage your interests (global)";
let plain = "Available commands:\n!help, !connect, !match, !skip, !stop, !pause, !disconnect, !autoskip, !interests";
let content = RoomMessageEventContent::text_html(plain, help_text);
let _ = room.send(content).await;
let _ = send_reply_html(room, reply_to, plain, help_text).await;
}
"!connect" => {
if handlers.contains_key(&room_id) {
let _ = send_info(room, "Already connected").await;
let _ = send_reply(room, reply_to, "Already connected").await;
return;
}
let msg_id = match send_info(room, "🔄 Connecting to Omgle...").await {
let connecting_msg = if config.omegle.selenium_url.is_empty() {
"🔄 Connecting to Omegle...".to_string()
} else {
format!("🔄 Connecting to Omegle... (If needed, complete bot check at {})", config.omegle.selenium_url)
};
let msg_id = match send_reply(room, reply_to, &connecting_msg).await {
Ok(id) => id,
Err(_) => return,
};
@@ -217,29 +240,29 @@ async fn handle_command(
let client_clone = client.clone();
tokio::spawn(async move {
let mut omgle_client = WsOmegleClient::new();
if let Err(e) = omgle_client.connect(&config_clone.omegle.websocket_url).await {
let mut omegle_client = WsOmegleClient::new();
if let Err(e) = omegle_client.connect(&config_clone.omegle.websocket_url).await {
let _ = edit_info(&room_clone, msg_id, &format!("❌ Failed to connect: {}", e)).await;
handlers_clone.remove(&room_id);
return;
}
let _ = tx.send(BotCommand::Connect { msg_id }).await;
let _ = omgle_client.request_people_online().await;
let _ = omegle_client.request_people_online().await;
handle_omgle_session(omgle_client, rx, room_clone, db_struct_clone, room_id.clone(), handlers_clone, client_clone).await;
handle_omegle_session(omegle_client, rx, room_clone, db_struct_clone, room_id.clone(), handlers_clone, client_clone).await;
});
}
"!match" => {
if let Some(tx) = handlers.get(&room_id) {
let prefer_same_country = parts.contains(&"--same-country");
let msg_id = match send_info(room, "🔍 Matching...").await {
let msg_id = match send_reply(room, reply_to, "🔍 Matching...").await {
Ok(id) => id,
Err(_) => return,
};
let _ = tx.send(BotCommand::Match { prefer_same_country, user_id, msg_id }).await;
} else {
let _ = send_info(room, "❌ Not connected to WebSocket. Use <code>!connect</code> first.").await;
let _ = send_reply(room, reply_to, "❌ Not connected to WebSocket. Use <code>!connect</code> first.").await;
}
}
"!interests" => {
@@ -251,49 +274,49 @@ async fn handle_command(
config.interests.push(i.to_string());
}
db.update_user_config(&config).unwrap();
let _ = send_info(room, &format!("✅ Added interests: {:?}", &parts[2..])).await;
let _ = send_reply(room, reply_to, &format!("✅ Added interests: {:?}", &parts[2..])).await;
}
"remove" => {
config.interests.retain(|i| !parts[2..].contains(&i.as_str()));
db.update_user_config(&config).unwrap();
let _ = send_info(room, &format!("🗑️ Removed interests: {:?}", &parts[2..])).await;
let _ = send_reply(room, reply_to, &format!("🗑️ Removed interests: {:?}", &parts[2..])).await;
}
"list" => {
let _ = send_info(room, &format!("📝 Your interests: {:?}", config.interests)).await;
let _ = send_reply(room, reply_to, &format!("📝 Your interests: {:?}", config.interests)).await;
}
"clear" => {
config.interests.clear();
db.update_user_config(&config).unwrap();
let _ = send_info(room, "✨ Interests cleared").await;
let _ = send_reply(room, reply_to, "✨ Interests cleared").await;
}
_ => {
let _ = send_info(room, "❌ Invalid <code>!interests</code> subcommand.").await;
let _ = send_reply(room, reply_to, "❌ Invalid <code>!interests</code> subcommand.").await;
}
}
}
"!skip" => {
if let Some(tx) = handlers.get(&room_id) {
let msg_id = match send_info(room, "⏩ Skipping...").await {
let msg_id = match send_reply(room, reply_to, "⏩ Skipping...").await {
Ok(id) => id,
Err(_) => return,
};
let _ = tx.send(BotCommand::Skip { user_id, msg_id }).await;
} else {
let _ = send_info(room, "❌ Not connected to WebSocket.").await;
let _ = send_reply(room, reply_to, "❌ Not connected to WebSocket.").await;
}
}
"!stop" => {
if let Some(tx) = handlers.get(&room_id) {
let _ = tx.send(BotCommand::Pause).await;
let _ = tx.send(BotCommand::Pause { reply_to }).await;
} else {
let _ = send_info(room, "❌ Not connected to WebSocket.").await;
let _ = send_reply(room, reply_to, "❌ Not connected to WebSocket.").await;
}
}
"!disconnect" => {
if let Some(tx) = handlers.get(&room_id) {
let _ = tx.send(BotCommand::Disconnect).await;
let _ = tx.send(BotCommand::Disconnect { reply_to }).await;
} else {
let _ = send_info(room, "❌ Not connected to WebSocket.").await;
let _ = send_reply(room, reply_to, "❌ Not connected to WebSocket.").await;
}
}
"!autoskip" => {
@@ -305,29 +328,29 @@ async fn handle_command(
config.autoskip_countries.push(c.to_uppercase());
}
db.update_user_config(&config).unwrap();
let _ = send_info(room, &format!("✅ Added to your skip list: {:?}", &parts[2..])).await;
let _ = send_reply(room, reply_to, &format!("✅ Added to your skip list: {:?}", &parts[2..])).await;
}
"remove" => {
config.autoskip_countries.retain(|c| !parts[2..].contains(&c.as_str()));
db.update_user_config(&config).unwrap();
let _ = send_info(room, &format!("🗑️ Removed from your skip list: {:?}", &parts[2..])).await;
let _ = send_reply(room, reply_to, &format!("🗑️ Removed from your skip list: {:?}", &parts[2..])).await;
}
"list" => {
let _ = send_info(room, &format!("📝 Your auto-skip countries: {:?}", config.autoskip_countries)).await;
let _ = send_reply(room, reply_to, &format!("📝 Your auto-skip countries: {:?}", config.autoskip_countries)).await;
}
_ => {
let _ = send_info(room, "❌ Invalid <code>!autoskip</code> subcommand.").await;
let _ = send_reply(room, reply_to, "❌ Invalid <code>!autoskip</code> subcommand.").await;
}
}
}
_ => {
let _ = send_info(room, &format!("❌ Invalid command: <code>{}</code>. Type <code>!help</code> for a list of commands.", cmd)).await;
let _ = send_reply(room, reply_to, &format!("❌ Invalid command: <code>{}</code>. Type <code>!help</code> for a list of commands.", cmd)).await;
}
}
}
async fn handle_omgle_session(
mut omgle: WsOmegleClient,
async fn handle_omegle_session(
mut omegle: WsOmegleClient,
mut rx: mpsc::Receiver<BotCommand>,
room: Room,
db: Arc<Db>,
@@ -350,17 +373,17 @@ async fn handle_omgle_session(
cmd = rx.recv() => {
match cmd {
Some(BotCommand::Connect { msg_id }) => {
let _ = edit_info(&room, msg_id, "✅ Connected to Omgle").await;
let _ = edit_info(&room, msg_id, "✅ Connected to Omegle").await;
},
Some(BotCommand::Match { prefer_same_country, user_id, msg_id }) => {
if peer_connected {
let _ = omgle.disconnect_peer().await;
let _ = omegle.disconnect_peer().await;
}
active_user_id = Some(user_id.clone());
last_prefer_same_country = prefer_same_country;
pending_msg_id = Some(msg_id);
let user_config = db.get_user_config(&user_id).unwrap();
let _ = omgle.request_match(prefer_same_country, user_config.interests).await;
let _ = omegle.request_match(prefer_same_country, user_config.interests).await;
peer_connected = false;
local_typing_active = false;
@@ -377,14 +400,14 @@ async fn handle_omgle_session(
active_user_id = Some(user_id.clone());
pending_msg_id = Some(msg_id);
let user_config = db.get_user_config(&user_id).unwrap();
let _ = omgle.disconnect_peer().await;
let _ = omegle.disconnect_peer().await;
peer_connected = false;
local_typing_active = false;
typing_active = false;
let _ = room.typing_notice(false).await;
let _ = omgle.request_match(last_prefer_same_country, user_config.interests).await;
let _ = omegle.request_match(last_prefer_same_country, user_config.interests).await;
let mut room_state = db.get_room_state(&room_id).unwrap();
room_state.active_user_id = Some(user_id);
@@ -393,44 +416,44 @@ async fn handle_omgle_session(
let _ = edit_info(&room, msg_id, "❌ No stranger to skip.").await;
}
},
Some(BotCommand::Pause) => {
Some(BotCommand::Pause { reply_to }) => {
if peer_connected {
let _ = send_info(&room, "⏸️ Paused (Skipped peer)").await;
let _ = omgle.disconnect_peer().await;
let _ = send_reply(&room, reply_to, "⏸️ Paused (Skipped peer)").await;
let _ = omegle.disconnect_peer().await;
peer_connected = false;
local_typing_active = false;
typing_active = false;
let _ = room.typing_notice(false).await;
} else {
let _ = send_info(&room, "❌ No stranger to pause.").await;
let _ = send_reply(&room, reply_to, "❌ No stranger to pause.").await;
}
},
Some(BotCommand::Disconnect) => {
let _ = omgle.disconnect().await;
Some(BotCommand::Disconnect { reply_to }) => {
let _ = omegle.disconnect().await;
let mut room_state = db.get_room_state(&room_id).unwrap();
room_state.is_connected = false;
let _ = db.update_room_state(&room_state);
let _ = send_info(&room, "🔌 Disconnected from Omgle").await;
let _ = send_reply(&room, reply_to, "🔌 Disconnected from Omegle").await;
handlers.remove(&room_id);
return;
},
Some(BotCommand::SendMessage(text)) => {
if peer_connected {
local_typing_active = false;
let _ = omgle.send_message(&text).await;
let _ = omegle.send_message(&text).await;
message_count += 1;
}
},
Some(BotCommand::SendTyping(typing)) => {
if peer_connected {
local_typing_active = typing;
let _ = omgle.send_typing(typing).await;
let _ = omegle.send_typing(typing).await;
}
},
None => break,
}
}
ev = omgle.next_event() => {
ev = omegle.next_event() => {
match ev {
Ok(Some(msg)) => {
match msg.channel.as_str() {
@@ -454,14 +477,14 @@ async fn handle_omgle_session(
let config = db.get_user_config(user_id).unwrap();
if config.autoskip_countries.contains(&data.country.to_uppercase()) {
pending_msg_id = send_info(&room, "⏩ Auto-skipping...").await.ok();
let _ = omgle.disconnect_peer().await;
let _ = omegle.disconnect_peer().await;
peer_connected = false;
local_typing_active = false;
typing_active = false;
let _ = room.typing_notice(false).await;
let _ = omgle.request_match(last_prefer_same_country, config.interests).await;
let _ = omegle.request_match(last_prefer_same_country, config.interests).await;
}
}
}
@@ -496,7 +519,7 @@ async fn handle_omgle_session(
if let Some(user_id) = &active_user_id {
pending_msg_id = send_info(&room, "⏩ Automatching...").await.ok();
let config = db.get_user_config(user_id).unwrap();
let _ = omgle.request_match(last_prefer_same_country, config.interests).await;
let _ = omegle.request_match(last_prefer_same_country, config.interests).await;
}
}
}
@@ -515,11 +538,11 @@ async fn handle_omgle_session(
}
if local_typing_active && peer_connected {
let _ = omgle.send_typing(true).await;
let _ = omegle.send_typing(true).await;
}
if last_people_online_request.elapsed().as_secs() >= 60 {
let _ = omgle.request_people_online().await;
let _ = omegle.request_people_online().await;
last_people_online_request = std::time::Instant::now();
}
}

View File

@@ -22,7 +22,18 @@ impl WsOmegleClient {
impl OmegleProvider for WsOmegleClient {
async fn connect(&mut self, url: &str) -> Result<()> {
let (ws_stream, _) = connect_async(url).await?;
self.ws = Some(ws_stream);
let mut ws = ws_stream;
let msg = ws.next().await.ok_or_else(|| anyhow!("Connection closed before receiving SUCCESS"))??;
match msg {
Message::Text(text) if text == "SUCCESS" => {}
Message::Text(text) => return Err(anyhow!("Unexpected message: {}", text)),
Message::Close(_) => return Err(anyhow!("Server closed connection")),
_ => return Err(anyhow!("Unexpected message type")),
}
self.ws = Some(ws);
Ok(())
}