Update snoopr.py

This updated version (2.0.0) brings a host of new features, including richer data collection, smarter snooper detection, whitelisting, automatic data pruning, and an improved web interface.
This commit is contained in:
AlienMajik
2025-05-04 15:07:32 -07:00
committed by GitHub
parent 5bfa59c226
commit ed769f591d

295
snoopr.py
View File

@ -1,16 +1,16 @@
import logging import logging
import sqlite3 import sqlite3
import os import os
from threading import Lock from threading import Lock, Thread
from datetime import datetime, timedelta
import time
from math import radians, sin, cos, sqrt, atan2
import subprocess
import pwnagotchi.plugins as plugins import pwnagotchi.plugins as plugins
from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK from pwnagotchi.ui.view import BLACK
import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.fonts as fonts
from flask import render_template_string from flask import render_template_string, request, jsonify
import time
from math import radians, sin, cos, sqrt, atan2
from datetime import datetime
import subprocess
class Database: class Database:
def __init__(self, path): def __init__(self, path):
@ -21,6 +21,7 @@ class Database:
logging.info('[SnoopR] Setting up database connection...') logging.info('[SnoopR] Setting up database connection...')
self.__connection = sqlite3.connect(self.__path, check_same_thread=False) self.__connection = sqlite3.connect(self.__path, check_same_thread=False)
cursor = self.__connection.cursor() cursor = self.__connection.cursor()
cursor.execute(''' cursor.execute('''
CREATE TABLE IF NOT EXISTS sessions ( CREATE TABLE IF NOT EXISTS sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -33,7 +34,7 @@ class Database:
mac TEXT NOT NULL UNIQUE, mac TEXT NOT NULL UNIQUE,
type TEXT NOT NULL, type TEXT NOT NULL,
name TEXT, name TEXT,
device_type TEXT NOT NULL, -- 'wifi' or 'bluetooth' device_type TEXT NOT NULL,
is_snooper INTEGER DEFAULT 0 is_snooper INTEGER DEFAULT 0
) )
''') ''')
@ -46,11 +47,23 @@ class Database:
signal_strength INTEGER, signal_strength INTEGER,
latitude TEXT, latitude TEXT,
longitude TEXT, longitude TEXT,
channel INTEGER,
auth_mode TEXT,
timestamp TEXT DEFAULT CURRENT_TIMESTAMP, timestamp TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(session_id) REFERENCES sessions(id), FOREIGN KEY(session_id) REFERENCES sessions(id),
FOREIGN KEY(network_id) REFERENCES networks(id) FOREIGN KEY(network_id) REFERENCES networks(id)
) )
''') ''')
cursor.execute("PRAGMA table_info(detections)")
columns = [column[1] for column in cursor.fetchall()]
if 'channel' not in columns:
cursor.execute("ALTER TABLE detections ADD COLUMN channel INTEGER")
logging.info('[SnoopR] Added "channel" column to detections table')
if 'auth_mode' not in columns:
cursor.execute("ALTER TABLE detections ADD COLUMN auth_mode TEXT")
logging.info('[SnoopR] Added "auth_mode" column to detections table')
cursor.close() cursor.close()
self.__connection.commit() self.__connection.commit()
logging.info('[SnoopR] Successfully connected to db') logging.info('[SnoopR] Successfully connected to db')
@ -68,7 +81,7 @@ class Database:
self.__connection.commit() self.__connection.commit()
return session_id return session_id
def add_detection(self, session_id, mac, type_, name, device_type, encryption, signal_strength, latitude, longitude): def add_detection(self, session_id, mac, type_, name, device_type, encryption, signal_strength, latitude, longitude, channel, auth_mode):
cursor = self.__connection.cursor() cursor = self.__connection.cursor()
cursor.execute('SELECT id FROM networks WHERE mac = ? AND device_type = ?', (mac, device_type)) cursor.execute('SELECT id FROM networks WHERE mac = ? AND device_type = ?', (mac, device_type))
result = cursor.fetchone() result = cursor.fetchone()
@ -78,16 +91,16 @@ class Database:
cursor.execute('INSERT INTO networks (mac, type, name, device_type) VALUES (?, ?, ?, ?)', (mac, type_, name, device_type)) cursor.execute('INSERT INTO networks (mac, type, name, device_type) VALUES (?, ?, ?, ?)', (mac, type_, name, device_type))
network_id = cursor.lastrowid network_id = cursor.lastrowid
cursor.execute(''' cursor.execute('''
INSERT INTO detections (session_id, network_id, encryption, signal_strength, latitude, longitude) INSERT INTO detections (session_id, network_id, encryption, signal_strength, latitude, longitude, channel, auth_mode)
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (session_id, network_id, encryption, signal_strength, latitude, longitude)) ''', (session_id, network_id, encryption, signal_strength, latitude, longitude, channel, auth_mode))
cursor.close() cursor.close()
self.__connection.commit() self.__connection.commit()
return network_id return network_id
def get_all_networks(self): def get_all_networks(self, sort_by=None, filter_by=None):
cursor = self.__connection.cursor() cursor = self.__connection.cursor()
cursor.execute(''' query = '''
SELECT n.mac, n.type, n.name, n.device_type, MIN(d.timestamp) as first_seen, MIN(d.session_id) as first_session, SELECT n.mac, n.type, n.name, n.device_type, MIN(d.timestamp) as first_seen, MIN(d.session_id) as first_session,
MAX(d.timestamp) as last_seen, MAX(d.session_id) as last_session, COUNT(DISTINCT d.session_id) as sessions_count, MAX(d.timestamp) as last_seen, MAX(d.session_id) as last_session, COUNT(DISTINCT d.session_id) as sessions_count,
d2.latitude, d2.longitude, n.is_snooper d2.latitude, d2.longitude, n.is_snooper
@ -96,8 +109,18 @@ class Database:
LEFT JOIN detections d2 ON n.id = d2.network_id AND d2.timestamp = ( LEFT JOIN detections d2 ON n.id = d2.network_id AND d2.timestamp = (
SELECT MAX(timestamp) FROM detections WHERE network_id = n.id SELECT MAX(timestamp) FROM detections WHERE network_id = n.id
) )
GROUP BY n.id, n.mac, n.type, n.name, n.device_type WHERE 1=1
''') '''
if filter_by == 'snoopers':
query += ' AND n.is_snooper = 1'
elif filter_by == 'bluetooth':
query += ' AND n.device_type = "bluetooth"'
query += ' GROUP BY n.id, n.mac, n.type, n.name, n.device_type'
if sort_by == 'device_type':
query += ' ORDER BY n.device_type'
elif sort_by == 'is_snooper':
query += ' ORDER BY n.is_snooper DESC'
cursor.execute(query)
rows = cursor.fetchall() rows = cursor.fetchall()
networks = [] networks = []
for row in rows: for row in rows:
@ -145,11 +168,19 @@ class Database:
cursor.close() cursor.close()
self.__connection.commit() self.__connection.commit()
def prune_old_data(self, days):
cursor = self.__connection.cursor()
cutoff_date = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d %H:%M:%S')
cursor.execute('DELETE FROM detections WHERE timestamp < ?', (cutoff_date,))
cursor.close()
self.__connection.commit()
logging.info(f'[SnoopR] Pruned data older than {days} days')
class SnoopR(plugins.Plugin): class SnoopR(plugins.Plugin):
__author__ = 'AlienMajik' __author__ = 'AlienMajik'
__version__ = '1.0.0' __version__ = '2.0.0'
__license__ = 'GPL3' __license__ = 'GPL3'
__description__ = 'A plugin for wardriving Wi-Fi and Bluetooth networks and detecting snoopers.' __description__ = 'A plugin for wardriving Wi-Fi and Bluetooth networks and detecting snoopers with enhanced functionality.'
DEFAULT_PATH = '/root/snoopr' DEFAULT_PATH = '/root/snoopr'
DATABASE_NAME = 'snoopr.db' DATABASE_NAME = 'snoopr.db'
@ -163,21 +194,26 @@ class SnoopR(plugins.Plugin):
self.__session_id = None self.__session_id = None
self.__bluetooth_enabled = False self.__bluetooth_enabled = False
self.last_scan_time = 0 self.last_scan_time = 0
self.__whitelist = []
self.prune_days = 30
def on_loaded(self): def on_loaded(self):
logging.info('[SnoopR] Plugin loaded.') logging.info('[SnoopR] Plugin loaded.')
self.__path = self.options.get('path', self.DEFAULT_PATH) self.__path = self.options.get('path', self.DEFAULT_PATH)
self.__ui_enabled = self.options.get('ui', {}).get('enabled', True) self.__ui_enabled = self.options.get('ui', {}).get('enabled', True)
self.__gps_config = {'method': self.options.get('gps', {}).get('method', 'bettercap')} self.__gps_config = {'method': self.options.get('gps', {}).get('method', 'bettercap')}
self.movement_threshold = self.options.get('movement_threshold', 0.1) # miles self.movement_threshold = self.options.get('movement_threshold', 0.1)
self.time_threshold_minutes = self.options.get('time_threshold_minutes', 5) # minutes self.time_threshold_minutes = self.options.get('time_threshold_minutes', 5)
self.__bluetooth_enabled = self.options.get('bluetooth_enabled', False) self.__bluetooth_enabled = self.options.get('bluetooth_enabled', False)
self.timer = self.options.get('timer', 45) # Scan interval in seconds self.timer = self.options.get('timer', 45)
self.__whitelist = self.options.get('whitelist', [])
self.prune_days = self.options.get('prune_days', 30)
if not os.path.exists(self.__path): if not os.path.exists(self.__path):
os.makedirs(self.__path) os.makedirs(self.__path)
self.__db = Database(os.path.join(self.__path, self.DATABASE_NAME)) self.__db = Database(os.path.join(self.__path, self.DATABASE_NAME))
self.__session_id = self.__db.new_session() self.__session_id = self.__db.new_session()
self.__db.prune_old_data(self.prune_days)
self.ready = True self.ready = True
def on_ui_setup(self, ui): def on_ui_setup(self, ui):
@ -188,6 +224,9 @@ class SnoopR(plugins.Plugin):
ui.add_element('snoopr_wifi_snoopers', LabeledValue( ui.add_element('snoopr_wifi_snoopers', LabeledValue(
color=BLACK, label='WiFi Snoopers:', value='0', position=(7, 105), color=BLACK, label='WiFi Snoopers:', value='0', position=(7, 105),
label_font=fonts.Small, text_font=fonts.Small)) label_font=fonts.Small, text_font=fonts.Small))
ui.add_element('snoopr_last_scan', LabeledValue(
color=BLACK, label='Last Scan:', value='N/A', position=(7, 135),
label_font=fonts.Small, text_font=fonts.Small))
if self.__bluetooth_enabled: if self.__bluetooth_enabled:
ui.add_element('snoopr_bt_networks', LabeledValue( ui.add_element('snoopr_bt_networks', LabeledValue(
color=BLACK, label='BT Nets:', value='0', position=(7, 115), color=BLACK, label='BT Nets:', value='0', position=(7, 115),
@ -201,9 +240,13 @@ class SnoopR(plugins.Plugin):
current_time = time.time() current_time = time.time()
if current_time - self.last_scan_time >= self.timer: if current_time - self.last_scan_time >= self.timer:
self.last_scan_time = current_time self.last_scan_time = current_time
self.on_bluetooth_scan() Thread(target=self.on_bluetooth_scan).start()
ui.set('snoopr_wifi_networks', str(self.__db.network_count('wifi'))) ui.set('snoopr_wifi_networks', str(self.__db.network_count('wifi')))
ui.set('snoopr_wifi_snoopers', str(self.__db.snooper_count('wifi'))) ui.set('snoopr_wifi_snoopers', str(self.__db.snooper_count('wifi')))
if self.last_scan_time == 0:
ui.set('snoopr_last_scan', 'N/A')
else:
ui.set('snoopr_last_scan', datetime.fromtimestamp(self.last_scan_time).strftime('%H:%M:%S'))
if self.__bluetooth_enabled: if self.__bluetooth_enabled:
ui.set('snoopr_bt_networks', str(self.__db.network_count('bluetooth'))) ui.set('snoopr_bt_networks', str(self.__db.network_count('bluetooth')))
ui.set('snoopr_bt_snoopers', str(self.__db.snooper_count('bluetooth'))) ui.set('snoopr_bt_snoopers', str(self.__db.snooper_count('bluetooth')))
@ -213,6 +256,7 @@ class SnoopR(plugins.Plugin):
with ui._lock: with ui._lock:
ui.remove_element('snoopr_wifi_networks') ui.remove_element('snoopr_wifi_networks')
ui.remove_element('snoopr_wifi_snoopers') ui.remove_element('snoopr_wifi_snoopers')
ui.remove_element('snoopr_last_scan')
if self.__bluetooth_enabled: if self.__bluetooth_enabled:
ui.remove_element('snoopr_bt_networks') ui.remove_element('snoopr_bt_networks')
ui.remove_element('snoopr_bt_snoopers') ui.remove_element('snoopr_bt_snoopers')
@ -222,55 +266,68 @@ class SnoopR(plugins.Plugin):
def on_unfiltered_ap_list(self, agent, aps): def on_unfiltered_ap_list(self, agent, aps):
if not self.ready: if not self.ready:
return return
gps_data = self.__get_gps(agent) with self.__lock:
if gps_data and all([gps_data['Latitude'], gps_data['Longitude']]): gps_data = self.__get_gps(agent)
self.__last_gps = { if gps_data and all([gps_data['Latitude'], gps_data['Longitude']]):
'latitude': gps_data['Latitude'], self.__last_gps = {
'longitude': gps_data['Longitude'], 'latitude': gps_data['Latitude'],
'altitude': gps_data['Altitude'] or '-' 'longitude': gps_data['Longitude'],
} 'altitude': gps_data['Altitude'] or '-'
coordinates = {'latitude': str(gps_data['Latitude']), 'longitude': str(gps_data['Longitude'])} }
coordinates = {'latitude': str(gps_data['Latitude']), 'longitude': str(gps_data['Longitude'])}
else:
coordinates = {'latitude': '-', 'longitude': '-'}
for ap in aps: for ap in aps:
mac = ap['mac'] mac = ap['mac']
ssid = ap['hostname'] if ap['hostname'] != '<hidden>' else '' ssid = ap['hostname'] if ap['hostname'] != '<hidden>' else ''
if ssid in self.__whitelist:
continue
encryption = f"{ap['encryption']}{ap.get('cipher', '')}{ap.get('authentication', '')}" encryption = f"{ap['encryption']}{ap.get('cipher', '')}{ap.get('authentication', '')}"
rssi = ap['rssi'] rssi = ap['rssi']
self.__db.add_detection(self.__session_id, mac, 'wi-fi ap', ssid, 'wifi', encryption, rssi, coordinates['latitude'], coordinates['longitude']) channel = ap.get('channel', 0)
auth_mode = ap.get('authentication', '')
network_id = self.__db.add_detection(self.__session_id, mac, 'wi-fi ap', ssid, 'wifi', encryption, rssi, coordinates['latitude'], coordinates['longitude'], channel, auth_mode)
self.check_and_update_snooper_status(mac, 'wifi') self.check_and_update_snooper_status(mac, 'wifi')
else:
self.__gps_available = False
self.__last_gps = {'latitude': '-', 'longitude': '-', 'altitude': '-'}
def on_bluetooth_scan(self): def on_bluetooth_scan(self):
if not self.ready or not self.__bluetooth_enabled: if not self.ready or not self.__bluetooth_enabled:
return return
gps_data = self.__last_gps with self.__lock:
if gps_data['latitude'] == '-' or gps_data['longitude'] == '-': gps_data = self.__last_gps
logging.warning('[SnoopR] GPS not available... skipping Bluetooth scan') if gps_data['latitude'] == '-' or gps_data['longitude'] == '-':
return logging.warning("[SnoopR] No valid GPS data available, skipping Bluetooth scan.")
coordinates = {'latitude': gps_data['latitude'], 'longitude': gps_data['longitude']} return
try: coordinates = {'latitude': gps_data['latitude'], 'longitude': gps_data['longitude']}
cmd_inq = "hcitool inq --flush" try:
inq_output = subprocess.check_output(cmd_inq.split(), stderr=subprocess.DEVNULL).decode().splitlines() cmd_inq = "hcitool inq --flush"
for line in inq_output[1:]: inq_output = subprocess.check_output(cmd_inq.split(), stderr=subprocess.DEVNULL).decode().splitlines()
fields = line.split() for line in inq_output[1:]:
if len(fields) < 1: fields = line.split()
continue if len(fields) < 1:
mac_address = fields[0] continue
name = self.get_device_name(mac_address) mac_address = fields[0]
self.__db.add_detection(self.__session_id, mac_address, 'bluetooth', name, 'bluetooth', '', 0, coordinates['latitude'], coordinates['longitude']) name = self.get_device_name(mac_address)
self.check_and_update_snooper_status(mac_address, 'bluetooth') if name in self.__whitelist:
logging.debug(f'[SnoopR] Logged Bluetooth device: {mac_address} ({name})') continue
except subprocess.CalledProcessError as e: network_id = self.__db.add_detection(self.__session_id, mac_address, 'bluetooth', name, 'bluetooth', '', 0, coordinates['latitude'], coordinates['longitude'], 0, '')
logging.error(f"[SnoopR] Error running hcitool: {e}") self.check_and_update_snooper_status(mac_address, 'bluetooth')
logging.debug(f'[SnoopR] Logged Bluetooth device: {mac_address} ({name})')
except subprocess.CalledProcessError as e:
logging.error(f"[SnoopR] Error running hcitool: {e}")
def get_device_name(self, mac_address): def get_device_name(self, mac_address):
try: for attempt in range(3):
cmd_name = f"hcitool name {mac_address}" try:
name_output = subprocess.check_output(cmd_name.split(), stderr=subprocess.DEVNULL).decode().strip() cmd_name = f"hcitool name {mac_address}"
return name_output if name_output else 'Unknown' name_output = subprocess.check_output(cmd_name.split(), stderr=subprocess.DEVNULL).decode().strip()
except subprocess.CalledProcessError: return name_output if name_output else 'Unknown'
return 'Unknown' except subprocess.CalledProcessError:
if attempt < 2:
time.sleep(1)
continue
else:
logging.warning(f"[SnoopR] Failed to get name for {mac_address} after 3 attempts")
return 'Unknown'
def check_and_update_snooper_status(self, mac, device_type): def check_and_update_snooper_status(self, mac, device_type):
cursor = self.__db._Database__connection.cursor() cursor = self.__db._Database__connection.cursor()
@ -282,7 +339,7 @@ class SnoopR(plugins.Plugin):
ORDER BY d.timestamp ORDER BY d.timestamp
''', (mac, device_type)) ''', (mac, device_type))
rows = cursor.fetchall() rows = cursor.fetchall()
if len(rows) < 2: if len(rows) < 3:
return return
is_snooper = False is_snooper = False
for i in range(1, len(rows)): for i in range(1, len(rows)):
@ -317,11 +374,14 @@ class SnoopR(plugins.Plugin):
return R * c return R * c
def on_webhook(self, path, request): def on_webhook(self, path, request):
if request.method == 'GET' and (path == '/' or not path): if request.method == 'GET':
all_networks = self.__db.get_all_networks() sort_by = request.args.get('sort_by', None)
snoopers = [n for n in all_networks if n['is_snooper']] filter_by = request.args.get('filter_by', None)
center = [float(self.__last_gps['latitude']), float(self.__last_gps['longitude'])] if self.__last_gps['latitude'] != '-' else [0, 0] if path == '/' or not path:
return render_template_string(HTML_PAGE, networks=all_networks, snoopers=snoopers, center=center) all_networks = self.__db.get_all_networks(sort_by=sort_by, filter_by=filter_by)
snoopers = [n for n in all_networks if n['is_snooper']]
center = [float(self.__last_gps['latitude']), float(self.__last_gps['longitude'])] if self.__last_gps['latitude'] != '-' else [0, 0]
return render_template_string(HTML_PAGE, networks=all_networks, snoopers=snoopers, center=center, sort_by=sort_by, filter_by=filter_by)
return "Not found.", 404 return "Not found.", 404
HTML_PAGE = ''' HTML_PAGE = '''
@ -335,30 +395,63 @@ HTML_PAGE = '''
<style> <style>
body { font-family: Arial, sans-serif; margin: 20px; } body { font-family: Arial, sans-serif; margin: 20px; }
table { width: 100%; border-collapse: collapse; } table { width: 100%; border-collapse: collapse; }
th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; } th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; cursor: pointer; }
th { background-color: #f2f2f2; } th { background-color: #f2f2f2; }
.snooper { background-color: #ffcccc; } .snooper { background-color: #ffcccc; }
#map { height: 400px; margin-top: 20px; } #map { height: 400px; margin-top: 20px; }
.filter-container { margin-bottom: 20px; }
.filter-btn { padding: 5px 10px; margin-right: 10px; cursor: pointer; }
.filter-btn.active { background-color: #4CAF50; color: white; }
#scroll-to-top-button {
position: fixed;
top: 10px;
left: 50%;
transform: translateX(-50%);
padding: 5px 10px;
background-color: #4CAF50;
color: white;
border: none;
cursor: pointer;
z-index: 1000;
}
#scroll-to-bottom-button {
position: fixed;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
padding: 5px 10px;
background-color: #4CAF50;
color: white;
border: none;
cursor: pointer;
z-index: 1000;
}
</style> </style>
</head> </head>
<body> <body>
<button id="scroll-to-top-button">Scroll to Top</button>
<h1>SnoopR - Wardrived Networks</h1> <h1>SnoopR - Wardrived Networks</h1>
<div class="filter-container">
<button class="filter-btn active" onclick="toggleFilter('all')">All Networks</button>
<button class="filter-btn" onclick="toggleFilter('snoopers')">Snoopers</button>
<button class="filter-btn" onclick="toggleFilter('bluetooth')">Bluetooth Networks</button>
</div>
<table> <table>
<thead> <thead>
<tr> <tr>
<th>Device Type</th> <th><a href="?sort_by=device_type&filter_by={{ filter_by }}">Device Type</a></th>
<th>MAC Address</th> <th>MAC Address</th>
<th>Type</th> <th>Type</th>
<th>Name</th> <th>Name</th>
<th>First Seen</th> <th>First Seen</th>
<th>Last Seen</th> <th>Last Seen</th>
<th># Sessions</th> <th># Sessions</th>
<th>Snooper</th> <th><a href="?sort_by=is_snooper&filter_by={{ filter_by }}">Snooper</a></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for network in networks %} {% for network in networks %}
<tr class="{{ 'snooper' if network.is_snooper else '' }}"> <tr onclick="panToNetwork({{ network.latitude }}, {{ network.longitude }})" class="{{ 'snooper' if network.is_snooper else '' }}">
<td>{{ network.device_type }}</td> <td>{{ network.device_type }}</td>
<td>{{ network.mac }}</td> <td>{{ network.mac }}</td>
<td>{{ network.type }}</td> <td>{{ network.type }}</td>
@ -372,25 +465,87 @@ HTML_PAGE = '''
</tbody> </tbody>
</table> </table>
<div id="map"></div> <div id="map"></div>
<button id="scroll-to-bottom-button">Scroll to Bottom</button>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script> <script>
document.getElementById('scroll-to-top-button').addEventListener('click', function() {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
document.getElementById('scroll-to-bottom-button').addEventListener('click', function() {
window.scrollTo({
top: document.body.scrollHeight,
behavior: 'smooth'
});
});
var networks = {{ networks | tojson }}; var networks = {{ networks | tojson }};
var center = {{ center | tojson }}; var center = {{ center | tojson }};
var map = L.map('map').setView(center, 13); var map = L.map('map').setView(center, 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors' attribution: '© OpenStreetMap contributors'
}).addTo(map); }).addTo(map);
var markers = [];
networks.forEach(function(network) { networks.forEach(function(network) {
if (network.latitude && network.longitude) { if (network.latitude && network.longitude) {
var color = network.is_snooper ? 'red' : 'blue'; var color = network.is_snooper ? 'red' : 'blue';
L.circleMarker([network.latitude, network.longitude], { var marker = L.circleMarker([network.latitude, network.longitude], {
color: color, color: color,
radius: 5 radius: 5
}).addTo(map).bindPopup( }).bindPopup(
`Device Type: ${network.device_type}<br>MAC: ${network.mac}<br>Name: ${network.name}<br>Snooper: ${network.is_snooper ? 'Yes' : 'No'}` `Device Type: ${network.device_type}<br>MAC: ${network.mac}<br>Name: ${network.name}<br>Snooper: ${network.is_snooper ? 'Yes' : 'No'}`
); );
markers.push({marker: marker, network: network});
marker.addTo(map);
} }
}); });
function panToNetwork(lat, lon) {
if (lat && lon) {
map.panTo([lat, lon]);
}
}
var currentFilter = 'all';
function toggleFilter(filter) {
if (currentFilter === filter) return;
currentFilter = filter;
// Update button styles
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.remove('active');
if (btn.textContent.toLowerCase().includes(filter)) {
btn.classList.add('active');
}
});
// Update map markers
markers.forEach(function(item) {
var network = item.network;
map.removeLayer(item.marker);
if (filter === 'all' ||
(filter === 'snoopers' && network.is_snooper) ||
(filter === 'bluetooth' && network.device_type === 'bluetooth')) {
item.marker.addTo(map);
}
});
}
var bounds = [];
networks.forEach(function(network) {
if (network.latitude && network.longitude) {
bounds.push([network.latitude, network.longitude]);
}
});
if (bounds.length > 0) {
map.fitBounds(bounds);
} else {
map.setView(center, 13);
}
</script> </script>
</body> </body>
</html> </html>