让ChatGPT再飞一会儿:真实谷歌插件开发例子,用AI写代码的高效方式

110 阅读6分钟

最近探索的让ChatGPT写代码的心得。背景:在一个第三方网站上开发chrome插件,方便用户摘录信息。功能是在宿主网页上保存操作,最终发到background.js上调用接口保存到我方服务器。

image.png

谷歌插件的各部分通信逻辑限制,宿主网页上的vue不能直接调用background.js,因为是两个不同的运行环境, 只能通过插件api的通信方式进行通信。 谷歌插件(Chrome Extension)的技术知识这里不做过多介绍,可以参考谷歌官方网站developer.chrome.com/docs/extens…

image.png

宿主上的hack vue 由插件通过srcript标签注入,跟宿主网页是一样的运行环境。它跟content.js 通信要通过window.PostMessage的方式,见上图中的1和3通信。 content.js不建议运行占用时间长的任务,丢到background.js里执行,这两者的通信方式采用插件的chrome.runtime.SendMessage方式,见上图中的2。

如果只是让AI写一个函数,一小段代码这样的事情, 还要自己组织梳理各个函数之间的参数传参和结果保存,未免太琐碎。于是整理出一段逻辑,让AI自己去写吧。下面是一个问问题的示例。

(1)先写content.js到background.js之间的这部分逻辑

背景chrome扩展程序,请写代码完成如下功能。
1.在content.js中发送消息给background.js ,命令名为uploadSuit
消息内容包括 图片链接imgUrl,armpx, sex, enName, cnName
2.background.js接受到uploadSuit命令后,执行如下处理
(1)先获取xxx.com的cookie,等下作为请求api的cookie
(2)然后下载imgUrl的图像,并通过https://xxx.com/apiv1/uploadpic上传到服务器,服务器返回类似这样的json
{"code":"0","data":"2307/ec9c1b37-2e8f-11ee-bc51-061e5f54070f.png","msg":"succ"}
其中data为服务器图片存储地址
(3)执行保存,参考代码如下。补充上错误处理
fetch("https://xxx.com/apiv1/addsuit", {
"headers": {
"accept": "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
"content-type": "application/json",
"sec-ch-ua": "\"Not.A/Brand\";v=\"8\", \"Chromium\";v=\"114\", \"Google Chrome\";v=\"114\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin"
},
"referrer": "https://xxx.com/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": "{\"enName\":\"Can you feel the Kenergy\",\"cnName\":\"你能感觉到Kenergy吗\",\"armpx\":3,\"sex\":0,\"img\":\"2307/ec9c1b37-2e8f-11ee-bc51-061e5f54070f.png\",\"onsale\":1}",
"method": "POST",
"mode": "cors",
"credentials": "include"
});
(4)成功后返回对象给content.js, 结果为{code:0, msg""},其中code=0代表成功,其他为失败, 在第(2)(3)步,有任何错误,中断并返回结果给content.js

ChatGPT给出的回答:

在content.js中,你可以使用chrome.runtime.sendMessage发送消息到background.js。示例代码如下:

chrome.runtime.sendMessage({
    command: "uploadSuit",
    data: {
        imgUrl: 'http://example.com/image.jpg',
        armpx: 2,
        sex: 'male',
        enName: 'John',
        cnName: '约翰'
    }
}, function(response) {
    console.log(response);
});
然后,在background.js中,你可以使用chrome.runtime.onMessage.addListener来监听并处理这些消息。示例代码如下:

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    if (request.command === 'uploadSuit') {
        handleUploadSuit(request.data, sendResponse);
        return true;  // 返回true表示你将异步回复消息,这样发送消息的页面就不会立刻关闭连接。
    }
});

function handleUploadSuit(data, sendResponse) {
    // 获取cookie
    chrome.cookies.get({url: 'https://xxx.com', name: 'cookieName'}, function(cookie) {
        if (chrome.runtime.lastError) {
            sendResponse({code: 1, msg: chrome.runtime.lastError.message});
            return;
        }

        // 下载图片并上传到服务器
        fetch(data.imgUrl)
            .then(response => response.blob())
            .then(blob => {
                const formData = new FormData();
                formData.append('file', blob);

                return fetch('https://xxx.com/apiv1/uploadpic', {
                    method: 'POST',
                    body: formData,
                    headers: {'Cookie': cookie.value}
                });
            })
            .then(response => response.json())
            .then(result => {
                if (result.code !== '0') {
                    throw new Error(result.msg);
                }

                // 保存数据
                return fetch('https://xxx.com/apiv1/addsuit', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'Cookie': cookie.value
                    },
                    body: JSON.stringify({
                        enName: data.enName,
                        cnName: data.cnName,
                        armpx: data.armpx,
                        sex: data.sex,
                        img: result.data,
                        onsale: 1
                    })
                });
            })
            .then(response => response.json())
            .then(result => {
                if (result.code !== '0') {
                    throw new Error(result.msg);
                }

                sendResponse({code: 0, msg: ''});
            })
            .catch(error => {
                sendResponse({code: 1, msg: error.message});
            });
    });
}
这是一个基本的示例,你可能需要根据实际情况进行调整。例如,你可能需要处理cookie不存在的情况,或者添加更多的错误处理代码。

里面总共有3个异步函数,promise的方法嵌套多层异步看起来也很丑, 于是让ChatGPT给出更优雅的解决方案。问:

handleUploadSuit 可以改为同步的写法吗

ChatGPT回答还是不错的:

由于在调用API接口时涉及到了网络请求,这是一个异步操作,所以handleUploadSuit函数本身需要保持异步。但你可以使用async/await语法来让代码看起来更像同步的样子。以下是使用async/await语法的示例:

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    if (request.command === 'uploadSuit') {
        handleUploadSuit(request.data)
            .then(result => sendResponse(result))
            .catch(error => sendResponse({code: 1, msg: error.message}));
        return true;  // 返回true表示你将异步回复消息,这样发送消息的页面就不会立刻关闭连接。
    }
});

async function handleUploadSuit(data) {
    // 获取cookie
    const cookie = await new Promise(resolve => chrome.cookies.get({url: 'https://xxx.com', name: 'cookieName'}, resolve));
    if (chrome.runtime.lastError) {
        throw new Error(chrome.runtime.lastError.message);
    }

    // 下载图片并上传到服务器
    const response = await fetch(data.imgUrl);
    const blob = await response.blob();
    const formData = new FormData();
    formData.append('file', blob);
    const uploadResponse = await fetch('https://https://xxx.com/apiv1/uploadpic', {
        method: 'POST',
        body: formData,
        headers: {'Cookie': cookie.value}
    });
    const uploadResult = await uploadResponse.json();
    if (uploadResult.code !== '0') {
        throw new Error(uploadResult.msg);
    }

    // 保存数据
    const saveResponse = await fetch('https://https://xxx.com/apiv1/addsuit', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Cookie': cookie.value
        },
        body: JSON.stringify({
            enName: data.enName,
            cnName: data.cnName,
            armpx: data.armpx,
            sex: data.sex,
            img: uploadResult.data,
            onsale: 1
        })
    });
    const saveResult = await saveResponse.json();
    if (saveResult.code !== '0') {
        throw new Error(saveResult.msg);
    }

    return {code: 0, msg: ''};
}

上面给出的回答,有两点不符合预期的:(1)是uploadResult.code !== '0'类似这样的语句判断, 我给出的提示是整数0, 它把code当做字符串来全等判断了 (2)抛出的错误我期望除了msg,还要有code信息 throw new Error(result.msg); 第(1)点, 自己改一下就好了, 第(2)点, 再问一下ChatGPT。

throw new Error(saveResult.msg);中的Error能增加saveResult.code

ChatGPT回答:

