為Chrome插件Google Dictionary進(jìn)行的一次小手術(shù)

1. 起因

經(jīng)常需要閱讀英文文檔,冷不丁的會碰到一些不懂的單詞,之前的做法是打開一個谷歌翻譯的網(wǎng)頁在一旁放著,有需要就切換過來查單詞牡借。但是來回的切換著實有點麻煩,就想著有沒有一些Chrome翻譯插件袭异,一番搜尋钠龙,找了兩款比較心儀的Chrome插件:一個是good word guide的Instant Dictionary,它的優(yōu)點是直接顯示英文的釋義御铃,因為很多時候直接看英文的釋義更容易理解一個單詞碴里。例如對于a bank of memory might be assigned to each CPU這句話,bank這單詞上真,不管你套用“銀行”咬腋、“湖畔”、“岸”等意思睡互,感覺都怪怪的根竿,而如果直接看它的英文釋義a set or series of similar things, especially electrical or electronic devices, grouped together in rows,一下子就能明白他說的是一組特性相同的內(nèi)存就珠;除了Instant Dictionary寇壳,另一個就是Google的Google Dictionary,因為我就比較中意谷歌翻譯妻怎】茄祝可是心儀歸心儀,這兩款插件都有個致命的缺陷:由于眾所周知的原因蹂季,他們倆都沒法鏈接到他們的服務(wù)器冕广。
對于Instant Dictionary疏日,這顯然已經(jīng)沒救了偿洁,但是對于Google Dictionary,我覺得還可以搶救一下沟优。為什么呢涕滋?因為谷歌翻譯在中國是可以直接打開的,對應(yīng)的域名是translate.google.cn挠阁,既然都是Google家的東西宾肺,連不上translate.google.com溯饵,那是否可以讓它去連translate.google.cn?查看了Google Dictionary配置選項锨用,并沒有切換到國內(nèi)服務(wù)器的選項丰刊,這樣一來,想在國內(nèi)用增拥,就只能手動改一改了啄巧。

胡適曾經(jīng)說過:大膽假設(shè),小心求證掌栅。我們的假設(shè)就是最終單詞翻譯的請求是通過HTTP進(jìn)行的秩仆,并且域名使用的是translate.google.com

2. 行動

好猾封,說干就干澄耍,只要思想不滑坡,方法總比困難多晌缘。

2.1. 獲取插件

Chrome插件的后綴名是.crx齐莲,其實就是一個壓縮包。常用的壓縮軟件一般都能解壓磷箕,解壓出來的是一堆JavaScript文件以及其他相關(guān)的一些文件铅搓。我有想過它為什么不直接用.zip做后綴?最后得到了一個我自己比較信服的答案搀捷,使用.zip等常用后綴就相當(dāng)于在挑釁:“你來解壓我靶顷!”嫩舟∏夂妫總會有好事者解壓一探究竟,并且這樣逼格也就不那么高了家厌。當(dāng)然播玖,最后.crx依舊沒能阻擋好事者。
但是想解壓饭于,那也要先拿到蜀踏。遺憾的是插件商店在中國正常情況下也是沒法訪問的,好在你不能訪問掰吕,別人也不能訪問果覆。但是不能訪問并不代表需求就消失了,需求始終都在殖熟,只有愛會消失局待。使用必應(yīng)搜索chrome extension downloader就能找到一堆下載插件的網(wǎng)站,例如https://crxdown.com/。緊接著钳榨,雖然我們不能直接訪問到插件舰罚,但我們可以網(wǎng)上搜索該插件,得到它的確切地址薛耻,這樣我們就能將它下載下來营罢。例如在搜索Google dictionary后找到來源為Chrome Web Store的結(jié)果,右鍵選擇復(fù)制鏈接地址就能得到對應(yīng)的插件地址饼齿。

搜索Google Dictionary的結(jié)果

2.2. 找入口

得到了插件愤钾,下面我們要做的就是找到入口。正常情況下候醒,出于安全的考慮Chrome是沒法安裝我們下載好了的插件的能颁,即便是來源正經(jīng)也不行,我們要進(jìn)入開發(fā)者模式倒淫。在瀏覽器地址欄輸入chrome://extensions/伙菊,在打開的界面中勾選開發(fā)者模式。

打開開發(fā)者模式

然后敌土,點擊load unpacked加載我們已經(jīng)解壓好了的插件镜硕,只需要選擇包含manifest.json這層的文件夾就行。
加載解壓后的Chrome插件

想要改代碼返干,那就必須先理解代碼兴枯,想要理解代碼,首先要找到一個合適的切入點矩欠。理論上财剖,manifest.json是整個插件的元文件,里面肯定會有描述整個插件的入口文件之類的癌淮。但這個門檻有點高躺坟,使用這種方法應(yīng)該是對Chrome插件的開發(fā)比較熟悉的,我這種門外漢算了乳蓄。除此之外咪橙,還有另一個方法,那就是直接搜索我們能看到的東西虚倒。

