Fever API support is coming
This commit is contained in:
parent
d105735453
commit
f99fc8698a
|
@ -32,6 +32,9 @@ class App : Application() {
|
|||
@Inject
|
||||
lateinit var localRssRepository: LocalRssRepository
|
||||
|
||||
@Inject
|
||||
lateinit var feverRssRepository: FeverRssRepository
|
||||
|
||||
@Inject
|
||||
lateinit var opmlRepository: OpmlRepository
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import androidx.paging.PagingSource
|
|||
import androidx.work.*
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import me.ash.reader.DataStoreKeys
|
||||
import me.ash.reader.data.account.AccountDao
|
||||
import me.ash.reader.data.article.Article
|
||||
|
@ -42,6 +43,8 @@ abstract class AbstractRssRepository constructor(
|
|||
val isNotSyncing: Boolean = !isSyncing
|
||||
}
|
||||
|
||||
abstract fun getSyncState(): StateFlow<SyncState>
|
||||
|
||||
abstract suspend fun updateArticleInfo(article: Article)
|
||||
|
||||
abstract suspend fun subscribe(feed: Feed, articles: List<Article>)
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
package me.ash.reader.data.repository
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.work.WorkManager
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import me.ash.reader.DataStoreKeys
|
||||
import me.ash.reader.data.account.AccountDao
|
||||
import me.ash.reader.data.article.Article
|
||||
import me.ash.reader.data.article.ArticleDao
|
||||
import me.ash.reader.data.feed.Feed
|
||||
import me.ash.reader.data.feed.FeedDao
|
||||
import me.ash.reader.data.group.Group
|
||||
import me.ash.reader.data.group.GroupDao
|
||||
import me.ash.reader.data.source.FeverApiDataSource
|
||||
import me.ash.reader.data.source.RssNetworkDataSource
|
||||
import me.ash.reader.dataStore
|
||||
import me.ash.reader.get
|
||||
import net.dankito.readability4j.extended.Readability4JExtended
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class FeverRssRepository @Inject constructor(
|
||||
@ApplicationContext
|
||||
private val context: Context,
|
||||
private val articleDao: ArticleDao,
|
||||
private val feedDao: FeedDao,
|
||||
private val groupDao: GroupDao,
|
||||
private val rssHelper: RssHelper,
|
||||
private val feverApiDataSource: FeverApiDataSource,
|
||||
rssNetworkDataSource: RssNetworkDataSource,
|
||||
accountDao: AccountDao,
|
||||
workManager: WorkManager,
|
||||
) : AbstractRssRepository(
|
||||
context, accountDao, articleDao, groupDao,
|
||||
feedDao, rssNetworkDataSource, workManager,
|
||||
) {
|
||||
private val mutex = Mutex()
|
||||
private val syncState = MutableStateFlow(SyncState())
|
||||
|
||||
override fun getSyncState() = syncState
|
||||
|
||||
override suspend fun updateArticleInfo(article: Article) {
|
||||
articleDao.update(article)
|
||||
}
|
||||
|
||||
override suspend fun subscribe(feed: Feed, articles: List<Article>) {
|
||||
feedDao.insert(feed)
|
||||
articleDao.insertList(articles.map {
|
||||
it.copy(feedId = feed.id)
|
||||
})
|
||||
}
|
||||
|
||||
override suspend fun sync(
|
||||
context: Context,
|
||||
accountDao: AccountDao,
|
||||
articleDao: ArticleDao,
|
||||
feedDao: FeedDao,
|
||||
rssNetworkDataSource: RssNetworkDataSource
|
||||
) {
|
||||
mutex.withLock {
|
||||
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId)
|
||||
?: return
|
||||
|
||||
syncState.update {
|
||||
it.copy(
|
||||
feedCount = 1,
|
||||
syncedCount = 1,
|
||||
currentFeedName = "Fever"
|
||||
)
|
||||
}
|
||||
|
||||
if (feedDao.queryAll(accountId).isNullOrEmpty()) {
|
||||
// Temporary add feeds
|
||||
val feverFeeds = feverApiDataSource.feeds().execute().body()!!.feeds
|
||||
val feverGroupsBody = feverApiDataSource.groups().execute().body()!!
|
||||
Log.i("RLog", "Fever groups: $feverGroupsBody")
|
||||
feverGroupsBody.groups.forEach {
|
||||
groupDao.insert(
|
||||
Group(
|
||||
id = it.id.toString(),
|
||||
name = it.title,
|
||||
accountId = accountId,
|
||||
)
|
||||
)
|
||||
}
|
||||
val feverFeedsGroupsMap = mutableMapOf<Int, Int>()
|
||||
feverGroupsBody.feeds_groups.forEach { item ->
|
||||
item.feed_ids
|
||||
.split(",")
|
||||
.map { it.toInt() }
|
||||
.forEach { id ->
|
||||
feverFeedsGroupsMap[id] = item.group_id
|
||||
}
|
||||
}
|
||||
val feeds = feverFeeds.map {
|
||||
Feed(
|
||||
id = it.id.toString(),
|
||||
name = it.title,
|
||||
url = it.url,
|
||||
groupId = feverFeedsGroupsMap[it.id].toString(),
|
||||
accountId = accountId
|
||||
)
|
||||
}
|
||||
feedDao.insertList(feeds)
|
||||
}
|
||||
|
||||
// Add articles
|
||||
val articles = mutableListOf<Article>()
|
||||
feverApiDataSource.itemsBySince(since = 1647444325925621L)
|
||||
.execute().body()!!.items
|
||||
.forEach {
|
||||
articles.add(
|
||||
Article(
|
||||
id = it.id,
|
||||
date = Date(it.created_on_time * 1000),
|
||||
title = it.title,
|
||||
author = it.author,
|
||||
rawDescription = it.html,
|
||||
shortDescription = (
|
||||
Readability4JExtended("", it.html)
|
||||
.parse().textContent ?: ""
|
||||
).take(100).trim(),
|
||||
link = it.url,
|
||||
accountId = accountId,
|
||||
feedId = it.feed_id.toString(),
|
||||
isUnread = it.is_read == 0,
|
||||
isStarred = it.is_saved == 1,
|
||||
)
|
||||
)
|
||||
}
|
||||
articleDao.insertList(articles)
|
||||
|
||||
// Complete sync
|
||||
accountDao.update(accountDao.queryById(accountId)!!.apply {
|
||||
updateAt = Date()
|
||||
})
|
||||
syncState.update {
|
||||
it.copy(
|
||||
feedCount = 0,
|
||||
syncedCount = 0,
|
||||
currentFeedName = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,8 +41,10 @@ class LocalRssRepository @Inject constructor(
|
|||
context, accountDao, articleDao, groupDao,
|
||||
feedDao, rssNetworkDataSource, workManager,
|
||||
) {
|
||||
val syncState = MutableStateFlow(SyncState())
|
||||
private val mutex = Mutex()
|
||||
private val syncState = MutableStateFlow(SyncState())
|
||||
|
||||
override fun getSyncState() = syncState
|
||||
|
||||
override suspend fun updateArticleInfo(article: Article) {
|
||||
articleDao.update(article)
|
||||
|
|
|
@ -46,10 +46,7 @@ class RssHelper @Inject constructor(
|
|||
author = it.author,
|
||||
rawDescription = it.description.toString(),
|
||||
shortDescription = (Readability4JExtended("", it.description.toString())
|
||||
.parse().textContent ?: "").trim().run {
|
||||
if (this.length > 100) this.substring(0, 100)
|
||||
else this
|
||||
},
|
||||
.parse().textContent ?: "").take(100).trim(),
|
||||
link = it.link ?: "",
|
||||
)
|
||||
)
|
||||
|
@ -112,10 +109,7 @@ class RssHelper @Inject constructor(
|
|||
author = it.author,
|
||||
rawDescription = it.description.toString(),
|
||||
shortDescription = (Readability4JExtended("", it.description.toString())
|
||||
.parse().textContent ?: "").trim().run {
|
||||
if (this.length > 100) this.substring(0, 100)
|
||||
else this
|
||||
},
|
||||
.parse().textContent ?: "").take(100).trim(),
|
||||
link = it.link ?: "",
|
||||
)
|
||||
)
|
||||
|
|
|
@ -12,13 +12,13 @@ class RssRepository @Inject constructor(
|
|||
@ApplicationContext
|
||||
private val context: Context,
|
||||
private val localRssRepository: LocalRssRepository,
|
||||
// private val feverRssRepository: FeverRssRepository,
|
||||
private val feverRssRepository: FeverRssRepository,
|
||||
// private val googleReaderRssRepository: GoogleReaderRssRepository,
|
||||
) {
|
||||
fun get() = when (getAccountType()) {
|
||||
// Account.Type.LOCAL -> localRssRepository
|
||||
Account.Type.LOCAL -> localRssRepository
|
||||
// Account.Type.FEVER -> feverRssRepository
|
||||
// Account.Type.LOCAL -> feverRssRepository
|
||||
Account.Type.FEVER -> feverRssRepository
|
||||
// Account.Type.GOOGLE_READER -> googleReaderRssRepository
|
||||
else -> throw IllegalStateException("Unknown account type: ${getAccountType()}")
|
||||
}
|
||||
|
|
|
@ -10,15 +10,38 @@ import retrofit2.converter.gson.GsonConverterFactory
|
|||
import retrofit2.http.Multipart
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Part
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface FeverApiDataSource {
|
||||
@Multipart
|
||||
@POST("fever.php?api&groups")
|
||||
@POST("fever.php/?api=&feeds=")
|
||||
fun feeds(@Part("api_key") apiKey: RequestBody? = "1352b707f828a6f502db3768fa8d7151".toRequestBody()): Call<FeverApiDto.Feed>
|
||||
|
||||
@Multipart
|
||||
@POST("fever.php/?api=&groups=")
|
||||
fun groups(@Part("api_key") apiKey: RequestBody? = "1352b707f828a6f502db3768fa8d7151".toRequestBody()): Call<FeverApiDto.Groups>
|
||||
|
||||
@Multipart
|
||||
@POST("fever.php?api&feeds")
|
||||
fun feeds(@Part("api_key") apiKey: RequestBody?="1352b707f828a6f502db3768fa8d7151".toRequestBody()): Call<FeverApiDto.Feed>
|
||||
@POST("fever.php/?api=&items=")
|
||||
fun itemsBySince(
|
||||
@Query("since_id") since: Long,
|
||||
@Part("api_key") apiKey: RequestBody? = "1352b707f828a6f502db3768fa8d7151".toRequestBody()
|
||||
): Call<FeverApiDto.Items>
|
||||
|
||||
@Multipart
|
||||
@POST("fever.php/?api=&unread_item_ids=")
|
||||
fun itemsByUnread(@Part("api_key") apiKey: RequestBody? = "1352b707f828a6f502db3768fa8d7151".toRequestBody()): Call<FeverApiDto.ItemsByUnread>
|
||||
|
||||
@Multipart
|
||||
@POST("fever.php/?api=&saved_item_ids=")
|
||||
fun itemsByStarred(@Part("api_key") apiKey: RequestBody? = "1352b707f828a6f502db3768fa8d7151".toRequestBody()): Call<FeverApiDto.ItemsByStarred>
|
||||
|
||||
@Multipart
|
||||
@POST("fever.php/?api=&items=")
|
||||
fun itemsByIds(
|
||||
@Query("with_ids") ids: String,
|
||||
@Part("api_key") apiKey: RequestBody? = "1352b707f828a6f502db3768fa8d7151".toRequestBody()
|
||||
): Call<FeverApiDto.Items>
|
||||
|
||||
companion object {
|
||||
private var instance: FeverApiDataSource? = null
|
||||
|
|
|
@ -1,13 +1,78 @@
|
|||
package me.ash.reader.data.source
|
||||
|
||||
object FeverApiDto {
|
||||
// &groups
|
||||
data class Groups(
|
||||
val apiVersion: Int,
|
||||
|
||||
/**
|
||||
* @link fever.php/?api=&feeds=
|
||||
* @sample
|
||||
* {
|
||||
* "api_version": 3,
|
||||
* "auth": 1,
|
||||
* "last_refreshed_on_time": 1647530101,
|
||||
* "feeds": [
|
||||
* {
|
||||
* "id": 2,
|
||||
* "favicon_id": 2,
|
||||
* "title": "Ash's Knowledge Base",
|
||||
* "url": "https://www.ashinch.com/feed",
|
||||
* "site_url": "http://ashinch.com/",
|
||||
* "is_spark": 0,
|
||||
* "last_updated_on_time": 1647530101
|
||||
* }
|
||||
* ],
|
||||
* "feeds_groups": [
|
||||
* {
|
||||
* "group_id": 2,
|
||||
* "feed_ids": "2,3,4"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
data class Feed(
|
||||
val api_version: Int,
|
||||
val auth: Int,
|
||||
val lastRefreshedOnTime: Long,
|
||||
val last_refreshed_on_time: Long,
|
||||
val feeds: List<FeedItem>,
|
||||
val feeds_groups: List<FeedsGroupsItem>,
|
||||
)
|
||||
|
||||
data class FeedItem(
|
||||
val id: Int,
|
||||
val favicon_id: Int,
|
||||
val title: String,
|
||||
val url: String,
|
||||
val site_url: String,
|
||||
val is_spark: Int,
|
||||
val last_refreshed_on_time: Long,
|
||||
)
|
||||
|
||||
/**
|
||||
* @link fever.php/?api=&groups=
|
||||
* @sample
|
||||
* {
|
||||
* "api_version": 3,
|
||||
* "auth": 1,
|
||||
* "last_refreshed_on_time": 1647534602,
|
||||
* "groups": [
|
||||
* {
|
||||
* "id": 1,
|
||||
* "title": "未分类"
|
||||
* }
|
||||
* ],
|
||||
* "feeds_groups": [
|
||||
* {
|
||||
* "group_id": 2,
|
||||
* "feed_ids": "2,3,4"
|
||||
* },
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
data class Groups(
|
||||
val api_version: Int,
|
||||
val auth: Int,
|
||||
val last_refreshed_on_time: Long,
|
||||
val groups: List<GroupItem>,
|
||||
val feedsGroups: List<FeedsGroupsItem>,
|
||||
val feeds_groups: List<FeedsGroupsItem>,
|
||||
)
|
||||
|
||||
data class GroupItem(
|
||||
|
@ -16,39 +81,85 @@ object FeverApiDto {
|
|||
)
|
||||
|
||||
data class FeedsGroupsItem(
|
||||
val groupId: Int,
|
||||
val feedsIds: String,
|
||||
val group_id: Int,
|
||||
val feed_ids: String,
|
||||
)
|
||||
|
||||
// &feeds
|
||||
data class Feed(
|
||||
val apiVersion: Int,
|
||||
/**
|
||||
* @link fever.php/?api=&items=&with_ids={ids}
|
||||
* @link fever.php/?api=&items=&since_id={since}
|
||||
* @sample
|
||||
* {
|
||||
* "api_version": 3,
|
||||
* "auth": 1,
|
||||
* "last_refreshed_on_time": 1647534602,
|
||||
* "total_items": 853,
|
||||
* "items": [
|
||||
* {
|
||||
* "id": "1647445533955157",
|
||||
* "feed_id": 37,
|
||||
* "title": "智能音箱自己把自己黑了:随机购物拨号,自主开灯关门,平均成功率达88%",
|
||||
* "author": "博雯",
|
||||
* "html": "<blockquote>\n<p data-track=\"48\">博雯 发自 凹非寺</p>\n<p d...",
|
||||
* "url": "https://www.qbitai.com/2022/03/33402.html",
|
||||
* "is_saved": 0,
|
||||
* "is_read": 0,
|
||||
* "created_on_time": 1647442680
|
||||
* }
|
||||
* ]
|
||||
* {
|
||||
*/
|
||||
data class Items(
|
||||
val api_version: Int,
|
||||
val auth: Int,
|
||||
val lastRefreshedOnTime: Long,
|
||||
val feeds: List<FeedItem>,
|
||||
val feedsGroups: List<FeedsGroupsItem>,
|
||||
val last_refreshed_on_time: Long,
|
||||
val total_items: Int,
|
||||
val items: List<Item>,
|
||||
)
|
||||
|
||||
data class FeedItem(
|
||||
val id: Int,
|
||||
val favicon_id: Int,
|
||||
data class Item(
|
||||
val id: String,
|
||||
val feed_id: Int,
|
||||
val title: String,
|
||||
val author: String,
|
||||
val html: String,
|
||||
val url: String,
|
||||
val siteUrl: String,
|
||||
val isSpark: Int,
|
||||
val lastRefreshedOnTime: Long,
|
||||
val is_saved: Int,
|
||||
val is_read: Int,
|
||||
val created_on_time: Long,
|
||||
)
|
||||
|
||||
// &favicons
|
||||
data class Favicons(
|
||||
val apiVersion: Int,
|
||||
/**
|
||||
* @link fever.php/?api=&unread_item_ids=
|
||||
* @sample
|
||||
* {
|
||||
* "api_version": 3,
|
||||
* "auth": 1,
|
||||
* "last_refreshed_on_time": 1647530135,
|
||||
* "unread_item_ids": "1646660589277217,1646660589277218"
|
||||
* }
|
||||
*/
|
||||
data class ItemsByUnread(
|
||||
val api_version: Int,
|
||||
val auth: Int,
|
||||
val lastRefreshedOnTime: Long,
|
||||
val favicons: List<FaviconItem>,
|
||||
val last_refreshed_on_time: Long,
|
||||
val unread_item_ids: String,
|
||||
)
|
||||
|
||||
data class FaviconItem(
|
||||
val id: Int,
|
||||
val data: String,
|
||||
/**
|
||||
* @link fever.php/?api=&saved_item_ids=
|
||||
* @sample
|
||||
* {
|
||||
* "api_version": 3,
|
||||
* "auth": 1,
|
||||
* "last_refreshed_on_time": 1647534602,
|
||||
* "saved_item_ids": "1647441026698935,1646660589277218"
|
||||
* }
|
||||
*/
|
||||
data class ItemsByStarred(
|
||||
val api_version: Int,
|
||||
val auth: Int,
|
||||
val last_refreshed_on_time: Long,
|
||||
val saved_item_ids: String,
|
||||
)
|
||||
}
|
|
@ -31,7 +31,7 @@ class HomeViewModel @Inject constructor(
|
|||
private val _filterState = MutableStateFlow(FilterState())
|
||||
val filterState = _filterState.asStateFlow()
|
||||
|
||||
val syncState = rssRepository.get().syncState
|
||||
val syncState = rssRepository.get().getSyncState()
|
||||
|
||||
fun dispatch(action: HomeViewAction) {
|
||||
when (action) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user