Skip to content

Commit a4e3f76

Browse files
author
Tom Softreck
committed
update
1 parent 5bdd059 commit a4e3f76

File tree

5 files changed

+367
-1
lines changed

5 files changed

+367
-1
lines changed

Makefile

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ help:
3131
# Installation
3232
install:
3333
pip install -e .
34+
pip install python-nmap opencv-python pycups
3435
@echo "✅ DialogChain installed"
3536

3637
dev: install
@@ -293,6 +294,37 @@ run-iot: setup-env
293294
@echo "🚀 Running IoT example..."
294295
@make run-example EXAMPLE=iot
295296

297+
scan-network:
298+
@echo "🔍 Scanning network for devices..."
299+
@python -c "from dialogchain.scanner import NetworkScanner; import asyncio; \
300+
scanner = NetworkScanner(); \
301+
services = asyncio.run(scanner.scan_network()); \
302+
print('\n'.join(f'{s.ip}:{s.port} - {s.service} ({s.banner})' for s in services))"
303+
304+
scan-cameras:
305+
@echo "📷 Scanning for cameras..."
306+
@python -c "from dialogchain.scanner import NetworkScanner; import asyncio; \
307+
scanner = NetworkScanner(); \
308+
services = asyncio.run(scanner.scan_network(service_types=['rtsp'])); \
309+
print('\n'.join(f'Camera found at rtsp://{s.ip}:{s.port}' for s in services))"
310+
311+
scan-printers:
312+
@echo "🖨️ Scanning for printers..."
313+
@python -c "import cups; conn = cups.Connection(); \
314+
printers = conn.getPrinters(); \
315+
[print(f'Printer: {p} - {printers[p]["device-uri"]}') for p in printers]"
316+
317+
print-test:
318+
@echo "🖨️ Sending test page to default printer..."
319+
@python -c "import cups; conn = cups.Connection(); \
320+
printers = conn.getPrinters(); \
321+
if printers: \
322+
printer = list(printers.keys())[0]; \
323+
job_id = conn.printFile(printer, '/dev/stdin', 'Test Print', {'raw': 'True'}); \
324+
print(f'✅ Sent test page to {printer} (Job ID: {job_id})'); \
325+
else: \
326+
print('❌ No printers found')" < <(echo 'Hello from DialogChain! This is a test print.')
327+
296328
run-simple: setup-env
297329
@echo "🚀 Running simple example..."
298330
@make run-example EXAMPLE=simple

