May / tools /web_tools.py
Mayank2027's picture
Create tools/web_tools.py
1adb234 verified
Raw
History Blame Contribute Delete
14.3 kB
# File 8: tools/web_tools.py
with open(f"{base_dir}/tools/web_tools.py", "w") as f:
f.write('''"""Web Search and Network Tools"""
import json
import urllib.request
import urllib.parse
import ssl
import re
from typing import Dict, List, Optional
from config import Config
from utils import Logger, truncate
class WebTools:
"""Web search, scraping, and network operations"""
def __init__(self):
self.logger = Logger()
self.ssl_context = ssl.create_default_context()
self.ssl_context.check_hostname = False
self.ssl_context.verify_mode = ssl.CERT_NONE
def _fetch_url(self, url: str, headers: Dict = None, timeout: int = 15) -> tuple:
"""Helper to fetch URL content"""
default_headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
if headers:
default_headers.update(headers)
req = urllib.request.Request(url, headers=default_headers)
with urllib.request.urlopen(req, timeout=timeout, context=self.ssl_context) as response:
content_type = response.headers.get("Content-Type", "")
data = response.read()
if "text" in content_type or "json" in content_type or "html" in content_type:
try:
text = data.decode("utf-8", errors="replace")
return text, content_type, response.status
except:
pass
return data, content_type, response.status
def web_search(self, query: str, engine: str = "duckduckgo",
max_results: int = 10, safe_search: bool = True) -> Dict:
"""Search the web"""
try:
results = []
if engine == "duckduckgo":
search_url = f"https://html.duckduckgo.com/html/?q={urllib.parse.quote(query)}"
html, _, status = self._fetch_url(search_url)
result_blocks = re.findall(
r'<a rel="nofollow" class="result__a" href="([^"]+)">([^<]+)</a>.*?'
r'<a class="result__snippet"[^>]*>([^<]+)</a>',
html, re.DOTALL
)
for i, (url, title, snippet) in enumerate(result_blocks[:max_results]):
url = urllib.parse.unquote(url)
if url.startswith("//"):
url = "https:" + url
results.append({
"title": re.sub(r"<[^>]+>", "", title).strip(),
"url": url,
"snippet": re.sub(r"<[^>]+>", "", snippet).strip(),
"source": "duckduckgo"
})
elif engine == "bing":
search_url = f"https://www.bing.com/search?q={urllib.parse.quote(query)}"
html, _, _ = self._fetch_url(search_url)
titles = re.findall(r'<h2><a href="([^"]+)"[^>]*>(.*?)</a></h2>', html)
for i, (url, title) in enumerate(titles[:max_results]):
results.append({
"title": re.sub(r"<[^>]+>", "", title).strip(),
"url": url,
"snippet": "",
"source": "bing"
})
self.logger.log("web_search", {"query": query, "engine": engine, "results": len(results)})
return {
"success": True,
"query": query,
"engine": engine,
"results": results,
"total": len(results)
}
except Exception as e:
return {"success": False, "error": str(e)}
def web_fetch(self, url: str, method: str = "GET",
headers: Dict = None, data: str = None,
timeout: int = 15, max_size: int = 100000) -> Dict:
"""Fetch content from URL"""
try:
default_headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
if headers:
default_headers.update(headers)
req = urllib.request.Request(
url,
data=data.encode() if data else None,
headers=default_headers,
method=method
)
with urllib.request.urlopen(req, timeout=timeout, context=self.ssl_context) as response:
content_type = response.headers.get("Content-Type", "")
data_bytes = response.read(max_size)
try:
text = data_bytes.decode("utf-8", errors="replace")
except:
text = data_bytes.hex()[:1000]
self.logger.log("web_fetch", {"url": url, "method": method, "size": len(data_bytes)})
return {
"success": True,
"url": url,
"status": response.status,
"content_type": content_type,
"headers": dict(response.headers),
"content": truncate(text, 3000),
"size": len(data_bytes),
"truncated": len(data_bytes) >= max_size
}
except Exception as e:
return {"success": False, "error": str(e)}
def web_scrape(self, url: str, extract: str = "text",
selector: str = "", max_length: int = 5000) -> Dict:
"""Scrape structured data from web page"""
try:
html, content_type, status = self._fetch_url(url)
if not isinstance(html, str):
return {"success": False, "error": "Binary content, cannot scrape"}
result = {"url": url, "extract": extract}
if extract == "text":
text = re.sub(r"<script[^>]*>.*?</script>", "", html, flags=re.DOTALL)
text = re.sub(r"<style[^>]*>.*?</style>", "", text, flags=re.DOTALL)
text = re.sub(r"<[^>]+>", " ", text)
text = re.sub(r"\s+", " ", text).strip()
result["content"] = text[:max_length]
result["total_length"] = len(text)
elif extract == "links":
links = re.findall(r'<a[^>]+href="([^"]+)"[^>]*>(.*?)</a>', html, re.DOTALL)
result["links"] = [
{
"url": urllib.parse.urljoin(url, href),
"text": re.sub(r"<[^>]+>", "", text).strip()[:100]
}
for href, text in links[:50]
]
result["total"] = len(links)
elif extract == "images":
images = re.findall(r'<img[^>]+src="([^"]+)"[^>]*>', html)
result["images"] = [
{"url": urllib.parse.urljoin(url, src)}
for src in images[:30]
]
result["total"] = len(images)
elif extract == "headings":
headings = re.findall(r"<h([1-6])[^>]*>(.*?)</h\\1>", html, re.DOTALL)
result["headings"] = [
{"level": int(level), "text": re.sub(r"<[^>]+>", "", text).strip()}
for level, text in headings
]
elif extract == "tables":
tables = re.findall(r"<table[^>]*>(.*?)</table>", html, re.DOTALL)
result["tables"] = len(tables)
result["first_table"] = truncate(tables[0], 2000) if tables else ""
self.logger.log("web_scrape", {"url": url, "extract": extract})
result["success"] = True
return result
except Exception as e:
return {"success": False, "error": str(e)}
def api_request(self, url: str, method: str = "GET",
headers: Dict = None, params: Dict = None,
json_data: Dict = None, timeout: int = 15) -> Dict:
"""Make API requests with JSON handling"""
try:
if params:
url += "?" + urllib.parse.urlencode(params)
default_headers = {
"User-Agent": "Swarm-CLI/1.0",
"Accept": "application/json"
}
if headers:
default_headers.update(headers)
data = None
if json_data:
data = json.dumps(json_data).encode()
default_headers["Content-Type"] = "application/json"
req = urllib.request.Request(
url,
data=data,
headers=default_headers,
method=method
)
with urllib.request.urlopen(req, timeout=timeout, context=self.ssl_context) as response:
content = response.read().decode("utf-8", errors="replace")
try:
parsed = json.loads(content)
except:
parsed = content
self.logger.log("api_request", {"url": url, "method": method, "status": response.status})
return {
"success": 200 <= response.status < 300,
"status": response.status,
"headers": dict(response.headers),
"data": parsed,
"raw": truncate(content, 2000)
}
except Exception as e:
return {"success": False, "error": str(e)}
def dns_lookup(self, hostname: str, record_type: str = "A") -> Dict:
"""DNS lookup"""
try:
import socket
if record_type == "A":
ip = socket.gethostbyname(hostname)
return {
"success": True,
"hostname": hostname,
"record_type": "A",
"result": ip
}
else:
import subprocess
result = subprocess.run(
f"nslookup -type={record_type} {hostname}",
shell=True, capture_output=True, text=True, timeout=10
)
return {
"success": result.returncode == 0,
"hostname": hostname,
"record_type": record_type,
"output": result.stdout or result.stderr
}
except Exception as e:
return {"success": False, "error": str(e)}
def port_scan(self, host: str, ports: List[int] = None,
timeout: float = 1.0) -> Dict:
"""Scan ports on a host"""
try:
import socket
if not ports:
ports = [21, 22, 23, 25, 53, 80, 110, 143, 443, 993, 995, 3306, 3389, 5432, 8080, 8443]
open_ports = []
for port in ports:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((host, port))
if result == 0:
try:
service = socket.getservbyport(port, "tcp")
except:
service = "unknown"
open_ports.append({"port": port, "service": service, "status": "open"})
sock.close()
return {
"success": True,
"host": host,
"scanned": len(ports),
"open_ports": open_ports,
"closed": len(ports) - len(open_ports)
}
except Exception as e:
return {"success": False, "error": str(e)}
def download_file(self, url: str, filename: str = "",
verify_checksum: str = "", chunk_size: int = 8192) -> Dict:
"""Download file with progress"""
try:
import hashlib
from config import Config
if not filename:
filename = url.split("/")[-1].split("?")[0] or "download"
save_path = os.path.join(Config.SANDBOX_PATH, filename)
os.makedirs(os.path.dirname(save_path), exist_ok=True)
req = urllib.request.Request(url, headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
})
hasher = hashlib.sha256()
downloaded = 0
with urllib.request.urlopen(req, context=self.ssl_context) as response:
total_size = int(response.headers.get("Content-Length", 0))
with open(save_path, "wb") as f:
while True:
chunk = response.read(chunk_size)
if not chunk:
break
f.write(chunk)
hasher.update(chunk)
downloaded += len(chunk)
checksum = hasher.hexdigest()
verified = True
if verify_checksum:
verified = checksum == verify_checksum
self.logger.log("download_file", {"url": url, "filename": filename, "size": downloaded})
return {
"success": True,
"url": url,
"filename": filename,
"path": save_path,
"size": downloaded,
"sha256": checksum,
"verified": verified if verify_checksum else None
}
except Exception as e:
return {"success": False, "error": str(e)}
''')
print("tools/web_tools.py done")