2015年4月22日水曜日

[WEB]第一次寫GreaseMonkey / TamperMonkey script(自動點選網頁按鈕)就上手

某天,點ガールフレンド(仮)的登校按鍵按到手酸,產生強烈的想要自動跑網頁遊戲的慾望。也一直想要研究這個東西很久了。(鍵人是javascript白痴)

GreaseMonkey / TamperMonkey 是FireFox/Chrome所提供的在網頁裡面嵌入script的套件。
藉由讀取網頁的時候嵌入使用者準備好的script,達到執行想要的功能的效果。

以下就用TamperMonkey為例,介紹如何寫一個自動按鍵的script。
前置作業:有程式設計的概念。知道javascript的基本語法,知道html的基本語法。
若是本篇的內容看不懂的話就請多多惡補了。

首先,到chrome市場的安裝TamperMonkey,安裝之後出現一個黑底,下方有兩個白色圈圈的icon。點擊一下會出現選單,選擇「DashBoard」,會出現TamperMonkey的主畫面。

此時按下最左邊那個比較小的icon,代表「增加新的script。」
畫面會變成這樣:

這樣就可以開始寫script了。
假如是要匯入準備好的script檔的話,有兩個方法。第一個就是開一個新的script,然後剪貼script的內容。第二個是把script放在TamperMonkey所指定的script放置目錄。
目錄位置請參考TemperMonkey的FAQ


接著要了解script的使用環境。
建議可以看看這篇介紹GreaseMonkey的文章,來補齊本篇所遺漏的地方。

先從系統自動填好的一些資料開始。
// ==UserScript==
// @name        test
// @namespace    http://your.homepage/
// @version      0.1
// @description  test.
// @author       You
// @include      /www.google.com*
// @grant        none
// ==/UserScript==

