feat: Some quality of life improvements
This commit is contained in:
@@ -4,7 +4,10 @@ MATRIX_USERNAME=@omeglebot:jzitnik.dev
|
|||||||
MATRIX_PASSWORD=
|
MATRIX_PASSWORD=
|
||||||
|
|
||||||
# Selenium Grid configuration
|
# Selenium Grid configuration
|
||||||
GRID_DASHBOARD_PORT=4444 # Bare in mind that by default there is no password for the VNC connection. So this shouldn't be exposed to the public.
|
GRID_DASHBOARD_PORT=4444 # Bare in mind that by default there is no password for the VNC connection.
|
||||||
|
|
||||||
|
# The URL of the Selenium Grid hub. This is used in a link for the user that sends `!connect` command to manually pass the Cloudflare challange.
|
||||||
|
SELENIUM_GRID_URL=http://localhost:4444
|
||||||
|
|
||||||
# Miscellaneous settings
|
# Miscellaneous settings
|
||||||
CF_WAIT_TIME=60 # The time you have to manually pass the Cloudflare challenge in seconds.
|
CF_WAIT_TIME=60 # The time you have to manually pass the Cloudflare challenge in seconds.
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ pub struct MatrixConfig {
|
|||||||
pub struct OmegleConfig {
|
pub struct OmegleConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub websocket_url: String,
|
pub websocket_url: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub selenium_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
@@ -33,11 +35,13 @@ impl Config {
|
|||||||
let username = env::var("MATRIX_USERNAME").unwrap_or_default();
|
let username = env::var("MATRIX_USERNAME").unwrap_or_default();
|
||||||
let password = env::var("MATRIX_PASSWORD").unwrap_or_default();
|
let password = env::var("MATRIX_PASSWORD").unwrap_or_default();
|
||||||
let websocket_url = env::var("OMEGLE_WEBSOCKET_URL").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()
|
if !homeserver.is_empty()
|
||||||
|| !username.is_empty()
|
|| !username.is_empty()
|
||||||
|| !password.is_empty()
|
|| !password.is_empty()
|
||||||
|| !websocket_url.is_empty()
|
|| !websocket_url.is_empty()
|
||||||
|
|| !selenium_url.is_empty()
|
||||||
{
|
{
|
||||||
return Ok(Config {
|
return Ok(Config {
|
||||||
matrix: MatrixConfig {
|
matrix: MatrixConfig {
|
||||||
@@ -45,7 +49,10 @@ impl Config {
|
|||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
},
|
},
|
||||||
omegle: OmegleConfig { websocket_url },
|
omegle: OmegleConfig {
|
||||||
|
websocket_url,
|
||||||
|
selenium_url,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ pub enum BotCommand {
|
|||||||
Connect { msg_id: OwnedEventId },
|
Connect { msg_id: OwnedEventId },
|
||||||
Match { prefer_same_country: bool, user_id: String, msg_id: OwnedEventId },
|
Match { prefer_same_country: bool, user_id: String, msg_id: OwnedEventId },
|
||||||
Skip { user_id: String, msg_id: OwnedEventId },
|
Skip { user_id: String, msg_id: OwnedEventId },
|
||||||
Pause,
|
Pause { reply_to: OwnedEventId },
|
||||||
Disconnect,
|
Disconnect { reply_to: OwnedEventId },
|
||||||
SendMessage(String),
|
SendMessage(String),
|
||||||
SendTyping(bool),
|
SendTyping(bool),
|
||||||
}
|
}
|
||||||
@@ -78,6 +78,24 @@ async fn edit_status(room: &Room, event_id: OwnedEventId, text: &str, emoji: &st
|
|||||||
Ok(resp.event_id)
|
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 {
|
impl MatrixBot {
|
||||||
pub async fn new(config: Config, db: Arc<Db>) -> Result<Self> {
|
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 {
|
if let MessageType::Text(text) = original.content.msgtype {
|
||||||
let body = text.body.trim();
|
let body = text.body.trim();
|
||||||
if body.starts_with('!') {
|
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) {
|
} else if let Some(tx) = handlers.get(&room_id) {
|
||||||
let _ = tx.send(BotCommand::SendMessage(body.to_string())).await;
|
let _ = tx.send(BotCommand::SendMessage(body.to_string())).await;
|
||||||
}
|
}
|
||||||
@@ -175,6 +193,7 @@ async fn handle_command(
|
|||||||
config: &Config,
|
config: &Config,
|
||||||
handlers: &Arc<DashMap<String, mpsc::Sender<BotCommand>>>,
|
handlers: &Arc<DashMap<String, mpsc::Sender<BotCommand>>>,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
|
reply_to: OwnedEventId,
|
||||||
) {
|
) {
|
||||||
let parts: Vec<&str> = body.split_whitespace().collect();
|
let parts: Vec<&str> = body.split_whitespace().collect();
|
||||||
let cmd = parts[0];
|
let cmd = parts[0];
|
||||||
@@ -185,24 +204,28 @@ async fn handle_command(
|
|||||||
"!help" => {
|
"!help" => {
|
||||||
let help_text = "<b>Available commands:</b><br/>\
|
let help_text = "<b>Available commands:</b><br/>\
|
||||||
<code>!help</code> - Show this help message<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>!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>!skip</code> - Skip current peer and automatch (uses your interests)<br/>\
|
||||||
<code>!stop</code> - Skip current peer without automatching<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 <CC></code> - Manage your automatic skipping list (global)<br/>\
|
<code>!autoskip add/remove/list <CC></code> - Manage your automatic skipping list (global)<br/>\
|
||||||
<code>!interests add/remove/list/clear <interest></code> - Manage your interests (global)";
|
<code>!interests add/remove/list/clear <interest></code> - Manage your interests (global)";
|
||||||
let plain = "Available commands:\n!help, !connect, !match, !skip, !stop, !pause, !disconnect, !autoskip, !interests";
|
let plain = "Available commands:\n!help, !connect, !match, !skip, !stop, !pause, !disconnect, !autoskip, !interests";
|
||||||
let content = RoomMessageEventContent::text_html(plain, help_text);
|
let _ = send_reply_html(room, reply_to, plain, help_text).await;
|
||||||
let _ = room.send(content).await;
|
|
||||||
}
|
}
|
||||||
"!connect" => {
|
"!connect" => {
|
||||||
if handlers.contains_key(&room_id) {
|
if handlers.contains_key(&room_id) {
|
||||||
let _ = send_info(room, "Already connected").await;
|
let _ = send_reply(room, reply_to, "Already connected").await;
|
||||||
return;
|
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,
|
Ok(id) => id,
|
||||||
Err(_) => return,
|
Err(_) => return,
|
||||||
};
|
};
|
||||||
@@ -217,29 +240,29 @@ async fn handle_command(
|
|||||||
let client_clone = client.clone();
|
let client_clone = client.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut omgle_client = WsOmegleClient::new();
|
let mut omegle_client = WsOmegleClient::new();
|
||||||
if let Err(e) = omgle_client.connect(&config_clone.omegle.websocket_url).await {
|
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;
|
let _ = edit_info(&room_clone, msg_id, &format!("❌ Failed to connect: {}", e)).await;
|
||||||
handlers_clone.remove(&room_id);
|
handlers_clone.remove(&room_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = tx.send(BotCommand::Connect { msg_id }).await;
|
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" => {
|
"!match" => {
|
||||||
if let Some(tx) = handlers.get(&room_id) {
|
if let Some(tx) = handlers.get(&room_id) {
|
||||||
let prefer_same_country = parts.contains(&"--same-country");
|
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,
|
Ok(id) => id,
|
||||||
Err(_) => return,
|
Err(_) => return,
|
||||||
};
|
};
|
||||||
let _ = tx.send(BotCommand::Match { prefer_same_country, user_id, msg_id }).await;
|
let _ = tx.send(BotCommand::Match { prefer_same_country, user_id, msg_id }).await;
|
||||||
} else {
|
} 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" => {
|
"!interests" => {
|
||||||
@@ -251,49 +274,49 @@ async fn handle_command(
|
|||||||
config.interests.push(i.to_string());
|
config.interests.push(i.to_string());
|
||||||
}
|
}
|
||||||
db.update_user_config(&config).unwrap();
|
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" => {
|
"remove" => {
|
||||||
config.interests.retain(|i| !parts[2..].contains(&i.as_str()));
|
config.interests.retain(|i| !parts[2..].contains(&i.as_str()));
|
||||||
db.update_user_config(&config).unwrap();
|
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" => {
|
"list" => {
|
||||||
let _ = send_info(room, &format!("📝 Your interests: {:?}", config.interests)).await;
|
let _ = send_reply(room, reply_to, &format!("📝 Your interests: {:?}", config.interests)).await;
|
||||||
}
|
}
|
||||||
"clear" => {
|
"clear" => {
|
||||||
config.interests.clear();
|
config.interests.clear();
|
||||||
db.update_user_config(&config).unwrap();
|
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" => {
|
"!skip" => {
|
||||||
if let Some(tx) = handlers.get(&room_id) {
|
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,
|
Ok(id) => id,
|
||||||
Err(_) => return,
|
Err(_) => return,
|
||||||
};
|
};
|
||||||
let _ = tx.send(BotCommand::Skip { user_id, msg_id }).await;
|
let _ = tx.send(BotCommand::Skip { user_id, msg_id }).await;
|
||||||
} else {
|
} else {
|
||||||
let _ = send_info(room, "❌ Not connected to WebSocket.").await;
|
let _ = send_reply(room, reply_to, "❌ Not connected to WebSocket.").await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"!stop" => {
|
"!stop" => {
|
||||||
if let Some(tx) = handlers.get(&room_id) {
|
if let Some(tx) = handlers.get(&room_id) {
|
||||||
let _ = tx.send(BotCommand::Pause).await;
|
let _ = tx.send(BotCommand::Pause { reply_to }).await;
|
||||||
} else {
|
} else {
|
||||||
let _ = send_info(room, "❌ Not connected to WebSocket.").await;
|
let _ = send_reply(room, reply_to, "❌ Not connected to WebSocket.").await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"!disconnect" => {
|
"!disconnect" => {
|
||||||
if let Some(tx) = handlers.get(&room_id) {
|
if let Some(tx) = handlers.get(&room_id) {
|
||||||
let _ = tx.send(BotCommand::Disconnect).await;
|
let _ = tx.send(BotCommand::Disconnect { reply_to }).await;
|
||||||
} else {
|
} else {
|
||||||
let _ = send_info(room, "❌ Not connected to WebSocket.").await;
|
let _ = send_reply(room, reply_to, "❌ Not connected to WebSocket.").await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"!autoskip" => {
|
"!autoskip" => {
|
||||||
@@ -305,29 +328,29 @@ async fn handle_command(
|
|||||||
config.autoskip_countries.push(c.to_uppercase());
|
config.autoskip_countries.push(c.to_uppercase());
|
||||||
}
|
}
|
||||||
db.update_user_config(&config).unwrap();
|
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" => {
|
"remove" => {
|
||||||
config.autoskip_countries.retain(|c| !parts[2..].contains(&c.as_str()));
|
config.autoskip_countries.retain(|c| !parts[2..].contains(&c.as_str()));
|
||||||
db.update_user_config(&config).unwrap();
|
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" => {
|
"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(
|
async fn handle_omegle_session(
|
||||||
mut omgle: WsOmegleClient,
|
mut omegle: WsOmegleClient,
|
||||||
mut rx: mpsc::Receiver<BotCommand>,
|
mut rx: mpsc::Receiver<BotCommand>,
|
||||||
room: Room,
|
room: Room,
|
||||||
db: Arc<Db>,
|
db: Arc<Db>,
|
||||||
@@ -350,17 +373,17 @@ async fn handle_omgle_session(
|
|||||||
cmd = rx.recv() => {
|
cmd = rx.recv() => {
|
||||||
match cmd {
|
match cmd {
|
||||||
Some(BotCommand::Connect { msg_id }) => {
|
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 }) => {
|
Some(BotCommand::Match { prefer_same_country, user_id, msg_id }) => {
|
||||||
if peer_connected {
|
if peer_connected {
|
||||||
let _ = omgle.disconnect_peer().await;
|
let _ = omegle.disconnect_peer().await;
|
||||||
}
|
}
|
||||||
active_user_id = Some(user_id.clone());
|
active_user_id = Some(user_id.clone());
|
||||||
last_prefer_same_country = prefer_same_country;
|
last_prefer_same_country = prefer_same_country;
|
||||||
pending_msg_id = Some(msg_id);
|
pending_msg_id = Some(msg_id);
|
||||||
let user_config = db.get_user_config(&user_id).unwrap();
|
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;
|
peer_connected = false;
|
||||||
local_typing_active = false;
|
local_typing_active = false;
|
||||||
@@ -377,14 +400,14 @@ async fn handle_omgle_session(
|
|||||||
active_user_id = Some(user_id.clone());
|
active_user_id = Some(user_id.clone());
|
||||||
pending_msg_id = Some(msg_id);
|
pending_msg_id = Some(msg_id);
|
||||||
let user_config = db.get_user_config(&user_id).unwrap();
|
let user_config = db.get_user_config(&user_id).unwrap();
|
||||||
let _ = omgle.disconnect_peer().await;
|
let _ = omegle.disconnect_peer().await;
|
||||||
|
|
||||||
peer_connected = false;
|
peer_connected = false;
|
||||||
local_typing_active = false;
|
local_typing_active = false;
|
||||||
typing_active = false;
|
typing_active = false;
|
||||||
let _ = room.typing_notice(false).await;
|
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();
|
let mut room_state = db.get_room_state(&room_id).unwrap();
|
||||||
room_state.active_user_id = Some(user_id);
|
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;
|
let _ = edit_info(&room, msg_id, "❌ No stranger to skip.").await;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(BotCommand::Pause) => {
|
Some(BotCommand::Pause { reply_to }) => {
|
||||||
if peer_connected {
|
if peer_connected {
|
||||||
let _ = send_info(&room, "⏸️ Paused (Skipped peer)").await;
|
let _ = send_reply(&room, reply_to, "⏸️ Paused (Skipped peer)").await;
|
||||||
let _ = omgle.disconnect_peer().await;
|
let _ = omegle.disconnect_peer().await;
|
||||||
peer_connected = false;
|
peer_connected = false;
|
||||||
local_typing_active = false;
|
local_typing_active = false;
|
||||||
typing_active = false;
|
typing_active = false;
|
||||||
let _ = room.typing_notice(false).await;
|
let _ = room.typing_notice(false).await;
|
||||||
} else {
|
} else {
|
||||||
let _ = send_info(&room, "❌ No stranger to pause.").await;
|
let _ = send_reply(&room, reply_to, "❌ No stranger to pause.").await;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(BotCommand::Disconnect) => {
|
Some(BotCommand::Disconnect { reply_to }) => {
|
||||||
let _ = omgle.disconnect().await;
|
let _ = omegle.disconnect().await;
|
||||||
let mut room_state = db.get_room_state(&room_id).unwrap();
|
let mut room_state = db.get_room_state(&room_id).unwrap();
|
||||||
room_state.is_connected = false;
|
room_state.is_connected = false;
|
||||||
let _ = db.update_room_state(&room_state);
|
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);
|
handlers.remove(&room_id);
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
Some(BotCommand::SendMessage(text)) => {
|
Some(BotCommand::SendMessage(text)) => {
|
||||||
if peer_connected {
|
if peer_connected {
|
||||||
local_typing_active = false;
|
local_typing_active = false;
|
||||||
let _ = omgle.send_message(&text).await;
|
let _ = omegle.send_message(&text).await;
|
||||||
message_count += 1;
|
message_count += 1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(BotCommand::SendTyping(typing)) => {
|
Some(BotCommand::SendTyping(typing)) => {
|
||||||
if peer_connected {
|
if peer_connected {
|
||||||
local_typing_active = typing;
|
local_typing_active = typing;
|
||||||
let _ = omgle.send_typing(typing).await;
|
let _ = omegle.send_typing(typing).await;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => break,
|
None => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ev = omgle.next_event() => {
|
ev = omegle.next_event() => {
|
||||||
match ev {
|
match ev {
|
||||||
Ok(Some(msg)) => {
|
Ok(Some(msg)) => {
|
||||||
match msg.channel.as_str() {
|
match msg.channel.as_str() {
|
||||||
@@ -454,14 +477,14 @@ async fn handle_omgle_session(
|
|||||||
let config = db.get_user_config(user_id).unwrap();
|
let config = db.get_user_config(user_id).unwrap();
|
||||||
if config.autoskip_countries.contains(&data.country.to_uppercase()) {
|
if config.autoskip_countries.contains(&data.country.to_uppercase()) {
|
||||||
pending_msg_id = send_info(&room, "⏩ Auto-skipping...").await.ok();
|
pending_msg_id = send_info(&room, "⏩ Auto-skipping...").await.ok();
|
||||||
let _ = omgle.disconnect_peer().await;
|
let _ = omegle.disconnect_peer().await;
|
||||||
|
|
||||||
peer_connected = false;
|
peer_connected = false;
|
||||||
local_typing_active = false;
|
local_typing_active = false;
|
||||||
typing_active = false;
|
typing_active = false;
|
||||||
let _ = room.typing_notice(false).await;
|
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 {
|
if let Some(user_id) = &active_user_id {
|
||||||
pending_msg_id = send_info(&room, "⏩ Automatching...").await.ok();
|
pending_msg_id = send_info(&room, "⏩ Automatching...").await.ok();
|
||||||
let config = db.get_user_config(user_id).unwrap();
|
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 {
|
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 {
|
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();
|
last_people_online_request = std::time::Instant::now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,18 @@ impl WsOmegleClient {
|
|||||||
impl OmegleProvider for WsOmegleClient {
|
impl OmegleProvider for WsOmegleClient {
|
||||||
async fn connect(&mut self, url: &str) -> Result<()> {
|
async fn connect(&mut self, url: &str) -> Result<()> {
|
||||||
let (ws_stream, _) = connect_async(url).await?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
# Selenium Grid with Chromium (or Chrome)
|
# Selenium Grid with Chromium (or Chrome)
|
||||||
selenium:
|
selenium:
|
||||||
image: selenium/standalone-chromium:4.43.0-20260404
|
build: ./selenium-patch
|
||||||
container_name: selenium
|
container_name: selenium
|
||||||
shm_size: 2gb
|
shm_size: 2gb
|
||||||
ports:
|
ports:
|
||||||
@@ -40,6 +40,7 @@ services:
|
|||||||
- MATRIX_USERNAME=${MATRIX_USERNAME}
|
- MATRIX_USERNAME=${MATRIX_USERNAME}
|
||||||
- MATRIX_PASSWORD=${MATRIX_PASSWORD}
|
- MATRIX_PASSWORD=${MATRIX_PASSWORD}
|
||||||
- OMEGLE_WEBSOCKET_URL=ws://omegle-proxy:8765
|
- OMEGLE_WEBSOCKET_URL=ws://omegle-proxy:8765
|
||||||
|
- SELENIUM_URL=${SELENIUM_GRID_URL}
|
||||||
- DB_PATH=/data/omegle.db
|
- DB_PATH=/data/omegle.db
|
||||||
volumes:
|
volumes:
|
||||||
- omegle-bot-data:/data
|
- omegle-bot-data:/data
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
FROM python:3.12-slim
|
FROM python:3.12-slim
|
||||||
|
|
||||||
RUN pip install --no-cache-dir setuptools selenium websockets
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY main.py .
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
CMD ["python", "main.py"]
|
CMD ["python", "main.py"]
|
||||||
|
|||||||
0
proxy/__init__.py
Normal file
0
proxy/__init__.py
Normal file
11
proxy/config.py
Normal file
11
proxy/config.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
TARGET_URL = os.getenv("TARGET_URL", "https://omegleweb.io/")
|
||||||
|
TARGET_WS_URL = os.getenv(
|
||||||
|
"TARGET_WS_URL", "wss://omegleweb.io:8443/socket.io/?EIO=4&transport=websocket"
|
||||||
|
)
|
||||||
|
LOCAL_HOST = os.getenv("LOCAL_HOST", "0.0.0.0")
|
||||||
|
LOCAL_PORT = int(os.getenv("LOCAL_PORT", "8765"))
|
||||||
|
HEADLESS = os.getenv("HEADLESS", "false").lower() in ("true", "1", "yes")
|
||||||
|
CF_WAIT_TIME = int(os.getenv("CF_WAIT_TIME", "60"))
|
||||||
|
SELENIUM_URL = os.getenv("SELENIUM_URL", "http://localhost:4444")
|
||||||
186
proxy/main.py
186
proxy/main.py
@@ -1,182 +1,24 @@
|
|||||||
import os
|
|
||||||
import time
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import logging
|
||||||
import websockets
|
import websockets
|
||||||
from datetime import datetime
|
|
||||||
from selenium import webdriver
|
|
||||||
from selenium.webdriver.chrome.options import Options
|
|
||||||
from selenium.webdriver.chrome.service import Service
|
|
||||||
from selenium.webdriver.common.by import By
|
|
||||||
from selenium.webdriver.support.ui import WebDriverWait
|
|
||||||
from selenium.webdriver.support import expected_conditions as EC
|
|
||||||
|
|
||||||
TARGET_URL = os.getenv("TARGET_URL", "https://omegleweb.io/")
|
from config import LOCAL_HOST, LOCAL_PORT
|
||||||
TARGET_WS_URL = os.getenv("TARGET_WS_URL", "wss://omegleweb.io:8443/socket.io/?EIO=4&transport=websocket")
|
from websocket_client import handle_connection
|
||||||
LOCAL_HOST = os.getenv("LOCAL_HOST", "0.0.0.0")
|
|
||||||
LOCAL_PORT = int(os.getenv("LOCAL_PORT", "8765"))
|
|
||||||
HEADLESS = os.getenv("HEADLESS", "false").lower() in ("true", "1", "yes")
|
|
||||||
CF_WAIT_TIME = int(os.getenv("CF_WAIT_TIME", "60"))
|
|
||||||
SELENIUM_URL = os.getenv("SELENIUM_URL", "http://localhost:4444")
|
|
||||||
|
|
||||||
credentials = {
|
logging.basicConfig(
|
||||||
"user_agent": None,
|
level=logging.INFO,
|
||||||
"cookies": None
|
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||||
}
|
datefmt="%H:%M:%S",
|
||||||
|
)
|
||||||
|
|
||||||
def stealth_js(driver):
|
|
||||||
driver.execute_script("""
|
|
||||||
Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
|
|
||||||
Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]});
|
|
||||||
Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en']});
|
|
||||||
window.chrome = {runtime: {}};
|
|
||||||
const originalQuery = window.navigator.permissions.query;
|
|
||||||
window.navigator.permissions.query = (parameters) => (
|
|
||||||
parameters.name === 'notifications' ?
|
|
||||||
Promise.resolve({state: Notification.permission}) :
|
|
||||||
originalQuery(parameters)
|
|
||||||
);
|
|
||||||
Object.defineProperty(navigator, 'permissions', {get: () => window.navigator.permissions});
|
|
||||||
""")
|
|
||||||
|
|
||||||
def extract_credentials():
|
async def main():
|
||||||
print("\n" + "="*50)
|
await websockets.serve(handle_connection, LOCAL_HOST, LOCAL_PORT)
|
||||||
print("[*] PHASE 1: SELENIUM EXTRACTION")
|
await asyncio.Future()
|
||||||
print("="*50)
|
|
||||||
print("[*] Launching Chrome browser...")
|
|
||||||
|
|
||||||
options = Options()
|
|
||||||
if HEADLESS:
|
|
||||||
options.add_argument("--headless=new")
|
|
||||||
options.add_argument("--no-sandbox")
|
|
||||||
options.add_argument("--disable-dev-shm-usage")
|
|
||||||
options.add_argument("--disable-gpu")
|
|
||||||
options.add_argument("--disable-software-rasterizer")
|
|
||||||
options.add_argument("--disable-extensions")
|
|
||||||
options.add_argument("--disable-blink-features=AutomationControlled")
|
|
||||||
options.add_argument("--disable-setuid-sandbox")
|
|
||||||
options.add_argument("--disable-background-networking")
|
|
||||||
options.add_argument("--disable-default-apps")
|
|
||||||
options.add_argument("--disable-sync")
|
|
||||||
options.add_argument("--disable-translate")
|
|
||||||
options.add_argument("--metrics-recording-only")
|
|
||||||
options.add_argument("--mute-audio")
|
|
||||||
options.add_argument("--no-first-run")
|
|
||||||
options.add_argument("--safebrowsing-disable-auto-update")
|
|
||||||
options.add_argument("--ignore-certificate-errors")
|
|
||||||
options.add_argument("--ignore-ssl-errors")
|
|
||||||
options.add_argument("--user-data-dir=/tmp/chrome-data")
|
|
||||||
options.add_argument("--disable-features=IsolateOrigins,site-per-process")
|
|
||||||
|
|
||||||
driver = webdriver.Remote(
|
|
||||||
command_executor=SELENIUM_URL + "/wd/hub",
|
|
||||||
options=options
|
|
||||||
)
|
|
||||||
|
|
||||||
stealth_js(driver)
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
print("[*] Navigating to OmegleWeb homepage...")
|
asyncio.run(main())
|
||||||
driver.get(TARGET_URL)
|
|
||||||
|
|
||||||
wait = WebDriverWait(driver, CF_WAIT_TIME)
|
|
||||||
wait.until(EC.presence_of_element_located((By.ID, "logo")))
|
|
||||||
|
|
||||||
print("[*] Time's up! Extracting the goods...")
|
|
||||||
user_agent = driver.execute_script("return navigator.userAgent;")
|
|
||||||
selenium_cookies = driver.get_cookies()
|
|
||||||
cookie_string = "; ".join([f"{c['name']}={c['value']}" for c in selenium_cookies])
|
|
||||||
|
|
||||||
credentials["user_agent"] = user_agent
|
|
||||||
credentials["cookies"] = cookie_string
|
|
||||||
|
|
||||||
print("[+] Extraction successful!")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[!] Extraction failed: {e}")
|
|
||||||
return False
|
|
||||||
finally:
|
|
||||||
print("[*] Closing browser...")
|
|
||||||
driver.quit()
|
|
||||||
|
|
||||||
async def start_bridge():
|
|
||||||
async def bridge_handler(local_client):
|
|
||||||
print(f"\n[{datetime.now().strftime('%H:%M:%S')}] [+] Local client connected to the bridge!")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
headers = {
|
|
||||||
"User-Agent": credentials["user_agent"],
|
|
||||||
"Cookie": credentials["cookies"]
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
print(f"[*] Attempting tunnel to OmegleWeb...")
|
|
||||||
async with websockets.connect(TARGET_WS_URL, additional_headers=headers) as target_server:
|
|
||||||
print("[+] Tunnel established! Relaying messages...")
|
|
||||||
|
|
||||||
async def forward_local_to_target():
|
|
||||||
async for message in local_client:
|
|
||||||
print(f"[{datetime.now().strftime('%H:%M:%S.%f')[:-3]}] [Local -> Omegle]: {message}")
|
|
||||||
await target_server.send(message)
|
|
||||||
|
|
||||||
async def forward_target_to_local():
|
|
||||||
async for message in target_server:
|
|
||||||
print(f"[{datetime.now().strftime('%H:%M:%S.%f')[:-3]}] [Omegle -> Local]: {message}")
|
|
||||||
await local_client.send(message)
|
|
||||||
|
|
||||||
t1 = asyncio.create_task(forward_local_to_target())
|
|
||||||
t2 = asyncio.create_task(forward_target_to_local())
|
|
||||||
|
|
||||||
done, pending = await asyncio.wait(
|
|
||||||
[t1, t2],
|
|
||||||
return_when=asyncio.FIRST_COMPLETED
|
|
||||||
)
|
|
||||||
|
|
||||||
for task in pending:
|
|
||||||
task.cancel()
|
|
||||||
|
|
||||||
if t1 in done and t1.exception() is None:
|
|
||||||
print("[-] Local client disconnected.")
|
|
||||||
break
|
|
||||||
|
|
||||||
print("[-] OmegleWeb connection closed.")
|
|
||||||
break
|
|
||||||
|
|
||||||
except websockets.exceptions.InvalidStatusCode as e:
|
|
||||||
if e.status_code == 403:
|
|
||||||
print(f"[!] HTTP 403 Forbidden: Cookies likely expired. Refreshing...")
|
|
||||||
loop = asyncio.get_running_loop()
|
|
||||||
success = await loop.run_in_executor(None, extract_credentials)
|
|
||||||
if not success:
|
|
||||||
print("[!] Failed to refresh cookies. Retrying in 5s...")
|
|
||||||
await asyncio.sleep(5)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
print(f"[!] Bridge error (Status {e.status_code}): {e}")
|
|
||||||
break
|
|
||||||
except websockets.exceptions.ConnectionClosed:
|
|
||||||
print("[-] OmegleWeb disconnected.")
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[!] Bridge error: {type(e).__name__}: {e}")
|
|
||||||
break
|
|
||||||
|
|
||||||
print(f"[*] Starting local WebSocket proxy on ws://{LOCAL_HOST}:{LOCAL_PORT}")
|
|
||||||
async with websockets.serve(bridge_handler, LOCAL_HOST, LOCAL_PORT):
|
|
||||||
await asyncio.Future()
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if not extract_credentials():
|
|
||||||
print("[!] Initial extraction failed. Exiting.")
|
|
||||||
return
|
|
||||||
|
|
||||||
print("\n" + "="*50)
|
|
||||||
print("[*] PHASE 2: LAUNCH THE WEBSOCKET PROXY")
|
|
||||||
print("="*50)
|
|
||||||
|
|
||||||
try:
|
|
||||||
asyncio.run(start_bridge())
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\n[*] Shutting down...")
|
logging.info("Shutting down...")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|||||||
2
proxy/requirements.txt
Normal file
2
proxy/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
selenium==4.43.0
|
||||||
|
websockets==16.0
|
||||||
86
proxy/selenium_utils.py
Normal file
86
proxy/selenium_utils.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import logging
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.chrome.options import Options
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
|
from selenium.webdriver.support import expected_conditions as EC
|
||||||
|
|
||||||
|
from config import HEADLESS, CF_WAIT_TIME, SELENIUM_URL, TARGET_URL
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def get_chrome_options() -> Options:
|
||||||
|
options = Options()
|
||||||
|
if HEADLESS:
|
||||||
|
options.add_argument("--headless=new")
|
||||||
|
options.add_argument("--no-sandbox")
|
||||||
|
options.add_argument("--disable-dev-shm-usage")
|
||||||
|
options.add_argument("--disable-gpu")
|
||||||
|
options.add_argument("--disable-software-rasterizer")
|
||||||
|
options.add_argument("--disable-extensions")
|
||||||
|
options.add_argument("--disable-blink-features=AutomationControlled")
|
||||||
|
options.add_argument("--disable-setuid-sandbox")
|
||||||
|
options.add_argument("--disable-background-networking")
|
||||||
|
options.add_argument("--disable-default-apps")
|
||||||
|
options.add_argument("--disable-sync")
|
||||||
|
options.add_argument("--disable-translate")
|
||||||
|
options.add_argument("--metrics-recording-only")
|
||||||
|
options.add_argument("--mute-audio")
|
||||||
|
options.add_argument("--no-first-run")
|
||||||
|
options.add_argument("--safebrowsing-disable-auto-update")
|
||||||
|
options.add_argument("--ignore-certificate-errors")
|
||||||
|
options.add_argument("--ignore-ssl-errors")
|
||||||
|
options.add_argument("--user-data-dir=/tmp/chrome-data")
|
||||||
|
options.add_argument("--disable-features=IsolateOrigins,site-per-process")
|
||||||
|
return options
|
||||||
|
|
||||||
|
|
||||||
|
def apply_stealth_scripts(driver: webdriver.Remote) -> None:
|
||||||
|
driver.execute_script("""
|
||||||
|
Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
|
||||||
|
Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]});
|
||||||
|
Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en']});
|
||||||
|
window.chrome = {runtime: {}};
|
||||||
|
|
||||||
|
const originalQuery = window.navigator.permissions.query;
|
||||||
|
window.navigator.permissions.query = (parameters) => (
|
||||||
|
parameters.name === 'notifications'
|
||||||
|
? Promise.resolve({state: Notification.permission})
|
||||||
|
: originalQuery(parameters)
|
||||||
|
);
|
||||||
|
Object.defineProperty(navigator, 'permissions', {get: () => window.navigator.permissions});
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_fresh_credentials() -> tuple[str, str] | None:
|
||||||
|
log.info("Starting Selenium to fetch fresh credentials...")
|
||||||
|
|
||||||
|
driver = None
|
||||||
|
try:
|
||||||
|
driver = webdriver.Remote(
|
||||||
|
command_executor=f"{SELENIUM_URL}/wd/hub",
|
||||||
|
options=get_chrome_options(),
|
||||||
|
)
|
||||||
|
apply_stealth_scripts(driver)
|
||||||
|
driver.get(TARGET_URL)
|
||||||
|
|
||||||
|
wait = WebDriverWait(driver, CF_WAIT_TIME)
|
||||||
|
wait.until(EC.presence_of_element_located((By.ID, "logo")))
|
||||||
|
|
||||||
|
user_agent = driver.execute_script("return navigator.userAgent;")
|
||||||
|
selenium_cookies = driver.get_cookies()
|
||||||
|
cookies = "; ".join(
|
||||||
|
f"{c['name']}={c['value']}"
|
||||||
|
for c in selenium_cookies
|
||||||
|
)
|
||||||
|
log.info("Successfully extracted credentials via Selenium")
|
||||||
|
return (user_agent, cookies)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Failed to extract credentials: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if driver is not None:
|
||||||
|
driver.quit()
|
||||||
85
proxy/websocket_client.py
Normal file
85
proxy/websocket_client.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import websockets
|
||||||
|
|
||||||
|
from config import TARGET_WS_URL, LOCAL_HOST, LOCAL_PORT
|
||||||
|
from selenium_utils import fetch_fresh_credentials
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_connection(local_client):
|
||||||
|
log.info("Local client connected, fetching credentials...")
|
||||||
|
|
||||||
|
result = await asyncio.get_running_loop().run_in_executor(
|
||||||
|
None, fetch_fresh_credentials
|
||||||
|
)
|
||||||
|
if result is None:
|
||||||
|
log.error("Could not fetch credentials")
|
||||||
|
await local_client.close(1011, "Failed to bypass Cloudflare")
|
||||||
|
return
|
||||||
|
|
||||||
|
user_agent, cookies = result
|
||||||
|
headers = {
|
||||||
|
"User-Agent": user_agent,
|
||||||
|
"Cookie": cookies,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
log.info("Connecting to OmegleWeb...")
|
||||||
|
async with websockets.connect(
|
||||||
|
TARGET_WS_URL,
|
||||||
|
additional_headers=headers,
|
||||||
|
) as target_server:
|
||||||
|
log.info("OmegleWeb connected! Notifying client...")
|
||||||
|
await local_client.send("SUCCESS")
|
||||||
|
log.info("Starting relay")
|
||||||
|
await relay_bidirectional(local_client, target_server)
|
||||||
|
|
||||||
|
except websockets.exceptions.InvalidStatusCode as e:
|
||||||
|
log.error(f"Omegle connection failed (Status {e.status_code})")
|
||||||
|
await local_client.close(1011, f"Connection failed: {e.status_code}")
|
||||||
|
|
||||||
|
except websockets.exceptions.ConnectionClosed:
|
||||||
|
log.info("OmegleWeb disconnected")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Connection error: {type(e).__name__}: {e}")
|
||||||
|
await local_client.close(1011, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
async def relay_bidirectional(local_ws, target_ws):
|
||||||
|
async def forward_local():
|
||||||
|
try:
|
||||||
|
async for message in local_ws:
|
||||||
|
log.debug(f"[Local -> Omegle]: {message}")
|
||||||
|
await target_ws.send(message)
|
||||||
|
except websockets.exceptions.ConnectionClosed:
|
||||||
|
log.info("Local client disconnected")
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Forward local error: {e}")
|
||||||
|
|
||||||
|
async def forward_remote():
|
||||||
|
try:
|
||||||
|
async for message in target_ws:
|
||||||
|
log.debug(f"[Omegle -> Local]: {message}")
|
||||||
|
await local_ws.send(message)
|
||||||
|
except websockets.exceptions.ConnectionClosed:
|
||||||
|
log.info("OmegleWeb disconnected")
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Forward remote error: {e}")
|
||||||
|
|
||||||
|
await asyncio.gather(
|
||||||
|
forward_local(),
|
||||||
|
forward_remote(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
log.info(f"Starting WebSocket proxy on ws://{LOCAL_HOST}:{LOCAL_PORT}")
|
||||||
|
await websockets.serve(handle_connection, LOCAL_HOST, LOCAL_PORT)
|
||||||
|
await asyncio.Future()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
14
selenium-patch/Dockerfile
Normal file
14
selenium-patch/Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
FROM selenium/standalone-chromium:4.43.0-20260404
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y python3-pip
|
||||||
|
RUN pip3 install undetected-chromedriver setuptools --break-system-packages
|
||||||
|
|
||||||
|
RUN echo 'from undetected_chromedriver.patcher import Patcher\n\
|
||||||
|
Patcher(executable_path="/usr/bin/chromedriver").patch()\n\
|
||||||
|
print("Chromedriver patched inside Docker successfully!")' > /tmp/patch.py
|
||||||
|
|
||||||
|
RUN python3 /tmp/patch.py
|
||||||
|
|
||||||
|
USER seluser
|
||||||
Reference in New Issue
Block a user