diff options
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps')
11 files changed, 229 insertions, 119 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt index ba76e594..1227fd6b 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt @@ -55,7 +55,7 @@ import com.pitchedapps.frost.utils.* import com.pitchedapps.frost.utils.iab.validatePro import com.pitchedapps.frost.views.BadgedIcon import com.pitchedapps.frost.views.FrostViewPager -import com.pitchedapps.frost.web.FrostWebViewSearch +import com.pitchedapps.frost.web.SearchWebView import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers @@ -63,7 +63,7 @@ import io.reactivex.subjects.PublishSubject import org.jsoup.Jsoup import java.util.concurrent.TimeUnit -class MainActivity : BaseActivity(), FrostWebViewSearch.SearchContract, +class MainActivity : BaseActivity(), SearchWebView.SearchContract, ActivityWebContract, FileChooserContract by FileChooserDelegate() { lateinit var adapter: SectionsPagerAdapter @@ -78,13 +78,13 @@ class MainActivity : BaseActivity(), FrostWebViewSearch.SearchContract, var webFragmentObservable = PublishSubject.create<Int>()!! var lastPosition = -1 val headerBadgeObservable = PublishSubject.create<String>() - var hiddenSearchView: FrostWebViewSearch? = null + var hiddenSearchView: SearchWebView? = null var firstLoadFinished = false set(value) { L.d("First fragment load has finished") field = value if (value && hiddenSearchView == null) { - hiddenSearchView = FrostWebViewSearch(this, this) + hiddenSearchView = SearchWebView(this, this) } } var searchView: SearchView? = null @@ -354,7 +354,7 @@ class MainActivity : BaseActivity(), FrostWebViewSearch.SearchContract, R.id.action_settings to GoogleMaterial.Icon.gmd_settings, R.id.action_search to GoogleMaterial.Icon.gmd_search) if (Prefs.searchBar) { - if (firstLoadFinished && hiddenSearchView == null) hiddenSearchView = FrostWebViewSearch(this, this) + if (firstLoadFinished && hiddenSearchView == null) hiddenSearchView = SearchWebView(this, this) if (searchView == null) searchView = bindSearchView(menu, R.id.action_search, Prefs.iconColor) { textObserver = { observable, _ -> diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt index 875f1c49..3b0125be 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt @@ -94,12 +94,14 @@ object FbCookie { * When coming back to the main app, switch back to our original account before continuing */ fun switchBackUser(callback: () -> Unit) { - if (Prefs.prevId != -1L && Prefs.prevId != Prefs.userId) { - switchUser(Prefs.prevId) { - L.d("Switch back user", "${Prefs.userId} to ${Prefs.prevId}") + if (Prefs.prevId == -1L) return callback() + val prevId = Prefs.prevId + Prefs.prevId = -1L + if (prevId != Prefs.userId) { + switchUser(prevId) { + L.d("Switch back user", "${Prefs.userId} to ${prevId}") callback() } } else callback() - if (Prefs.prevId != -1L) Prefs.prevId = -1L } }
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt index 4c44c1bf..de270948 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt @@ -15,6 +15,7 @@ enum class JsActions(body: String) : InjectorContract { */ LOGIN_CHECK("document.getElementById('signup-button')&&Frost.loadLogin();"), BASE_HREF("document.write(\"<base href='$FB_URL_BASE'/>\");"), + GET_MESSAGES("setTimeout(function(){Frost.handleHtml(document.getElementById('threadlist_rows').outerHtml)},1000)"), EMPTY(""); val function = "!function(){$body}();" diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt index 38282bf7..ad977d1a 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt @@ -17,7 +17,9 @@ import com.pitchedapps.frost.facebook.USER_AGENT_BASIC import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.frostAnswersCustom +import com.pitchedapps.frost.web.MessageWebView import org.jetbrains.anko.doAsync +import org.jetbrains.anko.uiThread import org.jsoup.Jsoup import org.jsoup.nodes.Element import java.util.concurrent.Future @@ -45,17 +47,29 @@ class NotificationService : JobService() { return false } + override fun onStartJob(params: JobParameters?): Boolean { future = doAsync { if (Prefs.notificationAllAccounts) { - loadFbCookiesSync().forEach { - data -> - fetchNotifications(data) - } + val cookies = loadFbCookiesSync() + cookies.forEach { fetchGeneralNotifications(it) } +// if (Prefs.notificationsInstantMessages) { +// Prefs.prevId = Prefs.userId +// uiThread { +// val messageWebView = MessageWebView(this@NotificationService, params) +// cookies.forEach { messageWebView.request(it) } +// } +// return@doAsync +// } } else { val currentCookie = loadFbCookie(Prefs.userId) - if (currentCookie != null) - fetchNotifications(currentCookie) + if (currentCookie != null) { + fetchGeneralNotifications(currentCookie) +// if (Prefs.notificationsInstantMessages) { +// uiThread { MessageWebView(this@NotificationService, params).request(currentCookie) } +// return@doAsync +// } + } } L.d("Finished notifications") jobFinished(params, false) @@ -69,12 +83,6 @@ class NotificationService : JobService() { return null } - fun fetchNotifications(data: CookieModel) { - fetchGeneralNotifications(data) -// fetchMessageNotifications(data) - debugNotification("Hello") - } - fun fetchGeneralNotifications(data: CookieModel) { L.i("Notif fetch for $data") val doc = Jsoup.connect(FbTab.NOTIFICATIONS.url).cookie(FACEBOOK_COM, data.cookie).userAgent(USER_AGENT_BASIC).get() @@ -96,7 +104,8 @@ class NotificationService : JobService() { newLatestEpoch = notif.timestamp notifCount++ } - if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epoch = newLatestEpoch).update() + if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epoch = newLatestEpoch).save() + L.d("Notif new latest epoch ${lastNotificationTime(data.id).epoch}") frostAnswersCustom("Notifications") { putCustomAttribute("Type", "General") putCustomAttribute("Count", notifCount) @@ -120,10 +129,9 @@ class NotificationService : JobService() { return NotificationContent(data, notifId.toInt(), a.attr("href"), null, text, epoch, pUrl) } - fun fetchMessageNotifications(data: CookieModel) { - if (!Prefs.notificationsInstantMessages) return + fun fetchMessageNotifications(data: CookieModel, content: String) { L.i("Notif IM fetch for $data") - val doc = Jsoup.connect(FbTab.MESSAGES.url).cookie(FACEBOOK_COM, data.cookie).userAgent(USER_AGENT_BASIC).get() + val doc = Jsoup.parseBodyFragment(content) val unreadNotifications = (doc.getElementById("threadlist_rows") ?: return L.eThrow("Notification messages not found")).getElementsByClass("aclb") var notifCount = 0 L.d("IM notif count ${unreadNotifications.size}") @@ -146,7 +154,8 @@ class NotificationService : JobService() { newLatestEpoch = notif.timestamp notifCount++ } -// if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epochIm = newLatestEpoch).update() + if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epochIm = newLatestEpoch).save() + L.d("Notif new latest im epoch ${lastNotificationTime(data.id).epochIm}") frostAnswersCustom("Notifications") { putCustomAttribute("Type", "Message") putCustomAttribute("Count", notifCount) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/BaseWebViewClient.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/BaseWebViewClient.kt deleted file mode 100644 index 09241254..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/BaseWebViewClient.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.pitchedapps.frost.web - -import android.webkit.WebResourceRequest -import android.webkit.WebResourceResponse -import android.webkit.WebView -import android.webkit.WebViewClient - -/** - * Created by Allan Wang on 2017-07-13. - */ -open class BaseWebViewClient : WebViewClient() { - - override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? - = shouldFrostInterceptRequest(view, request) - -}
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClient.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt index 4df6d6a7..b8ba0d1d 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClient.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt @@ -14,6 +14,19 @@ import io.reactivex.subjects.Subject /** * Created by Allan Wang on 2017-05-31. + * + * Collection of chrome clients + */ + +/** + * Nothing more than a client without logging + */ +class QuietChromeClient : WebChromeClient() { + override fun onConsoleMessage(consoleMessage: ConsoleMessage) = true +} + +/** + * The default chrome client */ class FrostChromeClient(webCore: FrostWebViewCore) : WebChromeClient() { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt index 45dc83aa..3f2891d0 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt @@ -1,17 +1,12 @@ package com.pitchedapps.frost.web -import android.graphics.Bitmap.CompressFormat import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse import android.webkit.WebView import ca.allanwang.kau.utils.use -import com.pitchedapps.frost.utils.GlideApp import com.pitchedapps.frost.utils.L -import com.pitchedapps.frost.utils.Prefs import okhttp3.HttpUrl import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.InputStream /** @@ -62,6 +57,23 @@ fun shouldFrostInterceptRequest(view: WebView, request: WebResourceRequest): Web return null } +/** + * Wrapper to ensure that null exceptions are not reached + */ +fun WebResourceRequest.query(action: (url: String) -> Boolean): Boolean { + return action(url?.path ?: return false) +} + +/** + * Generic filter passthrough + * If Resource is already nonnull, pass it, otherwise check if filter is met and override the response accordingly + */ +fun WebResourceResponse?.filter(request: WebResourceRequest, filter: (url: String) -> Boolean): WebResourceResponse? + = this ?: if (request.query { filter(it) }) blankResource else null + fun WebResourceResponse?.filterCss(request: WebResourceRequest): WebResourceResponse? - = this ?: if (request.url.path.endsWith(".css")) blankResource else null + = filter(request) { it.endsWith(".css") } + +fun WebResourceResponse?.filterImage(request: WebResourceRequest): WebResourceResponse? + = filter(request) { it.contains(".jpg") || it.contains(".png") } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClientMenu.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClientMenu.kt deleted file mode 100644 index 10648e73..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClientMenu.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.pitchedapps.frost.web - -import android.webkit.WebView -import com.pitchedapps.frost.facebook.FB_URL_BASE -import com.pitchedapps.frost.injectors.JsAssets -import com.pitchedapps.frost.injectors.jsInject - -/** - * Created by Allan Wang on 2017-05-31. - */ -class FrostWebViewClientMenu(webCore: FrostWebViewCore) : FrostWebViewClient(webCore) { - - private val String.shouldInjectMenu - get() = when (removePrefix(FB_URL_BASE)) { - "settings", - "settings#", - "settings#!/settings?soft=bookmarks" -> true - else -> false - } - - override fun onPageFinished(view: WebView, url: String) { - super.onPageFinished(view, url) - if (url.shouldInjectMenu) jsInject(JsAssets.MENU) - } - - override fun emit(flag: Int) { - super.emit(flag) - super.injectAndFinish() - } - - override fun onPageFinishedActions(url: String) { - if (!url.shouldInjectMenu) injectAndFinish() - } - -}
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt index 5b2b4bfd..3e6ddd06 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt @@ -3,11 +3,14 @@ package com.pitchedapps.frost.web import android.content.Context import android.graphics.Bitmap import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse import android.webkit.WebView +import android.webkit.WebViewClient import com.pitchedapps.frost.activities.LoginActivity import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.activities.SelectorActivity import com.pitchedapps.frost.facebook.FACEBOOK_COM +import com.pitchedapps.frost.facebook.FB_URL_BASE import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.injectors.* import com.pitchedapps.frost.utils.* @@ -15,6 +18,23 @@ import io.reactivex.subjects.Subject /** * Created by Allan Wang on 2017-05-31. + * + * Collection of webview clients + */ + +/** + * The base of all webview clients + * Used to ensure that resources are properly intercepted + */ +open class BaseWebViewClient : WebViewClient() { + + override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? + = shouldFrostInterceptRequest(view, request) + +} + +/** + * The default webview client */ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient() { @@ -96,9 +116,57 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient return super.shouldOverrideUrlLoading(view, request) } -// override fun onPageCommitVisible(view: WebView?, url: String?) { -// L.d("ASDF PCV") -// super.onPageCommitVisible(view, url) -// } +} + +/** + * Client variant for the menu view + */ +class FrostWebViewClientMenu(webCore: FrostWebViewCore) : FrostWebViewClient(webCore) { + + private val String.shouldInjectMenu + get() = when (removePrefix(FB_URL_BASE)) { + "settings", + "settings#", + "settings#!/settings?soft=bookmarks" -> true + else -> false + } + + override fun onPageFinished(view: WebView, url: String) { + super.onPageFinished(view, url) + if (url.shouldInjectMenu) jsInject(JsAssets.MENU) + } + + override fun emit(flag: Int) { + super.emit(flag) + super.injectAndFinish() + } + + override fun onPageFinishedActions(url: String) { + if (!url.shouldInjectMenu) injectAndFinish() + } +} + +/** + * Headless client that injects content after a page load + * The JSI is meant to handle everything else + */ +class HeadlessWebViewClient(val tag: String, val postInjection: InjectorContract) : BaseWebViewClient() { + + override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) { + super.onPageStarted(view, url, favicon) + L.d("Headless Page $tag Started", url) + } + + override fun onPageFinished(view: WebView, url: String) { + super.onPageFinished(view, url) + L.d("Headless Page $tag Finished", url) + postInjection.inject(view) + } + + /** + * In addition to general filtration, we will also strip away css and images + */ + override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? + = super.shouldInterceptRequest(view, request).filterCss(request).filterImage(request) }
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt new file mode 100644 index 00000000..0f3a12b6 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt @@ -0,0 +1,81 @@ +package com.pitchedapps.frost.web + +import android.annotation.SuppressLint +import android.app.job.JobParameters +import android.webkit.JavascriptInterface +import android.webkit.WebView +import ca.allanwang.kau.utils.gone +import com.pitchedapps.frost.dbflow.CookieModel +import com.pitchedapps.frost.facebook.FbCookie +import com.pitchedapps.frost.facebook.FbTab +import com.pitchedapps.frost.facebook.USER_AGENT_BASIC +import com.pitchedapps.frost.injectors.JsActions +import com.pitchedapps.frost.services.NotificationService +import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.utils.frostAnswersCustom +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.runOnUiThread + +@SuppressLint("ViewConstructor") +/** + * Created by Allan Wang on 2017-07-17. + * + * Bare boned headless view made solely to extract conversation info + */ +class MessageWebView(val service: NotificationService, val params: JobParameters?) : WebView(service) { + + init { + gone() + setupWebview() + } + + @SuppressLint("SetJavaScriptEnabled") + private fun setupWebview() { + settings.javaScriptEnabled = true + settings.userAgentString = USER_AGENT_BASIC + webViewClient = HeadlessWebViewClient("MessageNotifs", JsActions.GET_MESSAGES) + webChromeClient = QuietChromeClient() + addJavascriptInterface(MessageJSI(), "Frost") + } + + private val startTime = System.currentTimeMillis() + private val endTime: Long by lazy { System.currentTimeMillis() } + private var inProgress = false + private val pendingRequests: MutableList<CookieModel> = mutableListOf() + private lateinit var data: CookieModel + + fun request(data: CookieModel) { + pendingRequests.add(data) + if (inProgress) return + inProgress = true + load(data) + } + + private fun load(data: CookieModel) { + L.d("Notif retrieving messages", data.toString()) + this.data = data + FbCookie.setWebCookie(data.cookie) { context.runOnUiThread { L.d("Notif messages load"); loadUrl(FbTab.MESSAGES.url) } } + } + + inner class MessageJSI { + @JavascriptInterface + fun handleHtml(html: String) { + L.d("Notif messages received", data.toString()) + doAsync { service.fetchMessageNotifications(data, html) } + pendingRequests.remove(data) + if (pendingRequests.isEmpty()) { + val time = endTime - startTime + L.d("Notif messages finished $time") + frostAnswersCustom("Notifications") { + putCustomAttribute("Message retrieval duration", time) + } + post { destroy() } + service.jobFinished(params, false) + service.future = null + } else { + load(pendingRequests.first()) + } + } + } + +}
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewSearch.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt index bcadf32a..325d0333 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewSearch.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt @@ -3,14 +3,14 @@ package com.pitchedapps.frost.web import android.annotation.SuppressLint import android.content.Context import android.view.View -import android.webkit.* +import android.webkit.JavascriptInterface +import android.webkit.WebView import ca.allanwang.kau.searchview.SearchItem import ca.allanwang.kau.utils.gone import com.pitchedapps.frost.facebook.FbTab import com.pitchedapps.frost.facebook.USER_AGENT_BASIC import com.pitchedapps.frost.injectors.JsAssets import com.pitchedapps.frost.injectors.JsBuilder -import com.pitchedapps.frost.injectors.jsInject import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import io.reactivex.schedulers.Schedulers @@ -23,11 +23,10 @@ import java.util.concurrent.TimeUnit /** * Created by Allan Wang on 2017-06-25. * - * A bare bone search view meant solely to extract data from the web - * This should be hidden + * A bare bone headless search view meant solely to extract search results from the web * Having a single webview allows us to avoid loading the whole page with each query */ -class FrostWebViewSearch(context: Context, val contract: SearchContract) : WebView(context) { +class SearchWebView(context: Context, val contract: SearchContract) : WebView(context) { val searchSubject = PublishSubject.create<String>() @@ -50,12 +49,11 @@ class FrostWebViewSearch(context: Context, val contract: SearchContract) : WebVi } @SuppressLint("SetJavaScriptEnabled") - fun setupWebview() { + private fun setupWebview() { settings.javaScriptEnabled = true settings.userAgentString = USER_AGENT_BASIC - setLayerType(View.LAYER_TYPE_HARDWARE, null) - webViewClient = SearchWebViewClient() - webChromeClient = SearchChromeClient() + webViewClient = HeadlessWebViewClient("Search", JsAssets.SEARCH) + webChromeClient = QuietChromeClient() addJavascriptInterface(SearchJSI(), "Frost") searchSubject.debounce(300, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.newThread()) .map { @@ -105,29 +103,6 @@ class FrostWebViewSearch(context: Context, val contract: SearchContract) : WebVi JsBuilder().js("var e=document.getElementById('main-search-input');if(e){e.value='$input';var n=new Event('input',{bubbles:!0,cancelable:!0});e.dispatchEvent(n),e.dispatchEvent(new Event('focus'))}else console.log('Input field not found');").build().inject(this) } - /** - * Created by Allan Wang on 2017-05-31. - * - * Barebones client that does what [FrostWebViewSearch] needs - */ - inner class SearchWebViewClient : BaseWebViewClient() { - - override fun onPageFinished(view: WebView, url: String) { - super.onPageFinished(view, url) - L.i("Search Page finished $url") - view.jsInject(JsAssets.SEARCH) - } - - override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? - = super.shouldInterceptRequest(view, request).filterCss(request) - } - - class SearchChromeClient : WebChromeClient() { - - //mute console - override fun onConsoleMessage(consoleMessage: ConsoleMessage) = true - } - inner class SearchJSI { @JavascriptInterface fun handleHtml(html: String) { |