Skip to content

Commit 79f58ae

Browse files
committed
refactor(iplookup): restructure module using OOP principles and cleaner architecture
Improved code structure by removing ResultManager class and integrating its functionality directly Enhanced readability and maintainability through modular design and clean code practices Simplified logic and eliminated unnecessary codes
1 parent 1072d42 commit 79f58ae

File tree

5 files changed

+204
-157
lines changed

5 files changed

+204
-157
lines changed
Lines changed: 85 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,93 @@
1-
import concurrent.futures
1+
import os
2+
from concurrent.futures import ThreadPoolExecutor, as_completed
3+
24
from bugscanx.utils.common import get_input, is_cidr
35
from .sources import get_scrapers
46
from .utils import process_input, process_file
5-
from .result_manager import ResultManager
6-
from .logger import IPLookupConsole, console
7-
8-
def extract_domains(ip, scrapers, ip_console):
9-
ip_console.start_ip_scan(ip)
10-
domains = []
11-
for scraper in scrapers:
12-
domain_list = scraper.fetch_domains(ip)
13-
if domain_list:
14-
domains.extend(domain_list)
15-
16-
domains = sorted(set(domains))
17-
return (ip, domains)
18-
19-
def process_ips(ips, output_file):
20-
if not ips:
21-
console.print("[bold red] No valid IPs/CIDRs to process.[/bold red]")
22-
return 0
23-
24-
scrapers = get_scrapers()
25-
ip_console = IPLookupConsole()
26-
result_manager = ResultManager(output_file, ip_console)
27-
28-
def process_ip(ip):
29-
ip, domains = extract_domains(ip, scrapers, ip_console)
7+
from .logger import IPLookupConsole
8+
9+
10+
class IPLookup:
11+
def __init__(self):
12+
self.console = IPLookupConsole()
13+
self.completed = 0
14+
15+
def _fetch_from_source(self, source, ip):
16+
try:
17+
return source.fetch(ip)
18+
except Exception:
19+
return set()
20+
21+
def _save_domains(self, domains, output_file):
3022
if domains:
31-
result_manager.save_result(ip, domains)
32-
return ip, domains
33-
34-
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
35-
futures = {executor.submit(process_ip, ip): ip for ip in ips}
36-
for future in concurrent.futures.as_completed(futures):
37-
future.result()
38-
39-
for scraper in scrapers:
40-
scraper.close()
41-
42-
ip_console.print_final_summary(output_file)
43-
return ip_console.total_domains
44-
45-
def get_input_interactively():
23+
with open(output_file, "a", encoding="utf-8") as f:
24+
f.write("\n".join(sorted(domains)) + "\n")
25+
26+
def process_ip(self, ip, output_file, scrapers, total):
27+
self.console.print_ip_start(ip)
28+
self.console.print_progress(self.completed, total)
29+
30+
with ThreadPoolExecutor(max_workers=3) as executor:
31+
futures = [
32+
executor.submit(self._fetch_from_source, source, ip)
33+
for source in scrapers
34+
]
35+
results = [f.result() for f in as_completed(futures)]
36+
37+
domains = set().union(*results) if results else set()
38+
39+
self.console.update_ip_stats(ip, len(domains))
40+
self.console.print_ip_complete(ip, len(domains))
41+
self._save_domains(domains, output_file)
42+
43+
self.completed += 1
44+
self.console.print_progress(self.completed, total)
45+
return domains
46+
47+
def run(self, ips, output_file, scrapers=None):
48+
if not ips:
49+
self.console.print_error("No valid IPs provided")
50+
return
51+
52+
os.makedirs(os.path.dirname(output_file) or '.', exist_ok=True)
53+
self.completed = 0
54+
all_domains = set()
55+
total = len(ips)
56+
scrapers = scrapers or get_scrapers()
57+
58+
with ThreadPoolExecutor(max_workers=5) as executor:
59+
futures = [
60+
executor.submit(self.process_ip, ip, output_file, scrapers, total)
61+
for ip in ips
62+
]
63+
for future in as_completed(futures):
64+
try:
65+
result = future.result()
66+
all_domains.update(result)
67+
except Exception as e:
68+
self.console.print_error(f"Error processing IP: {str(e)}")
69+
70+
for scraper in scrapers:
71+
scraper.close()
72+
73+
self.console.print_final_summary(output_file)
74+
return all_domains
75+
76+
77+
def main():
4678
ips = []
47-
48-
input_choice = get_input("Select input mode", "choice",
49-
choices=["Manual", "File"])
50-
51-
if input_choice == "Manual":
52-
cidr = get_input("Enter IP or CIDR", validators=[is_cidr])
53-
ips.extend(process_input(cidr))
79+
input_type = get_input("Select input mode", "choice",
80+
choices=["Manual", "File"])
81+
82+
if input_type == "Manual":
83+
ip_input = get_input("Enter IP or CIDR", validators=[is_cidr])
84+
ips.extend(process_input(ip_input))
85+
default_output = f"{ip_input}_domains.txt".replace("/", "-")
5486
else:
5587
file_path = get_input("Enter filename", "file")
5688
ips.extend(process_file(file_path))
57-
58-
output_file = get_input("Enter output filename")
59-
return ips, output_file
60-
61-
def main(ips=None, output_file=None):
62-
if ips is None or output_file is None:
63-
ips, output_file = get_input_interactively()
64-
process_ips(ips, output_file)
89+
default_output = f"{file_path.rsplit('.', 1)[0]}_domains.txt"
90+
91+
output_file = get_input("Enter output filename", default=default_output)
92+
iplookup = IPLookup()
93+
iplookup.run(ips, output_file)
Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,36 @@
1-
from threading import RLock
21
from rich.console import Console
32

