mirror of
https://github.com/prajwalch/TorrentSearch.git
synced 2026-05-18 09:30:35 +00:00
feat: Enforce same upload date formatting for torrents using better date parser
Signed-off-by: prajwalch <prajwal.chapagain58@gmail.com>
This commit is contained in:
@@ -2,11 +2,11 @@
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 5,
|
||||
"identityHash": "a6af5f7e17fb26408d670e52e0d69958",
|
||||
"identityHash": "0128221c3b632dc50d6f1acb304aca4b",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "bookmarks",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `size` TEXT NOT NULL, `seeders` INTEGER NOT NULL, `peers` INTEGER NOT NULL, `providerName` TEXT NOT NULL, `uploadDate` TEXT NOT NULL, `category` TEXT NOT NULL, `descriptionPageUrl` TEXT NOT NULL, `magnetUri` TEXT DEFAULT NULL, `fileDownloadLink` TEXT DEFAULT NULL, PRIMARY KEY(`id`))",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `size` TEXT NOT NULL, `seeders` INTEGER NOT NULL, `peers` INTEGER NOT NULL, `providerName` TEXT NOT NULL, `uploadDate` INTEGER DEFAULT NULL, `category` TEXT NOT NULL, `descriptionPageUrl` TEXT NOT NULL, `magnetUri` TEXT DEFAULT NULL, `fileDownloadLink` TEXT DEFAULT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "infoHash",
|
||||
@@ -47,8 +47,8 @@
|
||||
{
|
||||
"fieldPath": "uploadDate",
|
||||
"columnName": "uploadDate",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
"affinity": "INTEGER",
|
||||
"defaultValue": "NULL"
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
@@ -208,7 +208,7 @@
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a6af5f7e17fb26408d670e52e0d69958')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0128221c3b632dc50d6f1acb304aca4b')"
|
||||
]
|
||||
}
|
||||
}
|
||||
+52
-2
@@ -22,8 +22,12 @@ import com.prajwalch.torrentsearch.data.local.entities.BookmarkedTorrent
|
||||
import com.prajwalch.torrentsearch.data.local.entities.SearchHistoryEntity
|
||||
import com.prajwalch.torrentsearch.data.local.entities.TorznabConfigEntity
|
||||
import com.prajwalch.torrentsearch.data.local.entities.ViewedTorrentEntity
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import java.time.Instant
|
||||
import java.util.Locale
|
||||
|
||||
/** Application database. */
|
||||
@Database(
|
||||
entities = [
|
||||
@@ -95,6 +99,7 @@ abstract class TorrentSearchDatabase : RoomDatabase() {
|
||||
/**
|
||||
* Migration from version 4 to 5:
|
||||
* - Changes `bookmarks.id` from `Long` to `String` (info hash).
|
||||
* - Changes `bookmarks.uploadDate` from `String` to `Long` representing [java.time.Instant].
|
||||
* - Creates a new viewed_torrents table.
|
||||
*/
|
||||
private val MIGRATION_4_5 = object : Migration(4, 5) {
|
||||
@@ -113,7 +118,7 @@ private val MIGRATION_4_5 = object : Migration(4, 5) {
|
||||
`seeders` INTEGER NOT NULL,
|
||||
`peers` INTEGER NOT NULL,
|
||||
`providerName` TEXT NOT NULL,
|
||||
`uploadDate` TEXT NOT NULL,
|
||||
`uploadDate` INTEGER DEFAULT NULL,
|
||||
`category` TEXT NOT NULL,
|
||||
`descriptionPageUrl` TEXT NOT NULL,
|
||||
`magnetUri` TEXT DEFAULT NULL,
|
||||
@@ -139,6 +144,10 @@ private val MIGRATION_4_5 = object : Migration(4, 5) {
|
||||
val peers = cursor.getInt(cursor.getColumnIndexOrThrow("peers"))
|
||||
val providerName = cursor.getString(cursor.getColumnIndexOrThrow("providerName"))
|
||||
val uploadDate = cursor.getString(cursor.getColumnIndexOrThrow("uploadDate"))
|
||||
val newUploadDate = parseOldTorrentUploadDate(
|
||||
date = uploadDate,
|
||||
providerName = providerName,
|
||||
)?.toEpochMilli()
|
||||
val category = cursor.getString(cursor.getColumnIndexOrThrow("category"))
|
||||
val descriptionPageUrl = cursor
|
||||
.getString(cursor.getColumnIndexOrThrow("descriptionPageUrl"))
|
||||
@@ -157,7 +166,7 @@ private val MIGRATION_4_5 = object : Migration(4, 5) {
|
||||
put("seeders", seeders)
|
||||
put("peers", peers)
|
||||
put("providerName", providerName)
|
||||
put("uploadDate", uploadDate)
|
||||
put("uploadDate", newUploadDate)
|
||||
put("category", category)
|
||||
put("descriptionPageUrl", descriptionPageUrl)
|
||||
put("magnetUri", magnetUri)
|
||||
@@ -183,4 +192,45 @@ private val MIGRATION_4_5 = object : Migration(4, 5) {
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseOldTorrentUploadDate(
|
||||
date: String,
|
||||
providerName: String,
|
||||
): Instant? = when (providerName) {
|
||||
// Eztv uses weird formatting on top of that it's now Cloudflare protected.
|
||||
// AniRena and FileMood date parsing was involved.
|
||||
"AniRena", "Eztv", "FileMood" -> null
|
||||
|
||||
"AnimeTosho",
|
||||
"BitSearch",
|
||||
"Dmhy",
|
||||
"InternetArchive",
|
||||
"Knaben",
|
||||
"Nyaa",
|
||||
"SubsPlease",
|
||||
"Sukebei",
|
||||
"ThePirateBay",
|
||||
"TheRarBg",
|
||||
"TokyoToshokan",
|
||||
"TorrentDatabase",
|
||||
"TorrentDownloads",
|
||||
"TorrentsCSV",
|
||||
"XXXClub",
|
||||
"Yts",
|
||||
-> TorrentDateParser.parse(date = date, format = "dd MMM yyyy")
|
||||
|
||||
"LimeTorrents",
|
||||
"MyPornClub",
|
||||
"TorrentDownload",
|
||||
"UIndex",
|
||||
-> TorrentDateParser.tryParseRelative(date)
|
||||
|
||||
"XXXTracker" -> TorrentDateParser.parse(
|
||||
date = date,
|
||||
format = "dd MMM yy",
|
||||
locale = Locale.forLanguageTag("ru_RU"),
|
||||
)
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -9,6 +9,7 @@ import com.prajwalch.torrentsearch.domain.model.Category
|
||||
import com.prajwalch.torrentsearch.domain.model.Torrent
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.time.Instant
|
||||
|
||||
@Entity(
|
||||
tableName = "bookmarks",
|
||||
@@ -24,7 +25,8 @@ data class BookmarkedTorrent(
|
||||
val seeders: Int,
|
||||
val peers: Int,
|
||||
val providerName: String,
|
||||
val uploadDate: String,
|
||||
@ColumnInfo(defaultValue = "NULL")
|
||||
val uploadDate: Long? = null,
|
||||
val category: String,
|
||||
val descriptionPageUrl: String,
|
||||
@ColumnInfo(defaultValue = "NULL")
|
||||
@@ -41,7 +43,7 @@ fun BookmarkedTorrent.toDomain() =
|
||||
seeders = this.seeders.toUInt(),
|
||||
peers = this.peers.toUInt(),
|
||||
providerName = this.providerName,
|
||||
uploadDate = this.uploadDate,
|
||||
uploadDate = this.uploadDate?.let(Instant::ofEpochMilli),
|
||||
category = if (this.category.isNotEmpty()) {
|
||||
Category.valueOf(this.category)
|
||||
} else {
|
||||
@@ -60,7 +62,7 @@ fun Torrent.toEntity() =
|
||||
seeders = this.seeders.toInt(),
|
||||
peers = this.peers.toInt(),
|
||||
providerName = this.providerName,
|
||||
uploadDate = this.uploadDate,
|
||||
uploadDate = this.uploadDate?.toEpochMilli(),
|
||||
category = this.category?.name ?: "",
|
||||
descriptionPageUrl = this.descriptionPageUrl,
|
||||
magnetUri = this.magnetUri,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.prajwalch.torrentsearch.domain.model
|
||||
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
import java.time.Instant
|
||||
|
||||
/** Represents a magnet URI. */
|
||||
typealias MagnetUri = String
|
||||
@@ -19,8 +20,8 @@ data class Torrent(
|
||||
val peers: UInt,
|
||||
/** Name of the search provider from where torrent is searched. */
|
||||
val providerName: String,
|
||||
/** Torrent upload date (in pretty format). */
|
||||
val uploadDate: String,
|
||||
/** Torrent upload date. */
|
||||
val uploadDate: Instant? = null,
|
||||
/** Category of the torrent. */
|
||||
val category: Category? = null,
|
||||
/** URL of the page where the torrent details is available. */
|
||||
|
||||
@@ -91,7 +91,6 @@ class AniRena : SearchProvider {
|
||||
// Getting upload date requires an additional request to
|
||||
// 'anirena.com/torrent_details.php?id={id}'. The ID can be found in
|
||||
// the 'id' attribute of the element next to given div as 'details{id}'.
|
||||
uploadDate = "0m ago",
|
||||
category = specializedCategory,
|
||||
providerName = name,
|
||||
descriptionPageUrl = "",
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.prajwalch.torrentsearch.domain.model.Category
|
||||
import com.prajwalch.torrentsearch.domain.model.Torrent
|
||||
import com.prajwalch.torrentsearch.domain.model.TorrentDetails
|
||||
import com.prajwalch.torrentsearch.network.HttpClient
|
||||
import com.prajwalch.torrentsearch.util.DateUtils
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -14,6 +14,8 @@ import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.nodes.TextNode
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
class AnimeTosho : SearchProvider, TorrentDetailsProvider {
|
||||
override val id = "animetosho"
|
||||
override val name = "AnimeTosho"
|
||||
@@ -80,7 +82,7 @@ private class AnimeToshoResultsPageParser(
|
||||
}
|
||||
|
||||
/** Parses the upload date and converts "Today"/"Yesterday" into real dates. */
|
||||
private fun parseUploadDate(entryDiv: Element): String? {
|
||||
private fun parseUploadDate(entryDiv: Element): Instant? {
|
||||
val raw = entryDiv
|
||||
.selectFirst("div.date")
|
||||
?.attr("title")
|
||||
@@ -89,14 +91,13 @@ private class AnimeToshoResultsPageParser(
|
||||
?: return null
|
||||
|
||||
return when {
|
||||
raw.startsWith("Today") -> DateUtils.formatTodayDate()
|
||||
raw.startsWith("Yesterday") -> DateUtils.formatYesterdayDate()
|
||||
raw.startsWith("Today") -> TorrentDateParser.getTodayDate()
|
||||
raw.startsWith("Yesterday") -> TorrentDateParser.getYesterdayDate()
|
||||
else -> {
|
||||
raw
|
||||
.split(' ', limit = 2)
|
||||
.firstOrNull()
|
||||
?.let { DateUtils.formatDayMonthYear(it) }
|
||||
?: raw
|
||||
?.let(TorrentDateParser::parseDayMonthYear)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.prajwalch.torrentsearch.domain.model.Category
|
||||
import com.prajwalch.torrentsearch.domain.model.Torrent
|
||||
import com.prajwalch.torrentsearch.domain.model.TorrentDetails
|
||||
import com.prajwalch.torrentsearch.network.HttpClient
|
||||
import com.prajwalch.torrentsearch.util.DateUtils
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -104,7 +104,7 @@ private class BitSearchResultsPageParser(
|
||||
val seeders = listItem.selectFirst(SEEDERS)?.ownText()
|
||||
val peers = listItem.selectFirst(PEERS)?.ownText()
|
||||
val uploadDate = listItem.selectFirst(UPLOAD_DATE)?.ownText()
|
||||
?.let(DateUtils::formatMonthDayYear)
|
||||
?.let(TorrentDateParser::parseMonthDayYear)
|
||||
val category = listItem.selectFirst(CATEGORY)?.ownText()?.let(::categoryFromRawString)
|
||||
val fileDownloadLink = listItem.selectFirst(FILE_DOWNLOAD_LINK)?.attr("abs:href")
|
||||
val detailsPageUrl = listItem.selectFirst(DETAILS_PAGE_URL)?.attr("abs:href")
|
||||
@@ -116,7 +116,7 @@ private class BitSearchResultsPageParser(
|
||||
seeders = seeders?.toUIntOrNull() ?: 0U,
|
||||
peers = peers?.toUIntOrNull() ?: 0U,
|
||||
providerName = providerName,
|
||||
uploadDate = uploadDate ?: "0 min. ago",
|
||||
uploadDate = uploadDate,
|
||||
category = category,
|
||||
magnetUri = magnetUri,
|
||||
fileDownloadLink = fileDownloadLink,
|
||||
@@ -134,7 +134,7 @@ private class BitSearchResultsPageParser(
|
||||
private const val SIZE = "$CATEGORY_AND_METADATA > span:nth-child(2) > span"
|
||||
private const val SEEDERS = "$SWARM_STATS > span:nth-child(1) > span:nth-child(2)"
|
||||
private const val PEERS = "$SWARM_STATS > span:nth-child(2) > span:nth-child(2)"
|
||||
private const val UPLOAD_DATE = "$CATEGORY_AND_METADATA >span:nth-child(3) > span"
|
||||
private const val UPLOAD_DATE = "$CATEGORY_AND_METADATA > span:nth-child(3) > span"
|
||||
private const val CATEGORY = "$CATEGORY_AND_METADATA > span:nth-child(1) > span"
|
||||
private const val MAGNET_LINK = "$DOWNLOAD_LINKS > a:nth-child(2)"
|
||||
private const val FILE_DOWNLOAD_LINK = "$DOWNLOAD_LINKS > a:nth-child(1)"
|
||||
|
||||
@@ -2,8 +2,8 @@ package com.prajwalch.torrentsearch.providers
|
||||
|
||||
import com.prajwalch.torrentsearch.domain.model.Category
|
||||
import com.prajwalch.torrentsearch.domain.model.Torrent
|
||||
import com.prajwalch.torrentsearch.util.DateUtils
|
||||
import com.prajwalch.torrentsearch.util.FileSizeUtils
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -53,10 +53,7 @@ private class DmhyResultsPageParser(
|
||||
val seeders = listItem.selectFirst(SEEDERS)?.text()?.toUIntOrNull()
|
||||
val peers = listItem.selectFirst(PEERS)?.text()?.toUIntOrNull()
|
||||
val uploadDate = listItem.selectFirst(UPLOAD_DATE)?.ownText()
|
||||
?.split(' ')
|
||||
?.firstOrNull()
|
||||
?.replace("/", "-")
|
||||
?.let(DateUtils::formatYearMonthDay)
|
||||
?.let { TorrentDateParser.parse(date = it, format = "yyyy/MM/dd HH:mm") }
|
||||
val category = listItem.selectFirst(CATEGORY)?.className()
|
||||
?.removePrefix("sort-")
|
||||
?.let(::getCategoryFromId)
|
||||
@@ -68,7 +65,7 @@ private class DmhyResultsPageParser(
|
||||
size = size ?: "0 KB",
|
||||
seeders = seeders ?: 0U,
|
||||
peers = peers ?: 0U,
|
||||
uploadDate = uploadDate ?: "0 min ago",
|
||||
uploadDate = uploadDate,
|
||||
category = category,
|
||||
providerName = providerName,
|
||||
magnetUri = magnetUri,
|
||||
@@ -82,7 +79,7 @@ private class DmhyResultsPageParser(
|
||||
private const val SIZE = "td:nth-child(5)"
|
||||
private const val SEEDERS = "td:nth-child(6)"
|
||||
private const val PEERS = "td:nth-child(7)"
|
||||
private const val UPLOAD_DATE = "td:nth-child(1)"
|
||||
private const val UPLOAD_DATE = "td:nth-child(1) > span"
|
||||
private const val CATEGORY = "td:nth-child(2) > a"
|
||||
private const val MAGNET_URI = "td:nth-child(4) > a:nth-child(1)"
|
||||
private const val DETAILS_PAGE_URL = TORRENT_NAME
|
||||
|
||||
@@ -96,7 +96,7 @@ class Eztv : SearchProvider {
|
||||
// TODO: The date format used the results page is 'time ago'
|
||||
// (e.g. '7h 8m', '1 week', '1 mo'). The format we want
|
||||
// is present in the details page. Let's extract it in future.
|
||||
val uploadDate = tr.selectFirst("td:nth-child(5)")?.ownText() ?: return null
|
||||
// val uploadDate = tr.selectFirst("td:nth-child(5)")?.ownText() ?: return null
|
||||
|
||||
// Some torrents will not have any seeds (no idea why), in that case
|
||||
// it will contain '-' text node, and in other case it will contain a
|
||||
@@ -116,7 +116,7 @@ class Eztv : SearchProvider {
|
||||
seeders = seeders.toUIntOrNull() ?: 0u,
|
||||
peers = peers,
|
||||
providerName = name,
|
||||
uploadDate = uploadDate,
|
||||
// uploadDate = uploadDate,
|
||||
category = specializedCategory,
|
||||
descriptionPageUrl = descriptionPageUrl,
|
||||
magnetUri = magnetUri,
|
||||
|
||||
@@ -67,7 +67,6 @@ private class FileMoodResultsPageParser(private val providerName: String) {
|
||||
size = size ?: "0 KB",
|
||||
seeders = seeders?.toUIntOrNull() ?: 0U,
|
||||
peers = peers?.toUIntOrNull() ?: 0U,
|
||||
uploadDate = "0 min ago",
|
||||
category = Category.Other,
|
||||
providerName = providerName,
|
||||
descriptionPageUrl = descriptionPageUrl,
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.prajwalch.torrentsearch.extension.getString
|
||||
import com.prajwalch.torrentsearch.network.HttpClient
|
||||
import com.prajwalch.torrentsearch.util.DateUtils
|
||||
import com.prajwalch.torrentsearch.util.FileSizeUtils
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -96,7 +97,7 @@ private class IAResultsJsonParser(
|
||||
val size = obj.getLong("item_size")
|
||||
?.let { FileSizeUtils.formatBytes(it.toFloat()) }
|
||||
?: return null
|
||||
val uploadDate = obj.getString("publicdate")?.let(DateUtils::formatIsoDate) ?: return null
|
||||
val uploadDate = obj.getString("publicdate")?.let(TorrentDateParser::parseIso)
|
||||
val category = obj.getString("mediatype")?.let(::categoryFromMediaType) ?: return null
|
||||
val descriptionPageUrl = obj.getString("identifier")
|
||||
?.let { "$providerUrl/details/$it" }
|
||||
|
||||
@@ -7,8 +7,8 @@ import com.prajwalch.torrentsearch.extension.getArray
|
||||
import com.prajwalch.torrentsearch.extension.getLong
|
||||
import com.prajwalch.torrentsearch.extension.getString
|
||||
import com.prajwalch.torrentsearch.extension.getUInt
|
||||
import com.prajwalch.torrentsearch.util.DateUtils
|
||||
import com.prajwalch.torrentsearch.util.FileSizeUtils
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -89,7 +89,7 @@ class Knaben : SearchProvider {
|
||||
|
||||
val seeders = obj.getUInt("seeders") ?: 0u
|
||||
val peers = obj.getUInt("peers") ?: 0u
|
||||
val uploadDate = obj.getString("date")?.let { DateUtils.formatIsoDate(it) } ?: ""
|
||||
val uploadDate = obj.getString("date")?.let(TorrentDateParser::parseIso)
|
||||
val descriptionPageUrl = obj.getString("details").orEmpty()
|
||||
val category = extractCategory(obj)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.prajwalch.torrentsearch.domain.model.Category
|
||||
import com.prajwalch.torrentsearch.domain.model.Torrent
|
||||
import com.prajwalch.torrentsearch.domain.model.TorrentDetails
|
||||
import com.prajwalch.torrentsearch.network.HttpClient
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -81,7 +82,7 @@ private class LimeTorrentsResultsPageParser(
|
||||
val descriptionPageUrl = nameAnchor.attr("abs:href")
|
||||
|
||||
val infoHash = extractInfoHash(row) ?: return null
|
||||
val uploadDate = extractUploadDate(row)
|
||||
val uploadDate = extractUploadDate(row).let(TorrentDateParser::tryParseRelative)
|
||||
val size = row.selectFirst("td:nth-child(3)")?.text() ?: return null
|
||||
val seeders = row.selectFirst(".tdseed")?.text()?.toUIntOrNull() ?: 0u
|
||||
val peers = row.selectFirst(".tdleech")?.text()?.toUIntOrNull() ?: 0u
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.prajwalch.torrentsearch.domain.model.Category
|
||||
import com.prajwalch.torrentsearch.domain.model.Torrent
|
||||
import com.prajwalch.torrentsearch.domain.model.TorrentDetails
|
||||
import com.prajwalch.torrentsearch.network.HttpClient
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -72,6 +73,7 @@ private class MyPornClubResultsPageParser(
|
||||
val seeders = listItem.selectFirst(SEEDERS)?.ownText()?.toUIntOrNull()
|
||||
val peers = listItem.selectFirst(PEERS)?.ownText()?.toUIntOrNull()
|
||||
val uploadDate = listItem.selectFirst(UPLOAD_DATE)?.text()
|
||||
?.let(TorrentDateParser::tryParseRelative)
|
||||
|
||||
return Torrent(
|
||||
infoHash = torrentDetails.infoHash,
|
||||
@@ -80,7 +82,7 @@ private class MyPornClubResultsPageParser(
|
||||
seeders = seeders ?: 0U,
|
||||
peers = peers ?: 0U,
|
||||
providerName = providerName,
|
||||
uploadDate = uploadDate ?: "0 min ago",
|
||||
uploadDate = uploadDate,
|
||||
category = providerSpecializedCategory,
|
||||
descriptionPageUrl = detailsPageUrl,
|
||||
magnetUri = torrentDetails.magnetUri,
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.prajwalch.torrentsearch.domain.model.Category
|
||||
import com.prajwalch.torrentsearch.domain.model.Torrent
|
||||
import com.prajwalch.torrentsearch.domain.model.TorrentDetails
|
||||
import com.prajwalch.torrentsearch.network.HttpClient
|
||||
import com.prajwalch.torrentsearch.util.DateUtils
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -92,8 +92,8 @@ private class NyaaResultsPageParser(
|
||||
val uploadDate = tr
|
||||
.selectFirst("td:nth-child(5)")
|
||||
?.attr("data-timestamp")
|
||||
?.let { DateUtils.formatEpochSecond(it.toLong()) }
|
||||
?: return null
|
||||
?.toLongOrNull()
|
||||
?.let(TorrentDateParser::epochSecondToInstant)
|
||||
val seeders = tr.selectFirst("td:nth-child(6)")?.ownText() ?: return null
|
||||
val peers = tr.selectFirst("td:nth-child(7)")?.ownText() ?: return null
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ import com.prajwalch.torrentsearch.domain.model.Torrent
|
||||
import com.prajwalch.torrentsearch.extension.asObject
|
||||
import com.prajwalch.torrentsearch.extension.getArray
|
||||
import com.prajwalch.torrentsearch.extension.getString
|
||||
import com.prajwalch.torrentsearch.util.DateUtils
|
||||
import com.prajwalch.torrentsearch.util.FileSizeUtils
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -52,12 +52,9 @@ class SubsPlease : SearchProvider {
|
||||
|
||||
private fun parseAnimeObject(animeObject: JsonObject): List<Torrent>? {
|
||||
val name = animeObject.getString("show") ?: return null
|
||||
val uploadDate = animeObject
|
||||
.getString("release_date")
|
||||
?.let(DateUtils::formatRFC1123Date)
|
||||
?: return null
|
||||
val descriptionPageUrl = animeObject
|
||||
.getString("page")
|
||||
val uploadDate = animeObject.getString("release_date")
|
||||
?.let(TorrentDateParser::parseRFC1123)
|
||||
val descriptionPageUrl = animeObject.getString("page")
|
||||
?.let { "$url/$it" }
|
||||
?: return null
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.prajwalch.torrentsearch.domain.model.Category
|
||||
import com.prajwalch.torrentsearch.domain.model.Torrent
|
||||
import com.prajwalch.torrentsearch.domain.model.TorrentDetails
|
||||
import com.prajwalch.torrentsearch.network.HttpClient
|
||||
import com.prajwalch.torrentsearch.util.DateUtils
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -66,7 +66,7 @@ private class SukebeiResultsPageParser(
|
||||
val size = listItem.selectFirst(SIZE)?.ownText()
|
||||
val uploadDate = listItem.selectFirst(UPLOAD_DATE)?.attr("data-timestamp")
|
||||
?.toLongOrNull()
|
||||
?.let(DateUtils::formatEpochSecond)
|
||||
?.let(TorrentDateParser::epochSecondToInstant)
|
||||
val seeders = listItem.selectFirst(SEEDERS)?.ownText()?.toUIntOrNull()
|
||||
val peers = listItem.selectFirst(PEERS)?.ownText()?.toUIntOrNull()
|
||||
val detailsPageUrl = listItem.selectFirst(DETAILS_PAGE_URL)?.attr("abs:href")
|
||||
@@ -78,7 +78,7 @@ private class SukebeiResultsPageParser(
|
||||
seeders = seeders ?: 0u,
|
||||
peers = peers ?: 0u,
|
||||
providerName = providerName,
|
||||
uploadDate = uploadDate ?: "0 min ago",
|
||||
uploadDate = uploadDate,
|
||||
category = providerSpecializedCategory,
|
||||
descriptionPageUrl = detailsPageUrl ?: "",
|
||||
magnetUri = magnetUri,
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.prajwalch.torrentsearch.extension.getString
|
||||
import com.prajwalch.torrentsearch.network.HttpClient
|
||||
import com.prajwalch.torrentsearch.util.DateUtils
|
||||
import com.prajwalch.torrentsearch.util.FileSizeUtils
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -121,7 +122,7 @@ private class TBPResultsJsonParser(
|
||||
val uploadDate = torrentObject
|
||||
.getString("added")
|
||||
?.toLongOrNull()
|
||||
?.let { DateUtils.formatEpochSecond(it) }
|
||||
?.let(TorrentDateParser::epochSecondToInstant)
|
||||
?: return null
|
||||
|
||||
val categoryId = torrentObject.getString("category") ?: return null
|
||||
|
||||
@@ -5,8 +5,8 @@ import com.prajwalch.torrentsearch.domain.model.Category
|
||||
import com.prajwalch.torrentsearch.domain.model.Torrent
|
||||
import com.prajwalch.torrentsearch.domain.model.TorrentDetails
|
||||
import com.prajwalch.torrentsearch.network.HttpClient
|
||||
import com.prajwalch.torrentsearch.util.DateUtils
|
||||
import com.prajwalch.torrentsearch.util.FileSizeUtils
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -90,7 +90,7 @@ private class TheRarBgResultsPageParser(private val providerName: String) {
|
||||
val peers = listItem.selectFirst(PEERS)?.ownText()?.toUIntOrNull()
|
||||
val uploadDate = listItem.selectFirst(UPLOAD_DATE)?.attr("data-order")
|
||||
?.toLongOrNull()
|
||||
?.let(DateUtils::formatEpochSecond)
|
||||
?.let(TorrentDateParser::epochSecondToInstant)
|
||||
val category = listItem.selectFirst(CATEGORY)?.ownText()?.let(::categoryFromRawString)
|
||||
|
||||
return Torrent(
|
||||
@@ -100,7 +100,7 @@ private class TheRarBgResultsPageParser(private val providerName: String) {
|
||||
seeders = seeders ?: 0U,
|
||||
peers = peers ?: 0U,
|
||||
providerName = providerName,
|
||||
uploadDate = uploadDate ?: "0 min ago",
|
||||
uploadDate = uploadDate,
|
||||
category = category,
|
||||
magnetUri = torrentDetails.magnetUri,
|
||||
fileDownloadLink = torrentDetails.fileDownloadLink,
|
||||
|
||||
@@ -4,8 +4,8 @@ import com.prajwalch.torrentsearch.domain.model.Category
|
||||
import com.prajwalch.torrentsearch.domain.model.Torrent
|
||||
import com.prajwalch.torrentsearch.domain.model.TorrentDetails
|
||||
import com.prajwalch.torrentsearch.network.HttpClient
|
||||
import com.prajwalch.torrentsearch.util.DateUtils
|
||||
import com.prajwalch.torrentsearch.util.FileSizeUtils
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -75,8 +75,7 @@ private class TokyoToshokanResultsPageParser(
|
||||
?: listOf(null, null)
|
||||
val size = rawSize?.let(FileSizeUtils::normalizeSize)
|
||||
val uploadDate = rawUploadDate
|
||||
?.takeWhile { !it.isWhitespace() }
|
||||
?.let(DateUtils::formatYearMonthDay)
|
||||
?.let { TorrentDateParser.parse(date = it, format = "yyyy-MM-dd HH:mm Z") }
|
||||
|
||||
// Seeders and peers.
|
||||
val seeders = tr2.selectFirst(SEEDERS)?.ownText()?.toUIntOrNull()
|
||||
@@ -89,7 +88,7 @@ private class TokyoToshokanResultsPageParser(
|
||||
seeders = seeders ?: 0u,
|
||||
peers = peers ?: 0u,
|
||||
providerName = providerName,
|
||||
uploadDate = uploadDate ?: "0 min ago",
|
||||
uploadDate = uploadDate,
|
||||
category = providerSpecializedCategory,
|
||||
descriptionPageUrl = detailsPageUrl ?: "",
|
||||
magnetUri = magnetUri,
|
||||
|
||||
@@ -4,8 +4,8 @@ import com.prajwalch.torrentsearch.domain.model.Category
|
||||
import com.prajwalch.torrentsearch.domain.model.Torrent
|
||||
import com.prajwalch.torrentsearch.domain.model.TorrentDetails
|
||||
import com.prajwalch.torrentsearch.network.HttpClient
|
||||
import com.prajwalch.torrentsearch.util.DateUtils
|
||||
import com.prajwalch.torrentsearch.util.FileSizeUtils
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -78,8 +78,7 @@ private class TdResultsPageParser(private val providerName: String) {
|
||||
val size = listItem.selectFirst(SIZE)?.ownText()
|
||||
val uploadDate = listItem.selectFirst(UPLOAD_DATE)
|
||||
?.ownText()
|
||||
?.takeWhile { !it.isWhitespace() }
|
||||
?.let(DateUtils::formatYearMonthDay)
|
||||
?.let { TorrentDateParser.parse(date = it, format = "yyyy-MM-dd HH:mm:ss") }
|
||||
val seeders = listItem.selectFirst(SEEDERS)?.ownText()
|
||||
val peers = listItem.selectFirst(PEERS)?.ownText()
|
||||
|
||||
@@ -89,7 +88,7 @@ private class TdResultsPageParser(private val providerName: String) {
|
||||
size = size ?: "0 KB",
|
||||
seeders = seeders?.toUIntOrNull() ?: 0U,
|
||||
peers = peers?.toUIntOrNull() ?: 0U,
|
||||
uploadDate = uploadDate ?: "0 min ago",
|
||||
uploadDate = uploadDate,
|
||||
category = category,
|
||||
providerName = providerName,
|
||||
descriptionPageUrl = descriptionPageUrl ?: "",
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.prajwalch.torrentsearch.domain.model.Category
|
||||
import com.prajwalch.torrentsearch.domain.model.Torrent
|
||||
import com.prajwalch.torrentsearch.domain.model.TorrentDetails
|
||||
import com.prajwalch.torrentsearch.network.HttpClient
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -69,7 +70,8 @@ private class TorrentDownloadResultsParser(private val providerName: String) {
|
||||
.trim()
|
||||
.lowercase()
|
||||
|
||||
val uploadDate = tr.selectFirst("td:nth-child(2)")?.ownText() ?: return null
|
||||
val uploadDate = tr.selectFirst("td:nth-child(2)")?.ownText()
|
||||
?.let(TorrentDateParser::tryParseRelative)
|
||||
val size = tr.selectFirst("td:nth-child(3)")?.ownText() ?: return null
|
||||
val seeders = tr.selectFirst("td:nth-child(4)")?.ownText()?.replace(",", "") ?: return null
|
||||
val peers = tr.selectFirst("td:nth-child(5)")?.ownText()?.replace(",", "") ?: return null
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.prajwalch.torrentsearch.providers
|
||||
import com.prajwalch.torrentsearch.domain.model.Category
|
||||
import com.prajwalch.torrentsearch.domain.model.Torrent
|
||||
import com.prajwalch.torrentsearch.network.HttpClient
|
||||
import com.prajwalch.torrentsearch.util.DateUtils
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -12,6 +12,8 @@ import kotlinx.coroutines.withContext
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
class TorrentDownloads : SearchProvider {
|
||||
override val id = "torrentdownloads"
|
||||
override val name = "TorrentDownloads"
|
||||
@@ -110,7 +112,7 @@ class TorrentDownloads : SearchProvider {
|
||||
private suspend fun getUploadDateAndMagnetUri(
|
||||
httpClient: HttpClient,
|
||||
descriptionPageUrl: String,
|
||||
): Pair<String, String>? {
|
||||
): Pair<Instant?, String>? {
|
||||
val responseHtml = httpClient.get(url = descriptionPageUrl)
|
||||
val innerContainer = Jsoup
|
||||
.parse(responseHtml)
|
||||
@@ -127,10 +129,7 @@ class TorrentDownloads : SearchProvider {
|
||||
val uploadDate = greyBars.getOrNull(6)
|
||||
?.selectFirst("p")
|
||||
?.ownText()
|
||||
?.split(' ')
|
||||
?.first()
|
||||
?.let { DateUtils.formatYearMonthDay(it) }
|
||||
?: return null
|
||||
?.let { TorrentDateParser.parse(date = it, format = "yyyy-MM-dd HH:mm:ss") }
|
||||
|
||||
return Pair(uploadDate, magnetUri)
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import com.prajwalch.torrentsearch.extension.getArray
|
||||
import com.prajwalch.torrentsearch.extension.getLong
|
||||
import com.prajwalch.torrentsearch.extension.getString
|
||||
import com.prajwalch.torrentsearch.extension.getUInt
|
||||
import com.prajwalch.torrentsearch.util.DateUtils
|
||||
import com.prajwalch.torrentsearch.util.FileSizeUtils
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -68,9 +68,8 @@ class TorrentsCSV : SearchProvider {
|
||||
|
||||
val seeders = torrentObject.getUInt("seeders") ?: return null
|
||||
val peers = torrentObject.getUInt("leechers") ?: return null
|
||||
val uploadDate = torrentObject
|
||||
.getLong("created_unix")
|
||||
?.let { DateUtils.formatEpochSecond(it) }
|
||||
val uploadDate = torrentObject.getLong("created_unix")
|
||||
?.let(TorrentDateParser::epochSecondToInstant)
|
||||
?: return null
|
||||
|
||||
return Torrent(
|
||||
@@ -79,7 +78,7 @@ class TorrentsCSV : SearchProvider {
|
||||
size = size,
|
||||
seeders = seeders,
|
||||
peers = peers,
|
||||
providerName = name,
|
||||
providerName = this.name,
|
||||
uploadDate = uploadDate,
|
||||
descriptionPageUrl = "",
|
||||
)
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.prajwalch.torrentsearch.domain.model.TorznabConfig
|
||||
import com.prajwalch.torrentsearch.domain.model.TorznabConnectionCheckResult
|
||||
import com.prajwalch.torrentsearch.network.HttpClient
|
||||
import com.prajwalch.torrentsearch.util.FileSizeUtils
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
@@ -22,6 +23,7 @@ import org.xmlpull.v1.XmlPullParserException
|
||||
|
||||
import java.net.ConnectException
|
||||
import java.net.UnknownHostException
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Search provider (or indexer) custom category range start.
|
||||
@@ -345,7 +347,7 @@ private class TorznabResponseXmlParser(
|
||||
var size: String? = null
|
||||
var seeders: String? = null
|
||||
var peers: String? = null
|
||||
var uploadDate: String? = null
|
||||
var uploadDate: Instant? = null
|
||||
var descriptionPageUrl: String? = null
|
||||
var magnetUri: String? = null
|
||||
var infoHash: String? = null
|
||||
@@ -404,7 +406,7 @@ private class TorznabResponseXmlParser(
|
||||
seeders = seeders?.toUIntOrNull() ?: return,
|
||||
peers = peers?.toUIntOrNull() ?: return,
|
||||
providerName = providerName,
|
||||
uploadDate = uploadDate ?: return,
|
||||
uploadDate = uploadDate,
|
||||
category = category,
|
||||
descriptionPageUrl = descriptionPageUrl ?: return,
|
||||
magnetUri = magnetUri,
|
||||
@@ -421,11 +423,9 @@ private class TorznabResponseXmlParser(
|
||||
return readTextContainedTag(tagName = "comments")
|
||||
}
|
||||
|
||||
private fun readPubDate(): String {
|
||||
private fun readPubDate(): Instant {
|
||||
return readTextContainedTag(tagName = "pubDate")
|
||||
.split(' ')
|
||||
.subList(fromIndex = 1, toIndex = 4)
|
||||
.joinToString(separator = " ")
|
||||
.let(TorrentDateParser::parseRFC1123)
|
||||
}
|
||||
|
||||
private fun readSize(): String {
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.prajwalch.torrentsearch.domain.model.Category
|
||||
import com.prajwalch.torrentsearch.domain.model.Torrent
|
||||
import com.prajwalch.torrentsearch.domain.model.TorrentDetails
|
||||
import com.prajwalch.torrentsearch.network.HttpClient
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -75,6 +76,7 @@ private class UIndexResultsPageParser(
|
||||
val seeders = listItem.selectFirst(SEEDERS)?.ownText()?.filter { it != ',' }?.toUIntOrNull()
|
||||
val peers = listItem.selectFirst(PEERS)?.ownText()?.filter { it != ',' }?.toUIntOrNull()
|
||||
val uploadDate = listItem.selectFirst(UPLOAD_DATE)?.ownText()
|
||||
?.let(TorrentDateParser::tryParseRelative)
|
||||
val category = listItem.selectFirst(CATEGORY)?.ownText()?.let(::categoryFromRawString)
|
||||
val detailsPageUrl = listItem.selectFirst(DETAILS_PAGE_URL)?.attr("abs:href")
|
||||
|
||||
@@ -85,7 +87,7 @@ private class UIndexResultsPageParser(
|
||||
seeders = seeders ?: 0u,
|
||||
peers = peers ?: 0u,
|
||||
providerName = providerName,
|
||||
uploadDate = uploadDate ?: "0 min ago",
|
||||
uploadDate = uploadDate,
|
||||
category = category,
|
||||
descriptionPageUrl = detailsPageUrl ?: "",
|
||||
magnetUri = magnetUri,
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.prajwalch.torrentsearch.domain.model.Category
|
||||
import com.prajwalch.torrentsearch.domain.model.Torrent
|
||||
import com.prajwalch.torrentsearch.domain.model.TorrentDetails
|
||||
import com.prajwalch.torrentsearch.network.HttpClient
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -61,7 +62,8 @@ private class XXXClubResultsPageParser(private val providerName: String) {
|
||||
val size = listItem.selectFirst(SIZE)?.ownText()
|
||||
val seeders = listItem.selectFirst(SEEDERS)?.ownText()?.toUIntOrNull()
|
||||
val peers = listItem.selectFirst(PEERS)?.ownText()?.toUIntOrNull()
|
||||
val uploadDate = listItem.selectFirst(UPLOAD_DATE)?.ownText()?.let(::parseUploadDate)
|
||||
val uploadDate = listItem.selectFirst(UPLOAD_DATE)?.ownText()
|
||||
?.let { TorrentDateParser.parse(date = it, format = "dd MMM yyyy HH:mm:ss") }
|
||||
|
||||
return Torrent(
|
||||
infoHash = torrentDetails.infoHash,
|
||||
@@ -70,7 +72,7 @@ private class XXXClubResultsPageParser(private val providerName: String) {
|
||||
seeders = seeders ?: 0u,
|
||||
peers = peers ?: 0u,
|
||||
providerName = providerName,
|
||||
uploadDate = uploadDate ?: "0 min ago",
|
||||
uploadDate = uploadDate,
|
||||
category = Category.Porn,
|
||||
descriptionPageUrl = detailsPageUrl,
|
||||
magnetUri = torrentDetails.magnetUri,
|
||||
@@ -78,15 +80,6 @@ private class XXXClubResultsPageParser(private val providerName: String) {
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseUploadDate(raw: String): String {
|
||||
val lastSpaceIndex = raw
|
||||
.indexOfLast { ch -> ch == ' ' }
|
||||
.takeIf { it != -1 }
|
||||
?: return raw
|
||||
|
||||
return raw.substring(0..lastSpaceIndex).trim()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val LIST_ITEM = "ul.tsearch > li"
|
||||
private const val NAME = "span:nth-child(2) > a:nth-child(2)"
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.prajwalch.torrentsearch.domain.model.Category
|
||||
import com.prajwalch.torrentsearch.domain.model.Torrent
|
||||
import com.prajwalch.torrentsearch.domain.model.TorrentDetails
|
||||
import com.prajwalch.torrentsearch.network.HttpClient
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
import com.prajwalch.torrentsearch.util.TorrentUtils
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -12,6 +13,8 @@ import kotlinx.coroutines.withContext
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
class XXXTracker : SearchProvider, TorrentDetailsProvider {
|
||||
override val id = "xxxtracker"
|
||||
override val name = "XXXTracker"
|
||||
@@ -51,7 +54,13 @@ class XXXTracker : SearchProvider, TorrentDetailsProvider {
|
||||
}
|
||||
|
||||
private fun parseTr(tr: Element): Torrent? {
|
||||
val uploadDate = tr.selectFirst("td:nth-child(1)")?.ownText() ?: return null
|
||||
val uploadDate = tr.selectFirst("td:nth-child(1)")?.ownText()?.let {
|
||||
TorrentDateParser.parse(
|
||||
date = it,
|
||||
format = "dd MMM yy",
|
||||
locale = Locale.forLanguageTag("ru_RU"),
|
||||
)
|
||||
}
|
||||
|
||||
val secondTd = tr.selectFirst("td:nth-child(2)") ?: return null
|
||||
val magnetUri = secondTd.selectFirst("a:nth-child(1)")?.attr("href") ?: return null
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.prajwalch.torrentsearch.extension.getLong
|
||||
import com.prajwalch.torrentsearch.extension.getObject
|
||||
import com.prajwalch.torrentsearch.extension.getString
|
||||
import com.prajwalch.torrentsearch.extension.getUInt
|
||||
import com.prajwalch.torrentsearch.util.DateUtils
|
||||
import com.prajwalch.torrentsearch.util.TorrentDateParser
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -173,8 +173,7 @@ class Yts : SearchProvider {
|
||||
val peers = torrentObject.getUInt("peers") ?: return null
|
||||
val uploadDate = torrentObject
|
||||
.getLong("date_uploaded_unix")
|
||||
?.let { DateUtils.formatEpochSecond(it) }
|
||||
?: return null
|
||||
?.let(TorrentDateParser::epochSecondToInstant)
|
||||
|
||||
return Torrent(
|
||||
infoHash = infoHash,
|
||||
|
||||
+1
-1
@@ -96,7 +96,7 @@ private fun BookmarkListItem(
|
||||
size = bookmark.size,
|
||||
seeders = bookmark.seeders,
|
||||
peers = bookmark.peers,
|
||||
uploadDate = bookmark.uploadDate,
|
||||
uploadDate = bookmark.uploadDate?.toString(),
|
||||
category = bookmark.category,
|
||||
providerName = bookmark.providerName,
|
||||
isNSFW = bookmark.isNSFW,
|
||||
|
||||
@@ -34,7 +34,7 @@ fun TorrentListItem(
|
||||
size: String,
|
||||
seeders: UInt,
|
||||
peers: UInt,
|
||||
uploadDate: String,
|
||||
uploadDate: String?,
|
||||
category: Category?,
|
||||
providerName: String,
|
||||
isNSFW: Boolean,
|
||||
@@ -58,7 +58,7 @@ fun TorrentListItem(
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spaces.small),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(uploadDate)
|
||||
uploadDate?.let { Text(it) }
|
||||
if (isNSFW) NSFWBadge()
|
||||
}
|
||||
Text(providerName)
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ fun SearchResults(
|
||||
size = it.size,
|
||||
seeders = it.seeders,
|
||||
peers = it.peers,
|
||||
uploadDate = it.uploadDate,
|
||||
uploadDate = it.uploadDate?.toString(),
|
||||
category = it.category,
|
||||
providerName = it.providerName,
|
||||
isNSFW = it.isNSFW,
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
package com.prajwalch.torrentsearch.util
|
||||
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
object DateUtils {
|
||||
private val dateFormatter = TorrentSearchDateFormatter.init()
|
||||
/** The date output format. */
|
||||
const val OUTPUT_FORMAT = "dd MMM yyyy"
|
||||
|
||||
private val outputFormatter = DateTimeFormatter.ofPattern(OUTPUT_FORMAT)
|
||||
|
||||
fun formatEpochSecond(epochSecond: Long) = dateFormatter.formatEpochSecond(epochSecond)
|
||||
fun formatEpochSecond(epochSecond: Long): String {
|
||||
val instant = Instant.ofEpochSecond(epochSecond)
|
||||
|
||||
fun formatYearMonthDay(date: String) = dateFormatter.formatYearMonthDay(date)
|
||||
val zoneId = ZoneId.systemDefault()
|
||||
val zonedDateTime = instant.atZone(zoneId)
|
||||
|
||||
fun formatDayMonthYear(date: String) = dateFormatter.formatDayMonthYear(date)
|
||||
|
||||
fun formatMonthDayYear(date: String) = dateFormatter.formatMonthDayYear(date)
|
||||
|
||||
fun formatIsoDate(date: String) = dateFormatter.formatIsoDate(date)
|
||||
|
||||
fun formatRFC1123Date(date: String) = dateFormatter.formatRFC1123Date(date)
|
||||
|
||||
fun formatTodayDate() = dateFormatter.formatTodayDate()
|
||||
|
||||
fun formatYesterdayDate() = dateFormatter.formatYesterdayDate()
|
||||
return zonedDateTime.format(outputFormatter)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.prajwalch.torrentsearch.util
|
||||
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
|
||||
object TorrentDateParser {
|
||||
const val YEAR_MONTH_DAY = "yyyy-M-d"
|
||||
const val DAY_MONTH_YEAR = "d/M/yyyy"
|
||||
const val MONTH_DAY_YEAR = "M/d/yyyy"
|
||||
|
||||
private val RelativeTimePattern = Regex(
|
||||
"""\b(\d+(?:\.\d+)?)\s*(s|sec|secs|second|seconds|m|min|mins|minute|minutes|h|hr|hrs|hour|hours|d|day|days|w|wk|wks|week|weeks|mo|mos|month|months|y|yr|yrs|year|years)\b\s*(ago)?\b""",
|
||||
RegexOption.IGNORE_CASE
|
||||
)
|
||||
|
||||
private val DefaultTimeZone = ZoneOffset.UTC
|
||||
|
||||
fun parse(date: String, format: String): Instant? {
|
||||
val inputFormatter = DateTimeFormatter.ofPattern(format)
|
||||
return LocalDate.parse(date, inputFormatter)
|
||||
.atStartOfDay(DefaultTimeZone)
|
||||
.toInstant()
|
||||
}
|
||||
|
||||
fun parse(date: String, format: String, locale: Locale): Instant? {
|
||||
val inputFormatter = DateTimeFormatter.ofPattern(format, locale)
|
||||
return LocalDate.parse(date, inputFormatter)
|
||||
.atStartOfDay(DefaultTimeZone)
|
||||
.toInstant()
|
||||
}
|
||||
|
||||
fun tryParseRelative(date: String): Instant? {
|
||||
// One of the clown doesn't even display proper relative time, and instead
|
||||
// uses '1 Year+' formatting for every old torrents.
|
||||
val date = date.removeSuffix("+").trim()
|
||||
|
||||
val parsed = tryParseSpecialRelative(date)
|
||||
if (parsed != null) return parsed
|
||||
|
||||
val matched = RelativeTimePattern.find(date) ?: return null
|
||||
val value = matched.groupValues[1].toDoubleOrNull()?.toLong() ?: return null
|
||||
val unit = matched.groupValues[2].lowercase()
|
||||
val duration = when (unit) {
|
||||
"s", "sec", "secs", "second", "seconds" -> Duration.ofSeconds(value)
|
||||
"m", "min", "mins", "minute", "minutes" -> Duration.ofMinutes(value)
|
||||
"h", "hr", "hrs", "hour", "hours" -> Duration.ofHours(value)
|
||||
"d", "day", "days" -> Duration.ofDays(value)
|
||||
"w", "wk", "wks", "week", "weeks" -> Duration.ofDays(value * 7L)
|
||||
"mo", "mos", "month", "months" -> Duration.ofDays(value * 30L)
|
||||
"y", "yr", "yrs", "year", "years" -> Duration.ofDays(value * 365L)
|
||||
else -> return null
|
||||
}
|
||||
|
||||
return Instant.now().minus(duration)
|
||||
}
|
||||
|
||||
fun tryParseSpecialRelative(date: String): Instant? = when (date.lowercase()) {
|
||||
"today", "just now", "moments ago" -> Instant.now()
|
||||
"yesterday" -> LocalDate.now(DefaultTimeZone)
|
||||
.minusDays(1L)
|
||||
.atStartOfDay(DefaultTimeZone)
|
||||
.toInstant()
|
||||
|
||||
"last week" -> Instant.now().minus(Duration.ofDays(7))
|
||||
"last month" -> Instant.now().minus(Duration.ofDays(30))
|
||||
"last year" -> Instant.now().minus(Duration.ofDays(365))
|
||||
else -> null
|
||||
}
|
||||
|
||||
fun epochSecondToInstant(second: Long): Instant =
|
||||
Instant.ofEpochSecond(second)
|
||||
|
||||
fun parseYearMonthDay(date: String): Instant? =
|
||||
parse(date = date, format = YEAR_MONTH_DAY)
|
||||
|
||||
fun parseDayMonthYear(date: String): Instant? =
|
||||
parse(date = date, format = DAY_MONTH_YEAR)
|
||||
|
||||
fun parseMonthDayYear(date: String): Instant? =
|
||||
parse(date = date, format = MONTH_DAY_YEAR)
|
||||
|
||||
fun parseIso(date: String): Instant =
|
||||
OffsetDateTime.parse(date).toInstant()
|
||||
|
||||
fun parseRFC1123(date: String): Instant {
|
||||
val inputFormatter = DateTimeFormatter.RFC_1123_DATE_TIME
|
||||
return LocalDate.parse(date, inputFormatter).atStartOfDay(DefaultTimeZone).toInstant()
|
||||
}
|
||||
|
||||
fun getTodayDate(): Instant =
|
||||
LocalDate.now(DefaultTimeZone)
|
||||
.atStartOfDay(DefaultTimeZone)
|
||||
.toInstant()
|
||||
|
||||
fun getYesterdayDate(): Instant =
|
||||
LocalDate.now(DefaultTimeZone)
|
||||
.minusDays(1L)
|
||||
.atStartOfDay(DefaultTimeZone)
|
||||
.toInstant()
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
package com.prajwalch.torrentsearch.util
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
class TorrentSearchDateFormatter private constructor(
|
||||
formatter: DateFormatter,
|
||||
) : DateFormatter by formatter {
|
||||
companion object {
|
||||
/** Initializes appropriate date formatter for current Android version. */
|
||||
fun init(): TorrentSearchDateFormatter {
|
||||
val formatter = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
ModernDateFormatter()
|
||||
} else {
|
||||
LegacyDateFormatter()
|
||||
}
|
||||
|
||||
return TorrentSearchDateFormatter(formatter = formatter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Different date formats used to either format or parse date. */
|
||||
private object DateFormats {
|
||||
/** The date output format. */
|
||||
const val OUTPUT_FORMAT = "dd MMM yyyy"
|
||||
|
||||
/** ISO-8601 variant format without timestamp. */
|
||||
const val YEAR_MONTH_DAY_HYPHEN_SEPARATED = "yyyy-M-d"
|
||||
|
||||
const val DAY_MONTH_YEAR = "d/M/yyyy"
|
||||
|
||||
const val MONTH_DAY_YEAR = "M/d/yyyy"
|
||||
|
||||
/** Format used to parse ISO-8601 date in legacy date formatter. */
|
||||
const val LEGACY_ISO_8601 = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
|
||||
|
||||
/** Format used to parse RFC-1123 date in legacy date formatter. */
|
||||
const val LEGACY_RFC_1123 = "EEE, dd MMM yyyy HH:mm:ss z"
|
||||
}
|
||||
|
||||
/** The common interface for both legacy and modern date formatters. */
|
||||
private interface DateFormatter {
|
||||
fun formatEpochSecond(epochSecond: Long): String
|
||||
|
||||
fun formatYearMonthDay(date: String): String
|
||||
|
||||
fun formatDayMonthYear(date: String): String
|
||||
|
||||
fun formatMonthDayYear(date: String): String
|
||||
|
||||
/**
|
||||
* Parses ISO 8601 date (e.g., `2025-06-11T06:13:57+00:00`) into a more
|
||||
* readable format.
|
||||
*
|
||||
* @return Date formatted as [DateFormats.OUTPUT_FORMAT], e.g., `"11 Jun 2025"`.
|
||||
*/
|
||||
fun formatIsoDate(date: String): String
|
||||
|
||||
fun formatRFC1123Date(date: String): String
|
||||
|
||||
fun formatTodayDate(): String
|
||||
|
||||
fun formatYesterdayDate(): String
|
||||
}
|
||||
|
||||
/** The date formatter for Android version >= 8.0. */
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private class ModernDateFormatter : DateFormatter {
|
||||
private val outputFormatter = DateTimeFormatter.ofPattern(DateFormats.OUTPUT_FORMAT)
|
||||
|
||||
override fun formatEpochSecond(epochSecond: Long): String {
|
||||
val instant = Instant.ofEpochSecond(epochSecond)
|
||||
|
||||
val zoneId = ZoneId.systemDefault()
|
||||
val zonedDateTime = instant.atZone(zoneId)
|
||||
|
||||
return zonedDateTime.format(outputFormatter)
|
||||
}
|
||||
|
||||
override fun formatYearMonthDay(date: String): String {
|
||||
return formatDate(pattern = DateFormats.YEAR_MONTH_DAY_HYPHEN_SEPARATED, date = date)
|
||||
}
|
||||
|
||||
override fun formatDayMonthYear(date: String): String {
|
||||
return formatDate(pattern = DateFormats.DAY_MONTH_YEAR, date = date)
|
||||
}
|
||||
|
||||
override fun formatMonthDayYear(date: String): String {
|
||||
return formatDate(pattern = DateFormats.MONTH_DAY_YEAR, date = date)
|
||||
}
|
||||
|
||||
override fun formatIsoDate(date: String): String {
|
||||
val date = OffsetDateTime.parse(date)
|
||||
return outputFormatter.format(date)
|
||||
}
|
||||
|
||||
override fun formatRFC1123Date(date: String): String {
|
||||
val inputFormatter = DateTimeFormatter.RFC_1123_DATE_TIME
|
||||
val parsedDate = LocalDate.parse(date, inputFormatter) ?: return date
|
||||
|
||||
return parsedDate.format(outputFormatter)
|
||||
}
|
||||
|
||||
override fun formatTodayDate(): String {
|
||||
return LocalDate.now().format(outputFormatter)
|
||||
}
|
||||
|
||||
override fun formatYesterdayDate(): String {
|
||||
return LocalDate.now().minusDays(1L).format(outputFormatter)
|
||||
}
|
||||
|
||||
private fun formatDate(pattern: String, date: String): String {
|
||||
val inputFormatter = DateTimeFormatter.ofPattern(pattern)
|
||||
val localDate = LocalDate.parse(date, inputFormatter) ?: return date
|
||||
|
||||
return localDate.format(outputFormatter)
|
||||
}
|
||||
}
|
||||
|
||||
/** The date formatter for Android version < 8.0. */
|
||||
private class LegacyDateFormatter : DateFormatter {
|
||||
private val outputFormatter = SimpleDateFormat(DateFormats.OUTPUT_FORMAT, Locale.getDefault())
|
||||
|
||||
override fun formatEpochSecond(epochSecond: Long): String {
|
||||
return outputFormatter.format(epochSecond * 1000L)
|
||||
}
|
||||
|
||||
override fun formatYearMonthDay(date: String): String {
|
||||
return formatDate(pattern = DateFormats.YEAR_MONTH_DAY_HYPHEN_SEPARATED, date = date)
|
||||
}
|
||||
|
||||
override fun formatDayMonthYear(date: String): String {
|
||||
return formatDate(pattern = DateFormats.DAY_MONTH_YEAR, date = date)
|
||||
}
|
||||
|
||||
override fun formatMonthDayYear(date: String): String {
|
||||
return formatDate(pattern = DateFormats.MONTH_DAY_YEAR, date = date)
|
||||
}
|
||||
|
||||
override fun formatIsoDate(date: String): String {
|
||||
return formatDate(pattern = DateFormats.LEGACY_ISO_8601, date = date)
|
||||
}
|
||||
|
||||
override fun formatRFC1123Date(date: String): String {
|
||||
return formatDate(pattern = DateFormats.LEGACY_RFC_1123, date = date)
|
||||
}
|
||||
|
||||
override fun formatTodayDate(): String {
|
||||
val todayCalender = Calendar.getInstance()
|
||||
todayCalender.set(Calendar.HOUR_OF_DAY, 0)
|
||||
|
||||
val todayDate = todayCalender.time
|
||||
|
||||
return outputFormatter.format(todayDate)
|
||||
}
|
||||
|
||||
override fun formatYesterdayDate(): String {
|
||||
val todayCalendar = Calendar.getInstance()
|
||||
todayCalendar.add(Calendar.DATE, -1)
|
||||
|
||||
val yesterdayDate = todayCalendar.time
|
||||
|
||||
return outputFormatter.format(yesterdayDate)
|
||||
}
|
||||
|
||||
private fun formatDate(pattern: String, date: String): String {
|
||||
val inputFormatter = SimpleDateFormat(pattern, Locale.getDefault())
|
||||
val date = inputFormatter.parse(date) ?: return date
|
||||
|
||||
return outputFormatter.format(date)
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ composeMarkdown = "0.7.0"
|
||||
coreKtx = "1.18.0"
|
||||
coreSplashscreen = "1.2.0"
|
||||
datastorePreferences = "1.2.1"
|
||||
desugarJdkLibs = "2.1.5"
|
||||
espressoCore = "3.7.0"
|
||||
flexmark = "0.64.8"
|
||||
hilt = "2.59.2"
|
||||
@@ -48,6 +49,7 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
|
||||
coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" }
|
||||
coil-network-okhttp = { group = "io.coil-kt.coil3", name = "coil-network-okhttp", version.ref = "coil" }
|
||||
compose-markdown = { group = "com.github.jeziellago", name = "compose-markdown", version.ref = "composeMarkdown" }
|
||||
desugar-jdk-libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugarJdkLibs" }
|
||||
flexmark-html2md-converter = { group = "com.vladsch.flexmark", name = "flexmark-html2md-converter", version.ref = "flexmark" }
|
||||
flexmark-util = { group = "com.vladsch.flexmark", name = "flexmark-util", version.ref = "flexmark" }
|
||||
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
|
||||
|
||||
Reference in New Issue
Block a user