refactor: async I/O, input sanitisation, and shared utilities cleanup
This commit is contained in:
+68
-85
@@ -1,46 +1,41 @@
|
||||
"""
|
||||
This plugin provides a command to get random SOCKS5 proxies.
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import random
|
||||
import requests
|
||||
import aiohttp
|
||||
import socket
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
import concurrent.futures
|
||||
import asyncio
|
||||
import simplematrixbotlib as botlib
|
||||
import sqlite3
|
||||
import ipaddress
|
||||
|
||||
from plugins.utils import is_public_destination
|
||||
from plugins.common import is_public_destination, html_escape
|
||||
|
||||
SOCKS5_LIST_URL = 'https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/socks5.txt'
|
||||
MAX_TRIES = 64
|
||||
PROXY_LIST_FILENAME = 'socks5.txt'
|
||||
PROXY_LIST_EXPIRATION = timedelta(hours=8)
|
||||
MAX_THREADS = 128
|
||||
MAX_THREADS = 64 # lowered to avoid resource exhaustion
|
||||
PROXIES_DB_FILE = 'proxies.db'
|
||||
MAX_PROXIES_IN_DB = 10
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
||||
def test_proxy(proxy):
|
||||
"""Test a SOCKS5 proxy and return the outcome."""
|
||||
"""Test a SOCKS5 proxy and return (success, proxy, latency)."""
|
||||
try:
|
||||
ip, port = proxy.split(':')
|
||||
logging.info(f"Testing SOCKS5 proxy: {ip}:{port}")
|
||||
start_time = time.time()
|
||||
with socket.create_connection((ip, int(port)), timeout=12) as client:
|
||||
client.sendall(b'\x05\x01\x00')
|
||||
response = client.recv(2)
|
||||
if response == b'\x05\x00':
|
||||
latency = int(round((time.time() - start_time) * 1000, 0))
|
||||
latency = int(round((time.time() - start_time) * 1000))
|
||||
return True, proxy, latency
|
||||
else:
|
||||
return False, proxy, None
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
return False, proxy, None
|
||||
|
||||
async def download_proxy_list():
|
||||
@@ -48,13 +43,15 @@ async def download_proxy_list():
|
||||
if not os.path.exists(PROXY_LIST_FILENAME) or \
|
||||
datetime.now() - datetime.fromtimestamp(os.path.getctime(PROXY_LIST_FILENAME)) > PROXY_LIST_EXPIRATION:
|
||||
logging.info("Downloading SOCKS5 proxy list")
|
||||
response = requests.get(SOCKS5_LIST_URL, timeout=5)
|
||||
with open(PROXY_LIST_FILENAME, 'w') as f:
|
||||
f.write(response.text)
|
||||
logging.info("Proxy list downloaded successfully")
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(SOCKS5_LIST_URL, timeout=20) as response:
|
||||
response.raise_for_status()
|
||||
text = await response.text()
|
||||
with open(PROXY_LIST_FILENAME, 'w') as f:
|
||||
f.write(text)
|
||||
logging.info("Proxy list downloaded")
|
||||
return True
|
||||
else:
|
||||
logging.info("Proxy list already exists and is up-to-date")
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"Error downloading proxy list: {e}")
|
||||
@@ -64,48 +61,39 @@ def check_db_for_proxy():
|
||||
try:
|
||||
with sqlite3.connect(PROXIES_DB_FILE) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS proxies (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
proxy TEXT,
|
||||
latency INTEGER,
|
||||
status TEXT
|
||||
)
|
||||
""")
|
||||
cursor.execute("SELECT proxy, latency FROM proxies WHERE status = 'working' AND latency < 3000 ORDER BY RANDOM() LIMIT 1")
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
proxy, latency = result
|
||||
cursor.execute("""CREATE TABLE IF NOT EXISTS proxies (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
proxy TEXT,
|
||||
latency INTEGER,
|
||||
status TEXT)""")
|
||||
cursor.execute("SELECT proxy, latency FROM proxies WHERE status='working' AND latency<3000 ORDER BY RANDOM() LIMIT 1")
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
proxy, latency = row
|
||||
success, _, _ = test_proxy(proxy)
|
||||
if success:
|
||||
return proxy, latency
|
||||
else:
|
||||
cursor.execute("DELETE FROM proxies WHERE proxy = ?", (proxy,))
|
||||
cursor.execute("DELETE FROM proxies WHERE proxy=?", (proxy,))
|
||||
conn.commit()
|
||||
logging.info(f"Removed non-working proxy from the database: {proxy}")
|
||||
return None, None
|
||||
else:
|
||||
return None, None
|
||||
return None, None
|
||||
except Exception as e:
|
||||
logging.error(f"Error checking proxies database: {e}")
|
||||
logging.error(f"DB error: {e}")
|
||||
return None, None
|
||||
|
||||
def save_proxy_to_db(proxy, latency):
|
||||
try:
|
||||
with sqlite3.connect(PROXIES_DB_FILE) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS proxies (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
proxy TEXT,
|
||||
latency INTEGER,
|
||||
status TEXT
|
||||
)
|
||||
""")
|
||||
cursor.execute("INSERT INTO proxies (proxy, latency, status) VALUES (?, ?, 'working')", (proxy, latency))
|
||||
cursor.execute("""CREATE TABLE IF NOT EXISTS proxies (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
proxy TEXT,
|
||||
latency INTEGER,
|
||||
status TEXT)""")
|
||||
cursor.execute("INSERT INTO proxies (proxy, latency, status) VALUES (?,?,'working')", (proxy, latency))
|
||||
conn.commit()
|
||||
except Exception as e:
|
||||
logging.error(f"Error saving proxy to database: {e}")
|
||||
logging.error(f"Error saving proxy: {e}")
|
||||
|
||||
async def handle_command(room, message, bot, prefix, config):
|
||||
match = botlib.MessageMatch(room, message, bot, prefix)
|
||||
@@ -113,52 +101,47 @@ async def handle_command(room, message, bot, prefix, config):
|
||||
logging.info("Received !proxy command")
|
||||
working_proxy, latency = check_db_for_proxy()
|
||||
if working_proxy:
|
||||
safe_proxy = html_escape(working_proxy)
|
||||
await bot.api.send_markdown_message(room.room_id,
|
||||
f"✅ Using cached working SOCKS5 Proxy: **{working_proxy}** - Latency: **{latency} ms**")
|
||||
f"✅ Using cached working SOCKS5 Proxy: **{safe_proxy}** - Latency: **{latency} ms**")
|
||||
return
|
||||
else:
|
||||
if not await download_proxy_list():
|
||||
await bot.api.send_markdown_message(room.room_id, "Error downloading proxy list")
|
||||
return
|
||||
try:
|
||||
with open(PROXY_LIST_FILENAME, 'r') as f:
|
||||
socks5_proxies = [line.replace("socks5://", "") for line in f.read().splitlines()]
|
||||
# Filter out private/internal proxies before testing
|
||||
socks5_proxies = [p for p in socks5_proxies if is_public_destination(p.split(':')[0])]
|
||||
random.shuffle(socks5_proxies)
|
||||
tested_proxies = 0
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
|
||||
futures = []
|
||||
for proxy in socks5_proxies[:MAX_TRIES]:
|
||||
futures.append(executor.submit(test_proxy, proxy))
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
success, proxy, latency = future.result()
|
||||
if success:
|
||||
await bot.api.send_markdown_message(room.room_id,
|
||||
f"✅ Anonymous SOCKS5 Proxy: **{proxy}** - Latency: **{latency} ms**")
|
||||
save_proxy_to_db(proxy, latency)
|
||||
tested_proxies += 1
|
||||
if tested_proxies >= MAX_PROXIES_IN_DB:
|
||||
break
|
||||
working_proxy, latency = check_db_for_proxy()
|
||||
if working_proxy:
|
||||
await bot.api.send_markdown_message(room.room_id,
|
||||
f"✅ Using cached working SOCKS5 Proxy: **{working_proxy}** - Latency: **{latency} ms**")
|
||||
else:
|
||||
await bot.api.send_markdown_message(room.room_id, "❌ No working anonymous SOCKS5 proxy found")
|
||||
except Exception as e:
|
||||
logging.error(f"Error handling !proxy command: {e}")
|
||||
await bot.api.send_markdown_message(room.room_id, "❌ Error handling !proxy command")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Plugin Metadata
|
||||
# ---------------------------------------------------------------------------
|
||||
__version__ = "1.0.1"
|
||||
if not await download_proxy_list():
|
||||
await bot.api.send_markdown_message(room.room_id, "Error downloading proxy list")
|
||||
return
|
||||
|
||||
try:
|
||||
with open(PROXY_LIST_FILENAME, 'r') as f:
|
||||
socks5_proxies = [line.replace("socks5://", "") for line in f.read().splitlines()]
|
||||
socks5_proxies = [p for p in socks5_proxies if is_public_destination(p.split(':')[0])]
|
||||
random.shuffle(socks5_proxies)
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
tested = 0
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
|
||||
futures = [loop.run_in_executor(executor, test_proxy, proxy) for proxy in socks5_proxies[:MAX_TRIES]]
|
||||
for future in asyncio.as_completed(futures):
|
||||
success, proxy, latency = await future
|
||||
if success:
|
||||
safe_proxy = html_escape(proxy)
|
||||
await bot.api.send_markdown_message(room.room_id,
|
||||
f"✅ Anonymous SOCKS5 Proxy: **{safe_proxy}** - Latency: **{latency} ms**")
|
||||
save_proxy_to_db(proxy, latency)
|
||||
tested += 1
|
||||
if tested >= MAX_PROXIES_IN_DB:
|
||||
break
|
||||
if tested == 0:
|
||||
await bot.api.send_markdown_message(room.room_id, "❌ No working anonymous SOCKS5 proxy found")
|
||||
except Exception as e:
|
||||
logging.error(f"Error handling !proxy command: {e}")
|
||||
await bot.api.send_markdown_message(room.room_id, "❌ Error handling !proxy command")
|
||||
|
||||
__version__ = "1.0.2"
|
||||
__author__ = "Funguy Bot"
|
||||
__description__ = "Working SOCKS5 proxy finder (SSRF‑safe)"
|
||||
__description__ = "Working SOCKS5 proxy finder (SSRF‑safe, async)"
|
||||
__help__ = """
|
||||
<details>
|
||||
<summary><strong>!proxy</strong> – Random working SOCKS5 proxy</summary>
|
||||
<p>Fetches, tests, and returns a random working SOCKS5 proxy with latency. Caches good proxies in SQLite.</p>
|
||||
<p>Fetches, tests, and returns a random working SOCKS5 proxy with latency.</p>
|
||||
</details>
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user