aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt')
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt154
1 files changed, 154 insertions, 0 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
new file mode 100644
index 00000000..e3e77c5c
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt
@@ -0,0 +1,154 @@
+package com.pitchedapps.frost.facebook.requests
+
+import com.pitchedapps.frost.BuildConfig
+import com.pitchedapps.frost.facebook.*
+import com.pitchedapps.frost.utils.L
+import io.reactivex.Single
+import io.reactivex.schedulers.Schedulers
+import okhttp3.*
+import okhttp3.logging.HttpLoggingInterceptor
+import org.apache.commons.text.StringEscapeUtils
+
+/**
+ * Created by Allan Wang on 21/12/17.
+ */
+private val authMap: MutableMap<String, RequestAuth> = mutableMapOf()
+
+/**
+ * Synchronously fetch [RequestAuth] from cookie
+ * [action] will only be called if a valid auth is found.
+ * Otherwise, [fail] will be called
+ */
+fun String.fbRequest(fail: () -> Unit = {}, 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 fail()
+ }
+ authMap.put(this, auth)
+ L.i(null, "Found auth $auth")
+ auth.action()
+ }
+}
+
+/**
+ * Underlying container for all fb requests
+ */
+data class RequestAuth(val userId: Long = -1,
+ val cookie: String = "",
+ val fb_dtsg: String = "",
+ val rev: String = "") {
+ val isValid
+ 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)
+}
+
+internal 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)
+ builder.addInterceptor(HttpLoggingInterceptor()
+ .setLevel(HttpLoggingInterceptor.Level.BASIC))
+ builder.build()
+}
+
+internal fun List<Pair<String, Any?>>.toForm(): FormBody {
+ val builder = FormBody.Builder()
+ forEach { (key, value) ->
+ val v = value?.toString() ?: ""
+ builder.add(key, v)
+ }
+ return builder.build()
+}
+
+internal fun List<Pair<String, Any?>>.withEmptyData(vararg key: String): List<Pair<String, Any?>> {
+ val newList = toMutableList()
+ newList.addAll(key.map { it to null })
+ return newList
+}
+
+private fun String.requestBuilder() = Request.Builder()
+ .header("Cookie", this)
+ .header("User-Agent", USER_AGENT_BASIC)
+ .cacheControl(CacheControl.FORCE_NETWORK)
+
+fun Request.Builder.call() = client.newCall(build())!!
+
+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 {
+ it.forEach {
+ val text = StringEscapeUtils.unescapeEcmaScript(it)
+ val fb_dtsg = FB_DTSG_MATCHER.find(text)[1]
+ if (fb_dtsg != null) {
+ 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 ${auth.userId}: $rev")
+ auth = auth.copy(rev = rev)
+ if (auth.isValid) return auth
+ }
+ }
+ }
+
+ return auth
+}
+
+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)
+ }
+}
+
+/**
+ * Execute the call and attempt to check validity
+ * Valid = not blank & no "error" instance
+ */
+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
+ }
+ }
+ return !empty
+}
+
+fun getJsonUrl(call: Call): String? {
+ val body = call.execute().body() ?: return null
+ val url = FB_JSON_URL_MATCHER.find(body.string())[1] ?: return null
+ return StringEscapeUtils.unescapeEcmaScript(url)
+}