parent
db31cf2500
commit
49acebd2f9
@ -0,0 +1,85 @@ |
|||||||
|
package xyz.fycz.myreader.entity; |
||||||
|
|
||||||
|
import android.text.TextUtils; |
||||||
|
import android.util.Log; |
||||||
|
|
||||||
|
import org.mozilla.universalchardet.UniversalDetector; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.IOException; |
||||||
|
import java.nio.charset.Charset; |
||||||
|
import java.util.Arrays; |
||||||
|
|
||||||
|
import okhttp3.MediaType; |
||||||
|
import okhttp3.Response; |
||||||
|
import okhttp3.ResponseBody; |
||||||
|
import xyz.fycz.myreader.common.APPCONST; |
||||||
|
import xyz.fycz.myreader.util.help.UTF8BOMFighter; |
||||||
|
import xyz.fycz.myreader.util.utils.EncodingDetect; |
||||||
|
import xyz.fycz.myreader.util.utils.FileUtils; |
||||||
|
import xyz.fycz.myreader.util.utils.StringUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author fengyue |
||||||
|
* @date 2021/5/14 12:05 |
||||||
|
*/ |
||||||
|
public class StrResponse { |
||||||
|
private String encodeType; |
||||||
|
private Response response; |
||||||
|
private String body; |
||||||
|
|
||||||
|
public String getEncodeType() { |
||||||
|
return encodeType; |
||||||
|
} |
||||||
|
|
||||||
|
public void setEncodeType(String encodeType) { |
||||||
|
this.encodeType = encodeType; |
||||||
|
} |
||||||
|
|
||||||
|
public Response getResponse() { |
||||||
|
return response; |
||||||
|
} |
||||||
|
|
||||||
|
public void setResponse(Response response) { |
||||||
|
this.response = response; |
||||||
|
} |
||||||
|
|
||||||
|
public String body() { |
||||||
|
if (body == null) { |
||||||
|
ResponseBody body = response.body(); |
||||||
|
if (body != null) { |
||||||
|
try { |
||||||
|
byte[] responseBytes = UTF8BOMFighter.removeUTF8BOM(body.bytes()); |
||||||
|
if (!TextUtils.isEmpty(encodeType)) { |
||||||
|
try { |
||||||
|
this.body = new String((responseBytes), Charset.forName(encodeType)); |
||||||
|
Log.d("Http: read finish", this.body); |
||||||
|
return this.body; |
||||||
|
} catch (Exception ignored) { |
||||||
|
} |
||||||
|
} |
||||||
|
String charsetStr; |
||||||
|
MediaType mediaType = body.contentType(); |
||||||
|
//根据http头判断
|
||||||
|
if (mediaType != null) { |
||||||
|
Charset charset = mediaType.charset(); |
||||||
|
if (charset != null) { |
||||||
|
this.body = new String((responseBytes), charset); |
||||||
|
Log.d("Http: read finish", this.body); |
||||||
|
return this.body; |
||||||
|
} |
||||||
|
} |
||||||
|
//根据内容判断
|
||||||
|
charsetStr = EncodingDetect.getEncodeInHtml(responseBytes); |
||||||
|
this.body = new String(responseBytes, Charset.forName(charsetStr)); |
||||||
|
Log.d("Http: read finish", this.body); |
||||||
|
return this.body; |
||||||
|
} catch (IOException e) { |
||||||
|
e.printStackTrace(); |
||||||
|
this.body = ""; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return this.body; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
package xyz.fycz.myreader.entity; |
||||||
|
|
||||||
|
import java.util.LinkedHashSet; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import xyz.fycz.myreader.greendao.entity.Chapter; |
||||||
|
|
||||||
|
public class WebChapterBean { |
||||||
|
private String url; |
||||||
|
|
||||||
|
private List<Chapter> data; |
||||||
|
|
||||||
|
private LinkedHashSet<String> nextUrlList; |
||||||
|
|
||||||
|
public WebChapterBean(String url) { |
||||||
|
this.url = url; |
||||||
|
} |
||||||
|
|
||||||
|
public WebChapterBean(List<Chapter> data, LinkedHashSet<String> nextUrlList) { |
||||||
|
this.data = data; |
||||||
|
this.nextUrlList = nextUrlList; |
||||||
|
} |
||||||
|
|
||||||
|
public List<Chapter> getData() { |
||||||
|
return data; |
||||||
|
} |
||||||
|
|
||||||
|
public void setData(List<Chapter> data) { |
||||||
|
this.data = data; |
||||||
|
} |
||||||
|
|
||||||
|
public LinkedHashSet<String> getNextUrlList() { |
||||||
|
return nextUrlList; |
||||||
|
} |
||||||
|
|
||||||
|
public String getUrl() { |
||||||
|
return url; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean noData() { |
||||||
|
return data == null; |
||||||
|
} |
||||||
|
} |
@ -1,8 +0,0 @@ |
|||||||
package xyz.fycz.myreader.model.sourceAnalyzer; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author fengyue |
|
||||||
* @date 2021/5/14 9:13 |
|
||||||
*/ |
|
||||||
public class ThirdAnalyzer { |
|
||||||
} |
|
@ -1,4 +1,4 @@ |
|||||||
package xyz.fycz.myreader.model.analyzeRule; |
package xyz.fycz.myreader.model.third.analyzeRule; |
||||||
|
|
||||||
import android.text.TextUtils; |
import android.text.TextUtils; |
||||||
|
|
@ -1,4 +1,4 @@ |
|||||||
package xyz.fycz.myreader.model.analyzeRule; |
package xyz.fycz.myreader.model.third.analyzeRule; |
||||||
|
|
||||||
import android.text.TextUtils; |
import android.text.TextUtils; |
||||||
|
|
@ -0,0 +1,248 @@ |
|||||||
|
package xyz.fycz.myreader.model.third.analyzeRule; |
||||||
|
|
||||||
|
import android.os.Build; |
||||||
|
import android.text.TextUtils; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Objects; |
||||||
|
import java.util.regex.Matcher; |
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
import xyz.fycz.myreader.greendao.entity.Book; |
||||||
|
import xyz.fycz.myreader.greendao.entity.rule.BookSource; |
||||||
|
import xyz.fycz.myreader.util.utils.NetworkUtils; |
||||||
|
import xyz.fycz.myreader.util.utils.StringUtils; |
||||||
|
|
||||||
|
import static android.text.TextUtils.isEmpty; |
||||||
|
|
||||||
|
public class AnalyzeByRegex { |
||||||
|
|
||||||
|
// 纯java模式正则表达式获取书籍详情信息
|
||||||
|
public static void getInfoOfRegex(String res, String[] regs, int index, |
||||||
|
Book book, AnalyzeRule analyzer, BookSource source, String baseUrl, String tag) throws Exception { |
||||||
|
Matcher resM = Pattern.compile(regs[index]).matcher(res); |
||||||
|
// 判断规则是否有效,当搜索列表规则无效时跳过详情页处理
|
||||||
|
if (!resM.find()) { |
||||||
|
book.setChapterUrl(baseUrl); |
||||||
|
return; |
||||||
|
} |
||||||
|
// 判断索引的规则是最后一个规则
|
||||||
|
if (index + 1 == regs.length) { |
||||||
|
// 获取规则列表
|
||||||
|
HashMap<String, String> ruleMap = new HashMap<>(); |
||||||
|
ruleMap.put("BookName", source.getInfoRule().getName()); |
||||||
|
ruleMap.put("BookAuthor", source.getInfoRule().getAuthor()); |
||||||
|
ruleMap.put("BookKind", source.getInfoRule().getType()); |
||||||
|
ruleMap.put("LastChapter", source.getInfoRule().getLastChapter()); |
||||||
|
ruleMap.put("Introduce", source.getInfoRule().getDesc()); |
||||||
|
ruleMap.put("CoverUrl", source.getInfoRule().getImgUrl()); |
||||||
|
ruleMap.put("ChapterUrl", source.getInfoRule().getTocUrl()); |
||||||
|
// 分离规则参数
|
||||||
|
List<String> ruleName = new ArrayList<>(); |
||||||
|
List<List<String>> ruleParams = new ArrayList<>(); // 创建规则参数容器
|
||||||
|
List<List<Integer>> ruleTypes = new ArrayList<>(); // 创建规则类型容器
|
||||||
|
List<Boolean> hasVarParams = new ArrayList<>(); // 创建put&get标志容器
|
||||||
|
for (String key : ruleMap.keySet()) { |
||||||
|
String val = ruleMap.get(key); |
||||||
|
ruleName.add(key); |
||||||
|
hasVarParams.add(!TextUtils.isEmpty(val) && (val.contains("@put") || val.contains("@get"))); |
||||||
|
List<String> ruleParam = new ArrayList<>(); |
||||||
|
List<Integer> ruleType = new ArrayList<>(); |
||||||
|
AnalyzeByRegex.splitRegexRule(val, ruleParam, ruleType); |
||||||
|
ruleParams.add(ruleParam); |
||||||
|
ruleTypes.add(ruleType); |
||||||
|
} |
||||||
|
// 提取规则内容
|
||||||
|
HashMap<String, String> ruleVal = new HashMap<>(); |
||||||
|
StringBuilder infoVal = new StringBuilder(); |
||||||
|
for (int i = ruleParams.size(); i-- > 0; ) { |
||||||
|
List<String> ruleParam = ruleParams.get(i); |
||||||
|
List<Integer> ruleType = ruleTypes.get(i); |
||||||
|
infoVal.setLength(0); |
||||||
|
for (int j = ruleParam.size(); j-- > 0; ) { |
||||||
|
int regType = ruleType.get(j); |
||||||
|
if (regType > 0) { |
||||||
|
infoVal.insert(0, resM.group(regType)); |
||||||
|
} else if (regType < 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
||||||
|
infoVal.insert(0, resM.group(ruleParam.get(j))); |
||||||
|
} else { |
||||||
|
infoVal.insert(0, ruleParam.get(j)); |
||||||
|
} |
||||||
|
} |
||||||
|
ruleVal.put(ruleName.get(i), hasVarParams.get(i) ? checkKeys(infoVal.toString(), analyzer) : infoVal.toString()); |
||||||
|
} |
||||||
|
// 保存详情信息
|
||||||
|
if (!isEmpty(ruleVal.get("BookName"))) |
||||||
|
book.setName(StringUtils.formatHtml(ruleVal.get("BookName"))); |
||||||
|
if (!isEmpty(ruleVal.get("BookAuthor"))) |
||||||
|
book.setAuthor(StringUtils.formatHtml(ruleVal.get("BookAuthor"))); |
||||||
|
if (!isEmpty(ruleVal.get("LastChapter"))) book.setNewestChapterTitle(ruleVal.get("LastChapter")); |
||||||
|
if (!isEmpty(ruleVal.get("Introduce"))) |
||||||
|
book.setDesc(StringUtils.formatHtml(ruleVal.get("Introduce"))); |
||||||
|
if (!isEmpty(ruleVal.get("CoverUrl"))) book.setImgUrl(ruleVal.get("CoverUrl")); |
||||||
|
if (!isEmpty(ruleVal.get("ChapterUrl"))) book.setChapterUrl(NetworkUtils.getAbsoluteURL(baseUrl, ruleVal.get("ChapterUrl"))); |
||||||
|
else book.setChapterUrl(baseUrl); |
||||||
|
} else { |
||||||
|
StringBuilder result = new StringBuilder(); |
||||||
|
do { |
||||||
|
result.append(resM.group()); |
||||||
|
} while (resM.find()); |
||||||
|
getInfoOfRegex(result.toString(), regs, ++index, book, analyzer, source, baseUrl, tag); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 正则表达式解析规则数据的通用方法(暂未使用,技术储备型代码)
|
||||||
|
public static void getInfoByRegex(String res, String[] regList, int regIndex, |
||||||
|
HashMap<String, String> ruleMap, final List<HashMap<String, String>> ruleVals) throws Exception { |
||||||
|
Matcher resM = Pattern.compile(regList[regIndex]).matcher(res); |
||||||
|
// 判断规则是否有效
|
||||||
|
if (!resM.find()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
// 判断索引规则是否为最后一个
|
||||||
|
if (regIndex + 1 == regList.length) { |
||||||
|
// 分离规则参数
|
||||||
|
List<String> ruleName = new ArrayList<>(); |
||||||
|
List<List<String>> ruleParams = new ArrayList<>(); // 创建规则参数容器
|
||||||
|
List<List<Integer>> ruleTypes = new ArrayList<>(); // 创建规则类型容器
|
||||||
|
List<Boolean> hasVarParams = new ArrayList<>(); // 创建put&get标志容器
|
||||||
|
for (String key : ruleMap.keySet()) { |
||||||
|
String val = ruleMap.get(key); |
||||||
|
ruleName.add(key); |
||||||
|
hasVarParams.add(val.contains("@put") || val.contains("@get")); |
||||||
|
List<String> ruleParam = new ArrayList<>(); |
||||||
|
List<Integer> ruleType = new ArrayList<>(); |
||||||
|
splitRegexRule(val, ruleParam, ruleType); |
||||||
|
ruleParams.add(ruleParam); |
||||||
|
ruleTypes.add(ruleType); |
||||||
|
} |
||||||
|
// 提取规则结果
|
||||||
|
do { |
||||||
|
HashMap<String, String> ruleVal = new HashMap<>(); |
||||||
|
StringBuilder infoVal = new StringBuilder(); |
||||||
|
for (int i = ruleParams.size(); i-- > 0; ) { |
||||||
|
List<String> ruleParam = ruleParams.get(i); |
||||||
|
List<Integer> ruleType = ruleTypes.get(i); |
||||||
|
infoVal.setLength(0); |
||||||
|
for (int j = ruleParam.size(); j-- > 0; ) { |
||||||
|
int regType = ruleType.get(j); |
||||||
|
if (regType > 0) { |
||||||
|
if (j == 0 && Objects.equals(ruleName.get(0), "ruleChapterName")) { |
||||||
|
infoVal.insert(0, resM.group(regType) == null ? "" : "\uD83D\uDD12"); |
||||||
|
} else { |
||||||
|
infoVal.insert(0, resM.group(regType)); |
||||||
|
} |
||||||
|
} else if (regType < 0) { |
||||||
|
if (j == 0 && Objects.equals(ruleName.get(0), "ruleChapterName")) { |
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
||||||
|
infoVal.insert(0, resM.group(ruleParam.get(j)) == null ? "" : "\uD83D\uDD12"); |
||||||
|
} |
||||||
|
} else { |
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
||||||
|
infoVal.insert(0, resM.group(ruleParam.get(j))); |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
infoVal.insert(0, ruleParam.get(j)); |
||||||
|
} |
||||||
|
} |
||||||
|
ruleVal.put(ruleName.get(i), infoVal.toString()); |
||||||
|
} |
||||||
|
ruleVals.add(ruleVal); |
||||||
|
} while (resM.find()); |
||||||
|
} else { |
||||||
|
StringBuilder result = new StringBuilder(); |
||||||
|
do { |
||||||
|
result.append(resM.group(0)); |
||||||
|
} while (resM.find()); |
||||||
|
getInfoByRegex(result.toString(), regList, ++regIndex, ruleMap, ruleVals); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 拆分正则表达式替换规则(如:$\d{1,2}或${name}) /*注意:千万别用正则表达式拆分字符串,效率太低了!*/
|
||||||
|
public static void splitRegexRule(String str, final List<String> ruleParam, final List<Integer> ruleType) throws Exception { |
||||||
|
if (TextUtils.isEmpty(str)) { |
||||||
|
ruleParam.add(""); |
||||||
|
ruleType.add(0); |
||||||
|
return; |
||||||
|
} |
||||||
|
int index = 0, start = 0, len = str.length(); |
||||||
|
while (index < len) { |
||||||
|
if (str.charAt(index) == '$') { |
||||||
|
if (str.charAt(index + 1) == '{') { |
||||||
|
if (index > start) { |
||||||
|
ruleParam.add(str.substring(start, index)); |
||||||
|
ruleType.add(0); |
||||||
|
start = index; |
||||||
|
} |
||||||
|
for (index += 2; index < len; index++) { |
||||||
|
if (str.charAt(index) == '}') { |
||||||
|
ruleParam.add(str.substring(start + 2, index)); |
||||||
|
ruleType.add(-1); |
||||||
|
start = ++index; |
||||||
|
break; |
||||||
|
} else if (str.charAt(index) == '$' || str.charAt(index) == '@') { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} else if ((str.charAt(index + 1) >= '0') && (str.charAt(index + 1) <= '9')) { |
||||||
|
if (index > start) { |
||||||
|
ruleParam.add(str.substring(start, index)); |
||||||
|
ruleType.add(0); |
||||||
|
start = index; |
||||||
|
} |
||||||
|
if ((index + 2 < len) && (str.charAt(index + 2) >= '0') && (str.charAt(index + 2) <= '9')) { |
||||||
|
ruleParam.add(str.substring(start, index + 3)); |
||||||
|
ruleType.add(string2Int(ruleParam.get(ruleParam.size() - 1))); |
||||||
|
start = index += 3; |
||||||
|
} else { |
||||||
|
ruleParam.add(str.substring(start, index + 2)); |
||||||
|
ruleType.add(string2Int(ruleParam.get(ruleParam.size() - 1))); |
||||||
|
start = index += 2; |
||||||
|
} |
||||||
|
} else { |
||||||
|
++index; |
||||||
|
} |
||||||
|
} else { |
||||||
|
++index; |
||||||
|
} |
||||||
|
} |
||||||
|
if (index > start) { |
||||||
|
ruleParam.add(str.substring(start, index)); |
||||||
|
ruleType.add(0); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 存取字符串中的put&get参数
|
||||||
|
public static String checkKeys(String str, AnalyzeRule analyzer) throws Exception { |
||||||
|
if (str.contains("@put:{")) { |
||||||
|
Matcher putMatcher = Pattern.compile("@put:\\{([^,]*):([^\\}]*)\\}").matcher(str); |
||||||
|
while (putMatcher.find()) { |
||||||
|
str = str.replace(putMatcher.group(0), ""); |
||||||
|
analyzer.put(putMatcher.group(1), putMatcher.group(2)); |
||||||
|
} |
||||||
|
} |
||||||
|
if (str.contains("@get:{")) { |
||||||
|
Matcher getMatcher = Pattern.compile("@get:\\{([^\\}]*)\\}").matcher(str); |
||||||
|
while (getMatcher.find()) { |
||||||
|
str = str.replace(getMatcher.group(), analyzer.get(getMatcher.group(1))); |
||||||
|
} |
||||||
|
} |
||||||
|
return str; |
||||||
|
} |
||||||
|
|
||||||
|
// String数字转int数字的高效方法(利用ASCII值判断)
|
||||||
|
public static int string2Int(String s) { |
||||||
|
int r = 0; |
||||||
|
char n; |
||||||
|
for (int i = 0, l = s.length(); i < l; i++) { |
||||||
|
n = s.charAt(i); |
||||||
|
if (n >= '0' && n <= '9') { |
||||||
|
r = r * 10 + (n - 0x30); //'0-9'的ASCII值为0x30-0x39
|
||||||
|
} |
||||||
|
} |
||||||
|
return r; |
||||||
|
} |
||||||
|
} |
@ -1,4 +1,4 @@ |
|||||||
package xyz.fycz.myreader.model.analyzeRule; |
package xyz.fycz.myreader.model.third.analyzeRule; |
||||||
|
|
||||||
import android.text.TextUtils; |
import android.text.TextUtils; |
||||||
|
|
@ -1,4 +1,4 @@ |
|||||||
package xyz.fycz.myreader.model.analyzeRule; |
package xyz.fycz.myreader.model.third.analyzeRule; |
||||||
|
|
||||||
import android.annotation.SuppressLint; |
import android.annotation.SuppressLint; |
||||||
|
|
@ -1,4 +1,4 @@ |
|||||||
package xyz.fycz.myreader.model.analyzeRule; |
package xyz.fycz.myreader.model.third.analyzeRule; |
||||||
|
|
||||||
import android.annotation.SuppressLint; |
import android.annotation.SuppressLint; |
||||||
import android.text.TextUtils; |
import android.text.TextUtils; |
@ -0,0 +1,65 @@ |
|||||||
|
package xyz.fycz.myreader.model.third.content; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import io.reactivex.disposables.Disposable; |
||||||
|
import io.reactivex.schedulers.Schedulers; |
||||||
|
import xyz.fycz.myreader.base.observer.MyObserver; |
||||||
|
import xyz.fycz.myreader.entity.WebChapterBean; |
||||||
|
import xyz.fycz.myreader.greendao.entity.Book; |
||||||
|
import xyz.fycz.myreader.greendao.entity.Chapter; |
||||||
|
import xyz.fycz.myreader.model.third.analyzeRule.AnalyzeUrl; |
||||||
|
import xyz.fycz.myreader.util.utils.OkHttpUtils; |
||||||
|
|
||||||
|
public class AnalyzeNextUrlTask { |
||||||
|
private WebChapterBean webChapterBean; |
||||||
|
private Callback callback; |
||||||
|
private Book bookShelfBean; |
||||||
|
private Map<String, String> headerMap; |
||||||
|
private BookChapterList bookChapterList; |
||||||
|
|
||||||
|
public AnalyzeNextUrlTask(BookChapterList bookChapterList, WebChapterBean webChapterBean, Book bookShelfBean, Map<String, String> headerMap) { |
||||||
|
this.bookChapterList = bookChapterList; |
||||||
|
this.webChapterBean = webChapterBean; |
||||||
|
this.bookShelfBean = bookShelfBean; |
||||||
|
this.headerMap = headerMap; |
||||||
|
} |
||||||
|
|
||||||
|
public AnalyzeNextUrlTask setCallback(Callback callback) { |
||||||
|
this.callback = callback; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public void analyzeUrl(AnalyzeUrl analyzeUrl) { |
||||||
|
OkHttpUtils.getStrResponse(analyzeUrl) |
||||||
|
.flatMap(stringResponse -> |
||||||
|
bookChapterList.analyzeChapterList(stringResponse.body(), bookShelfBean, headerMap)) |
||||||
|
.subscribeOn(Schedulers.io()) |
||||||
|
.observeOn(Schedulers.io()) |
||||||
|
.subscribe(new MyObserver<List<Chapter>>() { |
||||||
|
@Override |
||||||
|
public void onSubscribe(Disposable d) { |
||||||
|
callback.addDisposable(d); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onNext(List<Chapter> bookChapterBeans) { |
||||||
|
callback.analyzeFinish(webChapterBean, bookChapterBeans); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onError(Throwable throwable) { |
||||||
|
callback.onError(throwable); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public interface Callback { |
||||||
|
void addDisposable(Disposable disposable); |
||||||
|
|
||||||
|
void analyzeFinish(WebChapterBean bean, List<Chapter> bookChapterBeans); |
||||||
|
|
||||||
|
void onError(Throwable throwable); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,358 @@ |
|||||||
|
package xyz.fycz.myreader.model.third.content; |
||||||
|
|
||||||
|
import android.os.Build; |
||||||
|
import android.text.TextUtils; |
||||||
|
import android.util.Log; |
||||||
|
|
||||||
|
import org.jsoup.nodes.Element; |
||||||
|
import org.mozilla.javascript.NativeObject; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.LinkedHashSet; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.regex.Matcher; |
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
import io.reactivex.Emitter; |
||||||
|
import io.reactivex.Observable; |
||||||
|
import io.reactivex.disposables.CompositeDisposable; |
||||||
|
import io.reactivex.disposables.Disposable; |
||||||
|
import xyz.fycz.myreader.entity.StrResponse; |
||||||
|
import xyz.fycz.myreader.entity.WebChapterBean; |
||||||
|
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.third.analyzeRule.AnalyzeByRegex; |
||||||
|
import xyz.fycz.myreader.model.third.analyzeRule.AnalyzeRule; |
||||||
|
import xyz.fycz.myreader.model.third.analyzeRule.AnalyzeUrl; |
||||||
|
import xyz.fycz.myreader.util.utils.OkHttpUtils; |
||||||
|
|
||||||
|
public class BookChapterList { |
||||||
|
private String tag; |
||||||
|
private BookSource source; |
||||||
|
private TocRule tocRule; |
||||||
|
private List<WebChapterBean> webChapterBeans; |
||||||
|
private boolean dx = false; |
||||||
|
private boolean analyzeNextUrl; |
||||||
|
private CompositeDisposable compositeDisposable; |
||||||
|
private String chapterListUrl; |
||||||
|
|
||||||
|
public BookChapterList(String tag, BookSource source, boolean analyzeNextUrl) { |
||||||
|
this.tag = tag; |
||||||
|
this.source = source; |
||||||
|
this.analyzeNextUrl = analyzeNextUrl; |
||||||
|
tocRule = source.getTocRule(); |
||||||
|
} |
||||||
|
|
||||||
|
public Observable<List<Chapter>> analyzeChapterList(final String s, final Book book, Map<String, String> headerMap) { |
||||||
|
return Observable.create(e -> { |
||||||
|
if (TextUtils.isEmpty(s)) { |
||||||
|
e.onError(new Throwable("目录获取失败" + book.getChapterUrl())); |
||||||
|
return; |
||||||
|
} else { |
||||||
|
Log.d(tag, "┌成功获取目录页" + analyzeNextUrl); |
||||||
|
Log.d(tag, "└" + book.getChapterUrl() + analyzeNextUrl); |
||||||
|
} |
||||||
|
book.setTag(tag); |
||||||
|
AnalyzeRule analyzer = new AnalyzeRule(book); |
||||||
|
String ruleChapterList = tocRule.getChapterList(); |
||||||
|
if (ruleChapterList != null && ruleChapterList.startsWith("-")) { |
||||||
|
dx = true; |
||||||
|
ruleChapterList = ruleChapterList.substring(1); |
||||||
|
} |
||||||
|
chapterListUrl = book.getChapterUrl(); |
||||||
|
WebChapterBean webChapterBean = analyzeChapterList(s, chapterListUrl, ruleChapterList, analyzeNextUrl, analyzer, dx); |
||||||
|
final List<Chapter> chapterList = webChapterBean.getData(); |
||||||
|
|
||||||
|
final List<String> chapterUrlS = new ArrayList<>(webChapterBean.getNextUrlList()); |
||||||
|
if (chapterUrlS.isEmpty() || !analyzeNextUrl) { |
||||||
|
finish(chapterList, e); |
||||||
|
} |
||||||
|
//下一页为单页
|
||||||
|
else if (chapterUrlS.size() == 1) { |
||||||
|
List<String> usedUrl = new ArrayList<>(); |
||||||
|
usedUrl.add(book.getChapterUrl()); |
||||||
|
//循环获取直到下一页为空
|
||||||
|
Log.d(tag, "正在加载下一页"); |
||||||
|
while (!chapterUrlS.isEmpty() && !usedUrl.contains(chapterUrlS.get(0))) { |
||||||
|
usedUrl.add(chapterUrlS.get(0)); |
||||||
|
AnalyzeUrl analyzeUrl = new AnalyzeUrl(chapterUrlS.get(0), headerMap, tag); |
||||||
|
try { |
||||||
|
String body; |
||||||
|
StrResponse response = OkHttpUtils.getStrResponse(analyzeUrl) |
||||||
|
.blockingFirst(); |
||||||
|
body = response.body(); |
||||||
|
webChapterBean = analyzeChapterList(body, chapterUrlS.get(0), ruleChapterList, false, analyzer, dx); |
||||||
|
chapterList.addAll(webChapterBean.getData()); |
||||||
|
chapterUrlS.clear(); |
||||||
|
chapterUrlS.addAll(webChapterBean.getNextUrlList()); |
||||||
|
} catch (Exception exception) { |
||||||
|
if (!e.isDisposed()) { |
||||||
|
e.onError(exception); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
Log.d(tag, "下一页加载完成共" + usedUrl.size() + "页"); |
||||||
|
finish(chapterList, e); |
||||||
|
} |
||||||
|
//下一页为多页
|
||||||
|
else { |
||||||
|
Log.d(tag, "正在加载其它" + chapterUrlS.size() + "页"); |
||||||
|
compositeDisposable = new CompositeDisposable(); |
||||||
|
webChapterBeans = new ArrayList<>(); |
||||||
|
AnalyzeNextUrlTask.Callback callback = new AnalyzeNextUrlTask.Callback() { |
||||||
|
@Override |
||||||
|
public void addDisposable(Disposable disposable) { |
||||||
|
compositeDisposable.add(disposable); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void analyzeFinish(WebChapterBean bean, List<Chapter> chapterListBeans) { |
||||||
|
if (nextUrlFinish(bean, chapterListBeans)) { |
||||||
|
for (WebChapterBean chapterBean : webChapterBeans) { |
||||||
|
chapterList.addAll(chapterBean.getData()); |
||||||
|
} |
||||||
|
Log.d(tag, "其它页加载完成,目录共" + chapterList.size() + "条"); |
||||||
|
finish(chapterList, e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onError(Throwable throwable) { |
||||||
|
compositeDisposable.dispose(); |
||||||
|
e.onError(throwable); |
||||||
|
} |
||||||
|
}; |
||||||
|
for (String url : chapterUrlS) { |
||||||
|
final WebChapterBean bean = new WebChapterBean(url); |
||||||
|
webChapterBeans.add(bean); |
||||||
|
} |
||||||
|
for (WebChapterBean bean : webChapterBeans) { |
||||||
|
BookChapterList bookChapterList = new BookChapterList(tag, source, false); |
||||||
|
AnalyzeUrl analyzeUrl = new AnalyzeUrl(bean.getUrl(), headerMap, tag); |
||||||
|
new AnalyzeNextUrlTask(bookChapterList, bean, book, headerMap) |
||||||
|
.setCallback(callback) |
||||||
|
.analyzeUrl(analyzeUrl); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private synchronized boolean nextUrlFinish(WebChapterBean webChapterBean, List<Chapter> bookChapterBeans) { |
||||||
|
webChapterBean.setData(bookChapterBeans); |
||||||
|
for (WebChapterBean bean : webChapterBeans) { |
||||||
|
if (bean.noData()) return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private void finish(List<Chapter> chapterList, Emitter<List<Chapter>> emitter) { |
||||||
|
//去除重复,保留后面的,先倒序,从后面往前判断
|
||||||
|
if (!dx) { |
||||||
|
Collections.reverse(chapterList); |
||||||
|
} |
||||||
|
LinkedHashSet<Chapter> lh = new LinkedHashSet<>(chapterList); |
||||||
|
chapterList = new ArrayList<>(lh); |
||||||
|
Collections.reverse(chapterList); |
||||||
|
Log.d(tag, "-目录解析完成" + analyzeNextUrl); |
||||||
|
emitter.onNext(chapterList); |
||||||
|
emitter.onComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
private WebChapterBean analyzeChapterList(String s, String chapterUrl, String ruleChapterList, |
||||||
|
boolean printLog, AnalyzeRule analyzer, boolean dx) throws Exception { |
||||||
|
List<String> nextUrlList = new ArrayList<>(); |
||||||
|
analyzer.setContent(s, chapterUrl); |
||||||
|
if (!TextUtils.isEmpty(tocRule.getTocUrlNext()) && analyzeNextUrl) { |
||||||
|
if (printLog) Log.d(tag, "┌获取目录下一页网址"); |
||||||
|
nextUrlList = analyzer.getStringList(tocRule.getTocUrlNext(), true); |
||||||
|
int thisUrlIndex = nextUrlList.indexOf(chapterUrl); |
||||||
|
if (thisUrlIndex != -1) { |
||||||
|
nextUrlList.remove(thisUrlIndex); |
||||||
|
} |
||||||
|
if (printLog) Log.d(tag, "└" + nextUrlList.toString()); |
||||||
|
} |
||||||
|
|
||||||
|
List<Chapter> chapterBeans = new ArrayList<>(); |
||||||
|
if (printLog) Log.d(tag, "┌解析目录列表"); |
||||||
|
// 仅使用java正则表达式提取目录列表
|
||||||
|
if (ruleChapterList.startsWith(":")) { |
||||||
|
ruleChapterList = ruleChapterList.substring(1); |
||||||
|
regexChapter(s, ruleChapterList.split("&&"), 0, analyzer, chapterBeans); |
||||||
|
if (chapterBeans.size() == 0) { |
||||||
|
if (printLog) Log.d(tag, "└找到 0 个章节"); |
||||||
|
return new WebChapterBean(chapterBeans, new LinkedHashSet<>(nextUrlList)); |
||||||
|
} |
||||||
|
} |
||||||
|
// 使用AllInOne规则模式提取目录列表
|
||||||
|
else if (ruleChapterList.startsWith("+")) { |
||||||
|
ruleChapterList = ruleChapterList.substring(1); |
||||||
|
List<Object> collections = analyzer.getElements(ruleChapterList); |
||||||
|
if (collections.size() == 0) { |
||||||
|
Log.d(tag, "└找到 0 个章节"); |
||||||
|
return new WebChapterBean(chapterBeans, new LinkedHashSet<>(nextUrlList)); |
||||||
|
} |
||||||
|
String nameRule = tocRule.getChapterName(); |
||||||
|
String linkRule = tocRule.getChapterUrl(); |
||||||
|
String name = ""; |
||||||
|
String link = ""; |
||||||
|
for (Object object : collections) { |
||||||
|
if (object instanceof NativeObject) { |
||||||
|
name = String.valueOf(((NativeObject) object).get(nameRule)); |
||||||
|
link = String.valueOf(((NativeObject) object).get(linkRule)); |
||||||
|
} else if (object instanceof Element) { |
||||||
|
name = ((Element) object).text(); |
||||||
|
link = ((Element) object).attr(linkRule); |
||||||
|
} |
||||||
|
addChapter(chapterBeans, name, link); |
||||||
|
} |
||||||
|
} |
||||||
|
// 使用默认规则解析流程提取目录列表
|
||||||
|
else { |
||||||
|
List<Object> collections = analyzer.getElements(ruleChapterList); |
||||||
|
if (collections.size() == 0) { |
||||||
|
Log.d(tag, "└找到 0 个章节"); |
||||||
|
return new WebChapterBean(chapterBeans, new LinkedHashSet<>(nextUrlList)); |
||||||
|
} |
||||||
|
List<AnalyzeRule.SourceRule> nameRule = analyzer.splitSourceRule(tocRule.getChapterName()); |
||||||
|
List<AnalyzeRule.SourceRule> linkRule = analyzer.splitSourceRule(tocRule.getChapterUrl()); |
||||||
|
for (Object object : collections) { |
||||||
|
analyzer.setContent(object, chapterUrl); |
||||||
|
addChapter(chapterBeans, analyzer.getString(nameRule), analyzer.getString(linkRule)); |
||||||
|
} |
||||||
|
} |
||||||
|
if (printLog) Log.d(tag,"└找到 " + chapterBeans.size() + " 个章节"); |
||||||
|
Chapter firstChapter; |
||||||
|
if (dx) { |
||||||
|
if (printLog) Log.d(tag, "-倒序"); |
||||||
|
firstChapter = chapterBeans.get(chapterBeans.size() - 1); |
||||||
|
} else { |
||||||
|
firstChapter = chapterBeans.get(0); |
||||||
|
} |
||||||
|
if (printLog) Log.d(tag, "┌获取章节名称"); |
||||||
|
if (printLog) Log.d(tag, "└" + firstChapter.getTitle()); |
||||||
|
if (printLog) Log.d(tag, "┌获取章节网址"); |
||||||
|
if (printLog) Log.d(tag, "└" + firstChapter.getUrl()); |
||||||
|
return new WebChapterBean(chapterBeans, new LinkedHashSet<>(nextUrlList)); |
||||||
|
} |
||||||
|
|
||||||
|
private void addChapter(final List<Chapter> chapterBeans, String name, String link) { |
||||||
|
if (TextUtils.isEmpty(name)) return; |
||||||
|
if (TextUtils.isEmpty(link)) link = chapterListUrl; |
||||||
|
Chapter chapter = new Chapter(); |
||||||
|
chapter.setTitle(name); |
||||||
|
chapter.setUrl(link); |
||||||
|
chapterBeans.add(chapter); |
||||||
|
} |
||||||
|
|
||||||
|
// region 纯java模式正则表达式获取目录列表
|
||||||
|
private void regexChapter(String str, String[] regex, int index, AnalyzeRule analyzer, final List<Chapter> chapterBeans) throws Exception { |
||||||
|
Matcher resM = Pattern.compile(regex[index]).matcher(str); |
||||||
|
if (!resM.find()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (index + 1 == regex.length) { |
||||||
|
// 获取解析规则
|
||||||
|
String nameRule = tocRule.getChapterName(); |
||||||
|
String linkRule = tocRule.getChapterUrl(); |
||||||
|
if (TextUtils.isEmpty(nameRule) || TextUtils.isEmpty(linkRule)) return; |
||||||
|
// 替换@get规则
|
||||||
|
nameRule = analyzer.replaceGet(tocRule.getChapterName()); |
||||||
|
linkRule = analyzer.replaceGet(tocRule.getChapterUrl()); |
||||||
|
// 分离规则参数
|
||||||
|
List<String> nameParams = new ArrayList<>(); |
||||||
|
List<Integer> nameGroups = new ArrayList<>(); |
||||||
|
AnalyzeByRegex.splitRegexRule(nameRule, nameParams, nameGroups); |
||||||
|
List<String> linkParams = new ArrayList<>(); |
||||||
|
List<Integer> linkGroups = new ArrayList<>(); |
||||||
|
AnalyzeByRegex.splitRegexRule(linkRule, linkParams, linkGroups); |
||||||
|
// 是否包含VIP规则(hasVipRule>1 时视为包含vip规则)
|
||||||
|
int hasVipRule = 0; |
||||||
|
for (int i = nameGroups.size(); i-- > 0; ) { |
||||||
|
if (nameGroups.get(i) != 0) { |
||||||
|
++hasVipRule; |
||||||
|
} |
||||||
|
} |
||||||
|
String vipNameGroup = ""; |
||||||
|
int vipNumGroup = 0; |
||||||
|
if ((nameGroups.get(0) != 0) && (hasVipRule > 1)) { |
||||||
|
vipNumGroup = nameGroups.remove(0); |
||||||
|
vipNameGroup = nameParams.remove(0); |
||||||
|
} |
||||||
|
// 创建结果缓存
|
||||||
|
StringBuilder cName = new StringBuilder(); |
||||||
|
StringBuilder cLink = new StringBuilder(); |
||||||
|
// 提取书籍目录
|
||||||
|
if (vipNumGroup != 0) { |
||||||
|
do { |
||||||
|
cName.setLength(0); |
||||||
|
cLink.setLength(0); |
||||||
|
for (int i = nameParams.size(); i-- > 0; ) { |
||||||
|
if (nameGroups.get(i) > 0) { |
||||||
|
cName.insert(0, resM.group(nameGroups.get(i))); |
||||||
|
} else if (nameGroups.get(i) < 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
||||||
|
cName.insert(0, resM.group(nameParams.get(i))); |
||||||
|
} else { |
||||||
|
cName.insert(0, nameParams.get(i)); |
||||||
|
} |
||||||
|
} |
||||||
|
if (vipNumGroup > 0) { |
||||||
|
cName.insert(0, resM.group(vipNumGroup) == null ? "" : "\uD83D\uDD12"); |
||||||
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
||||||
|
cName.insert(0, resM.group(vipNameGroup) == null ? "" : "\uD83D\uDD12"); |
||||||
|
} else { |
||||||
|
cName.insert(0, vipNameGroup); |
||||||
|
} |
||||||
|
|
||||||
|
for (int i = linkParams.size(); i-- > 0; ) { |
||||||
|
if (linkGroups.get(i) > 0) { |
||||||
|
cLink.insert(0, resM.group(linkGroups.get(i))); |
||||||
|
} else if (linkGroups.get(i) < 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
||||||
|
cLink.insert(0, resM.group(linkParams.get(i))); |
||||||
|
} else { |
||||||
|
cLink.insert(0, linkParams.get(i)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
addChapter(chapterBeans, cName.toString(), cLink.toString()); |
||||||
|
} while (resM.find()); |
||||||
|
} else { |
||||||
|
do { |
||||||
|
cName.setLength(0); |
||||||
|
cLink.setLength(0); |
||||||
|
for (int i = nameParams.size(); i-- > 0; ) { |
||||||
|
if (nameGroups.get(i) > 0) { |
||||||
|
cName.insert(0, resM.group(nameGroups.get(i))); |
||||||
|
} else if (nameGroups.get(i) < 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
||||||
|
cName.insert(0, resM.group(nameParams.get(i))); |
||||||
|
} else { |
||||||
|
cName.insert(0, nameParams.get(i)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (int i = linkParams.size(); i-- > 0; ) { |
||||||
|
if (linkGroups.get(i) > 0) { |
||||||
|
cLink.insert(0, resM.group(linkGroups.get(i))); |
||||||
|
} else if (linkGroups.get(i) < 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
||||||
|
cLink.insert(0, resM.group(linkParams.get(i))); |
||||||
|
} else { |
||||||
|
cLink.insert(0, linkParams.get(i)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
addChapter(chapterBeans, cName.toString(), cLink.toString()); |
||||||
|
} while (resM.find()); |
||||||
|
} |
||||||
|
} else { |
||||||
|
StringBuilder result = new StringBuilder(); |
||||||
|
do { |
||||||
|
result.append(resM.group(0)); |
||||||
|
} while (resM.find()); |
||||||
|
regexChapter(result.toString(), regex, ++index, analyzer, chapterBeans); |
||||||
|
} |
||||||
|
} |
||||||
|
// endregion
|
||||||
|
} |
@ -0,0 +1,147 @@ |
|||||||
|
package xyz.fycz.myreader.model.third.content; |
||||||
|
|
||||||
|
import android.text.TextUtils; |
||||||
|
import android.util.Log; |
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.regex.Matcher; |
||||||
|
|
||||||
|
import io.reactivex.Observable; |
||||||
|
import xyz.fycz.myreader.entity.StrResponse; |
||||||
|
import xyz.fycz.myreader.greendao.GreenDaoManager; |
||||||
|
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.gen.ChapterDao; |
||||||
|
import xyz.fycz.myreader.model.third.analyzeRule.AnalyzeRule; |
||||||
|
import xyz.fycz.myreader.model.third.analyzeRule.AnalyzeUrl; |
||||||
|
import xyz.fycz.myreader.util.utils.NetworkUtils; |
||||||
|
import xyz.fycz.myreader.util.utils.OkHttpUtils; |
||||||
|
import xyz.fycz.myreader.util.utils.StringUtils; |
||||||
|
|
||||||
|
import static xyz.fycz.myreader.common.APPCONST.JS_PATTERN; |
||||||
|
|
||||||
|
|
||||||
|
public class BookContent { |
||||||
|
private String tag; |
||||||
|
private ContentRule contentRule; |
||||||
|
private String ruleBookContent; |
||||||
|
private String baseUrl; |
||||||
|
|
||||||
|
public BookContent(String tag, BookSource bookSource) { |
||||||
|
this.tag = tag; |
||||||
|
this.contentRule = bookSource.getContentRule(); |
||||||
|
ruleBookContent = contentRule.getContent(); |
||||||
|
if (ruleBookContent.startsWith("$") && !ruleBookContent.startsWith("$.")) { |
||||||
|
ruleBookContent = ruleBookContent.substring(1); |
||||||
|
Matcher jsMatcher = JS_PATTERN.matcher(ruleBookContent); |
||||||
|
if (jsMatcher.find()) { |
||||||
|
ruleBookContent = ruleBookContent.replace(jsMatcher.group(), ""); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public Observable<String> analyzeBookContent(final StrResponse response, final Chapter chapterBean, final Chapter nextChapterBean, Book book, Map<String, String> headerMap) { |
||||||
|
baseUrl = NetworkUtils.getUrl(response.getResponse()); |
||||||
|
return analyzeBookContent(response.body(), chapterBean, nextChapterBean, book, headerMap); |
||||||
|
} |
||||||
|
|
||||||
|
public Observable<String> analyzeBookContent(final String s, final Chapter chapterBean, final Chapter nextChapterBean, Book book, Map<String, String> headerMap) { |
||||||
|
return Observable.create(e -> { |
||||||
|
if (TextUtils.isEmpty(s)) { |
||||||
|
e.onError(new Throwable("内容获取失败" + chapterBean.getUrl())); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (TextUtils.isEmpty(baseUrl)) { |
||||||
|
baseUrl = NetworkUtils.getAbsoluteURL(book.getChapterUrl(), chapterBean.getUrl()); |
||||||
|
} |
||||||
|
Log.d(tag, "┌成功获取正文页"); |
||||||
|
Log.d(tag, "└" + baseUrl); |
||||||
|
|
||||||
|
AnalyzeRule analyzer = new AnalyzeRule(book); |
||||||
|
WebContentBean webContentBean = analyzeBookContent(analyzer, s, chapterBean.getUrl(), baseUrl); |
||||||
|
|
||||||
|
StringBuilder content = new StringBuilder(); |
||||||
|
content.append(webContentBean.content); |
||||||
|
|
||||||
|
/* 处理分页 |
||||||
|
*/ |
||||||
|
if (!TextUtils.isEmpty(webContentBean.nextUrl)) { |
||||||
|
List<String> usedUrlList = new ArrayList<>(); |
||||||
|
usedUrlList.add(chapterBean.getUrl()); |
||||||
|
Chapter nextChapter; |
||||||
|
if (nextChapterBean != null) { |
||||||
|
nextChapter = nextChapterBean; |
||||||
|
} else { |
||||||
|
nextChapter = GreenDaoManager.getDaoSession().getChapterDao().queryBuilder() |
||||||
|
.where(ChapterDao.Properties.Url.eq(chapterBean.getUrl()), |
||||||
|
ChapterDao.Properties.Number.eq(chapterBean.getNumber() + 1)) |
||||||
|
.build().unique(); |
||||||
|
} |
||||||
|
|
||||||
|
while (!TextUtils.isEmpty(webContentBean.nextUrl) && !usedUrlList.contains(webContentBean.nextUrl)) { |
||||||
|
usedUrlList.add(webContentBean.nextUrl); |
||||||
|
if (nextChapter != null && NetworkUtils.getAbsoluteURL(baseUrl, webContentBean.nextUrl).equals(NetworkUtils.getAbsoluteURL(baseUrl, nextChapter.getUrl()))) { |
||||||
|
break; |
||||||
|
} |
||||||
|
AnalyzeUrl analyzeUrl = new AnalyzeUrl(webContentBean.nextUrl, headerMap, tag); |
||||||
|
try { |
||||||
|
String body; |
||||||
|
StrResponse response = OkHttpUtils.getStrResponse(analyzeUrl).blockingFirst(); |
||||||
|
body = response.body(); |
||||||
|
webContentBean = analyzeBookContent(analyzer, body, webContentBean.nextUrl, baseUrl); |
||||||
|
if (!TextUtils.isEmpty(webContentBean.content)) { |
||||||
|
content.append("\n").append(webContentBean.content); |
||||||
|
} |
||||||
|
} catch (Exception exception) { |
||||||
|
if (!e.isDisposed()) { |
||||||
|
e.onError(exception); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
/*String replaceRule = bookSourceBean.getRuleBookContentReplace(); |
||||||
|
if (replaceRule != null && replaceRule.trim().length() > 0) { |
||||||
|
analyzer.setContent(bookContentBean.getDurChapterContent()); |
||||||
|
bookContentBean.setDurChapterContent(analyzer.getString(replaceRule)); |
||||||
|
}*/ |
||||||
|
e.onNext(content.toString()); |
||||||
|
e.onComplete(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public WebContentBean analyzeBookContent(AnalyzeRule analyzer, final String s, final String chapterUrl, String baseUrl) throws Exception { |
||||||
|
WebContentBean webContentBean = new WebContentBean(); |
||||||
|
|
||||||
|
analyzer.setContent(s, NetworkUtils.getAbsoluteURL(baseUrl, chapterUrl)); |
||||||
|
Log.d(tag, "┌解析正文内容"); |
||||||
|
if (ruleBookContent.equals("all") || ruleBookContent.contains("@all")) { |
||||||
|
webContentBean.content = analyzer.getString(ruleBookContent); |
||||||
|
} |
||||||
|
else { |
||||||
|
webContentBean.content = StringUtils.formatHtml(analyzer.getString(ruleBookContent)); |
||||||
|
} |
||||||
|
Log.d(tag, "└" + webContentBean.content); |
||||||
|
String nextUrlRule = contentRule.getContentUrlNext(); |
||||||
|
if (!TextUtils.isEmpty(nextUrlRule)) { |
||||||
|
Log.d(tag, "┌解析下一页url"); |
||||||
|
webContentBean.nextUrl = analyzer.getString(nextUrlRule, true); |
||||||
|
Log.d(tag, "└" + webContentBean.nextUrl); |
||||||
|
} |
||||||
|
|
||||||
|
return webContentBean; |
||||||
|
} |
||||||
|
|
||||||
|
private static class WebContentBean { |
||||||
|
private String content; |
||||||
|
private String nextUrl; |
||||||
|
|
||||||
|
private WebContentBean() { |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,86 @@ |
|||||||
|
package xyz.fycz.myreader.model.third.content; |
||||||
|
|
||||||
|
import android.text.TextUtils; |
||||||
|
import android.util.Log; |
||||||
|
|
||||||
|
|
||||||
|
import io.reactivex.Observable; |
||||||
|
import xyz.fycz.myreader.greendao.entity.Book; |
||||||
|
import xyz.fycz.myreader.greendao.entity.rule.BookSource; |
||||||
|
import xyz.fycz.myreader.greendao.entity.rule.InfoRule; |
||||||
|
import xyz.fycz.myreader.model.third.analyzeRule.AnalyzeRule; |
||||||
|
import xyz.fycz.myreader.util.utils.StringUtils; |
||||||
|
|
||||||
|
import static android.text.TextUtils.isEmpty; |
||||||
|
|
||||||
|
public class BookInfo { |
||||||
|
private String tag; |
||||||
|
private String sourceName; |
||||||
|
private BookSource source; |
||||||
|
|
||||||
|
public BookInfo(String tag, String sourceName, BookSource source) { |
||||||
|
this.tag = tag; |
||||||
|
this.sourceName = sourceName; |
||||||
|
this.source = source; |
||||||
|
} |
||||||
|
|
||||||
|
public Observable<Book> analyzeBookInfo(String s, final Book book) { |
||||||
|
return Observable.create(e -> { |
||||||
|
String baseUrl = book.getInfoUrl(); |
||||||
|
InfoRule infoRule = source.getInfoRule(); |
||||||
|
if (TextUtils.isEmpty(s)) { |
||||||
|
e.onError(new Throwable("书籍信息获取失败" + baseUrl)); |
||||||
|
return; |
||||||
|
} else { |
||||||
|
Log.d(tag, "┌成功获取详情页"); |
||||||
|
Log.d(tag, "└" + baseUrl); |
||||||
|
} |
||||||
|
book.setTag(tag); |
||||||
|
book.setSource(source.getSourceUrl()); |
||||||
|
|
||||||
|
AnalyzeRule analyzer = new AnalyzeRule(book); |
||||||
|
analyzer.setContent(s, baseUrl); |
||||||
|
|
||||||
|
Log.d(tag, "┌获取书名"); |
||||||
|
String bookName = StringUtils.formatHtml(analyzer.getString(infoRule.getName())); |
||||||
|
if (!isEmpty(bookName)) book.setName(bookName); |
||||||
|
Log.d(tag, "└" + bookName); |
||||||
|
|
||||||
|
Log.d(tag, "┌获取作者"); |
||||||
|
String bookAuthor = StringUtils.formatHtml(analyzer.getString(infoRule.getAuthor())); |
||||||
|
if (!isEmpty(bookAuthor)) book.setAuthor(bookAuthor); |
||||||
|
Log.d(tag, "└" + bookAuthor); |
||||||
|
|
||||||
|
Log.d(tag, "┌获取分类"); |
||||||
|
String bookKind = analyzer.getString(infoRule.getType()); |
||||||
|
Log.d(tag, "└" + bookKind); |
||||||
|
|
||||||
|
Log.d(tag, "┌获取最新章节"); |
||||||
|
String bookLastChapter = analyzer.getString(infoRule.getLastChapter()); |
||||||
|
if (!isEmpty(bookLastChapter)) book.setNewestChapterTitle(bookLastChapter); |
||||||
|
Log.d(tag, "└" + bookLastChapter); |
||||||
|
|
||||||
|
Log.d(tag, "┌获取简介"); |
||||||
|
String bookIntroduce = analyzer.getString(infoRule.getDesc()); |
||||||
|
if (!isEmpty(bookIntroduce)) book.setDesc(bookIntroduce); |
||||||
|
Log.d(tag, "└" + bookIntroduce); |
||||||
|
|
||||||
|
Log.d(tag, "┌获取封面"); |
||||||
|
String bookCoverUrl = analyzer.getString(infoRule.getImgUrl(), true); |
||||||
|
if (!isEmpty(bookCoverUrl)) book.setImgUrl(bookCoverUrl); |
||||||
|
Log.d(tag, "└" + bookCoverUrl); |
||||||
|
|
||||||
|
Log.d(tag, "┌获取目录网址"); |
||||||
|
String bookCatalogUrl = analyzer.getString(infoRule.getTocUrl(), true); |
||||||
|
if (isEmpty(bookCatalogUrl)) bookCatalogUrl = baseUrl; |
||||||
|
book.setChapterUrl(bookCatalogUrl); |
||||||
|
//如果目录页和详情页相同,暂存页面内容供获取目录用
|
||||||
|
//if (bookCatalogUrl.equals(baseUrl)) bookInfoBean.setChapterListHtml(s);
|
||||||
|
Log.d(tag, "└" + book.getChapterUrl()); |
||||||
|
Log.d(tag, "-详情页解析完成"); |
||||||
|
e.onNext(book); |
||||||
|
e.onComplete(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,375 @@ |
|||||||
|
package xyz.fycz.myreader.model.third.content; |
||||||
|
|
||||||
|
import android.os.Build; |
||||||
|
import android.text.TextUtils; |
||||||
|
import android.util.Log; |
||||||
|
|
||||||
|
import org.mozilla.javascript.NativeObject; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.regex.Matcher; |
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
import io.reactivex.Observable; |
||||||
|
import xyz.fycz.myreader.entity.StrResponse; |
||||||
|
import xyz.fycz.myreader.greendao.entity.Book; |
||||||
|
import xyz.fycz.myreader.greendao.entity.rule.BookSource; |
||||||
|
import xyz.fycz.myreader.greendao.entity.rule.FindRule; |
||||||
|
import xyz.fycz.myreader.greendao.entity.rule.InfoRule; |
||||||
|
import xyz.fycz.myreader.greendao.entity.rule.SearchRule; |
||||||
|
import xyz.fycz.myreader.model.third.analyzeRule.AnalyzeByRegex; |
||||||
|
import xyz.fycz.myreader.model.third.analyzeRule.AnalyzeRule; |
||||||
|
import xyz.fycz.myreader.util.utils.NetworkUtils; |
||||||
|
import xyz.fycz.myreader.util.utils.StringUtils; |
||||||
|
|
||||||
|
import static android.text.TextUtils.isEmpty; |
||||||
|
|
||||||
|
public class BookList { |
||||||
|
private String tag; |
||||||
|
private String sourceName; |
||||||
|
private BookSource bookSource; |
||||||
|
private boolean isFind; |
||||||
|
//规则
|
||||||
|
private String ruleList; |
||||||
|
private String ruleName; |
||||||
|
private String ruleAuthor; |
||||||
|
private String ruleKind; |
||||||
|
private String ruleIntroduce; |
||||||
|
private String ruleLastChapter; |
||||||
|
private String ruleCoverUrl; |
||||||
|
private String ruleNoteUrl; |
||||||
|
|
||||||
|
public BookList(String tag, String sourceName, BookSource bookSource, boolean isFind) { |
||||||
|
this.tag = tag; |
||||||
|
this.sourceName = sourceName; |
||||||
|
this.bookSource = bookSource; |
||||||
|
this.isFind = isFind; |
||||||
|
} |
||||||
|
|
||||||
|
public Observable<List<Book>> analyzeSearchBook(final StrResponse strResponse) { |
||||||
|
return Observable.create(e -> { |
||||||
|
String baseUrl; |
||||||
|
baseUrl = NetworkUtils.getUrl(strResponse.getResponse()); |
||||||
|
String body = strResponse.body(); |
||||||
|
if (TextUtils.isEmpty(body)) { |
||||||
|
e.onError(new Throwable(String.format("访问网站失败%s", baseUrl))); |
||||||
|
return; |
||||||
|
} else { |
||||||
|
Log.d(tag, "┌成功获取搜索结果"); |
||||||
|
Log.d(tag, "└" + baseUrl); |
||||||
|
} |
||||||
|
List<Book> books = new ArrayList<>(); |
||||||
|
AnalyzeRule analyzer = new AnalyzeRule(null); |
||||||
|
analyzer.setContent(body, baseUrl); |
||||||
|
//如果符合详情页url规则
|
||||||
|
|
||||||
|
initRule(); |
||||||
|
List<Object> collections; |
||||||
|
boolean reverse = false; |
||||||
|
boolean allInOne = false; |
||||||
|
if (ruleList.startsWith("-")) { |
||||||
|
reverse = true; |
||||||
|
ruleList = ruleList.substring(1); |
||||||
|
} |
||||||
|
// 仅使用java正则表达式提取书籍列表
|
||||||
|
if (ruleList.startsWith(":")) { |
||||||
|
ruleList = ruleList.substring(1); |
||||||
|
Log.d(tag, "┌解析搜索列表"); |
||||||
|
getBooksOfRegex(body, ruleList.split("&&"), 0, analyzer, books); |
||||||
|
} else { |
||||||
|
if (ruleList.startsWith("+")) { |
||||||
|
allInOne = true; |
||||||
|
ruleList = ruleList.substring(1); |
||||||
|
} |
||||||
|
//获取列表
|
||||||
|
Log.d(tag, "┌解析搜索列表"); |
||||||
|
collections = analyzer.getElements(ruleList); |
||||||
|
if (collections.size() == 0) { |
||||||
|
Log.d(tag, "└搜索列表为空,当做详情页处理"); |
||||||
|
Book item = getItem(analyzer, baseUrl); |
||||||
|
if (item != null) { |
||||||
|
//item.setBookInfoHtml(body);
|
||||||
|
books.add(item); |
||||||
|
} |
||||||
|
} else { |
||||||
|
Log.d(tag, "└找到 " + collections.size() + " 个匹配的结果"); |
||||||
|
if (allInOne) { |
||||||
|
for (int i = 0; i < collections.size(); i++) { |
||||||
|
Object object = collections.get(i); |
||||||
|
Book item = getItemAllInOne(analyzer, object, baseUrl, i == 0); |
||||||
|
if (item != null) { |
||||||
|
//如果网址相同则缓存
|
||||||
|
/*if (baseUrl.equals(item.getNoteUrl())) { |
||||||
|
item.setBookInfoHtml(body); |
||||||
|
}*/ |
||||||
|
books.add(item); |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
for (int i = 0; i < collections.size(); i++) { |
||||||
|
Object object = collections.get(i); |
||||||
|
analyzer.setContent(object, baseUrl); |
||||||
|
Book item = getItemInList(analyzer, baseUrl, i == 0); |
||||||
|
if (item != null) { |
||||||
|
//如果网址相同则缓存
|
||||||
|
/*if (baseUrl.equals(item.getNoteUrl())) { |
||||||
|
item.setBookInfoHtml(body); |
||||||
|
}*/ |
||||||
|
books.add(item); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if (books.size() > 1 && reverse) { |
||||||
|
Collections.reverse(books); |
||||||
|
} |
||||||
|
if (books.isEmpty()) { |
||||||
|
e.onError(new Throwable("未获取到书名")); |
||||||
|
return; |
||||||
|
} |
||||||
|
Log.d(tag, "-书籍列表解析结束"); |
||||||
|
e.onNext(books); |
||||||
|
e.onComplete(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private void initRule() { |
||||||
|
if (isFind && !TextUtils.isEmpty(bookSource.getFindRule().getBookList())) { |
||||||
|
FindRule findRule = bookSource.getFindRule(); |
||||||
|
ruleList = findRule.getBookList(); |
||||||
|
ruleName = findRule.getName(); |
||||||
|
ruleAuthor = findRule.getAuthor(); |
||||||
|
ruleKind = findRule.getType(); |
||||||
|
ruleIntroduce = findRule.getDesc(); |
||||||
|
ruleCoverUrl = findRule.getImgUrl(); |
||||||
|
ruleLastChapter = findRule.getLastChapter(); |
||||||
|
ruleNoteUrl = findRule.getTocUrl(); |
||||||
|
} else { |
||||||
|
SearchRule searchRule = bookSource.getSearchRule(); |
||||||
|
ruleList = searchRule.getList(); |
||||||
|
ruleName = searchRule.getName(); |
||||||
|
ruleAuthor = searchRule.getAuthor(); |
||||||
|
ruleKind = searchRule.getType(); |
||||||
|
ruleIntroduce = searchRule.getDesc(); |
||||||
|
ruleCoverUrl = searchRule.getImgUrl(); |
||||||
|
ruleLastChapter = searchRule.getLastChapter(); |
||||||
|
ruleNoteUrl = searchRule.getTocUrl(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 详情页 |
||||||
|
*/ |
||||||
|
private Book getItem(AnalyzeRule analyzer, String baseUrl) throws Exception { |
||||||
|
InfoRule infoRule = bookSource.getInfoRule(); |
||||||
|
Book item = new Book(); |
||||||
|
analyzer.setBook(item); |
||||||
|
item.setTag(tag); |
||||||
|
item.setSource(bookSource.getSourceUrl()); |
||||||
|
item.setChapterUrl(baseUrl); |
||||||
|
Log.d(tag, ">书籍网址:" + baseUrl); |
||||||
|
Log.d(tag, "┌获取书名"); |
||||||
|
String bookName = StringUtils.formatHtml(analyzer.getString(infoRule.getName())); |
||||||
|
Log.d(tag, "└" + bookName); |
||||||
|
if (!TextUtils.isEmpty(bookName)) { |
||||||
|
item.setName(bookName); |
||||||
|
Log.d(tag, "┌获取作者"); |
||||||
|
item.setAuthor(StringUtils.formatHtml(analyzer.getString(infoRule.getAuthor()))); |
||||||
|
Log.d(tag, "└" + item.getAuthor()); |
||||||
|
Log.d(tag, "┌获取分类"); |
||||||
|
item.setType(analyzer.getString(infoRule.getType())); |
||||||
|
Log.d(tag, "└" + item.getType()); |
||||||
|
Log.d(tag, "┌获取最新章节"); |
||||||
|
item.setNewestChapterTitle(analyzer.getString(infoRule.getLastChapter())); |
||||||
|
Log.d(tag, "└" + item.getNewestChapterId()); |
||||||
|
Log.d(tag, "┌获取简介"); |
||||||
|
item.setDesc(analyzer.getString(infoRule.getDesc())); |
||||||
|
Log.d(tag, "└" + item.getDesc()); |
||||||
|
Log.d(tag, "┌获取封面"); |
||||||
|
item.setImgUrl(analyzer.getString(infoRule.getImgUrl(), true)); |
||||||
|
Log.d(tag, "└" + item.getImgUrl()); |
||||||
|
return item; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
private Book getItemAllInOne(AnalyzeRule analyzer, Object object, String baseUrl, boolean printLog) { |
||||||
|
Book item = new Book(); |
||||||
|
analyzer.setBook(item); |
||||||
|
NativeObject nativeObject = (NativeObject) object; |
||||||
|
if (printLog) Log.d(tag, "┌获取书名"); |
||||||
|
String bookName = StringUtils.formatHtml(String.valueOf(nativeObject.get(ruleName))); |
||||||
|
if (printLog) Log.d(tag, "└" + bookName); |
||||||
|
if (!isEmpty(bookName)) { |
||||||
|
item.setTag(tag); |
||||||
|
item.setSource(bookSource.getSourceUrl()); |
||||||
|
item.setName(bookName); |
||||||
|
if (printLog) Log.d(tag, "┌获取作者"); |
||||||
|
item.setAuthor(StringUtils.formatHtml(String.valueOf(nativeObject.get(ruleAuthor)))); |
||||||
|
if (printLog) Log.d(tag, "└" + item.getAuthor()); |
||||||
|
if (printLog) Log.d(tag, "┌获取分类"); |
||||||
|
item.setType(String.valueOf(nativeObject.get(ruleKind))); |
||||||
|
if (printLog) Log.d(tag,"└" + item.getType()); |
||||||
|
if (printLog) Log.d(tag, "┌获取最新章节"); |
||||||
|
item.setNewestChapterTitle(String.valueOf(nativeObject.get(ruleLastChapter))); |
||||||
|
if (printLog) Log.d(tag, "└" + item.getNewestChapterId()); |
||||||
|
if (printLog) Log.d(tag, "┌获取简介"); |
||||||
|
item.setDesc(String.valueOf(nativeObject.get(ruleIntroduce))); |
||||||
|
if (printLog) Log.d(tag, "└" + item.getDesc()); |
||||||
|
if (printLog) Log.d(tag, "┌获取封面"); |
||||||
|
if (!isEmpty(ruleCoverUrl)) |
||||||
|
item.setImgUrl(NetworkUtils.getAbsoluteURL(baseUrl, String.valueOf(nativeObject.get(ruleCoverUrl)))); |
||||||
|
if (printLog) Log.d(tag, "└" + item.getImgUrl()); |
||||||
|
if (printLog) Log.d(tag, "┌获取书籍网址"); |
||||||
|
String resultUrl = String.valueOf(nativeObject.get(ruleNoteUrl)); |
||||||
|
if (isEmpty(resultUrl)) resultUrl = baseUrl; |
||||||
|
item.setChapterUrl(resultUrl); |
||||||
|
if (printLog) Log.d(tag, "└" + item.getChapterUrl()); |
||||||
|
return item; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
private Book getItemInList(AnalyzeRule analyzer, String baseUrl, boolean printLog) throws |
||||||
|
Exception { |
||||||
|
Book item = new Book(); |
||||||
|
analyzer.setBook(item); |
||||||
|
if (printLog) Log.d(tag, "┌获取书名"); |
||||||
|
String bookName = StringUtils.formatHtml(analyzer.getString(ruleName)); |
||||||
|
if (printLog) Log.d(tag, "└" + bookName); |
||||||
|
if (!TextUtils.isEmpty(bookName)) { |
||||||
|
item.setTag(tag); |
||||||
|
item.setSource(bookSource.getSourceUrl()); |
||||||
|
item.setName(bookName); |
||||||
|
if (printLog) Log.d(tag, "┌获取作者"); |
||||||
|
item.setAuthor(StringUtils.formatHtml(analyzer.getString(ruleAuthor))); |
||||||
|
if (printLog) Log.d(tag, "└" + item.getAuthor()); |
||||||
|
if (printLog) Log.d(tag, "┌获取分类"); |
||||||
|
item.setType(analyzer.getString(ruleKind)); |
||||||
|
if (printLog) Log.d(tag, "└" + item.getType()); |
||||||
|
if (printLog) Log.d(tag, "┌获取最新章节"); |
||||||
|
item.setNewestChapterTitle(analyzer.getString(ruleLastChapter)); |
||||||
|
if (printLog) Log.d(tag, "└" + item.getNewestChapterTitle()); |
||||||
|
if (printLog) Log.d(tag, "┌获取简介"); |
||||||
|
item.setDesc(analyzer.getString(ruleIntroduce)); |
||||||
|
if (printLog) Log.d(tag, "└" + item.getDesc()); |
||||||
|
if (printLog) Log.d(tag, "┌获取封面"); |
||||||
|
item.setImgUrl(analyzer.getString(ruleCoverUrl, true)); |
||||||
|
if (printLog) Log.d(tag, "└" + item.getImgUrl()); |
||||||
|
Log.d(tag, "┌获取书籍网址"); |
||||||
|
String resultUrl = analyzer.getString(ruleNoteUrl, true); |
||||||
|
if (isEmpty(resultUrl)) resultUrl = baseUrl; |
||||||
|
item.setChapterUrl(resultUrl); |
||||||
|
if (printLog) Log.d(tag, "└" + item.getChapterUrl()); |
||||||
|
return item; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
// 纯java模式正则表达式获取书籍列表
|
||||||
|
private void getBooksOfRegex(String res, String[] regs, |
||||||
|
int index, AnalyzeRule analyzer, final List<Book> books) throws Exception { |
||||||
|
Matcher resM = Pattern.compile(regs[index]).matcher(res); |
||||||
|
String baseUrl = analyzer.getBaseUrl(); |
||||||
|
// 判断规则是否有效,当搜索列表规则无效时当作详情页处理
|
||||||
|
if (!resM.find()) { |
||||||
|
books.add(getItem(analyzer, baseUrl)); |
||||||
|
return; |
||||||
|
} |
||||||
|
// 判断索引的规则是最后一个规则
|
||||||
|
if (index + 1 == regs.length) { |
||||||
|
// 获取规则列表
|
||||||
|
HashMap<String, String> ruleMap = new HashMap<>(); |
||||||
|
ruleMap.put("ruleName", ruleName); |
||||||
|
ruleMap.put("ruleAuthor", ruleAuthor); |
||||||
|
ruleMap.put("ruleKind", ruleKind); |
||||||
|
ruleMap.put("ruleLastChapter", ruleLastChapter); |
||||||
|
ruleMap.put("ruleIntroduce", ruleIntroduce); |
||||||
|
ruleMap.put("ruleCoverUrl", ruleCoverUrl); |
||||||
|
ruleMap.put("ruleNoteUrl", ruleNoteUrl); |
||||||
|
// 分离规则参数
|
||||||
|
List<String> ruleName = new ArrayList<>(); |
||||||
|
List<List<String>> ruleParams = new ArrayList<>(); // 创建规则参数容器
|
||||||
|
List<List<Integer>> ruleTypes = new ArrayList<>(); // 创建规则类型容器
|
||||||
|
List<Boolean> hasVarParams = new ArrayList<>(); // 创建put&get标志容器
|
||||||
|
for (String key : ruleMap.keySet()) { |
||||||
|
String val = ruleMap.get(key); |
||||||
|
ruleName.add(key); |
||||||
|
hasVarParams.add(!TextUtils.isEmpty(val) && (val.contains("@put") || val.contains("@get"))); |
||||||
|
List<String> ruleParam = new ArrayList<>(); |
||||||
|
List<Integer> ruleType = new ArrayList<>(); |
||||||
|
AnalyzeByRegex.splitRegexRule(val, ruleParam, ruleType); |
||||||
|
ruleParams.add(ruleParam); |
||||||
|
ruleTypes.add(ruleType); |
||||||
|
} |
||||||
|
// 提取书籍列表
|
||||||
|
do { |
||||||
|
// 新建书籍容器
|
||||||
|
Book item = new Book(); |
||||||
|
item.setTag(tag); |
||||||
|
item.setSource(bookSource.getSourceUrl()); |
||||||
|
analyzer.setBook(item); |
||||||
|
// 提取规则内容
|
||||||
|
HashMap<String, String> ruleVal = new HashMap<>(); |
||||||
|
StringBuilder infoVal = new StringBuilder(); |
||||||
|
for (int i = ruleParams.size(); i-- > 0; ) { |
||||||
|
List<String> ruleParam = ruleParams.get(i); |
||||||
|
List<Integer> ruleType = ruleTypes.get(i); |
||||||
|
infoVal.setLength(0); |
||||||
|
for (int j = ruleParam.size(); j-- > 0; ) { |
||||||
|
int regType = ruleType.get(j); |
||||||
|
if (regType > 0) { |
||||||
|
infoVal.insert(0, resM.group(regType)); |
||||||
|
} else if (regType < 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
||||||
|
infoVal.insert(0, resM.group(ruleParam.get(j))); |
||||||
|
} else { |
||||||
|
infoVal.insert(0, ruleParam.get(j)); |
||||||
|
} |
||||||
|
} |
||||||
|
ruleVal.put(ruleName.get(i), hasVarParams.get(i) ? AnalyzeByRegex.checkKeys(infoVal.toString(), analyzer) : infoVal.toString()); |
||||||
|
} |
||||||
|
// 保存当前节点的书籍信息
|
||||||
|
item.setName(StringUtils.formatHtml(ruleVal.get("ruleName"))); |
||||||
|
item.setAuthor(StringUtils.formatHtml(ruleVal.get("ruleAuthor"))); |
||||||
|
item.setType(ruleVal.get("ruleKind")); |
||||||
|
item.setNewestChapterTitle(ruleVal.get("ruleLastChapter")); |
||||||
|
item.setDesc(ruleVal.get("ruleIntroduce")); |
||||||
|
item.setImgUrl(ruleVal.get("ruleCoverUrl")); |
||||||
|
item.setChapterUrl(NetworkUtils.getAbsoluteURL(baseUrl, ruleVal.get("ruleNoteUrl"))); |
||||||
|
books.add(item); |
||||||
|
// 判断搜索结果是否为详情页
|
||||||
|
if (books.size() == 1 && (isEmpty(ruleVal.get("ruleNoteUrl")) || ruleVal.get("ruleNoteUrl").equals(baseUrl))) { |
||||||
|
books.get(0).setChapterUrl(baseUrl); |
||||||
|
//books.get(0).setBookInfoHtml(res);
|
||||||
|
return; |
||||||
|
} |
||||||
|
} while (resM.find()); |
||||||
|
// 输出调试信息
|
||||||
|
Log.d(tag, "└找到 " + books.size() + " 个匹配的结果"); |
||||||
|
Log.d(tag, "┌获取书名"); |
||||||
|
Log.d(tag, "└" + books.get(0).getName()); |
||||||
|
Log.d(tag, "┌获取作者"); |
||||||
|
Log.d(tag, "└" + books.get(0).getAuthor()); |
||||||
|
Log.d(tag, "┌获取分类"); |
||||||
|
Log.d(tag, "└" + books.get(0).getType()); |
||||||
|
Log.d(tag, "┌获取最新章节"); |
||||||
|
Log.d(tag, "└" + books.get(0).getNewestChapterTitle()); |
||||||
|
Log.d(tag, "┌获取简介"); |
||||||
|
Log.d(tag, "└" + books.get(0).getDesc()); |
||||||
|
Log.d(tag, "┌获取封面"); |
||||||
|
Log.d(tag, "└" + books.get(0).getImgUrl()); |
||||||
|
Log.d(tag, "┌获取书籍"); |
||||||
|
Log.d(tag, "└" + books.get(0).getChapterUrl()); |
||||||
|
} else { |
||||||
|
StringBuilder result = new StringBuilder(); |
||||||
|
do { |
||||||
|
result.append(resM.group()); |
||||||
|
} while (resM.find()); |
||||||
|
getBooksOfRegex(result.toString(), regs, ++index, analyzer, books); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
package xyz.fycz.myreader.util.help; |
||||||
|
|
||||||
|
public class UTF8BOMFighter { |
||||||
|
private static final byte[] UTF8_BOM_BYTES = new byte[]{(byte) 0xEF, (byte) 0xBB, (byte) 0xBF}; |
||||||
|
|
||||||
|
private UTF8BOMFighter() { |
||||||
|
} |
||||||
|
|
||||||
|
static public String removeUTF8BOM(String xmlText) { |
||||||
|
byte[] bytes = xmlText.getBytes(); |
||||||
|
boolean containsBOM = bytes.length > 3 |
||||||
|
&& bytes[0] == UTF8_BOM_BYTES[0] |
||||||
|
&& bytes[1] == UTF8_BOM_BYTES[1] |
||||||
|
&& bytes[2] == UTF8_BOM_BYTES[2]; |
||||||
|
if (containsBOM) { |
||||||
|
xmlText = new String(bytes, 3, bytes.length - 3); |
||||||
|
} |
||||||
|
return xmlText; |
||||||
|
} |
||||||
|
|
||||||
|
static public byte[] removeUTF8BOM(byte[] bytes) { |
||||||
|
boolean containsBOM = bytes.length > 3 |
||||||
|
&& bytes[0] == UTF8_BOM_BYTES[0] |
||||||
|
&& bytes[1] == UTF8_BOM_BYTES[1] |
||||||
|
&& bytes[2] == UTF8_BOM_BYTES[2]; |
||||||
|
if (containsBOM) { |
||||||
|
byte[] copy = new byte[bytes.length - 3]; |
||||||
|
System.arraycopy(bytes, 3, copy, 0, bytes.length - 3); |
||||||
|
return copy; |
||||||
|
} |
||||||
|
return bytes; |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -1,14 +0,0 @@ |
|||||||
package xyz.fycz.myreader.webapi.crawler.base; |
|
||||||
|
|
||||||
import java.util.Map; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author fengyue |
|
||||||
* @date 2021/5/13 22:29 |
|
||||||
*/ |
|
||||||
public abstract class BaseLocalCrawler implements ReadCrawler { |
|
||||||
@Override |
|
||||||
public Map<String, String> getHeaders() { |
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,37 @@ |
|||||||
|
package xyz.fycz.myreader.webapi.crawler.base; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import xyz.fycz.myreader.entity.SearchBookBean; |
||||||
|
import xyz.fycz.myreader.entity.StrResponse; |
||||||
|
import xyz.fycz.myreader.greendao.entity.Book; |
||||||
|
import xyz.fycz.myreader.greendao.entity.Chapter; |
||||||
|
import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap; |
||||||
|
|
||||||
|
/** |
||||||
|
* 因新版书源使用StrResponse,为了兼容旧版本,书源全部继承自此类 |
||||||
|
* @author fengyue |
||||||
|
* @date 2021/5/13 22:29 |
||||||
|
*/ |
||||||
|
public abstract class BaseReadCrawler implements ReadCrawler { |
||||||
|
@Override |
||||||
|
public Map<String, String> getHeaders() { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public ConcurrentMultiValueMap<SearchBookBean, Book> getBooksFromSearchHtml(String html) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public ArrayList<Chapter> getChaptersFromHtml(String html) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getContentFormHtml(String html) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,72 @@ |
|||||||
|
package xyz.fycz.myreader.webapi.crawler.source; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import xyz.fycz.myreader.entity.SearchBookBean; |
||||||
|
import xyz.fycz.myreader.greendao.entity.Book; |
||||||
|
import xyz.fycz.myreader.greendao.entity.rule.BookSource; |
||||||
|
import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap; |
||||||
|
import xyz.fycz.myreader.webapi.crawler.base.BaseReadCrawler; |
||||||
|
import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author fengyue |
||||||
|
* @date 2021/5/14 10:55 |
||||||
|
*/ |
||||||
|
public class ThirdCrawler extends BaseReadCrawler implements BookInfoCrawler { |
||||||
|
private BookSource source; |
||||||
|
|
||||||
|
public ThirdCrawler(BookSource source) { |
||||||
|
this.source = source; |
||||||
|
} |
||||||
|
|
||||||
|
public BookSource getSource() { |
||||||
|
return source; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getSearchLink() { |
||||||
|
return source.getSearchRule().getSearchUrl(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getCharset() { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Book getBookInfo(String html, Book book) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getSearchCharset() { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getNameSpace() { |
||||||
|
return source.getSourceUrl(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Boolean isPost() { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Map<String, String> getHeaders() { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public ConcurrentMultiValueMap<SearchBookBean, Book> getBooks(List<Book> books) { |
||||||
|
ConcurrentMultiValueMap<SearchBookBean, Book> newBooks = new ConcurrentMultiValueMap<>(); |
||||||
|
for (Book book : books){ |
||||||
|
book.setSource(source.getSourceUrl()); |
||||||
|
SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor()); |
||||||
|
newBooks.add(sbb, book); |
||||||
|
} |
||||||
|
return newBooks; |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue