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:
parent
32e6b5be0e
commit
041bafccea
@ -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
|
||||
|
@ -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"),
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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() })
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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])
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user