开发背景
平时用扇贝英语学习,但是其他网站遇到不会的单词就得复制切到扇贝英语生词本批量上传才能添加。所以我想开发一个浏览器扩展工具选中单词出现一个添加弹框,点击弹框快速添加到扇贝英语生词本。
资料
开发与调试
文件结构
- manijest.json 配置文件
{
"manifest_version": 3,
"name": "San Words",
"description": "扇贝英语生词本辅助工具,在网页上快速把不认识的单词添加到扇贝英语生词本。",
"version": "1.0.0",
"host_permissions": [
"http://*/*",
"https://*/*"
],
"permissions": [
"tabs",
"activeTab",
"cookies"
],
"action": {
"default_popup": "word.html",
"default_icon": {
"16": "images/word.png", // 图片没单独做就是一样的尺寸
"32": "images/word.png",
"48": "images/word.png",
"128": "images/word.png"
}
},
"background": {
"service_worker": "scripts/background.js"
},
"commands": {
"_execute_action": {
"suggested_key": {
"default": "Ctrl+B",
"mac": "Command+B"
}
}
},
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"scripts/jquery.min-3.7.0.js",
"scripts/content.js"
],
"css": ["css/content.css"]
}
]
}
- word.html内容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/font/iconfont.css">
<link rel="stylesheet" href="css/word.css">
<title>扇贝英语单词辅助工具</title>
</head>
<body>
<div class="wqword-container">
<span class="wqword-container__span wqword-container__space"><i class="iconfont icon-duihao"></i> 已启用</span>
<span class="wqword-container__span" id="use-intro"><i class="iconfont icon-jieshao"></i> 使用介绍</span>
</div>
<script src="scripts/jquery.min-3.7.0.js"></script>
<script src="scripts/word.js"></script>
</body>
</html>
- scripts/word.js 扩展工具内容区js文件
/**
* 扩展工具上元素操作
*/
(async () => {
/**
* 使用介绍
*/
$('#use-intro').click(() => {
window.open('https://gitee.com/wangqiao2/word-extension-readme')
})
})();
- scripts/word.js
/**
* 添加提示框html
*/
const addTip = (data) => {
const toolTip = {
0: {
text: "添加单词成功",
},
2: {
text: "未知错误,请刷新",
},
3: {
text: "单词不正确",
},
};
const temp = toolTip[data.status];
if (!temp) return;
let resultDom = $("#wqresult-tip");
if (resultDom.length <= 0) {
const resultTip = `<div id='wqresult-tip' class='wqresult-tip'></div>`;
$("body").append(resultTip);
resultDom = $("#wqresult-tip");
}
if (resultDom.hasClass("wqresult-tip--active")) {
clearTimeout(timerTip);
timerTip = null;
resultDom.removeClass("wqresult-tip--active");
}
// 添加成功提示
if (data.status === 0) {
if (resultDom.hasClass("wqresult-tip--fail")) {
resultDom.removeClass("wqresult-tip--fail");
}
if (!resultDom.hasClass("wqresult-tip--success")) {
resultDom.addClass("wqresult-tip--success");
}
}
// 添加失败提示
if (data.status !== 0) {
if (resultDom.hasClass("wqresult-tip--success")) {
resultDom.removeClass("wqresult-tip--success");
}
if (!resultDom.hasClass("wqresult-tip--fail")) {
resultDom.addClass("wqresult-tip--fail");
}
}
resultDom.addClass("wqresult-tip--active");
resultDom.html(
`<span class='wqerror-word'>${data.result.failed_words}</span> ${temp.text}`
);
timerTip = setTimeout(() => {
resultDom.removeClass("wqresult-tip--active");
resultDom.removeClass("wqresult-tip--success");
resultDom.removeClass("wqresult-tip--fail");
}, 4000);
};
/**
* 处理tab标签接口请求后返回的数据
*/
const dealRespond = (res) => {
if (!res) return;
switch (res.status) {
case 0: // success
case 2: // 其他错误 选择正确单词 请重试
case 3: // 单词不正确
addTip(res);
break;
case 1:
// expired 过期了
alert("扇贝英语登录信息过期了,请先登录扇贝英语,再重试");
window.open("https://web.shanbay.com/bayaccount/login");
break;
}
};
/**
* 监听 poupop页面事件
* search read
*/
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
try {
// 添加到扇贝生词本结果
if (message.type === "sanbei_result") {
console.log("添加结果", message);
$("#wqsanb-tip").hide();
dealRespond(message);
return;
}
} catch (error) {
console.log(
"chrome.runtime.onMessage.addListener ==== 捕获的content页面错误信息供调试查看",
error
);
}
});
- scripts/background.js 可以使用浏览器存储,发起请求等功能
/**
* 获取扇贝网站cookie
*/
const getSanBeiCookies = () => {
return new Promise((resolve, reject) => {
chrome.cookies.getAll(
{
domain: ".shanbay.com",
},
(cookies) => {
console.log(cookies);
if (!cookies || cookies.length <= 0) return reject("cookie 为空");
const cookieStr = cookies.reduce((pre, cur) => {
return (pre += `${cur.name}=${cur.value};`);
}, "");
resolve(cookieStr);
}
);
});
};
// Example POST method implementation:
async function postData(url = "", data = {}, cookies) {
// Default options are marked with *
const response = await fetch(url, {
method: "POST", // *GET, POST, PUT, DELETE, etc.
mode: "cors", // no-cors, *cors, same-origin
cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
credentials: "same-origin", // include, *same-origin, omit
headers: {
"Content-Type": "application/json",
Cookie: cookies,
},
redirect: "follow", // manual, *follow, error
referrerPolicy: "no-referrer", // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
body: JSON.stringify(data), // body data type must match "Content-Type" header
});
return response.json(); // parses JSON response into native JavaScript objects
}
// fetch get请求
async function getResquest(url = "", cookies) {
const response = await fetch(url, {
headers: {
"Content-Type": "application/json",
Cookie: cookies,
},
});
return response.json(); // parses JSON response into native JavaScript objects
}
/**
* 保存到到扇贝生词本总共3步
* 1,上传word数组换取taskid
* 2,拿着taskid上传
*/
// 换取taskid
const getSanBeiTaskId = async (cookies, words = []) => {
if (words.length <= 0) return Promise.resolve({ task_id: "" });
return await postData(
"请求地址和参数自己f12查看",
{ business_id: 6, words: words },
cookies
);
};
// 发送添加请求
const sendSanBeiNoteBook = async (taskId, cookies) => {
if (!taskId)
return Promise.resolve({
error_msg: "",
failed_count: 0,
failed_words: "",
status: 4,
total_count: 0,
});
return await getResquest(
`请求地址和参数自己f12查看`,
cookies
);
};
// 统一发送信息方法
const sendMessageFunc = (tabId, data) => {
if (!tabId) return;
chrome.tabs.sendMessage(tabId, data);
};
//接收消息处理器
chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
const tabId = sender.tab.id;
try {
// 发送给扇贝生词本
if (message.type === "send_sanbei") {
const cookies = await getSanBeiCookies();
console.log("cookies ===", cookies);
if (!cookies || !message.word) return;
console.log("添加的单词", message.word.split(";"));
const res = await getSanBeiTaskId(cookies, message.word.split(";"));
console.log("获取扇贝单词 task_id", res);
// 登录过期 提示用户重新登录
if (res.errors) {
sendMessageFunc(tabId, {
type: "sanbei_result",
status: 1,
msg: "过期了",
});
return;
}
if (!res.task_id) {
sendMessageFunc(tabId, {
type: "sanbei_result",
status: 2,
result: res,
msg: "其他错误,请重试",
});
return;
}
if (res.task_id) {
const result = await sendSanBeiNoteBook(res.task_id, cookies);
// 上传失败个数
if (result.failed_count > 0) {
return sendMessageFunc(tabId, {
type: "sanbei_result",
status: 3,
result,
msg: "单词不正确",
});
}
sendMessageFunc(tabId, {
type: "sanbei_result",
status: 0,
result,
msg: "成功",
});
}
}
} catch (error) {
console.log(
"chrome.runtime.onMessage.addListener 接受消息 catch error",
error
);
if (error === "cookie 为空") {
sendMessageFunc(tabId, {
type: "sanbei_result",
status: 1,
msg: "过期了",
});
return;
}
sendMessageFunc(tabId, {
type: "sanbei_result",
status: 2,
result: res,
msg: "其他错误,请重试",
});
}
});
- 本地调试开发的时候浏览器框输入 chrome://extensions/
发布
- 谷歌扩展工具开发者,现在发布应用需要$5,我用的是招行全币卡(卡上带有VISA标识都可)
- 谷歌浏览器填写资料发布就行了
推荐使用
- 谷歌市场应用商店:chrome.google.com/webstore/se…
- 扩展工具ID:ginojdhgglhhcjcjadggpflbdfmgggba