# 2024/07/28 內部培訓 - UserScript 輕鬆學
*Created on 2024/07/23
(c) 2024 Gandolfreddy*
## 課程錄影
<iframe width="560" height="315" src="https://www.youtube.com/embed/n52j_hO8fFo?si=ivJ57AtHpL-T1KJt" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
## 課程目標
- 讓參與者認識何謂 UserScript。
- 如何撰寫 UserScript。
- 如何在瀏覽器執行 UserScript。
- 搭配 Gemini 1.5 Flash API 實作嵌入翻譯(immersive translation)工具。
## 課程時間
- 總時長:2.5 小時(視參與者反應與需求調整時長)。
- 時間分配:視每個單元所需分配,剩餘時間用於答疑與互動。
## 教材與資源
### 大家的練習文件與 GitHub Repository
#### 練習文件
* [Freddy 的 UserScript]()
* [ㄚˇ綠 的 UserScript](https://hackmd.io/@Midori91/H1jPnomYC)
* [UJ 的 UserScript](https://hackmd.io/@YouJay/B1rUkn7YC)
* [BZ 的 UserScript](https://hackmd.io/@TonyTsai0319/rJXik37F0)
* [Shiro 的 UserScript](https://hackmd.io/@D5AeQvpWS0WnSxwhuxo6fQ/SJTxQ2mY0)
#### GitHub Repository
* [gandolfreddy/GoogleDinoCheat](https://github.com/gandolfreddy/GoogleDinoCheat?tab=readme-ov-file)
* [UJayJoAnDao/seeMyPassword](https://github.com/UJayJoAnDao/seeMyPassword)
* [Tzny/YtAdsRemove](https://github.com/TonyTsai03/traning)
### 參考教材與網站
:::danger
使用他人的 UserScript 前,務必確認其中有無惡意程式碼。
:::
* [awesome-scripts/awesome-userscripts](https://github.com/awesome-scripts/awesome-userscripts)。
* [google-gemini/cookbook](https://github.com/google-gemini/cookbook)。
* [Will 保哥 - doggy8088](https://github.com/doggy8088)
* [doggy8088/TampermonkeyUserscripts](https://github.com/doggy8088/TampermonkeyUserscripts)。
* [doggy8088/gemma-cookbook](https://github.com/doggy8088/gemma-cookbook/tree/zh-tw)。
* [doggy8088/gemini-api-cookbook](https://github.com/doggy8088/gemini-api-cookbook)。
* [【Tampermonkey】輕鬆上手 - 油猴腳本開發](https://wayne-blog.com/2022-12-15/tampermonkey-userscript-tutorial/)(支語多,請謹慎使用)。
### 實驗
* [Eloquent JavaScript 4th edition (2024)](https://eloquentjavascript.net/index.html)(英文)。
* [BBC News](https://www.bbc.com/news)(英文)。
* [Reuters](https://www.reuters.com/)(英文)。
* [讀賣新聞](https://www.yomiuri.co.jp/)(日文)。
* [UserScript(ユーザースクリプト)の概要と使い方](https://shukapin.com/blog/how-to-use-userscript)(日文)。
* [BBC NEWS Mundo](https://www.bbc.com/mundo)(西班牙文)。
* [YTN](https://www.ytn.co.kr/)(韓文)。
* [台灣教會公報 - 【台文世界】請咱來學白話字](https://tcnn.org.tw/archives/211510)(台文)。
## 培訓流程
### 認識 UserScript
#### 簡介
- 認識何謂 UserScript。
- UserScript 常見用途。
#### 活動
- 說明:認識何謂 UserScript。
- 可以使用 UserScript 修改網頁內容,通常是以 JavaScript 撰寫。
- 這類程式主要用於增強瀏覽體驗,通常設定於網頁載入時,加入特定功能或自動化操作,例如加入快捷鍵、控制媒體播放速度或修改網頁外觀。
- 說明:UserScript 常見用途。
1. 自動化操作:自動化一些重複性操作,例如自動填寫表單、點擊按鈕或自動登入網站。
2. 增強網頁功能:為網站加入新的功能,如在影片網站上加入下載按鈕,或在電商網站上加入商品價格歷史追蹤。
3. 修改網頁外觀:自訂網頁的樣式,例如隱藏廣告、改變網站的主題顏色或字體大小。
4. 資料擷取與分析:擷取網頁上的資料並分析。
5. 增強隱私保護:阻擋追蹤腳本或防止某些網站收集使用者的資料。
6. 加入網頁功能捷徑:透過快捷鍵快速啟動特定功能,如直接跳到頁面底部或頂部,或快速搜尋頁面內容。
### 如何撰寫 UserScript
#### 簡介
- 認識撰寫 UserScript 的環境。
- 簡單撰寫一份 UserScript。
#### 活動
- 說明:認識撰寫 UserScript 的環境。
- UserScript 可以使用 [Tampermonkey](https://www.tampermonkey.net/)、[Greasemonkey](https://wiki.greasespot.net/Main_Page) 這樣的瀏覽器擴充功能,來管理和執行。
- 說明:簡單撰寫一份 UserScript。
```javascript
// ==UserScript==
// @name Hello World
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author Me
// @match https://hackmd.io/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
alert('Hello, World!');
})();
```
:::info
什麼是 [IIFE (Immediately Invoked Function Expression)](https://developer.mozilla.org/zh-TW/docs/Glossary/IIFE)?
```javascript
/* IIFE 也可以帶入參數與引數使用 */
let result = (function(a, b) {
return a + b;
})(10, 5);
console.log(result); // 輸出: 15
```
:::
:::info
什麼是 [Strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode)?
:::
- 練習:於 DevTools 的 Console 中,實驗使用 [IIFE](https://developer.mozilla.org/zh-TW/docs/Glossary/IIFE)。
### 如何在瀏覽器執行 UserScript
#### 簡介
- 使用 TamperMonkey 執行自己的 UserScript。
- 介紹 TamperMonkey 細節設定。
- 配合 GitHub 建立方便安裝、更新的方式。
#### 活動
- 說明:使用 TamperMonkey 執行自己的 UserScript。
- 安裝 [TamperMonkey](https://www.tampermonkey.net/)。
- 於擴充功能中尋找 TamperMonkey。

- 練習:請嘗試安裝以下 UserScript 至 TamperMonkey,並於[此網站](https://chromedino.com/)測試執行。
```javascript=
// ==UserScript==
// @name Google dino Cheat
// @namespace http://tampermonkey.net/
// @version 2024-07-26
// @description Play google dino game with cheat script.
// @author Gandolfreddy
// @match https://chromedino.com/
// @icon https://seeklogo.com/images/D/dinosaur-game-logo-2723F385F0-seeklogo.com.png
// @grant none
// ==/UserScript==
(function() {
'use strict';
function dinoAI() {
if (Runner.instance_.horizon.obstacles.length > 0) {
let dist = Runner.instance_.horizon.obstacles[0].xPos;
let obj = Runner.instance_.horizon.obstacles[0];
let type = obj.typeConfig.type;
let speed = Runner.instance_.currentSpeed;
if (dist < speed * 22) {
if (type === 'PTERODACTYL' && obj.yPos < 60) {
if(!Runner.instance_.tRex.ducking) Runner.instance_.tRex.setDuck(true);
} else {
if(Runner.instance_.tRex.ducking) Runner.instance_.tRex.setDuck(false);
Runner.instance_.tRex.startJump(Runner.instance_.currentSpeed);
}
}
}
requestAnimationFrame(dinoAI);
}
dinoAI();
})();
```
- 練習:任選以下主題,撰寫出一份可以動作的 UserScript。
- 自動跳過 YouTube 廣告:可以自動點擊「跳過廣告」按鈕。
- 網頁自動捲動:使用語音輸入方式,控制網頁上下捲動。
- 暗色模式切換:為網站加入一個按鈕,點擊後可以切換到暗色模式。
- 增強隱私保護:移除網頁追蹤碼和廣告。
- 練習:嘗試用 TamperMonkey 使用他人於上一階段實作的 UserScript。
- 說明:介紹 TamperMonkey 細節設定。
| Metadata | 說明 | 範例 |
|----------|-----|------|
| `@name` | 腳本的名稱,會顯示在腳本管理對話框中。 | `// @name My Script` |
| `@namespace` | 用於區分不同作者的腳本,可以是 URL 或者是其他唯一的標識。 | `// @namespace http://example.com/` |
| `@description` | 腳本的簡短描述,用於說明其功能。 | `// @description 顯示「Hello World!」` |
| `@version` | 腳本的版本號,有助於版本管理。 | `// @version 1.0` |
| `@author` | 作者姓名或名稱。 | `// @author John Doe` |
| `@include` | 指定腳本要執行的 URL 範圍,支援萬用字元(wildcard)和正規表達式。 | `// @include http://*/*` |
| `@exclude` | 指定腳本不要執行的 URL 範圍。 | `// @exclude http://example.com/*` |
| `@match` | 更精確的 URL 匹配模式,推薦使用於安全性需求較高的腳本。 | `// @match *://*.example.com/*` |
| `@grant` | 定義腳本所需的特權,如 GM_xmlhttpRequest。 | `// @grant GM_xmlhttpRequest` |
| `@run-at` | 定義腳本何時執行,如 document-start。 | `// @run-at document-start` |
| `@require` | 外部資源的 URL,如外部函式庫。 | `// @require https://code.jquery.com/jquery-3.6.0.min.js` |
| `@resource` | 定義外部資源如圖片或 CSS,並可以附加 SRI 整合。 | `// @resource logo https://example.com/logo.png` |
| `@noframes` | 指定腳本不在 iframe 中執行。 | `// @noframes` |
| `@updateURL` | 指定腳本更新時的 URL。 | `// @updateURL https://example.com/script.user.js` |
| `@downloadURL` | 指定更新檢查時下載腳本的 URL。 | `// @downloadURL https://example.com/script.user.js` |
| `@supportURL` | 提供使用者支援和錯誤報告的 URL。 | `// @supportURL https://example.com/support` |
- 練習:嘗試調整介紹過的 TamperMonkey 設定。
- 說明:配合 GitHub 建立方便安裝、更新的方式。
- 範例:[gandolfreddy/GmailHTMLInserter](https://github.com/gandolfreddy/GmailHTMLInserter)。
- 需要將程式碼副檔名,改為 `*.user.js` 結尾。
- 練習:於自己的 GitHub 中,建立方便讓其他人使用的 Repository。
### 搭配 Gemini 1.5 Flash API 實作嵌入翻譯(immersive translation)工具
#### 簡介
- 簡介 Gemini API 服務。
- 簡介瀏覽器中 JavaScript Runtime - window。
- 簡介翻譯工具製作流程。
#### 活動
- 說明:簡介 Gemini API 服務。
- [Gemini 1.5 Flash](https://deepmind.google/technologies/gemini/flash/) 簡介。
「Gemini 1.5 Flash 是一款多模態的模型,能夠處理文本、影像、影片和語音輸入,主要被設計來執行快速和成本效益高的任務。此 API 支援多種語言和設定參數,如 Top p、Top k 和 Temperature 等,適用於需多輪對話和即時反應的應用場景」。(Summarize with ChatGPT)
- Gemini 1.5 Flash 的[使用費用](https://ai.google.dev/pricing)。

- 練習:註冊一個新的 Gemini API 並測試。
- 進入 [Google AI Studio](https://aistudio.google.com/app/prompts/new_chat)。
- 使用 [Get API Key](https://aistudio.google.com/app/apikey) 取得可使用的 API Key。
- 使用以下程式碼,於 DevTools 的 Console 中,測試是否可以使用 Gemini 服務。
```javaScript=
// 設定需要的常數
const GeminiFlashAPI = '這裡放大家申請的 API Key';
const UrlAPI = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${GeminiFlashAPI}`;
const SystemPrompt = `
1. 我是擅長翻譯英文至繁體中文的 AI 助理,我會翻譯任何使用者給我的英文語句至通順的繁體中文語句。
2. 我在翻譯時,會特別留意特定辭彙的使用,會把箭頭左邊的辭彙,替換成箭頭右邊的辭彙,例如:
"""
optimize:「優化」→「最佳化」
function:「函數」→「函式」
return:「返回」→「回傳」
create:「創建」→「建立」
add:「添加」→「加入」
global:「全局」→「全域」
local:「局域」→「區域」
array:「數組」→「陣列」
information:「信息」→「資訊」
"""
3. 我只會給予翻譯結果,不會對額外補充任何文字。
`;
// Source: https://www.bbc.com/news/articles/cv2g4pjj2gwo
const textToTrandslate = `
On the face of it, this past week's Nato summit in Washington has ticked the boxes.
The alliance can show it is bigger and stronger than ever,
its military support for Ukraine appears undiminished
and it has just sent a robust message to China to stop secretly supporting Russia's war on Kyiv.`;
// 發送 request,嘗試取得 Gemini 的翻譯結果
fetch(UrlAPI, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"contents": [
{ "role": "model", "parts": [{ "text": SystemPrompt }] },
{ "role": "user", "parts": [{ "text": textToTrandslate }] }],
})
})
.then(response => response.json())
.then(data => {
// 取得 Gemini 的翻譯結果
const translatedText = data.candidates[0].content.parts[0].text;
// 將翻譯結果顯示在 Console 上
console.log(translatedText);
})
.catch(error => {
// 發生錯誤的顯示訊息
console.error('Error:', error)
alert("發生錯誤,請稍後再次嘗試");
});
```
- 說明:簡介瀏覽器中 JavaScript Runtime - window。
- [Window](https://developer.mozilla.org/en-US/docs/Web/API/Window) - "The Window interface represents a window containing a [DOM](https://developer.mozilla.org/en-US/docs/Glossary/DOM) document; the `document` property points to the [DOM document](https://developer.mozilla.org/en-US/docs/Web/API/Document) loaded in that window."
「Window 介面代表一個包含 DOM 文件的溝通介面(視窗);其中 document 屬性,為指向該視窗中載入的 DOM 文件」。(Summarize with ChatGPT)
- 宿主物件(Host Objects)- 由 JavaScript 執行環境(Runtime)額外提供的物件。
(整理自保哥 - JavaScript 開發實戰:核心概念篇)
- [瀏覽器](https://developer.mozilla.org/en-US/docs/Web/API/Window):Window、所有 DOM 物件。
- [Node.js](https://nodejs.org/api/):Global、Process、OS、Net、Path。
:::info
[What's the difference between host objects and native objects?](https://hackmd.io/@pikachuchu/rkAzzO10d)
:::
- 練習:使用 DevTools 操作瀏覽器中的 JavaScript Runtime - window。
- [Document](https://developer.mozilla.org/en-US/docs/Web/API/Document)
1. 打開 Chrome DevTools。
2. 在 Console 中,使用 `document` 物件來存取和修改網頁內容,例如輸入 `document.title = '新標題';` 並按 Enter。
- [Window: fetch() method](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch)
1. 使用 `fetch()` 方法發送請求。
2. 範例:從公開 API 取得資料。
(來源:[政府資料開放平台](https://data.gov.tw/) - [空氣品質指標(AQI)](https://data.gov.tw/dataset/40448))
```javascript
const URL = 'https://data.moenv.gov.tw/api/v2/aqx_p_432?api_key=e8dd42e6-9b8b-43f8-991e-b3dee723a52d&limit=1000&sort=ImportDate desc&format=JSON';
fetch(URL)
.then(response => response.json())
.then(data => console.log(data));
```
- [Window: getSelection() method](https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection)
1. 使用 `getSelection()` 來存取頁面上選擇的文本。
2. 實作步驟:
- 選擇頁面上的某段文本。
- 在 Console 中輸入 `window.getSelection().toString();` 並按 Enter。
- 說明:簡介翻譯工具製作流程。
- 需求:
- 選取某段文字。
- 按下特定組合的快捷鍵後,可以在原本選取文字的區域附近,加入繁體中文翻譯的結果。
- 分析:
- 選取某段文字。
- *可以使用 `window.getSelection()` 取得選取文本的相關資訊*。
- 按下特定組合的快捷鍵後,可以在原本選取文字的區域附近,加入繁體中文翻譯的結果。
- *可以使用 `addEventListener` 加入按鍵事件,並偵測有哪些按鍵被按下,實作出組合鍵效果*。
- *將取得的選取文字內容,作為 request body 內文,利用 `fetch()`,使用 Gemini API 取得翻譯的 response*。
- *將取得的翻譯結果,包裝成 `<p>` 元素,重新加入該頁面 DOM 中*。
- 以 UserScript 操作頁面 DOM。
- 設計:
- 選定 TamperMonkey 安裝並執行自定義的 UserScript。
- Pseudo Code。
```javascript=
// 加入需要的 Metadata 資訊。
(function () {
'use strict';
// 建立需要的常數與設定
// 翻譯與操作網頁函式
// 使用 window.getSelection() 取得當下選擇的區域與文字
// 如果使用者沒有選取任何文字
// 顯示警告訊息
// 使用 fetch() 發送 request,嘗試取得 Gemini 的翻譯結果
// 如果成功取得翻譯結果
// 取得 Response 中的翻譯結果
// 將翻譯結果以 <p> 標籤包裝
// 將 <p> 標籤加入到原先段落文字之下
// 如果發生錯誤
// 顯示發生的錯誤訊息
// 加入按鍵按下的事件
// 如果 Ctrl+Alt+T 組合鍵有被按下
// 呼叫翻譯與操作網頁函式
})();
```
- 練習:依設計製作此工具。
- 加入需要的 Metadata 資訊。
```javascript=
// ==UserScript==
// @name TranslateE2MForMe
// @namespace http://tampermonkey.net/
// @version 2024-07-22
// @description 選取文字片段後,按下 Ctrl+Alt+T,即可即時翻譯(Powered by Gemini 1.5 flash)。
// @author Gandolfreddy
// @match *://*/*
// @icon https://static-00.iconduck.com/assets.00/translation-icon-512x510-96s0cfbr.png
// ==/UserScript==
```
- 建立需要的常數與設定。
```javascript=
const GeminiFlashAPI = '這裡填入申請的 API Key';
const UrlAPI = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${GeminiFlashAPI}`;
const SystemPrompt = `
1. 我是擅長翻譯英文至繁體中文的 AI 助理,我會翻譯任何使用者給我的英文語句至通順的繁體中文語句。
2. 我在翻譯時,會特別留意特定辭彙的使用,會把箭頭左邊的辭彙,替換成箭頭右邊的辭彙,例如:
"""
optimize:「優化」→「最佳化」
function:「函數」→「函式」
return:「返回」→「回傳」
create:「創建」→「建立」
add:「添加」→「加入」
global:「全局」→「全域」
local:「局域」→「區域」
array:「數組」→「陣列」
information:「信息」→「資訊」
"""
3. 我只會給予翻譯結果,不會對額外補充任何文字。
`;
```
- 翻譯與操作網頁函式。
```javascript=
async function translateE2M(api) {
// 使用 window.getSelection() 取得當下選擇的區域與文字
const selectedRange = window.getSelection();
const textToTrandslate = selectedRange.toString()
// 如果使用者沒有選取任何文字
if (!textToTrandslate.length) {
// 顯示警告訊息
alert("未選擇需要翻譯的語句");
return;
}
// 使用 fetch() 發送 request,嘗試取得 Gemini 的翻譯結果
await fetch(api, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"contents": [
{ "role": "model", "parts": [{ "text": SystemPrompt }] },
{ "role": "user", "parts": [{ "text": textToTrandslate }] }]
})
})
// 如果成功取得翻譯結果
.then(response => response.json())
.then(data => {
// 取得 Response 中的翻譯結果
const translatedText = data.candidates[0].content.parts[0].text;
// 將翻譯結果以 <p> 標籤包裝
const translatedTextNode = document.createElement('p');
translatedTextNode.appendChild(document.createTextNode(translatedText));
// 將 <p> 標籤加入到原先段落文字之下
const textToTrandslateRange = selectedRange.getRangeAt(0);
const textToTrandslateNode = document.createElement('p');
textToTrandslateNode.appendChild(textToTrandslateRange.cloneContents());
textToTrandslateRange.deleteContents();
textToTrandslateRange.insertNode(translatedTextNode);
textToTrandslateRange.insertNode(textToTrandslateNode);
})
// 如果發生錯誤
.catch(error => {
// 顯示發生的錯誤訊息
console.error('Error:', error)
alert("發生錯誤,請稍後再次嘗試");
});
}
```
- 加入按鍵按下的事件。
```javascript=
document.addEventListener('keydown', function (event) {
// 如果 Ctrl+Alt+T 組合鍵有被按下
if (event.ctrlKey && event.altKey && event.key === 't') {
// 呼叫翻譯與操作網頁函式
translateE2M(UrlAPI);
}
}, false);
```
- 完整程式碼。
```javascript=
// ==UserScript==
// @name TestTranslateE2MForMe
// @namespace http://tampermonkey.net/
// @version 2024-07-22
// @description 選取文字片段後,按下 Ctrl+Alt+T,即可即時翻譯(Powered by Gemini 1.5 flash)。
// @author Gandolfreddy
// @match *://*/*
// @icon https://static-00.iconduck.com/assets.00/translation-icon-512x510-96s0cfbr.png
// ==/UserScript==
(function() {
'use strict';
const GeminiFlashAPI = '這裡填入申請的 API Key';
const UrlAPI = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${GeminiFlashAPI}`;
const SystemPrompt = `
1. 我是擅長翻譯英文至繁體中文的 AI 助理,我會翻譯任何使用者給我的英文語句至通順的繁體中文語句。
2. 我在翻譯時,會特別留意特定辭彙的使用,會把箭頭左邊的辭彙,替換成箭頭右邊的辭彙,例如:
"""
optimize:「優化」→「最佳化」
function:「函數」→「函式」
return:「返回」→「回傳」
create:「創建」→「建立」
add:「添加」→「加入」
global:「全局」→「全域」
local:「局域」→「區域」
array:「數組」→「陣列」
information:「信息」→「資訊」
"""
3. 我只會給予翻譯結果,不會對額外補充任何文字。
`;
async function translateE2M(api) {
// 使用 window.getSelection() 取得當下選擇的區域與文字
const selectedRange = window.getSelection();
const textToTrandslate = selectedRange.toString()
// 如果使用者沒有選取任何文字
if (!textToTrandslate.length) {
// 顯示警告訊息
alert("未選擇需要翻譯的語句");
return;
}
// 使用 fetch() 發送 request,嘗試取得 Gemini 的翻譯結果
await fetch(api, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"contents": [
{ "role": "model", "parts": [{ "text": SystemPrompt }] },
{ "role": "user", "parts": [{ "text": textToTrandslate }] }]
})
})
// 如果成功取得翻譯結果
.then(response => response.json())
.then(data => {
// 取得 Response 中的翻譯結果
const translatedText = data.candidates[0].content.parts[0].text;
// 將翻譯結果以 <p> 標籤包裝
const translatedTextNode = document.createElement('p');
translatedTextNode.appendChild(document.createTextNode(translatedText));
// 將 <p> 標籤加入到原先段落文字之下
const textToTrandslateRange = selectedRange.getRangeAt(0);
const textToTrandslateNode = document.createElement('p');
textToTrandslateNode.appendChild(textToTrandslateRange.cloneContents());
textToTrandslateRange.deleteContents();
textToTrandslateRange.insertNode(translatedTextNode);
textToTrandslateRange.insertNode(textToTrandslateNode);
})
// 如果發生錯誤
.catch(error => {
// 顯示發生的錯誤訊息
console.error('Error:', error)
alert("發生錯誤,請稍後再次嘗試");
});
}
document.addEventListener('keydown', function (event) {
// 如果 Ctrl+Alt+T 組合鍵有被按下
if (event.ctrlKey && event.altKey && event.key === 't') {
// 呼叫翻譯與操作網頁函式
translateE2M(UrlAPI);
}
}, false);
})();
```
- 文字樣式加強版。
```javascript=
// ==UserScript==
// @name TranslateE2MForMe
// @namespace http://tampermonkey.net/
// @version 2024-07-22
// @description 選取文字片段後,按下 Ctrl+Alt+T,即可即時翻譯(Powered by Gemini 1.5 flash)。
// @author Gandolfreddy
// @match *://*/*
// @icon https://static-00.iconduck.com/assets.00/translation-icon-512x510-96s0cfbr.png
// @grant GM_addStyle
// ==/UserScript==
(function () {
'use strict';
/* Psuedo Code
加入需要的 Metadata 資訊。
(function () {
'use strict';
加入 style 標籤用以載入開源字體
加入自訂的 CSS
設定需要的常數
用 Gemini 翻譯並調整 DOM 的函式
取得當下選擇的區域
取得當下選擇區域的文字
檢查使用者是否有選取文字
發送 request,嘗試取得 Gemini 的翻譯結果
如果成功取得 response
取得 Gemini 的翻譯結果
將翻譯結果加入到原先段落文字之下
創造可以加入 class 設定的元素
加入設定文字樣式的類別
將原先段落文字與翻譯結果組合在一起
取消選取的區域
如果發生錯誤
顯示發生的錯誤訊息
加入按鍵按下的事件
如果 Ctrl+Alt+T 組合鍵有被按下
呼叫用 Gemini 翻譯並調整 DOM 的函式
})();
*/
// 加入 style 標籤用以載入開源字體
const globalStyle = document.createElement('style');
globalStyle.innerHTML = "@import url('https://fonts.googleapis.com/css2?family=LXGW+WenKai+Mono+TC&display=swap');";
document.body.appendChild(globalStyle);
// 加入自訂的 CSS
GM_addStyle(`
.original {
background-color: rgb(193, 223, 206);
}
.translated {
background-color: rgb(254, 246, 229);
}
.phrase-setting {
border-radius: 5px;
padding: 5px 10px;
width: fit-content;
border: 0.5px solid rgba(0, 0, 0, 0.1);
}
.lxgw-wenkai-mono-tc-regular {
font-family: "LXGW WenKai Mono TC", monospace;
font-weight: 400;
font-style: normal;
}
`);
// 設定需要的常數
const GeminiFlashAPI = '這裡填入申請的 API Key';
const UrlAPI = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${GeminiFlashAPI}`;
const SystemPrompt = `
1. 我是擅長翻譯英文至繁體中文的 AI 助理,我會翻譯任何使用者給我的英文語句至通順的繁體中文語句。
2. 我在翻譯時,會特別留意特定辭彙的使用,會把箭頭左邊的辭彙,替換成箭頭右邊的辭彙,例如:
"""
optimize:「優化」→「最佳化」
function:「函數」→「函式」
return:「返回」→「回傳」
create:「創建」→「建立」
add:「添加」→「加入」
global:「全局」→「全域」
local:「局域」→「區域」
array:「數組」→「陣列」
information:「信息」→「資訊」
"""
3. 我只會給予翻譯結果,不會對額外補充任何文字。
`;
// 用 Gemini 翻譯並調整 DOM
async function translateE2M(api) {
/*
* api: Gemini 1.5 flash
*/
// 取得當下選擇的區域
const selectedRange = window.getSelection();
// 取得當下選擇區域的文字
const textToTrandslate = selectedRange.toString()
// Check if user selects any texts or not.
if (!textToTrandslate.length) {
alert("未選擇需要翻譯的語句");
return;
}
// 發送 request,嘗試取得 Gemini 的翻譯結果
await fetch(api, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"contents": [
{ "role": "model", "parts": [{ "text": SystemPrompt }] },
{ "role": "user", "parts": [{ "text": textToTrandslate }] }],
"safetySettings": [
{
"category": "HARM_CATEGORY_HARASSMENT",
"threshold": "BLOCK_NONE"
},
{
"category": "HARM_CATEGORY_HATE_SPEECH",
"threshold": "BLOCK_NONE"
},
{
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
"threshold": "BLOCK_NONE"
},
{
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
"threshold": "BLOCK_NONE"
}
]
})
})
.then(response => response.json())
.then(data => {
// 取得 Gemini 的翻譯結果
const translatedText = data.candidates[0].content.parts[0].text;
// 將翻譯結果加入到原先段落文字之下
// 創造可以加入 class 設定的元素
const textToTrandslateNode = document.createElement('p');
const textToTrandslateRange = selectedRange.getRangeAt(0);
textToTrandslateNode.appendChild(textToTrandslateRange.cloneContents());
const translatedTextNode = document.createElement('p');
translatedTextNode.appendChild(document.createTextNode(translatedText));
// 加入設定文字樣式的類別
textToTrandslateNode.classList.add("original", "phrase-setting");
translatedTextNode.classList.add("translated", "phrase-setting", "lxgw-wenkai-mono-tc-regular");
// 將原先段落文字與翻譯結果組合在一起
textToTrandslateRange.deleteContents();
textToTrandslateRange.insertNode(translatedTextNode);
textToTrandslateRange.insertNode(textToTrandslateNode);
// 取消選取的區域
selectedRange.removeAllRanges();
})
.catch(error => {
// 顯示發生的錯誤訊息
console.error('Error:', error)
alert("發生錯誤,請稍後再次嘗試");
});
}
// 加入按鍵按下的事件
document.addEventListener('keydown', function (event) {
// 檢查 Ctrl+Alt+T 組合鍵是否有被按下
if (event.ctrlKey && event.altKey && event.key === 't') {
translateE2M(UrlAPI);
}
}, false);
})();
```
## 結語與互動時間
- 總結今日學習要點。
- 開放 Q&A,回答參與者的疑問。