題外話
說起這個應該是自身的需求和(英文水平有限,很不爽啊,所以決定好好學習英語,但是當務之急還是寫個用用。
準備工作
官方出處(需要科學上網 因為新手,先擼一遍官方教程。
需要原材料如下:
- 新建資料夾
- mainfest.json
- background.js
- popup.html
- popup.js
- options.html
- 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
<!DOCTYPE html>
<html>
// 這裡的html縮減了很多標籤,實際上我們完全可以用h5,這裡書寫的是點選外掛彈出的頁面, 類似下拉框按鈕樣式,你可以當初一個網頁來寫,一般這裡做開關控制或者配置項
<head>
<style>
button {
height: 30px;
width: 30px;
outline: none;
}
</style>
</head>
<body>
<button id="changeColor"></button>
<script src="popup.js"></script> // 這裡我們匯入彈窗的指令碼
</body>
</html>
複製程式碼
- 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
<!DOCTYPE html>
<html>
// 當稍複雜業務時,我們需要新開一個窗體進行一些配置,或者展示頁,這裡options的功能在此,允許匯入外部js指令碼等 這裡實現的是背景顏色切換選擇不同背景顏色
<head>
<style>
button {
height: 30px;
width: 30px;
outline: none;
margin: 10px;
}
</style>
</head>
<body>
<div id="buttonDiv">
</div>
<div>
<p>Choose a different background color!</p>
</div>
</body>
<script src="options.js"></script>
</html>
複製程式碼
- 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('<br>', ' ').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(`<div class="zh_sub" style="padding:0 5px 5px 5px;text-align:center;position:relative;top:-12px;background:#4F5155"><h2 style="text-shadow:0.07em 0.07em 0 rgba(0, 0, 0, 0.1);">${subtitle}</h2></div>`)
} 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(`<div class="zh_sub"
style="padding:0 8px 2px 8px;
text-align:center;
position:absolute;
bottom:10%;
left: 50%;
transform: translateX(-50%);
background:#4F5155">
<h2 style="text-shadow:0.07em 0.07em 0 rgba(0, 0, 0, 0.1);font-size:1.5rem;text-align:center">${subtitle}</h2></div>`)
} else {
$('.zh_sub h2').text(subtitle);
}
console.log(subtitle);
}
},
error: function() {
alert('使用者配置有誤,或當前介面流量已達上限');
}
});
}
複製程式碼
這裡的請求做了定時器限制,思路為100ms進行一次dom獲取並比較字幕是否發生改變,改變才得以傳送請求,避免流量超標(
結語
在開發過程中,參考官方文件較多,同時也翻看了大多免費翻譯api貼上地址供有興趣的人蔘考。
最後附上地址歡迎使用和star