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

@@ -4,7 +4,10 @@ MATRIX_USERNAME=@omeglebot:jzitnik.dev
MATRIX_PASSWORD=
# 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
CF_WAIT_TIME=60 # The time you have to manually pass the Cloudflare challenge in seconds.

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(())
}

View File

@@ -1,7 +1,7 @@
services:
# Selenium Grid with Chromium (or Chrome)
selenium:
image: selenium/standalone-chromium:4.43.0-20260404
build: ./selenium-patch
container_name: selenium
shm_size: 2gb
ports:
@@ -40,6 +40,7 @@ services:
- MATRIX_USERNAME=${MATRIX_USERNAME}
- MATRIX_PASSWORD=${MATRIX_PASSWORD}
- OMEGLE_WEBSOCKET_URL=ws://omegle-proxy:8765
- SELENIUM_URL=${SELENIUM_GRID_URL}
- DB_PATH=/data/omegle.db
volumes:
- omegle-bot-data:/data

View File

@@ -1,9 +1,10 @@
FROM python:3.12-slim
RUN pip install --no-cache-dir setuptools selenium websockets
WORKDIR /app
COPY main.py .
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "main.py"]

0
proxy/__init__.py Normal file
View File

11
proxy/config.py Normal file
View 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")

View File

@@ -1,182 +1,24 @@
import os
import time
import asyncio
import logging
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/")
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")
from config import LOCAL_HOST, LOCAL_PORT
from websocket_client import handle_connection
credentials = {
"user_agent": None,
"cookies": None
}
logging.basicConfig(
level=logging.INFO,
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():
print("\n" + "="*50)
print("[*] PHASE 1: SELENIUM EXTRACTION")
print("="*50)
print("[*] Launching Chrome browser...")
async def main():
await websockets.serve(handle_connection, LOCAL_HOST, LOCAL_PORT)
await asyncio.Future()
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:
print("[*] Navigating to OmegleWeb homepage...")
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())
asyncio.run(main())
except KeyboardInterrupt:
print("\n[*] Shutting down...")
if __name__ == '__main__':
main()
logging.info("Shutting down...")

2
proxy/requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
selenium==4.43.0
websockets==16.0

86
proxy/selenium_utils.py Normal file
View 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
View 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
View 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