diff --git a/app/src/main/java/io/legado/app/constant/BookType.kt b/app/src/main/java/io/legado/app/constant/BookType.kt index 686e0623b..4a8be5d26 100644 --- a/app/src/main/java/io/legado/app/constant/BookType.kt +++ b/app/src/main/java/io/legado/app/constant/BookType.kt @@ -1,7 +1,7 @@ package io.legado.app.constant object BookType { - const val default = 0 - const val audio = 1 + const val default = 0 // 0 文本 + const val audio = 1 // 1 音频 const val local = "loc_book" } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt b/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt index 273dcb20d..df0bf605c 100644 --- a/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt @@ -10,6 +10,9 @@ interface RssSourceDao { @Query("select * from rssSources where sourceUrl = :key") fun getByKey(key: String): RssSource? + @Query("select * from rssSources where sourceUrl in (:sourceUrls)") + fun getRssSources(vararg sourceUrls: String):List + @get:Query("SELECT * FROM rssSources") val all: List diff --git a/app/src/main/java/io/legado/app/data/entities/BookSource.kt b/app/src/main/java/io/legado/app/data/entities/BookSource.kt index bb0c5fbbb..39b569565 100644 --- a/app/src/main/java/io/legado/app/data/entities/BookSource.kt +++ b/app/src/main/java/io/legado/app/data/entities/BookSource.kt @@ -8,6 +8,7 @@ import androidx.room.PrimaryKey import io.legado.app.App import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst.userAgent +import io.legado.app.constant.BookType import io.legado.app.data.entities.rule.* import io.legado.app.help.JsExtensions import io.legado.app.utils.ACache @@ -25,26 +26,26 @@ import javax.script.SimpleBindings indices = [(Index(value = ["bookSourceUrl"], unique = false))] ) data class BookSource( - var bookSourceName: String = "", // 名称 - var bookSourceGroup: String? = null, // 分组 + var bookSourceName: String = "", // 名称 + var bookSourceGroup: String? = null, // 分组 @PrimaryKey - var bookSourceUrl: String = "", // 地址,包括 http/https - var bookSourceType: Int = 0, // 类型,0 文本,1 音频 - var bookUrlPattern: String? = null, //详情页url正则 - var customOrder: Int = 0, // 手动排序编号 - var enabled: Boolean = true, // 是否启用 - var enabledExplore: Boolean = true, //启用发现 - var header: String? = null, - var loginUrl: String? = null, // 登录地址 - var lastUpdateTime: Long = 0, // 最后更新时间,用于排序 - var weight: Int = 0, // 智能排序的权重 - var exploreUrl: String? = null, //发现url - var ruleExplore: String? = null, // 发现规则 - var searchUrl: String? = null, //搜索url - var ruleSearch: String? = null, // 搜索规则 - var ruleBookInfo: String? = null, // 书籍信息页规则 - var ruleToc: String? = null, // 目录页规则 - var ruleContent: String? = null // 正文页规则 + var bookSourceUrl: String = "", // 地址,包括 http/https + var bookSourceType: Int = BookType.default, // 类型,0 文本,1 音频 + var bookUrlPattern: String? = null, // 详情页url正则 + var customOrder: Int = 0, // 手动排序编号 + var enabled: Boolean = true, // 是否启用 + var enabledExplore: Boolean = true, // 启用发现 + var header: String? = null, // 请求头 + var loginUrl: String? = null, // 登录地址 + var lastUpdateTime: Long = 0, // 最后更新时间,用于排序 + var weight: Int = 0, // 智能排序的权重 + var exploreUrl: String? = null, // 发现url + var ruleExplore: String? = null, // 发现规则 + var searchUrl: String? = null, // 搜索url + var ruleSearch: String? = null, // 搜索规则 + var ruleBookInfo: String? = null, // 书籍信息页规则 + var ruleToc: String? = null, // 目录页规则 + var ruleContent: String? = null // 正文页规则 ) : Parcelable, JsExtensions { @Ignore @IgnoredOnParcel diff --git a/app/src/main/java/io/legado/app/help/storage/Backup.kt b/app/src/main/java/io/legado/app/help/storage/Backup.kt index b7e473880..2f0b040a4 100644 --- a/app/src/main/java/io/legado/app/help/storage/Backup.kt +++ b/app/src/main/java/io/legado/app/help/storage/Backup.kt @@ -23,6 +23,10 @@ object Backup { defaultPath + File.separator + "legadoBackUp" } + val exportPath by lazy { + legadoPath + File.separator + "Export" + } + fun backup() { doAsync { val path = legadoPath diff --git a/app/src/main/java/io/legado/app/help/storage/OldRule.kt b/app/src/main/java/io/legado/app/help/storage/OldRule.kt index 715b07159..1c180838c 100644 --- a/app/src/main/java/io/legado/app/help/storage/OldRule.kt +++ b/app/src/main/java/io/legado/app/help/storage/OldRule.kt @@ -118,7 +118,7 @@ object OldRule { } if (newRule.contains("|") && !newRule.contains("||")) { if (newRule.contains("##")) { - var list = newRule.split("##") + val list = newRule.split("##") if (list[0].contains("|")) { newRule = list[0].replace("|", "||") for (i in 1 until list.size) { diff --git a/app/src/main/java/io/legado/app/model/Debug.kt b/app/src/main/java/io/legado/app/model/Debug.kt index 5351b19e4..fee62972e 100644 --- a/app/src/main/java/io/legado/app/model/Debug.kt +++ b/app/src/main/java/io/legado/app/model/Debug.kt @@ -53,18 +53,26 @@ object Debug { fun startDebug(rssSource: RssSource) { cancelDebug() debugSource = rssSource.sourceUrl - log(debugSource, "︾开始解析RSS") + log(debugSource, "︾开始解析") Rss.getArticles(rssSource, null) .onSuccess { if (it == null) { log(debugSource, "︽解析失败", state = -1) + } else if(it.articles.isEmpty()) { + log(debugSource, "⇒列表页解析成功,为空") + log(debugSource, "︽解析完成", state = 1000) } else { val ruleContent = rssSource.ruleContent - if (it.articles.isNotEmpty() && it.articles[0].description.isNullOrEmpty() && !ruleContent.isNullOrEmpty()) { - log(debugSource, "︽解析完成") + if (!rssSource.ruleArticles.isNullOrBlank() && rssSource.ruleDescription.isNullOrBlank()) { + log(debugSource, "︽列表页解析完成") log(debugSource, showTime = false) - rssContentDebug(it.articles[0], ruleContent) + if (ruleContent.isNullOrEmpty()) { + log(debugSource, "⇒内容规则为空,默认获取整个网页", state = 1000) + } else { + rssContentDebug(it.articles[0], ruleContent) + } } else { + log(debugSource, "⇒存在描述规则,不解析内容页") log(debugSource, "︽解析完成", state = 1000) } } @@ -75,11 +83,11 @@ object Debug { } private fun rssContentDebug(rssArticle: RssArticle, ruleContent: String) { - log(debugSource, "︾开始解析内容") + log(debugSource, "︾开始解析内容页") Rss.getContent(rssArticle, ruleContent) .onSuccess { log(debugSource, it) - log(debugSource, "︽解析完成", state = 1000) + log(debugSource, "︽内容页解析完成", state = 1000) } .onError { log(debugSource, it.localizedMessage, state = -1) diff --git a/app/src/main/java/io/legado/app/model/WebBook.kt b/app/src/main/java/io/legado/app/model/WebBook.kt index 1d375498f..e28c09519 100644 --- a/app/src/main/java/io/legado/app/model/WebBook.kt +++ b/app/src/main/java/io/legado/app/model/WebBook.kt @@ -138,7 +138,7 @@ class WebBook(val bookSource: BookSource) { ): Coroutine { return Coroutine.async(scope, context) { if (bookSource.getContentRule().content.isNullOrEmpty()) { - Debug.log(sourceUrl, "内容规则为空,直接返回目录Url:${bookChapter.url}") + Debug.log(sourceUrl, "⇒正文规则为空,使用章节链接:${bookChapter.url}") return@async bookChapter.url } val body = if (bookChapter.url == book.bookUrl && !book.tocHtml.isNullOrEmpty()) { diff --git a/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt b/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt index ed8cbf4bf..a9c193545 100644 --- a/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt +++ b/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt @@ -25,7 +25,7 @@ object RssParserByRule { Debug.log(sourceUrl, "≡获取成功:$sourceUrl") var ruleArticles = rssSource.ruleArticles if (ruleArticles.isNullOrBlank()) { - Debug.log(sourceUrl, "列表规则为空, 使用默认规则解析") + Debug.log(sourceUrl, "⇒列表规则为空, 使用默认规则解析") return RssParser.parseXML(body, sourceUrl) } else { val articleList = mutableListOf() @@ -40,12 +40,12 @@ object RssParserByRule { val collections = analyzeRule.getElements(ruleArticles) Debug.log(sourceUrl, "└列表大小:${collections.size}") if (!rssSource.ruleNextPage.isNullOrEmpty()) { - Debug.log(sourceUrl, "┌获取下一页Url") + Debug.log(sourceUrl, "┌获取下一页链接") nextUrl = analyzeRule.getString(rssSource.ruleNextPage) if (nextUrl.isNotEmpty()) { nextUrl = NetworkUtils.getAbsoluteURL(sourceUrl, nextUrl) } - Debug.log(sourceUrl, "└获取下一页Url:$nextUrl") + Debug.log(sourceUrl, "└$nextUrl") } val ruleTitle = analyzeRule.splitSourceRule(rssSource.ruleTitle) val rulePubDate = analyzeRule.splitSourceRule(rssSource.rulePubDate) @@ -88,8 +88,13 @@ object RssParserByRule { rssArticle.pubDate = analyzeRule.getString(rulePubDate) Debug.log(sourceUrl, "└${rssArticle.pubDate}", log) Debug.log(sourceUrl, "┌获取描述", log) - rssArticle.description = analyzeRule.getString(ruleDescription) - Debug.log(sourceUrl, "└${rssArticle.description}", log) + if (ruleDescription.isNullOrEmpty()) { + rssArticle.description = null + Debug.log(sourceUrl, "└描述规则为空,将会解析内容页", log) + } else { + rssArticle.description = analyzeRule.getString(ruleDescription) + Debug.log(sourceUrl, "└${rssArticle.description}", log) + } Debug.log(sourceUrl, "┌获取图片url", log) rssArticle.image = analyzeRule.getString(ruleImage, true) Debug.log(sourceUrl, "└${rssArticle.image}", log) diff --git a/app/src/main/java/io/legado/app/model/webbook/BookChapterList.kt b/app/src/main/java/io/legado/app/model/webbook/BookChapterList.kt index 91456e0c5..c8fd7c9f8 100644 --- a/app/src/main/java/io/legado/app/model/webbook/BookChapterList.kt +++ b/app/src/main/java/io/legado/app/model/webbook/BookChapterList.kt @@ -167,6 +167,8 @@ object BookChapterList { Debug.log(bookSource.bookSourceUrl, "└${chapterList[0].title}", log) Debug.log(bookSource.bookSourceUrl, "┌获取首章链接", log) Debug.log(bookSource.bookSourceUrl, "└${chapterList[0].url}", log) + Debug.log(bookSource.bookSourceUrl, "┌获取首章信息", log) + Debug.log(bookSource.bookSourceUrl, "└${chapterList[0].tag}", log) } return ChapterData(chapterList, nextUrlList) } diff --git a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt index cefa60af7..fa6ca7ea4 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt @@ -4,7 +4,9 @@ import android.app.Activity import android.content.ClipData import android.content.ClipboardManager import android.content.Context +import android.content.Intent import android.graphics.Rect +import android.net.Uri import android.os.Bundle import android.view.Gravity import android.view.Menu @@ -85,6 +87,27 @@ class BookSourceEditActivity : } } R.id.menu_paste_source -> viewModel.pasteSource { upRecyclerView(it) } + R.id.menu_share_str -> { + GSON.toJson(getSource())?.let { sourceStr -> + try { + val textIntent = Intent(Intent.ACTION_SEND) + textIntent.setType("text/plain") + textIntent.putExtra(Intent.EXTRA_TEXT, sourceStr) + startActivity(Intent.createChooser(textIntent, "Source Share")) + } catch (e: Exception) { + toast(R.string.can_not_share) + } + } + } + R.id.menu_rule_summary -> { + try { + val intent = Intent(Intent.ACTION_VIEW) + intent.data = Uri.parse(getString(R.string.source_rule_url)) + startActivity(intent) + } catch (e: Exception) { + toast(R.string.can_not_open) + } + } } return super.onCompatOptionsItemSelected(item) } diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt index 6bfd7b679..4623579b7 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt @@ -92,6 +92,7 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity R.id.menu_enable_explore -> viewModel.enableSelectExplore(adapter.getSelectionIds()) R.id.menu_disable_explore -> viewModel.disableSelectExplore(adapter.getSelectionIds()) R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelectionIds()) + R.id.menu_export_selection -> viewModel.exportSelection(adapter.getSelectionIds()) R.id.menu_check_source -> startService(Pair("data", adapter.getSelectionIds())) R.id.menu_import_source_onLine -> showImportDialog() diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt index 475b3ba52..99e32c556 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt @@ -7,12 +7,15 @@ import io.legado.app.App import io.legado.app.base.BaseViewModel import io.legado.app.data.api.IHttpGetApi import io.legado.app.data.entities.BookSource +import io.legado.app.help.FileHelp import io.legado.app.help.http.HttpHelper +import io.legado.app.help.storage.Backup import io.legado.app.help.storage.OldRule import io.legado.app.help.storage.Restore.jsonPath import io.legado.app.utils.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import org.jetbrains.anko.toast import java.io.File class BookSourceViewModel(application: Application) : BaseViewModel(application) { @@ -82,6 +85,22 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) } } + fun exportSelection(ids: LinkedHashSet) { + execute { + ids.map { + App.db.bookSourceDao().getBookSource(it) + }.let { + val json = GSON.toJson(it) + val file = FileHelp.getFile(Backup.exportPath + File.separator + "exportBookSource.json") + file.writeText(json) + } + }.onSuccess { + context.toast("成功导出至\n${Backup.exportPath}") + }.onError { + context.toast("导出失败\n${it.localizedMessage}") + } + } + fun addGroup(group: String) { execute { val sources = App.db.bookSourceDao().noGroup diff --git a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt index e335bfb1b..2d161b010 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt @@ -66,6 +66,7 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelectionIds()) R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelectionIds()) R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelectionIds()) + R.id.menu_export_selection -> viewModel.exportSelection(adapter.getSelectionIds()) } return super.onCompatOptionsItemSelected(item) } diff --git a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt index c820749ef..7b80d5475 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt @@ -5,7 +5,12 @@ import android.text.TextUtils import io.legado.app.App import io.legado.app.base.BaseViewModel import io.legado.app.data.entities.ReplaceRule +import io.legado.app.help.FileHelp +import io.legado.app.help.storage.Backup +import io.legado.app.utils.GSON import io.legado.app.utils.splitNotBlank +import org.jetbrains.anko.toast +import java.io.File class ReplaceRuleViewModel(application: Application) : BaseViewModel(application) { @@ -57,6 +62,22 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application } } + fun exportSelection(ids: LinkedHashSet) { + execute { + ids.map { + App.db.replaceRuleDao().findById(it) + }.let { + val json = GSON.toJson(it) + val file = FileHelp.getFile(Backup.exportPath + File.separator + "exportReplaceRule.json") + file.writeText(json) + } + }.onSuccess { + context.toast("成功导出至\n${Backup.exportPath}") + }.onError { + context.toast("导出失败\n${it.localizedMessage}") + } + } + fun addGroup(group: String) { execute { val sources = App.db.replaceRuleDao().noGroup diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt index 1d0028b93..08f03ac8f 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt @@ -81,6 +81,7 @@ class RssSourceActivity : VMBaseActivity(R.layout.activity_r R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelectionIds()) R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelectionIds()) R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelectionIds()) + R.id.menu_export_selection -> viewModel.exportSelection(adapter.getSelectionIds()) R.id.menu_import_source_local -> selectFile() R.id.menu_import_source_onLine -> showImportDialog() R.id.menu_import_source_qr -> startActivityForResult(qrRequestCode) diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceAdapter.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceAdapter.kt index 8dee3c485..708abb904 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceAdapter.kt @@ -38,7 +38,7 @@ class RssSourceAdapter(context: Context, val callBack: CallBack) : fun getSelectionIds(): LinkedHashSet { val selection = linkedSetOf() - getItems().map { + getItems().forEach { if (selectedIds.contains(it.sourceUrl)) { selection.add(it.sourceUrl) } diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt index 67d12e25a..0e458b3aa 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt @@ -7,9 +7,12 @@ import io.legado.app.App import io.legado.app.base.BaseViewModel import io.legado.app.data.api.IHttpGetApi import io.legado.app.data.entities.RssSource +import io.legado.app.help.FileHelp import io.legado.app.help.http.HttpHelper +import io.legado.app.help.storage.Backup import io.legado.app.help.storage.Restore.jsonPath import io.legado.app.utils.* +import org.jetbrains.anko.toast import java.io.File class RssSourceViewModel(application: Application) : BaseViewModel(application) { @@ -57,6 +60,19 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application) } } + fun exportSelection(ids: LinkedHashSet) { + execute { + App.db.rssSourceDao().getRssSources(*ids.toTypedArray()).let { + val json = GSON.toJson(it) + val file = FileHelp.getFile(Backup.exportPath + File.separator + "exportRssSource.json") + file.writeText(json) + } + }.onSuccess { + context.toast("成功导出至\n${Backup.exportPath}") + }.onError { + context.toast("导出失败\n${it.localizedMessage}") + } + } fun addGroup(group: String) { execute { diff --git a/app/src/main/res/menu/book_source.xml b/app/src/main/res/menu/book_source.xml index 3aacc98b3..5740683bd 100644 --- a/app/src/main/res/menu/book_source.xml +++ b/app/src/main/res/menu/book_source.xml @@ -47,6 +47,11 @@ android:title="@string/del_select" app:showAsAction="never" /> + + diff --git a/app/src/main/res/menu/replace_rule.xml b/app/src/main/res/menu/replace_rule.xml index 1efeaa4f7..001b2113e 100644 --- a/app/src/main/res/menu/replace_rule.xml +++ b/app/src/main/res/menu/replace_rule.xml @@ -37,6 +37,11 @@ android:title="@string/del_select" app:showAsAction="never" /> + + diff --git a/app/src/main/res/menu/rss_source.xml b/app/src/main/res/menu/rss_source.xml index f46fd512c..36bbef025 100644 --- a/app/src/main/res/menu/rss_source.xml +++ b/app/src/main/res/menu/rss_source.xml @@ -37,6 +37,11 @@ android:title="@string/del_select" app:showAsAction="never" /> + + diff --git a/app/src/main/res/menu/source_edit.xml b/app/src/main/res/menu/source_edit.xml index ef909ad3b..ec8739c11 100644 --- a/app/src/main/res/menu/source_edit.xml +++ b/app/src/main/res/menu/source_edit.xml @@ -39,6 +39,11 @@ android:title="@string/qr_share" app:showAsAction="never" /> + + 当前源没有配置登陆地址 - 书源名称(bookSourceName) - 书源URL(bookSourceUrl) - 书源分组(bookSourceGroup) + 源名称(sourceName) + 源URL(sourceUrl) + 源分组(sourceGroup) 登录URL(loginUrl) 搜索地址(url) 发现地址规则(url) @@ -389,8 +389,8 @@ 目录列表规则(chapterList) 章节名称规则(ChapterName) 章节URL规则(chapterUrl) - 是否VIP(isVip) - 更新时间(updateTime) + VIP标识(isVip) + 章节信息(ChapterInfo) 正文规则(content) 正文下一页URL规则(nextContentUrl) webJs @@ -501,6 +501,7 @@ web端口 当前端口 %s 二维码分享 + 字符串分享 wifi分享 请给于存储权限 减速 @@ -543,6 +544,7 @@ 禁用发现 启用所选 禁用所选 + 导出所选 TTS 输入你的WebDav授权密码 输入你的服务器地址