보조바퀴(크랙 기억력 보조) - 수정판

크랙 기억력 보조용 메모장 (URL 구조 업데이트)

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         보조바퀴(크랙 기억력 보조) - 수정판
// @namespace    https://crack.wrtn.ai
// @version      2.1
// @description  크랙 기억력 보조용 메모장 (URL 구조 업데이트)
// @author       바보륍부이
// @license      MIT
// @match        https://crack.wrtn.ai/stories/*/episodes/*
// @match        https://crack.wrtn.ai/characters/*/chats/*
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  console.log('보조바퀴 스크립트 시작!', location.pathname);

  let textKey = `wrtn_custom_text_${location.pathname}`;
  const panelKey = 'wrtn_custom_panel';

  // 상태 저장을 위한 디바운스 함수
  const debounce = (func, wait) => {
    let timeout;
    return (...args) => {
      clearTimeout(timeout);
      timeout = setTimeout(() => func(...args), wait);
    };
  };

  // 패널 상태 저장 (300ms 디바운스)
  const savePanelState = debounce((state) => {
    localStorage.setItem(panelKey, JSON.stringify(state));
  }, 300);

  const savedPanelState = JSON.parse(localStorage.getItem(panelKey) || '{}');
  let isMinimized = savedPanelState.minimized || false;

  // 패널 생성
  const panel = document.createElement('div');
  Object.assign(panel.style, {
    position: 'fixed',
    top: savedPanelState.top || '100px',
    left: savedPanelState.left || '10px',
    width: savedPanelState.width || '600px',
    height: savedPanelState.height || '500px',
    backgroundColor: '#1a1a1a',
    padding: '0',
    borderRadius: '10px',
    zIndex: '99999',
    color: '#fff',
    fontFamily: 'sans-serif',
    boxShadow: '0 4px 10px rgba(0,0,0,0.3)',
    resize: 'both',
    overflow: 'hidden',
    display: 'flex',
    flexDirection: 'column'
  });

  // 헤더 생성
  const dragHeader = document.createElement('div');
  Object.assign(dragHeader.style, {
    backgroundColor: '#333',
    padding: '8px 10px',
    borderRadius: '10px 10px 0 0',
    cursor: 'move',
    userSelect: 'none',
    fontSize: '12px',
    fontWeight: 'bold',
    borderBottom: '1px solid #444',
    flexShrink: '0',
    position: 'relative'
  });
  dragHeader.textContent = '📝 보조바퀴-크랙 기억력 보조 메모장 (Ctrl+Enter로 빠른 입력)';

  // 도움말 아이콘
  const helpIcon = document.createElement('div');
  Object.assign(helpIcon.style, {
    position: 'absolute',
    top: '5px',
    right: '10px',
    cursor: 'help',
    fontSize: '14px'
  });
  helpIcon.textContent = '❓';
  helpIcon.title = [
    '요약은 캐즘 등 다른 프로그램을 활용해주세요',
    '직접 메모해도 됩니다',
    '다른 방으로 이동하는 등 URL이 바뀌면 갱신 버튼을 눌러주세요',
    '최상단을 더블클릭하면 최소화 됩니다'
  ].join('\n');
  dragHeader.appendChild(helpIcon);

  // 콘텐츠 영역
  const content = document.createElement('div');
  Object.assign(content.style, {
    padding: '10px',
    flex: '1',
    overflow: 'auto',
    display: 'flex',
    flexDirection: 'column'
  });

  // 텍스트 입력 영역
  const input = document.createElement('textarea');
  input.rows = 50;
  input.placeholder = '추가할 내용 입력';
  Object.assign(input.style, {
    width: '100%',
    flex: '1',
    padding: '8px',
    borderRadius: '5px',
    border: '1px solid #444',
    backgroundColor: '#2a2a2a',
    color: '#fff',
    resize: 'none',
    minHeight: '200px'
  });

  // 컨트롤 패널
  const controlRow = document.createElement('div');
  Object.assign(controlRow.style, {
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    padding: '10px',
    backgroundColor: '#222',
    borderTop: '1px solid #444',
    borderRadius: '0 0 10px 10px',
    flexShrink: '0'
  });

  // 텍스트 추가 버튼
  const button = document.createElement('button');
  button.textContent = '텍스트 추가';
  Object.assign(button.style, {
    flex: '1',
    padding: '8px',
    border: 'none',
    borderRadius: '5px',
    backgroundColor: '#4caf50',
    color: '#fff',
    cursor: 'pointer'
  });

  // 갱신 버튼
  const refreshButton = document.createElement('button');
  refreshButton.textContent = '🔄 갱신';
  Object.assign(refreshButton.style, {
    flex: '1',
    padding: '8px',
    border: 'none',
    borderRadius: '5px',
    backgroundColor: '#555',
    color: '#fff',
    cursor: 'pointer'
  });

  // 글자 수 표시
  const countDisplay = document.createElement('div');
  Object.assign(countDisplay.style, {
    marginLeft: 'auto',
    fontSize: '12px'
  });

  // 스토리지에서 텍스트 로드
  const loadTextFromStorage = () => {
    textKey = `wrtn_custom_text_${location.pathname}`;
    const savedText = localStorage.getItem(textKey);
    input.value = savedText || '';
    updateCharCount();
    console.log('메모 로드됨:', textKey);
  };

  // 글자 수 업데이트
  const updateCharCount = () => {
    countDisplay.textContent = `총 ${input.value.length}글자`;
  };

  loadTextFromStorage();

  // 갱신 버튼 이벤트
  refreshButton.addEventListener('click', () => {
    loadTextFromStorage();
    alert('메모를 갱신했습니다!');
  });

  // 최소화 토글
  const toggleMinimize = () => {
    isMinimized = !isMinimized;
    if (isMinimized) {
      content.style.display = 'none';
      controlRow.style.display = 'none';
      panel.style.height = 'auto';
      dragHeader.style.borderRadius = '10px';
      dragHeader.style.borderBottom = 'none';
    } else {
      content.style.display = 'flex';
      controlRow.style.display = 'flex';
      panel.style.height = savedPanelState.height || '500px';
      dragHeader.style.borderRadius = '10px 10px 0 0';
      dragHeader.style.borderBottom = '1px solid #444';
    }
    savePanelState({ ...getCurrentPanelState(), minimized: isMinimized });
  };

  if (isMinimized) toggleMinimize();
  dragHeader.addEventListener('dblclick', toggleMinimize);

  // 현재 패널 상태 가져오기
  const getCurrentPanelState = () => ({
    top: panel.style.top,
    left: panel.style.left,
    width: panel.style.width,
    height: panel.style.height,
    minimized: isMinimized
  });

  // 채팅창에 텍스트 추가
  const appendToTextarea = () => {
    const newText = input.value.trim();
    if (!newText) {
      alert('메모 내용이 비어있습니다!');
      return;
    }

    // 여러 가지 선택자로 시도
    const selectors = [
      'textarea[placeholder*="메시지"]',
      'textarea[placeholder*="입력"]',
      'textarea',
      '[contenteditable="true"]'
    ];

    let textarea = null;
    for (const selector of selectors) {
      textarea = document.querySelector(selector);
      if (textarea) {
        console.log('채팅창 찾음:', selector);
        break;
      }
    }

    if (!textarea) {
      alert("채팅 입력창을 찾을 수 없습니다.\n페이지를 새로고침하거나 채팅창이 로드될 때까지 기다려주세요.");
      console.error('채팅창을 찾을 수 없음');
      return;
    }

    // contenteditable인 경우
    if (textarea.contentEditable === 'true') {
      const currentValue = textarea.textContent || '';
      const newValue = currentValue ? `${currentValue}\n${newText}` : newText;
      textarea.textContent = newValue;
      textarea.dispatchEvent(new Event('input', { bubbles: true }));
    } else {
      // textarea인 경우
      const setter = Object.getOwnPropertyDescriptor(
        window.HTMLTextAreaElement.prototype,
        'value'
      )?.set;

      if (setter) {
        const currentValue = textarea.value;
        const newValue = currentValue ? `${currentValue}\n${newText}` : newText;
        setter.call(textarea, newValue);
        textarea.dispatchEvent(new Event('input', { bubbles: true }));
      }
    }

    console.log('텍스트 추가 완료');
  };

  // Ctrl+Enter 단축키
  window.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' && e.ctrlKey) {
      e.preventDefault();
      appendToTextarea();
    }
  });

  // 버튼 클릭 이벤트
  button.addEventListener('click', appendToTextarea);

  // 입력 시 저장 및 글자 수 업데이트 (디바운스 적용)
  const saveText = debounce(() => {
    localStorage.setItem(textKey, input.value);
    console.log('메모 자동저장됨');
  }, 500);

  input.addEventListener('input', () => {
    updateCharCount();
    saveText();
  });

  // 드래그 기능
  let offsetX, offsetY, dragging = false;

  dragHeader.addEventListener('mousedown', (e) => {
    if (e.target === helpIcon) return;
    dragging = true;
    offsetX = e.clientX - panel.offsetLeft;
    offsetY = e.clientY - panel.offsetTop;
    document.body.style.userSelect = 'none';
    e.preventDefault();
  });

  document.addEventListener('mousemove', (e) => {
    if (!dragging) return;
    panel.style.left = `${e.clientX - offsetX}px`;
    panel.style.top = `${e.clientY - offsetY}px`;
  });

  document.addEventListener('mouseup', () => {
    if (!dragging) return;
    dragging = false;
    document.body.style.userSelect = '';
    savePanelState(getCurrentPanelState());
  });

  // 리사이즈 감지 (디바운스 적용)
  new ResizeObserver(() => {
    if (!isMinimized) {
      const rect = panel.getBoundingClientRect();
      savePanelState({
        top: `${rect.top}px`,
        left: `${rect.left}px`,
        width: `${rect.width}px`,
        height: `${rect.height}px`,
        minimized: isMinimized
      });
    }
  }).observe(panel);

  // DOM 조립
  controlRow.appendChild(button);
  controlRow.appendChild(refreshButton);
  controlRow.appendChild(countDisplay);
  content.appendChild(input);
  panel.appendChild(dragHeader);
  panel.appendChild(content);
  panel.appendChild(controlRow);
  document.body.appendChild(panel);

  console.log('보조바퀴 패널 생성 완료!');
})();