Greasy Fork

Greasy Fork is available in English.

粤语划词翻译

快捷粤语翻译查询

当前为 2024-10-18 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         粤语划词翻译
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  快捷粤语翻译查询
// @author       口吃者
// @match        http://*/*
// @include      https://*/*
// @include      file:///*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=shyyp.net
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM.setValue
// @grant        GM.getValue
// @license MIT
// ==/UserScript==
const shyypTokenUrl = 'https://shyyp.net/api/gqgq2';
const shyypLongScriptUrl = 'https://shyyp.net/romanizer';//长文注音
const shyypConvertUrl = 'https://shyyp.net/translator';//粤普转换
const shyypSingleWorldUrl = 'https://shyyp.net/w/';
const shyypIconUrl = 'https://shyyp.net/imgs/sheep.svg'
const shyypIconMosaicUrl = 'https://pic2.imge.cc/2024/10/15/670e346fa2205.png'
const shyypIconOnlyHeadUrl = 'https://pic2.imge.cc/2024/10/15/670e3464ab2d1.png'
let textEncry = '';
let selected;// 当前选中文本
let pageX;// 图标显示的 X 坐标
let pageY;// 图标显示的 Y 坐标
const dragFluctuation = 4;// 当拖动多少像素以上时不触发查询
const zIndex = '2147473647'; // 渲染图层
/**鼠标拖动*/
class Drag {
    constructor(element) {
        this.dragging = false;
        this.startDragTime = 0;
        this.stopDragTime = 0;
        this.mouseDownPositionX = 0;
        this.mouseDownPositionY = 0;
        this.elementOriginalLeft = parseInt(element.style.left);
        this.elementOriginalTop = parseInt(element.style.top);
        this.backAndForthLeftMax = 0;
        this.backAndForthTopMax = 0;
        this.element = element;

        // 绑定事件处理函数
        // 事件处理函数由dom元素调用,一般是指向dom元素,强制绑定到Drag类上
        this.startDrag = this.startDrag.bind(this);
        this.dragElement = this.dragElement.bind(this);
        this.stopDrag = this.stopDrag.bind(this);

        // 添加鼠标事件监听器
        this.attachEventListeners();
    }

    attachEventListeners() {
        this.element.addEventListener('mousedown', this.startDrag);
    }

    detachEventListeners() {
        window.removeEventListener('mousemove', this.dragElement);
        window.removeEventListener('mouseup', this.stopDrag);
    }

    startDrag(e) {
        //阻止默认鼠标事件,比如选中文字
        e.preventDefault();
        this.dragging = true;
        this.startDragTime = new Date().getTime();
        this.mouseDownPositionX = e.clientX;
        this.mouseDownPositionY = e.clientY;
        this.elementOriginalLeft = parseInt(this.element.style.left);
        this.elementOriginalTop = parseInt(this.element.style.top);
        this.backAndForthLeftMax = 0;
        this.backAndForthTopMax = 0;

        // 设置全局鼠标事件
        window.addEventListener('mousemove', this.dragElement);
        window.addEventListener('mouseup', this.stopDrag);
        log('startDrag');
    }

    stopDrag(e) {
        e.preventDefault();
        this.dragging = false;
        this.stopDragTime = new Date().getTime();
        this.detachEventListeners();
        log('stopDrag');
    }

