diff options
author | Allan Wang <me@allanwang.ca> | 2017-12-31 00:42:49 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-12-31 00:42:49 -0500 |
commit | 3076d9a97c203497aec1415d8ac6037d10eebb46 (patch) | |
tree | cdeb914fa95f2b230f6327be3e1527d15b41dc94 /app/src/main/kotlin/com/pitchedapps/frost/facebook/requests | |
parent | 041bafcceadbd5203e95f2692899ac903dd2e883 (diff) | |
download | frost-3076d9a97c203497aec1415d8ac6037d10eebb46.tar.gz frost-3076d9a97c203497aec1415d8ac6037d10eebb46.tar.bz2 frost-3076d9a97c203497aec1415d8ac6037d10eebb46.zip |
feature/menu-parser (#582)
* Test menu parser
* Add menu fragment implementation
* Test proguard
* Clean up
* Use async
* Use invoke
* Try without proguard
* Try 2
* Add fallback logic
* Use normal notification event
* Add custom event flag
* Add rest of menu fragment data
* Ensure fallback works
* Update docs
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps/frost/facebook/requests')
-rw-r--r-- | app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt | 3 | ||||
-rw-r--r-- | app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Menu.kt | 158 |
2 files changed, 160 insertions, 1 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt index e3e77c5c..cefece36 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt @@ -19,7 +19,8 @@ private val authMap: MutableMap<String, RequestAuth> = mutableMapOf() * [action] will only be called if a valid auth is found. * Otherwise, [fail] will be called */ -fun String.fbRequest(fail: () -> Unit = {}, action: RequestAuth.() -> Unit) { +fun String?.fbRequest(fail: () -> Unit = {}, action: RequestAuth.() -> Unit) { + if (this == null) return fail() val savedAuth = authMap[this] if (savedAuth != null) { savedAuth.action() diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Menu.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Menu.kt new file mode 100644 index 00000000..59f87fbd --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Menu.kt @@ -0,0 +1,158 @@ +package com.pitchedapps.frost.facebook.requests + +import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.MapperFeature +import com.fasterxml.jackson.databind.ObjectMapper +import com.pitchedapps.frost.facebook.FB_URL_BASE +import com.pitchedapps.frost.facebook.formattedFbUrl +import com.pitchedapps.frost.utils.L +import okhttp3.Call +import org.apache.commons.text.StringEscapeUtils +import org.jsoup.Jsoup +import java.io.IOException + +/** + * Created by Allan Wang on 29/12/17. + */ +fun RequestAuth.getMenuData(): FrostRequest<MenuData?> { + + val body = listOf( + "fb_dtsg" to fb_dtsg, + "__user" to userId + ).withEmptyData("m_sess", "__dyn", "__req", "__ajax__") + + return frostRequest(::parseMenu) { + url("${FB_URL_BASE}bookmarks/flyout/body/?id=u_0_2") + post(body.toForm()) + } + +} + +fun parseMenu(call: Call): MenuData? { + val fullString = call.execute().body()?.string() ?: return null + var jsonString = fullString.substringAfter("bookmarkGroups", "") + .substringAfter("[", "") + + if (jsonString.isBlank()) return null + + jsonString = "{ \"data\" : [${StringEscapeUtils.unescapeEcmaScript(jsonString)}" + + val mapper = ObjectMapper() + .disable(MapperFeature.AUTO_DETECT_SETTERS) + + return try { + val data = mapper.readValue(jsonString, MenuData::class.java) + + // parse footer content + + val footer = fullString.substringAfter("footerMarkup", "") + .substringAfter("{", "") + .substringBefore("}", "") + + val doc = Jsoup.parseBodyFragment(StringEscapeUtils.unescapeEcmaScript( + StringEscapeUtils.unescapeEcmaScript(footer))) + val footerData = mutableListOf<MenuFooterItem>() + val footerSmallData = mutableListOf<MenuFooterItem>() + + doc.select("a[href]").forEach { + val text = it.text() + it.parent() + if (text.isEmpty()) return@forEach + val href = it.attr("href").formattedFbUrl + val item = MenuFooterItem(name = text, url = href) + if (it.parent().tag().name == "span") + footerSmallData.add(item) + else + footerData.add(item) + } + + return data.copy(footer = MenuFooter(footerData, footerSmallData)) + } catch (e: IOException) { + L.e(e, "Menu parse fail") + null + } +} + +@JsonIgnoreProperties(ignoreUnknown = true) +data class MenuData(val data: List<MenuHeader> = emptyList(), + val footer: MenuFooter = MenuFooter()) { + + @JsonCreator constructor( + @JsonProperty("data") data: List<MenuHeader>? + ) : this(data ?: emptyList(), MenuFooter()) + + fun flatMapValid() : List<MenuItemData> { + val items = mutableListOf<MenuItemData>() + data.forEach { + if (it.isValid) items.add(it) + items.addAll(it.visible.filter(MenuItem::isValid)) + } + + items.addAll(footer.data.filter(MenuFooterItem::isValid)) + items.addAll(footer.smallData.filter(MenuFooterItem::isValid)) + + return items + } + +} + +interface MenuItemData { + val isValid: Boolean +} + +@JsonIgnoreProperties(ignoreUnknown = true) +data class MenuHeader(val id: String? = null, + val header: String? = null, + val visible: List<MenuItem> = emptyList(), + val all: List<MenuItem> = emptyList()) : MenuItemData { + + @JsonCreator constructor( + @JsonProperty("id") id: String?, + @JsonProperty("header") header: String?, + @JsonProperty("visible") visible: List<MenuItem>?, + @JsonProperty("all") all: List<MenuItem>?, + @JsonProperty("fake") fake: Boolean? + ) : this(id, header, visible ?: emptyList(), all ?: emptyList()) + + override val isValid: Boolean + get() = header != null +} + +@JsonIgnoreProperties(ignoreUnknown = true) +data class MenuItem(val id: String? = null, + val name: String? = null, + val pic: String? = null, + val url: String? = null, + val count: Int = 0, + val countDetails: String? = null) : MenuItemData { + + @JsonCreator constructor( + @JsonProperty("id") id: String?, + @JsonProperty("name") name: String?, + @JsonProperty("pic") pic: String?, + @JsonProperty("url") url: String?, + @JsonProperty("count") count: Int?, + @JsonProperty("count_details") countDetails: String?, + @JsonProperty("fake") fake: Boolean? + ) : this(id, name, pic?.formattedFbUrl, url?.formattedFbUrl, count ?: 0, countDetails) + + override val isValid: Boolean + get() = name != null && url != null +} + +data class MenuFooter(val data: List<MenuFooterItem> = emptyList(), + val smallData: List<MenuFooterItem> = emptyList()) { + + val hasContent + get() = data.isNotEmpty() || smallData.isNotEmpty() + +} + +data class MenuFooterItem(val name: String? = null, + val url: String? = null, + val isSmall: Boolean = false) : MenuItemData { + override val isValid: Boolean + get() = name != null && url != null +}
\ No newline at end of file |