您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
快捷粤语翻译查询
当前为
// ==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); }