#!/usr/bin/env python3
"""公開したページを、IndexNow で検索エンジン（Bing など。ChatGPT の検索は Bing を土台にしている）にすぐ知らせる（2026-10-28）。

使い方（.github/workflows/indexnow.yml が main への push のたびに動かす）
  python3 tools/indexnow.py <前の commit> <今の commit>   … その間に増えた・変わったページだけ知らせる
  python3 tools/indexnow.py --all                         … 公開中のサイトマップの全ページを知らせる（最初の1回・まとめて出し直すとき）
  どちらも --dry-run を付けると、送らずに一覧だけ出す

鍵は public/{KEY}.txt に置いた公開の文字列（IndexNow の決まりで、誰でも見られる場所に置く。秘密ではない）。
"""
import json, re, subprocess, sys, urllib.request, xml.etree.ElementTree as ET

SITE = 'https://pixelstomotors.com'
HOST = 'pixelstomotors.com'
KEY = '<INDEXNOW_KEY>'
ENDPOINT = 'https://api.indexnow.org/indexnow'
# 名乗りがないと Cloudflare の守り・IndexNow に断られる（python の既定の名乗りは 403 になった）
UA = {'User-Agent': 'p2m-indexnow/1.0 (+https://pixelstomotors.com)'}


def changed_files(a, b):
    out = subprocess.run(['git', 'diff', '--name-only', '--diff-filter=AM', a, b], capture_output=True, text=True, check=True).stdout
    return [l for l in out.splitlines() if l]


def front_slug(path):
    try:
        text = open(path, encoding='utf-8').read()
    except OSError:
        return None
    m = re.search(r'^slug:\s*"?([\w.-]+)"?\s*$', text, re.M)
    return m.group(1) if m else None


def urls_from_diff(a, b):
    urls, record_changed = set(), False
    for f in changed_files(a, b):
        if f.startswith('src/content/records/') and f.endswith('.md'):
            s = front_slug(f)
            if s: urls.add(f'{SITE}/record/{s}'); record_changed = True
        elif f.startswith('src/data/records/') and f.endswith('.json'):
            try: s = json.load(open(f, encoding='utf-8')).get('slug')
            except (OSError, ValueError): s = None
            if s: urls.add(f'{SITE}/record/{s}'); record_changed = True
        elif re.fullmatch(r'src/pages/[\w-]+\.astro', f):
            name = f.split('/')[-1][:-6]
            urls.add(SITE + ('/' if name == 'index' else f'/{name}'))
        elif f.startswith('src/data/issues/'):
            urls.add(f'{SITE}/issues')
    if record_changed:
        urls.update({SITE + '/', SITE + '/llms.txt'})
    return sorted(urls)


def urls_from_sitemap():
    with urllib.request.urlopen(urllib.request.Request(SITE + '/sitemap-0.xml', headers=UA), timeout=30) as r:
        root = ET.fromstring(r.read())
    ns = {'s': 'http://www.sitemaps.org/schemas/sitemap/0.9'}
    return [e.text.strip() for e in root.findall('.//s:loc', ns)] + [SITE + '/llms.txt']


def submit(urls):
    body = json.dumps({'host': HOST, 'key': KEY, 'keyLocation': f'{SITE}/{KEY}.txt', 'urlList': urls[:10000]}).encode()
    req = urllib.request.Request(ENDPOINT, data=body, headers={'Content-Type': 'application/json; charset=utf-8', **UA}, method='POST')
    try:
        with urllib.request.urlopen(req, timeout=30) as r:
            return r.status, ''
    except urllib.error.HTTPError as e:
        return e.code, e.read()[:300].decode('utf-8', 'replace')


def main():
    args = [a for a in sys.argv[1:] if a != '--dry-run']
    dry = '--dry-run' in sys.argv
    urls = urls_from_sitemap() if args[:1] == ['--all'] else urls_from_diff(args[0], args[1])
    print(f'{len(urls)} 件')
    for u in urls[:50]: print(' ', u)
    if not urls or dry:
        return
    status, detail = submit(urls)
    # 200＝受け付けた／202＝受け付けたが鍵の確認待ち。それ以外は失敗（最初の数回は SiteVerificationNotCompleted の 403 が返ることがある）
    print('IndexNow の返事:', status, detail)
    if status not in (200, 202):
        sys.exit(1)


if __name__ == '__main__':
    main()
