编写一个谷歌插件翻译Udemy+NetFlix字幕
题外话
最好还是看原版,脱离字幕
准备工作
官方出处(需要科学上网
因为新手,先撸一遍官方教程。
需要原材料如下:
- [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
<!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
相关推荐
Ladyseven 2020-10-22
luofuIT成长记录 2020-09-22
Mynamezhuang 2020-09-18
李鴻耀 2020-08-17
yaodilu 2020-08-03
zhoujiyu 2020-06-28
89510194 2020-06-27
CaiKanXP 2020-06-13
MaureenChen 2020-06-12
Phoebe的学习天地 2020-06-07
淡风wisdon大大 2020-06-06
buttonChan 2020-06-06
xtuhcy 2020-05-20
AlisaClass 2020-05-18
赵家小少爷 2020-05-16
nicepainkiller 2020-05-05