    dragElement(e) {
        log('dragging');
        if (!this.dragging) {
            return;
        }
        e.preventDefault();

        // 移动元素
        this.element.style.left = `${this.elementOriginalLeft + (e.clientX - this.mouseDownPositionX)}px`;
        this.element.style.top = `${this.elementOriginalTop + (e.clientY - this.mouseDownPositionY)}px`;

        // 获取最大移动距离
        let left = Math.abs(this.elementOriginalLeft - parseInt(this.element.style.left));
        let top = Math.abs(this.elementOriginalTop - parseInt(this.element.style.top));

        //更新最大移动距离
        if (left > this.backAndForthLeftMax) {
            this.backAndForthLeftMax = left;
        }
        if (top > this.backAndForthTopMax) {
            this.backAndForthTopMax = top;
        }
        log('dragElement');
    }
}
(function() {
    'use strict';
    const icon = document.createElement('tr-icon');// 翻译图标
    icon.id = 'cantonese_translate';
    icon.style.cssText = 'display: none;top: 186px;left: 37px;position: absolute;z-index: 2147473647;cursor:move;';
    const imgShyyp = getImg(shyypIconUrl, 'shyyp', '长文注音');
    const imgShyyp01 = getImg(shyypIconMosaicUrl, 'shyyp01', '单字查询');
    const imgShyyp02 = getImg(shyypIconOnlyHeadUrl, 'shyyp03', '粤普转换');
    // 绑定图标拖动事件
    const iconDrag = new Drag(icon);
    //区分拖动和点击事件,有足够位移才触发窗口事件
    imgShyyp.addEventListener('mouseup', longscriptPopup);
    imgShyyp01.addEventListener('mouseup', singleWorldPopup);
    imgShyyp02.addEventListener('mouseup', toMandarionOrCantonese);
    icon.appendChild(imgShyyp01);
    icon.appendChild(imgShyyp);
    icon.appendChild(imgShyyp02);
    document.body.appendChild(icon);
    // 鼠标事件:防止选中的文本消失;显示、隐藏翻译图标
    document.addEventListener('mouseup', showIcon);
    // 选中变化事件
    document.addEventListener('selectionchange', showIcon);
    document.addEventListener('touchend', showIcon);
    //粤普转换自动化操作
    window.onload = () =>{
        checkUrlAndExecute(async function auto() {
            selected = await GM.getValue('selectedText', '');
            await new Promise(resolve => setTimeout(resolve, 200));
            var textareaEle = document.querySelector("#stage0");
            textareaEle.value = selected;
        } ,shyypConvertUrl)
    }
    var cssText = `
        #cantonese_translate img:hover{
            cursor:pointer;
        }
        #cantonese_translate img:hover{
            border:1px solid #1ABB27
        }
        #cantonese_translate img{
            cursor:pointer;
            display:inline-block;
            width:20px;
            height:20px;
            border:1px solid #dfe1e5;
            border-radius:4px;
            background-color:rgba(255,255,255,1);
            padding:2px;
            margin:0;
            margin-right:5px;
            box-sizing:content-box;vertical-align:middle}
    `
    GMaddStyle(cssText);
    /*  获取长文注音路径参数x请求的json*/
    function createMutationJson(srcValue) {
        const queryTemplate = `mutation Submit($src: String!){ submitSrc(src: $src) }`;
        const variables = { src: srcValue };
        const query = `{"query":"${queryTemplate}","variables":${JSON.stringify(variables)}}`;
        return JSON.parse(query);
    }
    async function sendPostRequest(url, data) {
        const body = JSON.stringify(data);
        const headers = new Headers({
            'Content-Type': 'application/json'
        });
        const options = {
            method: 'POST',
            headers,
            body
        };
        try {
            const response = await fetch(url, options);
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            return response.json();
        } catch (error) {
            console.error('Failed to fetch:', error);
        }
    }
    function sendPostRequestWithGM(url, data) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'POST',
                url: url,
                data: JSON.stringify(data),
                headers: {
                    'Content-Type': 'application/json'
                },
                onload: function(response) {
                    resolve(JSON.parse(response.responseText));
                },
                onerror: function(error) {
                    reject(error);
                }
            });
        });
    }
    /* 长文获取整页html,目前不需要 */
    async function sendGetRequestHtml(urlBase, param) {
        const url = new URL(urlBase);
        url.searchParams.set('x', param);
        try {
            const response = await fetch(url);
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            const htmlContent = await response.text();
            return htmlContent;
        } catch (error) {
            console.error('Failed to fetch:', error);
        }
    }
    function getImg(src01, alt01, title01, options = {}) {
        // 创建一个新的 img 元素
        const img = document.createElement('img');
        // 设置 img 元素的基本属性
        img.src = src01;
        img.alt = alt01;
        img.title = title01;
        // 设置额外的属性
        if (options.width) {
            img.width = options.width;
        }
        if (options.height) {
            img.height = options.height;
        }
        if (options.className) {
            img.className = options.className;
        }
        if (options.style) {
            Object.keys(options.style).forEach(key => {
                img.style[key] = options.style[key];
            });
        }
        // 返回创建的 img 元素
        return img;
    }
    /** 弹出居中窗口 */
    function popupCenter(url, title = '_blank', w, h) {
        // 检查参数有效性
        if (!url || typeof url !== 'string') {
            console.error('Invalid URL provided');
            return null;
        }

        // 设置默认标题和窗口尺寸
        title = title || '_blank';
        w = Math.min(w, screen.availWidth);
        h = Math.min(h, screen.availHeight);

        // 计算居中位置
        let x = (screen.availWidth - w) / 2;
        let y = (screen.availHeight - h) / 2;

        // 确保窗口不会超出屏幕边界
        x = Math.max(x, 0);
        y = Math.max(y, 0);

        // 打开新窗口
        let win;
        try {
            win = window.open(url, title, `width=${w},height=${h},left=${x},top=${y}`);
            if (win) {
                win.focus();
                let closeNewWindow =  window.addEventListener('focus', function() {
                    win.close();
                    window.removeEventListener('focus', closeNewWindow);
                });
            } else {
                throw new Error('Failed to open the window');
            }
        } catch (e) {
            console.error('Error opening the window:', e);
        }

        return win;
    }
    /**显示 icon*/
    function showIcon(e) {
        log('showIcon event:', e);
        let offsetX = -100; // 横坐标翻译图标偏移
        let offsetY = -40; // 纵坐标翻译图标偏移
        // 更新翻译图标 X、Y 坐标
        if (e.pageX && e.pageY) { // 鼠标
            log('mouse pageX/Y');
            pageX = e.pageX;
            pageY = e.pageY;
        }
        if (e.changedTouches) { // 触屏
            if (e.changedTouches.length > 0) { // 多点触控选取第 1 个
                log('touch pageX/Y');
                pageX = e.changedTouches[0].pageX;
                pageY = e.changedTouches[0].pageY;
                // 触屏修改翻译图标偏移(Android、iOS 选中后的动作菜单一般在当前文字顶部,翻译图标则放到底部)
                offsetX = -26; // 单个翻译图标块宽度
                offsetY = 16 * 3; // 一般字体高度的 3 倍,距离系统自带动作菜单、选择光标太近会导致无法点按
            }
        }
        log(`selected:${selected}, pageX:${pageX}, pageY:${pageY}`)
        if (e.target == icon || (e.target.parentNode && e.target.parentNode == icon)) { // 点击了翻译图标
            e.preventDefault();
            return;
        }
        selected = window.getSelection().toString().trim(); // 当前选中文本
        GM_setValue('selectedText', selected);
        log(`selected:${selected}, icon display:${icon.style.display}`);
        if (selected && icon.style.display != 'block' && pageX && pageY) { // 显示翻译图标
            log('show icon');
            icon.style.top = `${pageY + offsetY}px`;
            icon.style.left = `${pageX + offsetX}px`;
            icon.style.display = 'block';
            // 兼容部分 Content Security Policy
            icon.style.position = 'absolute';
            icon.style.zIndex = zIndex;
        } else if (!selected) { // 隐藏翻译图标
            log('hide icon');
            hideIcon();
        }
    }
    /**隐藏 icon*/
    function hideIcon() {
        icon.style.display = 'none';
        pageX = 0;
        pageY = 0;
    }
    /* 长文注音弹出 */
    async function longscriptPopup(){
        try {
            const response = await sendPostRequestWithGM(shyypTokenUrl, createMutationJson(selected));
            textEncry = response.data.submitSrc;
            console.log(textEncry);
            await new Promise(resolve => setTimeout(resolve, 100));
            if (iconDrag.backAndForthLeftMax <= dragFluctuation && iconDrag.backAndForthTopMax <= dragFluctuation) {
                popupCenter(`${shyypLongScriptUrl}?x=${textEncry}`, '长文注音', 1024, 800);
            }
        } catch (error) {
            console.error('Error:', error);
        }
    }
    /* 单字弹出 */
    async function singleWorldPopup(){
        try {
            await new Promise(resolve => setTimeout(resolve, 100));
            if (iconDrag.backAndForthLeftMax <= dragFluctuation && iconDrag.backAndForthTopMax <= dragFluctuation) {
                popupCenter(`${shyypSingleWorldUrl}${selected}`, '单字查询', 1024, 800);
            }
        } catch (error) {
            console.error('Error:', error);
        }
    }
    /* 粤普转换弹出 */
    async function toMandarionOrCantonese(){
        try {
            await new Promise(resolve => setTimeout(resolve, 100));
            if (iconDrag.backAndForthLeftMax <= dragFluctuation && iconDrag.backAndForthTopMax <= dragFluctuation) {
                popupCenter(shyypConvertUrl, '粤普转换', 1024, 800);
            }
        } catch (error) {
            console.error('Error:', error);
        }
    }
    /* 新窗口自动化操作 */
    function checkUrlAndExecute(customFunction, targetUrl) {
        // 获取当前页面的完整URL
        const currentUrl = window.location.href;
        
        // 检查当前URL是否与目标URL相等
        if (currentUrl === targetUrl) {
            // 如果URL匹配,则执行自定义函数
            customFunction();
        }
    }
})();
/**日志输出*/
function log(...args) {
    const debug = false;
    if (!debug) {
        return;
    }
    if (args) {
        for (let i = 0; i < args.length; i++) {
            console.log(args[i]);
        }
    }
}
function GMaddStyle(css){
    var myStyle = document.createElement('style');
    myStyle.textContent = css;
    var doc = document.head || document.documentElement;
    doc.appendChild(myStyle);
}