7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

open ai APIでフォームに自動入力するChrome拡張を簡単に作る

Last updated at Posted at 2025-04-22

はじめに

結論から言うと完璧を目指すならbrowser-useを使えって感じですが、
料金を抑えつつ、ある程度カジュアルに作るならこれでいい気がします。

前提

  • Chrome拡張の作り方の説明はしない
  • 拡張機能のインストール方法の説明はしない

各ファイル

全てディレクトリ直下に格納してます。

manifest.json

manifest.json
{
  "manifest_version": 2,
  "name": "AIフォーム自動入力",
  "version": "1.0",
  "description": "AI支援によるフォーム自動入力機能を持つChrome拡張機能",
  "browser_action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "images/icon16.png", // iconは適当に準備してください。
      "48": "images/icon48.png",
      "128": "images/icon128.png"
    }
  },
  "background": {
    "scripts": ["background.js"],
    "persistent": false
  },
  "permissions": [
    "storage", 
    "tabs", 
    "activeTab",
    "<all_urls>",
    "https://api.openai.com/*"
  ],
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content-script.js"],
      "run_at": "document_idle"
    }
  ],
  "web_accessible_resources": [
    "content-script.js"
  ]
} 

popup.html

ポップアップの見た目部分です。

popup.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>AIフォーム自動入力</title>
  <style>
    body {
      width: 350px;
      padding: 10px;
      font-family: Arial, sans-serif;
    }
    .form-group {
      margin-bottom: 10px;
    }
    label {
      display: block;
      margin-bottom: 5px;
      font-weight: bold;
    }
    input, textarea, select {
      width: 100%;
      padding: 6px;
      box-sizing: border-box;
      border: 1px solid #ccc;
      border-radius: 4px;
    }
    textarea {
      min-height: 80px;
      resize: vertical;
    }
    small {
      color: #666;
      display: block;
      margin-top: 3px;
      font-size: 0.8em;
    }
    button {
      background-color: #4285F4;
      color: white;
      border: none;
      padding: 8px 12px;
      border-radius: 4px;
      cursor: pointer;
      margin-top: 10px;
      margin-right: 5px;
    }
    button:hover {
      background-color: #3367D6;
    }
    .saved-message {
      color: green;
      margin-top: 10px;
      display: none;
    }
    h2 {
      margin-top: 0;
      color: #333;
      border-bottom: 1px solid #eee;
      padding-bottom: 10px;
    }
    .container {
      padding: 6px 12px;
    }
    .checkbox-group {
      display: flex;
      align-items: center;
      margin-top: 5px;
    }
    .checkbox-group input[type="checkbox"] {
      width: auto;
      margin-right: 8px;
    }
    .checkbox-group label {
      display: inline;
      font-weight: normal;
    }
  </style>
</head>
<body>
  <h2>AIフォーム自動入力</h2>
  
  <div class="container">
    <div class="form-group">
      <label for="apiKey">OpenAI APIキー:</label>
      <input type="password" id="apiKey" placeholder="sk-..." autocomplete="off">
      <small>APIキーは安全に保存され、フォーム自動入力のために使用されます</small>
    </div>
    
    <div class="form-group">
      <label for="model">AIモデル:</label>
      <select id="model">
        <option value="gpt-3.5-turbo">GPT-3.5 Turbo</option>
        <option value="gpt-4">GPT-4</option>
      </select>
    </div>
    
    <div class="form-group">
      <label for="formContext">フォームコンテキスト情報:</label>
      <textarea id="formContext" placeholder="フォームの目的や必要な入力情報について説明してください。例: 「これは求人応募フォームで、エンジニアのポジションに応募したいです」"></textarea>
      <small>AIがフォームをより適切に理解し、最適な値を入力するための補助情報を入力します</small>
    </div>
    
    <button id="saveSettingsButton">設定を保存</button>
    <button id="autoFillButton">フォームを自動入力</button>
    <p id="settingsSavedMessage" class="saved-message">設定を保存しました!</p>
    <p id="autoFillMessage" class="saved-message">自動入力を開始しました!</p>
  </div>

  <script src="popup.js"></script>
</body>
</html> 

popup.js

入力やボタン押下時の制御周りの処理です。

