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

gzszwxb4  于 2023-05-21  发布在  Java
关注(0)|答案(2)|浏览(147)

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

background.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,但似乎没有一个能解决我的问题。我对开发很陌生,所以我对这类事情基本上一无所知。

eiee3dmh

eiee3dmh1#

在这里扩展我的评论。
可以有两种类型的导航
1.整页刷新
1.基于Javascript的页面加载
在第一种情况下,扩展将完成其工作并加载按钮,但在第二种情况下,如果Web应用程序正在加载整个页面的新元素,那么您的“编辑”元素将被替换为新元素。
您可以通过两种方式解决此问题(AFAIK)
1.收听导航更改
1.突变观察者

收听导航事件

如果Web应用程序正在更改地址栏中的地址,则您可以侦听该事件并添加按钮(如果尚未存在)。https://developer.mozilla.org/en-US/docs/Web/API/Navigation/navigate_event

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

突变观察器

如果由于某种原因,您无法检测Web应用程序中的导航更改,那么您可以侦听DOM中的更改并根据事件做出React。
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

nwlls2ji

nwlls2ji2#

我将使用以下方法发布程序的框架:“倾听导航的变化”。这很复杂,但我没有找到更简单的解决方案。这段代码是firefox插件的一个简化的部分,它可以正常工作。制作这个附加组件的原因是为了帮助用户减少内部网页面的点击(自动化)。您需要在“permissions”下添加“webRequest”:[ ].我使用“manifest_version”: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)

相关问题