4-
console = Console()
53

6-
class Logger:
4+
class IPLookupConsole(Console):
75
def __init__(self):
8-
self._lock = RLock()
6+
super().__init__()
7+
self.total_domains = 0
8+
self.ip_stats = {}
9+
self.disable_cursor()
910

10-
def clear_line(self):
11-
with self._lock:
12-
print("\033[2K\r", end='', flush=True)
11+
def disable_cursor(self):
12+
print('\033[?25l', end='', flush=True)
1313

14-
def replace(self, message):
15-
with self._lock:
16-
print(f"{message}", end='', flush=True)
14+
def enable_cursor(self):
15+
print('\033[?25h', end='', flush=True)
1716

18-
class IPLookupConsole:
19-
def __init__(self):
20-
self.total_domains = 0
21-
self.ip_stats = {}
22-
self.logger = Logger()
23-
24-
def start_ip_scan(self, ip):
25-
self.logger.clear_line()
26-
console.print(f"[cyan] Processing: {ip}[/cyan]")
17+
def print_ip_start(self, ip):
18+
self.print(f"[cyan]Processing: {ip}[/cyan]")
2719

2820
def update_ip_stats(self, ip, count):
2921
self.ip_stats[ip] = count
3022
self.total_domains += count
3123

32-
def print_ip_complete(self, ip, domains_count):
33-
self.logger.clear_line()
34-
console.print(f"[green] {ip}: {domains_count} domains found[/green]")
24+
def print_ip_complete(self, ip, count):
25+
self.print(f"[green]{ip}: {count} domains found[/green]")
3526

3627
def print_final_summary(self, output_file):
37-
console.print(f"\n[green] Total [bold]{self.total_domains}[/bold] domains found[/green]")
38-
console.print(f"[green] Results saved to {output_file}[/green]")
39-
28+
self.print(f"\n[green]Total: [bold]{self.total_domains}[/bold] domains found")
29+
self.print(f"[green]Results saved to {output_file}[/green]")
30+
self.enable_cursor()
31+
32+
def print_progress(self, current, total):
33+
self.print(f"Progress: {current} / {total}", end="\r")
34+
4035
def print_error(self, message):
41-
console.print(f"[bold red]✗ ERROR: {message}[/bold red]")
36+
self.print(f"[red]{message}[/red]")

