为什么我添加上传文件按钮的 Firefox 扩展只能在一个聊天页面上工作?

问题描述 投票:0回答:2

我正在尝试为基于 FireFox/Gecko 的浏览器做一个扩展,将上传文件按钮添加到 chat.openai.com。当我添加我的扩展程序时,它只会在我刷新 1 个聊天页面时添加按钮。如果我去过去的聊天,它不会放入按钮。 (顺便说一句,我在 ChatGPT 的帮助下写了这段代码,哈哈)。

清单.json:

{
    "manifest_version": 3,
    "name": "ChatGPT File Upload",
    "version": "1.0",
    "description": "Adds a button to upload files into ChatGPT. (NOT for images, videos, Word Documents, or other non-raw-text files. Please use .txt, .js, .py, .html, .css, .json, and .csv.",
    "permissions": [
      "scripting",
      "https://chat.openai.com/*"
    ],
    "action": {
      "default_icon": {
        "128": "icon128.png",
        "256": "icon128.png"
      }
    },
    "icons": {
      "128": "icon128.png",
      "256": "icon256.png"
    },
    "content_scripts": [
      {
        "matches": ["https://chat.openai.com/*"],
        "js": ["content.js"]
      }
    ],
    "background": {
      "scripts": ["background.js"],
      "service_worker": "background.js"
    }
  }  

背景.js:

chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
    if (changeInfo.url && changeInfo.url.startsWith('https://chat.openai.com/')) {
      chrome.scripting.executeScript({
        target: { tabId: tabId },
        files: ['content.js']
      });
    }
  });
  

content.js:

console.log("Content script loaded.");

// This script will be injected into chat.openai.com pages
// You can add your desired functionality here
// Create the button
const button = document.createElement('button');
button.innerText = '📂 Submit File';
button.style.backgroundColor = '#35393d';
button.style.color = 'white';
button.style.padding = '5px';
button.style.border = '1px solid #6b6458';
button.style.borderRadius = '5px';
button.style.margin = '5px';
button.style.width = '180px';

// Create a container div for centering
const containerDiv = document.createElement('div');
containerDiv.style.display = 'flex';
containerDiv.style.justifyContent = 'center';

// Append the button to the container div
containerDiv.appendChild(button);

// Find the target element
const targetElement = document.querySelector("div.relative.flex.h-full.max-w-full.flex-1.overflow-hidden > div > main > div.absolute.bottom-0 > form > div > div:nth-child(1)");

// Insert the container div before the target element
targetElement.parentNode.insertBefore(containerDiv, targetElement);

// Add click event listener to the button
button.addEventListener('click', async () => {
  // Create the file input element
  const fileInput = document.createElement('input');
  fileInput.type = 'file';
  fileInput.accept = '.txt, .js, .py, .html, .css, .json, .csv';

  // Handle file selection
  fileInput.addEventListener('change', async (event) => {
    const file = event.target.files[0];
    if (file) {
      const reader = new FileReader();

      reader.onload = async (e) => {
        const fileContent = e.target.result;
        const chunkSize = 15000;
        const chunks = [];

        // Split file content into chunks
        for (let i = 0; i < fileContent.length; i += chunkSize) {
          const chunk = fileContent.slice(i, i + chunkSize);
          chunks.push(chunk);
        }

        // Submit each chunk to the conversation
        for (let i = 0; i < chunks.length; i++) {
          const chunk = chunks[i];
          const part = i + 1;
          const filename = file.name;
          await submitConversation(chunk, part, filename);
        }
      };

      reader.readAsText(file);
    }
  });

  // Trigger file input click event
  fileInput.click();
});

// Submit conversation function
async function submitConversation(text, part, filename) {
  const textarea = document.querySelector("textarea[tabindex='0']");
  const enterKeyEvent = new KeyboardEvent('keydown', {
    bubbles: true,
    cancelable: true,
    keyCode: 13,
  });
  textarea.value = `Part ${part} of ${filename}:\n\n${text}`;
  textarea.dispatchEvent(enterKeyEvent);
}

我浏览了网上找到的不同 background.js,但似乎没有一个能解决我的问题。我对开发很陌生,所以我大部分时间都迷失在这种事情上。

javascript json artificial-intelligence firefox-addon
2个回答
0
投票

在这里扩展我的评论。

可以有两种类型的导航

  1. 整页刷新
  2. 基于 Javascript 的页面加载

在第一种情况下,扩展程序将完成其工作并加载按钮,但在第二种情况下,如果网络应用程序正在使用新元素加载整个页面,那么您的“编辑”元素将被替换为新元素。