popup.js
document.addEventListener('DOMContentLoaded', function() {
  // DOM要素の取得
  const apiKeyInput = document.getElementById('apiKey');
  const modelSelect = document.getElementById('model');
  const formContextInput = document.getElementById('formContext');
  const saveSettingsButton = document.getElementById('saveSettingsButton');
  const settingsSavedMessage = document.getElementById('settingsSavedMessage');
  const autoFillButton = document.getElementById('autoFillButton');
  const autoFillMessage = document.getElementById('autoFillMessage');
  
  // 保存されたAPIキー設定を読み込む
  chrome.storage.sync.get(['apiSettings'], function(result) {
    if (result.apiSettings) {
      apiKeyInput.value = result.apiSettings.apiKey || '';
      modelSelect.value = result.apiSettings.model || 'gpt-3.5-turbo';
      if (formContextInput) {
        formContextInput.value = result.apiSettings.formContext || '';
      }
    }
  });
  
  // APIキー設定保存ボタンのクリックイベント
  saveSettingsButton.addEventListener('click', function() {
    // APIキー設定を取得
    const apiSettings = {
      apiKey: apiKeyInput.value,
      model: modelSelect.value,
      formContext: formContextInput ? formContextInput.value : ''
    };
    
    // データを保存
    chrome.storage.sync.set({apiSettings: apiSettings}, function() {
      // 保存完了メッセージを表示
      settingsSavedMessage.style.display = 'block';
      
      // 3秒後にメッセージを非表示にする
      setTimeout(function() {
        settingsSavedMessage.style.display = 'none';
      }, 3000);
    });
  });
  
  // フォーム自動入力ボタンのクリックイベント
  autoFillButton.addEventListener('click', function() {
    console.log('自動入力ボタンがクリックされました');
    // APIキー設定を取得
    chrome.storage.sync.get(['apiSettings'], function(result) {
      console.log('APIキー設定を取得しました:', result.apiSettings ? 'あり' : 'なし');
      if (!result.apiSettings || !result.apiSettings.apiKey) {
        alert('OpenAI APIキーを設定してください');
        return;
      }
      
      // 現在のタブにメッセージを送信
      chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
        if (!tabs || tabs.length === 0) {
          console.error('アクティブなタブが見つかりません');
          autoFillMessage.textContent = 'エラー: アクティブなタブが見つかりません';
          autoFillMessage.style.color = 'red';
          autoFillMessage.style.display = 'block';
          return;
        }
        
        const activeTab = tabs[0];
        console.log('アクティブなタブ:', activeTab.id);
        
        // 代替アプローチ: 直接フォーム自動入力を実行する
        autoFillMessage.textContent = 'フォーム分析中...';
        autoFillMessage.style.color = 'blue';
        autoFillMessage.style.display = 'block';
        
        // まずcontent-scriptを強制的に挿入
        chrome.tabs.executeScript(
          activeTab.id,
          { file: 'content-script.js' },
          function(injectionResults) {
            if (chrome.runtime.lastError) {
              console.error('スクリプト注入エラー:', chrome.runtime.lastError);
              autoFillMessage.textContent = `エラー: ${chrome.runtime.lastError.message}`;
              autoFillMessage.style.color = 'red';
              autoFillMessage.style.display = 'block';
              return;
            }
            
            console.log('content-scriptを注入または再読み込みしました');
            
            // 少し待ってからメッセージを送信してみる
            setTimeout(function() {
              try {
                chrome.tabs.sendMessage(
                  activeTab.id,
                  {
                    action: 'autoFillForms',
                    apiKey: result.apiSettings.apiKey,
                    model: result.apiSettings.model || 'gpt-3.5-turbo',
                    formContext: result.apiSettings.formContext || ''
                  },
                  function(response) {
                    console.log('content-scriptからのレスポンス:', response);
                    
                    if (chrome.runtime.lastError) {
                      console.warn('メッセージ送信エラー (フォールバック実行を試みます):', chrome.runtime.lastError);
                      
                      // フォールバック: 直接executeScriptで実行
                      executeDirectFill(activeTab.id, result.apiSettings, autoFillMessage);
                      return;
                    }
                    
                    if (response && response.success) {
                      // 成功メッセージを表示
                      autoFillMessage.textContent = response.message;
                      autoFillMessage.style.color = 'green';
                      autoFillMessage.style.display = 'block';
                      
                      // 3秒後にメッセージを非表示にする
                      setTimeout(function() {
                        autoFillMessage.style.display = 'none';
                      }, 3000);
                    } else {
                      // エラーメッセージを表示
                      autoFillMessage.textContent = response ? response.message : 'フォーム自動入力中にエラーが発生しました';
                      autoFillMessage.style.color = 'red';
                      autoFillMessage.style.display = 'block';
                    }
                  }
                );
              } catch (error) {
                console.error('メッセージ送信エラー:', error);
                
                // エラーの場合も直接実行を試みる
                executeDirectFill(activeTab.id, result.apiSettings, autoFillMessage);
              }
            }, 500);
          }
        );
      });
    });
  });
});