bugscanx/modules/scrapers/iplookup/result_manager.py

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 47 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,53 @@
1-
import random
2-
import requests
1+
from abc import ABC, abstractmethod
2+
33
from bs4 import BeautifulSoup
4-
from bugscanx.utils.http import USER_AGENTS, EXTRA_HEADERS
5-
from .utils import RateLimiter
6-
7-
class DomainScraper:
8-
def __init__(self, rate_limit=1.0):
9-
self.headers = {
10-
"User-Agent": random.choice(USER_AGENTS),
11-
**EXTRA_HEADERS
12-
}
13-
self.rate_limiter = RateLimiter(rate_limit)
14-
self.session = requests.Session()
15-
self.session.timeout = 10.0
16-
17-
def _make_request(self, url, method='get', data=None):
18-
self.rate_limiter.acquire()
19-
try:
20-
if method == 'get':
21-
response = self.session.get(url, headers=self.headers)
22-
else:
23-
response = self.session.post(url, headers=self.headers, data=data)
24-
response.raise_for_status()
25-
return response
26-
except requests.RequestException:
27-
return None
28-
29-
def fetch_domains(self, ip):
30-
raise NotImplementedError
31-
32-
def close(self):
33-
self.session.close()
34-
35-
class RapidDNSScraper(DomainScraper):
36-
def fetch_domains(self, ip):
37-
response = self._make_request(f"https://rapiddns.io/sameip/{ip}")
38-
if not response:
39-
return []
40-
soup = BeautifulSoup(response.content, 'html.parser')
41-
return [row.find_all('td')[0].text.strip()
42-
for row in soup.find_all('tr') if row.find_all('td')]
43-
44-
class YouGetSignalScraper(DomainScraper):
45-
def fetch_domains(self, ip):
4+
5+
from .utils import RequestHandler
6+
7+
8+
class DomainSource(RequestHandler, ABC):
9+
def __init__(self, name):
10+
super().__init__()
11+
self.name = name
12+
self.domains = set()
13+
14+
@abstractmethod
15+
def fetch(self, ip):
16+
pass
17+
18+
19+
class RapidDNSSource(DomainSource):
20+
def __init__(self):
21+
super().__init__("RapidDNS")
22+
23+
def fetch(self, ip):
24+
response = self.get(f"https://rapiddns.io/sameip/{ip}")
25+
if response:
26+
soup = BeautifulSoup(response.content, 'html.parser')
27+
self.domains.update(
28+
row.find_all('td')[0].text.strip()
29+
for row in soup.find_all('tr')
30+
if row.find_all('td')
31+
)
32+
return self.domains
33+
34+
35+
class YouGetSignalSource(DomainSource):
36+
def __init__(self):
37+
super().__init__("YouGetSignal")
38+
39+
def fetch(self, ip):
4640
data = {'remoteAddress': ip, 'key': '', '_': ''}
47-
response = self._make_request("https://domains.yougetsignal.com/domains.php",
48-
method='post', data=data)
49-
if not response:
50-
return []
51-
return [domain[0] for domain in response.json().get("domainArray", [])]
41+
response = self.post("https://domains.yougetsignal.com/domains.php", data=data)
42+
if response:
43+
self.domains.update(
44+
domain[0] for domain in response.json().get("domainArray", [])
45+
)
46+
return self.domains
47+
5248

5349
def get_scrapers():
5450
return [
55-
RapidDNSScraper(),
56-
YouGetSignalScraper()
51+
RapidDNSSource(),
52+
YouGetSignalSource()
5753
]

0 commit comments

Comments
 (0)