README.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,110 @@
99
[![Tests](https://github.com/dialogchain/python/actions/workflows/tests.yml/badge.svg)](https://github.com/dialogchain/python/actions/workflows/tests.yml)
1010
[![codecov](https://codecov.io/gh/dialogchain/python/graph/badge.svg?token=YOUR-TOKEN-HERE)](https://codecov.io/gh/dialogchain/python)
1111

12+
## 🔍 Network Scanning & Printing
13+
14+
DialogChain includes powerful network scanning capabilities to discover devices like cameras and printers on your local network.
15+
16+
### Scan for Network Devices
17+
18+
Scan your local network for various devices and services:
19+
20+
```bash
21+
make scan-network
22+
```
23+
24+
### Discover Cameras
25+
26+
Find RTSP cameras on your network:
27+
28+
```bash
29+
make scan-cameras
30+
```
31+
32+
### Discover Printers
33+
34+
List all available printers on your system:
35+
36+
```bash
37+
make scan-printers
38+
```
39+
40+
### Print a Test Page
41+
42+
Send a test page to your default printer:
43+
44+
```bash
45+
make print-test
46+
```
47+
48+
### Using the Network Scanner in Python
49+
50+
You can also use the network scanner directly in your Python code:
51+
52+
```python
53+
from dialogchain.scanner import NetworkScanner
54+
import asyncio
55+
56+
async def scan_network():
57+
scanner = NetworkScanner()
58+
59+
# Scan for all services
60+
services = await scanner.scan_network()
61+
62+
# Or scan for specific service types
63+
cameras = await scanner.scan_network(service_types=['rtsp'])
64+
65+
for service in services:
66+
print(f"{service.ip}:{service.port} - {service.service} ({service.banner})")
67+
68+
# Run the scan
69+
asyncio.run(scan_network())
70+
```
71+
72+
## 🖨️ Printing Support
73+
74+
DialogChain includes basic printing capabilities using the CUPS (Common Unix Printing System) interface.
75+
76+
### Print Text
77+
78+
```python
79+
import cups
80+
81+
def print_text(text, printer_name=None):
82+
conn = cups.Connection()
83+
printers = conn.getPrinters()
84+
85+
if not printers:
86+
print("No printers available")
87+
return
88+
89+
printer = printer_name or list(printers.keys())[0]
90+
job_id = conn.printFile(printer, "/dev/stdin", "DialogChain Print", {"raw": "True"}, text)
91+
print(f"Sent print job {job_id} to {printer}")
92+
93+
# Example usage
94+
print_text("Hello from DialogChain!")
95+
```
96+
97+
### Print from File
98+
99+
```python
100+
def print_file(file_path, printer_name=None):
101+
conn = cups.Connection()
102+
printers = conn.getPrinters()
103+
104+
if not printers:
105+
print("No printers available")
106+
return
107+
108+
printer = printer_name or list(printers.keys())[0]
109+
job_id = conn.printFile(printer, file_path, "Document Print", {})
110+
print(f"Sent print job {job_id} to {printer}")
111+
112+
# Example usage
113+
print_file("document.pdf")
114+
```
115+
12116
## 📦 Installation
13117

14118
### Prerequisites

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
44

55
[project]
66
name = "dialogchain"
7-
version = "0.1.9"
7+
version = "0.1.10"
88
description = "DialogChain - A flexible and extensible dialog processing framework"
99
authors = [
1010
{name = "DialogChain Team", email = "team@dialogchain.org"},

scripts/network_scanner.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Network scanner script for DialogChain.
4+
Provides basic network scanning capabilities without requiring root privileges.
5+
"""
6+
7+
import asyncio
8+
import socket
9+
import argparse
10+
from typing import List, Optional
11+
from dataclasses import dataclass
12+
13+
@dataclass
14+
class NetworkService:
15+
"""Represents a discovered network service."""
16+
ip: str
17+
port: int
18+
service: str = "unknown"
19+
protocol: str = "tcp"
20+
banner: str = ""
21+
is_secure: bool = False
22+
is_up: bool = True
23+
24+
class SimpleNetworkScanner:
25+
"""Simple network scanner that doesn't require root privileges."""
26+
27+
COMMON_PORTS = {
28+
'rtsp': [554, 8554],
29+
'http': [80, 8080, 8000, 8888],
30+
'https': [443, 8443],
31+
'ssh': [22],
32+
'vnc': [5900, 5901],
33+
'rdp': [3389],
34+
'mqtt': [1883],
35+
'mqtts': [8883]
36+
}
37+
38+
def __init__(self, timeout: float = 2.0):
39+
"""Initialize the scanner with connection timeout."""
40+
self.timeout = timeout
41+
42+
async def check_port(self, ip: str, port: int) -> bool:
43+
"""Check if a port is open."""
44+
try:
45+
reader, writer = await asyncio.wait_for(
46+
asyncio.open_connection(ip, port),
47+
timeout=self.timeout
48+
)
49+
writer.close()
50+
await writer.wait_closed()
51+
return True
52+
except (asyncio.TimeoutError, ConnectionRefusedError, OSError):
53+
return False
54+
55+
def identify_service(self, port: int) -> str:
56+
"""Identify service based on port number."""
57+
for service, ports in self.COMMON_PORTS.items():
58+
if port in ports:
59+
return service
60+
return "unknown"
61+
62+
async def scan_network(
63+
self,
64+
network: str = '192.168.1.0/24',
65+
ports: Optional[List[int]] = None,
66+
service_types: Optional[List[str]] = None
67+
) -> List[NetworkService]:
68+
"""Scan a network for open ports and services."""
69+
if ports is None and service_types is None:
70+
ports = list(set(p for ports in self.COMMON_PORTS.values() for p in ports))
71+
elif service_types:
72+
ports = []
73+
for svc in service_types:
74+
if svc in self.COMMON_PORTS:
75+
ports.extend(self.COMMON_PORTS[svc])
76+
ports = list(set(ports))
77+
78+
# Get IPs to scan
79+
base_ip = ".".join(network.split(".")[:3])
80+
ips = [f"{base_ip}.{i}" for i in range(1, 255)]
81+
82+
# Scan ports for each IP
83+
tasks = []
84+
for ip in ips:
85+
for port in ports:
86+
tasks.append(self.scan_port(ip, port))
87+
88+
# Run all scans concurrently
89+
results = await asyncio.gather(*tasks)
90+
return [service for service in results if service.is_up]
91+
92+
async def scan_port(self, ip: str, port: int) -> NetworkService:
93+
"""Scan a single port and return service info."""
94+
is_open = await self.check_port(ip, port)
95+
service = self.identify_service(port)
96+
return NetworkService(
97+
ip=ip,
98+
port=port,
99+
service=service,
100+
is_up=is_open
101+
)
102+
103+
async def main():
104+
"""Main function for command-line usage."""
105+
parser = argparse.ArgumentParser(description='Network scanner for DialogChain')
106+
parser.add_argument('--network', '-n', default='192.168.1.0/24',
107+
help='Network to scan in CIDR notation')
108+
parser.add_argument('--service', '-s', action='append',
109+
help='Service types to scan (rtsp, http, etc.)')
110+
parser.add_argument('--port', '-p', type=int, action='append',
111+
help='Specific ports to scan')
112+
parser.add_argument('--timeout', '-t', type=float, default=1.0,
113+
help='Connection timeout in seconds')
114+
115+
args = parser.parse_args()
116+
117+
scanner = SimpleNetworkScanner(timeout=args.timeout)
118+
services = await scanner.scan_network(
119+
network=args.network,
120+
ports=args.port,
121+
service_types=args.service
122+
)
123+
124+
# Print results
125+
print("\nScan Results:")
126+
print("-" * 60)
127+
print(f"{'IP':<15} {'Port':<6} {'Service':<10} {'Status'}")
128+
print("-" * 60)
129+
130+
for svc in sorted(services, key=lambda x: (x.ip, x.port)):
131+
status = "UP" if svc.is_up else "DOWN"
132+
print(f"{svc.ip:<15} {svc.port:<6} {svc.service:<10} {status}")
133+
134+
if __name__ == "__main__":
135+
asyncio.run(main())

scripts/printer_scanner.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Printer scanner script for DialogChain.
4+
Provides printer discovery and basic printing capabilities.
5+
"""
6+
7+
import cups
8+
import argparse
9+
import sys
10+
from typing import Dict, Any, Optional
11+
12+
def list_printers() -> Dict[str, Dict[str, Any]]:
13+
"""List all available printers."""
14+
try:
15+
conn = cups.Connection()
16+
return conn.getPrinters()
17+
except RuntimeError as e:
18+
print(f"Error connecting to CUPS: {e}")
19+
print("Make sure CUPS is installed and running.")
20+
print("On Ubuntu/Debian: sudo apt install cups")
21+
print("On RHEL/CentOS: sudo yum install cups")
22+
return {}
23+
24+
def print_text(text: str, printer_name: Optional[str] = None) -> int:
25+
"""Print text to the specified or default printer."""
26+
try:
27+
conn = cups.Connection()
28+
printers = conn.getPrinters()
29+
30+
if not printers:
31+
print("❌ No printers available")
32+
return 1
33+
34+
if printer_name and printer_name not in printers:
35+
print(f"❌ Printer '{printer_name}' not found")
36+
print("\nAvailable printers:")
37+
for name, attrs in printers.items():
38+
print(f"- {name} ({attrs.get('device-uri', 'no URI')})")
39+
return 1
40+
41+
printer = printer_name or list(printers.keys())[0]
42+
job_id = conn.printFile(
43+
printer,
44+
'/dev/stdin',
45+
"DialogChain Print",
46+
{"raw": "True"},
47+
text
48+
)
49+
50+
print(f"✅ Sent print job {job_id} to {printer}")
51+
return 0
52+
53+
except cups.IPPError as e:
54+
print(f"❌ Print error: {e}")
55+
return 1
56+
57+
def main() -> int:
58+
"""Main function for command-line usage."""
59+
parser = argparse.ArgumentParser(description='Printer scanner for DialogChain')
60+
subparsers = parser.add_subparsers(dest='command', required=True)
61+
62+
# List command
63+
list_parser = subparsers.add_parser('list', help='List available printers')
64+
65+
# Print command
66+
print_parser = subparsers.add_parser('print', help='Print a test page')
67+
print_parser.add_argument('--printer', '-p', help='Printer name (default: default printer)')
68+
print_parser.add_argument('--text', '-t', default='Hello from DialogChain!',
69+
help='Text to print')
70+
71+
args = parser.parse_args()
72+
73+
if args.command == 'list':
74+
printers = list_printers()
75+
if not printers:
76+
print("No printers found")
77+
return 1
78+
79+
print("\nAvailable Printers:")
80+
print("-" * 60)
81+
for name, attrs in printers.items():
82+
print(f"Name: {name}")
83+
print(f" URI: {attrs.get('device-uri', 'N/A')}")
84+
print(f" Info: {attrs.get('printer-info', 'N/A')}")
85+
print(f" State: {attrs.get('printer-state-message', 'N/A')}")
86+
print("-" * 60)
87+
return 0
88+
89+
elif args.command == 'print':
90+
return print_text(args.text, args.printer)
91+
92+
return 0
93+
94+
if __name__ == "__main__":
95+
sys.exit(main())

0 commit comments

Comments
 (0)