Skip to content

Commit 9492a14

Browse files
[投稿] 添加脚本: whois — by PMJFFF
1 parent 9efecd8 commit 9492a14

1 file changed

Lines changed: 345 additions & 0 deletions

File tree

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
import socket
2+
import re
3+
from datetime import datetime, timedelta, timezone
4+
5+
6+
def get_tld(domain):
7+
parts = domain.strip().lower().split(".")
8+
if len(parts) < 2:
9+
return ""
10+
return parts[-1]
11+
12+
13+
def normalize_domain(domain):
14+
domain = domain.strip()
15+
if not domain:
16+
return ""
17+
18+
if "://" in domain:
19+
domain = domain.split("://", 1)[1]
20+
domain = domain.split("/", 1)[0]
21+
domain = domain.split("@", 1)[-1]
22+
23+
if domain.startswith("[") and "]" in domain:
24+
return ""
25+
if ":" in domain:
26+
host, port = domain.rsplit(":", 1)
27+
if re.fullmatch(r"\d+", port):
28+
domain = host
29+
else:
30+
return ""
31+
32+
domain = domain.strip().strip(".").lower()
33+
if not domain:
34+
return ""
35+
36+
try:
37+
domain = domain.encode("idna").decode("ascii")
38+
except UnicodeError:
39+
return ""
40+
41+
if "." not in domain or ".." in domain or len(domain) > 253:
42+
return ""
43+
44+
labels = domain.split(".")
45+
for label in labels:
46+
if not label or len(label) > 63:
47+
return ""
48+
if label.startswith("-") or label.endswith("-"):
49+
return ""
50+
if not re.fullmatch(r"[a-z0-9-]+", label):
51+
return ""
52+
if re.fullmatch(r"\d+", label):
53+
return ""
54+
55+
if re.fullmatch(r"\d{1,3}(?:\.\d{1,3}){3}", domain):
56+
return ""
57+
58+
return domain
59+
60+
61+
def query_whois_server(server, domain):
62+
with socket.create_connection((server, 43), timeout=10) as sock:
63+
sock.sendall((domain + "\r\n").encode("utf-8"))
64+
chunks = []
65+
while True:
66+
data = sock.recv(4096)
67+
if not data:
68+
break
69+
chunks.append(data)
70+
return b"".join(chunks).decode("utf-8", errors="replace")
71+
72+
73+
def find_whois_server(domain):
74+
iana_response = query_whois_server("whois.iana.org", domain)
75+
refer_server = ""
76+
for line in iana_response.splitlines():
77+
line = line.strip()
78+
if not line or ":" not in line:
79+
continue
80+
key, value = line.split(":", 1)
81+
key = key.strip().lower()
82+
value = value.strip()
83+
if key == "whois" and value:
84+
return value
85+
if key == "refer" and value:
86+
refer_server = value
87+
return refer_server
88+
89+
90+
def parse_whois_text(text):
91+
data = {}
92+
dns_list = []
93+
status_list = []
94+
95+
field_map = {
96+
"domain name": "domain",
97+
"domain": "domain",
98+
"registrar": "registrar",
99+
"sponsoring registrar": "registrar",
100+
"registrar organization": "registrar",
101+
"creation date": "creation_date",
102+
"created": "creation_date",
103+
"created on": "creation_date",
104+
"registered on": "creation_date",
105+
"registration time": "creation_date",
106+
"registration date": "creation_date",
107+
"registry expiry date": "expiry_date",
108+
"expiration date": "expiry_date",
109+
"expiry date": "expiry_date",
110+
"expire": "expiry_date",
111+
"expires on": "expiry_date",
112+
"expiration time": "expiry_date",
113+
"paid-till": "expiry_date",
114+
"renewal date": "expiry_date",
115+
"updated date": "updated_date",
116+
"last updated on": "updated_date",
117+
"last modified": "updated_date",
118+
"changed": "updated_date",
119+
"updated date time": "updated_date",
120+
"modification time": "updated_date",
121+
"registrar iana id": "iana_id",
122+
"iana id": "iana_id",
123+
"iana_id": "iana_id",
124+
}
125+
126+
status_keys = {"status", "domain status"}
127+
dns_keys = {"name server", "nserver", "nserver1", "nserver2", "nserver3", "nserver4"}
128+
129+
for raw_line in text.splitlines():
130+
line = raw_line.strip()
131+
if not line or ":" not in line:
132+
continue
133+
134+
key, value = line.split(":", 1)
135+
key = key.strip().lower()
136+
value = value.strip()
137+
138+
if not value:
139+
continue
140+
141+
mapped_key = field_map.get(key)
142+
if mapped_key:
143+
if mapped_key == "domain":
144+
data[mapped_key] = value.lower()
145+
elif mapped_key not in data:
146+
data[mapped_key] = value
147+
continue
148+
149+
if key in status_keys:
150+
status_value = value.split()[0].strip().lower()
151+
if status_value and status_value not in status_list:
152+
status_list.append(status_value)
153+
continue
154+
155+
if key in dns_keys:
156+
dns_value = value.split()[0].strip(".").lower()
157+
if dns_value and dns_value not in dns_list:
158+
dns_list.append(dns_value)
159+
continue
160+
161+
if key == "dnssec" and "dnssec" not in data:
162+
data["dnssec"] = value
163+
164+
data["status_list"] = status_list
165+
data["dns_list"] = dns_list
166+
return data
167+
168+
169+
def parse_datetime(value):
170+
value = value.strip()
171+
if not value:
172+
return None
173+
174+
iso_value = value.replace("Z", "+00:00")
175+
try:
176+
return datetime.fromisoformat(iso_value)
177+
except ValueError:
178+
pass
179+
180+
iso_value = re.sub(r"([+-]\d{2})(\d{2})$", r"\1:\2", iso_value)
181+
try:
182+
return datetime.fromisoformat(iso_value)
183+
except ValueError:
184+
pass
185+
186+
normalized_value = re.sub(r"\s+UTC$", " +0000", value, flags=re.IGNORECASE)
187+
normalized_value = re.sub(r"\s+GMT$", " +0000", normalized_value, flags=re.IGNORECASE)
188+
189+
patterns = [
190+
"%Y-%m-%d %H:%M:%S",
191+
"%Y-%m-%d",
192+
"%d-%b-%Y",
193+
"%d-%b-%Y %H:%M:%S",
194+
"%Y.%m.%d %H:%M:%S",
195+
"%Y/%m/%d %H:%M:%S",
196+
"%Y/%m/%d",
197+
"%Y.%m.%d",
198+
"%Y-%m-%d %H:%M:%S %z",
199+
"%Y.%m.%d %H:%M:%S %z",
200+
"%Y-%m-%dT%H:%M:%S.%f%z",
201+
"%Y-%m-%dT%H:%M:%S%z",
202+
"%a %b %d %H:%M:%S %Y",
203+
]
204+
205+
for pattern in patterns:
206+
try:
207+
return datetime.strptime(normalized_value, pattern)
208+
except ValueError:
209+
pass
210+
return None
211+
212+
213+
def format_datetime_to_utc8(value):
214+
dt = parse_datetime(value)
215+
if not dt:
216+
return value
217+
utc8 = timezone(timedelta(hours=8))
218+
if dt.tzinfo is None:
219+
dt = dt.replace(tzinfo=timezone.utc)
220+
dt = dt.astimezone(utc8)
221+
return dt.strftime("%Y-%m-%d %H:%M:%S UTC+8")
222+
223+
224+
def translate_status(status):
225+
status_map = {
226+
"ok": "正常",
227+
"inactive": "未激活",
228+
"active": "已激活",
229+
"clientdeleteprohibited": "客户端禁止删除",
230+
"clienthold": "客户端暂停解析",
231+
"clientrenewprohibited": "客户端禁止续费",
232+
"clienttransferprohibited": "客户端禁止转移",
233+
"clientupdateprohibited": "客户端禁止更新",
234+
"pendingcreate": "创建处理中",
235+
"pendingdelete": "删除处理中",
236+
"pendingrenew": "续费处理中",
237+
"pendingrestore": "恢复处理中",
238+
"pendingtransfer": "转移处理中",
239+
"pendingupdate": "更新处理中",
240+
"serverdeleteprohibited": "注册局禁止删除",
241+
"serverhold": "注册局暂停解析",
242+
"serverrenewprohibited": "注册局禁止续费",
243+
"servertransferprohibited": "注册局禁止转移",
244+
"serverupdateprohibited": "注册局禁止更新",
245+
}
246+
return status_map.get(status.strip().lower(), status)
247+
248+
249+
def get_domain_age_text(creation_date_str):
250+
dt = parse_datetime(creation_date_str)
251+
if not dt:
252+
return ""
253+
if dt.tzinfo is None:
254+
dt = dt.replace(tzinfo=timezone.utc)
255+
now = datetime.now(timezone.utc)
256+
years = int((now - dt).days / 365.2425)
257+
if years > 0:
258+
return f"{years}年老米"
259+
return ""
260+
261+
262+
def normalize_dnssec(value):
263+
text = value.strip().lower()
264+
if not text or text in ("unsigned", "unsigned delegation", "no", "false", "inactive"):
265+
return "未配置"
266+
if text in ("signed", "yes", "true", "active"):
267+
return "已配置"
268+
return value
269+
270+
271+
def print_row(label, value):
272+
print(f"{label}: {value}")
273+
274+
275+
def get_whois_server(domain):
276+
whois_server = find_whois_server(domain)
277+
if whois_server:
278+
return whois_server
279+
280+
tld = get_tld(domain)
281+
if not tld:
282+
return ""
283+
284+
return f"whois.nic.{tld}"
285+
286+
287+
def main():
288+
domain = input("请输入你的域名: ").strip()
289+
if not domain:
290+
print("域名格式无效")
291+
return
292+
293+
domain = normalize_domain(domain)
294+
if not domain:
295+
print("域名格式无效")
296+
return
297+
298+
try:
299+
whois_server = get_whois_server(domain)
300+
if not whois_server:
301+
print("无法识别域名后缀")
302+
return
303+
304+
result = query_whois_server(whois_server, domain)
305+
info = parse_whois_text(result)
306+
307+
display_domain = info.get("domain", domain)
308+
age_text = get_domain_age_text(info.get("creation_date", ""))
309+
if age_text:
310+
print_row("域名", f"{display_domain} {age_text}")
311+
else:
312+
print_row("域名", display_domain)
313+
314+
print_row("注册商", info.get("registrar", "未知"))
315+
316+
creation_date = info.get("creation_date")
317+
expiry_date = info.get("expiry_date")
318+
updated_date = info.get("updated_date")
319+
320+
print_row("注册日", format_datetime_to_utc8(creation_date) if creation_date else "未知")
321+
print_row("过期日", format_datetime_to_utc8(expiry_date) if expiry_date else "未知")
322+
323+
if updated_date:
324+
print_row("更新日", format_datetime_to_utc8(updated_date))
325+
326+
if info.get("iana_id"):
327+
print_row("IANA_ID", info["iana_id"])
328+
329+
if info.get("status_list"):
330+
print_row("状态", " ".join(translate_status(s) for s in info["status_list"]))
331+
else:
332+
print_row("状态", "未知")
333+
334+
if info.get("dns_list"):
335+
print_row("DNS", " ".join(info["dns_list"]))
336+
else:
337+
print_row("DNS", "未知")
338+
339+
print_row("DNSSEC", normalize_dnssec(info.get("dnssec", "未配置")))
340+
except Exception as e:
341+
print(f"查询失败: {e}")
342+
343+
344+
if __name__ == "__main__":
345+
main()

0 commit comments

Comments
 (0)