1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
package com.pitchedapps.frost.settings
import android.content.Context
import android.support.annotation.UiThread
import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
import ca.allanwang.kau.utils.string
import com.afollestad.materialdialogs.MaterialDialog
import com.pitchedapps.frost.R
import com.pitchedapps.frost.activities.SettingsActivity
import com.pitchedapps.frost.facebook.FACEBOOK_COM
import com.pitchedapps.frost.facebook.FbCookie
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
import com.pitchedapps.frost.injectors.InjectorContract
import com.pitchedapps.frost.injectors.JsActions
import com.pitchedapps.frost.injectors.JsAssets
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.cleanHtml
import com.pitchedapps.frost.utils.materialDialogThemed
import com.pitchedapps.frost.utils.sendFrostEmail
import com.pitchedapps.frost.web.launchHeadlessHtmlExtractor
import com.pitchedapps.frost.web.query
import io.reactivex.disposables.Disposable
import org.jetbrains.anko.AnkoAsyncContext
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.runOnUiThread
import org.jetbrains.anko.uiThread
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
/**
* Created by Allan Wang on 2017-06-30.
*
* A sub pref section that is enabled through a hidden preference
* Each category will load a page, extract the contents, remove private info, and create a report
*/
fun SettingsActivity.getDebugPrefs(): KPrefAdapterBuilder.() -> Unit = {
plainText(R.string.experimental_disclaimer) {
descRes = R.string.debug_disclaimer_info
}
Debugger.values().forEach {
plainText(it.data.titleId) {
iicon = it.data.icon
onClick = { itemView, _, _ -> it.debug(itemView.context); true }
}
}
}
private enum class Debugger(val data: FbItem, val injector: InjectorContract?, vararg query: String) {
MENU(FbItem.MENU, JsAssets.MENU_DEBUG, "#viewport"), //todo modify menu js for debugging
NOTIFICATIONS(FbItem.NOTIFICATIONS, null, "#notifications_list"),
SEARCH(FbItem.SEARCH, JsActions.FETCH_BODY);
val query = if (query.isNotEmpty()) arrayOf(*query, "#root", "main", "body") else emptyArray()
fun debug(context: Context) {
val dialog = context.materialDialogThemed {
title("Debugging")
progress(true, 0)
canceledOnTouchOutside(false)
positiveText(R.string.kau_cancel)
onPositive { dialog, _ -> dialog.cancel() }
}
if (injector != null) dialog.extractHtml(injector)
else dialog.debugAsync {
loadJsoup()
}
}
fun MaterialDialog.debugAsync(task: AnkoAsyncContext<MaterialDialog>.() -> Unit) {
doAsync({ t: Throwable ->
val msg = t.message
L.e("Debugger failed: $msg")
context.runOnUiThread {
cancel()
context.materialDialogThemed {
title(R.string.debug_incomplete)
if (msg != null) content(msg)
}
}
}, task)
}
/**
* Wait for html to be returned from headless webview
*
* from [debug] to [simplifyJsoup] if [query] is not empty, or [createReport] otherwise
*/
@UiThread
private fun MaterialDialog.extractHtml(injector: InjectorContract) {
setContent("Fetching webpage")
var disposable: Disposable? = null
setOnCancelListener { disposable?.dispose() }
context.launchHeadlessHtmlExtractor(data.url, injector) {
disposable = it.subscribe { (html, errorRes) ->
debugAsync {
if (errorRes == -1) {
L.i("Debug report successful", html)
if (query.isNotEmpty()) simplifyJsoup(Jsoup.parseBodyFragment(html))
else createReport(html)
} else {
throw Throwable(context.string(errorRes))
}
}
}
}
}
/**
* Get data directly from the link and search for our queries, returning the outerHTML
* of the first query found
*
* from [debug] to [simplifyJsoup]
*/
private fun AnkoAsyncContext<MaterialDialog>.loadJsoup() {
uiThread {
it.setContent("Load Jsoup")
it.setOnCancelListener(null)
it.debugAsync {
val connection = Jsoup.connect(data.url).cookie(FACEBOOK_COM, FbCookie.webCookie).userAgent(USER_AGENT_BASIC)
val doc = connection.get()
simplifyJsoup(doc)
}
}
}
/**
* Takes snippet of given document that matches the first query in the [query] items
* before sending it to [createReport]
*/
private fun AnkoAsyncContext<MaterialDialog>.simplifyJsoup(doc: Document) {
weakRef.get() ?: return
val q = query.first { doc.select(it).isNotEmpty() }
createReport(doc.select(q).outerHtml())
}
private fun AnkoAsyncContext<MaterialDialog>.createReport(html: String) {
val cleanHtml = html.cleanHtml()
uiThread {
val c = it.context
it.dismiss()
c.sendFrostEmail("${c.string(R.string.debug_report_email_title)} $name") {
addItem("Query List", query.contentToString())
footer = cleanHtml
}
}
}
}
|