mirror of
https://github.com/AllanWang/Frost-for-Facebook.git
synced 2024-11-08 20:12:39 +01:00
Merge pull request #1542 from AllanWang/remove-auth
Remove auth and native ui elements by default
This commit is contained in:
commit
96908453ab
@ -61,7 +61,6 @@ import com.pitchedapps.frost.facebook.FbCookie
|
||||
import com.pitchedapps.frost.facebook.FbItem
|
||||
import com.pitchedapps.frost.facebook.formattedFbUrl
|
||||
import com.pitchedapps.frost.kotlin.subscribeDuringJob
|
||||
import com.pitchedapps.frost.services.FrostRunnable
|
||||
import com.pitchedapps.frost.utils.ARG_URL
|
||||
import com.pitchedapps.frost.utils.ARG_USER_ID
|
||||
import com.pitchedapps.frost.utils.BiometricUtils
|
||||
@ -224,9 +223,6 @@ abstract class WebOverlayActivityBase : BaseActivity(),
|
||||
}
|
||||
}
|
||||
|
||||
FrostRunnable.propagate(this, intent)
|
||||
L.v { "Done propagation" }
|
||||
|
||||
swipeBack = kauSwipeOnCreate {
|
||||
if (!Prefs.overlayFullScreenSwipe) edgeSize = 20.dpToPx
|
||||
transitionSystemBars = false
|
||||
|
@ -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"),
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
@ -67,7 +67,7 @@ abstract class BaseFragment : Fragment(), CoroutineScope, FragmentContract, Dyna
|
||||
data: FbItem,
|
||||
position: Int
|
||||
): BaseFragment {
|
||||
val fragment = if (useFallback || !Prefs.nativeUi) WebFragment() else base()
|
||||
val fragment = if (useFallback) WebFragment() else base()
|
||||
val d = if (data == FbItem.FEED) FeedSort(Prefs.feedSort).item else data
|
||||
fragment.withArguments(
|
||||
ARG_URL to d.url,
|
||||
|
@ -16,32 +16,20 @@
|
||||
*/
|
||||
package com.pitchedapps.frost.fragments
|
||||
|
||||
import com.mikepenz.fastadapter.IItem
|
||||
import com.pitchedapps.frost.facebook.FbCookie
|
||||
import com.pitchedapps.frost.facebook.FbItem
|
||||
import com.pitchedapps.frost.facebook.parsers.FrostNotifs
|
||||
import com.pitchedapps.frost.facebook.parsers.NotifParser
|
||||
import com.pitchedapps.frost.facebook.parsers.ParseResponse
|
||||
import com.pitchedapps.frost.facebook.requests.MenuFooterItem
|
||||
import com.pitchedapps.frost.facebook.requests.MenuHeader
|
||||
import com.pitchedapps.frost.facebook.requests.MenuItem
|
||||
import com.pitchedapps.frost.facebook.requests.MenuItemData
|
||||
import com.pitchedapps.frost.facebook.requests.fbAuth
|
||||
import com.pitchedapps.frost.facebook.requests.getMenuData
|
||||
import com.pitchedapps.frost.iitems.ClickableIItemContract
|
||||
import com.pitchedapps.frost.iitems.MenuContentIItem
|
||||
import com.pitchedapps.frost.iitems.MenuFooterIItem
|
||||
import com.pitchedapps.frost.iitems.MenuFooterSmallIItem
|
||||
import com.pitchedapps.frost.iitems.MenuHeaderIItem
|
||||
import com.pitchedapps.frost.iitems.NotificationIItem
|
||||
import com.pitchedapps.frost.utils.frostJsoup
|
||||
import com.pitchedapps.frost.views.FrostRecyclerView
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 27/12/17.
|
||||
*
|
||||
* Retained as an example. Deletion made at https://github.com/AllanWang/Frost-for-Facebook/pull/1542
|
||||
*/
|
||||
@Deprecated(message = "Retained as an example; currently does not support marking a notification as read")
|
||||
class NotificationFragment : FrostParserFragment<FrostNotifs, NotificationIItem>() {
|
||||
|
||||
override val parser = NotifParser
|
||||
@ -55,33 +43,3 @@ class NotificationFragment : FrostParserFragment<FrostNotifs, NotificationIItem>
|
||||
NotificationIItem.bindEvents(adapter)
|
||||
}
|
||||
}
|
||||
|
||||
class MenuFragment : GenericRecyclerFragment<MenuItemData, IItem<*, *>>() {
|
||||
|
||||
override fun mapper(data: MenuItemData): IItem<*, *> = when (data) {
|
||||
is MenuHeader -> MenuHeaderIItem(data)
|
||||
is MenuItem -> MenuContentIItem(data)
|
||||
is MenuFooterItem ->
|
||||
if (data.isSmall) MenuFooterSmallIItem(data)
|
||||
else MenuFooterIItem(data)
|
||||
else -> throw IllegalArgumentException("Menu item in fragment has invalid type ${data::class.java.simpleName}")
|
||||
}
|
||||
|
||||
override fun bindImpl(recyclerView: FrostRecyclerView) {
|
||||
ClickableIItemContract.bindEvents(adapter)
|
||||
}
|
||||
|
||||
override suspend fun reloadImpl(progress: (Int) -> Unit): List<MenuItemData>? =
|
||||
withContext(Dispatchers.IO) {
|
||||
val cookie = FbCookie.webCookie ?: return@withContext null
|
||||
progress(10)
|
||||
val auth = fbAuth.fetch(cookie).await()
|
||||
progress(30)
|
||||
val data = auth.getMenuData().invoke() ?: return@withContext null
|
||||
if (data.data.isEmpty()) return@withContext null
|
||||
progress(70)
|
||||
val items = data.flatMapValid()
|
||||
progress(90)
|
||||
return@withContext items
|
||||
}
|
||||
}
|
||||
|
@ -1,87 +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.iitems
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import ca.allanwang.kau.iitems.KauIItem
|
||||
import ca.allanwang.kau.ui.createSimpleRippleDrawable
|
||||
import ca.allanwang.kau.utils.bindView
|
||||
import ca.allanwang.kau.utils.gone
|
||||
import ca.allanwang.kau.utils.visible
|
||||
import com.mikepenz.fastadapter.FastAdapter
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.facebook.requests.MenuFooterItem
|
||||
import com.pitchedapps.frost.facebook.requests.MenuHeader
|
||||
import com.pitchedapps.frost.facebook.requests.MenuItem
|
||||
import com.pitchedapps.frost.glide.FrostGlide
|
||||
import com.pitchedapps.frost.glide.GlideApp
|
||||
import com.pitchedapps.frost.utils.Prefs
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 30/12/17.
|
||||
*/
|
||||
class MenuContentIItem(val data: MenuItem) :
|
||||
KauIItem<MenuContentIItem, MenuContentIItem.ViewHolder>(R.layout.iitem_menu, ::ViewHolder),
|
||||
ClickableIItemContract {
|
||||
|
||||
override val url: String?
|
||||
get() = data.url
|
||||
|
||||
class ViewHolder(itemView: View) : FastAdapter.ViewHolder<MenuContentIItem>(itemView) {
|
||||
|
||||
val frame: ViewGroup by bindView(R.id.item_frame)
|
||||
val icon: ImageView by bindView(R.id.item_icon)
|
||||
val content: TextView by bindView(R.id.item_content)
|
||||
val badge: TextView by bindView(R.id.item_badge)
|
||||
|
||||
override fun bindView(item: MenuContentIItem, payloads: MutableList<Any>) {
|
||||
frame.background = createSimpleRippleDrawable(Prefs.textColor, Prefs.nativeBgColor)
|
||||
content.setTextColor(Prefs.textColor)
|
||||
badge.setTextColor(Prefs.textColor)
|
||||
val iconUrl = item.data.pic
|
||||
if (iconUrl != null)
|
||||
GlideApp.with(itemView)
|
||||
.load(iconUrl)
|
||||
.transform(FrostGlide.circleCrop)
|
||||
.into(icon.visible())
|
||||
else
|
||||
icon.gone()
|
||||
content.text = item.data.name
|
||||
badge.text = item.data.badge
|
||||
}
|
||||
|
||||
override fun unbindView(item: MenuContentIItem) {
|
||||
GlideApp.with(itemView).clear(icon)
|
||||
content.text = null
|
||||
badge.text = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MenuHeaderIItem(val data: MenuHeader) : HeaderIItem(
|
||||
data.header,
|
||||
itemId = R.id.item_menu_header
|
||||
)
|
||||
|
||||
class MenuFooterIItem(val data: MenuFooterItem) :
|
||||
TextIItem(data.name, data.url, R.id.item_menu_footer)
|
||||
|
||||
class MenuFooterSmallIItem(val data: MenuFooterItem) :
|
||||
TextIItem(data.name, data.url, R.id.item_menu_footer_small)
|
@ -34,7 +34,6 @@ import com.pitchedapps.frost.facebook.FbItem
|
||||
import com.pitchedapps.frost.facebook.parsers.FrostNotif
|
||||
import com.pitchedapps.frost.glide.FrostGlide
|
||||
import com.pitchedapps.frost.glide.GlideApp
|
||||
import com.pitchedapps.frost.services.FrostRunnable
|
||||
import com.pitchedapps.frost.utils.Prefs
|
||||
import com.pitchedapps.frost.utils.isIndependent
|
||||
import com.pitchedapps.frost.utils.launchWebOverlay
|
||||
@ -53,7 +52,6 @@ class NotificationIItem(val notification: FrostNotif, val cookie: String) :
|
||||
.withOnClickListener { v, _, item, position ->
|
||||
val notif = item.notification
|
||||
if (notif.unread) {
|
||||
FrostRunnable.markNotificationRead(v!!.context, notif.id, item.cookie)
|
||||
adapter.set(
|
||||
position,
|
||||
NotificationIItem(notif.copy(unread = false), item.cookie)
|
||||
|
@ -76,11 +76,7 @@ enum class NotificationType(
|
||||
FbItem.NOTIFICATIONS,
|
||||
NotifParser,
|
||||
Prefs::notificationRingtone
|
||||
) {
|
||||
|
||||
override fun bindRequest(content: NotificationContent, cookie: String) =
|
||||
FrostRunnable.prepareMarkNotificationRead(content.id, cookie)
|
||||
},
|
||||
),
|
||||
|
||||
MESSAGE(
|
||||
NOTIF_CHANNEL_MESSAGES,
|
||||
|
@ -1,200 +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.services
|
||||
|
||||
import android.app.job.JobInfo
|
||||
import android.app.job.JobParameters
|
||||
import android.app.job.JobScheduler
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.BaseBundle
|
||||
import android.os.PersistableBundle
|
||||
import com.pitchedapps.frost.facebook.requests.RequestAuth
|
||||
import com.pitchedapps.frost.facebook.requests.fbAuth
|
||||
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
|
||||
import com.pitchedapps.frost.utils.L
|
||||
import com.pitchedapps.frost.utils.Prefs
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 28/12/17.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Private helper data
|
||||
*/
|
||||
private enum class FrostRequestCommands : EnumBundle<FrostRequestCommands> {
|
||||
|
||||
NOTIF_READ {
|
||||
|
||||
override fun invoke(auth: RequestAuth, bundle: PersistableBundle) {
|
||||
val id = bundle.getLong(ARG_0, -1L)
|
||||
val success = auth.markNotificationRead(id).invoke()
|
||||
L.d { "Marked notif $id as read: $success" }
|
||||
}
|
||||
|
||||
override fun propagate(bundle: BaseBundle) =
|
||||
FrostRunnable.prepareMarkNotificationRead(
|
||||
bundle.getLong(ARG_0),
|
||||
bundle.getCookie()
|
||||
)
|
||||
};
|
||||
|
||||
override val bundleContract: EnumBundleCompanion<FrostRequestCommands>
|
||||
get() = Companion
|
||||
|
||||
/**
|
||||
* Call request with arguments inside bundle
|
||||
*/
|
||||
abstract fun invoke(auth: RequestAuth, bundle: PersistableBundle)
|
||||
|
||||
/**
|
||||
* Return bundle builder given arguments in the old bundle
|
||||
* Must not write to old bundle!
|
||||
*/
|
||||
abstract fun propagate(bundle: BaseBundle): BaseBundle.() -> Unit
|
||||
|
||||
companion object : EnumCompanion<FrostRequestCommands>("frost_arg_commands", values())
|
||||
}
|
||||
|
||||
private const val ARG_COMMAND = "frost_request_command"
|
||||
private const val ARG_COOKIE = "frost_request_cookie"
|
||||
private const val ARG_0 = "frost_request_arg_0"
|
||||
private const val ARG_1 = "frost_request_arg_1"
|
||||
private const val ARG_2 = "frost_request_arg_2"
|
||||
private const val ARG_3 = "frost_request_arg_3"
|
||||
|
||||
private fun BaseBundle.getCookie(): String = getString(ARG_COOKIE)!!
|
||||
private fun BaseBundle.putCookie(cookie: String) = putString(ARG_COOKIE, cookie)
|
||||
|
||||
/**
|
||||
* Singleton handler for running requests in [FrostRequestService]
|
||||
* Requests are typically completely decoupled from the UI,
|
||||
* and are optional enhancers.
|
||||
*
|
||||
* Nothing guarantees the completion time, or whether it even executes at all
|
||||
*
|
||||
* Design:
|
||||
* prepare function - creates a bundle binder
|
||||
* actor function - calls the service with the given arguments
|
||||
*
|
||||
* Global:
|
||||
* propagator - given a bundle with a command, extracts and executes the requests
|
||||
*/
|
||||
object FrostRunnable {
|
||||
|
||||
fun prepareMarkNotificationRead(id: Long, cookie: String): BaseBundle.() -> Unit = {
|
||||
FrostRequestCommands.NOTIF_READ.put(this)
|
||||
putLong(ARG_0, id)
|
||||
putCookie(cookie)
|
||||
}
|
||||
|
||||
fun markNotificationRead(context: Context, id: Long, cookie: String): Boolean {
|
||||
if (id <= 0) {
|
||||
L.d { "Invalid notification id $id for marking as read" }
|
||||
return false
|
||||
}
|
||||
return schedule(
|
||||
context, FrostRequestCommands.NOTIF_READ,
|
||||
prepareMarkNotificationRead(id, cookie)
|
||||
)
|
||||
}
|
||||
|
||||
fun propagate(context: Context, intent: Intent?) {
|
||||
val extras = intent?.extras ?: return
|
||||
val command = FrostRequestCommands[intent] ?: return
|
||||
intent.removeExtra(ARG_COMMAND) // reset
|
||||
L.d { "Propagating command ${command.name}" }
|
||||
val builder = command.propagate(extras)
|
||||
schedule(context, command, builder)
|
||||
}
|
||||
|
||||
private fun schedule(
|
||||
context: Context,
|
||||
command: FrostRequestCommands,
|
||||
bundleBuilder: PersistableBundle.() -> Unit
|
||||
): Boolean {
|
||||
val scheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
|
||||
val serviceComponent = ComponentName(context, FrostRequestService::class.java)
|
||||
val bundle = PersistableBundle()
|
||||
bundle.bundleBuilder()
|
||||
bundle.putString(ARG_COMMAND, command.name)
|
||||
|
||||
if (bundle.getCookie().isNullOrBlank()) {
|
||||
L.e { "Scheduled frost request with empty cookie" }
|
||||
return false
|
||||
}
|
||||
|
||||
val builder = JobInfo.Builder(REQUEST_SERVICE_BASE + command.ordinal, serviceComponent)
|
||||
.setMinimumLatency(0L)
|
||||
.setExtras(bundle)
|
||||
.setOverrideDeadline(2000L)
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||
val result = scheduler.schedule(builder.build())
|
||||
if (result <= 0) {
|
||||
L.eThrow("FrostRequestService scheduler failed for ${command.name}")
|
||||
return false
|
||||
}
|
||||
L.d { "Scheduled ${command.name}" }
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class FrostRequestService : BaseJobService() {
|
||||
|
||||
override fun onStartJob(params: JobParameters?): Boolean {
|
||||
super.onStartJob(params)
|
||||
if (!Prefs.authRequests) {
|
||||
L.i { "Auth requests disabled; skipping request service" }
|
||||
return false
|
||||
}
|
||||
val bundle = params?.extras
|
||||
if (bundle == null) {
|
||||
L.eThrow("Launched ${this::class.java.simpleName} without param data")
|
||||
return false
|
||||
}
|
||||
val cookie = bundle.getCookie()
|
||||
if (cookie.isBlank()) {
|
||||
L.eThrow("Launched ${this::class.java.simpleName} without cookie")
|
||||
return false
|
||||
}
|
||||
val command = FrostRequestCommands[bundle]
|
||||
if (command == null) {
|
||||
L.eThrow("Launched ${this::class.java.simpleName} without command")
|
||||
return false
|
||||
}
|
||||
launch(Dispatchers.IO) {
|
||||
try {
|
||||
val auth = fbAuth.fetch(cookie).await()
|
||||
command.invoke(auth, bundle)
|
||||
L.d {
|
||||
"Finished frost service for ${command.name} in ${System.currentTimeMillis() - startTime} ms"
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
L.e(e) { "Failed frost service for ${command.name} in ${System.currentTimeMillis() - startTime} ms" }
|
||||
} finally {
|
||||
jobFinished(params, false)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
@ -79,13 +79,6 @@ fun SettingsActivity.getBehaviourPrefs(): KPrefAdapterBuilder.() -> Unit = {
|
||||
}
|
||||
}
|
||||
|
||||
checkbox(R.string.native_ui, Prefs::nativeUi, {
|
||||
Prefs.nativeUi = it
|
||||
shouldRestartMain()
|
||||
}) {
|
||||
descRes = R.string.native_ui_desc
|
||||
}
|
||||
|
||||
checkbox(R.string.exit_confirmation, Prefs::exitConfirmation, { Prefs.exitConfirmation = it }) {
|
||||
descRes = R.string.exit_confirmation_desc
|
||||
}
|
||||
|
@ -35,10 +35,6 @@ fun SettingsActivity.getExperimentalPrefs(): KPrefAdapterBuilder.() -> Unit = {
|
||||
|
||||
// Experimental content starts here ------------------
|
||||
|
||||
checkbox(R.string.auth_requests, Prefs::authRequests, { Prefs.authRequests = it }) {
|
||||
descRes = R.string.auth_requests_desc
|
||||
}
|
||||
|
||||
// Experimental content ends here --------------------
|
||||
|
||||
checkbox(R.string.verbose_logging, Prefs::verboseLogging, {
|
||||
|
@ -84,4 +84,4 @@ fun KauLoggerExtension.test(message: () -> Any?) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
d { "Test1234 ${message()}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -191,10 +191,6 @@ object Prefs : KPref() {
|
||||
|
||||
var showCreateFab: Boolean by kpref("show_create_fab", true)
|
||||
|
||||
var authRequests: Boolean by kpref("web_requests", false)
|
||||
|
||||
var nativeUi: Boolean by kpref("native_ui", true)
|
||||
|
||||
inline val mainActivityLayout: MainActivityLayout
|
||||
get() = MainActivityLayout(mainActivityLayoutType)
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
v2.4.0
|
||||
|
||||
* Removed web only mode for auth requests. Marking notifications as read is now disabled by default to deal with phishing accusations.
|
||||
* Removed request services, which potentially caused phishing warnings.
|
||||
* Save images with the correct extensions.
|
@ -17,8 +17,6 @@
|
||||
<string name="search_bar_desc">Enable the search bar instead of a search overlay</string>
|
||||
<string name="force_message_bottom">Force Message Bottom</string>
|
||||
<string name="force_message_bottom_desc">When loading a message thread, trigger a scroll to the bottom of the page rather than loading the page as is.</string>
|
||||
<string name="native_ui">Native UI</string>
|
||||
<string name="native_ui_desc">Enables native UI for certain options in the main activity. Done through parsing.</string>
|
||||
<string name="enable_pip">Enable PIP</string>
|
||||
<string name="enable_pip_desc">Enable picture in picture videos</string>
|
||||
<string name="autoplay_settings">Autoplay Settings</string>
|
||||
|
@ -7,7 +7,4 @@
|
||||
<string name="restart_frost">Restart Frost</string>
|
||||
<string name="restart_frost_desc">Launch a cold restart for the application.</string>
|
||||
|
||||
<!-- Debugging phishing warnings -->
|
||||
<string name="auth_requests" translatable="false">Auth Requests</string>
|
||||
<string name="auth_requests_desc" translatable="false">Enables post features such as marking notifications as read. Known to potentially cause phishing responses.</string>
|
||||
</resources>
|
@ -7,7 +7,7 @@
|
||||
-->
|
||||
|
||||
<version title="v2.4.0" />
|
||||
<item text="Removed web only mode for auth requests. Marking notifications as read is now disabled by default to deal with phishing accusations." />
|
||||
<item text="Removed request services, which potentially caused phishing warnings." />
|
||||
<item text="Save images with the correct extensions." />
|
||||
<item text="" />
|
||||
|
||||
|
@ -1,93 +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.databind.ObjectMapper
|
||||
import com.pitchedapps.frost.internal.AUTH
|
||||
import com.pitchedapps.frost.internal.COOKIE
|
||||
import com.pitchedapps.frost.internal.USER_ID
|
||||
import com.pitchedapps.frost.internal.authDependent
|
||||
import okhttp3.Call
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.fail
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 21/12/17.
|
||||
*/
|
||||
class FbRequestTest {
|
||||
|
||||
companion object {
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun before() {
|
||||
authDependent()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to emulate [executeForNoError]
|
||||
* Must be consistent with that method
|
||||
*/
|
||||
private fun Call.assertNoError() {
|
||||
val data = execute().body()?.string() ?: fail("Content was null")
|
||||
println("Call response: $data")
|
||||
assertTrue(data.isNotEmpty(), "Content was empty")
|
||||
assertFalse(data.contains("error"), "Content had error")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun auth() {
|
||||
val auth = COOKIE.getAuth()
|
||||
assertNotNull(auth)
|
||||
assertEquals(USER_ID, auth.userId)
|
||||
assertEquals(COOKIE, auth.cookie)
|
||||
println("Test auth: ${auth.fb_dtsg}")
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Post requests are now experimental")
|
||||
fun markNotification() {
|
||||
val notifId = 1514443903880
|
||||
AUTH.markNotificationRead(notifId).call.assertNoError()
|
||||
}
|
||||
|
||||
@Ignore("Broken as of 2019/01/03; however, this was never used in production to begin with")
|
||||
@Test
|
||||
fun fullSizeImage() {
|
||||
val fbid = 10150706277522838L // google's current cover photo
|
||||
val url = AUTH.getFullSizedImage(fbid).invoke()
|
||||
println(url)
|
||||
assertEquals(true, url?.startsWith("https://scontent"), "Bad start for url $url")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMenu() {
|
||||
val data = AUTH.getMenuData().invoke()
|
||||
assertNotNull(data)
|
||||
println(ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(data))
|
||||
assertTrue(data.data.isNotEmpty())
|
||||
assertTrue(data.footer.hasContent, "Footer may be badly parsed")
|
||||
val items = data.flatMapValid()
|
||||
assertTrue(items.size > 15, "Something may be badly parsed")
|
||||
}
|
||||
}
|
@ -19,8 +19,6 @@ package com.pitchedapps.frost.internal
|
||||
import com.pitchedapps.frost.facebook.FB_USER_MATCHER
|
||||
import com.pitchedapps.frost.facebook.FbItem
|
||||
import com.pitchedapps.frost.facebook.get
|
||||
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
|
||||
@ -53,11 +51,6 @@ val PROPS: Properties by lazy {
|
||||
|
||||
val COOKIE: String by lazy { PROPS.getProperty("COOKIE") ?: "" }
|
||||
val USER_ID: Long by lazy { FB_USER_MATCHER.find(COOKIE)[1]?.toLong() ?: -1 }
|
||||
val AUTH: RequestAuth by lazy {
|
||||
COOKIE.getAuth().apply {
|
||||
println("Auth:\nuser:$userId\nfb_dtsg: $fb_dtsg\nrev: $rev\ncomplete: $isComplete")
|
||||
}
|
||||
}
|
||||
|
||||
private val VALID_COOKIE: Boolean by lazy {
|
||||
val data = testJsoup(FbItem.SETTINGS.url)
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Changelog
|
||||
|
||||
## v2.4.0
|
||||
* Removed web only mode for auth requests. Marking notifications as read is now disabled by default to deal with phishing accusations.
|
||||
* Removed request services, which potentially caused phishing warnings.
|
||||
* Save images with the correct extensions.
|
||||
|
||||
## v2.3.2
|
||||
|
Loading…
Reference in New Issue
Block a user