| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -92,6 +92,24 @@ Here's how to discover services: | |||
| 92 | 92 | ||
| 93 | 93 | .. code-block:: python | |
| 94 | 94 | ||
| 95 | + from zeroconf import ServiceBrowser, ServiceListener, Zeroconf | ||
| 96 | + | ||
| 97 | + | ||
| 98 | + class EventLogger(ServiceListener): | ||
| 99 | + def add_service(self, zc: Zeroconf, type_: str, name: str) -> None: | ||
| 100 | + info = zc.get_service_info(type_, name) | ||
| 101 | + print(f"discovered {name}: {info}") | ||
| 102 | + | ||
| 103 | + def remove_service(self, zc: Zeroconf, type_: str, name: str) -> None: | ||
| 104 | + print(f"lost {name}") | ||
| 105 | + | ||
| 106 | + def update_service(self, zc: Zeroconf, type_: str, name: str) -> None: | ||
| 107 | + print(f"refreshed {name}") | ||
| 108 | + | ||
| 109 | + | ||
| 110 | + with Zeroconf() as zc: | ||
| 111 | + ServiceBrowser(zc, "_http._tcp.local.", EventLogger()) | ||
| 112 | + input("browsing, press enter to stop\n") | ||
| 95 | 113 | ||
| 96 | 114 | .. note:: | |
| 97 | 115 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,77 @@ | |||
| 1 | + #!/usr/bin/env python | ||
| 2 | + | ||
| 3 | + """Scan for Apple devices and dump the device info records they advertise.""" | ||
| 4 | + | ||
| 5 | + from __future__ import annotations | ||
| 6 | + | ||
| 7 | + import argparse | ||
| 8 | + import asyncio | ||
| 9 | + import contextlib | ||
| 10 | + import logging | ||
| 11 | + from typing import Any | ||
| 12 | + | ||
| 13 | + from zeroconf import DNSQuestionType, IPVersion, ServiceStateChange, Zeroconf | ||
| 14 | + from zeroconf.asyncio import AsyncServiceBrowser, AsyncServiceInfo, AsyncZeroconf | ||
| 15 | + | ||
| 16 | + HAP_TYPE = "_hap._tcp.local." | ||
| 17 | + DEVICE_INFO_TYPE = "_device-info._tcp.local." | ||
| 18 | + | ||
| 19 | + log = logging.getLogger(__name__) | ||
| 20 | + | ||
| 21 | + _background_tasks: set[asyncio.Task] = set() | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + def on_service_state_change( | ||
| 25 | + zeroconf: Zeroconf, service_type: str, name: str, state_change: ServiceStateChange | ||
| 26 | + ) -> None: | ||
| 27 | + print(f"{state_change.name}: {name}") | ||
| 28 | + if state_change is not ServiceStateChange.Added: | ||
| 29 | + return | ||
| 30 | + task = asyncio.create_task(dump_service_info(zeroconf, service_type, name)) | ||
| 31 | + _background_tasks.add(task) | ||
| 32 | + task.add_done_callback(_background_tasks.discard) | ||
| 33 | + | ||
| 34 | + | ||
| 35 | + async def dump_service_info(zeroconf: Zeroconf, service_type: str, name: str) -> None: | ||
| 36 | + info = AsyncServiceInfo(service_type, name) | ||
| 37 | + if not await info.async_request(zeroconf, 3000, question_type=DNSQuestionType.QU): | ||
| 38 | + print(f" {name}: no response") | ||
| 39 | + return | ||
| 40 | + show_info(info) | ||
| 41 | + device_name = f"{info.server.rstrip('.')}.{DEVICE_INFO_TYPE}" if info.server else None | ||
| 42 | + if device_name: | ||
| 43 | + device_info = AsyncServiceInfo(DEVICE_INFO_TYPE, device_name) | ||
| 44 | + if await device_info.async_request(zeroconf, 3000, question_type=DNSQuestionType.QU): | ||
| 45 | + show_info(device_info) | ||
| 46 | + | ||
| 47 | + | ||
| 48 | + def show_info(info: AsyncServiceInfo) -> None: | ||
| 49 | + print(f" {info.name}") | ||
| 50 | + print(f" reachable at: {', '.join(info.parsed_scoped_addresses())}") | ||
| 51 | + print(f" host {info.server} port {info.port} (priority {info.priority}, weight {info.weight})") | ||
| 52 | + for key, value in info.decoded_properties.items(): | ||
| 53 | + print(f" txt {key} = {value}") | ||
| 54 | + | ||
| 55 | + | ||
| 56 | + async def main(args: Any) -> None: | ||
| 57 | + ip_version = IPVersion.V6Only if args.v6_only else IPVersion.All | ||
| 58 | + aiozc = AsyncZeroconf(ip_version=ip_version) | ||
| 59 | + browser = AsyncServiceBrowser(aiozc.zeroconf, HAP_TYPE, handlers=[on_service_state_change]) | ||
| 60 | + print(f"scanning for {HAP_TYPE}, press ctrl-c to exit") | ||
| 61 | + try: | ||
| 62 | + await asyncio.Event().wait() | ||
| 63 | + finally: | ||
| 64 | + await browser.async_cancel() | ||
| 65 | + await aiozc.async_close() | ||
| 66 | + | ||
| 67 | + | ||
| 68 | + if __name__ == "__main__": | ||
| 69 | + parser = argparse.ArgumentParser(description=__doc__) | ||
| 70 | + parser.add_argument("--debug", action="store_true", help="enable debug logging") | ||
| 71 | + parser.add_argument("--v6-only", action="store_true", help="use IPv6 only") | ||
| 72 | + args = parser.parse_args() | ||
| 73 | + | ||
| 74 | + logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO) | ||
| 75 | + | ||
| 76 | + with contextlib.suppress(KeyboardInterrupt): | ||
| 77 | + asyncio.run(main(args)) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,62 @@ | |||
| 1 | + #!/usr/bin/env python | ||
| 2 | + | ||
| 3 | + """Resolve every HTTP service currently on the network, on a fixed cadence.""" | ||
| 4 | + | ||
| 5 | + from __future__ import annotations | ||
| 6 | + | ||
| 7 | + import argparse | ||
| 8 | + import asyncio | ||
| 9 | + import contextlib | ||
| 10 | + import logging | ||
| 11 | + | ||
| 12 | + from zeroconf import IPVersion, Zeroconf | ||
| 13 | + from zeroconf.asyncio import AsyncServiceBrowser, AsyncServiceInfo, AsyncZeroconf | ||
| 14 | + | ||
| 15 | + HTTP_TYPE = "_http._tcp.local." | ||
| 16 | + RESOLVE_INTERVAL = 5 | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + async def resolve_all(zeroconf: Zeroconf, names: set[str]) -> None: | ||
| 20 | + infos = [AsyncServiceInfo(HTTP_TYPE, name) for name in sorted(names)] | ||
| 21 | + await asyncio.gather(*(info.async_request(zeroconf, 3000) for info in infos)) | ||
| 22 | + for info in infos: | ||
| 23 | + print(f" {info.name}") | ||
| 24 | + if not info.server: | ||
| 25 | + print(" (unresolved)") | ||
| 26 | + continue | ||
| 27 | + print(f" reachable at: {', '.join(info.parsed_scoped_addresses())}") | ||
| 28 | + print(f" host {info.server} port {info.port} (priority {info.priority}, weight {info.weight})") | ||
| 29 | + for key, value in info.decoded_properties.items(): | ||
| 30 | + print(f" txt {key} = {value}") | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + async def main(ip_version: IPVersion) -> None: | ||
| 34 | + seen: set[str] = set() | ||
| 35 | + aiozc = AsyncZeroconf(ip_version=ip_version) | ||
| 36 | + browser = AsyncServiceBrowser( | ||
| 37 | + aiozc.zeroconf, | ||
| 38 | + HTTP_TYPE, | ||
| 39 | + handlers=[lambda zeroconf, service_type, name, state_change: seen.add(name)], | ||
| 40 | + ) | ||
| 41 | + print(f"collecting {HTTP_TYPE} services, resolving every {RESOLVE_INTERVAL}s; ctrl-c to exit") | ||
| 42 | + try: | ||
| 43 | + while True: | ||
| 44 | + await asyncio.sleep(RESOLVE_INTERVAL) | ||
| 45 | + print(f"resolving {len(seen)} known services:") | ||
| 46 | + await resolve_all(aiozc.zeroconf, seen) | ||
| 47 | + finally: | ||
| 48 | + await browser.async_cancel() | ||
| 49 | + await aiozc.async_close() | ||
| 50 | + | ||
| 51 | + | ||
| 52 | + if __name__ == "__main__": | ||
| 53 | + parser = argparse.ArgumentParser(description=__doc__) | ||
| 54 | + parser.add_argument("--debug", action="store_true", help="enable debug logging") | ||
| 55 | + parser.add_argument("--v6-only", action="store_true", help="use IPv6 only") | ||
| 56 | + args = parser.parse_args() | ||
| 57 | + | ||
| 58 | + logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO) | ||
| 59 | + ip_version = IPVersion.V6Only if args.v6_only else IPVersion.All | ||
| 60 | + | ||
| 61 | + with contextlib.suppress(KeyboardInterrupt): | ||
| 62 | + asyncio.run(main(ip_version)) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -541,6 +541,11 @@ def __hash__(self) -> int: | |||
| 541 | 541 | """Hash to compare like DNSText.""" | |
| 542 | 542 | return self._hash | |
| 543 | 543 | ||
| 544 | + def __repr__(self) -> str: | ||
| 545 | + if len(self.text) > 16: | ||
| 546 | + return self.to_string(f"{len(self.text)} bytes") | ||
| 547 | + return self.to_string(self.text) | ||
| 548 | + | ||
| 544 | 549 | def write(self, out: DNSOutgoing) -> None: | |
| 545 | 550 | out.write_string(self.text) | |
| 546 | 551 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments