aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com/pitchedapps/frost/facebook
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps/frost/facebook')
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt17
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRegex.kt6
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRequest.kt85
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