// 直接実行関数
function executeDirectFill(tabId, apiSettings, messageElement) {
  console.log('直接実行を試みます', apiSettings);
  
  // フォームコンテキストをエスケープして安全に扱う
  const safeFormContext = apiSettings.formContext ? 
    apiSettings.formContext.replace(/'/g, "\\'").replace(/\n/g, "\\n") : '';
  
  chrome.tabs.executeScript(
    tabId,
    {
      code: `
        // content-scriptの関数を直接呼び出す
        if (typeof window.directExecute === 'function') {
          console.log('directExecuteを実行します');
          window.directExecute('autoFillForms', '${apiSettings.apiKey}', '${apiSettings.model || 'gpt-3.5-turbo'}', { 
            formContext: '${safeFormContext}'
          });
        } else if (typeof window.detectForms === 'function') {
          console.log('content-scriptの関数は存在しますが、directExecuteがありません');
          { success: false, message: 'バージョンの不一致: 拡張機能を再読み込みしてください' };
        } else {
          console.error('content-scriptの関数が見つかりません');
          { success: false, message: 'content-scriptが正しく読み込まれていません' };
        }
      `
    },
    function(results) {
      console.log('直接実行結果:', results);
      
      if (chrome.runtime.lastError) {
        console.error('直接実行エラー:', chrome.runtime.lastError);
        messageElement.textContent = `エラー: ${chrome.runtime.lastError.message}`;
        messageElement.style.color = 'red';
        messageElement.style.display = 'block';
        return;
      }
      
      // 直接実行では正確な結果が取得できないため、処理中メッセージを表示
      messageElement.textContent = 'フォーム処理中...完了まで少々お待ちください';
      messageElement.style.color = 'blue';
      
      // 5秒後に完了メッセージを表示(非同期処理なので厳密な完了確認はできない)
      setTimeout(function() {
        messageElement.textContent = 'フォーム処理が完了しました(自動推測)';
        messageElement.style.color = 'green';
        
        // さらに3秒後に非表示
        setTimeout(function() {
          messageElement.style.display = 'none';
        }, 3000);
      }, 5000);
    }
  );
} 

background.js

スクリプトの読み込みなどの準備

background.js
// Chrome拡張機能のインストール/更新時に実行
chrome.runtime.onInstalled.addListener(function() {
  console.log('AI フォーム自動入力拡張機能がインストールされました');
});

// エラーを検出するリスナー
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
  if (message.type === 'error') {
    console.error('エラーが報告されました:', message.error);
  }
  
  if (message.type === 'debug') {
    console.log('デバッグメッセージ:', message.data);
  }
  
  // sendResponseが使われる可能性があるので、trueを返す
  return true;
});

// コンテンツスクリプトとの接続確認
chrome.runtime.onConnect.addListener(function(port) {
  console.log('新しい接続:', port.name);
  
  port.onMessage.addListener(function(message) {
    console.log('ポートメッセージ:', message);
  });
  
  port.onDisconnect.addListener(function() {
    console.log('ポート接続が終了しました:', port.name);
  });
});

// コンテンツスクリプトが読み込まれたときのメッセージリスナー
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
  if (message.action === 'contentScriptLoaded') {
    console.log('Content Scriptが読み込まれました:', sender.tab ? sender.tab.url : 'unknown');
    // 応答を返す
    sendResponse({status: 'confirmed'});
  }
  
  // 非同期レスポンスのためにtrueを返す
  return true;
}); 

content-script.js

アクティブなタブ上にあるフォームを検出して自動入力するコア機能

