From 1d9923156ffd6d0960bd7b5fec272b9c9be386db Mon Sep 17 00:00:00 2001
From: Alessandro Jean <alessandrojean@gmail.com>
Date: Sat, 21 Aug 2021 14:32:56 -0300
Subject: [PATCH] Add view count reporting to WpMangaStream and WpMangaReader.
 (#8711)

---
 .../multisrc/wpmangareader/WPMangaReader.kt   | 67 ++++++++++++++++---
 .../wpmangareader/WPMangaReaderGenerator.kt   |  2 +-
 .../multisrc/wpmangastream/WPMangaStream.kt   | 67 ++++++++++++++++---
 .../wpmangastream/WPMangaStreamGenerator.kt   |  2 +-
 4 files changed, 119 insertions(+), 19 deletions(-)

diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReader.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReader.kt
index 19659431b..84f1767fe 100644
--- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReader.kt
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReader.kt
@@ -1,6 +1,7 @@
 package eu.kanade.tachiyomi.multisrc.wpmangareader
 
 import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.POST
 import eu.kanade.tachiyomi.network.asObservableSuccess
 import eu.kanade.tachiyomi.source.model.Filter
 import eu.kanade.tachiyomi.source.model.FilterList
@@ -13,6 +14,7 @@ import eu.kanade.tachiyomi.util.asJsoup
 import kotlinx.serialization.json.Json
 import kotlinx.serialization.json.jsonArray
 import kotlinx.serialization.json.jsonPrimitive
+import okhttp3.FormBody
 import okhttp3.HttpUrl
 import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
 import okhttp3.OkHttpClient
@@ -59,7 +61,6 @@ abstract class WPMangaReader(
     // search
     override fun searchMangaSelector() = ".utao .uta .imgu, .listupd .bs .bsx, .listo .bs .bsx"
 
-
     /**
      * Given some string which represents an http url, returns a identifier (id) for a manga
      * which can be used to fetch its details at "$baseUrl$mangaUrlDirectory/$id"
@@ -75,7 +76,8 @@ abstract class WPMangaReader(
             val isMangaUrl = listOf(
                 baseMangaUrl.host == url.host,
                 pathLengthIs(url, 2),
-                url.pathSegments[0] == baseMangaUrl.pathSegments[0]).all { it }
+                url.pathSegments[0] == baseMangaUrl.pathSegments[0]
+            ).all { it }
             val potentiallyChapterUrl = pathLengthIs(url, 1)
             if (isMangaUrl)
                 Single.just(url.pathSegments[1])
@@ -104,14 +106,12 @@ abstract class WPMangaReader(
                 else
                     fetchMangaDetails(SManga.create().apply { this.url = "$mangaUrlDirectory/$id" })
                         .map {
-                            it.url = "$mangaUrlDirectory/$id"// isn't set in returned manga
+                            it.url = "$mangaUrlDirectory/$id" // isn't set in returned manga
                             MangasPage(listOf(it), false)
                         }
             }
-
     }
 
-
     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
         var url = "$baseUrl".toHttpUrlOrNull()!!.newBuilder()
         if (query.isNotEmpty()) {
@@ -182,10 +182,10 @@ abstract class WPMangaReader(
 
         // add alternative name to manga description
         document.select(altNameSelector).firstOrNull()?.ownText()?.let {
-            if (it.isBlank().not()) {
-                description = when {
-                    description.isNullOrBlank() -> altName + it
-                    else -> description + "\n\n$altName" + it
+            if (it.isEmpty().not()) {
+                description += when {
+                    description!!.isEmpty() -> altName + it
+                    else -> "\n\n$altName" + it
                 }
             }
         }
@@ -213,6 +213,8 @@ abstract class WPMangaReader(
         val checkChapter = document.select(chapterListSelector()).firstOrNull()
         if (date != "" && checkChapter != null) chapters[0].date_upload = parseDate(date)
 
+        countViews(document)
+
         return chapters
     }
 
@@ -243,6 +245,8 @@ abstract class WPMangaReader(
             .filterNot { it.attr("abs:src").isNullOrEmpty() }
             .mapIndexed { i, img -> pages.add(Page(i, "", img.attr("abs:src"))) }
 
+        countViews(document)
+
         // Some sites like mangakita now load pages via javascript
         if (pages.isNotEmpty()) { return pages }
 
@@ -261,6 +265,48 @@ abstract class WPMangaReader(
 
     override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used")
 
+    /**
+     * Set it to false if you want to disable the extension reporting the view count
+     * back to the source website through admin-ajax.php.
+     */
+    protected open val sendViewCount: Boolean = true
+
+    protected open fun countViewsRequest(document: Document): Request? {
+        val wpMangaData = document.select("script:containsData(dynamic_view_ajax)").firstOrNull()
+            ?.data() ?: return null
+
+        val postId = CHAPTER_PAGE_ID_REGEX.find(wpMangaData)?.groupValues?.get(1)
+            ?: MANGA_PAGE_ID_REGEX.find(wpMangaData)?.groupValues?.get(1)
+            ?: return null
+
+        val formBody = FormBody.Builder()
+            .add("action", "dynamic_view_ajax")
+            .add("post_id", postId)
+            .build()
+
+        val newHeaders = headersBuilder()
+            .set("Content-Length", formBody.contentLength().toString())
+            .set("Content-Type", formBody.contentType().toString())
+            .set("Referer", document.location())
+            .build()
+
+        return POST("$baseUrl/wp-admin/admin-ajax.php", newHeaders, formBody)
+    }
+
+    /**
+     * Send the view count request to the Madara endpoint.
+     *
+     * @param document The response document with the wp-manga data
+     */
+    protected open fun countViews(document: Document) {
+        if (!sendViewCount) {
+            return
+        }
+
+        val request = countViewsRequest(document) ?: return
+        runCatching { client.newCall(request).execute().close() }
+    }
+
     private interface UrlEncoded {
         fun encode(url: HttpUrl.Builder)
     }
@@ -379,5 +425,8 @@ abstract class WPMangaReader(
 
     companion object {
         const val URL_SEARCH_PREFIX = "url:"
+
+        private val MANGA_PAGE_ID_REGEX = "post_id\\s*:\\s*(\\d+)\\}".toRegex()
+        private val CHAPTER_PAGE_ID_REGEX = "post_id\\s*=\\s*(\\d+);?".toRegex()
     }
 }
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReaderGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReaderGenerator.kt
index 768014ca1..645666a33 100644
--- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReaderGenerator.kt
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReaderGenerator.kt
@@ -9,7 +9,7 @@ class WPMangaReaderGenerator : ThemeSourceGenerator {
 
     override val themeClass = "WPMangaReader"
 
-    override val baseVersionCode: Int = 9
+    override val baseVersionCode: Int = 10
 
     override val sources = listOf(
         SingleLang("Anitation Arts", "https://anitationarts.org", "en", overrideVersionCode = 1),
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStream.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStream.kt
index 54abe6cc6..4af6e3051 100644
--- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStream.kt
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStream.kt
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.multisrc.wpmangastream
 import android.app.Application
 import android.content.SharedPreferences
 import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.POST
 import eu.kanade.tachiyomi.source.ConfigurableSource
 import eu.kanade.tachiyomi.source.model.Filter
 import eu.kanade.tachiyomi.source.model.FilterList
@@ -14,6 +15,7 @@ import eu.kanade.tachiyomi.util.asJsoup
 import kotlinx.serialization.json.Json
 import kotlinx.serialization.json.jsonArray
 import kotlinx.serialization.json.jsonPrimitive
+import okhttp3.FormBody
 import okhttp3.Headers
 import okhttp3.HttpUrl.Companion.toHttpUrl
 import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
@@ -39,14 +41,6 @@ abstract class WPMangaStream(
 ) : ConfigurableSource, ParsedHttpSource() {
     override val supportsLatest = true
 
-    companion object {
-        private const val MID_QUALITY = 1
-        private const val LOW_QUALITY = 2
-
-        private const val SHOW_THUMBNAIL_PREF_Title = "Default thumbnail quality"
-        private const val SHOW_THUMBNAIL_PREF = "showThumbnailDefault"
-    }
-
     private val preferences: SharedPreferences by lazy {
         Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
     }
@@ -210,6 +204,8 @@ abstract class WPMangaStream(
         val checkChapter = document.select(chapterListSelector()).firstOrNull()
         if (date != "" && checkChapter != null) chapters[0].date_upload = parseDate(date)
 
+        countViews(document)
+
         return chapters
     }
 
@@ -296,6 +292,8 @@ abstract class WPMangaStream(
             htmlPages += scriptPages
         }
 
+        countViews(document)
+
         return htmlPages.distinctBy { it.imageUrl }
     }
 
@@ -327,6 +325,48 @@ abstract class WPMangaStream(
         }
     }
 
+    /**
+     * Set it to false if you want to disable the extension reporting the view count
+     * back to the source website through admin-ajax.php.
+     */
+    protected open val sendViewCount: Boolean = true
+
+    protected open fun countViewsRequest(document: Document): Request? {
+        val wpMangaData = document.select("script:containsData(dynamic_view_ajax)").firstOrNull()
+            ?.data() ?: return null
+
+        val postId = CHAPTER_PAGE_ID_REGEX.find(wpMangaData)?.groupValues?.get(1)
+            ?: MANGA_PAGE_ID_REGEX.find(wpMangaData)?.groupValues?.get(1)
+            ?: return null
+
+        val formBody = FormBody.Builder()
+            .add("action", "dynamic_view_ajax")
+            .add("post_id", postId)
+            .build()
+
+        val newHeaders = headersBuilder()
+            .set("Content-Length", formBody.contentLength().toString())
+            .set("Content-Type", formBody.contentType().toString())
+            .set("Referer", document.location())
+            .build()
+
+        return POST("$baseUrl/wp-admin/admin-ajax.php", newHeaders, formBody)
+    }
+
+    /**
+     * Send the view count request to the Madara endpoint.
+     *
+     * @param document The response document with the wp-manga data
+     */
+    protected open fun countViews(document: Document) {
+        if (!sendViewCount) {
+            return
+        }
+
+        val request = countViewsRequest(document) ?: return
+        runCatching { client.newCall(request).execute().close() }
+    }
+
     private class AuthorFilter : Filter.Text("Author")
 
     private class YearFilter : Filter.Text("Year")
@@ -476,4 +516,15 @@ abstract class WPMangaStream(
         Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
         fun toUriPart() = vals[state].second
     }
+
+    companion object {
+        private const val MID_QUALITY = 1
+        private const val LOW_QUALITY = 2
+
+        private const val SHOW_THUMBNAIL_PREF_Title = "Default thumbnail quality"
+        private const val SHOW_THUMBNAIL_PREF = "showThumbnailDefault"
+
+        private val MANGA_PAGE_ID_REGEX = "post_id\\s*:\\s*(\\d+)\\}".toRegex()
+        private val CHAPTER_PAGE_ID_REGEX = "chapter_id\\s*=\\s*(\\d+);?".toRegex()
+    }
 }
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamGenerator.kt
index aa737a1f6..df299cc54 100644
--- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamGenerator.kt
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamGenerator.kt
@@ -9,7 +9,7 @@ class WPMangaStreamGenerator : ThemeSourceGenerator {
 
     override val themeClass = "WPMangaStream"
 
-    override val baseVersionCode: Int = 10
+    override val baseVersionCode: Int = 11
 
     override val sources = listOf(
             SingleLang("Asura Scans", "https://www.asurascans.com", "en", overrideVersionCode = 5),