Skip to content

Commit d72faed

Browse files
authored
Add support for Pi-hole v6 API (#29)
1 parent e354ae3 commit d72faed

File tree

8 files changed

+799
-221
lines changed

8 files changed

+799
-221
lines changed

CHANGES.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
Changes
22
=======
33

4+
0.9.0 - 20250223
5+
----------------
6+
7+
- Add support for Pi-hole v6
8+
49
0.8.0 - 20221223
510
----------------
611

README.rst

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,21 @@ Python API for interacting with a xyz-hole instance. You know the thing that is
55
blocking Ads by manipulating your DNS requests and run on your single board
66
computer or on other hardware with different operating systems.
77

8-
This module is consuming the details provided by the endpoint ``api.php`` only
9-
for now.
8+
This module supports both v5 and v6 versions of the API through a unified interface.
9+
Simply specify the version when creating your client:
10+
11+
.. code:: python
12+
13+
from hole import Hole
14+
15+
# For v6 (default)
16+
client = Hole("YOUR_API_TOKEN")
17+
18+
# For v5
19+
client_v5 = Hole("YOUR_API_TOKEN", version=5)
20+
21+
This module is consuming the details provided by the endpoint ``api.php`` and other
22+
API endpoints available in v5 and v6.
1023

1124
If you wonder why the weird name and that the usage of xzy-hole instead of the
1225
real name, please contact the trademark holder. They were unhappy with original
@@ -43,13 +56,13 @@ available. The lastest release is usually present in the ``unstable`` channel.
4356
Usage
4457
-----
4558

46-
The file ``example.py`` contains an example about how to use this module.
59+
The files ``examplev5.py`` and ``examplev6.py`` contains examples about how to use this module.
4760

4861
Roadmap
4962
-------
5063

5164
There are more features on the roadmap but there is no ETA because I prefer
52-
to support Open Source projects were third party contributions are appreciated.
65+
to support Open Source projects where third party contributions are appreciated.
5366

5467
License
5568
-------

example.py renamed to examplev5.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
async def main():
1313
"""Get the data from a *hole instance."""
1414
async with aiohttp.ClientSession() as session:
15-
data = Hole("192.168.0.215", session)
15+
data = Hole("192.168.0.215", session, version=5)
1616

1717
await data.get_versions()
1818
print(json.dumps(data.versions, indent=4, sort_keys=True))
@@ -67,4 +67,4 @@ async def enable():
6767
if __name__ == "__main__":
6868
asyncio.run(main())
6969
asyncio.run(disable())
70-
asyncio.run(enable())
70+
asyncio.run(enable())

examplev6.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
"""Example for the usage of the hole module."""
2+
import asyncio
3+
import json
4+
from datetime import datetime
5+
6+
import aiohttp
7+
8+
from hole import Hole
9+
10+
PASSWORD = "your_password_here"
11+
HOST = "your_host_here"
12+
PROTOCOL = "https"
13+
PORT = 443
14+
VERIFY_TLS = False
15+
16+
async def main():
17+
"""Get the data from a Pi-hole instance."""
18+
async with aiohttp.ClientSession() as session:
19+
async with Hole(
20+
host=HOST,
21+
session=session,
22+
password=PASSWORD,
23+
protocol=PROTOCOL,
24+
port=PORT,
25+
verify_tls=VERIFY_TLS
26+
) as pihole:
27+
await pihole.get_data()
28+
29+
print("\n=== Version Information ===")
30+
print(f"Core Version: {pihole.core_current} (Latest: {pihole.core_latest}, Update Available: {pihole.core_update})")
31+
print(f"Web Version: {pihole.web_current} (Latest: {pihole.web_latest}, Update Available: {pihole.web_update})")
32+
print(f"FTL Version: {pihole.ftl_current} (Latest: {pihole.ftl_latest}, Update Available: {pihole.ftl_update})")
33+
34+
print("\n=== Basic Statistics ===")
35+
print(f"Status: {pihole.status}")
36+
print(f"Domains being blocked: {pihole.domains_being_blocked}")
37+
print(f"Total queries today: {pihole.dns_queries_today}")
38+
print(f"Queries blocked today: {pihole.ads_blocked_today}")
39+
print(f"Percentage blocked: {pihole.ads_percentage_today}%")
40+
41+
print("\n=== Client Statistics ===")
42+
print(f"Total clients ever seen: {pihole.clients_ever_seen}")
43+
print(f"Active clients: {pihole.unique_clients}")
44+
45+
print("\n=== Query Statistics ===")
46+
print(f"Queries forwarded: {pihole.queries_forwarded}")
47+
print(f"Queries cached: {pihole.queries_cached}")
48+
print(f"Unique domains: {pihole.unique_domains}")
49+
50+
print("\n=== Top Permitted Domains ===")
51+
for domain in pihole.top_queries:
52+
print(f"{domain['domain']}: {domain['count']} queries")
53+
54+
print("\n=== Top Blocked Domains (Ads) ===")
55+
for domain in pihole.top_ads:
56+
print(f"{domain['domain']}: {domain['count']} queries")
57+
58+
print("\n=== Forward Destinations ===")
59+
for upstream in pihole.forward_destinations:
60+
print(f"Name: {upstream['name']}, IP: {upstream['ip']}, Count: {upstream['count']}")
61+
62+
print("\n=== Reply Types ===")
63+
for reply_type, count in pihole.reply_types.items():
64+
print(f"{reply_type}: {count}")
65+
66+
print("\n=== Raw Data ===")
67+
print(json.dumps({
68+
"data": pihole.data,
69+
"blocked_domains": pihole.blocked_domains,
70+
"permitted_domains": pihole.permitted_domains,
71+
"clients": pihole.clients,
72+
"upstreams": pihole.upstreams,
73+
"blocking_status": pihole.blocking_status,
74+
"versions": pihole.versions
75+
}, indent=4, sort_keys=True))
76+
77+
async def toggle_blocking():
78+
"""Example of enabling and disabling Pi-hole blocking."""
79+
async with aiohttp.ClientSession() as session:
80+
async with Hole(
81+
host=HOST,
82+
session=session,
83+
password=PASSWORD,
84+
protocol=PROTOCOL,
85+
port=PORT,
86+
verify_tls=VERIFY_TLS
87+
) as pihole:
88+
await pihole.get_data()
89+
initial_status = pihole.status
90+
print(f"\nInitial Pi-hole status: {initial_status}")
91+
92+
print("\nDisabling Pi-hole blocking for 60 seconds...")
93+
disable_result = await pihole.disable(duration=60)
94+
95+
await pihole.get_data()
96+
if pihole.status != "disabled":
97+
print(f"ERROR: Failed to disable Pi-hole! Status is still: {pihole.status}")
98+
return
99+
print(f"Successfully disabled Pi-hole. Status: {pihole.status}")
100+
print(f"Disable operation response: {disable_result}")
101+
102+
print("\nWaiting 5 seconds...")
103+
await asyncio.sleep(5)
104+
105+
print("\nEnabling Pi-hole blocking...")
106+
enable_result = await pihole.enable()
107+
108+
await pihole.get_data()
109+
if pihole.status != "enabled":
110+
print(f"ERROR: Failed to enable Pi-hole! Status is still: {pihole.status}")
111+
return
112+
print(f"Successfully enabled Pi-hole. Status: {pihole.status}")
113+
print(f"Enable operation response: {enable_result}")
114+
115+
if pihole.status == initial_status:
116+
print("\nToggle test completed successfully! Pi-hole returned to initial state.")
117+
else:
118+
print(f"\nWARNING: Final status ({pihole.status}) differs from initial status ({initial_status})")
119+
120+
if __name__ == "__main__":
121+
print(f"=== Pi-hole Statistics as of {datetime.now()} ===")
122+
asyncio.run(main())
123+
asyncio.run(toggle_blocking())

0 commit comments

Comments
 (0)