在前一篇我們已經順利使用WebSocket連上Ptt了,接著要做的事情就是讀取Server回傳的內容。我的主要參考來源是@g21589的PTTCrawler。
首先承接前篇的內容,我需要做一個PttClient類別,用來負責接收與Ptt的連線內容,這個Class會是Singleton,因為預期上我會在多個Fragment以及將來須開啟的Service上使用到共通的內容。
class PttClient private constructor(serverUri: URI, header: MutableMap<String, String>) :
WebSocketClient(serverUri, Draft_6455(), header) {
// ...
companion object {
private var instance: PttClient? = null
public fun getInstance(): PttClient {
if (instance == null) {
val hashMap = mutableMapOf<String, String>()
hashMap["origin"] = "https://term.ptt.cc"
instance = PttClient(URI.create("wss://ws.ptt.cc:443/bbs"), hashMap)
instance!!.draft.reset()
instance!!.pipedInputStream = PipedInputStream(4096)
instance!!.pipedOutputStream = PipedOutputStream(instance!!.pipedInputStream)
}
return instance!!
}
}
// ...
}
可以看到我有分別多使用了PipedInputStream以及PipedOutputStream,這是用來持續讀取PTT Server回傳的byte array內容,在PttClient啟動時我有開啟一條Thread來做這件事。
class PttClient private constructor(serverUri: URI, header: MutableMap<String, String>) :
WebSocketClient(serverUri, Draft_6455(), header) {
...
private lateinit var readThread: Thread
private lateinit var pipedInputStream: PipedInputStream
private lateinit var pipedOutputStream: PipedOutputStream
public fun start() {
connectBlocking(30, TimeUnit.SECONDS)
readThread = Thread {
initReader()
}
readThread.start()
}
private var posX = -1
private var posY = -1
private lateinit var currentScreen: Array<CharArray> //用來模擬Ptt一頁的畫面內容
private fun initReader() {
val inputStreamReader = InputStreamReader(pipedInputStream, Charset.forName("big5"))
val bufferedReader = BufferedReader(inputStreamReader)
val pushBackReader = PushbackReader(bufferedReader, 128)
val charArray = CharArray(4096)
posX = -1
posY = -1
currentScreen = Array(72) { CharArray(80) }
while (true) {
val read = pushBackReader.read(charArray)
if (read == -1) {
pushBackReader.close()
bufferedReader.close()
inputStreamReader.close()
break
} else {
var i = 0
while (i < read) {
when (charArray[i]) {
Char(0x08) -> { // BS, 退格
// ...
}
Char(0x0A) -> { // LF, 換行
// ...
}
Char(0x0D) -> { // CR, 回車(Enter)
// ...
}
Char(0x1B) -> { // ESC
// ...
}
else -> {
// ...
}
i++
}
}
}
}
override fun onMessage(bytes: ByteBuffer?) {
super.onMessage(bytes)
Log.d(tag, "onMessage: $bytes")
// 收到byte array內容後直接導入pipedOutputStream內,
// 由initReader function中的內容進行讀取。
bytes?.run {
pipedOutputStream.write(array())
pipedOutputStream.flush()
}
}
...
}
initReader中的內容可直接參考開頭所講的PTTCrawler,裡面主要是分別解析出Ptt的VT100控制碼以及文字內容的部分。另外InputStreamReader中有特別使用big5,是因為Ptt的WebSocket預設是使用big5的文字編碼來傳輸,根據這篇文章在登入時的ID後面加上","能夠將編碼改成UTF-8,不過因為我是懶惰鬼就沒有繼續嘗試了,先單純以big-5把功能做完為主。
解析完成後,以一開始連線成功的畫面print log的話能看到如下:
private fun printScreen(): String {
if (!this::currentScreen.isInitialized) {
return ""
}
val stringBuilder = StringBuilder()
for (i in 0 until 24) {
for (j in 0 until 80) {
if (currentScreen[i][j] != Char(0x00)) {
stringBuilder.append(currentScreen[i][j])
}
}
stringBuilder.append("\n")
}
Log.d(tag, "printScreen: \n$stringBuilder")
return stringBuilder.toString()
}
Result:
感謝您提供PTTCrawler套件,我能問問如果想知道要應用的套件應該要在哪裡找呢?
之前都用純手工慢慢刻出來… 覺得這個套件實在太棒了!