Yandex一键翻译

右下角出现一个一键翻译的按钮,点击即可一键翻译当前页面

// ==UserScript==
// @name         Yandex一键翻译
// @namespace    http://tampermonkey.net/
// @version      0.0.3
// @description  右下角出现一个一键翻译的按钮,点击即可一键翻译当前页面
// @author       thunder-sword
// @match        *://*/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=yandex.com
// @license      MIT
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      translate.yandex.com
// ==/UserScript==

//系统变量
var gcpCode='';

class NetworkError extends Error {
  constructor(message) {
    super(message);
    this.name = "NetworkError";
  }
}

// 封装GM_xmlhttpRequest为Promise
function httpRequest(options) {
  return new Promise((resolve, reject) => {
    GM_xmlhttpRequest({
      ...options,
      onload: response => resolve(response),
      onerror: error => reject(error)
    });
  });
}

//作用:生成toast,让其在toast_container中,显示在页面中上部,会永久性向页面添加一个id为ths_toast_container的div标签
function showStackToast(message, backcolor='rgb(76, 175, 80)', timeout=3000){
    //没有容器则生成容器
    let box=document.querySelector("body > div#ths_toast_container");
    if(!box){
        box=document.createElement('div');
        box.id="ths_toast_container";
        box.style.cssText = `
    position: fixed;
    top: 10px;
    left: 50%;
    transform: translateX(-50%);
    right: 10px;
    width: 300px;
    height: auto;
    display: flex;
    z-index: 9999;
    flex-direction: column-reverse;`;
        document.body.appendChild(box);
    }
    //创建toast
    const toast = document.createElement('div');
    toast.innerText = message;
    toast.style.cssText = `
    padding: 10px;
    background-color: ${backcolor};
    color: rgb(255, 255, 255);
    border-radius: 10px;
    font-size: 24px;
    font-weight: bold;
    text-align: center;
    box-shadow: rgb(0 0 0 / 30%) 0px 5px 10px;
    opacity: 1;
    transition: opacity 0.3s ease-in-out 0s;
    z-index: 9999;
    margin: 5px;
  `;
    box.appendChild(toast);
    toast.style.opacity = 1;
    if(timeout > 0){
        setTimeout(() => {
            toast.style.opacity = 0;
            setTimeout(() => {
                box.removeChild(toast);
            }, 300);
        }, timeout);
    }
    return toast;
}

//作用:获取gpc
async function getGCP(){
    console.log("尝试获取token……");
    showStackToast("尝试获取token……");
    const data = await httpRequest({
        url: 'https://translate.yandex.com/',
	}).then(function(response) {
        return response.responseText;
    }).catch(error => {
        if (error instanceof NetworkError) {
            console.error("Network error: ", error.message);
        } else {
            console.error("Other error: ", error);
            alert("获取数据失败,请联系开发者");
        }
    });
    let fIndex=data.indexOf('GC_STRING');
    if(-1===fIndex){
        console.log(data);
        console.log("没有找到GC_STRING");
        showStackToast("获取token失败,请联系开发者", "red");
    }
    let gStart=fIndex+13;
    gcpCode=data.substr(gStart, data.indexOf("'", gStart)-gStart);
    showStackToast("获取token成功");
    //设置到全局变量中
    GM_setValue("gcpCode", gcpCode);
}

//作用:翻译一个页面
async function translatePage(url){
    if(!gcpCode){
        if(!GM_getValue("gcpCode")){
            await getGCP();
        } else{
            gcpCode=GM_getValue("gcpCode");
        }
    }
    //尝试使用gcpCode打开翻译页面
    window.open(`https://translate.yandex.com/translate?view=compact&lang=en-zh&gcp=${gcpCode}&url=${url}`);
}

