diff options
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps/frost/facebook')
6 files changed, 2 insertions, 528 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 a09b5d39..6f726b5b 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt @@ -23,8 +23,6 @@ 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.MenuFragment -import com.pitchedapps.frost.fragments.NotificationFragment import com.pitchedapps.frost.fragments.WebFragment import com.pitchedapps.frost.utils.EnumBundle import com.pitchedapps.frost.utils.EnumBundleCompanion @@ -47,15 +45,10 @@ enum class FbItem( FRIENDS(R.string.friends, GoogleMaterial.Icon.gmd_person_add, "friends/center/requests"), GROUPS(R.string.groups, GoogleMaterial.Icon.gmd_group, "groups"), MARKETPLACE(R.string.marketplace, GoogleMaterial.Icon.gmd_store, "marketplace"), - MENU(R.string.menu, GoogleMaterial.Icon.gmd_menu, "settings", ::MenuFragment), + MENU(R.string.menu, GoogleMaterial.Icon.gmd_menu, "settings"), MESSAGES(R.string.messages, MaterialDesignIconic.Icon.gmi_comments, "messages"), NOTES(R.string.notes, CommunityMaterial.Icon2.cmd_note, "notes"), - NOTIFICATIONS( - R.string.notifications, - MaterialDesignIconic.Icon.gmi_globe, - "notifications", - ::NotificationFragment - ), + NOTIFICATIONS(R.string.notifications, MaterialDesignIconic.Icon.gmi_globe, "notifications"), 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"), 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 index b948506f..7900534c 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt @@ -17,58 +17,11 @@ package com.pitchedapps.frost.facebook.requests import com.pitchedapps.frost.BuildConfig -import com.pitchedapps.frost.facebook.FB_DTSG_MATCHER -import com.pitchedapps.frost.facebook.FB_JSON_URL_MATCHER -import com.pitchedapps.frost.facebook.FB_REV_MATCHER -import com.pitchedapps.frost.facebook.FB_URL_BASE -import com.pitchedapps.frost.facebook.FB_USER_MATCHER import com.pitchedapps.frost.facebook.USER_AGENT -import com.pitchedapps.frost.facebook.get -import com.pitchedapps.frost.kotlin.Flyweight -import com.pitchedapps.frost.utils.L -import kotlinx.coroutines.GlobalScope import okhttp3.Call -import okhttp3.FormBody import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.logging.HttpLoggingInterceptor -import org.apache.commons.text.StringEscapeUtils - -/** - * Created by Allan Wang on 21/12/17. - */ -val fbAuth = Flyweight<String, RequestAuth>(GlobalScope, 3600000 /* an hour */) { - it.getAuth() -} - -/** - * Underlying container for all fb requests - */ -data class RequestAuth( - val userId: Long = -1, - val cookie: String = "", - val fb_dtsg: String = "", - val rev: String = "" -) { - val isComplete - 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) -} val httpClient: OkHttpClient by lazy { val builder = OkHttpClient.Builder() @@ -80,21 +33,6 @@ val httpClient: OkHttpClient by lazy { 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 -} - internal fun String?.requestBuilder(): Request.Builder { val builder = Request.Builder() .header("User-Agent", USER_AGENT) @@ -105,58 +43,3 @@ internal fun String?.requestBuilder(): Request.Builder { } fun Request.Builder.call(): Call = httpClient.newCall(build()) - -fun String.getAuth(): RequestAuth { - L.v { "Getting auth for ${hashCode()}" } - 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 { lines -> - lines.forEach { - val text = try { - StringEscapeUtils.unescapeEcmaScript(it) - } catch (ignore: Exception) { - return@forEach - } - val fb_dtsg = FB_DTSG_MATCHER.find(text)[1] - if (fb_dtsg != null) { - auth = auth.copy(fb_dtsg = fb_dtsg) - if (auth.isComplete) return auth - } - - val rev = FB_REV_MATCHER.find(text)[1] - if (rev != null) { - auth = auth.copy(rev = rev) - if (auth.isComplete) return auth - } - } - } - - return auth -} - -/** - * 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 { lines -> - lines.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) -} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt index 1b5e8b99..0115d6fc 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt @@ -16,38 +16,17 @@ */ package com.pitchedapps.frost.facebook.requests -import com.bumptech.glide.Priority -import com.bumptech.glide.RequestBuilder -import com.bumptech.glide.load.DataSource -import com.bumptech.glide.load.Options -import com.bumptech.glide.load.data.DataFetcher -import com.bumptech.glide.load.model.ModelLoader -import com.bumptech.glide.load.model.ModelLoaderFactory -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_REDIRECT_URL_MATCHER -import com.pitchedapps.frost.facebook.FB_URL_BASE import com.pitchedapps.frost.facebook.formattedFbUrl import com.pitchedapps.frost.facebook.get import com.pitchedapps.frost.utils.L import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout -import okhttp3.Call -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() -} /** * Attempts to get the fbcdn url of the supplied image redirect url @@ -65,106 +44,3 @@ suspend fun String.getFullSizedImageUrl(url: String, timeout: Long = 3000): Stri null } } - -/** - * Request loader for a potentially hd version of a url - * In this case, each url may potentially return an id, - * which may potentially be used to fetch a higher res image url - * The following aims to allow such loading while adhering to Glide's lifecycle - */ -data class HdImageMaybe(val url: String, val cookie: String) { - - val id: Long by lazy { FB_IMAGE_ID_MATCHER.find(url)[1]?.toLongOrNull() ?: -1 } - - val isValid: Boolean by lazy { - id != -1L && cookie.isNotBlank() - } -} - -/* - * The following was a test to see if hd image loading would work - * - * It's working and tested, though the improvements aren't really worth the extra data use - * and reload - */ - -class HdImageLoadingFactory : ModelLoaderFactory<HdImageMaybe, InputStream> { - - override fun build(multiFactory: MultiModelLoaderFactory) = HdImageLoading() - - override fun teardown() = Unit -} - -fun <T> RequestBuilder<T>.loadWithPotentialHd(model: HdImageMaybe) = - thumbnail(clone().load(model.url)) - .load(model) - .apply(RequestOptions().override(Target.SIZE_ORIGINAL)) - -class HdImageLoading : ModelLoader<HdImageMaybe, InputStream> { - - override fun buildLoadData( - model: HdImageMaybe, - width: Int, - height: Int, - options: Options - ): ModelLoader.LoadData<InputStream>? = - if (!model.isValid) null - else ModelLoader.LoadData(ObjectKey(model), HdImageFetcher(model)) - - override fun handles(model: HdImageMaybe) = model.isValid -} - -class HdImageFetcher(private val model: HdImageMaybe) : 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>) { - if (!model.isValid) return callback.fail("Model is invalid") - val result: Result<InputStream?> = runCatching { - runBlocking { - withTimeout(20000L) { - val auth = fbAuth.fetch(model.cookie).await() - if (cancelled) throw RuntimeException("Cancelled") - val url = auth.getFullSizedImage(model.id).invoke() - ?: throw RuntimeException("Null url") - if (cancelled) throw RuntimeException("Cancelled") - if (!url.contains("png") && !url.contains("jpg")) throw RuntimeException("Invalid format") - urlCall?.execute()?.body()?.byteStream() - } - } - } - if (result.isSuccess) - callback.onDataReady(result.getOrNull()) - else - callback.onLoadFailed( - result.exceptionOrNull() as? Exception ?: RuntimeException("Failed") - ) - } - - override fun cleanup() { - try { - inputStream?.close() - } catch (e: IOException) { - } finally { - inputStream = null - } - } - - override fun cancel() { - cancelled = true - urlCall?.cancel() - urlCall = null - cleanup() - } -} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Menu.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Menu.kt deleted file mode 100644 index dcb0ce10..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Menu.kt +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2018 Allan Wang - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ -package com.pitchedapps.frost.facebook.requests - -import com.fasterxml.jackson.annotation.JsonCreator -import com.fasterxml.jackson.annotation.JsonIgnoreProperties -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.MapperFeature -import com.fasterxml.jackson.databind.ObjectMapper -import com.pitchedapps.frost.facebook.FB_URL_BASE -import com.pitchedapps.frost.facebook.formattedFbUrl -import com.pitchedapps.frost.utils.L -import okhttp3.Call -import org.apache.commons.text.StringEscapeUtils -import org.jsoup.Jsoup -import java.io.IOException - -/** - * Created by Allan Wang on 29/12/17. - */ -fun RequestAuth.getMenuData(): FrostRequest<MenuData?> { - - val body = listOf( - "fb_dtsg" to fb_dtsg, - "__user" to userId - ).withEmptyData("m_sess", "__dyn", "__req", "__ajax__") - - return frostRequest(::parseMenu) { - url("${FB_URL_BASE}bookmarks/flyout/body/?id=u_0_2") - post(body.toForm()) - } -} - -fun parseMenu(call: Call): MenuData? { - val fullString = call.execute().body()?.string() ?: return null - var jsonString = fullString.substringAfter("bookmarkGroups", "") - .substringAfter("[", "") - - if (jsonString.isBlank()) return null - - jsonString = "{ \"data\" : [${StringEscapeUtils.unescapeEcmaScript(jsonString)}" - - val mapper = ObjectMapper() - .disable(MapperFeature.AUTO_DETECT_SETTERS) - - return try { - val data = mapper.readValue(jsonString, MenuData::class.java) - - // parse footer content - - val footer = fullString.substringAfter("footerMarkup", "") - .substringAfter("{", "") - .substringBefore("}", "") - - val doc = Jsoup.parseBodyFragment( - StringEscapeUtils.unescapeEcmaScript( - StringEscapeUtils.unescapeEcmaScript(footer) - ) - ) - val footerData = mutableListOf<MenuFooterItem>() - val footerSmallData = mutableListOf<MenuFooterItem>() - - doc.select("a[href]").forEach { - val text = it.text() - it.parent() - if (text.isEmpty()) return@forEach - val href = it.attr("href").formattedFbUrl - val item = MenuFooterItem(name = text, url = href) - if (it.parent().tag().name == "span") - footerSmallData.add(item) - else - footerData.add(item) - } - - return data.copy(footer = MenuFooter(footerData, footerSmallData)) - } catch (e: IOException) { - L.e(e) { "Menu parse fail" } - null - } -} - -@JsonIgnoreProperties(ignoreUnknown = true) -data class MenuData( - val data: List<MenuHeader> = emptyList(), - val footer: MenuFooter = MenuFooter() -) { - - @JsonCreator - constructor( - @JsonProperty("data") data: List<MenuHeader>? - ) : this(data ?: emptyList(), MenuFooter()) - - fun flatMapValid(): List<MenuItemData> { - val items = mutableListOf<MenuItemData>() - data.forEach { - if (it.isValid) items.add(it) - items.addAll(it.visible.filter(MenuItem::isValid)) - } - - items.addAll(footer.data.filter(MenuFooterItem::isValid)) - items.addAll(footer.smallData.filter(MenuFooterItem::isValid)) - - return items - } -} - -interface MenuItemData { - val isValid: Boolean -} - -@JsonIgnoreProperties(ignoreUnknown = true) -data class MenuHeader( - val id: String? = null, - val header: String? = null, - val visible: List<MenuItem> = emptyList(), - val all: List<MenuItem> = emptyList() -) : MenuItemData { - - @JsonCreator - constructor( - @JsonProperty("id") id: String?, - @JsonProperty("header") header: String?, - @JsonProperty("visible") visible: List<MenuItem>?, - @JsonProperty("all") all: List<MenuItem>?, - @JsonProperty("fake") fake: Boolean? - ) : this(id, header, visible ?: emptyList(), all ?: emptyList()) - - override val isValid: Boolean - get() = !header.isNullOrBlank() -} - -@JsonIgnoreProperties(ignoreUnknown = true) -data class MenuItem( - val id: String? = null, - val name: String? = null, - val pic: String? = null, - val url: String? = null, - val badge: String? = null, - val countDetails: String? = null -) : MenuItemData { - - @JsonCreator - constructor( - @JsonProperty("id") id: String?, - @JsonProperty("name") name: String?, - @JsonProperty("pic") pic: String?, - @JsonProperty("url") url: String?, - @JsonProperty("count") badge: String?, - @JsonProperty("count_details") countDetails: String?, - @JsonProperty("fake") fake: Boolean? - ) : this( - id, name, pic?.formattedFbUrl, - url?.formattedFbUrl, - if (badge == "0") null else badge, - countDetails - ) - - override val isValid: Boolean - get() = !name.isNullOrBlank() && !url.isNullOrBlank() -} - -data class MenuFooter( - val data: List<MenuFooterItem> = emptyList(), - val smallData: List<MenuFooterItem> = emptyList() -) { - - val hasContent - get() = data.isNotEmpty() || smallData.isNotEmpty() -} - -data class MenuFooterItem( - val name: String? = null, - val url: String? = null, - val isSmall: Boolean = false -) : MenuItemData { - override val isValid: Boolean - get() = name != null && url != null -} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Messages.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Messages.kt deleted file mode 100644 index f350c547..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Messages.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2018 Allan Wang - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ -package com.pitchedapps.frost.facebook.requests - -import com.pitchedapps.frost.facebook.FB_URL_BASE -import okhttp3.Call - -/** - * Created by Allan Wang on 07/01/18. - */ -fun RequestAuth.sendMessage(group: String, content: String): FrostRequest<Boolean> { - - // todo test more; only tested against tids=cid... - val body = listOf( - "tids" to group, - "body" to content, - "fb_dtsg" to fb_dtsg, - "__user" to userId - ).withEmptyData("m_sess", "__dyn", "__req", "__ajax__") - - return frostRequest(::validateMessage) { - url("${FB_URL_BASE}messages/send") - post(body.toForm()) - } -} - -/** - * Messages are a bit weird with their responses - */ -private fun validateMessage(call: Call): Boolean { - val body = call.execute().body() ?: return false - // todo - return true -} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Notifications.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Notifications.kt deleted file mode 100644 index bf974034..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Notifications.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2018 Allan Wang - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ -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()) - } -} |