第三方-3.0书源适配

pull/21/head
fengyuecanzhu 3 years ago
parent 055796fceb
commit 55da055e83
  1. 1
      app/build.gradle
  2. 1
      app/src/main/java/xyz/fycz/myreader/common/APPCONST.java
  3. 26
      app/src/main/java/xyz/fycz/myreader/entity/sourceedit/EditEntityUtil.kt
  4. 2
      app/src/main/java/xyz/fycz/myreader/entity/thirdsource/ThirdSourceUtil.java
  5. 37
      app/src/main/java/xyz/fycz/myreader/greendao/entity/Chapter.java
  6. 14
      app/src/main/java/xyz/fycz/myreader/greendao/entity/rule/BookListRule.kt
  7. 12
      app/src/main/java/xyz/fycz/myreader/greendao/entity/rule/ContentRule.java
  8. 22
      app/src/main/java/xyz/fycz/myreader/greendao/entity/rule/FindRule.java
  9. 2
      app/src/main/java/xyz/fycz/myreader/greendao/entity/rule/SearchRule.java
  10. 38
      app/src/main/java/xyz/fycz/myreader/greendao/entity/rule/TocRule.java
  11. 10
      app/src/main/java/xyz/fycz/myreader/greendao/service/ChapterService.java
  12. 4
      app/src/main/java/xyz/fycz/myreader/model/third2/content/BookList.java
  13. 217
      app/src/main/java/xyz/fycz/myreader/model/third3/Coroutine.kt
  14. 108
      app/src/main/java/xyz/fycz/myreader/model/third3/webBook/BookChapterList.kt
  15. 66
      app/src/main/java/xyz/fycz/myreader/model/third3/webBook/BookContent.kt
  16. 101
      app/src/main/java/xyz/fycz/myreader/model/third3/webBook/BookInfo.kt
  17. 180
      app/src/main/java/xyz/fycz/myreader/model/third3/webBook/BookList.kt
  18. 209
      app/src/main/java/xyz/fycz/myreader/model/third3/webBook/SearchModel.kt
  19. 129
      app/src/main/java/xyz/fycz/myreader/model/third3/webBook/WebBook.kt
  20. 6
      app/src/main/java/xyz/fycz/myreader/ui/activity/SourceEditActivity.java
  21. 90
      app/src/main/java/xyz/fycz/myreader/webapi/Third3SourceApi.kt
  22. 13
      app/src/main/java/xyz/fycz/myreader/webapi/ThirdSourceApi.java
  23. 5
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/ReadCrawlerUtil.java
  24. 10
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/source/Third3Crawler.kt
  25. 2
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/source/ThirdCrawler.java
  26. 18
      app/src/main/res/values/strings.xml

@ -154,6 +154,7 @@ dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
//anko
def anko_version = '0.10.8'