每一個script的區域前面都會有一塊這樣的註解區。 紀錄著script的名字,該作用在哪些網頁的資訊。
(以//開頭的都是註解,裡面寫的文字不會被當成是script),這部份紀錄了script的資訊。

一行行來看:
// ==UserScript==
代表script資訊區的開頭。為固定文字請勿修改。

// @name         My Fancy New Userscript
script的名稱。修改之後存檔,你會發現TemperMonkey的「Installed script」的列表裡面的script name會被更改。

// @namespace    http://your.homepage/
會影響「Installed script」的列表裡面的「HomePage」按鈕的超連結目標。跟script關係不大。



// @version      0.1
script的版本。自行定義。



// @description  enter something useful
script的解釋。詳細的描述這個scipt的動作會對之後的管理有幫助。要是這個script要提供給大家使用的話,更要寫清楚,使用者才知道該怎麼用。



// @author       You
script的創作者。寫上你想給其他人知道的名字吧。



// @match        https://www.google.com
// @include      /www.google.com
script該作用在哪些連結的網頁。有兩個條件可用。

  • 「@match」代表該網頁的url必須完全符合。
  • 「@include」會比對網址,只要符合部份的文字就會套用。還可以加上*號來比對中間文字不拘的url。星號在本文裡面用不到所以簡單帶過。不過假如要比對domain name的話,前方要加個「/」才比對得到。不知原因...
@match跟@include可以多行定義。
本文使用google的首頁當範例,所以填上「// @include      /www.google.com」



// @grant        none
這個值用不到所以不動。


// ==/UserScript==
代表script資訊區的結束。為固定文字請勿修改。


以上的資料都設定好之後,準備開始寫script內文。
script的執行順序是一行行的往下跑。也因此,變數宣告要放在程式碼前面,要不然變數會抓不到。(函式的話倒是沒有前後順序問題。javascript在被解讀的時候,系統會把一個個的function給預儲起來,方便其他script有呼叫的時候執行)

在前面寫的url 比對條件符合的時候,網頁下載load完之後,網頁開始正常運作之前就會被執行。接著網頁本身的script開始運作。(只顯示一半的狀態下,不會運作。)


一開始,完全不知道該怎麼寫javascript。雖然語法跟其他的程式語言接近,但是要怎麼得到想要的資料跟結果,還是不清楚。
得先找找有哪些javascript有提供的工具可用。經過漫長的資料蒐集時間(google,前面提到的「深入淺出GreaseMonkey」,stack overflow等等),找到了以下的武器:


  • 在網頁的指定區塊上面按滑鼠右鍵,選擇「Inspect Elements」就可以看到該區塊所描述的html tag,可以快速鎖定要操作的目標。在本文拿來尋找「I'm Feeling Lucky」按鈕。這時跳出來的視窗就是拿來觀察html tag變化的最好的除錯工具。接下來會一直用到script完成。
  • 續前點,上方的tab裡面有一個「Console」,會拿來觀察script執行的狀況。若是不知道script有沒有執行,
    在想要觀察的地方加上「console.log("想要顯示的文字" + 參數1 + ... )」就可以在Console裡面看到「想要顯示的文字」。要觀察參數內容的話就後面一直加下去。
  • Console也會顯示要是script有寫錯的時候,系統提示的錯誤。
    畢竟GreaseMonkey / TamperMonkey這類在網頁上面加上javascript的工具,在語法錯誤的時候所顯示的行數,是整個html文件裡面的行數。看不出來是自己寫的script的那一行有錯誤。
  • document.evaluate(),可以使用xpath表示法,過濾符合條件的html tag。沒有這工具的話要過濾特殊條件的tag會很複雜... 像是「下一層的tag的屬性為xxx的上層tag」
  • window.location.href 參數: 可以取得現在網頁連結的url,作為觀察url變化的工具。
  • setInterval(function(), 時間單位(千分之一秒)); 以一個固定的時間間隔執行指定的函式。
  • setTimeOut(function(), 時間單位(千分之一秒)); 可以在指定的時間後執行指定的函式一次。
  • MutationObserver:可以觀察指定html tag內容,有改變的時候觸發函式。
  • Node.attributes:取得該html tag的attributes,為一個列表,取用的時候需要使用迴圈一個個找。
  • location.reload():重新讀取網頁。更新資料的時候使用。
  • 觸發指定tag的滑鼠左鍵動作的函式。寫在下面的程式碼內。它可以通用在<a href=...><div><input type=submit>等等常用的tag上面。把這段程式碼直接貼在script的註解區的下方。

function anchorclick(node)
{
    var evt = document.createEvent("MouseEvents"); 
    evt.initMouseEvent("click", true, true, window, 
                       0, 0, 0, 0, 0, false, false, false, false, 0, null);
    var allowDefault = node.dispatchEvent(evt);
}


首先從最簡單的開始。來寫個固定時間觸發按鈕的動作吧。
第一個問題就是,觸發的按鈕要怎尋找?
把滑鼠游標移動到「I'm Feeling Lucky」按鈕,按滑鼠右鍵,選擇「Inspect Elements」。
會發現圖中那一行被標記起來。所以傳送按鈕動作的目標就是這個<input name="btnI">。

知道了目標之後,接下來要在script內取得這個目標。
在這裡使用document.evaluate()來取得。它可以幫忙尋找指定條件的tag。
因為動作是固定的,一樣做成函式方便之後套用。
把下面程式碼一樣貼在script的註解區的下方。

function xpath(query) {
    return document.evaluate(query, document, null,
                             XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
}

接著就開始來找吧。其實就只要下面幾行,只是裡面已經包含不少需要具備程式概念才能理解的語法了。

var btnITags =xpath("//input[@name='btnI']");
if ( btnITags.snapshotLength > 0)
{
    var thisDiv =  btnITags.snapshotItem(0);
    setInterval(function(){
            anchorclick(thisDiv);
    }, 5000);
}

第一行:取得所有<input name="btnI">的tags。 xpath是一種描述html/xml tag的搜尋條件的語法。
請參考此文件: http://zvon.org/xxl/XPathTutorial/General/examples.html
在這邊只解釋上面的這一行的意義:
開頭的「//」:尋找所有符合條件的tag。
加上「input」,成為「//input」的解釋:尋找所有的<input>tag。
加上「[@name='btnI']」,成為「//input[@name='btnI']」的解釋:尋找所有的<input name="btnI">tag。
@代表指定的條件為xml tag的屬性(attributes)。中括號是同時指定tag name跟屬性的時候,必須使用的格式。因為一定要先寫tag再寫屬性。

document.evaluate()尋找的回傳結果會是一個陣列。因為依據搜尋條件的不同,可能會有多個tag符合條件。
在此例,這樣的搜尋條件只會找到一個符合的。這當然也是思考如何決定搜尋條件的目標。
只有一個的話就直接使用它,不須再做其他判斷。

第二行是測試回傳的結果是不是至少一個。是的話才執行裡面的動作。
第四行,用一個變數thisDiv取出回傳陣列的第0個資料(陣列從0開始),也就是「I'm Feeling Lucky」按鈕的tag所在。
第五行,setTimeout()的參數是函式。裡面放了第六行「對此tag傳送滑鼠點擊動作」。
第七行的「5000」代表執行的時間點為5秒後(單位為ms)。

存檔,打開google的首頁,看看五秒之後按鈕會不會被觸發吧。會的話,恭喜你成功了。
下面是完整的script。

// ==UserScript==
// @name        test
// @namespace    http://your.homepage/
// @version      0.1
// @description  test.
// @author       You
// @include      /www.google.com*
// @grant        none
// ==/UserScript==



function xpath(query) {
    return document.evaluate(query, document, null,
                             XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
}

function anchorclick(node)
{
    var evt = document.createEvent("MouseEvents"); 
    evt.initMouseEvent("click", true, true, window, 
                       0, 0, 0, 0, 0, false, false, false, false, 0, null);
    var allowDefault = node.dispatchEvent(evt);
}

var btnITags =xpath("//input[@name='btnI']");
if ( btnITags.snapshotLength > 0)
{
    var thisDiv =  btnITags.snapshotItem(0);
    setTimeout(function(){
            anchorclick(thisDiv);
    }, 5000);
}

0 件のコメント:

コメントを投稿