aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com/pitchedapps/frost/services
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps/frost/services')
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt79
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt107
2 files changed, 70 insertions, 116 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
index 44b01bc3..afa30a91 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
@@ -24,12 +24,17 @@ import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.R
import com.pitchedapps.frost.activities.FrostWebActivity
import com.pitchedapps.frost.dbflow.CookieModel
+import com.pitchedapps.frost.dbflow.NotificationModel
+import com.pitchedapps.frost.dbflow.lastNotificationTime
import com.pitchedapps.frost.enums.OverlayContext
import com.pitchedapps.frost.facebook.FbItem
-import com.pitchedapps.frost.facebook.formattedFbUrl
-import com.pitchedapps.frost.parsers.FrostThread
+import com.pitchedapps.frost.parsers.FrostParser
+import com.pitchedapps.frost.parsers.MessageParser
+import com.pitchedapps.frost.parsers.NotifParser
+import com.pitchedapps.frost.parsers.ParseNotification
import com.pitchedapps.frost.utils.*
import org.jetbrains.anko.runOnUiThread
+import java.util.*
/**
* Created by Allan Wang on 2017-07-08.
@@ -88,23 +93,66 @@ class FrostNotificationTarget(val context: Context,
* Enum to handle notification creations
*/
enum class NotificationType(
- private val groupPrefix: String,
private val overlayContext: OverlayContext,
- private val contentRes: Int,
- private val pendingUrl: String,
+ private val fbItem: FbItem,
+ private val parser: FrostParser<ParseNotification>,
+ private val getTime: (notif: NotificationModel) -> Long,
+ private val putTime: (notif: NotificationModel, time: Long) -> NotificationModel,
private val ringtone: () -> String) {
- GENERAL("frost", OverlayContext.NOTIFICATION, R.string.notifications, FbItem.NOTIFICATIONS.url, { Prefs.notificationRingtone }),
- MESSAGE("frost_im", OverlayContext.MESSAGE, R.string.messages, FbItem.MESSAGES.url, { Prefs.messageRingtone });
+ GENERAL(OverlayContext.NOTIFICATION,
+ FbItem.NOTIFICATIONS,
+ NotifParser,
+ NotificationModel::epoch,
+ { notif, time -> notif.copy(epoch = time) },
+ Prefs::notificationRingtone),
+ MESSAGE(OverlayContext.MESSAGE,
+ FbItem.MESSAGES,
+ MessageParser,
+ NotificationModel::epochIm,
+ { notif, time -> notif.copy(epochIm = time) },
+ Prefs::messageRingtone);
+
+ private val groupPrefix = "frost_${name.toLowerCase(Locale.CANADA)}"
+
+ /**
+ * Get unread data from designated parser
+ * Display notifications for those after old epoch
+ * Save new epoch
+ */
+ fun fetch(context: Context, data: CookieModel) {
+ val response = parser.parse(data.cookie)
+ ?: return L.eThrow("$name notification data not found")
+ val notifs = response.data.getUnreadNotifications(data)
+ if (notifs.isEmpty()) return
+ var notifCount = 0
+ val userId = data.id
+ val prevNotifTime = lastNotificationTime(userId)
+ val prevLatestEpoch = getTime(prevNotifTime)
+ L.v("Notif $name prev epoch $prevLatestEpoch")
+ var newLatestEpoch = prevLatestEpoch
+ notifs.forEach { notif ->
+ L.v("Notif timestamp ${notif.timestamp}")
+ if (notif.timestamp <= prevLatestEpoch) return@forEach
+ createNotification(context, notif, notifCount == 0)
+ if (notif.timestamp > newLatestEpoch)
+ newLatestEpoch = notif.timestamp
+ notifCount++
+ }
+ if (newLatestEpoch != prevLatestEpoch)
+ putTime(prevNotifTime, newLatestEpoch).save()
+ L.d("Notif $name new epoch ${getTime(lastNotificationTime(userId))}")
+ summaryNotification(context, userId, notifCount)
+ }
/**
* Create and submit a new notification with the given [content]
* If [withDefaults] is set, it will also add the appropriate sound, vibration, and light
* Note that when we have multiple notifications coming in at once, we don't want to have defaults for all of them
*/
- fun createNotification(context: Context, content: NotificationContent, withDefaults: Boolean) {
+ private fun createNotification(context: Context, content: NotificationContent, withDefaults: Boolean) {
with(content) {
val intent = Intent(context, FrostWebActivity::class.java)
- intent.data = Uri.parse(href.formattedFbUrl)
+ intent.data = Uri.parse(href)
intent.putExtra(ARG_USER_ID, data.id)
intent.putExtra(ARG_OVERLAY_CONTEXT, overlayContext)
val group = "${groupPrefix}_${data.id}"
@@ -142,16 +190,16 @@ enum class NotificationType(
* This will always produce sound, vibration, and lights based on preferences
* and will only show if we have at least 2 notifications
*/
- fun summaryNotification(context: Context, userId: Long, count: Int) {
+ private fun summaryNotification(context: Context, userId: Long, count: Int) {
frostAnswersCustom("Notifications", "Type" to name, "Count" to count)
if (count <= 1) return
val intent = Intent(context, FrostWebActivity::class.java)
- intent.data = Uri.parse(pendingUrl)
+ intent.data = Uri.parse(fbItem.url)
intent.putExtra(ARG_USER_ID, userId)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
val notifBuilder = context.frostNotification.withDefaults(ringtone())
.setContentTitle(context.string(R.string.frost_name))
- .setContentText("$count ${context.string(contentRes)}")
+ .setContentText("$count ${context.string(fbItem.titleId)}")
.setGroup("${groupPrefix}_$userId")
.setGroupSummary(true)
.setContentIntent(pendingIntent)
@@ -167,13 +215,10 @@ enum class NotificationType(
data class NotificationContent(val data: CookieModel,
val notifId: Int,
val href: String,
- val title: String? = null,
+ val title: String? = null, // defaults to frost title
val text: String,
val timestamp: Long,
- val profileUrl: String) {
- constructor(data: CookieModel, thread: FrostThread)
- : this(data, thread.id, thread.url, thread.title, thread.content ?: "", thread.time, thread.img)
-}
+ val profileUrl: String)
const val NOTIFICATION_PERIODIC_JOB = 7
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 c4ab6161..adeefec6 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
@@ -7,18 +7,11 @@ import android.support.v4.app.NotificationManagerCompat
import ca.allanwang.kau.utils.string
import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.R
-import com.pitchedapps.frost.dbflow.CookieModel
-import com.pitchedapps.frost.dbflow.lastNotificationTime
import com.pitchedapps.frost.dbflow.loadFbCookiesSync
-import com.pitchedapps.frost.facebook.FbItem
-import com.pitchedapps.frost.facebook.formattedFbUrl
-import com.pitchedapps.frost.parsers.MessageParser
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.frostAnswersCustom
-import com.pitchedapps.frost.utils.frostJsoup
import org.jetbrains.anko.doAsync
-import org.jsoup.nodes.Element
import java.util.concurrent.Future
/**
@@ -27,8 +20,7 @@ import java.util.concurrent.Future
* Service to manage notifications
* Will periodically check through all accounts in the db and send notifications when appropriate
*
- * Note that general notifications are parsed directly with Jsoup,
- * but instant messages are done so with a headless webview as it is generated from JS
+ * All fetching is done through parsers
*/
class NotificationService : JobService() {
@@ -36,13 +28,6 @@ class NotificationService : JobService() {
val startTime = System.currentTimeMillis()
- companion object {
- val epochMatcher: Regex by lazy { Regex(":([0-9]*?),") }
- val notifIdMatcher: Regex by lazy { Regex("notif_id\":([0-9]*?),") }
- val messageNotifIdMatcher: Regex by lazy { Regex("thread_fbid_([0-9]+)") }
- val profMatcher: Regex by lazy { Regex("url\\(\"(.*?)\"\\)") }
- }
-
override fun onStopJob(params: JobParameters?): Boolean {
val time = System.currentTimeMillis() - startTime
L.d("Notification service has finished abruptly in $time ms")
@@ -70,104 +55,28 @@ class NotificationService : JobService() {
override fun onStartJob(params: JobParameters?): Boolean {
L.i("Fetching notifications")
future = doAsync {
+ val context = weakRef.get()
+ ?: return@doAsync L.eThrow("NotificationService had null weakRef to self")
val currentId = Prefs.userId
val cookies = loadFbCookiesSync()
cookies.forEach {
val current = it.id == currentId
if (current || Prefs.notificationAllAccounts)
- fetchGeneralNotifications(it)
- if (Prefs.notificationsInstantMessages && (current || Prefs.notificationsImAllAccounts))
- fetchMessageNotifications(it)
+ NotificationType.GENERAL.fetch(context, it)
+ if (Prefs.notificationsInstantMessages
+ && (current || Prefs.notificationsImAllAccounts))
+ NotificationType.MESSAGE.fetch(context, it)
}
finish(params)
}
return true
}
- fun logNotif(text: String): NotificationContent? {
+ private fun logNotif(text: String): NotificationContent? {
L.eThrow("NotificationService: $text")
return null
}
- /*
- * ----------------------------------------------------------------
- * General notification logic.
- * Fetch notifications -> Filter new ones -> Parse notifications ->
- * Show notifications -> Show group notification
- * ----------------------------------------------------------------
- */
-
- fun fetchGeneralNotifications(data: CookieModel) {
- L.d("Notif fetch", data.toString())
- val doc = frostJsoup(data.cookie, FbItem.NOTIFICATIONS.url)
- //aclb for unread, acw for read
- val unreadNotifications = (doc.getElementById("notifications_list") ?: return L.eThrow("Notification list not found")).getElementsByClass("aclb")
- var notifCount = 0
- //val prevLatestEpoch = 1498931565L // for testing
- val prevNotifTime = lastNotificationTime(data.id)
- val prevLatestEpoch = prevNotifTime.epoch
- L.v("Notif Prev Latest Epoch $prevLatestEpoch")
- var newLatestEpoch = prevLatestEpoch
- unreadNotifications.forEach unread@ { elem ->
- val notif = parseNotification(data, elem) ?: return@unread
- L.v("Notif timestamp ${notif.timestamp}")
- if (notif.timestamp <= prevLatestEpoch) return@unread
- NotificationType.GENERAL.createNotification(this, notif, notifCount == 0)
- if (notif.timestamp > newLatestEpoch)
- newLatestEpoch = notif.timestamp
- notifCount++
- }
- if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epoch = newLatestEpoch).save()
- L.d("Notif new latest epoch ${lastNotificationTime(data.id).epoch}")
- NotificationType.GENERAL.summaryNotification(this, data.id, notifCount)
- }
-
- fun parseNotification(data: CookieModel, element: Element): NotificationContent? {
- val a = element.getElementsByTag("a").first() ?: return logNotif("IM No a tag")
- val abbr = element.getElementsByTag("abbr")
- val epoch = epochMatcher.find(abbr.attr("data-store"))?.groups?.get(1)?.value?.toLong() ?: return logNotif("IM No epoch")
- //fetch id
- val notifId = notifIdMatcher.find(a.attr("data-store"))?.groups?.get(1)?.value?.toLong() ?: System.currentTimeMillis()
- val timeString = abbr.text()
- val text = a.text().replace("\u00a0", " ").removeSuffix(timeString).trim() //remove &nbsp;
- if (Prefs.notificationKeywords.any { text.contains(it, ignoreCase = true) }) return null //notification filtered out
- //fetch profpic
- val p = element.select("i.img[style*=url]")
- val pUrl = profMatcher.find(p.attr("style"))?.groups?.get(1)?.value?.formattedFbUrl ?: ""
- return NotificationContent(data, notifId.toInt(), a.attr("href"), null, text, epoch, pUrl)
- }
-
- /*
- * ----------------------------------------------------------------
- * Instant message notification logic.
- * Fetch notifications -> Filter new ones -> Parse notifications ->
- * Show notifications -> Show group notification
- * ----------------------------------------------------------------
- */
-
- fun fetchMessageNotifications(data: CookieModel) {
- L.d("Notif IM fetch", data.toString())
- val doc = frostJsoup(data.cookie, FbItem.MESSAGES.url)
- val (threads, _, _) = MessageParser.parse(doc.toString()) ?: return L.e("Could not parse IM")
-
- var notifCount = 0
- val prevNotifTime = lastNotificationTime(data.id)
- val prevLatestEpoch = prevNotifTime.epochIm
- L.v("Notif Prev Latest Im Epoch $prevLatestEpoch")
- var newLatestEpoch = prevLatestEpoch
- threads.filter { it.unread }.forEach { notif ->
- L.v("Notif Im timestamp ${notif.time}")
- if (notif.time <= prevLatestEpoch) return@forEach
- NotificationType.MESSAGE.createNotification(this, NotificationContent(data, notif), notifCount == 0)
- if (notif.time > newLatestEpoch)
- newLatestEpoch = notif.time
- notifCount++
- }
- if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epochIm = newLatestEpoch).save()
- L.d("Notif new latest im epoch ${lastNotificationTime(data.id).epochIm}")
- NotificationType.MESSAGE.summaryNotification(this, data.id, notifCount)
- }
-
private fun Context.debugNotification(text: String) {
if (!BuildConfig.DEBUG) return
val notifBuilder = frostNotification.withDefaults()