ipok 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ipok-0.1.0/.gitignore +19 -0
- ipok-0.1.0/LICENSE +21 -0
- ipok-0.1.0/PKG-INFO +132 -0
- ipok-0.1.0/README.md +110 -0
- ipok-0.1.0/pyproject.toml +41 -0
- ipok-0.1.0/src/ipok/__init__.py +3 -0
- ipok-0.1.0/src/ipok/__main__.py +4 -0
- ipok-0.1.0/src/ipok/cli.py +167 -0
ipok-0.1.0/.gitignore
ADDED
ipok-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 IPOK (ipok.io)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ipok-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ipok
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Terminal IP purity / risk / AI-availability check — powered by ipok.io
|
|
5
|
+
Project-URL: Homepage, https://ipok.io
|
|
6
|
+
Project-URL: Repository, https://github.com/szp2005/ipok-cli
|
|
7
|
+
Author: ipok.io
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: cli,fraud,geoip,ip,ip-purity,ip-quality,ip-reputation,proxy-detection,risk-score,vpn-detection
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Internet
|
|
18
|
+
Classifier: Topic :: Security
|
|
19
|
+
Classifier: Topic :: System :: Networking
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# IPOK CLI
|
|
24
|
+
|
|
25
|
+
[](https://ipok.io/?ip=1.1.1.1)
|
|
26
|
+
|
|
27
|
+
> Terminal IP **purity / risk / AI-availability** check — powered by [ipok.io](https://ipok.io)
|
|
28
|
+
|
|
29
|
+
Check any IP's quality from your VPS or terminal in one line: risk score, residential vs datacenter, **native IP**, whether it can use **ChatGPT / Claude / Gemini**, use-case fit for **TikTok / e-commerce / social / AI**, and more.
|
|
30
|
+
|
|
31
|
+
一行命令测 IP 纯净度 / 风险值 / 能不能用 AI —— 由 [ipok.io](https://ipok.io) 提供数据。
|
|
32
|
+
|
|
33
|
+
> 🧩 Prefer a browser? Get the **[IPOK Chrome extension](https://chromewebstore.google.com/detail/jnbkfmgldcchpdgnegafakbcnmkdhiai)** — one click to check your current IP's purity. ·
|
|
34
|
+
> 想用浏览器?装 **[IPOK Chrome 插件](https://chromewebstore.google.com/detail/jnbkfmgldcchpdgnegafakbcnmkdhiai)**,一键查当前 IP 纯净度。
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
IPOK ip check ipok.io
|
|
38
|
+
----------------------------------------------
|
|
39
|
+
IP 1.1.1.1 IPv4
|
|
40
|
+
Location Australia / South Brisbane
|
|
41
|
+
ASN AS13335 CLOUDFLARENET
|
|
42
|
+
Type business native: broadcast
|
|
43
|
+
Risk 11/100 Pristine
|
|
44
|
+
Signals hosting
|
|
45
|
+
AI ChatGPT:OK Claude:OK Gemini:OK
|
|
46
|
+
Use-case tiktok ** ecommerce ** social ** ai *****
|
|
47
|
+
Sources ip-api=55, Scamalytics=0, proxycheck=0, AbuseIPDB=0, ipapi.is=30, StopForumSpam=0
|
|
48
|
+
----------------------------------------------
|
|
49
|
+
full report: https://ipok.io/?ip=1.1.1.1
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
Pick whichever fits your stack — all give you the same `ipok` command:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Node (no install, run once)
|
|
58
|
+
npx ipok-cli # check this machine's egress IP
|
|
59
|
+
npx ipok-cli 1.1.1.1 # check a specific IP
|
|
60
|
+
|
|
61
|
+
# Python
|
|
62
|
+
pip install ipok # then: ipok / ipok 1.1.1.1
|
|
63
|
+
pipx run ipok 1.1.1.1 # zero-install one-off
|
|
64
|
+
|
|
65
|
+
# Homebrew (macOS / Linux)
|
|
66
|
+
brew install szp2005/ipok/ipok
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Usage
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
ipok # check this machine's egress IP
|
|
73
|
+
ipok 1.1.1.1 # check a specific IP
|
|
74
|
+
ipok --json 1.1.1.1 # raw JSON
|
|
75
|
+
ipok --help
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Set `IPOK_API` to point at a self-hosted endpoint; set `NO_COLOR=1` to disable colors.
|
|
79
|
+
|
|
80
|
+
### No-install one-liner (servers / CI)
|
|
81
|
+
|
|
82
|
+
Just `curl` + `python3`, nothing to install — ideal on a bare VPS:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
bash <(curl -sL https://raw.githubusercontent.com/szp2005/ipok-cli/main/ipok.sh) # this server's IP
|
|
86
|
+
bash <(curl -sL https://raw.githubusercontent.com/szp2005/ipok-cli/main/ipok.sh) 1.1.1.1 # a specific IP
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Streaming / AI unlock (run on the server)
|
|
90
|
+
|
|
91
|
+
Test what **this server's exit IP** can unlock — Netflix (full / originals-only / blocked + region), ChatGPT region support, YouTube Premium, TikTok:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
bash <(curl -sL https://raw.githubusercontent.com/szp2005/ipok-cli/main/media.sh)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Results reflect the server's outbound IP (the proxy/VPS use-case). Streaming providers change endpoints often, so treat results as best-effort.
|
|
98
|
+
|
|
99
|
+
## Why
|
|
100
|
+
|
|
101
|
+
Most IP-purity tools give a single black-box score. IPOK aggregates multiple risk sources and shows **why** an IP is flagged, plus AI-service availability and use-case fit — the stuff that actually matters for proxies, cross-border, and AI accounts.
|
|
102
|
+
|
|
103
|
+
## Free API (no auth, CORS-enabled)
|
|
104
|
+
|
|
105
|
+
The CLI just calls IPOK's public API. You can too:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
curl "https://ipok.io/api/ip?ip=1.1.1.1" # full IP report (JSON)
|
|
109
|
+
curl "https://ipok.io/api/bgp?asn=AS13335&ip=1.1.1.1" # BGP upstreams/downstreams (RIPEstat)
|
|
110
|
+
curl "https://ipok.io/api/reverse-ip?ip=1.1.1.1" # domains hosted on the IP
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Docs: <https://ipok.io/developers>
|
|
114
|
+
|
|
115
|
+
### Response shape (`/api/ip`, excerpt)
|
|
116
|
+
|
|
117
|
+
```jsonc
|
|
118
|
+
{
|
|
119
|
+
"geo": { "ip", "version", "country", "city", "asn", "asName", "isp", "lat", "lon" },
|
|
120
|
+
"ipType": "residential | hosting | mobile",
|
|
121
|
+
"risk": 0,
|
|
122
|
+
"nativeType": "native | broadcast | unknown",
|
|
123
|
+
"scenarios": [{ "key": "tiktok", "stars": 3, "verdict": "try" }],
|
|
124
|
+
"services": [{ "key": "chatgpt", "status": "available" }],
|
|
125
|
+
"sources": [{ "source": "ip-api", "risk": 10, "flags": {} }],
|
|
126
|
+
"rdap": { "registry", "country", "registered", "org" }
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
MIT — see [LICENSE](./LICENSE). Not affiliated with any provider; data is best-effort and for diagnostics only.
|
ipok-0.1.0/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# IPOK CLI
|
|
2
|
+
|
|
3
|
+
[](https://ipok.io/?ip=1.1.1.1)
|
|
4
|
+
|
|
5
|
+
> Terminal IP **purity / risk / AI-availability** check — powered by [ipok.io](https://ipok.io)
|
|
6
|
+
|
|
7
|
+
Check any IP's quality from your VPS or terminal in one line: risk score, residential vs datacenter, **native IP**, whether it can use **ChatGPT / Claude / Gemini**, use-case fit for **TikTok / e-commerce / social / AI**, and more.
|
|
8
|
+
|
|
9
|
+
一行命令测 IP 纯净度 / 风险值 / 能不能用 AI —— 由 [ipok.io](https://ipok.io) 提供数据。
|
|
10
|
+
|
|
11
|
+
> 🧩 Prefer a browser? Get the **[IPOK Chrome extension](https://chromewebstore.google.com/detail/jnbkfmgldcchpdgnegafakbcnmkdhiai)** — one click to check your current IP's purity. ·
|
|
12
|
+
> 想用浏览器?装 **[IPOK Chrome 插件](https://chromewebstore.google.com/detail/jnbkfmgldcchpdgnegafakbcnmkdhiai)**,一键查当前 IP 纯净度。
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
IPOK ip check ipok.io
|
|
16
|
+
----------------------------------------------
|
|
17
|
+
IP 1.1.1.1 IPv4
|
|
18
|
+
Location Australia / South Brisbane
|
|
19
|
+
ASN AS13335 CLOUDFLARENET
|
|
20
|
+
Type business native: broadcast
|
|
21
|
+
Risk 11/100 Pristine
|
|
22
|
+
Signals hosting
|
|
23
|
+
AI ChatGPT:OK Claude:OK Gemini:OK
|
|
24
|
+
Use-case tiktok ** ecommerce ** social ** ai *****
|
|
25
|
+
Sources ip-api=55, Scamalytics=0, proxycheck=0, AbuseIPDB=0, ipapi.is=30, StopForumSpam=0
|
|
26
|
+
----------------------------------------------
|
|
27
|
+
full report: https://ipok.io/?ip=1.1.1.1
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
Pick whichever fits your stack — all give you the same `ipok` command:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Node (no install, run once)
|
|
36
|
+
npx ipok-cli # check this machine's egress IP
|
|
37
|
+
npx ipok-cli 1.1.1.1 # check a specific IP
|
|
38
|
+
|
|
39
|
+
# Python
|
|
40
|
+
pip install ipok # then: ipok / ipok 1.1.1.1
|
|
41
|
+
pipx run ipok 1.1.1.1 # zero-install one-off
|
|
42
|
+
|
|
43
|
+
# Homebrew (macOS / Linux)
|
|
44
|
+
brew install szp2005/ipok/ipok
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Usage
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
ipok # check this machine's egress IP
|
|
51
|
+
ipok 1.1.1.1 # check a specific IP
|
|
52
|
+
ipok --json 1.1.1.1 # raw JSON
|
|
53
|
+
ipok --help
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Set `IPOK_API` to point at a self-hosted endpoint; set `NO_COLOR=1` to disable colors.
|
|
57
|
+
|
|
58
|
+
### No-install one-liner (servers / CI)
|
|
59
|
+
|
|
60
|
+
Just `curl` + `python3`, nothing to install — ideal on a bare VPS:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
bash <(curl -sL https://raw.githubusercontent.com/szp2005/ipok-cli/main/ipok.sh) # this server's IP
|
|
64
|
+
bash <(curl -sL https://raw.githubusercontent.com/szp2005/ipok-cli/main/ipok.sh) 1.1.1.1 # a specific IP
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Streaming / AI unlock (run on the server)
|
|
68
|
+
|
|
69
|
+
Test what **this server's exit IP** can unlock — Netflix (full / originals-only / blocked + region), ChatGPT region support, YouTube Premium, TikTok:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
bash <(curl -sL https://raw.githubusercontent.com/szp2005/ipok-cli/main/media.sh)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Results reflect the server's outbound IP (the proxy/VPS use-case). Streaming providers change endpoints often, so treat results as best-effort.
|
|
76
|
+
|
|
77
|
+
## Why
|
|
78
|
+
|
|
79
|
+
Most IP-purity tools give a single black-box score. IPOK aggregates multiple risk sources and shows **why** an IP is flagged, plus AI-service availability and use-case fit — the stuff that actually matters for proxies, cross-border, and AI accounts.
|
|
80
|
+
|
|
81
|
+
## Free API (no auth, CORS-enabled)
|
|
82
|
+
|
|
83
|
+
The CLI just calls IPOK's public API. You can too:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
curl "https://ipok.io/api/ip?ip=1.1.1.1" # full IP report (JSON)
|
|
87
|
+
curl "https://ipok.io/api/bgp?asn=AS13335&ip=1.1.1.1" # BGP upstreams/downstreams (RIPEstat)
|
|
88
|
+
curl "https://ipok.io/api/reverse-ip?ip=1.1.1.1" # domains hosted on the IP
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Docs: <https://ipok.io/developers>
|
|
92
|
+
|
|
93
|
+
### Response shape (`/api/ip`, excerpt)
|
|
94
|
+
|
|
95
|
+
```jsonc
|
|
96
|
+
{
|
|
97
|
+
"geo": { "ip", "version", "country", "city", "asn", "asName", "isp", "lat", "lon" },
|
|
98
|
+
"ipType": "residential | hosting | mobile",
|
|
99
|
+
"risk": 0,
|
|
100
|
+
"nativeType": "native | broadcast | unknown",
|
|
101
|
+
"scenarios": [{ "key": "tiktok", "stars": 3, "verdict": "try" }],
|
|
102
|
+
"services": [{ "key": "chatgpt", "status": "available" }],
|
|
103
|
+
"sources": [{ "source": "ip-api", "risk": 10, "flags": {} }],
|
|
104
|
+
"rdap": { "registry", "country", "registered", "org" }
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
MIT — see [LICENSE](./LICENSE). Not affiliated with any provider; data is best-effort and for diagnostics only.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ipok"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Terminal IP purity / risk / AI-availability check — powered by ipok.io"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "ipok.io" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"ip", "ip-reputation", "ip-quality", "fraud", "risk-score",
|
|
15
|
+
"proxy-detection", "vpn-detection", "ip-purity", "geoip", "cli",
|
|
16
|
+
]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Environment :: Console",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Intended Audience :: System Administrators",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Topic :: Internet",
|
|
25
|
+
"Topic :: Security",
|
|
26
|
+
"Topic :: System :: Networking",
|
|
27
|
+
]
|
|
28
|
+
dependencies = []
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://ipok.io"
|
|
32
|
+
Repository = "https://github.com/szp2005/ipok-cli"
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
ipok = "ipok.cli:main"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel]
|
|
38
|
+
packages = ["src/ipok"]
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.sdist]
|
|
41
|
+
include = ["src/ipok", "README.md", "LICENSE"]
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""IPOK CLI — terminal IP purity / risk / AI-availability check.
|
|
2
|
+
|
|
3
|
+
Powered by https://ipok.io (free, no-auth API). Zero third-party deps.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import urllib.error
|
|
11
|
+
import urllib.parse
|
|
12
|
+
import urllib.request
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|
|
15
|
+
|
|
16
|
+
API_DEFAULT = "https://ipok.io"
|
|
17
|
+
TIMEOUT = 25
|
|
18
|
+
|
|
19
|
+
# ---- ANSI helpers -----------------------------------------------------------
|
|
20
|
+
_NO_COLOR = bool(os.environ.get("NO_COLOR")) or not sys.stdout.isatty()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class C:
|
|
24
|
+
R = "" if _NO_COLOR else "\033[0m"
|
|
25
|
+
DIM = "" if _NO_COLOR else "\033[2m"
|
|
26
|
+
B = "" if _NO_COLOR else "\033[1m"
|
|
27
|
+
GREEN = "" if _NO_COLOR else "\033[32m"
|
|
28
|
+
YEL = "" if _NO_COLOR else "\033[33m"
|
|
29
|
+
RED = "" if _NO_COLOR else "\033[31m"
|
|
30
|
+
CYAN = "" if _NO_COLOR else "\033[36m"
|
|
31
|
+
GRAY = "" if _NO_COLOR else "\033[90m"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
HELP = f"""IPOK CLI v{__version__} — IP purity / risk / AI-availability check (ipok.io)
|
|
35
|
+
|
|
36
|
+
USAGE:
|
|
37
|
+
ipok check this machine's egress IP
|
|
38
|
+
ipok <ip> check a specific IP (e.g. ipok 1.1.1.1)
|
|
39
|
+
ipok --json [ip] print raw JSON from the API
|
|
40
|
+
ipok -h, --help show this help
|
|
41
|
+
ipok -v, --version show version
|
|
42
|
+
|
|
43
|
+
ENV:
|
|
44
|
+
IPOK_API override API base (default https://ipok.io)
|
|
45
|
+
NO_COLOR disable colored output
|
|
46
|
+
|
|
47
|
+
Data by https://ipok.io — free, no API key required.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def fetch(ip: str | None, api: str) -> dict:
|
|
52
|
+
url = api.rstrip("/") + "/api/ip"
|
|
53
|
+
if ip:
|
|
54
|
+
url += "?" + urllib.parse.urlencode({"ip": ip})
|
|
55
|
+
req = urllib.request.Request(url, headers={"User-Agent": f"ipok-cli/{__version__}"})
|
|
56
|
+
with urllib.request.urlopen(req, timeout=TIMEOUT) as resp: # noqa: S310 (trusted host)
|
|
57
|
+
return json.load(resp)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _band(risk: int) -> tuple[str, str]:
|
|
61
|
+
if risk < 15:
|
|
62
|
+
return C.GREEN, "Pristine"
|
|
63
|
+
if risk < 50:
|
|
64
|
+
return C.GREEN, "Clean"
|
|
65
|
+
if risk < 70:
|
|
66
|
+
return C.YEL, "Caution"
|
|
67
|
+
return C.RED, "High risk"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def render(d: dict) -> str:
|
|
71
|
+
g = d.get("geo", {}) or {}
|
|
72
|
+
risk = int(d.get("risk", 0) or 0)
|
|
73
|
+
bc, band = _band(risk)
|
|
74
|
+
out: list[str] = []
|
|
75
|
+
|
|
76
|
+
def row(k: str, v: str) -> None:
|
|
77
|
+
out.append(" " + C.GRAY + k.ljust(14) + C.R + " " + v)
|
|
78
|
+
|
|
79
|
+
out.append("")
|
|
80
|
+
out.append(" " + C.CYAN + "IPOK" + C.R + C.GRAY + " ip check " + C.R + C.DIM + "ipok.io" + C.R)
|
|
81
|
+
out.append(" " + C.GRAY + ("-" * 46) + C.R)
|
|
82
|
+
row("IP", C.B + str(g.get("ip", "-")) + C.R + C.GRAY + " IPv" + str(g.get("version", "?")) + C.R)
|
|
83
|
+
loc = " / ".join([x for x in [g.get("country"), g.get("city")] if x]) or "-"
|
|
84
|
+
row("Location", loc)
|
|
85
|
+
row("ASN", str(g.get("asn", "-")) + " " + str(g.get("asName") or g.get("isp") or ""))
|
|
86
|
+
row("Type", str(d.get("ipType", "-")) + C.GRAY + " native: " + str(d.get("nativeType", "-")) + C.R)
|
|
87
|
+
row("Risk", bc + str(risk) + "/100 " + band + C.R)
|
|
88
|
+
|
|
89
|
+
sig = d.get("signals") or []
|
|
90
|
+
if sig:
|
|
91
|
+
row("Signals", C.YEL + ", ".join(map(str, sig)) + C.R)
|
|
92
|
+
|
|
93
|
+
svc = d.get("services") or []
|
|
94
|
+
if svc:
|
|
95
|
+
parts = []
|
|
96
|
+
for s in svc:
|
|
97
|
+
st = s.get("status")
|
|
98
|
+
mark = (
|
|
99
|
+
C.GREEN + "OK" + C.R if st == "available"
|
|
100
|
+
else (C.RED + "NO" + C.R if st == "blocked" else C.GRAY + "?" + C.R)
|
|
101
|
+
)
|
|
102
|
+
parts.append(str(s.get("name")) + ":" + mark)
|
|
103
|
+
row("AI", " ".join(parts))
|
|
104
|
+
|
|
105
|
+
sc = d.get("scenarios") or []
|
|
106
|
+
if sc:
|
|
107
|
+
parts = []
|
|
108
|
+
for s in sc[:6]:
|
|
109
|
+
stars = "*" * int(s.get("stars", 0) or 0)
|
|
110
|
+
parts.append(str(s.get("key")) + " " + C.YEL + stars.ljust(5) + C.R)
|
|
111
|
+
row("Use-case", " ".join(parts))
|
|
112
|
+
|
|
113
|
+
srcs = [s for s in (d.get("sources") or []) if s.get("ok")]
|
|
114
|
+
if srcs:
|
|
115
|
+
row("Sources", C.DIM + ", ".join(
|
|
116
|
+
str(s.get("source")) + "=" + str(s.get("risk", "-")) for s in srcs) + C.R)
|
|
117
|
+
|
|
118
|
+
out.append(" " + C.GRAY + ("-" * 46) + C.R)
|
|
119
|
+
ipv = g.get("ip", "")
|
|
120
|
+
link = ("https://ipok.io/?ip=" + str(ipv)) if ipv else "https://ipok.io"
|
|
121
|
+
out.append(" " + C.CYAN + "full report: " + link + C.R)
|
|
122
|
+
out.append("")
|
|
123
|
+
return "\n".join(out)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def main(argv: list[str] | None = None) -> int:
|
|
127
|
+
args = list(sys.argv[1:] if argv is None else argv)
|
|
128
|
+
as_json = False
|
|
129
|
+
ip: str | None = None
|
|
130
|
+
|
|
131
|
+
for a in args:
|
|
132
|
+
if a in ("-h", "--help"):
|
|
133
|
+
print(HELP)
|
|
134
|
+
return 0
|
|
135
|
+
if a in ("-v", "--version"):
|
|
136
|
+
print(f"ipok {__version__}")
|
|
137
|
+
return 0
|
|
138
|
+
if a == "--json":
|
|
139
|
+
as_json = True
|
|
140
|
+
elif a.startswith("-"):
|
|
141
|
+
sys.stderr.write(f"ipok: unknown option {a}\n")
|
|
142
|
+
return 2
|
|
143
|
+
else:
|
|
144
|
+
ip = a
|
|
145
|
+
|
|
146
|
+
api = os.environ.get("IPOK_API", API_DEFAULT)
|
|
147
|
+
try:
|
|
148
|
+
data = fetch(ip, api)
|
|
149
|
+
except urllib.error.HTTPError as e:
|
|
150
|
+
sys.stderr.write(f"IPOK: request failed (HTTP {e.code})\n")
|
|
151
|
+
return 1
|
|
152
|
+
except (urllib.error.URLError, TimeoutError) as e:
|
|
153
|
+
sys.stderr.write(f"IPOK: request failed ({e})\n")
|
|
154
|
+
return 1
|
|
155
|
+
except Exception as e: # noqa: BLE001
|
|
156
|
+
sys.stderr.write(f"IPOK: {e}\n")
|
|
157
|
+
return 1
|
|
158
|
+
|
|
159
|
+
if as_json:
|
|
160
|
+
print(json.dumps(data, ensure_ascii=False, indent=2))
|
|
161
|
+
else:
|
|
162
|
+
print(render(data))
|
|
163
|
+
return 0
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
if __name__ == "__main__":
|
|
167
|
+
raise SystemExit(main())
|