MrBoomDeveloper / Awery

A mobile app for watching Anime, Movies and TV series based on Dantotsu, which is based on Saikou.
Other
244 stars 7 forks source link

Predict next episode release date #21

Open MrBoomDeveloper opened 7 months ago

MrBoomDeveloper commented 7 months ago

Screenshot_2024-04-16-22-13-08-81_572064f74bd5f9fa804b05334aa4f912

minsoramune commented 7 months ago

do you think this will work? this is mangaplus API actual code of abandonedcart has used mangaupdates API

package ani.dantotsu.connections.mangaplus

import android.content.Context
import ani.dantotsu.R
import ani.dantotsu.client
import ani.dantotsu.media.Media
import ani.dantotsu.tryWithSuspend
import ani.dantotsu.util.Logger
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.json.JSONException
import org.json.JSONObject
import org.jsoup.Jsoup
import java.io.IOException
import java.net.URLEncoder
import java.nio.charset.Charset
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.TimeUnit

class MangaPlusAPI {

    val String.utf8: String get() = URLEncoder.encode(this, Charset.forName("UTF-8").name())

    private val Int?.dateFormat get() = String.format("%02d", this)

    suspend fun findLatestRelease(media: Media): ReleaseResponse.Results? {
        return tryWithSuspend {
            val query = JSONObject().apply {
                try {
                    put("title", media.mangaName().utf8)
                    media.startDate?.let {
                        put(
                            "start_date",
                            "${it.year}-${it.month.dateFormat}-${it.day.dateFormat}"
                        )
                    }
                } catch (e: JSONException) {
                    e.printStackTrace()
                }
            }
            val res = client.post(
                "https://mangaplus.shueisha.co.jp/api/series/${media.mangaName().utf8}",
                json = query
            ).parsed<ReleaseResponse>()
            coroutineScope {
                res.data?.map {
                    async(Dispatchers.IO) {
                        Logger.log(it.toString())
                    }
                }
            }?.awaitAll()
            res.data?.firstOrNull()
        }
    }

    suspend fun getSeries(results: ReleaseResponse.Results): SeriesResponse? {
        return tryWithSuspend {
            val res = client.get(
                "https://mangaplus.shueisha.co.jp/api/manga/${results.titleId}"
            ).parsed<SeriesResponse>()
            Logger.log(res.toString())
            res.latestChapter?.let { res }
        }
    }

    fun getLatestChapter(context: Context, results: ReleaseResponse.Results): String {
        return context.getString(R.string.chapter_number, results.titleLastChapter)
    }

    private suspend fun findReleaseDates(media: Media): List<String> {
        val releaseList = hashMapOf<String, String>()
        return tryWithSuspend {
            val query = JSONObject().apply {
                try {
                    put("title", media.mangaName().utf8)
                    media.startDate?.let {
                        put(
                            "start_date",
                            "${it.year}-${it.month.dateFormat}-${it.day.dateFormat}"
                        )
                    }
                } catch (e: JSONException) {
                    e.printStackTrace()
                }
            }
            val res = client.post(
                "https://mangaplus.shueisha.co.jp/api/series/${media.mangaName().utf8}",
                json = query
            ).parsed<ReleaseResponse>()
            res.data?.filter {
                it.releaseDate != null
            }?.sortedByDescending { it.releaseDate }?.forEach {
                releaseList[it.titleLastChapter] = it.releaseDate!!
                Logger.log(it.toString())
            }
            releaseList.values.toList().sortedDescending()
        } ?: releaseList.values.toList()
    }

    private fun getCalendarInstance(releaseDate: String): Calendar {
        val calendar: Calendar = Calendar.getInstance()
        val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ROOT)
        calendar.timeZone = TimeZone.getDefault()
        dateFormat.parse(releaseDate)?.let { calendar.time = it }
        return calendar
    }

    suspend fun predictRelease(media: Media, latest: Long): Long? {
        val releaseDates = findReleaseDates(media)
        if (releaseDates.size < 5) return null
        releaseDates.forEach {
            Logger.log(it)
        }
        val date01 = getCalendarInstance(releaseDates[0])
        val date02 = getCalendarInstance(releaseDates[1])
        val date03 = getCalendarInstance(releaseDates[2])
        val date04 = getCalendarInstance(releaseDates[3])
        val date05 = getCalendarInstance(releaseDates[4])
        val days0102: Long = TimeUnit.MILLISECONDS.toDays(date01.timeInMillis - date02.timeInMillis)
        val days0203: Long = TimeUnit.MILLISECONDS.toDays(date02.timeInMillis - date03.timeInMillis)
        val days0304: Long = TimeUnit.MILLISECONDS.toDays(date03.timeInMillis - date04.timeInMillis)
        val days0405: Long = TimeUnit.MILLISECONDS.toDays(date04.timeInMillis - date05.timeInMillis)

        val average = (days0102 + days0203 + days0304 + days0405) / 4

        val date: Calendar = Calendar.getInstance()
        date.timeInMillis = latest

        return when {
            average in 5..14 -> {
                latest + 604800000 // 7 days
            }
            average in 28..36 -> {
                date.add(Calendar.MONTH, 1)
                date.timeInMillis
            }
            average in 84..98 -> {
                date.add(Calendar.MONTH, 3)
                date.timeInMillis
            }
            average >= 358 -> {
                date.add(Calendar.YEAR, 1)
                date.timeInMillis
            }
            else -> {
                null
            }
        }
    }

    @Serializable
    data class ReleaseResponse(
        val data: List<Data>?
    ) {
        @Serializable
        data class Data(
            val titleId: String,
            val title: String,
            val titleLastChapter: String,
            val releaseDate: String?
        )
    }

    @Serializable
    data class SeriesResponse(
        val titleId: String,
        val title: String,
        val titleEn: String?,
        val titleUrl: String?,
        val summary: String?,
        val thumbnailUrl: String?,
        val author: String?,
        val updateDay: String?,
        val isSerializing: Boolean,
        val firstChapterAt: String?,
        val lastChapterAt: String?,
        val viewCount: Int?,
        val favoriteCount: Int?,
        val pageViewCount: Int?,
        val shareCount: Int?,
        val episodeCount: Int?,
        val latestChapter: String?,
        val isLatestChapter: Boolean
    )

    suspend fun scrapeMangaInfo(mangaUrl: String): MangaInfo? {
        return try {
            val doc = Jsoup.connect(mangaUrl).get()
            val title = doc.select("h1.title").text()
            val latestChapter = doc.select("div.chapter-btn").first()?.text()
            val summary = doc.select("div#introduction-detail").text()
            val thumbnailUrl = doc.select("img.lazyload").attr("data-src")

            MangaInfo(title, latestChapter, summary, thumbnailUrl)
        } catch (e: IOException) {
            Logger.logError("Error scraping manga information: ${e.message}")
            null
        }
    }

    data class MangaInfo(
        val title: String,
        val latestChapter: String?,
        val summary: String?,
        val thumbnailUrl: String?
    )
}
MrBoomDeveloper commented 7 months ago

@minsoramune We can first ask the user to get a list of chapters from some source, and then, based on the release dates of the chapters, display the approximate date of the next one using the average duration between their release dates.

minsoramune commented 7 months ago

@minsoramune We can first ask the user to get a list of chapters from some source, and then, based on the release dates of the chapters, display the approximate date of the next one using the average duration between their release dates.

Yh main code gets info from the scanlators if I'm not wrong