Google Dictionary彈窗界面

運行該插件之后我們發(fā)現(xiàn)美侦,觸發(fā)翻譯的條件是我們輸入需要翻譯的內(nèi)容后回車或者點擊藍(lán)色按鈕,正常情況下兩種方法最終都會調(diào)用同一個函數(shù)魂奥。因為我們可以從這兩個動作入手菠剩。通過在所有文件中搜索Define這個單詞,最終我們在browser_action.html發(fā)現(xiàn)了這個按鈕的標(biāo)簽捧弃。

    <div id="form">
      <input type="text" id="query-field"><button id="define-btn" class="btn btn-primary" value="Define">Define</button>
    </div

從代碼中看到赠叼,這里有個叫qeury-field的輸入以及一個叫define-btn的按鈕擦囊,和我們看到的一致违霞。這個按鈕標(biāo)簽定義了id屬性卻沒有定義點擊的回調(diào)函數(shù)嘴办,那么很大概率在JavaScript代碼中會使用類似get_element_by_id()這類的函數(shù)獲取該標(biāo)簽并為其綁定回調(diào)函數(shù),因此我們接著使用它的id define-btn進(jìn)行搜索买鸽。

搜索define-btn結(jié)果

一共搜索到三條內(nèi)容涧郊,一條在html文件中,也就是我們剛看到的眼五,一條位于css文件中妆艘,說明是設(shè)置顯示樣式的不用管,剩下一條在js文件中看幼,果然和我們想的一樣批旺。打開該文件,返現(xiàn)代碼已經(jīng)經(jīng)過混淆擠作一團(tuán)了诵姜,正常人估計沒幾個能這么讀代碼汽煮,因此需要稍加處理。
簡直亂碼

處理方式很簡單棚唆,隨便找個JavaScript代碼美化網(wǎng)站進(jìn)行下格式調(diào)整暇赤,這里我使用的是https://beautifier.io/。進(jìn)過美化宵凌,代碼變成了下面的樣子鞋囊。

    d = document.getElementById("define-btn");
    e = document.getElementById("query-field");
    f = document.getElementById("status-box");
    g = document.getElementById("status-msg");
    h = document.getElementById("status-search-link");
    k = document.getElementById("usage-tip");
    n = document.getElementById("meaning");
    k.display = "block";
    k.innerText = "Tip: Select text on any webpage, then click the Google Dictionary button to view the definition of your selection.";
    document.getElementById("year").innerText = (new Date).getFullYear();
    p(h);
    p(document.getElementById("options-link"));
    e.focus();
    d.addEventListener("click", r, !1);
    e.addEventListener("keydown", function(a) {
        13 === a.keyCode && r()
    }, !1);

可以看到,在這里它找到了輸入框瞎惫,將它命名為e溜腐,找到了按鈕將它命名為d
這樣瓜喇,我們就算摸到門了逗扒。

2.3. 代碼梳理

經(jīng)過前面的探尋我們已經(jīng)找到了代碼入口,可以看到欠橘,當(dāng)我們點擊按鈕矩肩,最終會調(diào)用一個名為r的函數(shù)。好吧肃续,讓我們看看這個r長啥樣黍檩。

        r = function() {
            var a;
            if (a = e.value.replace(/^\s+|\s+$/g, "")) g.innerHTML = "Searching...", f.style.display = "block", h.style.display = "none", k.style.display = "none", n.style.display = "none", d.disabled = !0, c++, chrome.runtime.sendMessage({
                type: "fetch_html",
                eventKey: c,
                query: a
            }, q)
        },

從代碼中看到,首先這個r函數(shù)對輸入框中的字符做了簡單的處理始锚,最終傳遞給了chrome.runtime.sendMessage()刽酱。代碼到此戛然而止,在源代碼中再也找不到chrome.runtime.sendMessage()的定義了瞧捌,既然源代碼中找不到棵里,那么只能是別的庫中的API或者是系統(tǒng)API润文。從字面上我們知道它把參數(shù)發(fā)了出去,但是發(fā)給誰了呢殿怜?發(fā)給了服務(wù)器典蝌?沒道理啊。我們可以斷定的是變量a中肯定只包含了需要查詢的字符串头谜,而c的值是數(shù)字1骏掀,這些參數(shù)不足以告訴別的API你的目的。

既然猜測是外部API柱告,那就去搜索引擎搜索chrome.runtime.sendMessage吧截驮。
最終搜索得到完全匹配的結(jié)果都位于Google域名之下,很遺憾沒法訪問际度,但是在MDN Web Docs上看到了runtime.sendMessage的介紹葵袭。

image.png