//版本号:v0.0.2
//作用:创建一个在右下角出现的悬浮窗按钮,多个按钮会自动排序,点击即可执行对应函数
function createFloatButton(name, func){

     //没有容器则生成容器
    let box=document.querySelector("body > div#ths_button_container");
    if(!box){
        box=document.createElement('div');
        box.id="ths_button_container";
        box.style.cssText = `
    position: fixed;
    bottom: 10px;
    right: 10px;
    min-height: 30px; /* 设置一个最小高度,确保容器有一定高度 */
    display: flex;
    z-index: 9999;
    flex-direction: column;
    `;
        document.body.appendChild(box);
    }

    // 创建一个 div 元素
    var floatWindow = document.createElement('div');

    // 设置 div 的内容
    //floatWindow.innerHTML = '点我执行代码';
    floatWindow.innerHTML = name;

    // 设置 div 的样式
    floatWindow.style.cssText = `
    padding: 5px;
    background-color: #333;
    color: #fff;
    border-radius: 5px;
    font-size: 16px;
    text-align: center;
    opacity: 1;
    z-index: 9999;
    margin: 5px;
    cursor: pointer; /* 鼠标可以选中 */
    `;

    // 将悬浮窗的优先级提高
    floatWindow.style.zIndex = "99999";

    var isDragging = false;
    var currentX;
    var currentY;
    var initialX;
    var initialY;
    var xOffset = 0;
    var yOffset = 0;
    var cursorX;
    var cursorY;

    floatWindow.addEventListener("mousedown", function(e) {
        if (!isDragging) {
            cursorX = e.clientX;
            cursorY = e.clientY;
            initialX = cursorX - xOffset;
            initialY = cursorY - yOffset;
            isDragging = true;
        }
    });
    floatWindow.addEventListener("mousemove", function(e) {
        if (isDragging) {
            e.preventDefault();
            currentX = e.clientX - initialX;
            currentY = e.clientY - initialY;

            xOffset = currentX;
            yOffset = currentY;

            setTranslate(currentX, currentY, floatWindow);
        }
    });
    floatWindow.addEventListener("mouseup", async function(e) {
        initialX = currentX;
        initialY = currentY;

        isDragging = false;
        // 如果点击时鼠标的位置没有改变,就认为是真正的点击
        if (cursorX === e.clientX && cursorY === e.clientY) {
            await func();
        }
    });

    // 为悬浮窗添加事件处理程序,用来监听触摸开始和触摸移动事件
    // 这些事件处理程序的实现方式与上面的鼠标事件处理程序类似
    floatWindow.addEventListener('touchstart', (event) => {
        if (!isDragging) {
            cursorX = event.touches[0].clientX;
            cursorY = event.touches[0].clientY;
            initialX = cursorX - xOffset;
            initialY = cursorY - yOffset;
            isDragging = true;
        }
    });
    floatWindow.addEventListener('touchmove', (event) => {
        if (isDragging) {
            currentX = event.touches[0].clientX - initialX;
            currentY = event.touches[0].clientY - initialY;

            xOffset = currentX;
            yOffset = currentY;

            setTranslate(currentX, currentY, floatWindow);
        }
    });

    // 为悬浮窗添加事件处理程序,用来监听触摸结束事件
    // 这个事件处理程序的实现方式与上面的鼠标事件处理程序类似
    floatWindow.addEventListener('touchend', async () => {
        initialX = currentX;
        initialY = currentY;

        isDragging = false;
        // 如果点击时鼠标的位置没有改变,就认为是真正的点击
        if (cursorX === event.touches[0].clientX && cursorY === event.touches[0].clientY) {
            await func();
        }
    });

    function setTranslate(xPos, yPos, el) {
        el.style.transform = "translate3d(" + xPos + "px, " + yPos + "px, 0)";
    }

    //将悬浮窗添加到box元素中
    box.appendChild(floatWindow);
}

//作用:主函数,添加翻译当前页面的按钮
function mainFunction(){
    createFloatButton("一键翻译", ()=>{
        translatePage(window.location.href);
    });
}

(function() {
    'use strict';

    mainFunction();
})();