diff options
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps/frost/parsers')
-rw-r--r-- | app/src/main/kotlin/com/pitchedapps/frost/parsers/FrostParser.kt | 44 | ||||
-rw-r--r-- | app/src/main/kotlin/com/pitchedapps/frost/parsers/MessageParser.kt | 85 |
2 files changed, 129 insertions, 0 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/parsers/FrostParser.kt b/app/src/main/kotlin/com/pitchedapps/frost/parsers/FrostParser.kt new file mode 100644 index 00000000..86b280a8 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/parsers/FrostParser.kt @@ -0,0 +1,44 @@ +package com.pitchedapps.frost.parsers + +/** + * Created by Allan Wang on 2017-10-06. + * + * Interface for a given parser + * Use cases should be attached as delegates to objects that implement this interface + */ +interface FrostParser<T> { + fun parse(text: String?): T? + fun debug(text: String?): String +} + +internal abstract class FrostParserBase<T> : FrostParser<T> { + override final fun parse(text: String?): T? + = if (text == null) null else parseImpl(text) + + protected abstract fun parseImpl(text: String): T? + + override final fun debug(text: String?): String { + val result = mutableListOf<String>() + result.add("Testing parser for ${this::class.java.simpleName}") + if (text == null) { + result.add("Input is null") + return result.joinToString("\n") + } + val output = parseImpl(text) + if (output == null) { + result.add("Output is null") + return result.joinToString("\n") + } + debugImpl(output, result) + return result.joinToString("\n") + } + + protected abstract fun debugImpl(data: T, result: MutableList<String>) +} + +object FrostRegex { + val epoch = Regex(":([0-9]+)") + val notifId = Regex("notif_id\":([0-9]+)") + val messageNotifId = Regex("thread_fbid_([0-9]+)") + val profilePicture = Regex("url\\(\"(.*?)\"\\)") +}
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/parsers/MessageParser.kt b/app/src/main/kotlin/com/pitchedapps/frost/parsers/MessageParser.kt new file mode 100644 index 00000000..dbe2c0bb --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/parsers/MessageParser.kt @@ -0,0 +1,85 @@ +package com.pitchedapps.frost.parsers + +import com.pitchedapps.frost.facebook.formattedFbUrl +import com.pitchedapps.frost.facebook.formattedFbUrlCss +import com.pitchedapps.frost.utils.L +import org.apache.commons.text.StringEscapeUtils +import org.jsoup.Jsoup +import org.jsoup.nodes.Element + +/** + * Created by Allan Wang on 2017-10-06. + * + * In Facebook, messages are passed through scripts and loaded into view via react afterwards + * We can parse out the content we want directly and load it ourselves + * + */ +object MessageParser : FrostParser<Triple<List<FrostThread>, FrostLink?, List<FrostLink>>> by MessageParserImpl() + +data class FrostThread(val id: Int, val img: String, val title: String, val time: Long, val url: String, val unread: Boolean, val content: String?) + +data class FrostLink(val text: String, val href: String) + +private class MessageParserImpl : FrostParserBase<Triple<List<FrostThread>, FrostLink?, List<FrostLink>>>() { + + override fun parseImpl(text: String): Triple<List<FrostThread>, FrostLink?, List<FrostLink>>? { + var content = StringEscapeUtils.unescapeEcmaScript(text) + val begin = content.indexOf("id=\"threadlist_rows\"") + if (begin <= 0) { + L.d("Threadlist not found") + return null + } + content = content.substring(begin) + val end = content.indexOf("</script>") + if (end <= 0) { + L.d("Script tail not found") + return null + } + content = content.substring(0, end).substringBeforeLast("</div>") + val body = Jsoup.parseBodyFragment("<div $content") + val threadList = body.getElementById("threadlist_rows") + val threads: List<FrostThread> = threadList.getElementsByAttributeValueContaining("id", "thread_fbid_") + .mapNotNull { parseMessage(it) } + val seeMore = parseLink(body.getElementById("see_older_threads")) + val extraLinks = threadList.nextElementSibling().select("a") + .mapNotNull { parseLink(it) } + return Triple(threads, seeMore, extraLinks) + } + + private fun parseMessage(element: Element): FrostThread? { + val a = element.getElementsByTag("a").first() ?: return null + val abbr = element.getElementsByTag("abbr") + println(abbr.attr("data-store")) + val epoch = FrostRegex.epoch.find(abbr.attr("data-store")) + ?.groupValues?.getOrNull(1)?.toLongOrNull() ?: -1L + //fetch id + val id = FrostRegex.messageNotifId.find(element.id()) + ?.groupValues?.getOrNull(1)?.toLongOrNull() ?: System.currentTimeMillis() + val content = element.select("span.snippet").firstOrNull()?.text()?.trim() + //fetch convo pic + val p = element.select("i.img[style*=url]") + val pUrl = FrostRegex.profilePicture.find(p.attr("style"))?.groups?.get(1)?.value?.formattedFbUrl ?: "" + L.v("url", a.attr("href")) + return FrostThread( + id = id.toInt(), + img = pUrl.formattedFbUrlCss, + title = a.text(), + time = epoch, + url = a.attr("href").formattedFbUrlCss, + unread = !element.hasClass("acw"), + content = content + ) + } + + private fun parseLink(element: Element?): FrostLink? { + val a = element?.getElementsByTag("a")?.first() ?: return null + return FrostLink(a.text(), a.attr("href")) + } + + override fun debugImpl(data: Triple<List<FrostThread>, FrostLink?, List<FrostLink>>, result: MutableList<String>) { + result.addAll(data.first.map { it.toString() }) + result.add("See more link:") + result.add("\t${data.second}") + result.addAll(data.third.map { it.toString() }) + } +} |