最后顯示Chrome支持了這個API,那么八九不離十乖菱,就是它了坡锡。從介紹中我么知道,當(dāng)使用了runtime.sendMessage之后块请,會有一個叫runtime.onMessage的API對它進(jìn)行響應(yīng)娜氏,我們需要接著搜索。

最后發(fā)現(xiàn)兩個文件使用runtime.onMessage墩新,它們分別是backgrpund.min.js以及content.min.js贸弥。同樣的,我們對它們的內(nèi)容進(jìn)行了美化海渊。

搜索結(jié)果

對它們一個一個的梳理绵疲,最終確定了chrome.runtime.onMessage.addListener(G)這條語句中注冊的G函數(shù)最終會響應(yīng)之前點擊按鈕后調(diào)用的sendMessage()函數(shù),因為每個注冊的函數(shù)都會先通過type參數(shù)判定這是不是他們該響應(yīng)的臣疑,從中我們看到G函數(shù)判定的是fetch_raw以及fetch_html盔憨,恰好我們之前看到的sendMessage()函數(shù)中傳遞進(jìn)來的參數(shù)是fetch_html

image.png

最終讯沈,在梳理G函數(shù)的過程中郁岩,見到了我們夢寐以求一個字符串https://translate.google.com/translate_a/t?client=dict-chrome-ex&sl=,并且看到了XMLHttpRequest的使用缺狠,證明我們的假設(shè)是對的问慎。最后一番苦尋之后,只是將.com改成.cn挤茄。暗自祈禱如叼,希望能成功。

        F = function(a, c, b) {
            a = "https://translate.google.cn/translate_a/t?client=dict-chrome-ex&sl=" + c + "&tl=" + q.language + "&q=" + encodeURIComponent(a);
            var d = new XMLHttpRequest;
            d.open("GET", a, !0);
            d.onload = function() {
                var f = null;
                if (200 === this.status) try {
                    f = JSON.parse(d.response)
                } catch (l) {}
                return b(f)
            };
            d.send()
        },

遺憾的是穷劈,事情并沒有想象的那么順利笼恰,插件沒能查出詞來踊沸。

2.4. Debug

怎么回事?一開始就猜錯了么社证?

修改了域名之后逼龟,并沒有順利的得到結(jié)果。很沮喪猴仑,很無奈审轮,但是既然都到這份上了肥哎,不搞它一搞又心有不甘辽俗。于是乎,打開了調(diào)試窗口(鼠標(biāo)移動到插件圖標(biāo)上右鍵選擇inspect popup)篡诽。一番調(diào)試下拉崖飘,發(fā)現(xiàn)代碼根本沒有跳轉(zhuǎn)進(jìn)入關(guān)鍵的F函數(shù)當(dāng)中,而使得代碼能夠執(zhí)行F函數(shù)最重要的一個名叫p的變量的值始終是false杈女。問題的關(guān)鍵就是這個p什么時候會變成true朱浴。繼續(xù)梳理代碼,發(fā)現(xiàn)當(dāng)一個叫initBackgroundPageAsync的函數(shù)執(zhí)行的時候达椰,p就有可能被賦值true翰蠢,并且這是p唯一變成true的地方。

    window.initBackgroundPageAsync = function(a) {
        gapi.config.update("googleapis.config/root", "https://dictionaryextension-pa.googleapis.com");
        gapi.client.setApiKey("AIzaSyA6EEtrDCfBkHV8uU2lgGY-N383ZgAOo7Y");
        var c = function() {
                2 > Object.keys(r).length || (p = !0, a && a())
            },
            b = function(d) {
                Mustache.parse(d);
                return function(f) {
                    return Mustache.render(d, f)
                }
            };
        Q("templates/browser_action_dict.html", function(d) {
            r.browser_action_dict = b(d);
            c()
        });
        Q("templates/browser_action_tran.html", function(d) {
            r.browser_action_tran = b(d);
            c()
        })
    };

問題又變成了查看該函數(shù)何時被調(diào)用啰劲。最終發(fā)現(xiàn)一個名叫background.html的文件加載https://apis.google.com/js/client.js這個文件完成后會執(zhí)行梁沧。

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <script type="text/javascript" src="lang_map.min.js"></script>
    <script type="text/javascript" src="mustache.js"></script>
    
    <script type="text/javascript" src="background.min.js"></script>
    <script type="text/javascript"
            src="https://apis.google.com/js/client.js?onload=initBackgroundPageAsync">
    </script>
    <script type="text/javascript" src="ga.js"></script>
  </head>
  <body>
  </body>
</html>

查了一下,https://apis.google.com/js/client.js這個文件用于使用Google全家桶的蝇裤,在中國其實沒啥用并且會帶來麻煩廷支,因為根本訪問不。由于訪問不了栓辜,就不可能加載成功恋拍;而加載不成功就不會執(zhí)行initBackgroundPageAsync,因此決定手動執(zhí)行initBackgroundPageAsync藕甩。其實也就是在background.min.js文件的末尾增加一行代碼:

