/*
* Copyright 2018 Allan Wang
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package com.pitchedapps.frost.facebook.parsers
import com.pitchedapps.frost.db.CookieEntity
import com.pitchedapps.frost.facebook.FB_CSS_URL_MATCHER
import com.pitchedapps.frost.facebook.formattedFbUrl
import com.pitchedapps.frost.facebook.get
import com.pitchedapps.frost.services.NotificationContent
import com.pitchedapps.frost.utils.frostJsoup
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.select.Elements
/**
* Created by Allan Wang on 2017-10-06.
*
* Interface for a given parser
* Use cases should be attached as delegates to objects that implement this interface
*
* In all cases, parsing will be done from a JSoup document
* Variants accepting strings are also permitted, and they will be converted to documents accordingly
* The return type must be nonnull if no parsing errors occurred, as null signifies a parse error
* If null really must be allowed, use Optionals
*/
interface FrostParser {
/**
* Name associated to parser
* Purely for display
*/
var nameRes: Int
/**
* Url to request from
*/
val url: String
/**
* Call parsing with default implementation using cookie
*/
fun parse(cookie: String?): ParseResponse?
/**
* Call parsing with given document
*/
fun parse(cookie: String?, document: Document): ParseResponse?
/**
* Call parsing using jsoup to fetch from given url
*/
fun parseFromUrl(cookie: String?, url: String): ParseResponse?
/**
* Call parsing with given data
*/
fun parseFromData(cookie: String?, text: String): ParseResponse?
}
const val FALLBACK_TIME_MOD = 1000000
data class FrostLink(val text: String, val href: String)
data class ParseResponse(val cookie: String, val data: T) {
override fun toString() = "ParseResponse\ncookie: $cookie\ndata:\n$data"
}
interface ParseData {
val isEmpty: Boolean
}
interface ParseNotification : ParseData {
fun getUnreadNotifications(data: CookieEntity): List
}
internal fun List.toJsonString(tag: String, indent: Int) = StringBuilder().apply {
val tabs = "\t".repeat(indent)
append("$tabs$tag: [\n\t$tabs")
append(this@toJsonString.joinToString("\n\t$tabs"))
append("\n$tabs]\n")
}.toString()
/**
* T should have a readable toString() function
* [redirectToText] dictates whether all data should be converted to text then back to document before parsing
*/
internal abstract class FrostParserBase(private val redirectToText: Boolean) :
FrostParser {
final override fun parse(cookie: String?) = parseFromUrl(cookie, url)
final override fun parseFromData(cookie: String?, text: String): ParseResponse? {
cookie ?: return null
val doc = textToDoc(text) ?: return null
val data = parseImpl(doc) ?: return null
return ParseResponse(cookie, data)
}
final override fun parseFromUrl(cookie: String?, url: String): ParseResponse? =
parse(cookie, frostJsoup(cookie, url))
override fun parse(cookie: String?, document: Document): ParseResponse? {
cookie ?: return null
if (redirectToText)
return parseFromData(cookie, document.toString())
val data = parseImpl(document) ?: return null
return ParseResponse(cookie, data)
}
protected abstract fun parseImpl(doc: Document): T?
/**
* Attempts to find inner element with some style containing a url
* Returns the formatted url, or an empty string if nothing was found
*/
protected fun Element.getInnerImgStyle(): String? =
select("i.img[style*=url]").getStyleUrl()
protected fun Elements.getStyleUrl(): String? =
FB_CSS_URL_MATCHER.find(attr("style"))[1]?.formattedFbUrl
protected open fun textToDoc(text: String): Document? =
if (!redirectToText) Jsoup.parse(text)
else throw RuntimeException("${this::class.java.simpleName} requires text redirect but did not implement textToDoc")
protected fun parseLink(element: Element?): FrostLink? {
val a = element?.getElementsByTag("a")?.first() ?: return null
return FrostLink(a.text(), a.attr("href"))
}
}