mirror of
https://github.com/AllanWang/Frost-for-Facebook.git
synced 2024-11-09 12:32:30 +01:00
Fix view full image (#882)
* Test including full photo viewer * Test cookie in glide * Fix parser and add redirects to view full image * Update changelog
This commit is contained in:
parent
d9e2562267
commit
c5e769deab
@ -24,9 +24,11 @@ import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.facebook.FB_IMAGE_ID_MATCHER
|
||||
import com.pitchedapps.frost.facebook.get
|
||||
import com.pitchedapps.frost.facebook.requests.call
|
||||
import com.pitchedapps.frost.facebook.requests.getFullSizedImageUrl
|
||||
import com.pitchedapps.frost.facebook.requests.requestBuilder
|
||||
import com.pitchedapps.frost.utils.*
|
||||
import com.sothree.slidinguppanel.SlidingUpPanelLayout
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jetbrains.anko.activityUiThreadWithContext
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.uiThread
|
||||
@ -86,8 +88,18 @@ class ImageActivity : KauBaseActivity() {
|
||||
private val L = KauLoggerExtension("Image", com.pitchedapps.frost.utils.L)
|
||||
}
|
||||
|
||||
private val cookie: String? by lazy { intent.getStringExtra(ARG_COOKIE) }
|
||||
|
||||
val imageUrl: String by lazy { intent.getStringExtra(ARG_IMAGE_URL).trim('"') }
|
||||
|
||||
private val trueImageUrl: String by lazy {
|
||||
val result = if (!imageUrl.isIndirectImageUrl) imageUrl
|
||||
else cookie?.getFullSizedImageUrl(imageUrl)?.blockingGet() ?: imageUrl
|
||||
if (result != imageUrl)
|
||||
L.v { "Launching with true url $result" }
|
||||
result
|
||||
}
|
||||
|
||||
private val imageText: String? by lazy { intent.getStringExtra(ARG_TEXT) }
|
||||
|
||||
// a unique image identifier based on the id (if it exists), and its hash
|
||||
@ -205,12 +217,13 @@ class ImageActivity : KauBaseActivity() {
|
||||
return File.createTempFile(imageFileName, IMG_EXTENSION, frostDir)
|
||||
}
|
||||
|
||||
private fun getImageResponse() = Request.Builder()
|
||||
.url(imageUrl)
|
||||
private fun getImageResponse(): Response = cookie.requestBuilder()
|
||||
.url(trueImageUrl)
|
||||
.get()
|
||||
.call()
|
||||
.execute()
|
||||
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun downloadImageTo(file: File) {
|
||||
val body = getImageResponse().body()
|
||||
@ -263,7 +276,7 @@ class ImageActivity : KauBaseActivity() {
|
||||
override fun onDestroy() {
|
||||
tempFile = null
|
||||
val purge = System.currentTimeMillis() - PURGE_TIME
|
||||
tempDir.listFiles(FileFilter { it.isFile && it.lastModified() < purge }).forEach {
|
||||
tempDir.listFiles(FileFilter { it.isFile && it.lastModified() < purge })?.forEach {
|
||||
it.delete()
|
||||
}
|
||||
super.onDestroy()
|
||||
|
@ -20,6 +20,10 @@ import io.reactivex.subjects.SingleSubject
|
||||
*/
|
||||
object FbCookie {
|
||||
|
||||
/**
|
||||
* Retrieves the facebook cookie if it exists
|
||||
* Note that this is a synchronized call
|
||||
*/
|
||||
inline val webCookie: String?
|
||||
get() = CookieManager.getInstance().getCookie(FB_URL_BASE)
|
||||
|
||||
|
@ -26,6 +26,7 @@ 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.*?)\"") }
|
||||
val FB_IMAGE_ID_MATCHER: Regex by lazy { Regex("fbcdn.*?/[0-9]+_([0-9]+)_") }
|
||||
val FB_REDIRECT_URL_MATCHER: Regex by lazy { Regex("url=(.*?fbcdn.*?)\"") }
|
||||
|
||||
operator fun MatchResult?.get(groupIndex: Int) = this?.groupValues?.get(groupIndex)
|
||||
|
||||
|
@ -43,6 +43,7 @@ class FbUrlFormatter(url: String) {
|
||||
L.e(e) { "Failed url formatting" }
|
||||
return url
|
||||
}
|
||||
cleanedUrl = cleanedUrl.replace("&", "&")
|
||||
if (changed && !cleanedUrl.contains("?")) //ensure we aren't missing '?'
|
||||
cleanedUrl = cleanedUrl.replaceFirst("&", "?")
|
||||
val qm = cleanedUrl.indexOf("?")
|
||||
@ -54,8 +55,6 @@ class FbUrlFormatter(url: String) {
|
||||
cleanedUrl = cleanedUrl.substring(0, qm)
|
||||
}
|
||||
discardableQueries.forEach { queries.remove(it) }
|
||||
//final cleanup
|
||||
misc.forEach { (k, v) -> cleanedUrl = cleanedUrl.replace(k, v, true) }
|
||||
if (cleanedUrl.startsWith("/")) cleanedUrl = FB_URL_BASE + cleanedUrl.substring(1)
|
||||
cleanedUrl = cleanedUrl.replaceFirst(".facebook.com//", ".facebook.com/") //sometimes we are given a bad url
|
||||
L.v { "Formatted url from $url to $cleanedUrl" }
|
||||
@ -101,8 +100,6 @@ class FbUrlFormatter(url: String) {
|
||||
VIDEO_REDIRECT
|
||||
)
|
||||
|
||||
val misc = arrayOf("&" to "&")
|
||||
|
||||
val discardableQueries = arrayOf("ref", "refid", "acontext", "SharedWith")
|
||||
|
||||
val converter = listOf(
|
||||
|
@ -6,7 +6,10 @@ import com.pitchedapps.frost.rx.RxFlyweight
|
||||
import com.pitchedapps.frost.utils.L
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import okhttp3.*
|
||||
import okhttp3.Call
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import org.apache.commons.text.StringEscapeUtils
|
||||
|
||||
@ -92,12 +95,16 @@ internal fun List<Pair<String, Any?>>.withEmptyData(vararg key: String): List<Pa
|
||||
return newList
|
||||
}
|
||||
|
||||
private fun String.requestBuilder() = Request.Builder()
|
||||
.header("Cookie", this)
|
||||
.header("User-Agent", USER_AGENT_BASIC)
|
||||
.cacheControl(CacheControl.FORCE_NETWORK)
|
||||
internal fun String?.requestBuilder(): Request.Builder {
|
||||
val builder = Request.Builder()
|
||||
.header("User-Agent", USER_AGENT_BASIC)
|
||||
if (this != null)
|
||||
builder.header("Cookie", this)
|
||||
// .cacheControl(CacheControl.FORCE_NETWORK)
|
||||
return builder
|
||||
}
|
||||
|
||||
fun Request.Builder.call() = httpClient.newCall(build())!!
|
||||
fun Request.Builder.call(): Call = httpClient.newCall(build())
|
||||
|
||||
fun String.getAuth(): RequestAuth {
|
||||
L.v { "Getting auth for ${hashCode()}" }
|
||||
|
@ -11,9 +11,8 @@ import com.bumptech.glide.load.model.MultiModelLoaderFactory
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.bumptech.glide.signature.ObjectKey
|
||||
import com.pitchedapps.frost.facebook.FB_IMAGE_ID_MATCHER
|
||||
import com.pitchedapps.frost.facebook.FB_URL_BASE
|
||||
import com.pitchedapps.frost.facebook.get
|
||||
import com.pitchedapps.frost.facebook.*
|
||||
import io.reactivex.Maybe
|
||||
import okhttp3.Call
|
||||
import okhttp3.Request
|
||||
import java.io.IOException
|
||||
@ -27,6 +26,18 @@ fun RequestAuth.getFullSizedImage(fbid: Long) = frostRequest(::getJsonUrl) {
|
||||
get()
|
||||
}
|
||||
|
||||
val test: () -> InputStream? = { null }
|
||||
|
||||
/**
|
||||
* Attempts to get the fbcdn url of the supplied image redirect url
|
||||
*/
|
||||
fun String.getFullSizedImageUrl(url: String): Maybe<String?> = Maybe.fromCallable {
|
||||
val redirect = requestBuilder().url(url).get().call()
|
||||
.execute().body()?.string() ?: return@fromCallable null
|
||||
return@fromCallable FB_REDIRECT_URL_MATCHER.find(redirect)[1]?.formattedFbUrl
|
||||
?: return@fromCallable null
|
||||
}.onErrorComplete()
|
||||
|
||||
/**
|
||||
* Request loader for a potentially hd version of a url
|
||||
* In this case, each url may potentially return an id,
|
||||
|
@ -10,6 +10,11 @@ import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
||||
import com.bumptech.glide.load.resource.bitmap.CircleCrop
|
||||
import com.bumptech.glide.module.AppGlideModule
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.pitchedapps.frost.facebook.FbCookie
|
||||
import com.pitchedapps.frost.utils.L
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Response
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 28/12/17.
|
||||
@ -35,6 +40,22 @@ fun <T> RequestBuilder<T>.transform(vararg transformation: BitmapTransformation)
|
||||
class FrostGlideModule : AppGlideModule() {
|
||||
|
||||
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
||||
// registry.replace(GlideUrl::class.java,
|
||||
// InputStream::class.java,
|
||||
// OkHttpUrlLoader.Factory(getFrostHttpClient()))
|
||||
// registry.prepend(HdImageMaybe::class.java, InputStream::class.java, HdImageLoadingFactory())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFrostHttpClient(): OkHttpClient =
|
||||
OkHttpClient.Builder().addInterceptor(FrostCookieInterceptor()).build()
|
||||
|
||||
class FrostCookieInterceptor : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val origRequest = chain.request()
|
||||
val cookie = FbCookie.webCookie ?: return chain.proceed(origRequest)
|
||||
L.v { "Add cookie to req $cookie" }
|
||||
val request = origRequest.newBuilder().addHeader("Cookie", cookie).build()
|
||||
return chain.proceed(request)
|
||||
}
|
||||
}
|
@ -44,6 +44,7 @@ const val ARG_URL = "arg_url"
|
||||
const val ARG_USER_ID = "arg_user_id"
|
||||
const val ARG_IMAGE_URL = "arg_image_url"
|
||||
const val ARG_TEXT = "arg_text"
|
||||
const val ARG_COOKIE = "arg_cookie"
|
||||
|
||||
inline fun <reified T : Activity> Context.launchNewTask(cookieList: ArrayList<CookieModel> = arrayListOf(), clearStack: Boolean = false) {
|
||||
startActivity<T>(clearStack, intentBuilder = {
|
||||
@ -83,11 +84,12 @@ fun Context.launchWebOverlayBasic(url: String) = launchWebOverlayImpl<WebOverlay
|
||||
private fun Context.fadeBundle() = ActivityOptions.makeCustomAnimation(this,
|
||||
android.R.anim.fade_in, android.R.anim.fade_out).toBundle()
|
||||
|
||||
fun Context.launchImageActivity(imageUrl: String, text: String?) {
|
||||
fun Context.launchImageActivity(imageUrl: String, text: String? = null, cookie: String? = null) {
|
||||
startActivity<ImageActivity>(intentBuilder = {
|
||||
putExtras(fadeBundle())
|
||||
putExtra(ARG_IMAGE_URL, imageUrl)
|
||||
putExtra(ARG_TEXT, text)
|
||||
putExtra(ARG_COOKIE, cookie)
|
||||
})
|
||||
}
|
||||
|
||||
@ -241,10 +243,20 @@ inline val String.isVideoUrl
|
||||
(startsWith("https://video-") && contains(FBCDN_NET))
|
||||
|
||||
/**
|
||||
* [true] if url is or redirects to an explicit facebook image
|
||||
* [true] if url directly leads to a usable image
|
||||
*/
|
||||
inline val String.isImageUrl
|
||||
get() = contains(FBCDN_NET) && (contains(".png") || contains(".jpg"))
|
||||
inline val String.isImageUrl: Boolean
|
||||
get() {
|
||||
return contains(FBCDN_NET) && (contains(".png") || contains(".jpg"))
|
||||
}
|
||||
|
||||
/**
|
||||
* [true] if url can be retrieved to get a direct image url
|
||||
*/
|
||||
inline val String.isIndirectImageUrl: Boolean
|
||||
get() {
|
||||
return contains("/photo/view_full_size/") && contains("fbid=")
|
||||
}
|
||||
|
||||
/**
|
||||
* [true] if url can be displayed in a different webview
|
||||
|
@ -4,6 +4,7 @@ import com.pitchedapps.frost.activities.WebOverlayActivity
|
||||
import com.pitchedapps.frost.activities.WebOverlayActivityBase
|
||||
import com.pitchedapps.frost.activities.WebOverlayBasicActivity
|
||||
import com.pitchedapps.frost.contracts.VideoViewHolder
|
||||
import com.pitchedapps.frost.facebook.FbCookie
|
||||
import com.pitchedapps.frost.facebook.FbItem
|
||||
import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
|
||||
import com.pitchedapps.frost.facebook.formattedFbUrl
|
||||
@ -37,7 +38,12 @@ fun FrostWebView.requestWebOverlay(url: String): Boolean {
|
||||
}
|
||||
if (url.isImageUrl) {
|
||||
L.d { "Found fb image" }
|
||||
context.launchImageActivity(url.formattedFbUrl, null)
|
||||
context.launchImageActivity(url.formattedFbUrl)
|
||||
return true
|
||||
}
|
||||
if (url.isIndirectImageUrl) {
|
||||
L.d { "Found indirect fb image" }
|
||||
context.launchImageActivity(url.formattedFbUrl, cookie = FbCookie.webCookie)
|
||||
return true
|
||||
}
|
||||
if (!url.isIndependent) {
|
||||
|
@ -7,7 +7,9 @@ import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import com.pitchedapps.frost.facebook.FB_URL_BASE
|
||||
import com.pitchedapps.frost.facebook.FbCookie
|
||||
import com.pitchedapps.frost.facebook.FbItem
|
||||
import com.pitchedapps.frost.facebook.formattedFbUrl
|
||||
import com.pitchedapps.frost.injectors.*
|
||||
import com.pitchedapps.frost.utils.*
|
||||
import com.pitchedapps.frost.views.FrostWebView
|
||||
@ -57,7 +59,6 @@ open class FrostWebViewClient(val web: FrostWebView) : BaseWebViewClient() {
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
override fun onPageCommitVisible(view: WebView, url: String?) {
|
||||
super.onPageCommitVisible(view, url)
|
||||
injectBackgroundColor()
|
||||
@ -91,7 +92,7 @@ open class FrostWebViewClient(val web: FrostWebView) : BaseWebViewClient() {
|
||||
onPageFinishedActions(url)
|
||||
}
|
||||
|
||||
open internal fun onPageFinishedActions(url: String) {
|
||||
internal open fun onPageFinishedActions(url: String) {
|
||||
if (url.startsWith("${FbItem.MESSAGES.url}/read/") && Prefs.messageScrollToBottom)
|
||||
web.pageDown(true)
|
||||
injectAndFinish()
|
||||
@ -125,9 +126,9 @@ open class FrostWebViewClient(val web: FrostWebView) : BaseWebViewClient() {
|
||||
return web.requestWebOverlay(request.url.toString())
|
||||
}
|
||||
|
||||
private fun launchImage(url: String, text: String? = null): Boolean {
|
||||
private fun launchImage(url: String, text: String? = null, cookie: String? = null): Boolean {
|
||||
v { "Launching image: $url" }
|
||||
web.context.launchImageActivity(url, text)
|
||||
web.context.launchImageActivity(url, text, cookie)
|
||||
if (web.canGoBack()) web.goBack()
|
||||
return true
|
||||
}
|
||||
@ -143,7 +144,9 @@ open class FrostWebViewClient(val web: FrostWebView) : BaseWebViewClient() {
|
||||
}
|
||||
if (path.startsWith("/composer/")) return launchRequest(request)
|
||||
if (url.isImageUrl)
|
||||
return launchImage(url)
|
||||
return launchImage(url.formattedFbUrl)
|
||||
if (url.isIndirectImageUrl)
|
||||
return launchImage(url.formattedFbUrl, cookie = FbCookie.webCookie)
|
||||
if (Prefs.linksInDefaultApp && view.context.resolveActivityForUri(request.url)) return true
|
||||
return super.shouldOverrideUrlLoading(view, request)
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
<item text="Catch crashes if device has no webview" />
|
||||
<item text="Fix all the notifications coming in for the first load" />
|
||||
<item text="Reorder settings" />
|
||||
<item text="" />
|
||||
<item text="Fix view full image" />
|
||||
<item text="" />
|
||||
<item text="" />
|
||||
<item text="" />
|
||||
|
@ -0,0 +1,32 @@
|
||||
package com.pitchedapps.frost.facebook
|
||||
|
||||
import com.pitchedapps.frost.facebook.requests.getFullSizedImage
|
||||
import com.pitchedapps.frost.facebook.requests.getFullSizedImageUrl
|
||||
import com.pitchedapps.frost.internal.COOKIE
|
||||
import com.pitchedapps.frost.internal.authDependent
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 12/04/18.
|
||||
*/
|
||||
class FbFullImageTest {
|
||||
|
||||
companion object {
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun before() {
|
||||
authDependent()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getFullImage() {
|
||||
val url = "https://touch.facebook.com/photo/view_full_size/?fbid=107368839645039"
|
||||
val result = COOKIE.getFullSizedImageUrl(url).blockingGet()
|
||||
assertNotNull(result)
|
||||
println(result)
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package com.pitchedapps.frost.facebook
|
||||
|
||||
import com.pitchedapps.frost.utils.isImageUrl
|
||||
import com.pitchedapps.frost.utils.isIndirectImageUrl
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
@ -47,6 +48,13 @@ class FbUrlTest {
|
||||
assertFbFormat(expected, url)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ampersand() {
|
||||
val url = "https://scontent-yyz1-1.xx.fbcdn.net/v/t31.0-8/fr/cp0/e15/q65/123.jpg?_nc_cat=0&efg=asdf"
|
||||
val formattedUrl = "https://scontent-yyz1-1.xx.fbcdn.net/v/t31.0-8/fr/cp0/e15/q65/123.jpg?_nc_cat=0&efg=asdf"
|
||||
assertFbFormat(formattedUrl, url)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun doubleDash() {
|
||||
assertFbFormat("${FB_URL_BASE}relative", "$FB_URL_BASE/relative")
|
||||
@ -72,6 +80,15 @@ class FbUrlTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun indirectImage() {
|
||||
arrayOf(
|
||||
"#!/photo/view_full_size/?fbid=107368839645039"
|
||||
).forEach {
|
||||
assertTrue(it.isIndirectImageUrl, "Failed to match indirect image for $it")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun antiImageRegex() {
|
||||
arrayOf(
|
||||
|
@ -24,11 +24,13 @@ import kotlin.test.fail
|
||||
|
||||
private const val FILE = "priv.properties"
|
||||
|
||||
private val propPaths = arrayOf(FILE, "../$FILE")
|
||||
|
||||
val PROPS: Properties by lazy {
|
||||
val props = Properties()
|
||||
val file = File(FILE)
|
||||
if (!file.exists()) {
|
||||
println("$FILE not found")
|
||||
val file = propPaths.map(::File).firstOrNull { it.isFile }
|
||||
if (file == null) {
|
||||
println("$FILE not found at ${File(".").absolutePath}")
|
||||
return@lazy props
|
||||
}
|
||||
println("Found properties at ${file.absolutePath}")
|
||||
|
Loading…
Reference in New Issue
Block a user