你可以通过两种方式解决这个问题(AFAIK)

  1. 监听导航变化
  2. 突变观察者

听导航事件

如果网络应用程序正在更改地址栏中的地址,那么您可以收听该事件并添加按钮(如果尚不存在)。 https://developer.mozilla.org/zh-CN/docs/Web/API/Navigation/navigate_event

navigation.addEventListener("navigate", (event) => {
  checkAndInjectButton()
})

突变观察者

如果由于某种原因您无法检测到网络应用程序中的导航变化,那么您可以监听 DOM 中的变化并根据事件做出反应。

Mutation oberver 跟踪属性、子节点和子树,因此如果对目标元素进行任何更改,您的脚本将获得回调。

// Select the node that will be observed for mutations
const targetElement = document.querySelector("div.relative.flex.h-full.max-w-full.flex-1.overflow-hidden > div > main > div.absolute.bottom-0 > form > div > div:nth-child(1)");

// Options for the observer (which mutations to observe)
const config = { attributes: true, childList: true, subtree: true };

// Callback function to execute when mutations are observed
const callback = (mutationList, observer) => {
  checkAndInjectButton();
};

// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);

// Start observing the target node for configured mutations
observer.observe(targetElement, config);

// If you need to stop observing
observer.disconnect();

这两种解决方案都会转到您的

content.js


0
投票

我将使用以下方法发布程序的框架:“监听导航更改”。这很复杂,但我没有找到任何更简单的解决方案。该代码是正在运行的 firefox 附加组件的简化部分。制作该插件的原因是帮助用户减少在内联网页面中的点击次数(自动化)。您需要“权限”下的“webRequest”:[]。我使用“清单版本”:2

/* CONTENT SCRIPT anyname.js */
"use strict"
function listenAny(I, sender, outcom) {
   if (I.what == SID_REQ_COMPLETE) {
      
      //Here listen for messages from background.js  //from line with //@@1
      //the page maybe load something by ajax call
      DoAnythingInPage(2)  // 2 => reason is PAGE MODIFIED

   }
}
browser.runtime.onMessage.addListener(listenAny)

function start() {
   //send message to background and wait a response to take the information: 
   //in whitch tab this script is loaded (via Response.tabid)
   browser.runtime.sendMessage({ what: SID_LOADING }).then((Response) => {

      //here the script knows in whitch tab is executing  
      DoAnythingInPage(1)  // 1 => reason is PAGE LOADING
      
   }).catch((e) => {
      //
   })
}

function DoAnythingInPage(reason) {
   //HERE WRITE THE CODE TO MODIFY THE PAGE. THE reason MEANS
   // a)the page is loading of xmlhttprequest or b) some partial loading happans, maybe ajax request
   // You will deside what to do in any case
}


//when the page loads this is the first line of code to execute:
start()



/* BACKGROUND.JS*/
"use strict"
let tabsIwant = []
const urlFilter = {
   urls: ['https://chat.openai.com/*','https://anything.else.com/*']
}

function back_listenAny(rq, sender, resp) {
   if (rq.what == SID_LOADING) {
      //in an array store all tabs in whitch the content script loads
      tabsIwant.push(sender.tab.id)
      resp({ tabid: sender.tab.id }) 
   }
}

function removeTab(tabId) {
   let i = tabsIwant.indexOf(tabId)
   if (i >= 0) {
      tabsIwant.splice(i, 1)
   }
}

function onReqComplete(rDtls) {
   if (rDtls.fromCache || (rDtls.type != 'xmlhttprequest') || rDtls.tabId == -1 ) return  
   //in line above you can eliminate firing the SID_REQ_COMPLETE to content script 
   //after analysing the requests from browser tools. 
   //In my case I did the following additional checks
   /*|| rDtls.url.search('/images/') >= 0 || rDtls.url.search('/resources/') >= 0 */

   //check if this tab is in your interests... 
   //you don't need this check (and all the code that supports the array tabsIwant) if the urlFilter
   //contains only one domain
   if (tabsIwant.indexOf(rDtls.tabId) >= 0) {
      browser.tabs.sendMessage(rDtls.tabId, { what: SID_REQ_COMPLETE, rDtls: rDtls }) //@@1
         .catch((e) => {
            //...
         })
   }
}

browser.tabs.onRemoved.addListener((tabId, removeInfo) => {
   removeTab(tabId)
})

browser.webRequest.onCompleted.addListener(onReqComplete, urlFilter)
© www.soinside.com 2019 - 2024. All rights reserved.