JavaScriptError类型默认只包含一个message字段,不过你可以创建自定义的错误类型来包含更多的信息。以下是一个创建自定义错误类型的示例:

class CustomError extends Error {
    constructor(message, code) {
        super(message);
        this.code = code;
    }
}
然后,你可以像下面这样抛出并处理自定义错误:

try {
    throw new CustomError('Something went wrong', 123);
} catch (error) {
    if (error instanceof CustomError) {
        console.log('Caught a custom error:', error.message, error.code);
    } else {
        console.log('Caught a regular error:', error.message);
    }
}
所以,你可以在handleUploadSuit函数中像下面这样抛出自定义错误:

if (saveResult.code !== '0') {
    throw new CustomError(saveResult.msg, saveResult.code);
}
然后,在消息监听器中,你可以像下面这样处理自定义错误:

handleUploadSuit(request.data)
    .then(result => sendResponse(result))
    .catch(error => {
        if (error instanceof CustomError) {
            sendResponse({code: error.code, msg: error.message});
        } else {
            sendResponse({code: 1, msg: error.message});
        }
    });

完全可以解决我的问题,太棒了。

(2)再写hack vue到content.js这部分逻辑

在问下面这个问题的时候ChatGPT的API卡了, 那我去试试Claude吧,也挺好用的

背景chrome扩展程序,请写代码完成如下功能。

1.在vue中用window.postmessage给content.js发消息,并接收处理结果
消息参数如下
{
    command: "uploadSuit",
    data: {
        imgUrl: this.img,
        armpx: this.armpx,
        sex: this.sex,
        enName: this.enName,
        cnName: this.cnName
    }
}
2.在content.js接收消息,并把消息通过runtime发送给background.js,接收处理结果,透传给第1步中的vue

Claude回答

好的,代码实现如下:

Vue组件中发送消息:

// Vue组件

const data = {
  imgUrl,
  armpx,
  sex,
  enName,
  cnName  
}

window.postMessage({
  command: 'uploadSuit',
  data  
}, '*');

window.addEventListener('message', evt => {
  // 收到background的响应
  const {result} = evt.data;
})


content.js中转发消息:
// content.js

window.addEventListener('message', evt => {

  const {command, data} = evt.data;
  
  chrome.runtime.sendMessage({
    command,
    data
  }, response => {

    window.postMessage({
      result: response
    }, '*');

  })

})

background.js处理上传:

// background.js

chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {

  const {command, data} = req;

  if(command === 'uploadSuit') {
    // 处理上传逻辑
    const result = {success: true};
    sendResponse(result); 
  }

})
这样通过postMessage和runtime的消息机制,可以在不同页面间传递消息和结果。

这样的代码质量也可以,基本上拿来就可以用了。

(3)在整合一下(1)(2)中的通信

1.在hack vue中发起保存,由按钮触发

//hack vue
        onOk:function(){
            console.log("onOk")
            this.okLoading = true
            //发消息给background , 保存皮肤,要经过content.js中转
            window.postMessage({
                command: "reqUploadSuit",
                data: {
                    imgUrl: this.img,
                    armpx: parseInt(this.armpx),
                    sex: parseInt(this.sex),
                    enName: this.enName,
                    cnName: this.cnName
                }
            }, '*');
        },

2.在content.js中接收1的信息并中转给background.js, 收到background.js返回的response信息再通过(postMessage)发给hack vue

//content.js
function initSaveSuitMessage() {
    window.addEventListener('message', evt => {
        const { command, data } = evt.data;
        if (command != "reqUploadSuit") {
            return
        }

        console.log("initSaveSuitMessage", evt.data)
        chrome.runtime.sendMessage({
            command,
            data
        }, response => {
            window.postMessage({
                command: "uploadSuitResponse",
                data: response
            }, '*');
        })
    })
}

到此,借助ChatGPT和Claude, 原本要学习和探索至少两天的工作量,2小时解决了。撒花!