@ -52,6 +52,7 @@ public class APPCONST {
public static final String XPATH = "Xpath";
public static final String JSON_PATH = "JsonPath";
public static final String THIRD_SOURCE = "ThirdSource";
public static final String THIRD_3_SOURCE = "Third3Source";
public static final String DATA_KEY = "data_key";
public static final String FIND_CRAWLER = "findCrawler";

@ -2,7 +2,6 @@ package xyz.fycz.myreader.entity.sourceedit
import xyz.fycz.myreader.R
import xyz.fycz.myreader.greendao.entity.rule.*
import java.util.*
import kotlin.collections.ArrayList
/**
@ -85,7 +84,7 @@ object EditEntityUtil {
add(EditEntity("url", findRule?.url, R.string.r_find_url))
add(
EditEntity(
"bookList", findRule?.bookList, R.string.r_book_list,
"bookList", findRule?.list, R.string.r_book_list,
"对于Matcher解析器:此处填写书籍列表所在区间,仅支持普通函数;" +
"\n对于Xpath/JsonPath解析器:此处填写书籍列表规则,仅支持列表函数"
)
@ -154,6 +153,18 @@ object EditEntityUtil {
"对于Xpath/JsonPath解析器:此处填写章节URL规则"
)
)
add(
EditEntity(
"isVip", tocRule?.isVip, R.string.rule_is_vip,
""
)
)
add(
EditEntity(
"updateTime", tocRule?.updateTime, R.string.rule_update_time,
""
)
)
add(
EditEntity(
"tocUrlNext", tocRule?.tocUrlNext, R.string.rule_next_toc_url,
@ -180,6 +191,12 @@ object EditEntityUtil {
"填写后正文时将会不断地从下一页获取内容,直至下一页URL为空时停止,注意:千万不要获取恒存在的URL,否则将出现死循环甚至崩溃"
)
)
add(
EditEntity(
"replaceRegex", contentRule?.replaceRegex, R.string.rule_replace_regex,
""
)
)
}
return contentEntities
}
@ -231,7 +248,7 @@ object EditEntityUtil {
findEntities.forEach {
when (it.key) {
"url" -> findRule.url = it.value
"bookList" -> findRule.bookList = it.value
"bookList" -> findRule.list = it.value
"name" -> findRule.name = it.value
"author" -> findRule.author = it.value
"type" -> findRule.type = it.value
@ -277,6 +294,8 @@ object EditEntityUtil {
"chapterBaseUrl" -> tocRule.chapterBaseUrl = it.value
"chapterName" -> tocRule.chapterName = it.value
"chapterUrl" -> tocRule.chapterUrl = it.value
"isVip" -> tocRule.isVip = it.value
"updateTime" -> tocRule.updateTime = it.value
"tocUrlNext" -> tocRule.tocUrlNext = it.value
}
}
@ -290,6 +309,7 @@ object EditEntityUtil {
"content" -> contentRule.content = it.value
"contentBaseUrl" -> contentRule.contentBaseUrl = it.value
"contentUrlNext" -> contentRule.contentUrlNext = it.value
"replaceRegex" -> contentRule.replaceRegex = it.value
}
}
return contentRule

@ -67,7 +67,7 @@ public class ThirdSourceUtil {
FindRule findRule = new FindRule();
findRule.setUrl(bean.getRuleFindUrl());
findRule.setBookList(bean.getRuleFindList());
findRule.setList(bean.getRuleFindList());
findRule.setName(bean.getRuleFindName());
findRule.setAuthor(bean.getRuleFindAuthor());
findRule.setType(bean.getRuleFindKind());

@ -37,6 +37,9 @@ public class Chapter implements RuleDataInterface {
private int number;//章节序号
private String title;//章节标题
private String url;//章节链接(本地书籍为:字符编码)
private boolean isVip;//是否VIP
private boolean isPay;//是否已购买
private String updateTime;//更新时间
@Nullable
private String content;//章节正文
@ -49,14 +52,19 @@ public class Chapter implements RuleDataInterface {
@Transient
private Map<String, String> variableMap;
@Generated(hash = 1398484308)
@Generated(hash = 1109296579)
public Chapter(String id, String bookId, int number, String title, String url,
String content, long start, long end, String variable) {
boolean isVip, boolean isPay, String updateTime, String content,
long start, long end, String variable) {
this.id = id;
this.bookId = bookId;
this.number = number;
this.title = title;
this.url = url;
this.isVip = isVip;
this.isPay = isPay;
this.updateTime = updateTime;
this.content = content;
this.start = start;
this.end = end;
@ -67,6 +75,7 @@ public class Chapter implements RuleDataInterface {
public Chapter() {
}
public String getId() {
return this.id;
}
@ -169,4 +178,28 @@ public class Chapter implements RuleDataInterface {
public void setVariable(String variable) {
this.variable = variable;
}
public String getUpdateTime() {
return this.updateTime;
}
public void setUpdateTime(String updateTime) {
this.updateTime = updateTime;
}
public boolean getIsVip() {
return this.isVip;
}
public void setIsVip(boolean isVip) {
this.isVip = isVip;
}
public boolean getIsPay() {
return this.isPay;
}
public void setIsPay(boolean isPay) {
this.isPay = isPay;
}
}

@ -0,0 +1,14 @@
package xyz.fycz.myreader.greendao.entity.rule
interface BookListRule {
var list: String?
var name: String?
var author: String?
var desc: String?
var type: String?
var lastChapter: String?
var updateTime: String?
var infoUrl: String?
var imgUrl: String?
var wordCount: String?
}

@ -19,11 +19,13 @@ public class ContentRule implements Parcelable {
private String content;
private String contentBaseUrl;
private String contentUrlNext;
private String replaceRegex;
protected ContentRule(Parcel in) {
content = in.readString();
contentBaseUrl = in.readString();
contentUrlNext = in.readString();
replaceRegex = in.readString();
}
@Override
@ -31,6 +33,7 @@ public class ContentRule implements Parcelable {
dest.writeString(content);
dest.writeString(contentBaseUrl);
dest.writeString(contentUrlNext);
dest.writeString(replaceRegex);
}
@Override
@ -58,7 +61,8 @@ public class ContentRule implements Parcelable {
ContentRule that = (ContentRule) o;
return stringEquals(content, that.content) &&
stringEquals(contentBaseUrl, that.contentBaseUrl) &&
stringEquals(contentUrlNext, that.contentUrlNext);
stringEquals(contentUrlNext, that.contentUrlNext) &&
stringEquals(replaceRegex, that.replaceRegex);
}
@ -89,5 +93,11 @@ public class ContentRule implements Parcelable {
this.contentUrlNext = contentUrlNext;
}
public String getReplaceRegex() {
return replaceRegex;
}
public void setReplaceRegex(String replaceRegex) {
this.replaceRegex = replaceRegex;
}
}

@ -3,19 +3,15 @@ package xyz.fycz.myreader.greendao.entity.rule;
import android.os.Parcel;
import android.os.Parcelable;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import static xyz.fycz.myreader.util.utils.StringUtils.stringEquals;
/**
* @author fengyue
* @date 2021/2/10 8:57
*/
public class FindRule implements Parcelable {
public class FindRule implements Parcelable, BookListRule {
private String url;
private String bookList;
private String list;
private String name;
private String author;
private String type;
@ -33,7 +29,7 @@ public class FindRule implements Parcelable {
protected FindRule(Parcel in) {
url = in.readString();
bookList = in.readString();
list = in.readString();
name = in.readString();
author = in.readString();
type = in.readString();
@ -50,7 +46,7 @@ public class FindRule implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(url);
dest.writeString(bookList);
dest.writeString(list);
dest.writeString(name);
dest.writeString(author);
dest.writeString(type);
@ -89,12 +85,12 @@ public class FindRule implements Parcelable {
this.url = url;
}
public String getBookList() {
return bookList;
public String getList() {
return list;
}
public void setBookList(String bookList) {
this.bookList = bookList;
public void setList(String bookList) {
this.list = bookList;
}
public String getName() {
@ -192,7 +188,7 @@ public class FindRule implements Parcelable {
if (getClass() != o.getClass()) return false;
FindRule findRule = (FindRule) o;
return stringEquals(url, findRule.url) &&
stringEquals(bookList, findRule.bookList) &&
stringEquals(list, findRule.list) &&
stringEquals(name, findRule.name) &&
stringEquals(author, findRule.author) &&
stringEquals(type, findRule.type) &&

@ -17,7 +17,7 @@ import static xyz.fycz.myreader.util.utils.StringUtils.stringEquals;
* @author fengyue
* @date 2021/2/8 17:48
*/
public class SearchRule implements Parcelable {
public class SearchRule implements Parcelable, BookListRule {
private String searchUrl;
private String charset;
private String list;

@ -22,6 +22,9 @@ public class TocRule implements Parcelable {
private String chapterName;
private String chapterUrl;
private String tocUrlNext;
private String isVip;
private String isPay;
private String updateTime;
protected TocRule(Parcel in) {
chapterList = in.readString();
@ -29,6 +32,9 @@ public class TocRule implements Parcelable {
chapterName = in.readString();
chapterUrl = in.readString();
tocUrlNext = in.readString();
isVip = in.readString();
isPay = in.readString();
updateTime = in.readString();
}
@Override
@ -38,6 +44,9 @@ public class TocRule implements Parcelable {
dest.writeString(chapterName);
dest.writeString(chapterUrl);
dest.writeString(tocUrlNext);
dest.writeString(isVip);
dest.writeString(isPay);
dest.writeString(updateTime);
}
@Override
@ -67,7 +76,10 @@ public class TocRule implements Parcelable {
stringEquals(chapterBaseUrl, tocRule.chapterBaseUrl) &&
stringEquals(chapterName, tocRule.chapterName) &&
stringEquals(chapterUrl, tocRule.chapterUrl) &&
stringEquals(tocUrlNext, tocRule.tocUrlNext);
stringEquals(tocUrlNext, tocRule.tocUrlNext) &&
stringEquals(isVip, tocRule.isVip) &&
stringEquals(isPay, tocRule.isPay) &&
stringEquals(updateTime, tocRule.updateTime);
}
public String getChapterList() {
@ -110,6 +122,30 @@ public class TocRule implements Parcelable {
this.tocUrlNext = tocUrlNext;
}
public String getIsVip() {
return isVip;
}
public void setIsVip(String isVip) {
this.isVip = isVip;
}
public String getIsPay() {
return isPay;
}
public void setIsPay(String isPay) {
this.isPay = isPay;
}
public String getUpdateTime() {
return updateTime;
}
public void setUpdateTime(String updateTime) {
this.updateTime = updateTime;
}
public TocRule() {
}

@ -39,10 +39,12 @@ public class ChapterService extends BaseService {
chapter.setBookId(cursor.getString(1));
chapter.setNumber(cursor.getInt(2));
chapter.setTitle(cursor.getString(3));
chapter.setUrl(cursor.getString(4));
chapter.setContent(cursor.getString(5));
chapter.setStart(cursor.getInt(6));
chapter.setEnd(cursor.getInt(7));
chapter.setIsVip(cursor.getInt(4) != 0);
chapter.setIsPay(cursor.getInt(5) != 0);
chapter.setUpdateTime(cursor.getString(6));
chapter.setContent(cursor.getString(7));
chapter.setStart(cursor.getInt(8));
chapter.setEnd(cursor.getInt(9));
chapters.add(chapter);
}
} catch (Exception e) {

@ -147,9 +147,9 @@ public class BookList {
}
private void initRule() {
if (isFind && !TextUtils.isEmpty(bookSource.getFindRule().getBookList())) {
if (isFind && !TextUtils.isEmpty(bookSource.getFindRule().getList())) {
FindRule findRule = bookSource.getFindRule();
ruleList = findRule.getBookList();
ruleList = findRule.getList();
ruleName = findRule.getName();
ruleAuthor = findRule.getAuthor();
ruleKind = findRule.getType();

@ -0,0 +1,217 @@
package xyz.fycz.myreader.model.third3
import android.util.Log
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
@Suppress("unused")
class Coroutine<T>(
val scope: CoroutineScope,
context: CoroutineContext = Dispatchers.IO,
block: suspend CoroutineScope.() -> T
) {
companion object {
private val DEFAULT = MainScope()
fun <T> async(
scope: CoroutineScope = DEFAULT,
context: CoroutineContext = Dispatchers.IO,
block: suspend CoroutineScope.() -> T
): Coroutine<T> {
return Coroutine(scope, context, block)
}
}
private val job: Job
private var start: VoidCallback? = null
private var success: Callback<T>? = null
private var error: Callback<Throwable>? = null
private var finally: VoidCallback? = null
private var cancel: VoidCallback? = null
private var timeMillis: Long? = null
private var errorReturn: Result<T>? = null
val isCancelled: Boolean
get() = job.isCancelled
val isActive: Boolean
get() = job.isActive
val isCompleted: Boolean
get() = job.isCompleted
init {
this.job = executeInternal(context, block)
}
fun timeout(timeMillis: () -> Long): Coroutine<T> {
this.timeMillis = timeMillis()
return this@Coroutine
}
fun timeout(timeMillis: Long): Coroutine<T> {
this.timeMillis = timeMillis
return this@Coroutine
}
fun onErrorReturn(value: () -> T?): Coroutine<T> {
this.errorReturn = Result(value())
return this@Coroutine
}
fun onErrorReturn(value: T?): Coroutine<T> {
this.errorReturn = Result(value)
return this@Coroutine
}
fun onStart(
context: CoroutineContext? = null,
block: (suspend CoroutineScope.() -> Unit)
): Coroutine<T> {
this.start = VoidCallback(context, block)
return this@Coroutine
}
fun onSuccess(
context: CoroutineContext? = null,
block: suspend CoroutineScope.(T) -> Unit
): Coroutine<T> {
this.success = Callback(context, block)
return this@Coroutine
}
fun onError(
context: CoroutineContext? = null,
block: suspend CoroutineScope.(Throwable) -> Unit
): Coroutine<T> {
this.error = Callback(context, block)
return this@Coroutine
}
fun onFinally(
context: CoroutineContext? = null,
block: suspend CoroutineScope.() -> Unit
): Coroutine<T> {
this.finally = VoidCallback(context, block)
return this@Coroutine
}
fun onCancel(
context: CoroutineContext? = null,
block: suspend CoroutineScope.() -> Unit
): Coroutine<T> {
this.cancel = VoidCallback(context, block)
return this@Coroutine
}
//取消当前任务
fun cancel(cause: CancellationException? = null) {
job.cancel(cause)
cancel?.let {
MainScope().launch {
if (null == it.context) {
it.block.invoke(scope)
} else {
withContext(scope.coroutineContext.plus(it.context)) {
it.block.invoke(this)
}
}
}
}
}
fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle {
return job.invokeOnCompletion(handler)
}
private fun executeInternal(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): Job {
return scope.plus(Dispatchers.Main).launch {
try {
start?.let { dispatchVoidCallback(this, it) }
ensureActive()
val value = executeBlock(scope, context, timeMillis ?: 0L, block)
ensureActive()
success?.let { dispatchCallback(this, value, it) }
} catch (e: CancellationException) {
Log.d("Coroutine", "任务取消")
} catch (e: Throwable) {
e.printStackTrace()
Log.d("Coroutine", "" + e.localizedMessage)
val consume: Boolean = errorReturn?.value?.let { value ->
if (isActive) {
success?.let { dispatchCallback(this, value, it) }
}
true
} ?: false
if (!consume && isActive) {
error?.let { dispatchCallback(this, e, it) }
}
} finally {
if (isActive) {
finally?.let { dispatchVoidCallback(this, it) }
}
}
}
}
private suspend inline fun dispatchVoidCallback(scope: CoroutineScope, callback: VoidCallback) {
if (null == callback.context) {
callback.block.invoke(scope)
} else {
withContext(scope.coroutineContext.plus(callback.context)) {
callback.block.invoke(this)
}
}
}
private suspend inline fun <R> dispatchCallback(
scope: CoroutineScope,
value: R,
callback: Callback<R>
) {
if (!scope.isActive) return
if (null == callback.context) {
callback.block.invoke(scope, value)
} else {
withContext(scope.coroutineContext.plus(callback.context)) {
callback.block.invoke(this, value)
}
}
}
private suspend inline fun executeBlock(
scope: CoroutineScope,
context: CoroutineContext,
timeMillis: Long,
noinline block: suspend CoroutineScope.() -> T
): T {
return withContext(scope.coroutineContext.plus(context)) {
if (timeMillis > 0L) withTimeout(timeMillis) {
block()
} else {
block()
}
}
}
private data class Result<out T>(val value: T?)
private inner class VoidCallback(
val context: CoroutineContext?,
val block: suspend CoroutineScope.() -> Unit
)
private inner class Callback<VALUE>(
val context: CoroutineContext?,
val block: suspend CoroutineScope.(VALUE) -> Unit
)
}

@ -1,14 +1,7 @@
package xyz.fycz.myreader.model.third3.webBook
import android.text.TextUtils
import io.legado.app.R
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource
import xyz.fycz.myreader.model.third3.rule.TocRule
import io.legado.app.model.Debug
import io.legado.app.model.NoStackTraceException
import io.legado.app.model.TocEmptyException
import android.util.Log
import xyz.fycz.myreader.model.third3.analyzeRule.AnalyzeRule
import xyz.fycz.myreader.model.third3.analyzeRule.AnalyzeUrl
import kotlinx.coroutines.CoroutineScope
@ -16,7 +9,15 @@ import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.async
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.withContext
import splitties.init.appCtx
import xyz.fycz.myreader.R
import xyz.fycz.myreader.application.App
import xyz.fycz.myreader.greendao.entity.Book
import xyz.fycz.myreader.greendao.entity.Chapter
import xyz.fycz.myreader.greendao.entity.rule.BookSource
import xyz.fycz.myreader.greendao.entity.rule.TocRule
import xyz.fycz.myreader.model.third3.NoStackTraceException
import xyz.fycz.myreader.model.third3.TocEmptyException
/**
* 获取目录
@ -31,15 +32,15 @@ object BookChapterList {
book: Book,
redirectUrl: String,
baseUrl: String,
body: String?
): List<BookChapter> {
body: String?,
): List<Chapter> {
body ?: throw NoStackTraceException(
appCtx.getString(R.string.error_get_web_content, baseUrl)
App.getmContext().getString(R.string.error_get_web_content, baseUrl)
)
val chapterList = ArrayList<BookChapter>()
Debug.log(bookSource.bookSourceUrl, "≡获取成功:${baseUrl}")
Debug.log(bookSource.bookSourceUrl, body, state = 30)
val tocRule = bookSource.getTocRule()
val chapterList = ArrayList<Chapter>()
Log.d(bookSource.sourceUrl, "≡获取成功:${baseUrl}")
Log.d(bookSource.sourceUrl, body)
val tocRule = bookSource.tocRule
val nextUrlList = arrayListOf(baseUrl)
var reverse = false
var listRule = tocRule.chapterList ?: ""
@ -66,7 +67,7 @@ object BookChapterList {
mUrl = nextUrl,
source = bookSource,
ruleData = book,
headerMapF = bookSource.getHeaderMap()
//headerMapF = bookSource.getHeaderMap()
).getStrResponseAwait().body?.let { nextBody ->
chapterData = analyzeChapterList(
scope, book, nextUrl, nextUrl,
@ -76,10 +77,10 @@ object BookChapterList {
chapterList.addAll(chapterData.first)
}
}
Debug.log(bookSource.bookSourceUrl, "◇目录总页数:${nextUrlList.size}")
Log.d(bookSource.sourceUrl, "◇目录总页数:${nextUrlList.size}")
}
else -> {
Debug.log(bookSource.bookSourceUrl, "◇并发解析目录,总页数:${chapterData.second.size}")
Log.d(bookSource.sourceUrl, "◇并发解析目录,总页数:${chapterData.second.size}")
withContext(IO) {
val asyncArray = Array(chapterData.second.size) {
async(IO) {
@ -88,7 +89,7 @@ object BookChapterList {
mUrl = urlStr,
source = bookSource,
ruleData = book,
headerMapF = bookSource.getHeaderMap()
//headerMapF = bookSource.getHeaderMap()
)
val res = analyzeUrl.getStrResponseAwait()
analyzeChapterList(
@ -104,7 +105,7 @@ object BookChapterList {
}
}
if (chapterList.isEmpty()) {
throw TocEmptyException(appCtx.getString(R.string.chapter_list_empty))
throw TocEmptyException(App.getmContext().getString(R.string.chapter_list_empty))
}
//去重
if (!reverse) {
@ -112,22 +113,22 @@ object BookChapterList {
}
val lh = LinkedHashSet(chapterList)
val list = ArrayList(lh)
if (!book.getReverseToc()) {
/*if (!book.getReverseToc()) {
list.reverse()
}
Debug.log(book.origin, "◇目录总数:${list.size}")
}*/
Log.d(book.source, "◇目录总数:${list.size}")
list.forEachIndexed { index, bookChapter ->
bookChapter.index = index
bookChapter.number = index
}
book.latestChapterTitle = list.last().title
book.durChapterTitle =
list.getOrNull(book.durChapterIndex)?.title ?: book.latestChapterTitle
if (book.totalChapterNum < list.size) {
book.lastCheckCount = list.size - book.totalChapterNum
book.latestChapterTime = System.currentTimeMillis()
book.newestChapterTitle = list.last().title
book.historyChapterId =
list.getOrNull(book.histtoryChapterNum)?.title ?: book.newestChapterTitle
if (book.chapterTotalNum < list.size) {
book.noReadNum = list.size - book.chapterTotalNum
book.lastReadTime = System.currentTimeMillis()
}
book.lastCheckTime = System.currentTimeMillis()
book.totalChapterNum = list.size
book.lastReadTime = System.currentTimeMillis()
book.chapterTotalNum = list.size
return list
}
@ -142,20 +143,20 @@ object BookChapterList {
bookSource: BookSource,
getNextUrl: Boolean = true,
log: Boolean = false
): Pair<List<BookChapter>, List<String>> {
): Pair<List<Chapter>, List<String>> {
val analyzeRule = AnalyzeRule(book, bookSource)
analyzeRule.setContent(body).setBaseUrl(baseUrl)
analyzeRule.setRedirectUrl(redirectUrl)
//获取目录列表
val chapterList = arrayListOf<BookChapter>()
Debug.log(bookSource.bookSourceUrl, "┌获取目录列表", log)
val chapterList = arrayListOf<Chapter>()
if (log) Log.d(bookSource.sourceUrl, "┌获取目录列表")
val elements = analyzeRule.getElements(listRule)
Debug.log(bookSource.bookSourceUrl, "└列表大小:${elements.size}", log)
if (log) Log.d(bookSource.sourceUrl, "└列表大小:${elements.size}",)
//获取下一页链接
val nextUrlList = arrayListOf<String>()
val nextTocRule = tocRule.nextTocUrl
val nextTocRule = tocRule.tocUrlNext
if (getNextUrl && !nextTocRule.isNullOrEmpty()) {
Debug.log(bookSource.bookSourceUrl, "┌获取目录下一页列表", log)
if (log) Log.d(bookSource.sourceUrl, "┌获取目录下一页列表")
analyzeRule.getStringList(nextTocRule, isUrl = true)?.let {
for (item in it) {
if (item != baseUrl) {
@ -163,15 +164,11 @@ object BookChapterList {
}
}
}
Debug.log(
bookSource.bookSourceUrl,
"" + TextUtils.join("\n", nextUrlList),
log
)
if (log) Log.d(bookSource.sourceUrl, "" + TextUtils.join("\n", nextUrlList),)
}
scope.ensureActive()
if (elements.isNotEmpty()) {
Debug.log(bookSource.bookSourceUrl, "┌解析目录列表", log)
if (log) Log.d(bookSource.sourceUrl, "┌解析目录列表")
val nameRule = analyzeRule.splitSourceRule(tocRule.chapterName)
val urlRule = analyzeRule.splitSourceRule(tocRule.chapterUrl)
val vipRule = analyzeRule.splitSourceRule(tocRule.isVip)
@ -180,14 +177,15 @@ object BookChapterList {
elements.forEachIndexed { index, item ->
scope.ensureActive()
analyzeRule.setContent(item)
val bookChapter = BookChapter(bookUrl = book.bookUrl, baseUrl = baseUrl)
//val bookChapter = Chapter(bookUrl = book.bookUrl, baseUrl = baseUrl)
val bookChapter = Chapter()
analyzeRule.chapter = bookChapter
bookChapter.title = analyzeRule.getString(nameRule)
bookChapter.url = analyzeRule.getString(urlRule)
bookChapter.tag = analyzeRule.getString(upTimeRule)
bookChapter.updateTime = analyzeRule.getString(upTimeRule)
if (bookChapter.url.isEmpty()) {
bookChapter.url = baseUrl
Debug.log(bookSource.bookSourceUrl, "目录${index}未获取到url,使用baseUrl替代")
if (log) Log.d(bookSource.sourceUrl, "目录${index}未获取到url,使用baseUrl替代")
}
if (bookChapter.title.isNotEmpty()) {
val isVip = analyzeRule.getString(vipRule)
@ -201,13 +199,13 @@ object BookChapterList {
chapterList.add(bookChapter)
}
}
Debug.log(bookSource.bookSourceUrl, "└目录列表解析完成", log)
Debug.log(bookSource.bookSourceUrl, "┌获取首章名称", log)
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)
if (log) Log.d(bookSource.sourceUrl, "└目录列表解析完成")
if (log) Log.d(bookSource.sourceUrl, "┌获取首章名称")
if (log) Log.d(bookSource.sourceUrl, "${chapterList[0].title}")
if (log) Log.d(bookSource.sourceUrl, "┌获取首章链接")
if (log) Log.d(bookSource.sourceUrl, "${chapterList[0].url}")
if (log) Log.d(bookSource.sourceUrl, "┌获取首章信息")
if (log) Log.d(bookSource.sourceUrl, "${chapterList[0].updateTime}")
}
return Pair(chapterList, nextUrlList)
}

@ -1,24 +1,25 @@
package xyz.fycz.myreader.model.third3.webBook
import io.legado.app.R
import io.legado.app.data.appDb
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.rule.ContentRule
import io.legado.app.help.BookHelp
import io.legado.app.model.ContentEmptyException
import io.legado.app.model.Debug
import io.legado.app.model.NoStackTraceException
import android.util.Log
import xyz.fycz.myreader.model.third3.analyzeRule.AnalyzeRule
import xyz.fycz.myreader.model.third3.analyzeRule.AnalyzeUrl
import io.legado.app.utils.NetworkUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.async
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.withContext
import splitties.init.appCtx
import xyz.fycz.myreader.R
import xyz.fycz.myreader.application.App
import xyz.fycz.myreader.greendao.DbManager
import xyz.fycz.myreader.greendao.entity.Book
import xyz.fycz.myreader.greendao.entity.Chapter
import xyz.fycz.myreader.greendao.entity.rule.BookSource
import xyz.fycz.myreader.greendao.entity.rule.ContentRule
import xyz.fycz.myreader.greendao.service.ChapterService
import xyz.fycz.myreader.model.third3.ContentEmptyException
import xyz.fycz.myreader.model.third3.NoStackTraceException
import xyz.fycz.myreader.util.utils.HtmlFormatter
import xyz.fycz.myreader.util.utils.NetworkUtils
/**
* 获取正文
@ -30,25 +31,25 @@ object BookContent {
scope: CoroutineScope,
bookSource: BookSource,
book: Book,
bookChapter: BookChapter,
bookChapter: Chapter,
redirectUrl: String,
baseUrl: String,
body: String?,
nextChapterUrl: String? = null
): String {
body ?: throw NoStackTraceException(
appCtx.getString(R.string.error_get_web_content, baseUrl)
App.getmContext().getString(R.string.error_get_web_content, baseUrl)
)
Debug.log(bookSource.bookSourceUrl, "≡获取成功:${baseUrl}")
Debug.log(bookSource.bookSourceUrl, body, state = 40)
Log.d(bookSource.sourceUrl, "≡获取成功:${baseUrl}")
Log.d(bookSource.sourceUrl, body)
val mNextChapterUrl = if (!nextChapterUrl.isNullOrEmpty()) {
nextChapterUrl
} else {
appDb.bookChapterDao.getChapter(book.bookUrl, bookChapter.index + 1)?.url
ChapterService.getInstance().findBookAllChapterByBookId(book.id)[bookChapter.number + 1].url
}
val content = StringBuilder()
val nextUrlList = arrayListOf(baseUrl)
val contentRule = bookSource.getContentRule()
val contentRule = bookSource.contentRule
val analyzeRule = AnalyzeRule(book, bookSource).setContent(body, baseUrl)
analyzeRule.setRedirectUrl(baseUrl)
analyzeRule.nextChapterUrl = mNextChapterUrl
@ -70,7 +71,7 @@ object BookContent {
mUrl = nextUrl,
source = bookSource,
ruleData = book,
headerMapF = bookSource.getHeaderMap()
//headerMapF = bookSource.getHeaderMap()
).getStrResponseAwait()
res.body?.let { nextBody ->
contentData = analyzeContent(
@ -82,9 +83,9 @@ object BookContent {
content.append("\n").append(contentData.first)
}
}
Debug.log(bookSource.bookSourceUrl, "◇本章总页数:${nextUrlList.size}")
Log.d(bookSource.sourceUrl, "◇本章总页数:${nextUrlList.size}")
} else if (contentData.second.size > 1) {
Debug.log(bookSource.bookSourceUrl, "◇并发解析目录,总页数:${contentData.second.size}")
Log.d(bookSource.sourceUrl, "◇并发解析目录,总页数:${contentData.second.size}")
withContext(IO) {
val asyncArray = Array(contentData.second.size) {
async(IO) {
@ -93,7 +94,7 @@ object BookContent {
mUrl = urlStr,
source = bookSource,
ruleData = book,
headerMapF = bookSource.getHeaderMap()
//headerMapF = bookSource.getHeaderMap()
)
val res = analyzeUrl.getStrResponseAwait()
analyzeContent(
@ -113,14 +114,14 @@ object BookContent {
if (!replaceRegex.isNullOrEmpty()) {
contentStr = analyzeRule.getString(replaceRegex, contentStr)
}
Debug.log(bookSource.bookSourceUrl, "┌获取章节名称")
Debug.log(bookSource.bookSourceUrl, "${bookChapter.title}")
Debug.log(bookSource.bookSourceUrl, "┌获取正文内容")
Debug.log(bookSource.bookSourceUrl, "\n$contentStr")
Log.d(bookSource.sourceUrl, "┌获取章节名称")
Log.d(bookSource.sourceUrl, "${bookChapter.title}")
Log.d(bookSource.sourceUrl, "┌获取正文内容")
Log.d(bookSource.sourceUrl, "\n$contentStr")
if (contentStr.isBlank()) {
throw ContentEmptyException("内容为空")
}
BookHelp.saveContent(bookSource, book, bookChapter, contentStr)
//BookHelp.saveContent(bookSource, book, bookChapter, contentStr)
return contentStr
}
@ -131,7 +132,7 @@ object BookContent {
redirectUrl: String,
body: String,
contentRule: ContentRule,
chapter: BookChapter,
chapter: Chapter,
bookSource: BookSource,
nextChapterUrl: String?,
printLog: Boolean = true
@ -144,15 +145,16 @@ object BookContent {
analyzeRule.chapter = chapter
//获取正文
var content = analyzeRule.getString(contentRule.content)
content = HtmlFormatter.formatKeepImg(content, rUrl)
//content = HtmlFormatter.formatKeepImg(content, rUrl)
content = HtmlFormatter.format(content)
//获取下一页链接
val nextUrlRule = contentRule.nextContentUrl
val nextUrlRule = contentRule.contentUrlNext
if (!nextUrlRule.isNullOrEmpty()) {
Debug.log(bookSource.bookSourceUrl, "┌获取正文下一页链接", printLog)
if (printLog) Log.d(bookSource.sourceUrl, "┌获取正文下一页链接")
analyzeRule.getStringList(nextUrlRule, isUrl = true)?.let {
nextUrlList.addAll(it)
}
Debug.log(bookSource.bookSourceUrl, "" + nextUrlList.joinToString(""), printLog)
if (printLog) Log.d(bookSource.sourceUrl, "" + nextUrlList.joinToString(""))
}
return Pair(content, nextUrlList)
}

@ -1,19 +1,17 @@
package xyz.fycz.myreader.model.third3.webBook
import io.legado.app.R
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookSource
import io.legado.app.help.BookHelp
import io.legado.app.model.Debug
import io.legado.app.model.NoStackTraceException
import android.util.Log
import xyz.fycz.myreader.model.third3.analyzeRule.AnalyzeRule
import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.StringUtils.wordCountFormat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ensureActive
import splitties.init.appCtx
import xyz.fycz.myreader.R
import xyz.fycz.myreader.application.App
import xyz.fycz.myreader.greendao.entity.Book
import xyz.fycz.myreader.greendao.entity.rule.BookSource
import xyz.fycz.myreader.model.third3.NoStackTraceException
import xyz.fycz.myreader.util.utils.HtmlFormatter
import xyz.fycz.myreader.util.utils.NetworkUtils
/**
* 获取详情
@ -31,10 +29,10 @@ object BookInfo {
canReName: Boolean,
) {
body ?: throw NoStackTraceException(
appCtx.getString(R.string.error_get_web_content, baseUrl)
App.getmContext().getString(R.string.error_get_web_content, baseUrl)
)
Debug.log(bookSource.bookSourceUrl, "≡获取成功:${baseUrl}")
Debug.log(bookSource.bookSourceUrl, body, state = 20)
Log.d(bookSource.sourceUrl, "≡获取成功:${baseUrl}")
Log.d(bookSource.sourceUrl, body)
val analyzeRule = AnalyzeRule(book, bookSource)
analyzeRule.setContent(body).setBaseUrl(baseUrl)
analyzeRule.setRedirectUrl(redirectUrl)
@ -55,87 +53,92 @@ object BookInfo {
infoRule.init?.let {
if (it.isNotBlank()) {
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "≡执行详情页初始化规则")
Log.d(bookSource.sourceUrl, "≡执行详情页初始化规则")
analyzeRule.setContent(analyzeRule.getElement(it))
}
}
val mCanReName = canReName && !infoRule.canReName.isNullOrBlank()
//val mCanReName = canReName && !infoRule.canReName.isNullOrBlank()
val mCanReName = false
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取书名")
BookHelp.formatBookName(analyzeRule.getString(infoRule.name)).let {
Log.d(bookSource.sourceUrl, "┌获取书名")
BookList.formatBookName(analyzeRule.getString(infoRule.name)).let {
if (it.isNotEmpty() && (mCanReName || book.name.isEmpty())) {
book.name = it
}
Debug.log(bookSource.bookSourceUrl, "${it}")
Log.d(bookSource.sourceUrl, "${it}")
}
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取作者")
BookHelp.formatBookAuthor(analyzeRule.getString(infoRule.author)).let {
Log.d(bookSource.sourceUrl, "┌获取作者")
BookList.formatBookAuthor(analyzeRule.getString(infoRule.author)).let {
if (it.isNotEmpty() && (mCanReName || book.author.isEmpty())) {
book.author = it
}
Debug.log(bookSource.bookSourceUrl, "${it}")
Log.d(bookSource.sourceUrl, "${it}")
}
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取分类")
Log.d(bookSource.sourceUrl, "┌获取分类")
try {
analyzeRule.getStringList(infoRule.kind)
analyzeRule.getStringList(infoRule.type)
?.joinToString(",")
?.let {
if (it.isNotEmpty()) book.kind = it
if (it.isNotEmpty()) book.type = it
}
Debug.log(bookSource.bookSourceUrl, "${book.kind}")
Log.d(bookSource.sourceUrl, "${book.type}")
} catch (e: Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}")
Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
}
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取字数")
Log.d(bookSource.sourceUrl, "┌获取字数")
try {
wordCountFormat(analyzeRule.getString(infoRule.wordCount)).let {
/*wordCountFormat(analyzeRule.getString(infoRule.wordCount)).let {
if (it.isNotEmpty()) book.wordCount = it
}*/
analyzeRule.getString(infoRule.wordCount).let {
if (it.isNotEmpty()) book.wordCount = it
}
Debug.log(bookSource.bookSourceUrl, "${book.wordCount}")
Log.d(bookSource.sourceUrl, "${book.wordCount}")
} catch (e: Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}")
Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
}
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取最新章节")
Log.d(bookSource.sourceUrl, "┌获取最新章节")
try {
analyzeRule.getString(infoRule.lastChapter).let {
if (it.isNotEmpty()) book.latestChapterTitle = it
if (it.isNotEmpty()) book.newestChapterTitle = it
}
Debug.log(bookSource.bookSourceUrl, "${book.latestChapterTitle}")
Log.d(bookSource.sourceUrl, "${book.newestChapterTitle}")
} catch (e: Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}")
Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
}
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取简介")
Log.d(bookSource.sourceUrl, "┌获取简介")
try {
analyzeRule.getString(infoRule.intro).let {
if (it.isNotEmpty()) book.intro = HtmlFormatter.format(it)
analyzeRule.getString(infoRule.desc).let {
if (it.isNotEmpty()) book.desc = HtmlFormatter.format(it)
}
Debug.log(bookSource.bookSourceUrl, "${book.intro}")
Log.d(bookSource.sourceUrl, "${book.desc}")
} catch (e: Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}")
Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
}
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取封面链接")
Log.d(bookSource.sourceUrl, "┌获取封面链接")
try {
analyzeRule.getString(infoRule.coverUrl).let {
if (it.isNotEmpty()) book.coverUrl = NetworkUtils.getAbsoluteURL(baseUrl, it)
analyzeRule.getString(infoRule.imgUrl).let {
if (it.isNotEmpty()) book.imgUrl = NetworkUtils.getAbsoluteURL(baseUrl, it)
}
Debug.log(bookSource.bookSourceUrl, "${book.coverUrl}")
Log.d(bookSource.sourceUrl, "${book.imgUrl}")
} catch (e: Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}")
Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
}
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取目录链接")
book.tocUrl = analyzeRule.getString(infoRule.tocUrl, isUrl = true)
if (book.tocUrl.isEmpty()) book.tocUrl = redirectUrl
if (book.tocUrl == redirectUrl) {
book.tocHtml = body
Log.d(bookSource.sourceUrl, "┌获取目录链接")
book.chapterUrl = analyzeRule.getString(infoRule.tocUrl, isUrl = true)
if (book.chapterUrl.isEmpty()) book.chapterUrl = redirectUrl
if (book.chapterUrl == redirectUrl) {
book.putCathe("tocHtml", body)
//book.tocHtml = body
}
Debug.log(bookSource.bookSourceUrl, "${book.tocUrl}")
Log.d(bookSource.sourceUrl, "${book.chapterUrl}")
}
}

@ -1,20 +1,18 @@
package xyz.fycz.myreader.model.third3.webBook
import io.legado.app.R
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.SearchBook
import io.legado.app.data.entities.rule.BookListRule
import io.legado.app.help.BookHelp
import io.legado.app.model.Debug
import io.legado.app.model.NoStackTraceException
import android.util.Log
import xyz.fycz.myreader.model.third3.analyzeRule.AnalyzeRule
import xyz.fycz.myreader.model.third3.analyzeRule.AnalyzeUrl
import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.StringUtils.wordCountFormat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ensureActive
import splitties.init.appCtx
import xyz.fycz.myreader.R
import xyz.fycz.myreader.application.App
import xyz.fycz.myreader.greendao.entity.Book
import xyz.fycz.myreader.greendao.entity.rule.BookListRule
import xyz.fycz.myreader.greendao.entity.rule.BookSource
import xyz.fycz.myreader.model.third3.NoStackTraceException
import xyz.fycz.myreader.util.utils.HtmlFormatter
import xyz.fycz.myreader.util.utils.NetworkUtils
/**
* 获取书籍列表
@ -25,32 +23,32 @@ object BookList {
fun analyzeBookList(
scope: CoroutineScope,
bookSource: BookSource,
variableBook: SearchBook,
variableBook: Book,
analyzeUrl: AnalyzeUrl,
baseUrl: String,
body: String?,
isSearch: Boolean = true,
): ArrayList<SearchBook> {
): ArrayList<Book> {
body ?: throw NoStackTraceException(
appCtx.getString(
App.getmContext().getString(
R.string.error_get_web_content,
analyzeUrl.ruleUrl
)
)
val bookList = ArrayList<SearchBook>()
Debug.log(bookSource.bookSourceUrl, "≡获取成功:${analyzeUrl.ruleUrl}")
Debug.log(bookSource.bookSourceUrl, body, state = 10)
val bookList = ArrayList<Book>()
Log.d(bookSource.sourceUrl, "≡获取成功:${analyzeUrl.ruleUrl}")
Log.d(bookSource.sourceUrl, body)
val analyzeRule = AnalyzeRule(variableBook, bookSource)
analyzeRule.setContent(body).setBaseUrl(baseUrl)
analyzeRule.setRedirectUrl(baseUrl)
bookSource.bookUrlPattern?.let {
bookSource.infoRule.urlPattern?.let {
scope.ensureActive()
if (baseUrl.matches(it.toRegex())) {
Debug.log(bookSource.bookSourceUrl, "≡链接为详情页")
Log.d(bookSource.sourceUrl, "≡链接为详情页")
getInfoItem(
scope, bookSource, analyzeRule, analyzeUrl, body, baseUrl, variableBook.variable
)?.let { searchBook ->
searchBook.infoHtml = body
searchBook.putCathe("infoHtml", body)
bookList.add(searchBook)
}
return bookList
@ -59,11 +57,11 @@ object BookList {
val collections: List<Any>
var reverse = false
val bookListRule: BookListRule = when {
isSearch -> bookSource.getSearchRule()
bookSource.getExploreRule().bookList.isNullOrBlank() -> bookSource.getSearchRule()
else -> bookSource.getExploreRule()
isSearch -> bookSource.searchRule
bookSource.findRule.url.isNullOrBlank() -> bookSource.searchRule
else -> bookSource.findRule
}
var ruleList: String = bookListRule.bookList ?: ""
var ruleList: String = bookListRule.list ?: ""
if (ruleList.startsWith("-")) {
reverse = true
ruleList = ruleList.substring(1)
@ -71,27 +69,27 @@ object BookList {
if (ruleList.startsWith("+")) {
ruleList = ruleList.substring(1)
}
Debug.log(bookSource.bookSourceUrl, "┌获取书籍列表")
Log.d(bookSource.sourceUrl, "┌获取书籍列表")
collections = analyzeRule.getElements(ruleList)
scope.ensureActive()
if (collections.isEmpty() && bookSource.bookUrlPattern.isNullOrEmpty()) {
Debug.log(bookSource.bookSourceUrl, "└列表为空,按详情页解析")
if (collections.isEmpty() && bookSource.infoRule.urlPattern.isNullOrEmpty()) {
Log.d(bookSource.sourceUrl, "└列表为空,按详情页解析")
getInfoItem(
scope, bookSource, analyzeRule, analyzeUrl, body, baseUrl, variableBook.variable
)?.let { searchBook ->
searchBook.infoHtml = body
searchBook.putCathe("infoHtml", body)
bookList.add(searchBook)
}
} else {
val ruleName = analyzeRule.splitSourceRule(bookListRule.name)
val ruleBookUrl = analyzeRule.splitSourceRule(bookListRule.bookUrl)
val ruleBookUrl = analyzeRule.splitSourceRule(bookListRule.infoUrl)
val ruleAuthor = analyzeRule.splitSourceRule(bookListRule.author)
val ruleCoverUrl = analyzeRule.splitSourceRule(bookListRule.coverUrl)
val ruleIntro = analyzeRule.splitSourceRule(bookListRule.intro)
val ruleKind = analyzeRule.splitSourceRule(bookListRule.kind)
val ruleCoverUrl = analyzeRule.splitSourceRule(bookListRule.imgUrl)
val ruleIntro = analyzeRule.splitSourceRule(bookListRule.desc)
val ruleKind = analyzeRule.splitSourceRule(bookListRule.type)
val ruleLastChapter = analyzeRule.splitSourceRule(bookListRule.lastChapter)
val ruleWordCount = analyzeRule.splitSourceRule(bookListRule.wordCount)
Debug.log(bookSource.bookSourceUrl, "└列表大小:${collections.size}")
Log.d(bookSource.sourceUrl, "└列表大小:${collections.size}")
for ((index, item) in collections.withIndex()) {
getSearchItem(
scope, bookSource, analyzeRule, item, baseUrl, variableBook.variable,
@ -105,8 +103,8 @@ object BookList {
ruleLastChapter = ruleLastChapter,
ruleWordCount = ruleWordCount
)?.let { searchBook ->
if (baseUrl == searchBook.bookUrl) {
searchBook.infoHtml = body
if (baseUrl == searchBook.infoUrl) {
searchBook.putCathe("infoHtml", body)
}
bookList.add(searchBook)
}
@ -127,13 +125,14 @@ object BookList {
body: String,
baseUrl: String,
variable: String?
): SearchBook? {
val book = Book(variable = variable)
book.bookUrl = analyzeUrl.ruleUrl
book.origin = bookSource.bookSourceUrl
book.originName = bookSource.bookSourceName
book.originOrder = bookSource.customOrder
book.type = bookSource.bookSourceType
): Book? {
val book = Book()
book.variable = variable
book.infoUrl = analyzeUrl.ruleUrl
book.source = bookSource.sourceUrl
//book.originName = bookSource.bookSourceName
//book.originOrder = bookSource.customOrder
//book.type = bookSource.bookSourceType
analyzeRule.book = book
BookInfo.analyzeBookInfo(
scope,
@ -146,7 +145,8 @@ object BookList {
false
)
if (book.name.isNotBlank()) {
return book.toSearchBook()
//return book.toSearchBook()
return book
}
return null
}
@ -168,76 +168,98 @@ object BookList {
ruleWordCount: List<AnalyzeRule.SourceRule>,
ruleIntro: List<AnalyzeRule.SourceRule>,
ruleLastChapter: List<AnalyzeRule.SourceRule>
): SearchBook? {
val searchBook = SearchBook(variable = variable)
searchBook.origin = bookSource.bookSourceUrl
searchBook.originName = bookSource.bookSourceName
): Book? {
val searchBook = Book()
searchBook.variable = variable
searchBook.source = bookSource.sourceUrl
/* searchBook.originName = bookSource.bookSourceName
searchBook.type = bookSource.bookSourceType
searchBook.originOrder = bookSource.customOrder
searchBook.originOrder = bookSource.customOrder*/
analyzeRule.book = searchBook
analyzeRule.setContent(item)
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取书名", log)
searchBook.name = BookHelp.formatBookName(analyzeRule.getString(ruleName))
Debug.log(bookSource.bookSourceUrl, "${searchBook.name}", log)
if (log) if (log) Log.d(bookSource.sourceUrl, "┌获取书名")
searchBook.name = formatBookName(analyzeRule.getString(ruleName))
if (log) Log.d(bookSource.sourceUrl, "${searchBook.name}")
if (searchBook.name.isNotEmpty()) {
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取作者", log)
searchBook.author = BookHelp.formatBookAuthor(analyzeRule.getString(ruleAuthor))
Debug.log(bookSource.bookSourceUrl, "${searchBook.author}", log)
if (log) Log.d(bookSource.sourceUrl, "┌获取作者")
searchBook.author = formatBookAuthor(analyzeRule.getString(ruleAuthor))
if (log) Log.d(bookSource.sourceUrl, "${searchBook.author}")
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取分类", log)
if (log) Log.d(bookSource.sourceUrl, "┌获取分类")
try {
searchBook.kind = analyzeRule.getStringList(ruleKind)?.joinToString(",")
Debug.log(bookSource.bookSourceUrl, "${searchBook.kind}", log)
searchBook.type = analyzeRule.getStringList(ruleKind)?.joinToString(",")
if (log) Log.d(bookSource.sourceUrl, "${searchBook.type}")
} catch (e: Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log)
if (log) Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
}
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取字数", log)
if (log) Log.d(bookSource.sourceUrl, "┌获取字数")
try {
searchBook.wordCount = wordCountFormat(analyzeRule.getString(ruleWordCount))
Debug.log(bookSource.bookSourceUrl, "${searchBook.wordCount}", log)
//searchBook.wordCount = wordCountFormat(analyzeRule.getString(ruleWordCount))
searchBook.wordCount = analyzeRule.getString(ruleWordCount)
if (log) Log.d(bookSource.sourceUrl, "${searchBook.wordCount}")
} catch (e: java.lang.Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log)
if (log) Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
}
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取最新章节", log)
if (log) Log.d(bookSource.sourceUrl, "┌获取最新章节")
try {
searchBook.latestChapterTitle = analyzeRule.getString(ruleLastChapter)
Debug.log(bookSource.bookSourceUrl, "${searchBook.latestChapterTitle}", log)
searchBook.newestChapterTitle = analyzeRule.getString(ruleLastChapter)
if (log) Log.d(bookSource.sourceUrl, "${searchBook.newestChapterTitle}")
} catch (e: java.lang.Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log)
if (log) Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
}
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取简介", log)
if (log) Log.d(bookSource.sourceUrl, "┌获取简介")
try {
searchBook.intro = HtmlFormatter.format(analyzeRule.getString(ruleIntro))
Debug.log(bookSource.bookSourceUrl, "${searchBook.intro}", log)
searchBook.desc = HtmlFormatter.format(analyzeRule.getString(ruleIntro))
if (log) Log.d(bookSource.sourceUrl, "${searchBook.desc}")
} catch (e: java.lang.Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log)
if (log) Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
}
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取封面链接", log)
if (log) Log.d(bookSource.sourceUrl, "┌获取封面链接")
try {
analyzeRule.getString(ruleCoverUrl).let {
if (it.isNotEmpty()) searchBook.coverUrl =
if (it.isNotEmpty()) searchBook.imgUrl =
NetworkUtils.getAbsoluteURL(baseUrl, it)
}
Debug.log(bookSource.bookSourceUrl, "${searchBook.coverUrl}", log)
if (log) Log.d(bookSource.sourceUrl, "${searchBook.imgUrl}")
} catch (e: java.lang.Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log)
if (log) Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
}
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取详情页链接", log)
searchBook.bookUrl = analyzeRule.getString(ruleBookUrl, isUrl = true)
if (searchBook.bookUrl.isEmpty()) {
searchBook.bookUrl = baseUrl
if (log) Log.d(bookSource.sourceUrl, "┌获取详情页链接")
searchBook.infoUrl = analyzeRule.getString(ruleBookUrl, isUrl = true)
if (searchBook.infoUrl.isEmpty()) {
searchBook.infoUrl = baseUrl
}
Debug.log(bookSource.bookSourceUrl, "${searchBook.bookUrl}", log)
if (log) Log.d(bookSource.sourceUrl, "${searchBook.infoUrl}")
return searchBook
}
return null
}
val nameRegex = Regex("\\s+作\\s*者.*|\\s+\\S+\\s+著")
val authorRegex = Regex("^\\s*作\\s*者[::\\s]+|\\s+著")
/**
* 格式化书名
*/
fun formatBookName(name: String): String {
return name
.replace(nameRegex, "")
.trim { it <= ' ' }
}
/**
* 格式化作者
*/
fun formatBookAuthor(author: String): String {
return author
.replace(authorRegex, "")
.trim { it <= ' ' }
}
}

@ -1,209 +0,0 @@
package xyz.fycz.myreader.model.third3.webBook
import io.legado.app.constant.AppConst
import io.legado.app.constant.PreferKey
import io.legado.app.data.appDb
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.SearchBook
import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.CompositeCoroutine
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getPrefString
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExecutorCoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.isActive
import splitties.init.appCtx
import java.util.concurrent.Executors
import kotlin.math.min
class SearchModel(private val scope: CoroutineScope, private val callBack: CallBack) {
val threadCount = AppConfig.threadCount
private var searchPool: ExecutorCoroutineDispatcher? = null
private var mSearchId = 0L
private var searchPage = 1
private var searchKey: String = ""
private var tasks = CompositeCoroutine()
private var bookSourceList = arrayListOf<BookSource>()
private var searchBooks = arrayListOf<SearchBook>()
@Volatile
private var searchIndex = -1
private fun initSearchPool() {
searchPool?.close()
searchPool = Executors
.newFixedThreadPool(min(threadCount, AppConst.MAX_THREAD)).asCoroutineDispatcher()
}
fun search(searchId: Long, key: String) {
callBack.onSearchStart()
if (searchId != mSearchId) {
if (key.isEmpty()) {
callBack.onSearchCancel()
return
} else {
this.searchKey = key
}
if (mSearchId != 0L) {
close()
}
initSearchPool()
mSearchId = searchId
searchPage = 1
val searchGroup = appCtx.getPrefString("searchGroup") ?: ""
bookSourceList.clear()
if (searchGroup.isBlank()) {
bookSourceList.addAll(appDb.bookSourceDao.allEnabled)
} else {
val sources = appDb.bookSourceDao.getEnabledByGroup(searchGroup)
if (sources.isEmpty()) {
bookSourceList.addAll(appDb.bookSourceDao.allEnabled)
} else {
bookSourceList.addAll(sources)
}
}
} else {
searchPage++
}
searchIndex = -1
for (i in 0 until threadCount) {
search(searchId)
}
}
@Synchronized
private fun search(searchId: Long) {
if (searchIndex >= bookSourceList.lastIndex) {
return
}
searchIndex++
val source = bookSourceList[searchIndex]
searchPool?.let { searchPool ->
val task = WebBook.searchBook(
scope,
source,
searchKey,
searchPage,
context = searchPool
).timeout(30000L)
.onSuccess(searchPool) {
onSuccess(searchId, it)
}
.onFinally(searchPool) {
onFinally(searchId)
}
tasks.add(task)
}
}
@Synchronized
private fun onSuccess(searchId: Long, items: ArrayList<SearchBook>) {
if (searchId == mSearchId) {
appDb.searchBookDao.insert(*items.toTypedArray())
val precision = appCtx.getPrefBoolean(PreferKey.precisionSearch)
mergeItems(scope, items, precision)
callBack.onSearchSuccess(searchBooks)
}
}
@Synchronized
private fun onFinally(searchId: Long) {
if (searchIndex < bookSourceList.lastIndex) {
search(searchId)
} else {
searchIndex++
}
if (searchIndex >= bookSourceList.lastIndex
+ min(bookSourceList.size, threadCount)
) {
callBack.onSearchFinish()
}
}
private fun mergeItems(scope: CoroutineScope, newDataS: List<SearchBook>, precision: Boolean) {
if (newDataS.isNotEmpty()) {
val copyData = ArrayList(searchBooks)
val equalData = arrayListOf<SearchBook>()
val containsData = arrayListOf<SearchBook>()
val otherData = arrayListOf<SearchBook>()
copyData.forEach {
if (!scope.isActive) return
if (it.name == searchKey || it.author == searchKey) {
equalData.add(it)
} else if (it.name.contains(searchKey) || it.author.contains(searchKey)) {
containsData.add(it)
} else {
otherData.add(it)
}
}
newDataS.forEach { nBook ->
if (!scope.isActive) return
if (nBook.name == searchKey || nBook.author == searchKey) {
var hasSame = false
equalData.forEach { pBook ->
if (!scope.isActive) return
if (pBook.name == nBook.name && pBook.author == nBook.author) {
pBook.addOrigin(nBook.origin)
hasSame = true
}
}
if (!hasSame) {
equalData.add(nBook)
}
} else if (nBook.name.contains(searchKey) || nBook.author.contains(searchKey)) {
var hasSame = false
containsData.forEach { pBook ->
if (!scope.isActive) return
if (pBook.name == nBook.name && pBook.author == nBook.author) {
pBook.addOrigin(nBook.origin)
hasSame = true
}
}
if (!hasSame) {
containsData.add(nBook)
}
} else if (!precision) {
var hasSame = false
otherData.forEach { pBook ->
if (!scope.isActive) return
if (pBook.name == nBook.name && pBook.author == nBook.author) {
pBook.addOrigin(nBook.origin)
hasSame = true
}
}
if (!hasSame) {
otherData.add(nBook)
}
}
}
if (!scope.isActive) return
equalData.sortByDescending { it.origins.size }
equalData.addAll(containsData.sortedByDescending { it.origins.size })
if (!precision) {
equalData.addAll(otherData)
}
searchBooks = equalData
}
}
fun cancelSearch() {
close()
callBack.onSearchCancel()
}
fun close() {
tasks.clear()
searchPool?.close()
searchPool = null
mSearchId = 0L
}
interface CallBack {
fun onSearchStart()
fun onSearchSuccess(searchBooks: ArrayList<SearchBook>)
fun onSearchFinish()
fun onSearchCancel()
}
}

@ -1,12 +1,17 @@
package xyz.fycz.myreader.model.third3.webBook
import android.util.Log
import xyz.fycz.myreader.model.third3.http.StrResponse
import xyz.fycz.myreader.model.third3.analyzeRule.AnalyzeUrl
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import xyz.fycz.myreader.greendao.entity.Book
import xyz.fycz.myreader.greendao.entity.Chapter
import xyz.fycz.myreader.greendao.entity.rule.BookSource
import xyz.fycz.myreader.model.third3.Coroutine
import xyz.fycz.myreader.model.third3.NoStackTraceException
import xyz.fycz.myreader.util.utils.NetworkUtils
import kotlin.coroutines.CoroutineContext
@Suppress("MemberVisibilityCanBePrivate")
@ -21,7 +26,7 @@ object WebBook {
key: String,
page: Int? = 1,
context: CoroutineContext = Dispatchers.IO,
): Coroutine<ArrayList<SearchBook>> {
): Coroutine<ArrayList<Book>> {
return Coroutine.async(scope, context) {
searchBookAwait(scope, bookSource, key, page)
}
@ -32,25 +37,25 @@ object WebBook {
bookSource: BookSource,
key: String,
page: Int? = 1,
): ArrayList<SearchBook> {
val variableBook = SearchBook()
bookSource.searchUrl?.let { searchUrl ->
): ArrayList<Book> {
val variableBook = Book()
bookSource.searchRule.searchUrl?.let { searchUrl ->
val analyzeUrl = AnalyzeUrl(
mUrl = searchUrl,
key = key,
page = page,
baseUrl = bookSource.bookSourceUrl,
headerMapF = bookSource.getHeaderMap(true),
baseUrl = bookSource.sourceUrl,
//headerMapF = bookSource.getHeaderMap(true),
source = bookSource,
ruleData = variableBook,
)
var res = analyzeUrl.getStrResponseAwait()
//检测书源是否已登录
bookSource.loginCheckJs?.let { checkJs ->
/*bookSource.loginCheckJs?.let { checkJs ->
if (checkJs.isNotBlank()) {
res = analyzeUrl.evalJS(checkJs, res) as StrResponse
}
}
}*/
return BookList.analyzeBookList(
scope,
bookSource,
@ -73,7 +78,7 @@ object WebBook {
url: String,
page: Int? = 1,
context: CoroutineContext = Dispatchers.IO,
): Coroutine<List<SearchBook>> {
): Coroutine<List<Book>> {
return Coroutine.async(scope, context) {
exploreBookAwait(scope, bookSource, url, page)
}
@ -84,23 +89,23 @@ object WebBook {
bookSource: BookSource,
url: String,
page: Int? = 1,
): ArrayList<SearchBook> {
val variableBook = SearchBook()
): ArrayList<Book> {
val variableBook = Book()
val analyzeUrl = AnalyzeUrl(
mUrl = url,
page = page,
baseUrl = bookSource.bookSourceUrl,
baseUrl = bookSource.sourceUrl,
source = bookSource,
ruleData = variableBook,
headerMapF = bookSource.getHeaderMap(true)
//headerMapF = bookSource.getHeaderMap(true)
)
var res = analyzeUrl.getStrResponseAwait()
//检测书源是否已登录
bookSource.loginCheckJs?.let { checkJs ->
/*bookSource.loginCheckJs?.let { checkJs ->
if (checkJs.isNotBlank()) {
res = analyzeUrl.evalJS(checkJs, result = res) as StrResponse
}
}
}*/
return BookList.analyzeBookList(
scope,
bookSource,
@ -133,37 +138,37 @@ object WebBook {
book: Book,
canReName: Boolean = true,
): Book {
book.type = bookSource.bookSourceType
if (!book.infoHtml.isNullOrEmpty()) {
//book.type = bookSource.bookSourceType
if (!book.getCathe("infoHtml").isNullOrEmpty()) {
BookInfo.analyzeBookInfo(
scope,
bookSource,
book,
book.bookUrl,
book.bookUrl,
book.infoHtml,
book.infoUrl,
book.infoUrl,
book.getCathe("infoHtml"),
canReName
)
} else {
val analyzeUrl = AnalyzeUrl(
mUrl = book.bookUrl,
baseUrl = bookSource.bookSourceUrl,
mUrl = book.infoUrl,
baseUrl = bookSource.sourceUrl,
source = bookSource,
ruleData = book,
headerMapF = bookSource.getHeaderMap(true)
//headerMapF = bookSource.getHeaderMap(true)
)
var res = analyzeUrl.getStrResponseAwait()
//检测书源是否已登录
bookSource.loginCheckJs?.let { checkJs ->
/*bookSource.loginCheckJs?.let { checkJs ->
if (checkJs.isNotBlank()) {
res = analyzeUrl.evalJS(checkJs, result = res) as StrResponse
}
}
}*/
BookInfo.analyzeBookInfo(
scope,
bookSource,
book,
book.bookUrl,
book.infoUrl,
res.url,
res.body,
canReName
@ -180,7 +185,7 @@ object WebBook {
bookSource: BookSource,
book: Book,
context: CoroutineContext = Dispatchers.IO
): Coroutine<List<BookChapter>> {
): Coroutine<List<Chapter>> {
return Coroutine.async(scope, context) {
getChapterListAwait(scope, bookSource, book)
}
@ -190,39 +195,38 @@ object WebBook {
scope: CoroutineScope,
bookSource: BookSource,
book: Book,
): List<BookChapter> {
book.type = bookSource.bookSourceType
return if (book.bookUrl == book.tocUrl && !book.tocHtml.isNullOrEmpty()) {
): List<Chapter> {
//book.type = bookSource.bookSourceType
return if (book.infoUrl == book.chapterUrl && !book.getCathe("tocHtml").isNullOrEmpty()) {
BookChapterList.analyzeChapterList(
scope,
bookSource,
book,
book.tocUrl,
book.tocUrl,
book.tocHtml
book.chapterUrl,
book.chapterUrl,
book.getCathe("tocHtml"),
)
} else {
val analyzeUrl = AnalyzeUrl(
mUrl = book.tocUrl,
baseUrl = book.bookUrl,
mUrl = book.chapterUrl,
baseUrl = book.infoUrl,
source = bookSource,
ruleData = book,
headerMapF = bookSource.getHeaderMap(true)
)
var res = analyzeUrl.getStrResponseAwait()
//检测书源是否已登录
bookSource.loginCheckJs?.let { checkJs ->
/*bookSource.loginCheckJs?.let { checkJs ->
if (checkJs.isNotBlank()) {
res = analyzeUrl.evalJS(checkJs, result = res) as StrResponse
}
}
}*/
BookChapterList.analyzeChapterList(
scope,
bookSource,
book,
book.tocUrl,
book.chapterUrl,
res.url,
res.body
res.body,
)
}
}
@ -234,7 +238,7 @@ object WebBook {
scope: CoroutineScope,
bookSource: BookSource,
book: Book,
bookChapter: BookChapter,
bookChapter: Chapter,
nextChapterUrl: String? = null,
context: CoroutineContext = Dispatchers.IO
): Coroutine<String> {
@ -247,49 +251,50 @@ object WebBook {
scope: CoroutineScope,
bookSource: BookSource,
book: Book,
bookChapter: BookChapter,
nextChapterUrl: String? = null
bookChapter: Chapter,
nextChapterUrl: String? = null,
): String {
if (bookSource.getContentRule().content.isNullOrEmpty()) {
Debug.log(bookSource.bookSourceUrl, "⇒正文规则为空,使用章节链接:${bookChapter.url}")
if (bookSource.contentRule.content.isNullOrEmpty()) {
Log.d(bookSource.sourceUrl, "⇒正文规则为空,使用章节链接:${bookChapter.url}")
return bookChapter.url
}
return if (bookChapter.url == book.bookUrl && !book.tocHtml.isNullOrEmpty()) {
val absoluteUrl = NetworkUtils.getAbsoluteURL(book.chapterUrl, bookChapter.url)
return if (bookChapter.url == book.infoUrl && !book.getCathe("tocHtml").isNullOrEmpty()) {
BookContent.analyzeContent(
scope,
bookSource,
book,
bookChapter,
bookChapter.getAbsoluteURL(),
bookChapter.getAbsoluteURL(),
book.tocHtml,
absoluteUrl,
absoluteUrl,
book.getCathe("tocHtml"),
nextChapterUrl
)
} else {
val analyzeUrl = AnalyzeUrl(
mUrl = bookChapter.getAbsoluteURL(),
baseUrl = book.tocUrl,
mUrl = absoluteUrl,
baseUrl = book.chapterUrl,
source = bookSource,
ruleData = book,
chapter = bookChapter,
headerMapF = bookSource.getHeaderMap(true)
)
var res = analyzeUrl.getStrResponseAwait(
jsStr = bookSource.getContentRule().webJs,
sourceRegex = bookSource.getContentRule().sourceRegex
)
/*var res = analyzeUrl.getStrResponseAwait(
jsStr = bookSource.contentRule.webJs,
sourceRegex = bookSource.contentRule.sourceRegex
)*/
var res = analyzeUrl.getStrResponseAwait()
//检测书源是否已登录
bookSource.loginCheckJs?.let { checkJs ->
/*bookSource.loginCheckJs?.let { checkJs ->
if (checkJs.isNotBlank()) {
res = analyzeUrl.evalJS(checkJs, result = res) as StrResponse
}
}
}*/
BookContent.analyzeContent(
scope,
bookSource,
book,
bookChapter,
bookChapter.getAbsoluteURL(),
absoluteUrl,
res.url,
res.body,
nextChapterUrl
@ -317,7 +322,7 @@ object WebBook {
scope: CoroutineScope,
bookSources: List<BookSource>,
name: String,
author: String
author: String,
): Pair<BookSource, Book>? {
bookSources.forEach { source ->
kotlin.runCatching {
@ -326,8 +331,8 @@ object WebBook {
it.name == name && it.author == author
}?.let { searchBook ->
if (!scope.isActive) return null
var book = searchBook.toBook()
if (book.tocUrl.isBlank()) {
var book = searchBook
if (book.chapterUrl.isBlank()) {
book = getBookInfoAwait(scope, source, book)
}
return Pair(source, book)

@ -120,6 +120,9 @@ public class SourceEditActivity extends BaseActivity {
case APPCONST.THIRD_SOURCE:
sourceType = 3;
break;
case APPCONST.THIRD_3_SOURCE:
sourceType = 4;
break;
}
binding.sSourceType.setSelection(sourceType);
}
@ -286,6 +289,9 @@ public class SourceEditActivity extends BaseActivity {
case 3:
sourceType = APPCONST.THIRD_SOURCE;
break;
case 4:
sourceType = APPCONST.THIRD_3_SOURCE;
break;
}
source.setSourceType(sourceType);
source.setSearchRule(entityUtil.getSearchRule(searchEntities));

@ -0,0 +1,90 @@
package xyz.fycz.myreader.webapi
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import io.reactivex.Observable
import xyz.fycz.myreader.application.App
import xyz.fycz.myreader.entity.SearchBookBean
import xyz.fycz.myreader.greendao.entity.Book
import xyz.fycz.myreader.greendao.entity.Chapter
import xyz.fycz.myreader.model.mulvalmap.ConMVMap
import xyz.fycz.myreader.model.third3.webBook.WebBook
import xyz.fycz.myreader.webapi.crawler.source.Third3Crawler
/**
* @author fengyue
* @date 2022/1/20 11:46
*/
object Third3SourceApi : AndroidViewModel(App.getApplication()) {
val scope = viewModelScope
fun searchByT3C(
key: String,
rc: Third3Crawler
): Observable<ConMVMap<SearchBookBean, Book>> {
return Observable.create { emitter ->
WebBook.searchBook(
scope,
rc.source,
key,
1,
).timeout(30000L)
.onSuccess {
emitter.onNext(rc.getBooks(it))
}.onError {
emitter.onError(it)
}.onFinally {
emitter.onComplete()
}
}
}
fun getBookInfoByT3C(book: Book, rc: Third3Crawler): Observable<Book> {
return Observable.create { emitter ->
WebBook.getBookInfo(
scope,
rc.source,
book,
).onSuccess {
emitter.onNext(it)
}.onError {
emitter.onError(it)
}.onFinally {
emitter.onComplete()
}
}
}
fun getBookChaptersByT3C(book: Book, rc: Third3Crawler): Observable<List<Chapter>>{
return Observable.create { emitter ->
WebBook.getChapterList(
scope,
rc.source,
book,
).onSuccess {
emitter.onNext(it)
}.onError {
emitter.onError(it)
}.onFinally {
emitter.onComplete()
}
}
}
fun getChapterContentByT3C(chapter: Chapter, book: Book, rc: Third3Crawler): Observable<String>{
return Observable.create { emitter ->
WebBook.getContent(
scope,
rc.source,
book,
chapter
).onSuccess {
emitter.onNext(it)
}.onError {
emitter.onError(it)
}.onFinally {
emitter.onComplete()
}
}
}
}

@ -23,6 +23,7 @@ import xyz.fycz.myreader.model.third2.content.BookInfo;
import xyz.fycz.myreader.model.third2.content.BookList;
import xyz.fycz.myreader.util.utils.OkHttpUtils;
import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
import xyz.fycz.myreader.webapi.crawler.source.Third3Crawler;
import xyz.fycz.myreader.webapi.crawler.source.ThirdCrawler;
import xyz.fycz.myreader.webapi.crawler.source.find.ThirdFindCrawler;
@ -43,6 +44,9 @@ public class ThirdSourceApi {
* @return
*/
protected static Observable<ConMVMap<SearchBookBean, Book>> searchByTC(String key, final ThirdCrawler rc) {
if (rc instanceof Third3Crawler) {
return Third3SourceApi.INSTANCE.searchByT3C(key, (Third3Crawler) rc);
}
try {
Map<String, String> headers = rc.getHeaders();
headers.putAll(getCookies(rc.getNameSpace()));
@ -61,6 +65,9 @@ public class ThirdSourceApi {
}
protected static Observable<Book> getBookInfoByTC(Book book, ThirdCrawler rc) {
if (rc instanceof Third3Crawler) {
return Third3SourceApi.INSTANCE.getBookInfoByT3C(book, (Third3Crawler) rc);
}
BookSource source = rc.getSource();
BookInfo bookInfo = new BookInfo(source.getSourceUrl(), source.getSourceName(), source);
if (!TextUtils.isEmpty(book.getCathe("BookInfoHtml"))) {
@ -79,6 +86,9 @@ public class ThirdSourceApi {
}
protected static Observable<List<Chapter>> getBookChaptersByTC(Book book, ThirdCrawler rc) {
if (rc instanceof Third3Crawler) {
return Third3SourceApi.INSTANCE.getBookChaptersByT3C(book, (Third3Crawler) rc);
}
BookSource source = rc.getSource();
Map<String, String> headers = rc.getHeaders();
headers.putAll(getCookies(rc.getNameSpace()));
@ -113,6 +123,9 @@ public class ThirdSourceApi {
}
protected static Observable<String> getChapterContentByTC(Chapter chapter, Book book, ThirdCrawler rc) {
if (rc instanceof Third3Crawler) {
return Third3SourceApi.INSTANCE.getChapterContentByT3C(chapter, book, (Third3Crawler) rc);
}
BookSource source = rc.getSource();
Map<String, String> headers = rc.getHeaders();
headers.putAll(getCookies(rc.getNameSpace()));

@ -16,6 +16,7 @@ import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
import xyz.fycz.myreader.webapi.crawler.read.FYReadCrawler;
import xyz.fycz.myreader.webapi.crawler.source.JsonPathCrawler;
import xyz.fycz.myreader.webapi.crawler.source.MatcherCrawler;
import xyz.fycz.myreader.webapi.crawler.source.Third3Crawler;
import xyz.fycz.myreader.webapi.crawler.source.ThirdCrawler;
import xyz.fycz.myreader.webapi.crawler.source.XpathCrawler;
@ -27,6 +28,7 @@ import java.util.ResourceBundle;
import static xyz.fycz.myreader.common.APPCONST.JSON_PATH;
import static xyz.fycz.myreader.common.APPCONST.MATCHER;
import static xyz.fycz.myreader.common.APPCONST.THIRD_3_SOURCE;
import static xyz.fycz.myreader.common.APPCONST.THIRD_SOURCE;
import static xyz.fycz.myreader.common.APPCONST.XPATH;
@ -178,6 +180,7 @@ public class ReadCrawlerUtil {
public static ReadCrawler getReadCrawler(BookSource source) {
return getReadCrawler(source, false);
}
public static ReadCrawler getReadCrawler(BookSource source, boolean isInfo) {
try {
if (StringHelper.isEmpty(source.getSourceEName())) {
@ -196,6 +199,8 @@ public class ReadCrawlerUtil {
break;
case THIRD_SOURCE:
return new ThirdCrawler(source);
case THIRD_3_SOURCE:
return new Third3Crawler(source);
}
if (source.getSearchRule().isRelatedWithInfo() || isInfo) {
return crawler;

@ -0,0 +1,10 @@
package xyz.fycz.myreader.webapi.crawler.source
import xyz.fycz.myreader.greendao.entity.rule.BookSource
/**
* @author fengyue
* @date 2022/1/20 12:23
*/
class Third3Crawler(source: BookSource) : ThirdCrawler(source) {
}

@ -15,7 +15,7 @@ import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler;
* @date 2021/5/14 10:55
*/
public class ThirdCrawler extends BaseReadCrawler implements BookInfoCrawler {
private BookSource source;
private final BookSource source;
public ThirdCrawler(BookSource source) {
this.source = source;

@ -521,6 +521,21 @@
<string name="re_get_email_code">重新发送%s</string>
<!--error string start-->
<string name="error_no_source">没有书源</string>
<string name="error_get_book_info">书籍信息获取失败</string>
<string name="error_get_content">内容获取失败</string>
<string name="error_get_chapter_list">目录获取失败</string>
<string name="error_get_web_content">访问网站失败:%s</string>
<string name="error_read_file">文件读取失败</string>
<string name="error_load_toc">加载目录失败</string>
<string name="error_get_data">获取数据失败!</string>
<string name="error_load_msg">加载失败\n%s</string>
<string name="net_error_10001">没有网络</string>
<string name="net_error_10002">网络连接超时</string>
<string name="net_error_10003">数据解析失败</string>
<!--error string end-->
<string-array name="reset_screen_time">
<item>跟随系统</item>
<item>常亮</item>
@ -602,7 +617,8 @@
<item>Matcher</item>
<item>Xpath</item>
<item>JsonPath</item>
<item>第三方</item>
<item>第三方-2.0</item>
<item>第三方-3.0</item>
</string-array>
<string-array name="select_folder">

Loading…
Cancel
Save