# https://t.me/ZGQinc

import json
import os
import sys
import time
from playwright.sync_api import sync_playwright, TimeoutError, Page

MAX_RETRIES = 5
OUTPUT_DIR = "link"
TASK_JSON = "favourites.json"

if not os.path.exists(OUTPUT_DIR):
    os.makedirs(OUTPUT_DIR)

def wait_for_user_input(msg=""):
    print(f"\n{'!'*10}\n{msg}\n>>> 请修复网络问题，然后按回车键重试...\n{'!'*10}", flush=True)
    sys.stdin.readline()
    print("继续...", flush=True)

def extract_discord_links(page: Page):
    try:
        page.wait_for_selector("li[id^='message-']", timeout=5000)
    except:
        pass

    links = page.evaluate('''() => {
        const anchors = Array.from(document.querySelectorAll("a[href*='/data/']"));
        return anchors.map(a => a.href);
    }''')
    
    return list(set(links))

def process_discord_channel(context, channel_url, file_handle):
    page = context.new_page()
    offset = 0
    channel_id = channel_url.split('/')[-1]
    
    print(f"[Discord] 开始处理频道: {channel_id}")

    while True:
        current_url = f"{channel_url}?o={offset}"
        
        attempt = 0
        while True:
            try:
                page.goto(current_url, wait_until="domcontentloaded", timeout=5000)
                try:
                    page.wait_for_selector("footer", timeout=5000)
                except:
                    pass
                break 
            except Exception as e:
                print(f"    [Error] 加载频道页失败 (尝试 {attempt+1}/{MAX_RETRIES}): {e}")
                if attempt >= MAX_RETRIES:
                    wait_for_user_input(f"Discord 频道 {channel_id} (offset {offset}) 加载失败。")
                    attempt = 0 
                else:
                    attempt += 1
                    time.sleep(2)
        
        links = extract_discord_links(page)
        if links:
            print(f"[Discord] 频道 {channel_id} (offset {offset}) 找到 {len(links)} 个链接")
            for link in links:
                file_handle.write(link + "\n")
                file_handle.flush()
        else:
            print(f"[Discord] 频道 {channel_id} (offset {offset}) 无新链接")

        msg_count = page.locator("li[id^='message-']").count()
        if msg_count == 0:
            print(f"[Discord] 频道 {channel_id} 处理完毕 (无消息)。")
            break

        if len(links) == 0:
            print(f"[Discord] 频道 {channel_id} 处理完毕 (无链接提取)。")
            break

        offset += 50
    
    page.close()

def process_standard_artist(context, service, artist_id, file_handle):
    page = context.new_page()
    base_url = f"https://kemono.cr/{service}/user/{artist_id}"
    offset = 0
    
    while True:
        page_url = f"{base_url}?o={offset}"
        print(f"[{service}_{artist_id}] 处理列表页: offset {offset}")

        attempt = 0
        while True:
            try:
                page.goto(page_url, wait_until="domcontentloaded", timeout=5000)
                break
            except Exception as e:
                print(f"    [Error] 加载列表页失败 (尝试 {attempt+1}/{MAX_RETRIES}): {e}")
                if attempt >= MAX_RETRIES:
                    wait_for_user_input(f"列表页加载失败: {page_url}")
                    attempt = 0 
                else:
                    attempt += 1
                    time.sleep(2)

        post_card_selector = "div.post-card__image-container"
        try:
            page.wait_for_selector(post_card_selector, timeout=5000)
        except:
            print(f"[{service}_{artist_id}] 本页无帖子，结束。")
            break

        post_cards = page.locator(post_card_selector).all()
        if not post_cards:
            break
            
        print(f"[{service}_{artist_id}] 本页发现 {len(post_cards)} 个帖子")

        post_hrefs = []
        for card in post_cards:
            href = card.evaluate("el => el.parentElement.getAttribute('href')")
            if href:
                post_hrefs.append(f"https://kemono.cr{href}")
        
        for post_link in post_hrefs:
            attempt = 0
            while True:
                try:
                    page.goto(post_link, wait_until="domcontentloaded", timeout=5000)
                    
                    try:
                        page.wait_for_selector("div.post__content", timeout=5000)
                    except:
                        pass

                    links = page.evaluate('''() => {
                        const thumbs = Array.from(document.querySelectorAll("div.post__thumbnail a.fileThumb"));
                        const attachments = Array.from(document.querySelectorAll("a.post__attachment-link"));
                        const contentImgs = Array.from(document.querySelectorAll("div.post__content img"));
                        
                        let urls = [];
                        urls.push(...thumbs.map(a => a.href));
                        urls.push(...attachments.map(a => a.href));
                        urls.push(...contentImgs.map(img => img.src));
                        
                        return urls;
                    }''')
                    
                    valid_links = [l for l in links if l and "http" in l]
                    
                    if valid_links:
                        for vl in valid_links:
                            file_handle.write(vl + "\n")
                        file_handle.flush()
                    
                    break 

                except Exception as e:
                    print(f"    [Error] 帖子处理失败 (尝试 {attempt+1}/{MAX_RETRIES}): {e}")
                    
                    if attempt >= MAX_RETRIES:
                        wait_for_user_input(f"帖子处理失败: {post_link}")
                        attempt = 0 
                    else:
                        attempt += 1
                        time.sleep(2)

        offset += 50
    
    page.close()