content-script.js
// フォーム要素を検出する関数
function detectForms() {
  // まず標準的な<form>要素を探す
  const forms = document.querySelectorAll('form');
  if (forms.length > 0) {
    console.log('フォームが検出されました:', forms.length, '');
    return forms;
  }
  
  // フォームが見つからない場合、入力フィールドの集まりを探す
  // 入力フィールドを集めて仮想的なフォームを作成
  console.log('標準フォームが見つからないため、入力フィールドを探します');
  
  const inputElements = document.querySelectorAll('input, textarea, select');
  if (inputElements.length > 0) {
    console.log('入力フィールドが見つかりました:', inputElements.length, '');
    
    // 仮想フォーム(div要素)を作成
    const virtualForm = document.createElement('div');
    virtualForm.setAttribute('data-virtual-form', 'true');
    
    // 入力要素の参照を保持(実際にDOM操作はしない)
    virtualForm.inputElements = inputElements;
    
    // 仮想フォーム用のquerySelectorAll関数をオーバーライド
    virtualForm.querySelectorAll = function(selector) {
      if (selector === 'input, textarea, select') {
        return this.inputElements;
      } 
      return [];
    };
    
    return [virtualForm];
  }
  
  console.log('フォームも入力フィールドも見つかりませんでした');
  return [];
}

