diff --git a/app/src/main/java/xyz/fycz/myreader/greendao/service/CookieStore.kt b/app/src/main/java/xyz/fycz/myreader/greendao/service/CookieStore.kt new file mode 100644 index 0000000..1f23a28 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/greendao/service/CookieStore.kt @@ -0,0 +1,78 @@ +@file:Suppress("unused") + +package xyz.fycz.myreader.greendao.service + +import android.text.TextUtils +import xyz.fycz.myreader.greendao.DbManager +import xyz.fycz.myreader.greendao.entity.CookieBean +import xyz.fycz.myreader.greendao.service.api.CookieManager +import xyz.fycz.myreader.util.utils.NetworkUtils + +object CookieStore : CookieManager { + + override fun setCookie(url: String, cookie: String?) { + val cookieBean = CookieBean(NetworkUtils.getSubDomain(url), cookie ?: "") + DbManager.getDaoSession().cookieBeanDao.insertOrReplace(cookieBean) + } + + override fun replaceCookie(url: String, cookie: String) { + if (TextUtils.isEmpty(url) || TextUtils.isEmpty(cookie)) { + return + } + val oldCookie = getCookie(url) + if (TextUtils.isEmpty(oldCookie)) { + setCookie(url, cookie) + } else { + val cookieMap = cookieToMap(oldCookie) + cookieMap.putAll(cookieToMap(cookie)) + val newCookie = mapToCookie(cookieMap) + setCookie(url, newCookie) + } + } + + override fun getCookie(url: String): String { + val cookieBean = DbManager.getDaoSession().cookieBeanDao.load(NetworkUtils.getSubDomain(url)) + return cookieBean?.cookie ?: "" + } + + override fun removeCookie(url: String) { + DbManager.getDaoSession().cookieBeanDao.deleteByKey(NetworkUtils.getSubDomain(url)) + } + + override fun cookieToMap(cookie: String): MutableMap { + val cookieMap = mutableMapOf() + if (cookie.isBlank()) { + return cookieMap + } + val pairArray = cookie.split(";".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + for (pair in pairArray) { + val pairs = pair.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + if (pairs.size == 1) { + continue + } + val key = pairs[0].trim { it <= ' ' } + val value = pairs[1] + if (value.isNotBlank() || value.trim { it <= ' ' } == "null") { + cookieMap[key] = value.trim { it <= ' ' } + } + } + return cookieMap + } + + override fun mapToCookie(cookieMap: Map?): String? { + if (cookieMap == null || cookieMap.isEmpty()) { + return null + } + val builder = StringBuilder() + for (key in cookieMap.keys) { + val value = cookieMap[key] + if (value?.isNotBlank() == true) { + builder.append(key) + .append("=") + .append(value) + .append(";") + } + } + return builder.deleteCharAt(builder.lastIndexOf(";")).toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/greendao/service/api/CookieManager.kt b/app/src/main/java/xyz/fycz/myreader/greendao/service/api/CookieManager.kt new file mode 100644 index 0000000..0625ff1 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/greendao/service/api/CookieManager.kt @@ -0,0 +1,28 @@ +package xyz.fycz.myreader.greendao.service.api + +interface CookieManager { + + /** + * 保存cookie + */ + fun setCookie(url: String, cookie: String?) + + /** + * 替换cookie + */ + fun replaceCookie(url: String, cookie: String) + + /** + * 获取cookie + */ + fun getCookie(url: String): String + + /** + * 移除cookie + */ + fun removeCookie(url: String) + + fun cookieToMap(cookie: String): MutableMap + + fun mapToCookie(cookieMap: Map?): String? +} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/model/third2/analyzeRule/AnalyzeRule.java b/app/src/main/java/xyz/fycz/myreader/model/third2/analyzeRule/AnalyzeRule.java index 7417821..1c65732 100644 --- a/app/src/main/java/xyz/fycz/myreader/model/third2/analyzeRule/AnalyzeRule.java +++ b/app/src/main/java/xyz/fycz/myreader/model/third2/analyzeRule/AnalyzeRule.java @@ -18,6 +18,7 @@ import java.util.regex.Pattern; import javax.script.SimpleBindings; import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.service.CookieStore; import xyz.fycz.myreader.util.StringHelper; import xyz.fycz.myreader.util.help.JsExtensions; import xyz.fycz.myreader.util.utils.NetworkUtils; @@ -533,9 +534,9 @@ public class AnalyzeRule implements JsExtensions { private Object evalJS(String jsStr, Object result) throws Exception { SimpleBindings bindings = new SimpleBindings(); bindings.put("java", this); + bindings.put("cookie", CookieStore.INSTANCE); bindings.put("result", result); bindings.put("baseUrl", baseUrl); - bindings.put("book", book); return SCRIPT_ENGINE.eval(jsStr, bindings); } diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java index dc39def..b96b56a 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java @@ -318,6 +318,7 @@ public class BookDetailedActivity extends BaseActivity { mSourceDialog.setOnSourceChangeListener((bean, pos) -> { Book bookTem = (Book) mBook.clone(); bookTem.setChapterUrl(bean.getChapterUrl()); + bookTem.setInfoUrl(bean.getInfoUrl()); if (!StringHelper.isEmpty(bean.getImgUrl())) { bookTem.setImgUrl(bean.getImgUrl()); } @@ -327,6 +328,15 @@ public class BookDetailedActivity extends BaseActivity { if (!StringHelper.isEmpty(bean.getDesc())) { bookTem.setDesc(bean.getDesc()); } + if (!StringHelper.isEmpty(bean.getUpdateDate())){ + bookTem.setUpdateDate(bean.getUpdateDate()); + } + if (!StringHelper.isEmpty(bean.getWordCount())){ + bookTem.setWordCount(bean.getWordCount()); + } + if (!StringHelper.isEmpty(bean.getStatus())){ + bookTem.setStatus(bean.getStatus()); + } bookTem.setSource(bean.getSource()); if (isCollected) { mBookService.updateBook(mBook, bookTem); diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/SourceLoginActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/SourceLoginActivity.java index 0313386..16aaec5 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/SourceLoginActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/SourceLoginActivity.java @@ -15,9 +15,8 @@ import xyz.fycz.myreader.R; import xyz.fycz.myreader.base.BaseActivity; import xyz.fycz.myreader.common.APPCONST; import xyz.fycz.myreader.databinding.ActivitySourceLoginBinding; -import xyz.fycz.myreader.greendao.DbManager; -import xyz.fycz.myreader.greendao.entity.CookieBean; import xyz.fycz.myreader.greendao.entity.rule.BookSource; +import xyz.fycz.myreader.greendao.service.CookieStore; import xyz.fycz.myreader.util.ToastUtils; /** @@ -67,14 +66,14 @@ public class SourceLoginActivity extends BaseActivity { @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { String cookie = cookieManager.getCookie(url); - DbManager.getDaoSession().getCookieBeanDao().insertOrReplace(new CookieBean(bookSource.getLoginUrl(), cookie)); + CookieStore.INSTANCE.setCookie(bookSource.getSourceUrl(), cookie); super.onPageStarted(view, url, favicon); } @Override public void onPageFinished(WebView view, String url) { String cookie = cookieManager.getCookie(url); - DbManager.getDaoSession().getCookieBeanDao().insertOrReplace(new CookieBean(bookSource.getLoginUrl(), cookie)); + CookieStore.INSTANCE.setCookie(bookSource.getSourceUrl(), cookie); if (checking) finish(); else diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/SplashActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/SplashActivity.java index 414543c..1a01110 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/SplashActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/SplashActivity.java @@ -269,7 +269,7 @@ public class SplashActivity extends BaseActivity { SharedPreUtils preUtils = SharedPreUtils.getInstance(); String splashImageMD5 = preUtils.getString("splashImageMD5"); if (!imgFile.exists() || preUtils.getBoolean("needUdSI") || - !splashImageMD5.equals(MD5Utils.getFileMD5s(imgFile, 16))) { + !splashImageMD5.equals(MD5Utils.INSTANCE.getFileMD5s(imgFile, 16))) { if ("".equals(splashImageMD5)) return; downLoadImage(); return; diff --git a/app/src/main/java/xyz/fycz/myreader/util/help/JSExtensions.kt b/app/src/main/java/xyz/fycz/myreader/util/help/JSExtensions.kt new file mode 100644 index 0000000..60e8c4e --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/help/JSExtensions.kt @@ -0,0 +1,365 @@ +package xyz.fycz.myreader.util.help + +import android.util.Base64 +import android.util.Log +import androidx.annotation.Keep +import org.jsoup.Connection +import org.jsoup.Jsoup +import xyz.fycz.myreader.greendao.service.CookieStore +import xyz.fycz.myreader.model.third2.analyzeRule.AnalyzeUrl +import xyz.fycz.myreader.util.ZipUtils +import xyz.fycz.myreader.util.utils.* +import java.io.File +import java.io.IOException +import java.net.URLEncoder +import java.text.SimpleDateFormat +import java.util.* + +/** + * @author fengyue + * @date 2021/5/15 19:26 + */ +@Keep +@Suppress("unused") +interface JSExtensions { + /** + * js实现跨域访问,不能删 + */ + fun ajax(urlStr: String?): String? { + return try { + val analyzeUrl = AnalyzeUrl(urlStr) + OkHttpUtils.getStrResponse(analyzeUrl).blockingFirst().body() + } catch (e: Exception) { + e.localizedMessage + } + } + + + /** + * js实现压缩文件解压 + */ + fun unzipFile(zipPath: String): String { + if (zipPath.isEmpty()) return "" + val unzipPath = FileUtils.getCachePath() + File.separator + FileUtils.getNameExcludeExtension(zipPath) + FileUtils.deleteFile(unzipPath) + val zipFile = FileUtils.getFile(zipPath) + val unzipFolder = FileUtils.getFolder(unzipPath) + ZipUtils.unzipFile(zipFile, unzipFolder) + FileUtils.deleteFile(zipPath) + return unzipPath + } + + /** + * js实现文件夹内所有文件读取 + */ + fun getTxtInFolder(unzipPath: String): String { + if (unzipPath.isEmpty()) return "" + val unzipFolder = FileUtils.getFolder(unzipPath) + val contents = StringBuilder() + unzipFolder.listFiles().let { + if (it != null) { + for (f in it) { + val charsetName = FileUtils.getFileCharset(f) + contents.append(String(f.readBytes(), charset(charsetName))) + .append("\n") + } + contents.deleteCharAt(contents.length - 1) + } + } + FileUtils.deleteFile(unzipPath) + return contents.toString() + } + + /** + * js实现重定向拦截,不能删 + */ + @Throws(IOException::class) + operator fun get(urlStr: String?, headers: Map?): Connection.Response? { + return Jsoup.connect(urlStr) + .sslSocketFactory(SSLSocketClient.createSSLSocketFactory()) + .ignoreContentType(true) + .followRedirects(false) + .headers(headers) + .method(Connection.Method.GET) + .execute() + } + + /** + * js实现重定向拦截,不能删 + */ + @Throws(IOException::class) + fun post( + urlStr: String?, + body: String?, + headers: Map? + ): Connection.Response? { + return Jsoup.connect(urlStr) + .sslSocketFactory(SSLSocketClient.createSSLSocketFactory()) + .ignoreContentType(true) + .followRedirects(false) + .requestBody(body) + .headers(headers) + .method(Connection.Method.POST) + .execute() + } + + /** + *js实现读取cookie + */ + fun getCookie(tag: String, key: String? = null): String { + val cookie = CookieStore.getCookie(tag) + val cookieMap = CookieStore.cookieToMap(cookie) + return if (key != null) { + cookieMap[key] ?: "" + } else { + cookie + } + } + + /** + * js实现解码,不能删 + */ + fun base64Decode(str: String): String { + return EncoderUtils.base64Decode(str, Base64.NO_WRAP) + } + + fun base64Decode(str: String, flags: Int): String { + return EncoderUtils.base64Decode(str, flags) + } + + fun base64DecodeToByteArray(str: String?): ByteArray? { + if (str.isNullOrBlank()) { + return null + } + return Base64.decode(str, Base64.DEFAULT) + } + + fun base64DecodeToByteArray(str: String?, flags: Int): ByteArray? { + if (str.isNullOrBlank()) { + return null + } + return Base64.decode(str, flags) + } + + fun base64Encode(str: String): String? { + return EncoderUtils.base64Encode(str, Base64.NO_WRAP) + } + + fun base64Encode(str: String, flags: Int): String? { + return EncoderUtils.base64Encode(str, flags) + } + + fun md5Encode(str: String): String { + return MD5Utils.md5Encode(str) + } + + fun md5Encode16(str: String): String { + return MD5Utils.md5Encode16(str) + } + + /** + * 时间格式化 + */ + fun timeFormat(time: Long): String { + val sdf = SimpleDateFormat("yyyy/MM/dd HH:mm") + return sdf.format(Date(time)) + } + + /** + * utf8编码转gbk编码 + */ + fun utf8ToGbk(str: String): String { + val utf8 = String(str.toByteArray(charset("UTF-8"))) + val unicode = String(utf8.toByteArray(), charset("UTF-8")) + return String(unicode.toByteArray(charset("GBK"))) + } + + fun encodeURI(str: String): String { + return try { + URLEncoder.encode(str, "UTF-8") + } catch (e: Exception) { + "" + } + } + + fun encodeURI(str: String, enc: String): String { + return try { + URLEncoder.encode(str, enc) + } catch (e: Exception) { + "" + } + } + + fun htmlFormat(str: String): String { + return StringUtils.formatHtml(str) + } + + /** + * 读取本地文件 + */ + fun readFile(path: String): ByteArray { + return File(path).readBytes() + } + + fun readTxtFile(path: String): String { + val f = File(path) + val charsetName = FileUtils.getFileCharset(f) + return String(f.readBytes(), charset(charsetName)) + } + + fun readTxtFile(path: String, charsetName: String): String { + return String(File(path).readBytes(), charset(charsetName)) + } + + /** + * 输出调试日志 + */ + fun log(msg: String): String { + Log.d("JS", msg) + return msg + } + + /** + * AES 解码为 ByteArray + * @param str 传入的AES加密的数据 + * @param key AES 解密的key + * @param transformation AES加密的方式 + * @param iv ECB模式的偏移向量 + */ + fun aesDecodeToByteArray( + str: String, + key: String, + transformation: String, + iv: String = "" + ): ByteArray? { + + return EncoderUtils.decryptAES( + data = str.encodeToByteArray(), + key = key.encodeToByteArray(), + transformation, + iv.encodeToByteArray() + ) + } + + /** + * AES 解码为 String + * @param str 传入的AES加密的数据 + * @param key AES 解密的key + * @param transformation AES加密的方式 + * @param iv ECB模式的偏移向量 + */ + + fun aesDecodeToString( + str: String, + key: String, + transformation: String, + iv: String = "" + ): String? { + return aesDecodeToByteArray(str, key, transformation, iv)?.let { String(it) } + } + + /** + * 已经base64的AES 解码为 ByteArray + * @param str 传入的AES Base64加密的数据 + * @param key AES 解密的key + * @param transformation AES加密的方式 + * @param iv ECB模式的偏移向量 + */ + + fun aesBase64DecodeToByteArray( + str: String, + key: String, + transformation: String, + iv: String = "" + ): ByteArray? { + return EncoderUtils.decryptBase64AES( + data = str.encodeToByteArray(), + key = key.encodeToByteArray(), + transformation, + iv.encodeToByteArray() + ) + } + + /** + * 已经base64的AES 解码为 String + * @param str 传入的AES Base64加密的数据 + * @param key AES 解密的key + * @param transformation AES加密的方式 + * @param iv ECB模式的偏移向量 + */ + + fun aesBase64DecodeToString( + str: String, + key: String, + transformation: String, + iv: String = "" + ): String? { + return aesBase64DecodeToByteArray(str, key, transformation, iv)?.let { String(it) } + } + + /** + * 加密aes为ByteArray + * @param data 传入的原始数据 + * @param key AES加密的key + * @param transformation AES加密的方式 + * @param iv ECB模式的偏移向量 + */ + fun aesEncodeToByteArray( + data: String, key: String, transformation: String, + iv: String = "" + ): ByteArray? { + return EncoderUtils.encryptAES( + data.encodeToByteArray(), + key = key.encodeToByteArray(), + transformation, + iv.encodeToByteArray() + ) + } + + /** + * 加密aes为String + * @param data 传入的原始数据 + * @param key AES加密的key + * @param transformation AES加密的方式 + * @param iv ECB模式的偏移向量 + */ + fun aesEncodeToString( + data: String, key: String, transformation: String, + iv: String = "" + ): String? { + return aesEncodeToByteArray(data, key, transformation, iv)?.let { String(it) } + } + + /** + * 加密aes后Base64化的ByteArray + * @param data 传入的原始数据 + * @param key AES加密的key + * @param transformation AES加密的方式 + * @param iv ECB模式的偏移向量 + */ + fun aesEncodeToBase64ByteArray( + data: String, key: String, transformation: String, + iv: String = "" + ): ByteArray? { + return EncoderUtils.encryptAES2Base64( + data.encodeToByteArray(), + key = key.encodeToByteArray(), + transformation, + iv.encodeToByteArray() + ) + } + + /** + * 加密aes后Base64化的String + * @param data 传入的原始数据 + * @param key AES加密的key + * @param transformation AES加密的方式 + * @param iv ECB模式的偏移向量 + */ + fun aesEncodeToBase64String( + data: String, key: String, transformation: String, + iv: String = "" + ): String? { + return aesEncodeToBase64ByteArray(data, key, transformation, iv)?.let { String(it) } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/util/help/JsExtensions.java b/app/src/main/java/xyz/fycz/myreader/util/help/JsExtensions.java index 94bf2ed..46daad0 100644 --- a/app/src/main/java/xyz/fycz/myreader/util/help/JsExtensions.java +++ b/app/src/main/java/xyz/fycz/myreader/util/help/JsExtensions.java @@ -1,22 +1,38 @@ package xyz.fycz.myreader.util.help; +import android.text.TextUtils; +import android.util.Base64; +import android.util.Log; + import org.jsoup.Connection; import org.jsoup.Jsoup; +import java.io.File; import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import xyz.fycz.myreader.greendao.service.CookieStore; import xyz.fycz.myreader.model.third2.analyzeRule.AnalyzeUrl; +import xyz.fycz.myreader.util.ZipUtils; +import xyz.fycz.myreader.util.utils.EncoderUtils; +import xyz.fycz.myreader.util.utils.EncodingDetect; +import xyz.fycz.myreader.util.utils.FileUtils; +import xyz.fycz.myreader.util.utils.MD5Utils; import xyz.fycz.myreader.util.utils.OkHttpUtils; import xyz.fycz.myreader.util.utils.StringUtils; @SuppressWarnings({"unused", "WeakerAccess"}) public interface JsExtensions { + String TAG = JsExtensions.class.getSimpleName(); /** * js实现跨域访问,不能删 @@ -31,27 +47,41 @@ public interface JsExtensions { } /** - * js实现解码,不能删 + * js实现压缩文件解压 */ - default String base64Decoder(String base64) { - return StringUtils.base64Decode(base64); + default String unzipFile(String zipPath) throws IOException { + if (zipPath.isEmpty()) return ""; + String unzipPath = FileUtils.getCachePath() + File.separator + FileUtils.getNameExcludeExtension(zipPath); + FileUtils.deleteFile(unzipPath); + File zipFile = FileUtils.getFile(zipPath); + File unzipFolder = FileUtils.getFolder(unzipPath); + ZipUtils.unzipFile(zipFile, unzipFolder); + FileUtils.deleteFile(zipPath); + return unzipPath; } /** - * 章节数转数字 + * js实现文件夹内所有文件读取 */ - default String toNumChapter(String s) { - if (s == null) { - return null; - } - Pattern pattern = Pattern.compile("(第)(.+?)(章)"); - Matcher matcher = pattern.matcher(s); - if (matcher.find()) { - return matcher.group(1) + StringUtils.stringToInt(matcher.group(2)) + matcher.group(3); + default String getTxtInFolder(String unzipPath) throws UnsupportedEncodingException { + if (unzipPath.isEmpty()) return ""; + File unzipFolder = FileUtils.getFolder(unzipPath); + StringBuilder contents = new StringBuilder(); + File[] files = unzipFolder.listFiles(); + if (files != null) { + for (File file : files) { + String charsetName = FileUtils.getFileCharset(file); + contents.append(new String(FileUtils.getBytes(file), Charset.forName(charsetName))) + .append("\n"); + } + if (contents.length() > 0) + contents.deleteCharAt(contents.length() - 1); } - return s; + FileUtils.deleteFile(unzipPath); + return contents.toString(); } + /** * js实现重定向拦截,不能删 */ @@ -79,6 +109,79 @@ public interface JsExtensions { .execute(); } + /** + * js实现读取cookie + */ + default String getCookie(String tag, String key) { + String cookie = CookieStore.INSTANCE.getCookie(tag); + Map cookieMap = CookieStore.INSTANCE.cookieToMap(cookie); + if (key != null) { + return cookieMap.get(key) == null ? "" : cookieMap.get(key); + } else { + return cookie; + } + } + + default String getCookie(String tag) { + return getCookie(tag, null); + } + + /** + * js实现解码,不能删 + */ + default String base64Decoder(String base64) { + return EncoderUtils.INSTANCE.base64Decode(base64, Base64.NO_WRAP); + } + + default String base64Decoder(String base64, int flags) { + return EncoderUtils.INSTANCE.base64Decode(base64, flags); + } + + default byte[] base64DecodeToByteArray(String str) { + if (TextUtils.isEmpty(str)) { + return null; + } + return Base64.decode(str, Base64.DEFAULT); + } + + default byte[] base64DecodeToByteArray(String str, int flags) { + if (TextUtils.isEmpty(str)) { + return null; + } + return Base64.decode(str, flags); + } + + default String base64Encode(String base64) { + return EncoderUtils.INSTANCE.base64Encode(base64, Base64.NO_WRAP); + } + + default String base64Encode(String base64, int flags) { + return EncoderUtils.INSTANCE.base64Encode(base64, flags); + } + + default String md5Encode(String str) { + return MD5Utils.INSTANCE.md5Encode(str); + } + + default String md5Encode16(String str) { + return MD5Utils.INSTANCE.md5Encode16(str); + } + + /** + * 章节数转数字 + */ + default String toNumChapter(String s) { + if (s == null) { + return null; + } + Pattern pattern = Pattern.compile("(第)(.+?)(章)"); + Matcher matcher = pattern.matcher(s); + if (matcher.find()) { + return matcher.group(1) + StringUtils.stringToInt(matcher.group(2)) + matcher.group(3); + } + return s; + } + /** * 时间格式化 */ @@ -86,4 +189,224 @@ public interface JsExtensions { SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm"); return sdf.format(new Date(time)); } + + /** + * utf8编码转gbk编码 + */ + default String utf8ToGbk(String str) { + String utf8 = new String(str.getBytes(StandardCharsets.UTF_8)); + String unicode = new String(utf8.getBytes(), StandardCharsets.UTF_8); + return new String(unicode.getBytes(Charset.forName("GBK"))); + } + + default String encodeURI(String str) { + try { + return URLEncoder.encode(str, "UTF-8"); + } catch (Exception e) { + return ""; + } + } + + default String encodeURI(String str, String enc) { + try { + return URLEncoder.encode(str, enc); + } catch (Exception e) { + return ""; + } + } + + /** + * 读取本地文件 + */ + default byte[] readFile(String path) { + return FileUtils.getBytes(new File(path)); + } + + default String readTxtFile(String path) { + File f = new File(path); + String charsetName = FileUtils.getFileCharset(f); + return new String(FileUtils.getBytes(f), Charset.forName(charsetName)); + } + + default String readTxtFile(String path, String charsetName) { + return new String(FileUtils.getBytes(new File(path)), Charset.forName(charsetName)); + } + + /** + * 输出调试日志 + */ + default String log(String msg) { + Log.d(TAG, msg); + return msg; + } + + + /** + * AES 解码为 ByteArray + * + * @param str 传入的AES加密的数据 + * @param key AES 解密的key + * @param transformation AES加密的方式 + * @param iv ECB模式的偏移向量 + */ + default byte[] aesDecodeToByteArray(String str, String key, String transformation, String iv) { + + return EncoderUtils.INSTANCE.decryptAES( + str.getBytes(StandardCharsets.UTF_8), + key.getBytes(StandardCharsets.UTF_8), + transformation, + iv.getBytes(StandardCharsets.UTF_8) + ); + } + + default byte[] aesDecodeToByteArray(String str, String key, String transformation) { + return aesDecodeToByteArray(str, key, transformation, ""); + } + + /** + * AES 解码为 String + * + * @param str 传入的AES加密的数据 + * @param key AES 解密的key + * @param transformation AES加密的方式 + * @param iv ECB模式的偏移向量 + */ + + default String aesDecodeToString(String str, String key, String transformation, String iv) { + byte[] bytes = aesDecodeToByteArray(str, key, transformation, iv); + if (bytes == null) { + return ""; + } + return new String(bytes); + } + + default String aesDecodeToString(String str, String key, String transformation) { + return aesDecodeToString(str, key, transformation, ""); + } + + /** + * 已经base64的AES 解码为 ByteArray + * + * @param str 传入的AES Base64加密的数据 + * @param key AES 解密的key + * @param transformation AES加密的方式 + * @param iv ECB模式的偏移向量 + */ + + default byte[] aesBase64DecodeToByteArray(String str, String key, String transformation, String iv) { + return EncoderUtils.INSTANCE.decryptBase64AES( + str.getBytes(StandardCharsets.UTF_8), + key.getBytes(StandardCharsets.UTF_8), + transformation, + iv.getBytes(StandardCharsets.UTF_8) + ); + } + + default byte[] aesBase64DecodeToByteArray(String str, String key, String transformation) { + return aesBase64DecodeToByteArray(str, key, transformation, ""); + } + + + /** + * 已经base64的AES 解码为 String + * + * @param str 传入的AES Base64加密的数据 + * @param key AES 解密的key + * @param transformation AES加密的方式 + * @param iv ECB模式的偏移向量 + */ + + default String aesBase64DecodeToString(String str, String key, String transformation, String iv) { + byte[] bytes = aesBase64DecodeToByteArray(str, key, transformation, iv); + if (bytes == null) { + return ""; + } + return new String(bytes); + } + + default String aesBase64DecodeToString(String str, String key, String transformation) { + return aesBase64DecodeToString(str, key, transformation, ""); + } + + /** + * 加密aes为ByteArray + * + * @param data 传入的原始数据 + * @param key AES加密的key + * @param transformation AES加密的方式 + * @param iv ECB模式的偏移向量 + */ + default byte[] aesEncodeToByteArray(String data, String key, String transformation, String iv) { + return EncoderUtils.INSTANCE.encryptAES( + data.getBytes(StandardCharsets.UTF_8), + key.getBytes(StandardCharsets.UTF_8), + transformation, + iv.getBytes(StandardCharsets.UTF_8) + ); + } + + default byte[] aesEncodeToByteArray(String data, String key, String transformation) { + return aesEncodeToByteArray(data, key, transformation, ""); + } + + /** + * 加密aes为String + * + * @param data 传入的原始数据 + * @param key AES加密的key + * @param transformation AES加密的方式 + * @param iv ECB模式的偏移向量 + */ + default String aesEncodeToString(String data, String key, String transformation, String iv) { + byte[] bytes = aesEncodeToByteArray(data, key, transformation, iv); + if (bytes == null) { + return ""; + } + return new String(bytes); + } + + default String aesEncodeToString(String str, String key, String transformation) { + return aesEncodeToString(str, key, transformation, ""); + } + + /** + * 加密aes后Base64化的ByteArray + * + * @param data 传入的原始数据 + * @param key AES加密的key + * @param transformation AES加密的方式 + * @param iv ECB模式的偏移向量 + */ + default byte[] aesEncodeToBase64ByteArray(String data, String key, String transformation, String iv) { + return EncoderUtils.INSTANCE.encryptAES2Base64( + data.getBytes(StandardCharsets.UTF_8), + key.getBytes(StandardCharsets.UTF_8), + transformation, + iv.getBytes(StandardCharsets.UTF_8) + ); + } + + default byte[] aesEncodeToBase64ByteArray(String data, String key, String transformation) { + return aesEncodeToBase64ByteArray(data, key, transformation, ""); + } + + /** + * 加密aes后Base64化的String + * + * @param data 传入的原始数据 + * @param key AES加密的key + * @param transformation AES加密的方式 + * @param iv ECB模式的偏移向量 + */ + default String aesEncodeToBase64String(String data, String key, String transformation, String iv) { + byte[] bytes = aesEncodeToBase64ByteArray(data, key, transformation, iv); + if (bytes == null) { + return ""; + } + return new String(bytes); + } + + default String aesEncodeToBase64String(String str, String key, String transformation) { + return aesEncodeToBase64String(str, key, transformation, ""); + } } diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/EncoderUtils.kt b/app/src/main/java/xyz/fycz/myreader/util/utils/EncoderUtils.kt new file mode 100644 index 0000000..834ed4e --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/utils/EncoderUtils.kt @@ -0,0 +1,141 @@ +package xyz.fycz.myreader.util.utils + +import android.util.Base64 +import java.security.spec.AlgorithmParameterSpec +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + +@Suppress("unused") +object EncoderUtils { + @JvmOverloads + fun base64Decode(str: String, flags: Int = Base64.DEFAULT): String { + val bytes = Base64.decode(str, flags) + return String(bytes) + } + + @JvmOverloads + fun base64Encode(str: String, flags: Int = Base64.NO_WRAP): String? { + return Base64.encodeToString(str.toByteArray(), flags) + } + //////////AES Start + + /** + * Return the Base64-encode bytes of AES encryption. + * + * @param data The data. + * @param key The key. + * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*. + * @param iv The buffer with the IV. The contents of the + * buffer are copied to protect against subsequent modification. + * @return the Base64-encode bytes of AES encryption + */ + fun encryptAES2Base64( + data: ByteArray?, + key: ByteArray?, + transformation: String?, + iv: ByteArray? + ): ByteArray? { + return Base64.encode(encryptAES(data, key, transformation, iv), Base64.NO_WRAP) + } + + /** + * Return the bytes of AES encryption. + * + * @param data The data. + * @param key The key. + * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*. + * @param iv The buffer with the IV. The contents of the + * buffer are copied to protect against subsequent modification. + * @return the bytes of AES encryption + */ + fun encryptAES( + data: ByteArray?, + key: ByteArray?, + transformation: String?, + iv: ByteArray? + ): ByteArray? { + return symmetricTemplate(data, key, "AES", transformation!!, iv, true) + } + + + /** + * Return the bytes of AES decryption for Base64-encode bytes. + * + * @param data The data. + * @param key The key. + * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*. + * @param iv The buffer with the IV. The contents of the + * buffer are copied to protect against subsequent modification. + * @return the bytes of AES decryption for Base64-encode bytes + */ + fun decryptBase64AES( + data: ByteArray?, + key: ByteArray?, + transformation: String?, + iv: ByteArray? + ): ByteArray? { + return decryptAES(Base64.decode(data, Base64.NO_WRAP), key, transformation, iv) + } + + /** + * Return the bytes of AES decryption. + * + * @param data The data. + * @param key The key. + * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*. + * @param iv The buffer with the IV. The contents of the + * buffer are copied to protect against subsequent modification. + * @return the bytes of AES decryption + */ + fun decryptAES( + data: ByteArray?, + key: ByteArray?, + transformation: String?, + iv: ByteArray? + ): ByteArray? { + return symmetricTemplate(data, key, "AES", transformation!!, iv, false) + } + + + /** + * Return the bytes of symmetric encryption or decryption. + * + * @param data The data. + * @param key The key. + * @param algorithm The name of algorithm. + * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. + * @param isEncrypt True to encrypt, false otherwise. + * @return the bytes of symmetric encryption or decryption + */ + + private fun symmetricTemplate( + data: ByteArray?, + key: ByteArray?, + algorithm: String, + transformation: String, + iv: ByteArray?, + isEncrypt: Boolean + ): ByteArray? { + return if (data == null || data.isEmpty() || key == null || key.isEmpty()) null else try { + val keySpec = SecretKeySpec(key, algorithm) + val cipher = Cipher.getInstance(transformation) + if (iv == null || iv.isEmpty()) { + cipher.init(if (isEncrypt) Cipher.ENCRYPT_MODE else Cipher.DECRYPT_MODE, keySpec) + } else { + val params: AlgorithmParameterSpec = IvParameterSpec(iv) + cipher.init( + if (isEncrypt) Cipher.ENCRYPT_MODE else Cipher.DECRYPT_MODE, + keySpec, + params + ) + } + cipher.doFinal(data) + } catch (e: Throwable) { + e.printStackTrace() + null + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/FileUtils.java b/app/src/main/java/xyz/fycz/myreader/util/utils/FileUtils.java index 3f59893..bebd01a 100644 --- a/app/src/main/java/xyz/fycz/myreader/util/utils/FileUtils.java +++ b/app/src/main/java/xyz/fycz/myreader/util/utils/FileUtils.java @@ -256,7 +256,7 @@ public class FileUtils { FileOutputStream fos = null; File temFile = null; try { - temFile = getFile(APPCONST.TEM_FILE_DIR + "tem.fy"); + temFile = getFile(getCachePath() + File.separator + "tem.fy"); fis = new FileInputStream(file); fos = new FileOutputStream(temFile); //用10kb作为试探 @@ -878,83 +878,6 @@ public class FileUtils { return null; } - public static byte[] File2byte(String filePath) - { - byte[] buffer = null; - try - { - File file = new File(filePath); - FileInputStream fis = new FileInputStream(file); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - byte[] b = new byte[1024]; - int n; - while ((n = fis.read(b)) != -1) - { - bos.write(b, 0, n); - } - fis.close(); - bos.close(); - buffer = bos.toByteArray(); - } - catch (FileNotFoundException e) - { - e.printStackTrace(); - } - catch (IOException e) - { - e.printStackTrace(); - } - return buffer; - } - - public static void byte2File(byte[] buf, String filePath, String fileName) - { - BufferedOutputStream bos = null; - FileOutputStream fos = null; - File file = null; - try - { - File dir = new File(filePath); - if (!dir.exists() && dir.isDirectory()) - { - dir.mkdirs(); - } - file = new File(filePath + File.separator + fileName); - fos = new FileOutputStream(file); - bos = new BufferedOutputStream(fos); - bos.write(buf); - } - catch (Exception e) - { - e.printStackTrace(); - } - finally - { - if (bos != null) - { - try - { - bos.close(); - } - catch (IOException e) - { - e.printStackTrace(); - } - } - if (fos != null) - { - try - { - fos.close(); - } - catch (IOException e) - { - e.printStackTrace(); - } - } - } - } - public static String getFileSuffix(String filePath) { File file = new File(filePath); return getFileSuffix(file); @@ -968,4 +891,21 @@ public class FileUtils { int dotIndex = fileName.lastIndexOf("."); return dotIndex > 0 ? fileName.substring(dotIndex) : ""; } + + /** + * 获取文件名(不包括扩展名) + */ + public static String getNameExcludeExtension(String filePath) { + try { + String fileName = new File(filePath).getName(); + int lastIndexOf = fileName.lastIndexOf("."); + if (lastIndexOf != -1) { + fileName = fileName.substring(0, lastIndexOf); + } + return fileName; + } catch (Exception e) { + return ""; + } + } + } diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/MD5Utils.java b/app/src/main/java/xyz/fycz/myreader/util/utils/MD5Utils.java deleted file mode 100644 index 1b2b57d..0000000 --- a/app/src/main/java/xyz/fycz/myreader/util/utils/MD5Utils.java +++ /dev/null @@ -1,73 +0,0 @@ -package xyz.fycz.myreader.util.utils; - -import java.io.File; -import java.io.FileInputStream; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -import xyz.fycz.myreader.util.IOUtils; - - -public class MD5Utils { - - public static String strToMd5By32(String str) { - String reStr = null; - try { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - byte[] bytes = md5.digest(str.getBytes()); - StringBuilder stringBuffer = new StringBuilder(); - for (byte b : bytes) { - int bt = b & 0xff; - if (bt < 16) { - stringBuffer.append(0); - } - stringBuffer.append(Integer.toHexString(bt)); - } - reStr = stringBuffer.toString(); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } - return reStr; - } - - public static String strToMd5By16(String str) { - String reStr = strToMd5By32(str); - if (reStr != null) { - reStr = reStr.substring(8, 24); - } - return reStr; - } - - /** - * 获取单个文件的MD5值 - * @param file 文件 - * @param radix 位 16 32 64 - * - * @return - */ - - public static String getFileMD5s(File file, int radix) { - if (!file.isFile()) { - return null; - } - MessageDigest digest = null; - FileInputStream in = null; - byte[] buffer = new byte[1024]; - int len; - try { - digest = MessageDigest.getInstance("MD5"); - in = new FileInputStream(file); - while ((len = in.read(buffer, 0, 1024)) != -1) { - digest.update(buffer, 0, len); - } - } catch (Exception e) { - e.printStackTrace(); - return null; - }finally { - IOUtils.close(in); - } - BigInteger bigInt = new BigInteger(1, digest.digest()); - return bigInt.toString(radix); - } -} diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/MD5Utils.kt b/app/src/main/java/xyz/fycz/myreader/util/utils/MD5Utils.kt new file mode 100644 index 0000000..5bd98fa --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/utils/MD5Utils.kt @@ -0,0 +1,76 @@ +package xyz.fycz.myreader.util.utils + +import xyz.fycz.myreader.util.IOUtils +import java.io.File +import java.io.FileInputStream +import java.math.BigInteger +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException +import kotlin.experimental.and + +/** + * 将字符串转化为MD5 + */ +@Suppress("unused") +object MD5Utils { + + fun md5Encode(str: String?): String { + if (str == null) return "" + var reStr = "" + try { + val md5: MessageDigest = MessageDigest.getInstance("MD5") + val bytes: ByteArray = md5.digest(str.toByteArray()) + val stringBuffer: StringBuilder = StringBuilder() + for (b in bytes) { + val bt: Int = b.toInt() and 0xff + if (bt < 16) { + stringBuffer.append(0) + } + stringBuffer.append(Integer.toHexString(bt)) + } + reStr = stringBuffer.toString() + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } + + return reStr + } + + fun md5Encode16(str: String): String { + var reStr = md5Encode(str) + reStr = reStr.substring(8, 24) + return reStr + } + + + /** + * 获取单个文件的MD5值 + * @param file 文件 + * @param radix 位 16 32 64 + * + * @return + */ + fun getFileMD5s(file: File, radix: Int): String? { + if (!file.isFile) { + return null + } + var digest: MessageDigest? = null + var `in`: FileInputStream? = null + val buffer = ByteArray(1024) + var len: Int + try { + digest = MessageDigest.getInstance("MD5") + `in` = FileInputStream(file) + while (`in`.read(buffer, 0, 1024).also { len = it } != -1) { + digest.update(buffer, 0, len) + } + } catch (e: Exception) { + e.printStackTrace() + return null + } finally { + IOUtils.close(`in`) + } + val bigInt = BigInteger(1, digest!!.digest()) + return bigInt.toString(radix) + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/NetworkUtils.java b/app/src/main/java/xyz/fycz/myreader/util/utils/NetworkUtils.java index 71b1882..353662f 100644 --- a/app/src/main/java/xyz/fycz/myreader/util/utils/NetworkUtils.java +++ b/app/src/main/java/xyz/fycz/myreader/util/utils/NetworkUtils.java @@ -50,6 +50,7 @@ public class NetworkUtils { return false; } } + public static String getUrl(Response response) { okhttp3.Response networkResponse = response.networkResponse(); if (networkResponse != null) { @@ -104,11 +105,31 @@ public class NetworkUtils { } return relativePath; } + public static boolean isUrl(String urlStr) { if (urlStr == null) return false; String regex = "^(https?)://.+$";//设置正则表达式 return urlStr.matches(regex); } + + public static String getBaseUrl(String url) { + if (url == null || !url.startsWith("http")) return null; + int index = url.indexOf("/", 9); + if (index == -1) { + return url; + } else return url.substring(0, index); + } + + public static String getSubDomain(String url) { + String baseUrl = getBaseUrl(url); + if (baseUrl == null) return ""; + if (baseUrl.indexOf(".") == baseUrl.lastIndexOf(".")) { + return baseUrl.substring(baseUrl.lastIndexOf("/") + 1); + } else { + return baseUrl.substring(baseUrl.indexOf(".") + 1); + } + } + /** * Ipv4 address check. */ diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/OkHttpUtils.java b/app/src/main/java/xyz/fycz/myreader/util/utils/OkHttpUtils.java index 01ada23..76e415a 100644 --- a/app/src/main/java/xyz/fycz/myreader/util/utils/OkHttpUtils.java +++ b/app/src/main/java/xyz/fycz/myreader/util/utils/OkHttpUtils.java @@ -15,24 +15,30 @@ import org.json.JSONObject; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; -import io.reactivex.Observable; -import okhttp3.*; -import xyz.fycz.myreader.application.App; -import xyz.fycz.myreader.common.APPCONST; -import xyz.fycz.myreader.entity.StrResponse; -import xyz.fycz.myreader.greendao.DbManager; -import xyz.fycz.myreader.greendao.entity.CookieBean; -import xyz.fycz.myreader.greendao.entity.rule.BookSource; -import xyz.fycz.myreader.model.third2.analyzeRule.AnalyzeUrl; - import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; +import io.reactivex.Observable; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; +import xyz.fycz.myreader.application.App; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.entity.StrResponse; +import xyz.fycz.myreader.greendao.entity.rule.BookSource; +import xyz.fycz.myreader.greendao.service.CookieStore; +import xyz.fycz.myreader.model.third2.analyzeRule.AnalyzeUrl; + import static xyz.fycz.myreader.util.help.SSLSocketClient.createSSLSocketFactory; import static xyz.fycz.myreader.util.help.SSLSocketClient.createTrustAllManager; import static xyz.fycz.myreader.util.help.SSLSocketClient.getHeaderInterceptor; @@ -82,7 +88,7 @@ public class OkHttpUtils { if (body == null) { return ""; } else { - String bodyStr = new String(body.bytes(), encodeType); + String bodyStr = new String(body.bytes(), Charset.forName(encodeType)); Log.d("Http: read finish", bodyStr); return bodyStr; } @@ -253,8 +259,7 @@ public class OkHttpUtils { webView.setWebViewClient(new WebViewClient() { @Override public void onPageFinished(WebView view, String url) { - DbManager.getDaoSession().getCookieBeanDao() - .insertOrReplace(new CookieBean(tag, cookieManager.getCookie(webView.getUrl()))); + CookieStore.INSTANCE.setCookie(tag, cookieManager.getCookie(webView.getUrl())); handler.postDelayed(retryRunnable, 1000); } }); @@ -286,7 +291,7 @@ public class OkHttpUtils { } String cookie = cookieBuilder.toString(); if (!TextUtils.isEmpty(cookie)) { - DbManager.getDaoSession().getCookieBeanDao().insertOrReplace(new CookieBean(tag, cookie)); + CookieStore.INSTANCE.setCookie(tag, cookie); } } e.onNext(response); @@ -304,10 +309,7 @@ public class OkHttpUtils { public static Map getCookies(String url) { Map cookieMap = new HashMap<>(); if (url != null) { - CookieBean cookie = DbManager.getDaoSession().getCookieBeanDao().load(url); - if (cookie != null) { - cookieMap.put("Cookie", cookie.getCookie()); - } + cookieMap.put("Cookie", CookieStore.INSTANCE.getCookie(url)); } return cookieMap; } diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/ThirdSourceApi.java b/app/src/main/java/xyz/fycz/myreader/webapi/ThirdSourceApi.java index 0b48399..4c2d053 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/ThirdSourceApi.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/ThirdSourceApi.java @@ -1,6 +1,5 @@ package xyz.fycz.myreader.webapi; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; @@ -10,10 +9,8 @@ import io.reactivex.ObservableOnSubscribe; import io.reactivex.ObservableSource; import io.reactivex.functions.Function; import xyz.fycz.myreader.entity.SearchBookBean; -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.CookieBean; import xyz.fycz.myreader.greendao.entity.rule.BookSource; import xyz.fycz.myreader.model.mulvalmap.ConMVMap; import xyz.fycz.myreader.model.third2.analyzeRule.AnalyzeUrl;