#!/usr/bin/env python3
"""robots.txt が AIクローラー3種類（学習 / 検索 / ユーザー起点）をどう扱っているかを調べる。

使い方:
  python3 check_ai_bots.py example.com            # サイトのrobots.txtを取得して判定
  python3 check_ai_bots.py --file robots.txt      # 手元のファイルを判定
  python3 check_ai_bots.py example.com --path /blog/post-1/   # 見たいページのパスを指定

標準ライブラリだけで動く。判定ルールはGoogleのrobots.txt仕様に合わせている:
  1. そのボットに一致するUser-agentグループのうち、最も具体的な1つだけを使う（*とは合成しない）
  2. グループ内では、パスが長く一致するルールが勝つ。同じ長さならAllow
  3. どのグループにも当たらなければ許可
robots.txtがCDNやWAFで止められているか（IP単位の遮断）は、この判定では分からない。
"""
import re
import sys
import urllib.request
from urllib.parse import urlparse

# (robots.txtのトークン, 種類, 一言)
BOTS = [
    ("GPTBot", "学習", "OpenAI: 基盤モデルの学習"),
    ("ClaudeBot", "学習", "Anthropic: 学習データの収集"),
    ("Google-Extended", "学習", "Google: Gemini学習・グラウンディング用トークン"),
    ("Applebot-Extended", "学習", "Apple: 学習のみを断るトークン"),
    ("Meta-ExternalAgent", "学習", "Meta: 学習・インデックス"),
    ("CCBot", "学習", "Common Crawl: 公開データセット"),
    ("OAI-SearchBot", "検索", "OpenAI: ChatGPT検索に載せる"),
    ("Claude-SearchBot", "検索", "Anthropic: 検索結果の品質向上"),
    ("PerplexityBot", "検索", "Perplexity: 検索結果に載せる"),
    ("Meta-WebIndexer", "検索", "Meta: Meta AIの検索"),
    ("Googlebot", "検索", "Google検索（AI OverviewsもここでOK/NGが決まる）"),
    ("Bingbot", "検索", "Bing（Copilot・ChatGPT検索の下支えになりうる）"),
    ("ChatGPT-User", "ユーザー起点", "OpenAI: ユーザーの操作で開く（robots.txt非適用の場合あり）"),
    ("Claude-User", "ユーザー起点", "Anthropic: ユーザーの質問で開く"),
    ("Perplexity-User", "ユーザー起点", "Perplexity: ユーザーの質問で開く（通常robots.txtを無視）"),
]


def parse(text):
    """robots.txtを [(UAのリスト, [(allow/disallow, パス)])] に分解する。"""
    groups, uas, rules, in_rules = [], [], [], False
    for raw in text.splitlines():
        line = raw.split("#", 1)[0].strip()
        if ":" not in line:
            continue
        key, value = (s.strip() for s in line.split(":", 1))
        key = key.lower()
        if key == "user-agent":
            if in_rules:
                groups.append((uas, rules))
                uas, rules, in_rules = [], [], False
            uas.append(value.lower())
        elif key in ("allow", "disallow"):
            in_rules = True
            rules.append((key, value))
    if uas:
        groups.append((uas, rules))
    return groups


def pick_rules(groups, bot):
    """最も具体的なUA一致のグループ（同じUAのグループは結合）。なければ*。"""
    bot = bot.lower()
    best_len, picked = -1, []
    for uas, rules in groups:
        for ua in uas:
            if ua != "*" and ua in bot and len(ua) >= best_len:
                if len(ua) > best_len:
                    best_len, picked = len(ua), []
                picked += rules
    if best_len >= 0:
        return picked, True
    star = []
    for uas, rules in groups:
        if "*" in uas:
            star += rules
    return star, False


def match_len(pattern, path):
    """パターンが一致すればその長さ、しなければ-1（*と$に対応）。"""
    regex = re.escape(pattern).replace(r"\*", ".*")
    if regex.endswith(r"\$"):
        regex = regex[:-2] + "$"
    return len(pattern) if re.match(regex, path) else -1


def allowed(rules, path):
    best_len, ok = -1, True
    for kind, pattern in rules:
        if kind == "disallow" and pattern == "":
            continue
        n = match_len(pattern, path)
        if n > best_len or (n == best_len and kind == "allow"):
            best_len, ok = n, kind == "allow"
    return ok


def fetch(host):
    url = f"https://{host}/robots.txt"
    req = urllib.request.Request(url, headers={"User-Agent": "ai-bot-check/1.0 (robots.txt reader)"})
    with urllib.request.urlopen(req, timeout=15) as r:
        return r.read().decode("utf-8", "replace")


def main(argv):
    path = "/"
    if "--path" in argv:
        i = argv.index("--path")
        path = argv[i + 1]
        argv = argv[:i] + argv[i + 2:]
    if len(argv) == 2 and argv[0] == "--file":
        text = open(argv[1], encoding="utf-8").read()
    elif len(argv) == 1:
        host = urlparse(argv[0]).netloc or argv[0]
        text = fetch(host)
    else:
        print(__doc__)
        return 1
    groups = parse(text)
    print(f"対象パス: {path}\n")
    print(f"{'種類':　<7}{'ボット':<20}{'結果':　<3}どのルールが効いたか")
    for token, kind, note in BOTS:
        rules, named = pick_rules(groups, token)
        ok = allowed(rules, path)
        via = f"専用グループ({token})" if named else ("User-agent: * のグループ" if rules else "ルールなし")
        print(f"{kind:　<7}{token:<20}{'許可' if ok else '拒否':　<3}{via}")
    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
