1
0
mirror of https://github.com/AllanWang/Frost-for-Facebook.git synced 2024-11-08 20:12:39 +01:00

Feature/image retrieval (#581)

* Refactor

* Attempt new content

* Clean up to make compile friendly

* Update docs
This commit is contained in:
Allan Wang 2017-12-29 23:37:10 -05:00 committed by GitHub
parent 32e6b5be0e
commit 041bafccea
12 changed files with 145 additions and 33 deletions

View File

@ -170,6 +170,8 @@ dependencies {
implementation"com.mikepenz:fastadapter-extensions:${FAST_ADAPTER_EXTENSIONS}@aar"
implementation "com.github.bumptech.glide:okhttp3-integration:${GLIDE}"
//noinspection GradleDependency
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:${LEAK_CANARY}"
//noinspection GradleDependency

View File

@ -20,6 +20,7 @@ enum class FbItem(
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"),

View File

@ -24,6 +24,7 @@ val FB_EPOCH_MATCHER: Regex by lazy { Regex(":([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_JSON_URL_MATCHER: Regex by lazy { Regex("\"(http.*?)\"") }
operator fun MatchResult?.get(groupIndex: Int) = this?.groupValues?.get(groupIndex)

View File

@ -1,6 +1,7 @@
package com.pitchedapps.frost.facebook
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
@ -13,7 +14,12 @@ import org.apache.commons.text.StringEscapeUtils
*/
private val authMap: MutableMap<String, RequestAuth> = mutableMapOf()
fun String.fbRequest(action: RequestAuth.() -> Unit) {
/**
* 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()
@ -21,7 +27,7 @@ fun String.fbRequest(action: RequestAuth.() -> Unit) {
val auth = getAuth()
if (!auth.isValid) {
L.e("Attempted fbrequest with invalid auth")
return
return fail()
}
authMap.put(this, auth)
L.i(null, "Found auth $auth")
@ -29,6 +35,9 @@ fun String.fbRequest(action: RequestAuth.() -> Unit) {
}
}
/**
* Underlying container for all fb requests
*/
data class RequestAuth(val userId: Long = -1,
val cookie: String = "",
val fb_dtsg: String = "",
@ -40,11 +49,11 @@ data class RequestAuth(val userId: Long = -1,
/**
* Request container with the execution call
*/
class FrostRequest<out T : Any>(val call: Call, private val invoke: (Call) -> T) {
class FrostRequest<out T : Any?>(val call: Call, private val invoke: (Call) -> T) {
fun invoke() = invoke(call)
}
private inline fun <T : Any> RequestAuth.frostRequest(
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> {
@ -61,7 +70,7 @@ private val client: OkHttpClient by lazy {
builder.build()
}
private fun List<Pair<String, Any?>>.toForm(): FormBody {
internal fun List<Pair<String, Any?>>.toForm(): FormBody {
val builder = FormBody.Builder()
forEach { (key, value) ->
val v = value?.toString() ?: ""
@ -70,7 +79,7 @@ private fun List<Pair<String, Any?>>.toForm(): FormBody {
return builder.build()
}
private fun List<Pair<String, Any?>>.withEmptyData(vararg key: String): List<Pair<String, Any?>> {
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
@ -81,7 +90,7 @@ private fun String.requestBuilder() = Request.Builder()
.header("User-Agent", USER_AGENT_BASIC)
.cacheControl(CacheControl.FORCE_NETWORK)
private fun Request.Builder.call() = client.newCall(build())
fun Request.Builder.call() = client.newCall(build())!!
fun String.getAuth(): RequestAuth {
var auth = RequestAuth(cookie = this)
@ -113,22 +122,6 @@ fun String.getAuth(): RequestAuth {
return auth
}
fun RequestAuth.markNotificationRead(notifId: Long): FrostRequest<Boolean> {
val body = listOf(
"click_type" to "notification_click",
"id" to notifId,
"target_id" to "null",
"fb_dtsg" to fb_dtsg,
"__user" to userId
).withEmptyData("m_sess", "__dyn", "__req", "__ajax__")
return frostRequest(::executeForNoError) {
url("${FB_URL_BASE}a/jewel_notifications_log.php")
post(body.toForm())
}
}
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()) }
@ -138,11 +131,6 @@ inline fun <T, reified R : Any, O> Array<T>.zip(crossinline mapper: (List<R>) ->
}
}
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
@ -158,3 +146,9 @@ fun executeForNoError(call: Call): Boolean {
}
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)
}

View File

@ -0,0 +1,67 @@
package com.pitchedapps.frost.facebook.requests
import com.bumptech.glide.Priority
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.data.DataFetcher
import com.pitchedapps.frost.facebook.FB_URL_BASE
import okhttp3.Call
import okhttp3.Request
import java.io.IOException
import java.io.InputStream
/**
* Created by Allan Wang on 29/12/17.
*/
fun RequestAuth.getFullSizedImage(fbid: Long) = frostRequest(::getJsonUrl) {
url("${FB_URL_BASE}photo/view_full_size/?fbid=$fbid&__ajax__=&__user=$userId")
get()
}
class ImageFbidFetcher(private val fbid: Long,
private val cookie: String) : DataFetcher<InputStream> {
@Volatile private var cancelled: Boolean = false
private var urlCall: Call? = null
private var inputStream: InputStream? = null
private fun DataFetcher.DataCallback<in InputStream>.fail(msg: String) {
onLoadFailed(RuntimeException(msg))
}
override fun getDataClass(): Class<InputStream> = InputStream::class.java
override fun getDataSource(): DataSource = DataSource.REMOTE
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
cookie.fbRequest(fail = { callback.fail("Invalid auth") }) {
if (cancelled) return@fbRequest callback.fail("Cancelled")
val url = getFullSizedImage(fbid).invoke() ?: return@fbRequest callback.fail("Null url")
if (cancelled) return@fbRequest callback.fail("Cancelled")
urlCall = Request.Builder().url(url).get().call()
inputStream = try {
urlCall?.execute()?.body()?.byteStream()
} catch (e: IOException) {
null
}
callback.onDataReady(inputStream)
}
}
override fun cleanup() {
try {
inputStream?.close()
} catch (e: IOException) {
} finally {
inputStream = null
}
}
override fun cancel() {
cancelled = true
urlCall?.cancel()
urlCall = null
cleanup()
}
}

View File

@ -0,0 +1,27 @@
package com.pitchedapps.frost.facebook.requests
import com.pitchedapps.frost.facebook.FB_URL_BASE
/**
* Created by Allan Wang on 29/12/17.
*/
fun RequestAuth.markNotificationRead(notifId: Long): FrostRequest<Boolean> {
val body = listOf(
"click_type" to "notification_click",
"id" to notifId,
"target_id" to "null",
"fb_dtsg" to fb_dtsg,
"__user" to userId
).withEmptyData("m_sess", "__dyn", "__req", "__ajax__")
return frostRequest(::executeForNoError) {
url("${FB_URL_BASE}a/jewel_notifications_log.php")
post(body.toForm())
}
}
fun RequestAuth.markNotificationsRead(vararg notifId: Long) =
notifId.toTypedArray().zip<Long, Boolean, Boolean>(
{ it.all { it } },
{ markNotificationRead(it).invoke() })

View File

@ -9,9 +9,9 @@ import android.content.Context
import android.content.Intent
import android.os.BaseBundle
import android.os.PersistableBundle
import com.pitchedapps.frost.facebook.RequestAuth
import com.pitchedapps.frost.facebook.fbRequest
import com.pitchedapps.frost.facebook.markNotificationRead
import com.pitchedapps.frost.facebook.requests.RequestAuth
import com.pitchedapps.frost.facebook.requests.fbRequest
import com.pitchedapps.frost.facebook.requests.markNotificationRead
import com.pitchedapps.frost.utils.EnumBundle
import com.pitchedapps.frost.utils.EnumBundleCompanion
import com.pitchedapps.frost.utils.EnumCompanion

View File

@ -1,6 +1,6 @@
package com.pitchedapps.frost
import com.pitchedapps.frost.facebook.zip
import com.pitchedapps.frost.facebook.requests.zip
import com.pitchedapps.frost.injectors.CssHider
import org.junit.Test
import kotlin.test.assertTrue

View File

@ -41,6 +41,12 @@ class FbRegexTest {
assertEquals(id, FB_MESSAGE_NOTIF_ID_MATCHER.find(data)[1]?.toLong(), "thread_fbid mismatch")
val userData = "threadlist_row_other_user_fbid_${id}thread_fbid_"
assertEquals(id, FB_MESSAGE_NOTIF_ID_MATCHER.find(userData)[1]?.toLong(), "user_fbid mismatch")
}
@Test
fun jsonUrlRegex() {
val url = "https://www.hello.world"
val data = "\"uri\":\"$url\"}"
assertEquals(url, FB_JSON_URL_MATCHER.find(data)[1])
}
}

View File

@ -1,5 +1,8 @@
package com.pitchedapps.frost.facebook
import com.pitchedapps.frost.facebook.requests.getAuth
import com.pitchedapps.frost.facebook.requests.getFullSizedImage
import com.pitchedapps.frost.facebook.requests.markNotificationRead
import com.pitchedapps.frost.internal.AUTH
import com.pitchedapps.frost.internal.COOKIE
import com.pitchedapps.frost.internal.USER_ID
@ -48,4 +51,12 @@ class FbRequestTest {
AUTH.markNotificationRead(notifId).call.assertNoError()
}
@Test
fun fullSizeImage() {
val fbid = 10155966932992838L // google's current cover photo
val url = AUTH.getFullSizedImage(fbid).invoke()
println(url)
assertTrue(url?.startsWith("https://scontent") == true)
}
}

View File

@ -1,6 +1,8 @@
package com.pitchedapps.frost.internal
import com.pitchedapps.frost.facebook.*
import com.pitchedapps.frost.facebook.requests.RequestAuth
import com.pitchedapps.frost.facebook.requests.getAuth
import com.pitchedapps.frost.utils.frostJsoup
import org.junit.Assume
import java.io.File

View File

@ -26,6 +26,7 @@ CRASHLYTICS=2.8.0
DBFLOW=4.2.3
EXOMEDIA=4.1.0
FAST_ADAPTER_EXTENSIONS=3.0.3
GLIDE=4.4.0
IAB=1.0.44
IICON_COMMUNITY=2.0.46.1
IICON_MATERIAL=2.2.0.4