window.initBackgroundPageAsync();

本以為到此大功告成施敢,可是現(xiàn)實還是狠狠地給了一巴掌,雖然代碼終于執(zhí)行了F函數(shù)狭莱,但依舊沒有得到想要的結(jié)果僵娃。嘿我這暴脾氣,跟它杠上了贩毕。

這次調(diào)試返現(xiàn)悯许,代碼在奇怪的地方卡住了,定睛一看辉阶,在一個叫D的函數(shù)里面出不來了先壕。

        D = function(a, c, b) {
            var d = c;
            "en-uk" == c && (d = "en");
            var f = window["gdx.LANG_TO_CORPUS"][c];
            f || (f = c);
            a = {
                path: "v1/dictionaryExtensionData",
                params: {
                    term: a,
                    language: d,
                    corpus: f
                }
            };
            (f = window["gdx.CORPUS_TO_COUNTRY"][f]) && (a.params.country = f);
            gapi.client.request(a).execute(function(l) {
                var e =
                    l.status;
                if (e && 200 != e) b(null);
                else {
                    l = H(l, "dictionaryData[0]");
                    if (!l) return b(null);
                    var m = function(g) {
                        if (!g.senseFamilies) return 0;
                        g = g.senseFamilies;
                        for (var h = g.length, n = 0; n < g.length; n++) g[n].senses && (h += .1 * g[n].senses.length);
                        return h
                    };
                    e = function(g, h) {
                        return m(h) - m(g)
                    };
                    l.entries && (l.entries = l.entries.sort(e));
                    l.webDefinitions && (l.hasWebDefinitions = !0);
                    b(l)
                }
            })
        },

仔細(xì)分析了下代碼瘩扼,發(fā)現(xiàn)它又使用谷歌的API去請求一些奇奇怪怪的東西,然后調(diào)用一個回調(diào)函數(shù)b垃僚。但是有意思的是集绰,當(dāng)請求失敗了,也就是返回碼不是200的時候谆棺,它依然會調(diào)用回調(diào)函數(shù)b栽燕,只不過傳了個空參數(shù)。既然如此,有一種可能是傳遞進(jìn)去的這個參數(shù)是錦上添花的立镶。那么我們就假設(shè)他次次都請求失敗味榛,所以我們將代碼改成了下面的樣子:

D = function(a, c, b) {
            b(null);
            return;
}

重新加載插件運行,呀蔼啦,成功了!

image.png

3. 總結(jié)

我為什么一開始就沒想到直接搜索translate.google.com這個字符串呢仰猖?以為抄了近路捏肢,最后回過頭看還是拐了拐。
代碼見文后鏈接饥侵。

歡1迎2關(guān)3注4個5人6微7信8公9眾0號: 愛碼士1024
源碼 | 原理 | 語言 | 工具

4. Resources

[1] https://github.com/zmychou/google-dictionary-chrome-extension
[2] https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/sendMessage
[3] https://beautifier.io/
[4] https://crxdown.com/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鸵赫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子躏升,更是在濱河造成了極大的恐慌辩棒,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件煮甥,死亡現(xiàn)場離奇詭異盗温,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)成肘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門卖局,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人双霍,你說我怎么就攤上這事砚偶。” “怎么了洒闸?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵染坯,是天一觀的道長。 經(jīng)常有香客問我丘逸,道長单鹿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任深纲,我火速辦了婚禮仲锄,結(jié)果婚禮上劲妙,老公的妹妹穿的比我還像新娘。我一直安慰自己儒喊,他們只是感情好镣奋,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著怀愧,像睡著了一般侨颈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芯义,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天哈垢,我揣著相機(jī)與錄音,去河邊找鬼毕贼。 笑死温赔,一個胖子當(dāng)著我的面吹牛蛤奢,可吹牛的內(nèi)容都是我干的鬼癣。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼啤贩,長吁一口氣:“原來是場噩夢啊……” “哼待秃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起痹屹,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤章郁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后志衍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體暖庄,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年楼肪,在試婚紗的時候發(fā)現(xiàn)自己被綠了培廓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡春叫,死狀恐怖肩钠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情暂殖,我是刑警寧澤价匠,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站呛每,受9級特大地震影響踩窖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晨横,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一洋腮、第九天 我趴在偏房一處隱蔽的房頂上張望廉沮。 院中可真熱鬧,春花似錦徐矩、人聲如沸滞时。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坪稽。三九已至,卻和暖如春鳞骤,著一層夾襖步出監(jiān)牢的瞬間窒百,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工豫尽, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留篙梢,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓美旧,卻偏偏與公主長得像渤滞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子榴嗅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容