diff options
author | Allan Wang <me@allanwang.ca> | 2017-12-29 19:39:04 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-12-29 19:39:04 -0500 |
commit | 32e6b5be0e662bbac22806bcc87259fd1a2e2ed0 (patch) | |
tree | c97f7ef11b60231bbe7337f5960413b95da0a8c2 /app/src/main/kotlin/com/pitchedapps/frost/facebook | |
parent | 8fee0629c27edee847358efc82309118f3a9a3a5 (diff) | |
download | frost-32e6b5be0e662bbac22806bcc87259fd1a2e2ed0.tar.gz frost-32e6b5be0e662bbac22806bcc87259fd1a2e2ed0.tar.bz2 frost-32e6b5be0e662bbac22806bcc87259fd1a2e2ed0.zip |
Feature/native notifs (#579)
* Improve parser and add zip test
* Remove ActivityOptionsCompat, resolves #555
* Create native notifs
* Add animations
* Add image rounder
* Improve glide transformations
* Add request service
* Fix parser
* Fix parser
* Add thumbnail and fix notification text
* Update parsers and regex
* Auto mark as read
* Add request implementation in pending intent
* Remove unnecessary return data
* Simplify command retrieval
* Use name keys instead
* Revamp all bundle calls
* Fix up thumbnail layout
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps/frost/facebook')
3 files changed, 71 insertions, 37 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt index cc2ca556..e2e9d9e5 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt @@ -7,15 +7,19 @@ import com.mikepenz.iconics.typeface.IIcon import com.mikepenz.material_design_iconic_typeface_library.MaterialDesignIconic import com.pitchedapps.frost.R import com.pitchedapps.frost.fragments.BaseFragment +import com.pitchedapps.frost.fragments.NotificationFragment import com.pitchedapps.frost.fragments.WebFragment import com.pitchedapps.frost.fragments.WebFragmentMenu +import com.pitchedapps.frost.utils.EnumBundle +import com.pitchedapps.frost.utils.EnumBundleCompanion +import com.pitchedapps.frost.utils.EnumCompanion enum class FbItem( @StringRes val titleId: Int, val icon: IIcon, relativeUrl: String, val fragmentCreator: () -> BaseFragment = ::WebFragment -) { +) : EnumBundle<FbItem> { ACTIVITY_LOG(R.string.activity_log, GoogleMaterial.Icon.gmd_list, "me/allactivity"), BIRTHDAYS(R.string.birthdays, GoogleMaterial.Icon.gmd_cake, "events/birthdays"), CHAT(R.string.chat, GoogleMaterial.Icon.gmd_chat, "buddylist"), @@ -28,7 +32,7 @@ enum class FbItem( MENU(R.string.menu, GoogleMaterial.Icon.gmd_menu, "settings", ::WebFragmentMenu), MESSAGES(R.string.messages, MaterialDesignIconic.Icon.gmi_comments, "messages"), NOTES(R.string.notes, CommunityMaterial.Icon.cmd_note, "notes"), - NOTIFICATIONS(R.string.notifications, MaterialDesignIconic.Icon.gmi_globe, "notifications"), + NOTIFICATIONS(R.string.notifications, MaterialDesignIconic.Icon.gmi_globe, "notifications", ::NotificationFragment), ON_THIS_DAY(R.string.on_this_day, GoogleMaterial.Icon.gmd_today, "onthisday"), PAGES(R.string.pages, GoogleMaterial.Icon.gmd_flag, "pages"), PHOTOS(R.string.photos, GoogleMaterial.Icon.gmd_photo, "me/photos"), @@ -39,12 +43,11 @@ enum class FbItem( ; val url = "$FB_URL_BASE$relativeUrl" -} -inline val fbSearch - get() = fbSearch() + override val bundleContract: EnumBundleCompanion<FbItem> + get() = Companion -fun fbSearch(query: String = "a") = "$FB_SEARCH$query" + companion object : EnumCompanion<FbItem>("frost_arg_fb_item", values()) +} -private const val FB_SEARCH = "${FB_URL_BASE}search/top/?q=" fun defaultTabs(): List<FbItem> = listOf(FbItem.FEED, FbItem.MESSAGES, FbItem.NOTIFICATIONS, FbItem.MENU) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRegex.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRegex.kt index 8d625582..24f685be 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRegex.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRegex.kt @@ -13,7 +13,7 @@ package com.pitchedapps.frost.facebook * Matches the fb_dtsg component of a page containing it as a hidden value */ val FB_DTSG_MATCHER: Regex by lazy { Regex("name=\"fb_dtsg\" value=\"(.*?)\"") } -val FB_REV_MATCHER: Regex by lazy{Regex("\"app_version\":\"(.*?)\"")} +val FB_REV_MATCHER: Regex by lazy { Regex("\"app_version\":\"(.*?)\"") } /** * Matches user id from cookie @@ -21,9 +21,9 @@ val FB_REV_MATCHER: Regex by lazy{Regex("\"app_version\":\"(.*?)\"")} val FB_USER_MATCHER: Regex by lazy { Regex("c_user=([0-9]*);") } val FB_EPOCH_MATCHER: Regex by lazy { Regex(":([0-9]+)") } -val FB_NOTIF_ID_MATCHER: Regex by lazy { Regex("notif_id\":([0-9]+)") } +val FB_NOTIF_ID_MATCHER: Regex by lazy { Regex("notif_([0-9]+)") } val FB_MESSAGE_NOTIF_ID_MATCHER: Regex by lazy { Regex("[thread|user]_fbid_([0-9]+)") } -val FB_CSS_URL_MATCHER: Regex by lazy { Regex("url\\([\"|'](.*?)[\"|']\\)") } +val FB_CSS_URL_MATCHER: Regex by lazy { Regex("url\\([\"|']?(.*?)[\"|']?\\)") } operator fun MatchResult?.get(groupIndex: Int) = this?.groupValues?.get(groupIndex) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRequest.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRequest.kt index 2fa20917..51e14097 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRequest.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRequest.kt @@ -11,6 +11,24 @@ import org.apache.commons.text.StringEscapeUtils /** * Created by Allan Wang on 21/12/17. */ +private val authMap: MutableMap<String, RequestAuth> = mutableMapOf() + +fun String.fbRequest(action: RequestAuth.() -> Unit) { + val savedAuth = authMap[this] + if (savedAuth != null) { + savedAuth.action() + } else { + val auth = getAuth() + if (!auth.isValid) { + L.e("Attempted fbrequest with invalid auth") + return + } + authMap.put(this, auth) + L.i(null, "Found auth $auth") + auth.action() + } +} + data class RequestAuth(val userId: Long = -1, val cookie: String = "", val fb_dtsg: String = "", @@ -19,6 +37,22 @@ data class RequestAuth(val userId: Long = -1, get() = userId > 0 && cookie.isNotEmpty() && fb_dtsg.isNotEmpty() && rev.isNotEmpty() } +/** + * Request container with the execution call + */ +class FrostRequest<out T : Any>(val call: Call, private val invoke: (Call) -> T) { + fun invoke() = invoke(call) +} + +private inline fun <T : Any> RequestAuth.frostRequest( + noinline invoke: (Call) -> T, + builder: Request.Builder.() -> Request.Builder // to ensure we don't do anything extra at the end +): FrostRequest<T> { + val request = cookie.requestBuilder() + request.builder() + return FrostRequest(request.call(), invoke) +} + private val client: OkHttpClient by lazy { val builder = OkHttpClient.Builder() if (BuildConfig.DEBUG) @@ -49,12 +83,12 @@ private fun String.requestBuilder() = Request.Builder() private fun Request.Builder.call() = client.newCall(build()) - -fun Pair<Long, String>.getAuth(): RequestAuth { - val (userId, cookie) = this - var auth = RequestAuth(userId, cookie) - val call = cookie.requestBuilder() - .url("https://touch.facebook.com") +fun String.getAuth(): RequestAuth { + var auth = RequestAuth(cookie = this) + val id = FB_USER_MATCHER.find(this)[1]?.toLong() ?: return auth + auth = auth.copy(userId = id) + val call = this.requestBuilder() + .url(FB_URL_BASE) .get() .call() call.execute().body()?.charStream()?.useLines { @@ -62,14 +96,14 @@ fun Pair<Long, String>.getAuth(): RequestAuth { val text = StringEscapeUtils.unescapeEcmaScript(it) val fb_dtsg = FB_DTSG_MATCHER.find(text)[1] if (fb_dtsg != null) { - L.d(null, "fb_dtsg for $userId: $fb_dtsg") + L.d(null, "fb_dtsg for ${auth.userId}: $fb_dtsg") auth = auth.copy(fb_dtsg = fb_dtsg) if (auth.isValid) return auth } val rev = FB_REV_MATCHER.find(text)[1] if (rev != null) { - L.d(null, "rev for $userId: $rev") + L.d(null, "rev for ${auth.userId}: $rev") auth = auth.copy(rev = rev) if (auth.isValid) return auth } @@ -79,7 +113,7 @@ fun Pair<Long, String>.getAuth(): RequestAuth { return auth } -fun RequestAuth.markNotificationRead(notifId: Long): Call { +fun RequestAuth.markNotificationRead(notifId: Long): FrostRequest<Boolean> { val body = listOf( "click_type" to "notification_click", @@ -89,40 +123,37 @@ fun RequestAuth.markNotificationRead(notifId: Long): Call { "__user" to userId ).withEmptyData("m_sess", "__dyn", "__req", "__ajax__") - return cookie.requestBuilder() - .url("${FB_URL_BASE}a/jewel_notifications_log.php") - .post(body.toForm()) - .call() + return frostRequest(::executeForNoError) { + url("${FB_URL_BASE}a/jewel_notifications_log.php") + post(body.toForm()) + } } -private inline fun <T, reified R : Any, O> zip(data: Array<T>, - crossinline mapper: (List<R>) -> O, - crossinline caller: (T) -> R): Single<O> { - val singles = data.map { Single.fromCallable { caller(it) }.subscribeOn(Schedulers.io()) } +inline fun <T, reified R : Any, O> Array<T>.zip(crossinline mapper: (List<R>) -> O, + crossinline caller: (T) -> R): Single<O> { + val singles = map { Single.fromCallable { caller(it) }.subscribeOn(Schedulers.io()) } return Single.zip(singles) { val results = it.mapNotNull { it as? R } mapper(results) } } -fun RequestAuth.markNotificationsRead(vararg notifId: Long) = zip<Long, Boolean, Int>(notifId.toTypedArray(), - { it.count { it } }) { - val response = markNotificationRead(it).execute() - val buffer = CharArray(20) - response.body()?.charStream()?.read(buffer) ?: return@zip false - !buffer.toString().contains("error") -} +fun RequestAuth.markNotificationsRead(vararg notifId: Long) = + notifId.toTypedArray().zip<Long, Boolean, Boolean>( + { it.all { it } }, + { markNotificationRead(it).invoke() }) /** * Execute the call and attempt to check validity + * Valid = not blank & no "error" instance */ -fun Call.executeAndCheck(): Boolean { - val body = execute().body() ?: return false +fun executeForNoError(call: Call): Boolean { + val body = call.execute().body() ?: return false var empty = true body.charStream().useLines { it.forEach { + if (it.contains("error")) return false if (empty && it.isNotEmpty()) empty = false - if (it.contains("error")) return true } } return !empty |