aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
blob: d00ca8f05a04256932bc5647f3c6db32b7ceddb4 (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
package com.pitchedapps.frost.services

import android.app.Notification
import android.app.PendingIntent
import android.app.job.JobParameters
import android.app.job.JobService
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.support.v4.app.NotificationManagerCompat
import ca.allanwang.kau.utils.string
import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.FrostWebActivity
import com.pitchedapps.frost.R
import com.pitchedapps.frost.WebOverlayActivity
import com.pitchedapps.frost.dbflow.*
import com.pitchedapps.frost.facebook.FACEBOOK_COM
import com.pitchedapps.frost.facebook.FB_URL_BASE
import com.pitchedapps.frost.facebook.FbTab
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.frostAnswersCustom
import com.pitchedapps.frost.utils.frostNotification
import org.jetbrains.anko.doAsync
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import java.util.concurrent.Future

/**
 * Created by Allan Wang on 2017-06-14.
 *
 * Service to manage notifications
 * Will periodically check through all accounts in the db and send notifications when appropriate
 */
class NotificationService : JobService() {

    var future: Future<Unit>? = null

    override fun onStopJob(params: JobParameters?): Boolean {
        future?.cancel(true)
        future = null
        return false
    }

    override fun onStartJob(params: JobParameters?): Boolean {
        future = doAsync {
            loadFbCookiesSync().forEach {
                data ->
                L.i("Handle notifications for $data")
                val doc = Jsoup.connect(FbTab.NOTIFICATIONS.url).cookie(FACEBOOK_COM, data.cookie).get()
                val unreadNotifications = doc.getElementById("notifications_list").getElementsByClass("aclb")
                var notifCount = 0
                var latestEpoch = lastNotificationTime(data.id)
                L.v("Latest Epoch $latestEpoch")
                unreadNotifications.forEach unread@ {
                    elem ->
                    val notif = parseNotification(data, elem)
                    if (notif != null) {
                        if (notif.timestamp <= latestEpoch) return@unread
                        notif.createNotification(this@NotificationService)
                        latestEpoch = notif.timestamp
                        notifCount++
                    }
                }
                if (notifCount > 0) saveNotificationTime(NotificationModel(data.id, latestEpoch))
                frostAnswersCustom("Notifications") { putCustomAttribute("Count", notifCount) }
                summaryNotification(data.id, notifCount)
            }
            L.d("Finished notifications")
            jobFinished(params, false)
            future = null
        }
        return true
    }

    companion object {
        val epochMatcher: Regex by lazy { Regex(":([0-9]*),") }
        val notifIdMatcher: Regex by lazy { Regex("notif_id\":([0-9]*),") }
    }

    fun parseNotification(data: CookieModel, element: Element): NotificationContent? {
        val a = element.getElementsByTag("a").first() ?: return null
        val dataStore = a.attr("data-store")
        val notifId = if (dataStore == null) System.currentTimeMillis()
        else notifIdMatcher.find(dataStore)?.groups?.get(1)?.value?.toLong() ?: System.currentTimeMillis()
        val abbr = element.getElementsByTag("abbr")
        val timeString = abbr?.text()
        var text = a.text().replace("\u00a0", " ") //remove &nbsp;
        if (Prefs.notificationKeywords.any { text.contains(it, ignoreCase = true) }) return null //notification filtered out
        if (timeString != null) text = text.removeSuffix(timeString)
        text = text.trim()
        val abbrData = abbr?.attr("data-store")
        val epoch = if (abbrData == null) -1L else epochMatcher.find(abbrData)?.groups?.get(1)?.value?.toLong() ?: -1L
        return NotificationContent(data, notifId.toInt(), a.attr("href"), text, epoch)
    }

    private fun Context.debugNotification(text: String) {
        if (BuildConfig.DEBUG) {
            val notifBuilder = frostNotification
                    .setContentTitle(string(R.string.app_name))
                    .setContentText(text)

            NotificationManagerCompat.from(this).notify(999, notifBuilder.build())
        }
    }

    data class NotificationContent(val data: CookieModel, val notifId: Int, val href: String, val text: String, val timestamp: Long) {
        fun createNotification(context: Context, verifiedUser: Boolean = false) {
            //in case we haven't found the name, we will try one more time before passing the notification
            if (!verifiedUser && data.name?.isBlank() ?: true) {
                data.fetchUsername {
                    data.name = it
                    createNotification(context, true)
                }
            } else {
                val intent = Intent(context, FrostWebActivity::class.java)
                intent.data = Uri.parse("$FB_URL_BASE$href")
                intent.putExtra(WebOverlayActivity.ARG_USER_ID, data.id)
                val group = "frost_${data.id}"
                val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
                val notifBuilder = context.frostNotification
                        .setContentTitle(context.string(R.string.app_name))
                        .setContentText(text)
                        .setContentIntent(pendingIntent)
                        .setCategory(Notification.CATEGORY_SOCIAL)
                        .setSubText(data.name)
                        .setGroup(group)

                if (timestamp != -1L) notifBuilder.setWhen(timestamp * 1000)

                NotificationManagerCompat.from(context).notify(group, notifId, notifBuilder.build())
            }
        }
    }

    fun summaryNotification(userId: Long, count: Int) {
        if (count <= 1) return
        val notifBuilder = frostNotification
                .setContentTitle(string(R.string.app_name))
                .setContentText("$count notifications")
                .setGroup("frost_$userId")
                .setGroupSummary(true)

        NotificationManagerCompat.from(this).notify("frost_$userId", userId.toInt(), notifBuilder.build())
    }

}