ptt 號稱窮人的 facebook,只要一台可以上網的破電腦就以可以玩了, 不過老是用 pcman 上 ptt 實在是了無新意,也只能當個一般的鄉民。站長身為一名專業的阿宅工程師,當然要用不同的方法來玩 ptt

大家都知道 facebook 有開放 API,ptt 沒有,這也不能強求它,畢竟他建立在古老的 telnet 協定之上,要在上面種菜是有點困難;不過呢,我們還是可以寫程式跟他互動,這得感謝 Matz 發明了 Ruby 這個強大 script language,還有 Net::Telnet 模組的作者 Wakou Aoyama 讓我們省掉大量研究 telnet 協定細節的時間可以專注在惡搞 ptt 上 XDDD

行前須知:

    1. 懂 Ruby(也不用多懂,站長也只懂個皮毛而已,科科)
    2. ptt 帳號
    3. Ruby 1.8.7
    4. 教學範例

當然有一點請大家特別注意,站長不是教大家hack ptt喔!如果您被查 IP 或是浸水桶都與站長無關,請不要用以下的知識幹非法的事,以免得不嘗失!

說教完了,讓我們踏出第一步...如何用 Ruby 連上 ptt:

夠簡單吧!下一步是觀察ptt到底傳了些什麼東西給我們,Net::Telnet 提供了一個強大的 method - waitfor(/pattern/),如果你對 Perl 稍有認識 會覺得/pattern/很面熟,跟 Perl 一樣,這代表一組 Regular Expression(後面簡稱為 Regex),所以可以利用 Regex 的強大威力偵測 ptt 的輸出,不過第一次連上也不知道 ptt 到底吐什麼東西給我們,就隨便填一些垃圾吧:

看看收到了些什麼...

用紅線標記的部份是一種叫做 ANSI 控制碼的東西,ptt 上的圖形顏色跟動畫都是靠它,所以我們的程式也必須看懂它;不過現在我們最在意的是什麼時候可以輸入帳號?觀察一下 ptt 最後的輸出:

所以我們可以知道讀到結尾劃線那一段就是可以開始輸入帳號了,要如何辨識出這一段有兩個問題要解決:

  1. 如何辨識中文: 好在 ptt 是用 Big5,查查表就可以了
  2. 辨識 ANSI 控制碼:

查了資料後發現這段 ANSI 控制碼的作用如下:

它會有幾種變形 "\x1B[m"、"\x1B[12;12m"、"\x1B[12m"都是合法的,要辨識它請用下面的 Regex:

站長不想多花時間去解釋它的原理,不然這篇文章會岔題變成 Regex 教學大全,有興趣自己去找資料看吧!

現在我們知道如何辨識出請輸入帳號或密碼,實驗一下:

實驗結果:

這是怎麼回事呢?不要氣餒,再回頭看看我們缺了什麼:

這條光棒好像被我們遺忘了,因為 Ruby 的 print() 只會列印標準的 ASCII 可見字元,所以我們在畫面上看不到。研究封包之後發現這一段 ptt 傳給我們的是 \x20\x20\x20...\x08\x08\x08,把它加入到我們的 pattern 中再實驗看看:

這裡順便解釋一下前面建立連線時提到過的'Waittime'參數,這個參數的意義是假如比對成功,不要急著馬上進行下一步,多等一下下('Waittime'設定的時間),讓 server(e.g ptt.cc)多吐一些資料給我們再檢查一下,免得我們的程式看見黑影就開槍(在此不得不佩服 Wakou Aoyama 的確是很有智慧設計了這個參數)。

第二步登入 ptt:

現在我們已經成功的跨出第一步,再來就是輸入帳號密碼,所以我們要學習另外一個 method - cmd():
(註:'String'帶入的參數會自動加上'\n')

我們再次如法炮製,先在比對的部份填些垃圾,然後看看 ptt 輸出什麼:

現在這對我們來說是小菜一碟:

輸入密碼之後沒有意外的話,通常會看到下面的畫面:

Ruby script 這邊看到的是:

我們要辨識的就是紅線框起來的部份:

可是接下來我們要如何送出「任意鍵」呢?在這裡 cmd() 並不好用,因為它的 'String' 參數傳入後會強迫加上 '\n',當然這邊 'String' 帶入空字串當參數也是可以過關(相當於按下 Enter),不過還有其它很多場合必須要能夠送出單一字元(比方說貼文章是 Ctrl+P(\x10))。

好在 Net::Telnet 有提供另一個 method - print(),請不要跟印到螢幕上那個 print() 搞混喔,這個 method 的功能是傳送字串但是不會主動加上 '\n',正好符合我們的需要:

終於進入主功能表,是不是感動到想哭哭 Q_Q 啊?

紅線框起來的部份已經是老梗了,不過多了一個沒看過的\x1B[16;22H,查了資料發現它是:

看起來跟設定游標有關,這大概是為什麼游標會停留在的原因;不過游標停在哪邊都不關我們的事,它不過是我們摸著石頭過河中最後那塊石頭罷了 XD

 

第三步:發表文章

現在我們已經順利進入 ptt,來試試看發篇廢文吧!抱歉說錯了,是發一篇測試文才對,到了這裡假如還從一層一層找下去就太累人了,ptt 有提供一個快速鍵's',按下去會跳出

我們可以用這個方式直接跳到想去的板,哪個板最適合拿來實驗呢?當然是最人畜無害的測試板(test),我們現在來看看怎麼進入這一板:

接著再輸入"test\n"就可以進入測試板,以下是歡迎畫面:

 

不過假如我們再次進入,就會跳過這個歡迎畫面直接顯示文章列表:

也就是說當我們進入測試板時需要同時偵測以下兩種狀況:

解決方法:

這段程式看起來有點沒水準,同樣的事情做兩次。不過這也是沒辦法的事,例如假如你用下面的方式是不會成功的:

因為這邊 $1 將永遠得到 nil,站長猜測應該是 cmd() 裡面還有其它地方用了 Regex 以至於 $1 被洗掉(從 source code 來看也是如此),有個可以稍微改良的方法是把 #{ArticleList} 移到 do...end 裡面去做,不過這要小心這就失去 'Waittime' 參數帶來的效果了。另外對這隻 kuso script 應用的場景來說,速度從來就不是 issue,程式看起來笨一點也無所謂 :)

現在可以發表文章了,模擬 Ctrl+P(\x10):

對應的程式:

我們選"1",然後 ptt 要求你輸入標題:

輸入標題後進入編輯畫面:

接下來是輸入文章,但是要如何知道文章已經輸入完了呢?如果您有輸入文章時有注意的話,會發現 ptt 在您輸入文字後更新游標位址:

對應的程式:

下一步就是輸入 Ctrl+X(\x18) 存檔:

輸入's'儲存,接著討厭的狀況又來了,假如您有簽名檔會看到:

沒有簽名檔就會看到:

套用前面的解法:

欣賞成果:

 

後記

一定有人會說:「不過發篇廢文而已,這麼厚工!」,當然這只是第一步,後面還有更勁爆的應用,敬請期待!

To be continued...