def process_discord_server(context, server_id, file_handle):
    base_url = f"https://kemono.cr/discord/server/{server_id}"
    print(f"[Discord_{server_id}] 正在分析服务器结构...")
    
    page = context.new_page()
    
    channels = []
    
    attempt = 0
    while True:
        try:
            page.goto(base_url, wait_until="domcontentloaded", timeout=5000)
            page.wait_for_selector("aside", timeout=5000)
            
            hrefs = page.evaluate('''() => {
                const links = Array.from(document.querySelectorAll("aside ul li a"));
                return links.map(a => a.getAttribute('href'));
            }''')
            
            for href in hrefs:
                if href and f"/discord/server/{server_id}/" in href:
                    full_url = f"https://kemono.cr{href}"
                    if full_url not in channels:
                        channels.append(full_url)
            break
        except Exception as e:
            print(f"[Error] 获取 Discord 频道列表失败: {e}")
            if attempt >= MAX_RETRIES:
                wait_for_user_input(f"无法获取 Discord 服务器 {server_id} 的频道列表。")
                attempt = 0
            else:
                attempt += 1
                time.sleep(2)
                    
    print(f"[Discord_{server_id}] 找到 {len(channels)} 个频道")
    page.close()

    for channel_url in channels:
        process_discord_channel(context, channel_url, file_handle)

def process_artist_task(browser, artist):
    service = artist.get('service')
    artist_id = artist.get('id')
    name = artist.get('name', 'unknown')
    
    invalid_chars = '<>:"/\\|?*'
    safe_name = "".join(c for c in name if c not in invalid_chars)
    
    filename = f"{safe_name}_{service}_{artist_id}.txt"
    filepath = os.path.join(OUTPUT_DIR, filename)
    
    print(f"开始任务: {name} ({service}/{artist_id}) -> {filename}")
    
    context = browser.new_context(
        user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:143.0) Gecko/20100101 Firefox/143.0"
    )
    
    try:
        with open(filepath, "w", encoding="utf-8") as f:
            if service == 'discord':
                process_discord_server(context, artist_id, f)
            else:
                process_standard_artist(context, service, artist_id, f)
        print(f"任务完成: {filename}")
    except Exception as e:
        print(f"任务异常 ({name}): {e}")
    finally:
        context.close()

def main():
    try:
        with open(TASK_JSON, 'r', encoding='utf-8') as f:
            data = json.load(f)
            artists = data.get('artists', [])
    except FileNotFoundError:
        print(f"错误: 当前目录下找不到 {TASK_JSON}")
        return

    if not artists:
        print("json 中没有找到 artists 数据")
        return

    print(f"共加载了 {len(artists)} 个任务。")

    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        
        for artist in artists:
            process_artist_task(browser, artist)
        
        browser.close()
    
    print("\n所有任务处理完毕。")

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("用户手动停止")
        