// ==UserScript==
// @name Hacker News Translator
// @namespace http://tampermonkey.net/Hacker-News-Translator
// @version 0.2
// @description Hacker News Translator Using tmt.tencentcloudapi.com
// @author Luoyayu
// @match https://news.ycombinator.com/*
// @icon https://www.google.com/s2/favicons?domain=ycombinator.com
// @grant GM_xmlhttpRequest
// @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.js
// ==/UserScript==
(function() {
'use strict';
const HighLight_Color = 'orange';
function translate(text) {
// console.log('CryptoJS OK: ', CryptoJS);
const Translate_Target = 'zh';
function sha256(message, secret = '') {
return CryptoJS.HmacSHA256(message, secret);
}
function getHash(message) {
return CryptoJS.SHA256(message);
}
function getDate(timestamp) {
const date = new Date(timestamp * 1000);
const year = date.getUTCFullYear();
const month = ('0' + (date.getUTCMonth() + 1)).slice(-2);
const day = ('0' + date.getUTCDate()).slice(-2);
return `${year}-${month}-${day}`;
}
// Key parameter 密钥参数
const SECRET_ID = 'AKID*****************************';
const SECRET_KEY = 'e5KQ****************************';
const endpoint = 'tmt.tencentcloudapi.com';
const service = 'tmt';
const region = 'ap-shanghai';
const action = 'TextTranslate';
const version = '2018-03-21';
const timestamp = Math.floor(Date.now() / 1000);
const date = getDate(timestamp);
// ************* 步骤 1:拼接规范请求串 *************
const signedHeaders = 'content-type;host';
const payload = JSON.stringify({
'SourceText': text,
'Source': 'auto',
'Target': Translate_Target,
'ProjectId': 0,
});
const hashedRequestPayload = getHash(payload);
const httpRequestMethod = 'POST';
const canonicalUri = '/';
const canonicalQueryString = '';
const canonicalHeaders = 'content-type:application/json\n' + 'host:' +
endpoint + '\n';
const canonicalRequest = httpRequestMethod + '\n' + canonicalUri + '\n' +
canonicalQueryString + '\n' + canonicalHeaders + '\n' + signedHeaders +
'\n' + hashedRequestPayload;
// console.log(canonicalRequest);
// ************* 步骤 2:拼接待签名字符串 *************
const algorithm = 'TC3-HMAC-SHA256';
const hashedCanonicalRequest = getHash(canonicalRequest);
const credentialScope = date + '/' + service + '/' + 'tc3_request';
const stringToSign = algorithm + '\n' + timestamp + '\n' + credentialScope +
'\n' + hashedCanonicalRequest;
// console.log(stringToSign);
// ************* 步骤 3:计算签名 *************
const kDate = sha256(date, 'TC3' + SECRET_KEY);
const kService = sha256(service, kDate);
const kSigning = sha256('tc3_request', kService);
const signature = sha256(stringToSign, kSigning).toString(CryptoJS.enc.Hex);
// console.log(signature);
// ************* 步骤 4:拼接 Authorization *************
const authorization = algorithm + ' ' + 'Credential=' + SECRET_ID + '/' +
credentialScope + ', ' + 'SignedHeaders=' + signedHeaders + ', ' +
'Signature=' + signature;
// console.log(authorization);
/*const curlcmd = 'curl -X POST ' + 'https://' + endpoint +
' -H "Authorization: ' + authorization + '"' +
' -H "Content-Type: application/json"' + ' -H "Host: ' +
endpoint + '"' + ' -H "X-TC-Action: ' + action + '"' +
' -H "X-TC-Timestamp: ' + timestamp.toString() + '"' +
' -H "X-TC-Version: ' + version + '"' + ' -H "X-TC-Region: ' + region +
'"' + ' -d \'' + payload + '\'';*/
// console.log(curlcmd);
// return xmlhttpRequest
return {
url: 'https://' + endpoint, method: 'POST', headers: {
'Authorization': authorization,
'Content-Type': 'application/json',
'Host': endpoint,
'X-TC-Action': action,
'X-TC-Timestamp': timestamp.toString(),
'X-TC-Version': version,
'X-TC-Region': region,
}, data: payload, onload: null, // 回调函数实现
};
}
// Example
/*let xmlhttpRequest = translate('Hello World!');
xmlhttpRequest['onload'] = xhr => {
let data = JSON.parse(xhr.responseText);
console.log(data['Response']['TargetText']);
};
GM_xmlhttpRequest(xmlhttpRequest);*/
function translate_eventHandler(event, div_comment) {
let commtext = div_comment.querySelector('span');
// console.log(div_comment.innerText);
let xmlhttpRequest = translate(div_comment.innerText);
xmlhttpRequest['onload'] = xhr => {
// console.log(xhr.responseText);
let data = JSON.parse(xhr.responseText);
let translated_text = data['Response']['TargetText'];
const paragraphs = translated_text.split('\n\n');
Array.from(commtext.getElementsByTagName('p')).forEach((el, index) => {
let translated_paragraph = document.createElement('p');
translated_paragraph.setAttribute('style', `color:${HighLight_Color}`);
translated_paragraph.innerHTML = paragraphs[index];
commtext.insertBefore(translated_paragraph,
el.innerText !== 'reply' ? el : commtext.querySelector('.reply'));
});
// no paragraphs
if (commtext.getElementsByTagName('p').length === 0) {
let translated_paragraph = document.createElement('p');
translated_paragraph.setAttribute('style', `color:${HighLight_Color}`);
translated_paragraph.innerHTML = paragraphs[0];
commtext.insertBefore(translated_paragraph,
commtext.querySelector('.reply'));
}
};
GM_xmlhttpRequest(xmlhttpRequest);
}
if (window.location.pathname === '/news' || window.location.pathname ===
'/' || window.location.pathname === '/newest') {
// 主页
let titles = [];
Array.from(document.getElementsByClassName('titlelink')).forEach((el) => {
titles.push(el.innerHTML);
});
let xmlhttpRequest = translate(titles.join('。ZZZZZZZZZZZZZZZZZZZZZZZZ。'));
xmlhttpRequest['onload'] = xhr => {
let data = JSON.parse(xhr.responseText);
// console.log(data['Response']['TargetText']);
titles = data['Response']['TargetText'].split(
'ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ。');
Array.from(document.getElementsByClassName('titlelink')).
forEach((el, index) => {
el.innerHTML += '<br/>' + titles[index];
});
};
GM_xmlhttpRequest(xmlhttpRequest);
} else if (window.location.pathname === '/item') {
// 评论页面
Array.from(document.getElementsByClassName('comhead')).
forEach((el) => {
if (el.className === 'sitebit comhead') {
// This is a Title comhead
let xmlhttpRequest = translate(el.parentNode.textContent);
xmlhttpRequest['onload'] = xhr => {
let data = JSON.parse(xhr.responseText);
// console.log('标题翻译: ', data['Response']['TargetText']);
el.append(document.createElement('BR'),
data['Response']['TargetText']);
};
GM_xmlhttpRequest(xmlhttpRequest);
} else {
// 评论正文
let div_comment = el.parentNode.parentNode.querySelector(
'.comment');
// 导航栏
let navs = el.querySelector('.navs');
let translate_in_nav = document.createElement('a');
translate_in_nav.setAttribute('style', 'color:orange');
translate_in_nav.innerHTML = '翻译';
translate_in_nav.addEventListener('click', (function(node) {
return function(e) {translate_eventHandler(e, node); };
})(div_comment), {once: true});
navs.append(' | ', translate_in_nav);
}
});
}
})();
(function() {
'use strict';
const HighLight_Color = 'orange';
function translate(text) {
// console.log('CryptoJS OK: ', CryptoJS);
const Translate_Target = 'zh';
function sha256(message, secret = '') {
return CryptoJS.HmacSHA256(message, secret);
}
function getHash(message) {
return CryptoJS.SHA256(message);
}
function getDate(timestamp) {
const date = new Date(timestamp * 1000);
const year = date.getUTCFullYear();
const month = ('0' + (date.getUTCMonth() + 1)).slice(-2);
const day = ('0' + date.getUTCDate()).slice(-2);
return `${year}-${month}-${day}`;
}
// Key parameter 密钥参数
const SECRET_ID = 'AKIDr***************************';
const SECRET_KEY = 'e5KQNw*************************';
const endpoint = 'tmt.tencentcloudapi.com';
const service = 'tmt';
const region = 'ap-shanghai';
const action = 'TextTranslate';
const version = '2018-03-21';
const timestamp = Math.floor(Date.now() / 1000);
const date = getDate(timestamp);
// ************* 步骤 1:拼接规范请求串 *************
const signedHeaders = 'content-type;host';
const payload = JSON.stringify({
'SourceText': text,
'Source': 'auto',
'Target': Translate_Target,
'ProjectId': 0,
});
const hashedRequestPayload = getHash(payload);
const httpRequestMethod = 'POST';
const canonicalUri = '/';
const canonicalQueryString = '';
const canonicalHeaders = 'content-type:application/json\n' + 'host:' +
endpoint + '\n';
const canonicalRequest = httpRequestMethod + '\n' + canonicalUri + '\n' +
canonicalQueryString + '\n' + canonicalHeaders + '\n' + signedHeaders +
'\n' + hashedRequestPayload;
// console.log(canonicalRequest);
// ************* 步骤 2:拼接待签名字符串 *************
const algorithm = 'TC3-HMAC-SHA256';
const hashedCanonicalRequest = getHash(canonicalRequest);
const credentialScope = date + '/' + service + '/' + 'tc3_request';
const stringToSign = algorithm + '\n' + timestamp + '\n' + credentialScope +
'\n' + hashedCanonicalRequest;
// console.log(stringToSign);
// ************* 步骤 3:计算签名 *************
const kDate = sha256(date, 'TC3' + SECRET_KEY);
const kService = sha256(service, kDate);
const kSigning = sha256('tc3_request', kService);
const signature = sha256(stringToSign, kSigning).toString(CryptoJS.enc.Hex);
// console.log(signature);
// ************* 步骤 4:拼接 Authorization *************
const authorization = algorithm + ' ' + 'Credential=' + SECRET_ID + '/' +
credentialScope + ', ' + 'SignedHeaders=' + signedHeaders + ', ' +
'Signature=' + signature;
// console.log(authorization);
/*const curlcmd = 'curl -X POST ' + 'https://' + endpoint +
' -H "Authorization: ' + authorization + '"' +
' -H "Content-Type: application/json"' + ' -H "Host: ' +
endpoint + '"' + ' -H "X-TC-Action: ' + action + '"' +
' -H "X-TC-Timestamp: ' + timestamp.toString() + '"' +
' -H "X-TC-Version: ' + version + '"' + ' -H "X-TC-Region: ' + region +
'"' + ' -d \'' + payload + '\'';*/
// console.log(curlcmd);
// return xmlhttpRequest
return {
url: 'https://' + endpoint, method: 'POST', headers: {
'Authorization': authorization,
'Content-Type': 'application/json',
'Host': endpoint,
'X-TC-Action': action,
'X-TC-Timestamp': timestamp.toString(),
'X-TC-Version': version,
'X-TC-Region': region,
}, data: payload, onload: null, // 回调函数实现
};
}
// Example
/*let xmlhttpRequest = translate('Hello World!');
xmlhttpRequest['onload'] = xhr => {
let data = JSON.parse(xhr.responseText);
console.log(data['Response']['TargetText']);
};
GM_xmlhttpRequest(xmlhttpRequest);*/
function translate_eventHandler(event, div_comment) {
let commtext = div_comment.querySelector('span');
// console.log(div_comment.innerText);
let xmlhttpRequest = translate(div_comment.innerText);
xmlhttpRequest['onload'] = xhr => {
// console.log(xhr.responseText);
let data = JSON.parse(xhr.responseText);
let translated_text = data['Response']['TargetText'];
const paragraphs = translated_text.split('\n\n');
Array.from(commtext.getElementsByTagName('p')).forEach((el, index) => {
let translated_paragraph = document.createElement('p');
translated_paragraph.setAttribute('style', `color:${HighLight_Color}`);
translated_paragraph.innerHTML = paragraphs[index];
commtext.insertBefore(translated_paragraph,
el.innerText !== 'reply' ? el : commtext.querySelector('.reply'));
});
if (commtext.getElementsByTagName('p').length === 0) {
let translated_paragraph = document.createElement('p');
translated_paragraph.setAttribute('style', `color:${HighLight_Color}`);
translated_paragraph.innerHTML = paragraphs[0];
commtext.insertBefore(translated_paragraph,
commtext.querySelector('.reply'));
}
};
GM_xmlhttpRequest(xmlhttpRequest);
}
Array.from(document.getElementsByClassName('comhead')).
forEach((el) => {
if (el.className === 'sitebit comhead') {
// This is a Title comhead
let xmlhttpRequest = translate(el.parentNode.textContent);
xmlhttpRequest['onload'] = xhr => {
let data = JSON.parse(xhr.responseText);
// console.log('标题翻译: ', data['Response']['TargetText']);
el.append(document.createElement('BR'),
data['Response']['TargetText']);
};
GM_xmlhttpRequest(xmlhttpRequest);
} else {
// 评论正文
let div_comment = el.parentNode.parentNode.querySelector('.comment');
// 导航栏
let navs = el.querySelector('.navs');
let translate_in_nav = document.createElement('a');
translate_in_nav.setAttribute('style', 'color:orange');
translate_in_nav.innerHTML = '翻译';
translate_in_nav.addEventListener('click', (function(node) {
return function(e) {translate_eventHandler(e, node); };
})(div_comment), {once: true});
navs.append(' | ', translate_in_nav);
}
});
})();