题外话
最好还是看原版,脱离字幕
准备工作
官方出处(需要科学上网
因为新手,先撸一遍官方教程。
需要原材料如下:
- [x] 新建文件夹
- [x] mainfest.json
- [x] background.js
- [x] popup.html
- [x] popup.js
- [x] options.html
- [x] options.js
将这些文件放入文件夹中,那么你的初始的谷歌插件已经搭好了,不过里面没有任何内容。
- mainfest.json
{
"name": "Getting Started Example", // 插件名
"version": "1.0", // 版本
"description": "Build an Extension!", // 描述
"permissions": ["declarativeContent", "storage",activeTab], // 授予插件能访问到的模块
"background": { // 插件执行时后台js
"scripts": ["background.js"],
"persistent": false
},
"page_action": { // 谷歌插件栏 点击触发弹窗的提示页面文件
"default_popup": "popup.html",
"default_icon": { // 插件图标
"16": "images/get_started16.png",
"32": "images/get_started32.png",
"48": "images/get_started48.png",
"128": "images/get_started128.png"
}
},
"options_page": "options.html", // 配置项页面文件,点击单独开一个窗体
"icons": { // 拓展程序页面显示图标
"16": "images/get_started16.png",
"32": "images/get_started32.png",
"48": "images/get_started48.png",
"128": "images/get_started128.png"
},
"manifest_version": 2
}
- background.js
chrome.runtime.onInstalled.addListener(function() {
//插件安装完成时的事件触发,下方执行了一次设置了storage的操作,特别注意到的是谷歌的storage是沙盒内的,不能通过H5的获取到
chrome.storage.sync.set({
color: '#3aa757'
}, function() {
console.log('The color is green.');
});
chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
// 这里定义的是 何时执行popup.js+popup.html的脚本和作用域,默认情况下 点击弹出窗体执行脚本
chrome.declarativeContent.onPageChanged.addRules([{
conditions: [new chrome.declarativeContent.PageStateMatcher({
pageUrl: {
hostEquals: 'developer.chrome.com'
},
})],
actions: [new chrome.declarativeContent.ShowPageAction()]
}]);
});
});
- popup.html
// 这里的html缩减了很多标签,实际上我们完全可以用h5,这里书写的是点击插件弹出的页面, 类似下拉框按钮样式,你可以当初一个网页来写,一般这里做开关控制或者配置项
// 这里我们导入弹窗的脚本
- popup.js
let changeColor = document.getElementById('changeColor');
// 点击弹窗,出现按钮,这里执行脚本,会绑定一个点击改变颜色的事件,将当前页面颜色改变,如果你的需求不是很多,到这里我们一个简单按钮 已经初步实现一个插件了(done
...
changeColor.onclick = function(element) {
let color = element.target.value;
chrome.tabs.query({
active: true,
currentWindow: true
}, function(tabs) {
chrome.tabs.executeScript(
tabs[0].id, {
code: 'document.body.style.backgroundColor = "' + color + '";'
});
});
};
- options.html
// 当稍复杂业务时,我们需要新开一个窗体进行一些配置,或者展示页,这里options的功能在此,允许导入外部js脚本等 这里实现的是背景颜色切换选择不同背景颜色
Choose a different background color!
- options.js
let page = document.getElementById('buttonDiv');
const kButtonColors = ['#3aa757', '#e8453c', '#f9bb2d', '#4688f1'];
// 该文件是配置的js 作用是点击按钮选择不同颜色并存入到插件的storage中,点击改变颜色页面颜色随配置色变化
function constructOptions(kButtonColors) {
for (let item of kButtonColors) {
let button = document.createElement('button');
button.style.backgroundColor = item;
button.addEventListener('click', function() {
chrome.storage.sync.set({
color: item
}, function() {
console.log('color is ' + item);
})
});
page.appendChild(button);
}
}
constructOptions(kButtonColors);
到此官方的demo结束,在此基础上我们需要实现翻译字幕的功能,我们会遇到两个问题一个是数据存储和外部js,css引入报csp安全机制禁止的问题.
实际开发
先上效果图
│ background.js // 后台执行文件
│ end.js // 点击关闭js
│ LICENSE
│ localstorage.js // 模块文件
│ manifest.json // 配置文件
│ options.html // 配置页面
│ options.js // 配置脚本
│ popup.html // 弹出页面
│ popup.js // 弹窗脚本
│ README.md
│ README_zh.md
│ start.js // 点击开始js
│
├─css
│ style.css
│
├─images // 静态资源
│ get_started128.png
│ get_started16.png
│ get_started32.png
│ get_started48.png
│
├─lib // 依赖
│ jquery-3.1.1.min.js
│ md5.js
│ metro.min.js
│
└─media
config.png
download.png
netflix.png
show.png
step1.png
step2.png
step3.png
step4.png
在开发过程中会报
csp错误和导入外部依赖的步骤
{
"name": "udemy translate",
"version": "1.0",
"description": "udemy translate",
"background": {
"scripts": [ // 后台依赖脚本在这里引入
"background.js",
"lib/jquery-3.1.1.min.js",
"lib/md5.js"
],
"persistent": true // 持久化
},
"browser_action": {
"default_popup": "popup.html",
"default_icon": {
"16": "images/get_started16.png",
"32": "images/get_started32.png",
"48": "images/get_started48.png",
"128": "images/get_started128.png"
}
},
"content_scripts": [ // 这里解决csp安全问题,当需要在某个域下应用相关脚本和样式文件
{
"matches": ["https://*.udemy.com/*","https://*.youtube.com/*","https://*.netflix.com/*"],
"css": ["css/style.css"],
"js": ["localstorage.js","lib/jquery-3.1.1.min.js","lib/md5.js"]
}
],
"permissions": [ // 这里是授予谷歌插件的权限
"http://*/*",
"https://*/*",
"tabs",
"contextMenus",
"notifications",
"webRequestBlocking",
"storage",
"activeTab",
"declarativeContent"
],
"options_page": "options.html",
"icons": {
"16": "images/get_started16.png",
"32": "images/get_started32.png",
"48": "images/get_started48.png",
"128": "images/get_started128.png"
},
"manifest_version": 2
}
文件目录结构剖析
我们执行的顺序是
思路,字幕文件是事实的dom监听,我们需要定位到相关的页面节点并捕获其内容。
之后便是请求翻译接口来实现翻译并配置到页面上,开关定义原字幕的显示和隐藏。
关键popup解析
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
$(function() {
// popup按钮事件区域
let start_btn = document.getElementById('on');
let end_btn = document.getElementById('off');
let option_btn = document.getElementById('options');
chrome.storage.sync.get('currentState', function(data) {
console.log(data.currentState)
if (data.currentState == 'off') { // if is off before
chrome.storage.sync.get('color', function(data) {
end_btn.style.backgroundColor = data.color;
end_btn.style.color = 'white';
});
} else {
chrome.storage.sync.get('color', function(data) {
start_btn.style.backgroundColor = data.color;
start_btn.style.color = 'white';
});
$('#on').click();
}
});
// options event
option_btn.onclick = function() {
chrome.tabs.query({
active: true,
currentWindow: true
}, function(tabs) {
chrome.runtime.openOptionsPage()
});
}
// start event
start_btn.onclick = function(element) {
let color = element.target.value;
// 这里之前遇到一个storage跨域的问题,最终换成chrome沙盒内的storage,可以通过chrome.storage.sync.get/set 进行读取,得以解决实现配置存储的功能
chrome.storage.sync.get('color', function(data) { // get color property
resetBtn(end_btn);
start_btn.style.backgroundColor = data.color;
start_btn.setAttribute('value', data.color);
start_btn.style.color = 'white';
});
chrome.storage.sync.set({
currentState: 'on'
});
chrome.tabs.query({
active: true,
currentWindow: true
}, function(tabs) { // 这里我们因为域的问题,我们通过自带的api引入动态的脚本来执行我们的开始和关闭脚本
chrome.tabs.executeScript(null, {
file: "lib/jquery-3.1.1.min.js"
});
chrome.tabs.executeScript(null, {
file: "lib/md5.js"
});
chrome.tabs.executeScript(null, {
file: "start.js"
});
});
};
// end_btn event
end_btn.onclick = function(element) {
let color = element.target.value;
chrome.storage.sync.get('color', function(data) {
resetBtn(start_btn);
end_btn.style.backgroundColor = data.color;
end_btn.setAttribute('value', data.color);
end_btn.style.color = 'white';
});
chrome.storage.sync.set({
currentState: 'off'
});
chrome.tabs.query({
active: true,
currentWindow: true
}, function(tabs) {
chrome.tabs.executeScript(null, {
file: "end.js"
});
});
};
function resetBtn(dom) {
dom.style.color = '';
dom.style.backgroundColor = ''
}
})
实现功能代码
在start.js里面我们定位到了字幕节点并获取文本信息,之后翻译并送到窗体中。
1.获取文本信息
let typeUrl = window.location.href;
if (typeUrl.includes('udemy')) {
if (document.getElementsByClassName('captions-display--vjs-ud-captions-cue-text--38tMf')[0]) {
var oldSub = document.getElementsByClassName('captions-display--vjs-ud-captions-cue-text--38tMf')[0].outerText;
}
} else if (typeUrl.includes('netflix')) {
if ($('.player-timedtext-text-container').length) {
var oldSub = '';
var container = $('.player-timedtext-text-container').find('span');
for (let i = 0, len = container.length; i < len; i++) {
oldSub += container.eq(i).html().replace('
', ' ').replace('-', '').replace(/\[(.+)\]/, '');
}
}
2.翻译替换
function youdaoSend(configInfo, apiKey, key, subtitle, md5) { // youdao translate request
var apiKey = apiKey;
var key = key;
var salt = (new Date).getTime();
var query = subtitle;
var from = '';
var to = configInfo.aimLang == 'undefined' ? 'zh-CHS' : configInfo.aimLang;
var str1 = apiKey + query + salt + key;
var sign = md5(str1);
// console.log(apiKey, key);
$.ajax({
url: 'https://openapi.youdao.com/api',
type: 'post',
dataType: 'json',
data: {
q: query,
appKey: apiKey,
salt: salt,
from: from,
to: to,
sign: sign
},
success: function(data) {
if (typeof data.translation == "undefined") {
chrome.storage.sync.set({
currentState: 'off'
}, function() {
console.log('error,reset state')
});
return
}
let subtitle = typeof data.translation == "undefined" ? '当前配置错误,或目标语言相同' : data.translation[0]
// judge typeUrl
let typeUrl = window.location.href;
if (typeUrl.includes('udemy')) {
var wrapper = $('.vjs-ud-captions-display div').eq(1);
if (!wrapper.has('h2').length) {
wrapper.append(`${subtitle}
`)
} else {
wrapper.find('h2').text(subtitle)
}
}
if (typeUrl.includes('netflix')) {
var wrapper = $('.player-timedtext')
chrome.storage.sync.set({
netflixSubCache: wrapper.html()
}, function() {
console.log('saved')
});
if (wrapper.siblings(".zh_sub").length < 1) {
wrapper.append(`
${subtitle}
`)
} else {
$('.zh_sub h2').text(subtitle);
}
console.log(subtitle);
}
},
error: function() {
alert('用户配置有误,或当前接口流量已达上限');
}
});
}
这里的请求做了定时器限制,思路为100ms进行一次dom获取并比较字幕是否发生改变,改变才得以发送请求,避免流量超标(
结语
在开发过程中,参考官方文档较多,同时也翻看了大多免费翻译api贴上地址供有兴趣的人参考。
chrome标签操作
web-csp
chrome api
最后附上地址欢迎使用和star
udemy+netflix谷歌翻译插件