Gemini Copy Text Button

Google Gemini(Advanced)の自分の質問からテキストをコピーするボタンを追加します。

// ==UserScript==
// @name        Gemini Copy Text Button
// @namespace   https://qestir.hatenablog.com/entry/2024/06/23/153328
// @match       https://gemini.google.com/*
// @grant       none
// @version     1.4.1
// @description Google Gemini(Advanced)の自分の質問からテキストをコピーするボタンを追加します。
// @author      qestir
// @license GPL-3.0-or-later
// ==/UserScript==

(function() {
  // テキストをコピーする関数
  function copyTextToClipboard(text) {
    // 一時的なテキストエリアを作成
    var textArea = document.createElement("textarea");
    textArea.value = text;

    // テキストエリアをドキュメントに追加
    document.body.appendChild(textArea);

    // テキストを選択
    textArea.select();

    try {
      // クリップボードにコピー
      var successful = document.execCommand('copy');
      var msg = successful ? 'コピーに成功しました!' : 'コピーに失敗しました。';
      showNotification(msg, successful ? 'success' : 'error');
    } catch (err) {
      console.error('テキストのコピーに失敗しました', err);
      showNotification('コピーに失敗しました。', 'error');
    }

    // テキストエリアをドキュメントから削除
    document.body.removeChild(textArea);
  }

  // 通知を表示する関数
  function showNotification(message, type) {
    var notification = document.createElement('div');
    notification.textContent = message;
    notification.style.position = 'fixed';
    notification.style.bottom = '20px';
    notification.style.right = '20px';
    notification.style.padding = '10px 20px';
    notification.style.color = 'white';
    notification.style.backgroundColor = type === 'success' ? '#4CAF50' : '#f44336';
    notification.style.borderRadius = '5px';
    notification.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.1)';
    notification.style.zIndex = '1000';
    document.body.appendChild(notification);

    setTimeout(function() {
      document.body.removeChild(notification);
    }, 3000);
  }

  // テキストをコピーするボタンを作成する関数
  function createCopyButton(targetElement) {
    // コピー用ボタンを作成
    var button = document.createElement("button");
    button.textContent = "コピー";
    button.style.marginLeft = "10px";
    button.style.padding = "5px 10px";
    button.style.backgroundColor = "#4CAF50";
    button.style.color = "white";
    button.style.border = "none";
    button.style.borderRadius = "5px";
    button.style.cursor = "pointer";

    // ボタンにホバースタイルを追加
    button.addEventListener("mouseover", function() {
      button.style.backgroundColor = "#45a049";
    });
    button.addEventListener("mouseout", function() {
      button.style.backgroundColor = "#4CAF50";
    });

    // ボタンがクリックされた時の処理
    button.addEventListener("click", function() {
      // query-textクラスのテキストのみを取得
      var textElement = targetElement.querySelector(".query-text");
      if (textElement) {
        var text = textElement.textContent;
        copyTextToClipboard(text);
      }
    });

    // 対象の要素にボタンを追加
    targetElement.appendChild(button);
  }

  // 特定のクラス名の要素にボタンを追加する関数
  function addCopyButtons() {
    var userQueryElements = document.querySelectorAll("user-query");
    userQueryElements.forEach(function(element) {
      if (!element.querySelector("button")) {
        createCopyButton(element);
      }
    });
  }

  // MutationObserverの設定
  var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      mutation.addedNodes.forEach(function(node) {
        if (node.nodeType === 1) { // 要素ノードの場合
          var userQueryElements = node.querySelectorAll("user-query");
          userQueryElements.forEach(function(element) {
            createCopyButton(element);
          });
        }
      });
    });
  });

  // 監視を開始
  observer.observe(document.body, { childList: true, subtree: true });

  // 初回実行
  addCopyButtons();
})();