// OpenAIのAPIを使ってフォームフィールドを解析する関数
async function analyzeFormWithAI(formElement, apiKey, model, formContext = '') {
  // フォーム内の入力要素を取得
  const inputElements = formElement.querySelectorAll('input, textarea, select');
  if (!inputElements || inputElements.length === 0) {
    throw new Error('フォーム内に入力要素が見つかりません');
  }
  
  console.log('入力要素数:', inputElements.length);
  
  const formFields = Array.from(inputElements).map(element => {
    // 基本的なラベル検出
    let label = findLabelForElement(element);
    
    return {
      id: element.id,
      name: element.name,
      type: element.type,
      placeholder: element.placeholder,
      label: label,
      required: element.required
    };
  });

  console.log('解析するフォームフィールド:', formFields);
  
  // 入力可能な要素のみをフィルタリング
  const editableFields = formFields.filter(field => {
    const type = field.type.toLowerCase();
    return type !== 'submit' && type !== 'button' && type !== 'reset' && 
           type !== 'hidden' && type !== 'file' && type !== 'image';
  });
  
  if (editableFields.length === 0) {
    throw new Error('フォーム内に入力可能な要素が見つかりません');
  }

  // OpenAI APIへのリクエスト
  try {
    console.log(`OpenAI API (${model})にリクエストを送信中...`);
    
    const systemMessage = `
あなたはウェブフォームの自動入力を支援するAIアシスタントです。
以下のフォームフィールド情報に基づいて、各フィールドに適切な値を入力してください。
必ず、以下の形式でJSON形式で回答してください:

{
  "フィールドID1": "入力値1",
  "フィールドID2": "入力値2",
  ...
}

注意:
- フィールドIDがない場合は、nameまたはlabelを使用してください
- 実際の値のみを含め、説明やコメントは含めないでください
- 妥当な日本語の値を使用してください(例: 名前なら「山田太郎」など)
- メールアドレスには有効な形式を使用してください(例: user@example.com)
- パスワードには複雑なパスワードを使用してください
- checkboxやradioボタンには "true"/"false" または "1"/"0" で応答してください
`;
    
    let userMessage = `
このウェブフォームに適切な値を入力してください。以下はフォームフィールドの詳細情報です:
${JSON.stringify(editableFields, null, 2)}

各フィールドに適切な値を割り当てたJSONオブジェクトで回答してください。
フィールドIDまたは名前をキーとして使用してください。
`;

    // ユーザーが提供したコンテキスト情報があれば追加
    if (formContext && formContext.trim() !== '') {
      userMessage += `

フォームに関する追加コンテキスト情報:
${formContext}
この情報に基づいて、フォームフィールドに適切な値を入力してください。
`;
    }

    const response = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiKey}`
      },
      body: JSON.stringify({
        model: model,
        messages: [
          {
            role: 'system',
            content: systemMessage
          },
          {
            role: 'user',
            content: userMessage
          }
        ],
        temperature: 0.2,  // より決定論的な結果を得るために低い値を設定
        ...(model.includes('gpt-4') ? {} : { response_format: { type: "json_object" } })  // JSON形式の応答を要求
      })
    });

    if (!response.ok) {
      const errorData = await response.json().catch(() => null);
      console.error('API応答エラー:', response.status, errorData);
      throw new Error(`APIエラー: ${response.status} - ${errorData?.error?.message || '不明なエラー'}`);
    }

    const data = await response.json();
    console.log('AIレスポンス:', data);
    
    if (data.choices && data.choices.length > 0) {
      const contentResult = parseAIResponse(data.choices[0].message.content, editableFields);
      if (Object.keys(contentResult).length === 0) {
        console.warn('AIレスポンスのパースに失敗しました。レスポンス:', data.choices[0].message.content);
        return null;
      }
      return contentResult;
    } else {
      throw new Error('AIからの有効なレスポンスがありません');
    }
  } catch (error) {
    console.error('AI APIエラー:', error);
    throw error; // 上位の関数でキャッチできるようにエラーを再スロー
  }
}

// AIのレスポンスをパースする関数
function parseAIResponse(responseText, formFields) {
  console.log('AIレスポンスのパース開始:', responseText.substring(0, 100) + '...');
  
  // この関数はAIのレスポンスをパースして、各フォームフィールドの値を返す
  const result = {};
  
  try {
    // ステップ1: JSONとして解析を試みる
    let parsedJson = null;
    
    if (responseText.includes('{') && responseText.includes('}')) {
      try {
        // JSON部分を抽出する試み
        const jsonRegex = /{[\s\S]*?}/g;
        const jsonMatches = responseText.match(jsonRegex);
        
        if (jsonMatches && jsonMatches.length > 0) {
          // 最も長いJSONを試す
          const sortedMatches = [...jsonMatches].sort((a, b) => b.length - a.length);
          
          for (const jsonString of sortedMatches) {
            try {
              parsedJson = JSON.parse(jsonString);
              console.log('JSONとして解析成功:', parsedJson);
              break;
            } catch (e) {
              console.log('JSONパース失敗、次の候補を試します');
            }
          }
        }
      } catch (jsonError) {
        console.log('JSON抽出エラー:', jsonError);
      }
    }
    
    // ステップ2: 解析したJSONから値を抽出
    if (parsedJson) {
      console.log('JSONからフィールド値を抽出します');
      
      for (const field of formFields) {
        const fieldId = field.id || field.name;
        const fieldLabel = field.label ? field.label.toLowerCase() : '';
        
        if (!fieldId && !fieldLabel) continue;
        
        // プロパティを直接検索
        if (fieldId && parsedJson[fieldId] !== undefined) {
          result[fieldId] = parsedJson[fieldId];
          continue;
        }
        
        // ラベルで検索
        if (fieldLabel && parsedJson[fieldLabel] !== undefined) {
          result[fieldId || fieldLabel] = parsedJson[fieldLabel];
          continue;
        }
        
        // プロパティ名の一部一致での検索
        for (const key in parsedJson) {
          const keyLower = key.toLowerCase();
          
          if (fieldId && keyLower.includes(fieldId.toLowerCase())) {
            result[fieldId] = parsedJson[key];
            break;
          }
          if (fieldLabel && keyLower.includes(fieldLabel)) {
            result[fieldId || fieldLabel] = parsedJson[key];
            break;
          }
        }
      }
    }
    
    // ステップ3: 行ごとの解析(JSON解析が失敗した場合、または結果が不十分な場合)
    if (Object.keys(result).length < formFields.length / 2) {
      console.log('行ごとの解析を実行します');
      
      const lines = responseText.split('\n');
      for (const line of lines) {
        if (line.includes(':')) {
          // フォーマット例: "フィールド名: 値" または "フィールドID: 値"
          const parts = line.split(':', 2);
          if (parts.length === 2) {
            const key = parts[0].trim().toLowerCase();
            const value = parts[1].trim();
            
            // 対応するフィールドを検索
            for (const field of formFields) {
              const fieldId = field.id || field.name;
              const fieldLabel = field.label ? field.label.toLowerCase() : '';
              
              if (!fieldId && !fieldLabel) continue;
              
              // 完全一致
              if ((fieldId && key === fieldId.toLowerCase()) || 
                  (fieldLabel && key === fieldLabel)) {
                result[fieldId || fieldLabel] = value;
                break;
              }
              
              // 部分一致
              if ((fieldId && key.includes(fieldId.toLowerCase())) || 
                  (fieldLabel && key.includes(fieldLabel)) ||
                  (fieldId && fieldId.toLowerCase().includes(key)) ||
                  (fieldLabel && fieldLabel.includes(key))) {
                result[fieldId || fieldLabel] = value;
                break;
              }
            }
          }
        }
      }
    }
    
    // ステップ4: プレースホルダーやフィールドタイプに基づく推測
    if (Object.keys(result).length < formFields.length) {
      console.log('デフォルト値の推測を実行します');
      
      for (const field of formFields) {
        const fieldId = field.id || field.name || field.label;
        if (!fieldId || result[fieldId]) continue;
        
        // 既に値が設定されていなければデフォルト値を設定
        if (field.type === 'email') {
          result[fieldId] = 'user@example.com';
        } else if (field.type === 'password') {
          result[fieldId] = 'SecurePassword123!';
        } else if (field.type === 'tel') {
          result[fieldId] = '090-1234-5678';
        } else if (field.type === 'number') {
          result[fieldId] = '123';
        } else if (field.name && field.name.toLowerCase().includes('name')) {
          result[fieldId] = '山田太郎';
        } else if (field.placeholder) {
          // プレースホルダーから推測
          if (field.placeholder.toLowerCase().includes('email')) {
            result[fieldId] = 'user@example.com';
          } else if (field.placeholder.toLowerCase().includes('name')) {
            result[fieldId] = '山田太郎';
          }
        }
      }
    }
    
    console.log('パース結果:', result);
    return result;
  } catch (error) {
    console.error('AIレスポンスのパースエラー:', error);
    // エラーがあっても部分的な結果を返す
    return result;
  }
}

// 要素に関連するラベルを見つける関数
function findLabelForElement(element) {
  // idがある場合、そのidを参照するlabelを探す
  if (element.id) {
    const label = document.querySelector(`label[for="${element.id}"]`);
    if (label) {
      return label.textContent.trim();
    }
  }
  
  // 親要素内のラベルを探す
  const parentElement = element.parentElement;
  if (parentElement) {
    const label = parentElement.querySelector('label');
    if (label) {
      return label.textContent.trim();
    }
  }
  
  return '';
}

// フォームフィールドに値を入力する関数
function fillFormFields(form, fieldValues) {
  const inputElements = form.querySelectorAll('input, textarea, select');
  
  inputElements.forEach(element => {
    const fieldId = element.id || element.name;
    if (fieldId && fieldValues[fieldId]) {
      if (element.type === 'checkbox' || element.type === 'radio') {
        if (fieldValues[fieldId].toLowerCase() === 'true' || 
            fieldValues[fieldId].toLowerCase() === 'yes' ||
            fieldValues[fieldId] === '1') {
          element.checked = true;
        }
      } else {
        element.value = fieldValues[fieldId];
      }
    } else if (!fieldId && element.labels && element.labels.length > 0) {
      // ラベルを使用して値を入力
      const labelText = element.labels[0].textContent.trim();
      if (labelText && fieldValues[labelText]) {
        if (element.type === 'checkbox' || element.type === 'radio') {
          if (fieldValues[labelText].toLowerCase() === 'true' || 
              fieldValues[labelText].toLowerCase() === 'yes' ||
              fieldValues[labelText] === '1') {
            element.checked = true;
          }
        } else {
          element.value = fieldValues[labelText];
        }
      }
    }
  });
}

// グローバルスコープで関数を定義(executeScriptから呼び出せるようにするため)
window.detectForms = detectForms;

// 直接実行用のグローバル関数
window.directExecute = function(action, apiKey, model, options = {}) {
  console.log('directExecuteが呼び出されました:', action, options);
  
  if (action === 'autoFillForms') {
    // フォームコンテキスト情報
    const formContext = options.formContext || '';
    
    // 非同期処理を即時実行関数で実行
    (async () => {
      try {
        // フォーム検出
        console.log('フォーム検出を実行します');
        const forms = detectForms();
          
        if (forms.length === 0) {
          console.error('フォームが見つかりませんでした');
          return { 
            success: false, 
            message: 'ページ内にフォームまたは入力フィールドが見つかりませんでした' 
          };
        }
        
        // 最初のフォームに対して処理を実行
        const fieldValues = await analyzeFormWithAI(forms[0], apiKey, model, formContext);
        if (fieldValues && Object.keys(fieldValues).length > 0) {
          fillFormFields(forms[0], fieldValues);
          return { 
            success: true, 
            message: 'フォームを自動入力しました',
            fields: Object.keys(fieldValues).length
          };
        } else {
          return { 
            success: false, 
            message: 'フォームの解析に失敗しました' 
          };
        }
      } catch (error) {
        console.error('フォーム処理エラー:', error);
        return { 
          success: false, 
          message: `エラー: ${error.message}` 
        };
      }
    })();
      
    return { status: 'processing' };
  }
  
  return { success: false, message: '不明なアクション' };
};

// メイン処理
console.log('content-script が読み込まれました');

// バックグラウンドスクリプトに読み込みを通知
try {
  chrome.runtime.sendMessage({action: 'contentScriptLoaded'}, function(response) {
    console.log('バックグラウンドスクリプトからの応答:', response);
  });
} catch (err) {
  console.error('バックグラウンドスクリプトへの通知エラー:', err);
}

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  console.log('メッセージを受信しました:', message);
  
  // バックグラウンドスクリプトにデバッグメッセージを送信
  try {
    chrome.runtime.sendMessage({
      type: 'debug',
      data: { message: 'content-scriptがメッセージを受信', details: message }
    });
  } catch (err) {
    console.error('デバッグメッセージ送信エラー:', err);
  }
  
  if (message.action === 'autoFillForms') {
    const { apiKey, model, formContext } = message;
    
    if (!apiKey) {
      console.error('APIキーがありません');
      sendResponse({ success: false, message: 'APIキーが設定されていません' });
      return true;
    }
    
    // 非同期処理を開始
    (async () => {
      try {
        // フォーム検出
        console.log('フォーム検出を実行します');
        const forms = detectForms();
        
        if (forms.length === 0) {
          console.error('フォームが見つかりませんでした');
          sendResponse({ 
            success: false, 
            message: 'ページ内にフォームまたは入力フィールドが見つかりませんでした' 
          });
          return;
        }
        
        console.log('フォーム解析を開始します');
        
        // 検出された最初のフォームを処理
        const fieldValues = await analyzeFormWithAI(
          forms[0], 
          apiKey, 
          model, 
          formContext
        );
        console.log('解析結果:', fieldValues);
        
        if (fieldValues && Object.keys(fieldValues).length > 0) {
          fillFormFields(forms[0], fieldValues);
          sendResponse({ 
            success: true, 
            message: 'フォームを自動入力しました',
            fields: Object.keys(fieldValues).length
          });
        } else {
          sendResponse({ 
            success: false, 
            message: 'フォームの解析に失敗しました。入力フィールドに適切な値を見つけられませんでした。'
          });
        }
      } catch (error) {
        console.error('フォーム処理エラー:', error);
        
        // エラーメッセージの詳細化
        let errorMsg = error.message;
        if (errorMsg.includes('API')) {
          errorMsg = `OpenAI API エラー: ${errorMsg}`;
        }
        
        sendResponse({ 
          success: false, 
          message: `エラー: ${errorMsg}`
        });
        
        // バックグラウンドスクリプトにエラーを報告
        try {
          chrome.runtime.sendMessage({
            type: 'error',
            error: {
              message: errorMsg,
              stack: error.stack
            }
          });
        } catch (e) {
          console.error('エラー報告中のエラー:', e);
        }
      }
    })();
    
    return true; // 非同期レスポンスのために true を返す
  }
  
  // デフォルトのレスポンス
  sendResponse({ success: false, message: '不明なアクション' });
  return true;
}); 

実際に動かしてみる

こちらのサイトにフォームの事例が載っているので、入力できるか試してみましょう。
下記がちょうど良さそうなのでやってみます。

こんな感じの設定にしました。

スクリーンショット 2025-04-20 14.10.50.png

少し待つとこんな感じで入力できました。
生年月日も適当に入ってくれているのでそこそこ良さそうです

screencapture-pro-form-mailer-jp-fms-6d59e0a6280818-2025-04-20-14_12_34.png

7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?