aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt
blob: f8dc81d1291e4bc803eb7c0be5dddcb8dc5f966b (plain)
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.email.sendEmail
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.utils.L
import com.pitchedapps.frost.utils.cleanHtml
import com.pitchedapps.frost.utils.materialDialogThemed
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) {
    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.sendEmail(c.string(R.string.dev_email),
                    "${c.string(R.string.debug_report_email_title)} $name") {
                addItem("Query List", query.contentToString())
                footer = cleanHtml
            }
        }
    }
}