Day04 - Parsing Ptt

在前一篇我们已经顺利使用WebSocket连上Ptt了,接着要做的事情就是读取Server回传的内容。我的主要参考来源是@g21589PTTCrawler

首先承接前篇的内容,我需要做一个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:
https://ithelp.ithome.com.tw/upload/images/20210918/20124602NEb1aIbRYb.png


<<:  Ruby on Rails CRUD 之 U(Update)

>>:  Day 4— 自动化回信机(1) 前置作业

Day 14 试用 Heroku

今天来试用 Heroku,并请使用 Heroku 的 Python 范例。 在这之前我已经有注册过 ...

Day 0x14 - 订单查询 (Part2 : View)

0x1 前言 昨天把 Controller 跟 Route 建立好了,今天来针对回覆内容做更新,并简...

Swift纯Code之旅 Day28. 「新增闹钟功能(1) - Struct使用、取得UIDatePicker值」

前言 如果只有画面像的话,那也太弱了吧! 赶紧来实作新增闹钟的功能,做完拿去炫耀给边身边的人看! 实...

ViewModel 中的 UI 状态 - 以 Selection state 为例

在一个应用程序中,有着各种不同类型的资料,这些不同的资料也有属於他们的生命周期,有些资料就像之前介绍...

实战-我是如何挑到飙股

这几天有朋友跟我说,不要再写劝世文了啦,直接实战说明最快! 好吧,既然如此,我就直接解说,我是如何选...