Greasy Fork镜像 支持简体中文。

Hacker News Translator

Hacker News Translator Using tmt.tencentcloudapi.com

目前為 2021-12-22 提交的版本,檢視 最新版本

// ==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);
          }
        });
  }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址