第三方-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 'androidx.core:core-ktx:1.7.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
//anko //anko
def anko_version = '0.10.8' def anko_version = '0.10.8'

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

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

@ -37,6 +37,9 @@ public class Chapter implements RuleDataInterface {
private int number;//章节序号 private int number;//章节序号
private String title;//章节标题 private String title;//章节标题
private String url;//章节链接(本地书籍为:字符编码) private String url;//章节链接(本地书籍为:字符编码)
private boolean isVip;//是否VIP
private boolean isPay;//是否已购买
private String updateTime;//更新时间
@Nullable @Nullable
private String content;//章节正文 private String content;//章节正文
@ -49,14 +52,19 @@ public class Chapter implements RuleDataInterface {
@Transient @Transient
private Map<String, String> variableMap; private Map<String, String> variableMap;
@Generated(hash = 1398484308)
@Generated(hash = 1109296579)
public Chapter(String id, String bookId, int number, String title, String url, 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.id = id;
this.bookId = bookId; this.bookId = bookId;
this.number = number; this.number = number;
this.title = title; this.title = title;
this.url = url; this.url = url;
this.isVip = isVip;
this.isPay = isPay;
this.updateTime = updateTime;
this.content = content; this.content = content;
this.start = start; this.start = start;
this.end = end; this.end = end;
@ -67,6 +75,7 @@ public class Chapter implements RuleDataInterface {
public Chapter() { public Chapter() {
} }
public String getId() { public String getId() {
return this.id; return this.id;
} }
@ -169,4 +178,28 @@ public class Chapter implements RuleDataInterface {
public void setVariable(String variable) { public void setVariable(String variable) {
this.variable = 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 content;
private String contentBaseUrl; private String contentBaseUrl;
private String contentUrlNext; private String contentUrlNext;
private String replaceRegex;
protected ContentRule(Parcel in) { protected ContentRule(Parcel in) {
content = in.readString(); content = in.readString();
contentBaseUrl = in.readString(); contentBaseUrl = in.readString();
contentUrlNext = in.readString(); contentUrlNext = in.readString();
replaceRegex = in.readString();
} }
@Override @Override
@ -31,6 +33,7 @@ public class ContentRule implements Parcelable {
dest.writeString(content); dest.writeString(content);
dest.writeString(contentBaseUrl); dest.writeString(contentBaseUrl);
dest.writeString(contentUrlNext); dest.writeString(contentUrlNext);
dest.writeString(replaceRegex);
} }
@Override @Override
@ -58,7 +61,8 @@ public class ContentRule implements Parcelable {
ContentRule that = (ContentRule) o; ContentRule that = (ContentRule) o;
return stringEquals(content, that.content) && return stringEquals(content, that.content) &&
stringEquals(contentBaseUrl, that.contentBaseUrl) && 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; 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.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import static xyz.fycz.myreader.util.utils.StringUtils.stringEquals; import static xyz.fycz.myreader.util.utils.StringUtils.stringEquals;
/** /**
* @author fengyue * @author fengyue
* @date 2021/2/10 8:57 * @date 2021/2/10 8:57
*/ */
public class FindRule implements Parcelable { public class FindRule implements Parcelable, BookListRule {
private String url; private String url;
private String bookList; private String list;
private String name; private String name;
private String author; private String author;
private String type; private String type;
@ -33,7 +29,7 @@ public class FindRule implements Parcelable {
protected FindRule(Parcel in) { protected FindRule(Parcel in) {
url = in.readString(); url = in.readString();
bookList = in.readString(); list = in.readString();
name = in.readString(); name = in.readString();
author = in.readString(); author = in.readString();
type = in.readString(); type = in.readString();
@ -50,7 +46,7 @@ public class FindRule implements Parcelable {
@Override @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeString(url); dest.writeString(url);
dest.writeString(bookList); dest.writeString(list);
dest.writeString(name); dest.writeString(name);
dest.writeString(author); dest.writeString(author);
dest.writeString(type); dest.writeString(type);
@ -89,12 +85,12 @@ public class FindRule implements Parcelable {
this.url = url; this.url = url;
} }
public String getBookList() { public String getList() {
return bookList; return list;
} }
public void setBookList(String bookList) { public void setList(String bookList) {
this.bookList = bookList; this.list = bookList;
} }
public String getName() { public String getName() {
@ -192,7 +188,7 @@ public class FindRule implements Parcelable {
if (getClass() != o.getClass()) return false; if (getClass() != o.getClass()) return false;
FindRule findRule = (FindRule) o; FindRule findRule = (FindRule) o;
return stringEquals(url, findRule.url) && return stringEquals(url, findRule.url) &&
stringEquals(bookList, findRule.bookList) && stringEquals(list, findRule.list) &&
stringEquals(name, findRule.name) && stringEquals(name, findRule.name) &&
stringEquals(author, findRule.author) && stringEquals(author, findRule.author) &&
stringEquals(type, findRule.type) && stringEquals(type, findRule.type) &&

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

@ -22,6 +22,9 @@ public class TocRule implements Parcelable {
private String chapterName; private String chapterName;
private String chapterUrl; private String chapterUrl;
private String tocUrlNext; private String tocUrlNext;
private String isVip;
private String isPay;
private String updateTime;
protected TocRule(Parcel in) { protected TocRule(Parcel in) {
chapterList = in.readString(); chapterList = in.readString();
@ -29,6 +32,9 @@ public class TocRule implements Parcelable {
chapterName = in.readString(); chapterName = in.readString();
chapterUrl = in.readString(); chapterUrl = in.readString();
tocUrlNext = in.readString(); tocUrlNext = in.readString();
isVip = in.readString();
isPay = in.readString();
updateTime = in.readString();
} }
@Override @Override
@ -38,6 +44,9 @@ public class TocRule implements Parcelable {
dest.writeString(chapterName); dest.writeString(chapterName);
dest.writeString(chapterUrl); dest.writeString(chapterUrl);
dest.writeString(tocUrlNext); dest.writeString(tocUrlNext);
dest.writeString(isVip);
dest.writeString(isPay);
dest.writeString(updateTime);
} }
@Override @Override
@ -67,7 +76,10 @@ public class TocRule implements Parcelable {
stringEquals(chapterBaseUrl, tocRule.chapterBaseUrl) && stringEquals(chapterBaseUrl, tocRule.chapterBaseUrl) &&
stringEquals(chapterName, tocRule.chapterName) && stringEquals(chapterName, tocRule.chapterName) &&
stringEquals(chapterUrl, tocRule.chapterUrl) && 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() { public String getChapterList() {
@ -110,6 +122,30 @@ public class TocRule implements Parcelable {
this.tocUrlNext = tocUrlNext; 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() { public TocRule() {
} }

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

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

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

@ -1,19 +1,17 @@
package xyz.fycz.myreader.model.third3.webBook package xyz.fycz.myreader.model.third3.webBook
import io.legado.app.R
import io.legado.app.data.entities.Book import android.util.Log
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 xyz.fycz.myreader.model.third3.analyzeRule.AnalyzeRule 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.CoroutineScope
import kotlinx.coroutines.ensureActive 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.Book
import xyz.fycz.myreader.greendao.entity.rule.BookSource 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, canReName: Boolean,
) { ) {
body ?: throw NoStackTraceException( 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}") Log.d(bookSource.sourceUrl, "≡获取成功:${baseUrl}")
Debug.log(bookSource.bookSourceUrl, body, state = 20) Log.d(bookSource.sourceUrl, body)
val analyzeRule = AnalyzeRule(book, bookSource) val analyzeRule = AnalyzeRule(book, bookSource)
analyzeRule.setContent(body).setBaseUrl(baseUrl) analyzeRule.setContent(body).setBaseUrl(baseUrl)
analyzeRule.setRedirectUrl(redirectUrl) analyzeRule.setRedirectUrl(redirectUrl)
@ -55,87 +53,92 @@ object BookInfo {
infoRule.init?.let { infoRule.init?.let {
if (it.isNotBlank()) { if (it.isNotBlank()) {
scope.ensureActive() scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "≡执行详情页初始化规则") Log.d(bookSource.sourceUrl, "≡执行详情页初始化规则")
analyzeRule.setContent(analyzeRule.getElement(it)) analyzeRule.setContent(analyzeRule.getElement(it))
} }
} }
val mCanReName = canReName && !infoRule.canReName.isNullOrBlank() //val mCanReName = canReName && !infoRule.canReName.isNullOrBlank()
val mCanReName = false
scope.ensureActive() scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取书名") Log.d(bookSource.sourceUrl, "┌获取书名")
BookHelp.formatBookName(analyzeRule.getString(infoRule.name)).let { BookList.formatBookName(analyzeRule.getString(infoRule.name)).let {
if (it.isNotEmpty() && (mCanReName || book.name.isEmpty())) { if (it.isNotEmpty() && (mCanReName || book.name.isEmpty())) {
book.name = it book.name = it
} }
Debug.log(bookSource.bookSourceUrl, "${it}") Log.d(bookSource.sourceUrl, "${it}")
} }
scope.ensureActive() scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取作者") Log.d(bookSource.sourceUrl, "┌获取作者")
BookHelp.formatBookAuthor(analyzeRule.getString(infoRule.author)).let { BookList.formatBookAuthor(analyzeRule.getString(infoRule.author)).let {
if (it.isNotEmpty() && (mCanReName || book.author.isEmpty())) { if (it.isNotEmpty() && (mCanReName || book.author.isEmpty())) {
book.author = it book.author = it
} }
Debug.log(bookSource.bookSourceUrl, "${it}") Log.d(bookSource.sourceUrl, "${it}")
} }
scope.ensureActive() scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取分类") Log.d(bookSource.sourceUrl, "┌获取分类")
try { try {
analyzeRule.getStringList(infoRule.kind) analyzeRule.getStringList(infoRule.type)
?.joinToString(",") ?.joinToString(",")
?.let { ?.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) { } catch (e: Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}") Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
} }
scope.ensureActive() scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取字数") Log.d(bookSource.sourceUrl, "┌获取字数")
try { 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 if (it.isNotEmpty()) book.wordCount = it
} }
Debug.log(bookSource.bookSourceUrl, "${book.wordCount}") Log.d(bookSource.sourceUrl, "${book.wordCount}")
} catch (e: Exception) { } catch (e: Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}") Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
} }
scope.ensureActive() scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取最新章节") Log.d(bookSource.sourceUrl, "┌获取最新章节")
try { try {
analyzeRule.getString(infoRule.lastChapter).let { 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) { } catch (e: Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}") Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
} }
scope.ensureActive() scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取简介") Log.d(bookSource.sourceUrl, "┌获取简介")
try { try {
analyzeRule.getString(infoRule.intro).let { analyzeRule.getString(infoRule.desc).let {
if (it.isNotEmpty()) book.intro = HtmlFormatter.format(it) if (it.isNotEmpty()) book.desc = HtmlFormatter.format(it)
} }
Debug.log(bookSource.bookSourceUrl, "${book.intro}") Log.d(bookSource.sourceUrl, "${book.desc}")
} catch (e: Exception) { } catch (e: Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}") Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
} }
scope.ensureActive() scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取封面链接") Log.d(bookSource.sourceUrl, "┌获取封面链接")
try { try {
analyzeRule.getString(infoRule.coverUrl).let { analyzeRule.getString(infoRule.imgUrl).let {
if (it.isNotEmpty()) book.coverUrl = NetworkUtils.getAbsoluteURL(baseUrl, it) if (it.isNotEmpty()) book.imgUrl = NetworkUtils.getAbsoluteURL(baseUrl, it)
} }
Debug.log(bookSource.bookSourceUrl, "${book.coverUrl}") Log.d(bookSource.sourceUrl, "${book.imgUrl}")
} catch (e: Exception) { } catch (e: Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}") Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
} }
scope.ensureActive() scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取目录链接") Log.d(bookSource.sourceUrl, "┌获取目录链接")
book.tocUrl = analyzeRule.getString(infoRule.tocUrl, isUrl = true) book.chapterUrl = analyzeRule.getString(infoRule.tocUrl, isUrl = true)
if (book.tocUrl.isEmpty()) book.tocUrl = redirectUrl if (book.chapterUrl.isEmpty()) book.chapterUrl = redirectUrl
if (book.tocUrl == redirectUrl) { if (book.chapterUrl == redirectUrl) {
book.tocHtml = body 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 package xyz.fycz.myreader.model.third3.webBook
import io.legado.app.R import android.util.Log
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 xyz.fycz.myreader.model.third3.analyzeRule.AnalyzeRule import xyz.fycz.myreader.model.third3.analyzeRule.AnalyzeRule
import xyz.fycz.myreader.model.third3.analyzeRule.AnalyzeUrl 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.CoroutineScope
import kotlinx.coroutines.ensureActive 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( fun analyzeBookList(
scope: CoroutineScope, scope: CoroutineScope,
bookSource: BookSource, bookSource: BookSource,
variableBook: SearchBook, variableBook: Book,
analyzeUrl: AnalyzeUrl, analyzeUrl: AnalyzeUrl,
baseUrl: String, baseUrl: String,
body: String?, body: String?,
isSearch: Boolean = true, isSearch: Boolean = true,
): ArrayList<SearchBook> { ): ArrayList<Book> {
body ?: throw NoStackTraceException( body ?: throw NoStackTraceException(
appCtx.getString( App.getmContext().getString(
R.string.error_get_web_content, R.string.error_get_web_content,
analyzeUrl.ruleUrl analyzeUrl.ruleUrl
) )
) )
val bookList = ArrayList<SearchBook>() val bookList = ArrayList<Book>()
Debug.log(bookSource.bookSourceUrl, "≡获取成功:${analyzeUrl.ruleUrl}") Log.d(bookSource.sourceUrl, "≡获取成功:${analyzeUrl.ruleUrl}")
Debug.log(bookSource.bookSourceUrl, body, state = 10) Log.d(bookSource.sourceUrl, body)
val analyzeRule = AnalyzeRule(variableBook, bookSource) val analyzeRule = AnalyzeRule(variableBook, bookSource)
analyzeRule.setContent(body).setBaseUrl(baseUrl) analyzeRule.setContent(body).setBaseUrl(baseUrl)
analyzeRule.setRedirectUrl(baseUrl) analyzeRule.setRedirectUrl(baseUrl)
bookSource.bookUrlPattern?.let { bookSource.infoRule.urlPattern?.let {
scope.ensureActive() scope.ensureActive()
if (baseUrl.matches(it.toRegex())) { if (baseUrl.matches(it.toRegex())) {
Debug.log(bookSource.bookSourceUrl, "≡链接为详情页") Log.d(bookSource.sourceUrl, "≡链接为详情页")
getInfoItem( getInfoItem(
scope, bookSource, analyzeRule, analyzeUrl, body, baseUrl, variableBook.variable scope, bookSource, analyzeRule, analyzeUrl, body, baseUrl, variableBook.variable
)?.let { searchBook -> )?.let { searchBook ->
searchBook.infoHtml = body searchBook.putCathe("infoHtml", body)
bookList.add(searchBook) bookList.add(searchBook)
} }
return bookList return bookList
@ -59,11 +57,11 @@ object BookList {
val collections: List<Any> val collections: List<Any>
var reverse = false var reverse = false
val bookListRule: BookListRule = when { val bookListRule: BookListRule = when {
isSearch -> bookSource.getSearchRule() isSearch -> bookSource.searchRule
bookSource.getExploreRule().bookList.isNullOrBlank() -> bookSource.getSearchRule() bookSource.findRule.url.isNullOrBlank() -> bookSource.searchRule
else -> bookSource.getExploreRule() else -> bookSource.findRule
} }
var ruleList: String = bookListRule.bookList ?: "" var ruleList: String = bookListRule.list ?: ""
if (ruleList.startsWith("-")) { if (ruleList.startsWith("-")) {
reverse = true reverse = true
ruleList = ruleList.substring(1) ruleList = ruleList.substring(1)
@ -71,27 +69,27 @@ object BookList {
if (ruleList.startsWith("+")) { if (ruleList.startsWith("+")) {
ruleList = ruleList.substring(1) ruleList = ruleList.substring(1)
} }
Debug.log(bookSource.bookSourceUrl, "┌获取书籍列表") Log.d(bookSource.sourceUrl, "┌获取书籍列表")
collections = analyzeRule.getElements(ruleList) collections = analyzeRule.getElements(ruleList)
scope.ensureActive() scope.ensureActive()
if (collections.isEmpty() && bookSource.bookUrlPattern.isNullOrEmpty()) { if (collections.isEmpty() && bookSource.infoRule.urlPattern.isNullOrEmpty()) {
Debug.log(bookSource.bookSourceUrl, "└列表为空,按详情页解析") Log.d(bookSource.sourceUrl, "└列表为空,按详情页解析")
getInfoItem( getInfoItem(
scope, bookSource, analyzeRule, analyzeUrl, body, baseUrl, variableBook.variable scope, bookSource, analyzeRule, analyzeUrl, body, baseUrl, variableBook.variable
)?.let { searchBook -> )?.let { searchBook ->
searchBook.infoHtml = body searchBook.putCathe("infoHtml", body)
bookList.add(searchBook) bookList.add(searchBook)
} }
} else { } else {
val ruleName = analyzeRule.splitSourceRule(bookListRule.name) 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 ruleAuthor = analyzeRule.splitSourceRule(bookListRule.author)
val ruleCoverUrl = analyzeRule.splitSourceRule(bookListRule.coverUrl) val ruleCoverUrl = analyzeRule.splitSourceRule(bookListRule.imgUrl)
val ruleIntro = analyzeRule.splitSourceRule(bookListRule.intro) val ruleIntro = analyzeRule.splitSourceRule(bookListRule.desc)
val ruleKind = analyzeRule.splitSourceRule(bookListRule.kind) val ruleKind = analyzeRule.splitSourceRule(bookListRule.type)
val ruleLastChapter = analyzeRule.splitSourceRule(bookListRule.lastChapter) val ruleLastChapter = analyzeRule.splitSourceRule(bookListRule.lastChapter)
val ruleWordCount = analyzeRule.splitSourceRule(bookListRule.wordCount) val ruleWordCount = analyzeRule.splitSourceRule(bookListRule.wordCount)
Debug.log(bookSource.bookSourceUrl, "└列表大小:${collections.size}") Log.d(bookSource.sourceUrl, "└列表大小:${collections.size}")
for ((index, item) in collections.withIndex()) { for ((index, item) in collections.withIndex()) {
getSearchItem( getSearchItem(
scope, bookSource, analyzeRule, item, baseUrl, variableBook.variable, scope, bookSource, analyzeRule, item, baseUrl, variableBook.variable,
@ -105,8 +103,8 @@ object BookList {
ruleLastChapter = ruleLastChapter, ruleLastChapter = ruleLastChapter,
ruleWordCount = ruleWordCount ruleWordCount = ruleWordCount
)?.let { searchBook -> )?.let { searchBook ->
if (baseUrl == searchBook.bookUrl) { if (baseUrl == searchBook.infoUrl) {
searchBook.infoHtml = body searchBook.putCathe("infoHtml", body)
} }
bookList.add(searchBook) bookList.add(searchBook)
} }
@ -127,13 +125,14 @@ object BookList {
body: String, body: String,
baseUrl: String, baseUrl: String,
variable: String? variable: String?
): SearchBook? { ): Book? {
val book = Book(variable = variable) val book = Book()
book.bookUrl = analyzeUrl.ruleUrl book.variable = variable
book.origin = bookSource.bookSourceUrl book.infoUrl = analyzeUrl.ruleUrl
book.originName = bookSource.bookSourceName book.source = bookSource.sourceUrl
book.originOrder = bookSource.customOrder //book.originName = bookSource.bookSourceName
book.type = bookSource.bookSourceType //book.originOrder = bookSource.customOrder
//book.type = bookSource.bookSourceType
analyzeRule.book = book analyzeRule.book = book
BookInfo.analyzeBookInfo( BookInfo.analyzeBookInfo(
scope, scope,
@ -146,7 +145,8 @@ object BookList {
false false
) )
if (book.name.isNotBlank()) { if (book.name.isNotBlank()) {
return book.toSearchBook() //return book.toSearchBook()
return book
} }
return null return null
} }
@ -168,76 +168,98 @@ object BookList {
ruleWordCount: List<AnalyzeRule.SourceRule>, ruleWordCount: List<AnalyzeRule.SourceRule>,
ruleIntro: List<AnalyzeRule.SourceRule>, ruleIntro: List<AnalyzeRule.SourceRule>,
ruleLastChapter: List<AnalyzeRule.SourceRule> ruleLastChapter: List<AnalyzeRule.SourceRule>
): SearchBook? { ): Book? {
val searchBook = SearchBook(variable = variable) val searchBook = Book()
searchBook.origin = bookSource.bookSourceUrl searchBook.variable = variable
searchBook.originName = bookSource.bookSourceName searchBook.source = bookSource.sourceUrl
/* searchBook.originName = bookSource.bookSourceName
searchBook.type = bookSource.bookSourceType searchBook.type = bookSource.bookSourceType
searchBook.originOrder = bookSource.customOrder searchBook.originOrder = bookSource.customOrder*/
analyzeRule.book = searchBook analyzeRule.book = searchBook
analyzeRule.setContent(item) analyzeRule.setContent(item)
scope.ensureActive() scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取书名", log) if (log) if (log) Log.d(bookSource.sourceUrl, "┌获取书名")
searchBook.name = BookHelp.formatBookName(analyzeRule.getString(ruleName)) searchBook.name = formatBookName(analyzeRule.getString(ruleName))
Debug.log(bookSource.bookSourceUrl, "${searchBook.name}", log) if (log) Log.d(bookSource.sourceUrl, "${searchBook.name}")
if (searchBook.name.isNotEmpty()) { if (searchBook.name.isNotEmpty()) {
scope.ensureActive() scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取作者", log) if (log) Log.d(bookSource.sourceUrl, "┌获取作者")
searchBook.author = BookHelp.formatBookAuthor(analyzeRule.getString(ruleAuthor)) searchBook.author = formatBookAuthor(analyzeRule.getString(ruleAuthor))
Debug.log(bookSource.bookSourceUrl, "${searchBook.author}", log) if (log) Log.d(bookSource.sourceUrl, "${searchBook.author}")
scope.ensureActive() scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取分类", log) if (log) Log.d(bookSource.sourceUrl, "┌获取分类")
try { try {
searchBook.kind = analyzeRule.getStringList(ruleKind)?.joinToString(",") searchBook.type = analyzeRule.getStringList(ruleKind)?.joinToString(",")
Debug.log(bookSource.bookSourceUrl, "${searchBook.kind}", log) if (log) Log.d(bookSource.sourceUrl, "${searchBook.type}")
} catch (e: Exception) { } catch (e: Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log) if (log) Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
} }
scope.ensureActive() scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取字数", log) if (log) Log.d(bookSource.sourceUrl, "┌获取字数")
try { try {
searchBook.wordCount = wordCountFormat(analyzeRule.getString(ruleWordCount)) //searchBook.wordCount = wordCountFormat(analyzeRule.getString(ruleWordCount))
Debug.log(bookSource.bookSourceUrl, "${searchBook.wordCount}", log) searchBook.wordCount = analyzeRule.getString(ruleWordCount)
if (log) Log.d(bookSource.sourceUrl, "${searchBook.wordCount}")
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log) if (log) Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
} }
scope.ensureActive() scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取最新章节", log) if (log) Log.d(bookSource.sourceUrl, "┌获取最新章节")
try { try {
searchBook.latestChapterTitle = analyzeRule.getString(ruleLastChapter) searchBook.newestChapterTitle = analyzeRule.getString(ruleLastChapter)
Debug.log(bookSource.bookSourceUrl, "${searchBook.latestChapterTitle}", log) if (log) Log.d(bookSource.sourceUrl, "${searchBook.newestChapterTitle}")
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log) if (log) Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
} }
scope.ensureActive() scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取简介", log) if (log) Log.d(bookSource.sourceUrl, "┌获取简介")
try { try {
searchBook.intro = HtmlFormatter.format(analyzeRule.getString(ruleIntro)) searchBook.desc = HtmlFormatter.format(analyzeRule.getString(ruleIntro))
Debug.log(bookSource.bookSourceUrl, "${searchBook.intro}", log) if (log) Log.d(bookSource.sourceUrl, "${searchBook.desc}")
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log) if (log) Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
} }
scope.ensureActive() scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取封面链接", log) if (log) Log.d(bookSource.sourceUrl, "┌获取封面链接")
try { try {
analyzeRule.getString(ruleCoverUrl).let { analyzeRule.getString(ruleCoverUrl).let {
if (it.isNotEmpty()) searchBook.coverUrl = if (it.isNotEmpty()) searchBook.imgUrl =
NetworkUtils.getAbsoluteURL(baseUrl, it) NetworkUtils.getAbsoluteURL(baseUrl, it)
} }
Debug.log(bookSource.bookSourceUrl, "${searchBook.coverUrl}", log) if (log) Log.d(bookSource.sourceUrl, "${searchBook.imgUrl}")
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log) if (log) Log.d(bookSource.sourceUrl, "${e.localizedMessage}")
} }
scope.ensureActive() scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取详情页链接", log) if (log) Log.d(bookSource.sourceUrl, "┌获取详情页链接")
searchBook.bookUrl = analyzeRule.getString(ruleBookUrl, isUrl = true) searchBook.infoUrl = analyzeRule.getString(ruleBookUrl, isUrl = true)
if (searchBook.bookUrl.isEmpty()) { if (searchBook.infoUrl.isEmpty()) {
searchBook.bookUrl = baseUrl searchBook.infoUrl = baseUrl
} }
Debug.log(bookSource.bookSourceUrl, "${searchBook.bookUrl}", log) if (log) Log.d(bookSource.sourceUrl, "${searchBook.infoUrl}")
return searchBook return searchBook
} }
return null 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 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.http.StrResponse
import xyz.fycz.myreader.model.third3.analyzeRule.AnalyzeUrl import xyz.fycz.myreader.model.third3.analyzeRule.AnalyzeUrl
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import xyz.fycz.myreader.greendao.entity.Book 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.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 import kotlin.coroutines.CoroutineContext
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
@ -21,7 +26,7 @@ object WebBook {
key: String, key: String,
page: Int? = 1, page: Int? = 1,
context: CoroutineContext = Dispatchers.IO, context: CoroutineContext = Dispatchers.IO,
): Coroutine<ArrayList<SearchBook>> { ): Coroutine<ArrayList<Book>> {
return Coroutine.async(scope, context) { return Coroutine.async(scope, context) {
searchBookAwait(scope, bookSource, key, page) searchBookAwait(scope, bookSource, key, page)
} }
@ -32,25 +37,25 @@ object WebBook {
bookSource: BookSource, bookSource: BookSource,
key: String, key: String,
page: Int? = 1, page: Int? = 1,
): ArrayList<SearchBook> { ): ArrayList<Book> {
val variableBook = SearchBook() val variableBook = Book()
bookSource.searchUrl?.let { searchUrl -> bookSource.searchRule.searchUrl?.let { searchUrl ->
val analyzeUrl = AnalyzeUrl( val analyzeUrl = AnalyzeUrl(
mUrl = searchUrl, mUrl = searchUrl,
key = key, key = key,
page = page, page = page,
baseUrl = bookSource.bookSourceUrl, baseUrl = bookSource.sourceUrl,
headerMapF = bookSource.getHeaderMap(true), //headerMapF = bookSource.getHeaderMap(true),
source = bookSource, source = bookSource,
ruleData = variableBook, ruleData = variableBook,
) )
var res = analyzeUrl.getStrResponseAwait() var res = analyzeUrl.getStrResponseAwait()
//检测书源是否已登录 //检测书源是否已登录
bookSource.loginCheckJs?.let { checkJs -> /*bookSource.loginCheckJs?.let { checkJs ->
if (checkJs.isNotBlank()) { if (checkJs.isNotBlank()) {
res = analyzeUrl.evalJS(checkJs, res) as StrResponse res = analyzeUrl.evalJS(checkJs, res) as StrResponse
} }
} }*/
return BookList.analyzeBookList( return BookList.analyzeBookList(
scope, scope,
bookSource, bookSource,
@ -73,7 +78,7 @@ object WebBook {
url: String, url: String,
page: Int? = 1, page: Int? = 1,
context: CoroutineContext = Dispatchers.IO, context: CoroutineContext = Dispatchers.IO,
): Coroutine<List<SearchBook>> { ): Coroutine<List<Book>> {
return Coroutine.async(scope, context) { return Coroutine.async(scope, context) {
exploreBookAwait(scope, bookSource, url, page) exploreBookAwait(scope, bookSource, url, page)
} }
@ -84,23 +89,23 @@ object WebBook {
bookSource: BookSource, bookSource: BookSource,
url: String, url: String,
page: Int? = 1, page: Int? = 1,
): ArrayList<SearchBook> { ): ArrayList<Book> {
val variableBook = SearchBook() val variableBook = Book()
val analyzeUrl = AnalyzeUrl( val analyzeUrl = AnalyzeUrl(
mUrl = url, mUrl = url,
page = page, page = page,
baseUrl = bookSource.bookSourceUrl, baseUrl = bookSource.sourceUrl,
source = bookSource, source = bookSource,
ruleData = variableBook, ruleData = variableBook,
headerMapF = bookSource.getHeaderMap(true) //headerMapF = bookSource.getHeaderMap(true)
) )
var res = analyzeUrl.getStrResponseAwait() var res = analyzeUrl.getStrResponseAwait()
//检测书源是否已登录 //检测书源是否已登录
bookSource.loginCheckJs?.let { checkJs -> /*bookSource.loginCheckJs?.let { checkJs ->
if (checkJs.isNotBlank()) { if (checkJs.isNotBlank()) {
res = analyzeUrl.evalJS(checkJs, result = res) as StrResponse res = analyzeUrl.evalJS(checkJs, result = res) as StrResponse
} }
} }*/
return BookList.analyzeBookList( return BookList.analyzeBookList(
scope, scope,
bookSource, bookSource,
@ -133,37 +138,37 @@ object WebBook {
book: Book, book: Book,
canReName: Boolean = true, canReName: Boolean = true,
): Book { ): Book {
book.type = bookSource.bookSourceType //book.type = bookSource.bookSourceType
if (!book.infoHtml.isNullOrEmpty()) { if (!book.getCathe("infoHtml").isNullOrEmpty()) {
BookInfo.analyzeBookInfo( BookInfo.analyzeBookInfo(
scope, scope,
bookSource, bookSource,
book, book,
book.bookUrl, book.infoUrl,
book.bookUrl, book.infoUrl,
book.infoHtml, book.getCathe("infoHtml"),
canReName canReName
) )
} else { } else {
val analyzeUrl = AnalyzeUrl( val analyzeUrl = AnalyzeUrl(
mUrl = book.bookUrl, mUrl = book.infoUrl,
baseUrl = bookSource.bookSourceUrl, baseUrl = bookSource.sourceUrl,
source = bookSource, source = bookSource,
ruleData = book, ruleData = book,
headerMapF = bookSource.getHeaderMap(true) //headerMapF = bookSource.getHeaderMap(true)
) )
var res = analyzeUrl.getStrResponseAwait() var res = analyzeUrl.getStrResponseAwait()
//检测书源是否已登录 //检测书源是否已登录
bookSource.loginCheckJs?.let { checkJs -> /*bookSource.loginCheckJs?.let { checkJs ->
if (checkJs.isNotBlank()) { if (checkJs.isNotBlank()) {
res = analyzeUrl.evalJS(checkJs, result = res) as StrResponse res = analyzeUrl.evalJS(checkJs, result = res) as StrResponse
} }
} }*/
BookInfo.analyzeBookInfo( BookInfo.analyzeBookInfo(
scope, scope,
bookSource, bookSource,
book, book,
book.bookUrl, book.infoUrl,
res.url, res.url,
res.body, res.body,
canReName canReName
@ -180,7 +185,7 @@ object WebBook {
bookSource: BookSource, bookSource: BookSource,
book: Book, book: Book,
context: CoroutineContext = Dispatchers.IO context: CoroutineContext = Dispatchers.IO
): Coroutine<List<BookChapter>> { ): Coroutine<List<Chapter>> {
return Coroutine.async(scope, context) { return Coroutine.async(scope, context) {
getChapterListAwait(scope, bookSource, book) getChapterListAwait(scope, bookSource, book)
} }
@ -190,39 +195,38 @@ object WebBook {
scope: CoroutineScope, scope: CoroutineScope,
bookSource: BookSource, bookSource: BookSource,
book: Book, book: Book,
): List<BookChapter> { ): List<Chapter> {
book.type = bookSource.bookSourceType //book.type = bookSource.bookSourceType
return if (book.bookUrl == book.tocUrl && !book.tocHtml.isNullOrEmpty()) { return if (book.infoUrl == book.chapterUrl && !book.getCathe("tocHtml").isNullOrEmpty()) {
BookChapterList.analyzeChapterList( BookChapterList.analyzeChapterList(
scope, scope,
bookSource, bookSource,
book, book,
book.tocUrl, book.chapterUrl,
book.tocUrl, book.chapterUrl,
book.tocHtml book.getCathe("tocHtml"),
) )
} else { } else {
val analyzeUrl = AnalyzeUrl( val analyzeUrl = AnalyzeUrl(
mUrl = book.tocUrl, mUrl = book.chapterUrl,
baseUrl = book.bookUrl, baseUrl = book.infoUrl,
source = bookSource, source = bookSource,
ruleData = book, ruleData = book,
headerMapF = bookSource.getHeaderMap(true)
) )
var res = analyzeUrl.getStrResponseAwait() var res = analyzeUrl.getStrResponseAwait()
//检测书源是否已登录 //检测书源是否已登录
bookSource.loginCheckJs?.let { checkJs -> /*bookSource.loginCheckJs?.let { checkJs ->
if (checkJs.isNotBlank()) { if (checkJs.isNotBlank()) {
res = analyzeUrl.evalJS(checkJs, result = res) as StrResponse res = analyzeUrl.evalJS(checkJs, result = res) as StrResponse
} }
} }*/
BookChapterList.analyzeChapterList( BookChapterList.analyzeChapterList(
scope, scope,
bookSource, bookSource,
book, book,
book.tocUrl, book.chapterUrl,
res.url, res.url,
res.body res.body,
) )
} }
} }
@ -234,7 +238,7 @@ object WebBook {
scope: CoroutineScope, scope: CoroutineScope,
bookSource: BookSource, bookSource: BookSource,
book: Book, book: Book,
bookChapter: BookChapter, bookChapter: Chapter,
nextChapterUrl: String? = null, nextChapterUrl: String? = null,
context: CoroutineContext = Dispatchers.IO context: CoroutineContext = Dispatchers.IO
): Coroutine<String> { ): Coroutine<String> {
@ -247,49 +251,50 @@ object WebBook {
scope: CoroutineScope, scope: CoroutineScope,
bookSource: BookSource, bookSource: BookSource,
book: Book, book: Book,
bookChapter: BookChapter, bookChapter: Chapter,
nextChapterUrl: String? = null nextChapterUrl: String? = null,
): String { ): String {
if (bookSource.getContentRule().content.isNullOrEmpty()) { if (bookSource.contentRule.content.isNullOrEmpty()) {
Debug.log(bookSource.bookSourceUrl, "⇒正文规则为空,使用章节链接:${bookChapter.url}") Log.d(bookSource.sourceUrl, "⇒正文规则为空,使用章节链接:${bookChapter.url}")
return 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( BookContent.analyzeContent(
scope, scope,
bookSource, bookSource,
book, book,
bookChapter, bookChapter,
bookChapter.getAbsoluteURL(), absoluteUrl,
bookChapter.getAbsoluteURL(), absoluteUrl,
book.tocHtml, book.getCathe("tocHtml"),
nextChapterUrl nextChapterUrl
) )
} else { } else {
val analyzeUrl = AnalyzeUrl( val analyzeUrl = AnalyzeUrl(
mUrl = bookChapter.getAbsoluteURL(), mUrl = absoluteUrl,
baseUrl = book.tocUrl, baseUrl = book.chapterUrl,
source = bookSource, source = bookSource,
ruleData = book, ruleData = book,
chapter = bookChapter, 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()) { if (checkJs.isNotBlank()) {
res = analyzeUrl.evalJS(checkJs, result = res) as StrResponse res = analyzeUrl.evalJS(checkJs, result = res) as StrResponse
} }
} }*/
BookContent.analyzeContent( BookContent.analyzeContent(
scope, scope,
bookSource, bookSource,
book, book,
bookChapter, bookChapter,
bookChapter.getAbsoluteURL(), absoluteUrl,
res.url, res.url,
res.body, res.body,
nextChapterUrl nextChapterUrl
@ -317,7 +322,7 @@ object WebBook {
scope: CoroutineScope, scope: CoroutineScope,
bookSources: List<BookSource>, bookSources: List<BookSource>,
name: String, name: String,
author: String author: String,
): Pair<BookSource, Book>? { ): Pair<BookSource, Book>? {
bookSources.forEach { source -> bookSources.forEach { source ->
kotlin.runCatching { kotlin.runCatching {
@ -326,8 +331,8 @@ object WebBook {
it.name == name && it.author == author it.name == name && it.author == author
}?.let { searchBook -> }?.let { searchBook ->
if (!scope.isActive) return null if (!scope.isActive) return null
var book = searchBook.toBook() var book = searchBook
if (book.tocUrl.isBlank()) { if (book.chapterUrl.isBlank()) {
book = getBookInfoAwait(scope, source, book) book = getBookInfoAwait(scope, source, book)
} }
return Pair(source, book) return Pair(source, book)

@ -120,6 +120,9 @@ public class SourceEditActivity extends BaseActivity {
case APPCONST.THIRD_SOURCE: case APPCONST.THIRD_SOURCE:
sourceType = 3; sourceType = 3;
break; break;
case APPCONST.THIRD_3_SOURCE:
sourceType = 4;
break;
} }
binding.sSourceType.setSelection(sourceType); binding.sSourceType.setSelection(sourceType);
} }
@ -286,6 +289,9 @@ public class SourceEditActivity extends BaseActivity {
case 3: case 3:
sourceType = APPCONST.THIRD_SOURCE; sourceType = APPCONST.THIRD_SOURCE;
break; break;
case 4:
sourceType = APPCONST.THIRD_3_SOURCE;
break;
} }
source.setSourceType(sourceType); source.setSourceType(sourceType);
source.setSearchRule(entityUtil.getSearchRule(searchEntities)); 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.model.third2.content.BookList;
import xyz.fycz.myreader.util.utils.OkHttpUtils; import xyz.fycz.myreader.util.utils.OkHttpUtils;
import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; 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.ThirdCrawler;
import xyz.fycz.myreader.webapi.crawler.source.find.ThirdFindCrawler; import xyz.fycz.myreader.webapi.crawler.source.find.ThirdFindCrawler;
@ -43,6 +44,9 @@ public class ThirdSourceApi {
* @return * @return
*/ */
protected static Observable<ConMVMap<SearchBookBean, Book>> searchByTC(String key, final ThirdCrawler rc) { protected static Observable<ConMVMap<SearchBookBean, Book>> searchByTC(String key, final ThirdCrawler rc) {
if (rc instanceof Third3Crawler) {
return Third3SourceApi.INSTANCE.searchByT3C(key, (Third3Crawler) rc);
}
try { try {
Map<String, String> headers = rc.getHeaders(); Map<String, String> headers = rc.getHeaders();
headers.putAll(getCookies(rc.getNameSpace())); headers.putAll(getCookies(rc.getNameSpace()));
@ -61,6 +65,9 @@ public class ThirdSourceApi {
} }
protected static Observable<Book> getBookInfoByTC(Book book, ThirdCrawler rc) { protected static Observable<Book> getBookInfoByTC(Book book, ThirdCrawler rc) {
if (rc instanceof Third3Crawler) {
return Third3SourceApi.INSTANCE.getBookInfoByT3C(book, (Third3Crawler) rc);
}
BookSource source = rc.getSource(); BookSource source = rc.getSource();
BookInfo bookInfo = new BookInfo(source.getSourceUrl(), source.getSourceName(), source); BookInfo bookInfo = new BookInfo(source.getSourceUrl(), source.getSourceName(), source);
if (!TextUtils.isEmpty(book.getCathe("BookInfoHtml"))) { if (!TextUtils.isEmpty(book.getCathe("BookInfoHtml"))) {
@ -79,6 +86,9 @@ public class ThirdSourceApi {
} }
protected static Observable<List<Chapter>> getBookChaptersByTC(Book book, ThirdCrawler rc) { 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(); BookSource source = rc.getSource();
Map<String, String> headers = rc.getHeaders(); Map<String, String> headers = rc.getHeaders();
headers.putAll(getCookies(rc.getNameSpace())); headers.putAll(getCookies(rc.getNameSpace()));
@ -113,6 +123,9 @@ public class ThirdSourceApi {
} }
protected static Observable<String> getChapterContentByTC(Chapter chapter, Book book, ThirdCrawler rc) { 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(); BookSource source = rc.getSource();
Map<String, String> headers = rc.getHeaders(); Map<String, String> headers = rc.getHeaders();
headers.putAll(getCookies(rc.getNameSpace())); 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.read.FYReadCrawler;
import xyz.fycz.myreader.webapi.crawler.source.JsonPathCrawler; import xyz.fycz.myreader.webapi.crawler.source.JsonPathCrawler;
import xyz.fycz.myreader.webapi.crawler.source.MatcherCrawler; 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.ThirdCrawler;
import xyz.fycz.myreader.webapi.crawler.source.XpathCrawler; 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.JSON_PATH;
import static xyz.fycz.myreader.common.APPCONST.MATCHER; 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.THIRD_SOURCE;
import static xyz.fycz.myreader.common.APPCONST.XPATH; import static xyz.fycz.myreader.common.APPCONST.XPATH;
@ -178,6 +180,7 @@ public class ReadCrawlerUtil {
public static ReadCrawler getReadCrawler(BookSource source) { public static ReadCrawler getReadCrawler(BookSource source) {
return getReadCrawler(source, false); return getReadCrawler(source, false);
} }
public static ReadCrawler getReadCrawler(BookSource source, boolean isInfo) { public static ReadCrawler getReadCrawler(BookSource source, boolean isInfo) {
try { try {
if (StringHelper.isEmpty(source.getSourceEName())) { if (StringHelper.isEmpty(source.getSourceEName())) {
@ -196,6 +199,8 @@ public class ReadCrawlerUtil {
break; break;
case THIRD_SOURCE: case THIRD_SOURCE:
return new ThirdCrawler(source); return new ThirdCrawler(source);
case THIRD_3_SOURCE:
return new Third3Crawler(source);
} }
if (source.getSearchRule().isRelatedWithInfo() || isInfo) { if (source.getSearchRule().isRelatedWithInfo() || isInfo) {
return crawler; 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 * @date 2021/5/14 10:55
*/ */
public class ThirdCrawler extends BaseReadCrawler implements BookInfoCrawler { public class ThirdCrawler extends BaseReadCrawler implements BookInfoCrawler {
private BookSource source; private final BookSource source;
public ThirdCrawler(BookSource source) { public ThirdCrawler(BookSource source) {
this.source = source; this.source = source;

@ -521,6 +521,21 @@
<string name="re_get_email_code">重新发送%s</string> <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"> <string-array name="reset_screen_time">
<item>跟随系统</item> <item>跟随系统</item>
<item>常亮</item> <item>常亮</item>
@ -602,7 +617,8 @@
<item>Matcher</item> <item>Matcher</item>
<item>Xpath</item> <item>Xpath</item>
<item>JsonPath</item> <item>JsonPath</item>
<item>第三方</item> <item>第三方-2.0</item>
<item>第三方-3.0</item>
</string-array> </string-array>
<string-array name="select_folder"> <string-array name="select_folder">

Loading…
Cancel
Save