// ==UserScript==
// @name lynda.com 字幕翻译
// @description 使用搜狗网页翻译接口将 lynda.com 的字幕译为中文
// @namespace https://github.com/journey-ad
// @version 0.1.2
// @author journey-ad
// @match *://www.lynda.com/*
// @license MIT
// @run-at document-end
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function () {
"use strict"; // 监听历史记录变化,实现自动翻译每节课程
(function (history) {
var pushState = history.pushState;
history.pushState = function (state) {
if (typeof history.onpushstate == "function") {
history.onpushstate({
state: state
});
}
console.log("章节切换");
unsafeWindow.transTimer = window.setInterval(transText, 1000); // 用定时器检查待翻译文本是否已准备好
return pushState.apply(history, arguments);
};
})(unsafeWindow.history);
function transText() {
try {
if (mejs.players.mep_0.selectedTrack) {
// 待翻译文本已准备好
unsafeWindow.clearInterval(unsafeWindow.transTimer); // 清除定时器
console.log("字幕文本可用");
var s = "",
r = "",
arr = [],
num = 0,
count = 0,
subtitle = mejs.players.mep_0.selectedTrack.entries.text,
subtitleTrans = [];
subtitle.forEach(function (e) { // 去除每条字幕的换行符并按行排列
s += e.replace(/\r?\n|\r/g, "") + "\n";
});
// console.log(s);
num = translate(s, function (data, index) { // 调用翻译方法并处理回调
count++;
arr[index] = data; // 按分块原始下标放回结果数组
if (count >= num) { // 所有翻译文本已取回
r = arr.join("");
subtitleTrans = r.split("\n");
subtitle.forEach(function (e, i) {
subtitle[i] = subtitleTrans[i] + "\n" + e.replace(/\r?\n|\r/g, ""); // 翻译文本填回原处
});
// console.log(r.split('\n'));
console.log(r);
console.log("翻译完成");
}
});
}
} catch (error) {
}
}
function translate(str, callback) {
var KEY = "b33bf8c58706155663d1ad5dba4192dc"; // 硬编码于搜狗网页翻译js
var textArr = [],
count = 1;
if (str.length > 5000) {
//大于5000字符分块翻译
var strArr = str.split("\n"),
i = 0;
strArr.forEach(function (v) {
textArr[i] = textArr[i] || "";
if ((textArr[i] + v).length > (i + 1) * 5000) { // 若加上此行后长度超出5000字符则分块
i++;
textArr[i] = "";
}
textArr[i] += v + "\n";
});
count = i + 1; // 记录块的数量
} else {
textArr[0] = str;
}
var data = {
from: "auto",
to: "zh-CHS",
client: "pc",
fr: "browser_pc",
text: null,
pid: "sogou-dict-vr",
useDetect: "on",
useDetectResult: "on",
oxford: "on",
isReturnSugg: "on",
needQc: 1,
s: null
};
textArr.forEach(function (text, index) {
// 遍历每块分别进行翻译
data.text = text;
data.s = md5("autozh-CHS".concat(text).concat(KEY)); // 签名算法
GM_xmlhttpRequest({
method: "POST",
url: "https://fanyi.sogou.com/reventondc/translateV1",
headers: {
accept: "application/json",
"content-type": "application/x-www-form-urlencoded; charset=UTF-8"
},
data: serialize(data),
onload: function onload(response) {
var result = JSON.parse(response.responseText);
// console.log(result);
callback(result.data.translate.dit + "\n", index); // 执行回调,在回调中拼接
}
});
});
return count; // 返回分块数量
}
function serialize(obj) {
return Object.keys(obj)
.map(function (k) {
return (
encodeURIComponent(k) +
"=" +
encodeURIComponent(obj[k]).replace("%20", "+")
);
})
.join("&");
}
function md5(str) {
var k = [],
i = 0;
for (i = 0; i < 64;) {
k[i] = 0 | (Math.abs(Math.sin(++i)) * 4294967296);
}
var b,
c,
d,
j,
x = [],
str2 = unescape(encodeURI(str)),
a = str2.length,
h = [(b = 1732584193), (c = -271733879), ~b, ~c];
for (i = 0; i <= a;) {
x[i >> 2] |= (str2.charCodeAt(i) || 128) << (8 * (i++ % 4));
}
x[(str = ((a + 8) >> 6) * 16 + 14)] = a * 8;
i = 0;
for (; i < str; i += 16) {
a = h;
j = 0;
for (; j < 64;) {
a = [
(d = a[3]),
(b = a[1] | 0) +
(((d =
a[0] + [
(b & (c = a[2])) | (~b & d),
(d & b) | (~d & c),
b ^ c ^ d,
c ^ (b | ~d)
][(a = j >> 4)] +
(k[j] +
(x[([j, 5 * j + 1, 3 * j + 5, 7 * j][a] % 16) + i] | 0))) <<
(a = [7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21][
4 * a + (j++ % 4)
])) |
(d >>> (32 - a))),
b,
c
];
}
for (j = 4; j;) {
h[--j] = h[j] + a[j];
}
}
str = "";
for (; j < 32;) {
str += ((h[j >> 3] >> ((1 ^ (j++ & 7)) * 4)) & 15).toString(16);
}
return str;
}
})();