크랙채팅로거

crack 캐릭터챗 채팅로그를 JSON으로 복사하거나 txt로 저장합니다

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         크랙채팅로거
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  crack 캐릭터챗 채팅로그를 JSON으로 복사하거나 txt로 저장합니다
// @author       바보륍부이
// @match        https://crack.wrtn.ai/stories/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const API_BASE = 'https://contents-api.wrtn.ai';

    function getChatroomInfo() {
        const match = location.pathname.match(/\/stories\/([a-f0-9]+)\/episodes\/([a-f0-9]+)/);
        return match ? { characterId: match[1], chatroomId: match[2] } : null;
    }

    function getAccessToken() {
        const match = document.cookie.match(/(?:^|; )access_token=([^;]*)/);
        return match ? decodeURIComponent(match[1]) : null;
    }

    async function fetchChatLogs(chatroomId, limit = 50) {
        const token = getAccessToken();
        if (!token) {
            alert('[Chatlog Copier] 토큰을 찾을 수 없습니다.');
            return null;
        }

        const res = await fetch(`${API_BASE}/character-chat/api/v2/chat-room/${chatroomId}/messages?limit=${limit}`, {
            headers: {
                'Authorization': `Bearer ${token}`,
                'Content-Type': 'application/json'
            }
        });

        if (!res.ok) {
            alert(`[Chatlog Copier] 서버 오류: ${res.status}`);
            return null;
        }

        const json = await res.json();
        if (!json?.data?.list) {
            alert('[Chatlog Copier] 채팅 로그 응답 형식을 인식할 수 없습니다.');
            return null;
        }

        return json.data.list.reverse();
    }

    function copyToClipboard(text) {
        navigator.clipboard.writeText(text).then(() => {
            alert('[Chatlog Copier] 채팅 로그가 JSON 형식으로 복사되었습니다.');
        }).catch(() => {
            alert('[Chatlog Copier] 클립보드 복사에 실패했습니다.');
        });
    }

    function saveToFile(text, filename = "chatlog.txt") {
        const blob = new Blob([text], { type: 'text/plain' });
        const link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = filename;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    }

    function createButton() {
        const wrap = document.createElement('div');
        wrap.style.position = 'fixed';
        wrap.style.right = '20px';
        wrap.style.bottom = '20px';
        wrap.style.zIndex = 9999;
        wrap.style.backgroundColor = '#222';
        wrap.style.padding = '12px';
        wrap.style.borderRadius = '12px';
        wrap.style.boxShadow = '0 0 8px rgba(0,0,0,0.3)';
        wrap.style.display = 'flex';
        wrap.style.flexDirection = 'column';
        wrap.style.gap = '6px';

        const countInput = document.createElement('input');
        countInput.type = 'number';
        countInput.min = 1;
        countInput.max = 100;
        countInput.value = 50;
        countInput.placeholder = '채팅 수';
        countInput.style.padding = '4px';
        countInput.style.borderRadius = '6px';
        countInput.style.border = '1px solid #555';
        countInput.style.color = 'white';
        countInput.style.backgroundColor = '#333';

        const nameInput = document.createElement('input');
        nameInput.type = 'text';
        nameInput.placeholder = 'AI 이름 (기본: AI)';
        nameInput.style.padding = '4px';
        nameInput.style.borderRadius = '6px';
        nameInput.style.border = '1px solid #555';
        nameInput.style.color = 'white';
        nameInput.style.backgroundColor = '#333';

        const copyButton = document.createElement('button');
        copyButton.textContent = 'JSON 복사';
        copyButton.style.backgroundColor = '#007bff';
        copyButton.style.color = '#fff';
        copyButton.style.border = 'none';
        copyButton.style.borderRadius = '6px';
        copyButton.style.padding = '6px';
        copyButton.style.cursor = 'pointer';

        const saveButton = document.createElement('button');
        saveButton.textContent = 'txt로 저장';
        saveButton.style.backgroundColor = '#00aa55';
        saveButton.style.color = '#fff';
        saveButton.style.border = 'none';
        saveButton.style.borderRadius = '6px';
        saveButton.style.padding = '6px';
        saveButton.style.cursor = 'pointer';

        async function getFormattedLog() {
            const info = getChatroomInfo();
            if (!info) {
                alert('[Chatlog Copier] 이 페이지에서는 작동하지 않습니다.');
                return null;
            }

            const count = parseInt(countInput.value);
            const aiName = nameInput.value.trim() || 'AI';

            const logs = await fetchChatLogs(info.chatroomId, count);
            if (!logs) return null;

            const result = {
                chatId: info.chatroomId,
                messages: logs.map(item => ({
                    role: item.role,
                    speaker: item.role === 'assistant' ? aiName : '유저',
                    content: item.content
                }))
            };

            return JSON.stringify(result, null, 2);
        }

        copyButton.onclick = async () => {
            const log = await getFormattedLog();
            if (log) copyToClipboard(log);
        };

        saveButton.onclick = async () => {
            const log = await getFormattedLog();
            if (log) saveToFile(log);
        };

        wrap.appendChild(countInput);
        wrap.appendChild(nameInput);
        wrap.appendChild(copyButton);
        wrap.appendChild(saveButton);
        document.body.appendChild(wrap);
    }

    window.addEventListener('load', () => {
        setTimeout(createButton, 1000);
    });
})();