Merge pull request #50 from gedoor/master

merge
pull/898/head
hoodie13 4 years ago committed by GitHub
commit 7c9735dcec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 60
      .github/scripts/lzy.sh
  2. 99
      .github/scripts/lzy_web.py
  3. 26
      .github/workflows/legado.yml
  4. 5
      app/src/main/assets/updateLog.md
  5. 2
      app/src/main/assets/web/new/css/detail.79c91c30.css
  6. 4
      app/src/main/assets/web/new/index.html
  7. 0
      app/src/main/assets/web/new/js/about.32f651f9.js
  8. 2
      app/src/main/assets/web/new/js/app.3f3311a5.js
  9. 4
      app/src/main/assets/web/new/js/chunk-vendors.305d5536.js
  10. 1
      app/src/main/assets/web/new/js/detail.0cc184f2.js
  11. 1
      app/src/main/assets/web/new/js/detail.4d0609e7.js
  12. 28
      app/src/main/assets/web/new/precache-manifest.63ef7cbb041538ea344680ff651c5232.js
  13. 2
      app/src/main/assets/web/new/service-worker.js
  14. 16
      app/src/main/java/io/legado/app/data/AppDatabase.kt
  15. 2
      app/src/main/java/io/legado/app/data/dao/ReadRecordDao.kt
  16. 4
      app/src/main/java/io/legado/app/data/entities/ReadRecord.kt
  17. 13
      app/src/main/java/io/legado/app/help/ContentProcessor.kt
  18. 17
      app/src/main/java/io/legado/app/help/JsExtensions.kt
  19. 4
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  20. 4
      app/src/main/java/io/legado/app/model/Debug.kt
  21. 4
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
  22. 2
      app/src/main/java/io/legado/app/model/localBook/EPUBFile.kt
  23. 45
      app/src/main/java/io/legado/app/model/webBook/BookContent.kt
  24. 4
      app/src/main/java/io/legado/app/model/webBook/BookInfo.kt
  25. 4
      app/src/main/java/io/legado/app/model/webBook/BookList.kt
  26. 2
      app/src/main/java/io/legado/app/model/webBook/WebBook.kt
  27. 2
      app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudConfigDialog.kt
  28. 2
      app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt
  29. 21
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  30. 11
      app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt
  31. 46
      app/src/main/java/io/legado/app/utils/HtmlFormatter.kt
  32. 13
      app/src/main/java/io/legado/app/utils/StringExtensions.kt
  33. 1
      app/src/main/res/values-zh-rHK/strings.xml
  34. 1
      app/src/main/res/values-zh-rTW/strings.xml
  35. 1
      app/src/main/res/values-zh/strings.xml
  36. 9
      app/src/main/res/values/strings.xml

@ -1,60 +0,0 @@
#!/usr/bin/env bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
# --------------------------------------------------------------
# 系统: ALL
# 项目: 蓝奏云上传文件
# 版本: 1.0.3
# 作者: XIU2
# 官网: https://shell.xiu2.xyz
# 项目: https://github.com/XIU2/Shell
# --------------------------------------------------------------
USERNAME="admin" # 蓝奏云用户名
COOKIE_PHPDISK_INFO="$LANZOU_PSD" # Cookie 中 phpdisk_info 的值
COOKIE_YLOGIN="$LANZOU_ID" # Cookie 中 ylogin 的值
UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.7.3670.199 Safari/537.36"
HEADER_CHECK_LOGIN="User-Agent: ${UA}
Referer: https://up.woozooo.com/mydisk.php?item=files&action=index&u=${USERNAME}
Accept-Language: zh-CN,zh;q=0.9"
URL_ACCOUNT="https://pc.woozooo.com/account.php"
URL_UPLOAD="https://up.woozooo.com/fileup.php"
INFO="[信息]" && ERROR="[错误]" && TIP="[注意]"
# 检查是否已登录
_CHECK_LOGIN() {
if [[ "${COOKIE_PHPDISK_INFO}" = "" || "${COOKIE_PHPDISK_INFO}" = "XXX" ]]; then
_NOTICE "ERROR" "请指定 Cookie 中 phpdisk_info 的值!"
fi
if [[ "${COOKIE_YLOGIN}" = "" || "${COOKIE_YLOGIN}" = "XXX" ]]; then
_NOTICE "ERROR" "请指定 Cookie 中 ylogin 的值!"
fi
HTML_CHECK_LOGIN=$(curl -s --http1.1 -b "ylogin=${COOKIE_YLOGIN};phpdisk_info=${COOKIE_PHPDISK_INFO}" -H "${HEADER_CHECK_LOGIN}" "${URL_ACCOUNT}"|grep "登录")
[[ ! -z "${HTML_CHECK_LOGIN}" ]] && _NOTICE "ERROR" "Cookie 已失效,请更新!"
}
# 上传文件
_UPLOAD() {
[[ $(du "${NAME_FILE}"|awk '{print $1}') -gt 100000000 ]] && _NOTICE "ERROR" "${NAME}文件大于 100MB!"
HTML_UPLOAD=$(curl --connect-timeout 120 -m 5000 --retry 2 -s -b "ylogin=${COOKIE_YLOGIN};phpdisk_info=${COOKIE_PHPDISK_INFO}" -H "${URL_UPLOAD}" -F "task=1" -F "id=WU_FILE_0" -F "folder_id=${FOLDER_ID}" -F "name=${NAME}" -F "upload_file=@${NAME_FILE}" "${URL_UPLOAD}"|grep '\\u4e0a\\u4f20\\u6210\\u529f')
[[ -z "${HTML_UPLOAD}" ]] && _NOTICE "ERROR" "${NAME}文件上传失败!"
echo -e "${INFO} 文件上传成功![$(date -u -d '+8 hour' '+%Y-%m-%d %H:%M:%S')]"
}
NAME="$1" # 文件名
NAME_FILE="$2" # 文件路径
FOLDER_ID="$3" # 上传文件夹ID
if [[ -z "${NAME}" ]]; then
echo -e "${ERROR} 未指定文件名!" && exit 1
elif [[ -z "${NAME_FILE}" ]]; then
echo -e "${ERROR} 未指定文件路径!" && exit 1
elif [[ -z "${FOLDER_ID}" ]]; then
echo -e "${ERROR} 未指定上传文件夹ID!" && exit 1
fi
_CHECK_LOGIN
_UPLOAD

@ -0,0 +1,99 @@
import requests, os, datetime, sys
# Cookie 中 phpdisk_info 的值
cookie_phpdisk_info = os.environ.get('phpdisk_info')
# Cookie 中 ylogin 的值
cookie_ylogin = os.environ.get('ylogin')
# 请求头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36 Edg/89.0.774.45',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Referer': 'https://pc.woozooo.com/account.php?action=login'
}
# 小饼干
cookie = {
'ylogin': cookie_ylogin,
'phpdisk_info': cookie_phpdisk_info
}
# 日志打印
def log(msg):
utc_time = datetime.datetime.utcnow()
china_time = utc_time + datetime.timedelta(hours=8)
print(f"[{china_time.strftime('%Y.%m.%d %H:%M:%S')}] {msg}")
# 检查是否已登录
def login_by_cookie():
url_account = "https://pc.woozooo.com/account.php"
if cookie['phpdisk_info'] is None:
log('ERROR: 请指定 Cookie 中 phpdisk_info 的值!')
return False
if cookie['ylogin'] is None:
log('ERROR: 请指定 Cookie 中 ylogin 的值!')
return False
res = requests.get(url_account, headers=headers, cookies=cookie, verify=True)
if '网盘用户登录' in res.text:
log('ERROR: 登录失败,请更新Cookie')
return False
else:
log('登录成功')
return True
# 上传文件
def upload_file(file_dir, folder_id):
file_name = os.path.basename(file_dir)
url_upload = "https://up.woozooo.com/fileup.php"
headers['Referer'] = f'https://up.woozooo.com/mydisk.php?item=files&action=index&u={cookie_ylogin}'
post_data = {
"task": "1",
"folder_id": folder_id,
"id": "WU_FILE_0",
"name": file_name,
}
files = {'upload_file': (file_name, open(file_dir, "rb"), 'application/octet-stream')}
res = requests.post(url_upload, data=post_data, files=files, headers=headers, cookies=cookie, timeout=120,
verify=True).json()
log(f"{file_dir} -> {res['info']}")
return res['zt'] == 1
# 上传文件夹内的文件
def upload_folder(folder_dir, folder_id):
file_list = os.listdir(folder_dir)
for file in file_list:
path = os.path.join(folder_dir, file)
if os.path.isfile(path):
upload_file(path, folder_id)
else:
upload_folder(path, folder_id)
# 上传
def upload(dir, folder_id):
if dir is None:
log('ERROR: 请指定上传的文件路径')
return
if folder_id is None:
log('ERROR: 请指定蓝奏云的文件夹id')
return
if os.path.isfile(dir):
upload_file(dir, str(folder_id))
else:
upload_folder(dir, str(folder_id))
if __name__ == '__main__':
argv = sys.argv[1:]
if len(argv) != 2:
log('ERROR: 参数错误,请以这种格式重新尝试\npython lzy_web.py 需上传的路径 蓝奏云文件夹id')
# 需上传的路径
upload_path = argv[0]
# 蓝奏云文件夹id
lzy_folder_id = argv[1]
if login_by_cookie():
upload(upload_path, lzy_folder_id)

@ -15,11 +15,9 @@ jobs:
build:
runs-on: ubuntu-latest
env:
# 登录蓝奏云后查看cookie
# LANZOU_ID : woozooo.com -> Cookie -> ylogin
# LANZOU_PSD : pc.woozooo.com -> Cookie -> phpdisk_info
LANZOU_ID: ${{ secrets.LANZOU_ID }}
LANZOU_PSD: ${{ secrets.LANZOU_PSD }}
# 登录蓝奏云后在控制台运行document.cookie
ylogin: ${{ secrets.LANZOU_ID }}
phpdisk_info: ${{ secrets.LANZOU_PSD }}
# 蓝奏云里的文件夹ID(阅读3测试版:2670621)
LANZOU_FOLDER_ID: '2670621'
# 是否上传到artifact
@ -34,7 +32,7 @@ jobs:
key: ${{ runner.os }}-legado-${{ hashFiles('**/updateLog.md') }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-legado-${{ hashFiles('**/updateLog.md') }}-
- name: Clear 18PlusList.txt
run: |
echo "清空18PlusList.txt"
@ -64,23 +62,19 @@ jobs:
name: legado apk
path: ${{ github.workspace }}/app/build/outputs/apk/app/release/*.apk
- name: Upload App To Lanzou
if: ${{ env.LANZOU_PSD }}
if: ${{ env.ylogin }}
run: |
path="$GITHUB_WORKSPACE/app/build/outputs/apk/app/release/"
path="$GITHUB_WORKSPACE/app/build/outputs/apk/app/release"
files=$(ls $path)
for f in $files
do
if [[ $f == *"apk" ]]; then
file=$f
echo "文件:$file"
echo "[$(date -u -d '+8 hour' '+%Y.%m.%d %H:%M:%S')] 文件:$file"
break
fi
done
bash $GITHUB_WORKSPACE/.github/scripts/lzy.sh "$file" \
"$GITHUB_WORKSPACE/app/build/outputs/apk/app/release/$file" "${{ env.LANZOU_FOLDER_ID }}"
echo "分享链接: https://kunfei.lanzous.com/b0f810h4b"
env:
LANZOU_ID: ${{ env.LANZOU_ID }}
LANZOU_PSD: ${{ env.LANZOU_PSD }}
python3 $GITHUB_WORKSPACE/.github/scripts/lzy_web.py "$path/$file" "$LANZOU_FOLDER_ID"
echo "[$(date -u -d '+8 hour' '+%Y.%m.%d %H:%M:%S')] 分享链接: https://kunfei.lanzous.com/b0f810h4b"

@ -3,6 +3,11 @@
* 关注合作公众号 **[小说拾遗]** 获取好看的小说。
* 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
**2021/03/15**
* 优化图片TEXT样式显示
* 图片url在解析正文时就拼接成绝对url
* 修复一些bug
**2021/03/08**
* 阅读页面停留10分钟之后自动备份进度
* 添加了针对中文的断行排版处理-by hoodie13, 需要再阅读界面设置里手动开启

@ -1,3 +1,3 @@
<!DOCTYPE html><html lang="en" style="padding: 0;height:100%"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="favicon.ico" /><![endif]--><title>Legado Bookshelf</title><link href="css/about.dbe575e1.css" rel="prefetch"><link href="css/detail.b4324411.css" rel="prefetch"><link href="js/about.4300b5ad.js" rel="prefetch"><link href="js/about~detail.c1b29cbc.js" rel="prefetch"><link href="js/detail.4d0609e7.js" rel="prefetch"><link href="css/app.e4c919b7.css" rel="preload" as="style"><link href="css/chunk-vendors.ad4ff18f.css" rel="preload" as="style"><link href="js/app.acf50eba.js" rel="preload" as="script"><link href="js/chunk-vendors.1042cf7f.js" rel="preload" as="script"><link href="css/chunk-vendors.ad4ff18f.css" rel="stylesheet"><link href="css/app.e4c919b7.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png"><link rel="manifest" href="manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="yd-web-tool"><link rel="apple-touch-icon" href="img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><style>body::-webkit-scrollbar {
<!DOCTYPE html><html lang="en" style="padding: 0;height:100%"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="favicon.ico" /><![endif]--><title>Legado Bookshelf</title><link href="css/about.dbe575e1.css" rel="prefetch"><link href="css/detail.79c91c30.css" rel="prefetch"><link href="js/about.32f651f9.js" rel="prefetch"><link href="js/about~detail.c1b29cbc.js" rel="prefetch"><link href="js/detail.0cc184f2.js" rel="prefetch"><link href="css/app.e4c919b7.css" rel="preload" as="style"><link href="css/chunk-vendors.ad4ff18f.css" rel="preload" as="style"><link href="js/app.3f3311a5.js" rel="preload" as="script"><link href="js/chunk-vendors.305d5536.js" rel="preload" as="script"><link href="css/chunk-vendors.ad4ff18f.css" rel="stylesheet"><link href="css/app.e4c919b7.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png"><link rel="manifest" href="manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="yd-web-tool"><link rel="apple-touch-icon" href="img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><style>body::-webkit-scrollbar {
display: none;
}</style><body style="margin: 0;height:100%"><noscript><strong>We're sorry but yd-web-tool doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/chunk-vendors.1042cf7f.js"></script><script src="js/app.acf50eba.js"></script></body></html>
}</style><body style="margin: 0;height:100%"><noscript><strong>We're sorry but yd-web-tool doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/chunk-vendors.305d5536.js"></script><script src="js/app.3f3311a5.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,19 +1,19 @@
self.__precacheManifest = (self.__precacheManifest || []).concat([
{
"revision": "12386c7dce7d463af4ed",
"revision": "a4809f9e102ca093c5e0",
"url": "css/about.dbe575e1.css"
},
{
"revision": "d761b0dee902b1105e91",
"revision": "92b120ee6d7003919840",
"url": "css/app.e4c919b7.css"
},
{
"revision": "16ab934214be8df372e3",
"revision": "d14a57da901d79a81631",
"url": "css/chunk-vendors.ad4ff18f.css"
},
{
"revision": "4083fbb292940cf804ca",
"url": "css/detail.b4324411.css"
"revision": "329d93821b9c998393c1",
"url": "css/detail.79c91c30.css"
},
{
"revision": "535877f50039c0cb49a6196a5b7517cd",
@ -40,28 +40,28 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "img/noCover.b5c48bc1.jpeg"
},
{
"revision": "bc7efb33b2dbae4d4578e3657e73de4b",
"revision": "6b1059341e07bf6fefd98e31613ba6da",
"url": "index.html"
},
{
"revision": "12386c7dce7d463af4ed",
"url": "js/about.4300b5ad.js"
"revision": "a4809f9e102ca093c5e0",
"url": "js/about.32f651f9.js"
},
{
"revision": "2828d7869f38c2483b33",
"url": "js/about~detail.c1b29cbc.js"
},
{
"revision": "d761b0dee902b1105e91",
"url": "js/app.acf50eba.js"
"revision": "92b120ee6d7003919840",
"url": "js/app.3f3311a5.js"
},
{
"revision": "16ab934214be8df372e3",
"url": "js/chunk-vendors.1042cf7f.js"
"revision": "d14a57da901d79a81631",
"url": "js/chunk-vendors.305d5536.js"
},
{
"revision": "4083fbb292940cf804ca",
"url": "js/detail.4d0609e7.js"
"revision": "329d93821b9c998393c1",
"url": "js/detail.0cc184f2.js"
},
{
"revision": "b46d04eb43bc31ca0f9f95121646440d",

@ -14,7 +14,7 @@
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
importScripts(
"precache-manifest.591d552208e3dedf905ccfa69870ce16.js"
"precache-manifest.63ef7cbb041538ea344680ff651c5232.js"
);
workbox.core.setCacheNameDetails({prefix: "yd-web-tool"});

@ -23,7 +23,7 @@ val appDb by lazy {
RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::class,
RssStar::class, TxtTocRule::class, ReadRecord::class, HttpTTS::class, Cache::class,
RuleSub::class, EpubChapter::class],
version = 30,
version = 31,
exportSchema = true
)
abstract class AppDatabase : RoomDatabase() {
@ -59,7 +59,7 @@ abstract class AppDatabase : RoomDatabase() {
migration_14_15, migration_15_17, migration_17_18, migration_18_19,
migration_19_20, migration_20_21, migration_21_22, migration_22_23,
migration_23_24, migration_24_25, migration_25_26, migration_26_27,
migration_27_28, migration_28_29, migration_29_30
migration_27_28, migration_28_29, migration_29_30, migration_30_31
)
.allowMainThreadQueries()
.addCallback(dbCallback)
@ -278,6 +278,18 @@ abstract class AppDatabase : RoomDatabase() {
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_epubChapters_bookUrl_href` ON `epubChapters` (`bookUrl`, `href`)")
}
}
private val migration_30_31 = object : Migration(30, 31) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE readRecord RENAME TO readRecord1")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `readRecord` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))
"""
)
database.execSQL("insert into readRecord (deviceId, bookName, readTime) select androidId, bookName, readTime from readRecord1")
}
}
}
}

@ -19,7 +19,7 @@ interface ReadRecordDao {
@Query("select sum(readTime) from readRecord where bookName = :bookName")
fun getReadTime(bookName: String): Long?
@Query("select readTime from readRecord where androidId = :androidId and bookName = :bookName")
@Query("select readTime from readRecord where deviceId = :androidId and bookName = :bookName")
fun getReadTime(androidId: String, bookName: String): Long?
@Insert(onConflict = OnConflictStrategy.REPLACE)

@ -2,9 +2,9 @@ package io.legado.app.data.entities
import androidx.room.Entity
@Entity(tableName = "readRecord", primaryKeys = ["androidId", "bookName"])
@Entity(tableName = "readRecord", primaryKeys = ["deviceId", "bookName"])
data class ReadRecord(
var androidId: String = "",
var deviceId: String = "",
var bookName: String = "",
var readTime: Long = 0L
)

@ -5,8 +5,6 @@ import io.legado.app.data.appDb
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import splitties.init.appCtx
class ContentProcessor(private val bookName: String, private val bookOrigin: String) {
@ -23,7 +21,8 @@ class ContentProcessor(private val bookName: String, private val bookOrigin: Str
replaceRules.addAll(appDb.replaceRuleDao.findEnabledByScope(bookName, bookOrigin))
}
suspend fun getContent(
@Synchronized
fun getContent(
book: Book,
title: String, //已经经过简繁转换
content: String,
@ -41,9 +40,7 @@ class ContentProcessor(private val bookName: String, private val bookOrigin: Str
content1.replace(item.pattern, item.replacement)
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
appCtx.toastOnUi("${item.name}替换出错")
}
appCtx.toastOnUi("${item.name}替换出错")
}
}
}
@ -58,9 +55,7 @@ class ContentProcessor(private val bookName: String, private val bookOrigin: Str
2 -> content1 = HanLP.convertToTraditionalChinese(content1)
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
appCtx.toastOnUi("简繁转换出错")
}
appCtx.toastOnUi("简繁转换出错")
}
}
val contents = arrayListOf<String>()

@ -227,7 +227,7 @@ interface JsExtensions {
}
fun htmlFormat(str: String): String {
return str.htmlFormat()
return HtmlFormatter.formatKeepImg(str)
}
/**
@ -257,6 +257,10 @@ interface JsExtensions {
return null
}
/**
* 返回字体解析类
* @param str 支持url,本地文件,base64,自动判断,自动缓存
*/
fun queryTTF(str: String?): QueryTTF? {
str ?: return null
val key = md5Encode16(str)
@ -283,10 +287,15 @@ interface JsExtensions {
return qTTF
}
/**
* @param text 包含错误字体的内容
* @param font1 错误的字体
* @param font2 正确的字体
*/
fun replaceFont(
text: String,
font1: QueryTTF?,
font2: QueryTTF?
text: String,
font1: QueryTTF?,
font2: QueryTTF?
): String {
if (font1 == null || font2 == null) return text
val contentArray = text.toCharArray()

@ -149,11 +149,11 @@ object Restore {
fileToListT<ReadRecord>(path, "readRecord.json")?.let {
it.forEach { readRecord ->
//判断是不是本机记录
if (readRecord.androidId != androidId) {
if (readRecord.deviceId != androidId) {
appDb.readRecordDao.insert(readRecord)
} else {
val time = appDb.readRecordDao
.getReadTime(readRecord.androidId, readRecord.bookName)
.getReadTime(readRecord.deviceId, readRecord.bookName)
if (time == null || time < readRecord.readTime) {
appDb.readRecordDao.insert(readRecord)
}

@ -8,7 +8,7 @@ import io.legado.app.data.entities.RssSource
import io.legado.app.help.coroutine.CompositeCoroutine
import io.legado.app.model.rss.Rss
import io.legado.app.model.webBook.WebBook
import io.legado.app.utils.htmlFormat
import io.legado.app.utils.HtmlFormatter
import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.msg
import kotlinx.coroutines.CoroutineScope
@ -36,7 +36,7 @@ object Debug {
if (debugSource != sourceUrl || callback == null || !print) return
var printMsg = msg ?: ""
if (isHtml) {
printMsg = printMsg.htmlFormat()
printMsg = HtmlFormatter.format(msg)
}
if (showTime) {
printMsg =

@ -67,11 +67,11 @@ class AnalyzeRule(val ruleData: RuleDataInterface) : JsExtensions {
return this
}
fun setRedirectUrl(url: String): AnalyzeRule {
fun setRedirectUrl(url: String): URL? {
kotlin.runCatching {
redirectUrl = URL(url.split(AnalyzeUrl.splitUrlRegex, 1)[0])
}
return this
return redirectUrl
}
/**

@ -181,7 +181,7 @@ class EPUBFile(var book: io.legado.app.data.entities.Book) {
if (book.getDelTag(tag)) {
html = html.replace("<ruby>\\s?([\\u4e00-\\u9fa5])\\s?.*?</ruby>".toRegex(), "$1")
}
return html.htmlFormat()
return HtmlFormatter.formatKeepImg(html)
}
return null
}

@ -10,8 +10,8 @@ import io.legado.app.help.BookHelp
import io.legado.app.model.Debug
import io.legado.app.model.analyzeRule.AnalyzeRule
import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.utils.HtmlFormatter
import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.htmlFormat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.withContext
import splitties.init.appCtx
@ -26,6 +26,7 @@ object BookContent {
bookChapter: BookChapter,
bookSource: BookSource,
baseUrl: String,
redirectUrl: String,
nextChapterUrl: String? = null
): String {
body ?: throw Exception(
@ -44,7 +45,7 @@ object BookContent {
analyzeRule.setRedirectUrl(baseUrl)
analyzeRule.nextChapterUrl = mNextChapterUrl
var contentData = analyzeContent(
book, baseUrl, body, contentRule, bookChapter, bookSource, mNextChapterUrl
book, baseUrl, redirectUrl, body, contentRule, bookChapter, bookSource, mNextChapterUrl
)
content.append(contentData.content).append("\n")
@ -56,20 +57,15 @@ object BookContent {
== NetworkUtils.getAbsoluteURL(baseUrl, mNextChapterUrl)
) break
nextUrlList.add(nextUrl)
AnalyzeUrl(
val res = AnalyzeUrl(
ruleUrl = nextUrl,
book = book,
headerMapF = bookSource.getHeaderMap()
).getStrResponse(bookSource.bookSourceUrl).body?.let { nextBody ->
).getStrResponse(bookSource.bookSourceUrl)
res.body?.let { nextBody ->
contentData = analyzeContent(
book,
nextUrl,
nextBody,
contentRule,
bookChapter,
bookSource,
mNextChapterUrl,
false
book, nextUrl, res.url, nextBody, contentRule,
bookChapter, bookSource, mNextChapterUrl, false
)
nextUrl =
if (contentData.nextUrl.isNotEmpty()) contentData.nextUrl[0] else ""
@ -85,20 +81,15 @@ object BookContent {
}
for (item in contentDataList) {
withContext(scope.coroutineContext) {
AnalyzeUrl(
val res = AnalyzeUrl(
ruleUrl = item.nextUrl,
book = book,
headerMapF = bookSource.getHeaderMap()
).getStrResponse(bookSource.bookSourceUrl).body?.let {
).getStrResponse(bookSource.bookSourceUrl)
res.body?.let { nextBody ->
contentData = analyzeContent(
book,
item.nextUrl,
it,
contentRule,
bookChapter,
bookSource,
mNextChapterUrl,
false
book, item.nextUrl, res.url, nextBody, contentRule,
bookChapter, bookSource, mNextChapterUrl, false
)
item.content = contentData.content
}
@ -109,7 +100,7 @@ object BookContent {
}
}
content.deleteCharAt(content.length - 1)
var contentStr = content.toString().htmlFormat()
var contentStr = content.toString()
val replaceRegex = contentRule.replaceRegex
if (!replaceRegex.isNullOrEmpty()) {
contentStr = analyzeRule.getString(replaceRegex, value = contentStr)
@ -128,6 +119,7 @@ object BookContent {
private fun analyzeContent(
book: Book,
baseUrl: String,
redirectUrl: String,
body: String,
contentRule: ContentRule,
chapter: BookChapter,
@ -136,13 +128,14 @@ object BookContent {
printLog: Boolean = true
): ContentData<List<String>> {
val analyzeRule = AnalyzeRule(book)
analyzeRule.setContent(body).setBaseUrl(baseUrl)
analyzeRule.setRedirectUrl(baseUrl)
analyzeRule.setContent(body, baseUrl)
val rUrl = analyzeRule.setRedirectUrl(redirectUrl)
analyzeRule.nextChapterUrl = nextChapterUrl
val nextUrlList = arrayListOf<String>()
analyzeRule.chapter = chapter
//获取正文
val content = analyzeRule.getString(contentRule.content)
var content = analyzeRule.getString(contentRule.content)
content = HtmlFormatter.formatKeepImg(content, rUrl)
//获取下一页链接
val nextUrlRule = contentRule.nextContentUrl
if (!nextUrlRule.isNullOrEmpty()) {

@ -6,9 +6,9 @@ import io.legado.app.data.entities.BookSource
import io.legado.app.help.BookHelp
import io.legado.app.model.Debug
import io.legado.app.model.analyzeRule.AnalyzeRule
import io.legado.app.utils.HtmlFormatter
import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.StringUtils.wordCountFormat
import io.legado.app.utils.htmlFormat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ensureActive
import splitties.init.appCtx
@ -93,7 +93,7 @@ object BookInfo {
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取简介")
analyzeRule.getString(infoRule.intro).let {
if (it.isNotEmpty()) book.intro = it.htmlFormat()
if (it.isNotEmpty()) book.intro = HtmlFormatter.format(it)
}
Debug.log(bookSource.bookSourceUrl, "${book.intro}")
scope.ensureActive()

@ -9,9 +9,9 @@ import io.legado.app.help.BookHelp
import io.legado.app.model.Debug
import io.legado.app.model.analyzeRule.AnalyzeRule
import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.utils.HtmlFormatter
import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.StringUtils.wordCountFormat
import io.legado.app.utils.htmlFormat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ensureActive
import splitties.init.appCtx
@ -197,7 +197,7 @@ object BookList {
Debug.log(bookSource.bookSourceUrl, "${searchBook.latestChapterTitle}", log)
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取简介", log)
searchBook.intro = analyzeRule.getString(ruleIntro).htmlFormat()
searchBook.intro = HtmlFormatter.format(analyzeRule.getString(ruleIntro))
Debug.log(bookSource.bookSourceUrl, "${searchBook.intro}", log)
scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取封面链接", log)

@ -228,6 +228,7 @@ class WebBook(val bookSource: BookSource) {
bookChapter,
bookSource,
bookChapter.getAbsoluteURL(),
bookChapter.getAbsoluteURL(),
nextChapterUrl
)
} else {
@ -249,6 +250,7 @@ class WebBook(val bookSource: BookSource) {
bookChapter,
bookSource,
bookChapter.getAbsoluteURL(),
res.url,
nextChapterUrl
)
}

@ -63,7 +63,7 @@ class ReadAloudConfigDialog : DialogFragment() {
get() {
val eid = appCtx.getPrefLong(PreferKey.speakEngine)
val ht = appDb.httpTTSDao.get(eid)
return ht?.name ?: getString(R.string.local_tts)
return ht?.name ?: getString(R.string.system_tts)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {

@ -65,7 +65,7 @@ class SpeakEngineDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
recyclerView.layoutManager = LinearLayoutManager(requireContext())
adapter = Adapter(requireContext())
recyclerView.adapter = adapter
tvFooterLeft.setText(R.string.local_tts)
tvFooterLeft.setText(R.string.system_tts)
tvFooterLeft.visible()
tvFooterLeft.setOnClickListener {
removePref(PreferKey.speakEngine)

@ -119,8 +119,9 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
lineTop,
lineBase,
lineBottom,
isTitle = textLine.isTitle,
isReadAloud = textLine.isReadAloud
textLine.isTitle,
textLine.isReadAloud,
textLine.isImage
)
}
@ -135,6 +136,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
lineBottom: Float,
isTitle: Boolean,
isReadAloud: Boolean,
isImageLine: Boolean
) {
val textPaint = if (isTitle) {
ChapterProvider.titlePaint
@ -145,7 +147,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
if (isReadAloud) context.accentColor else ReadBookConfig.textColor
textChars.forEach {
if (it.isImage) {
drawImage(canvas, it, lineTop, lineBottom)
drawImage(canvas, it, lineTop, lineBottom, isImageLine)
} else {
canvas.drawText(it.charData, it.start, lineBase, textPaint)
}
@ -163,14 +165,19 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
textChar: TextChar,
lineTop: Float,
lineBottom: Float,
isImageLine: Boolean
) {
ReadBook.book?.let { book ->
ImageProvider.getImage(book, textPage.chapterIndex, textChar.charData, true)
?.let {
/*以宽度为基准保持图片的原始比例叠加,当div为负数时,允许高度比字符更高*/
val h = (textChar.end - textChar.start) / it.width * it.height
val div = (lineBottom - lineTop - h) / 2
val rectF = RectF(textChar.start, lineTop + div, textChar.end, lineBottom - div)
val rectF = if (isImageLine) {
RectF(textChar.start, lineTop, textChar.end, lineBottom)
} else {
/*以宽度为基准保持图片的原始比例叠加,当div为负数时,允许高度比字符更高*/
val h = (textChar.end - textChar.start) / it.width * it.height
val div = (lineBottom - lineTop - h) / 2
RectF(textChar.start, lineTop + div, textChar.end, lineBottom - div)
}
canvas.drawBitmap(it, null, rectF, null)
}
}

@ -69,8 +69,7 @@ object ChapterProvider {
@JvmStatic
lateinit var contentPaint: TextPaint
/*🖼比汉字宽,跟字一起效果的效果不是很好,用卍取代*/
private const val srcReplaceChar = ""
private const val srcReplaceChar = ""
init {
upStyle()
@ -91,13 +90,12 @@ object ChapterProvider {
textPages.add(TextPage())
contents.forEachIndexed { index, content ->
if (book.getImageStyle() == Book.imgStyleText) {
var text = content.replace(srcReplaceChar, "")
var text = content.replace(srcReplaceChar, "")
val srcList = LinkedList<String>()
val sb = StringBuffer()
val matcher = AppPattern.imgPattern.matcher(text)
while (matcher.find()) {
matcher.group(1)?.let { it ->
val src = NetworkUtils.getAbsoluteURL(bookChapter.url, it)
matcher.group(1)?.let { src ->
srcList.add(src)
ImageProvider.getImage(book, bookChapter.index, src)
matcher.appendReplacement(sb, srcReplaceChar)
@ -119,9 +117,8 @@ object ChapterProvider {
if (text.isNotBlank()) {
val matcher = AppPattern.imgPattern.matcher(text)
if (matcher.find()) {
matcher.group(1)?.let { it ->
matcher.group(1)?.let { src ->
if (!book.isEpub()) {
val src = NetworkUtils.getAbsoluteURL(bookChapter.url, it)
durY = setTypeImage(
book, bookChapter, src,
durY, textPages, book.getImageStyle()

@ -0,0 +1,46 @@
package io.legado.app.utils
import java.net.URL
import java.util.regex.Pattern
object HtmlFormatter {
private val wrapHtmlRegex = "</?(?:div|p|br|hr|h\\d|article|dd|dl)[^>]*>".toRegex()
private val notImgHtmlRegex = "</?(?!img)[a-zA-Z]+(?=[ >])[^<>]*>".toRegex()
private val otherHtmlRegex = "</?[a-zA-Z]+(?=[ >])[^<>]*>".toRegex()
private val imgPattern = Pattern.compile("(<img )[^>]*src=\"([^\"]+)\"[^>]*(>)")
fun format(html: String?): String {
html ?: return ""
return html.replace(wrapHtmlRegex, "\n")
.replace(otherHtmlRegex, "")
.replace("\\s*\\n+\\s*".toRegex(), "\n  ")
.replace("^[\\n\\s]+".toRegex(), "  ")
.replace("[\\n\\s]+$".toRegex(), "")
}
fun formatKeepImg(html: String?): String {
html ?: return ""
return html.replace(wrapHtmlRegex, "\n")
.replace(notImgHtmlRegex, "")
.replace("\\s*\\n+\\s*".toRegex(), "\n  ")
.replace("^[\\n\\s]+".toRegex(), "  ")
.replace("[\\n\\s]+$".toRegex(), "")
}
fun formatKeepImg(html: String?, redirectUrl: URL?): String {
html ?: return ""
val sb = StringBuffer()
val matcher = imgPattern.matcher(html)
while (matcher.find()) {
val url = NetworkUtils.getAbsoluteURL(redirectUrl, matcher.group(2)!!)
matcher.appendReplacement(sb, "${matcher.group(1)} src=\"$url\"${matcher.group(3)}")
}
matcher.appendTail(sb)
return sb.replace(wrapHtmlRegex, "\n")
.replace(notImgHtmlRegex, "")
.replace("\\s*\\n+\\s*".toRegex(), "\n  ")
.replace("^[\\n\\s]+".toRegex(), "  ")
.replace("[\\n\\s]+$".toRegex(), "")
}
}

@ -8,9 +8,6 @@ import android.net.Uri
import java.io.File
import java.util.*
val removeHtmlRegex = "</?(?:div|p|br|hr|h\\d|article|dd|dl)[^>]*>".toRegex()
val notImgHtmlRegex = "</?(?!img)[a-zA-Z]+(?=[ >])[^<>]*>".toRegex()
fun String?.safeTrim() = if (this.isNullOrBlank()) null else this.trim()
fun String?.isContentScheme(): Boolean = this?.startsWith("content://") == true
@ -50,16 +47,6 @@ fun String?.isJsonArray(): Boolean =
str.startsWith("[") && str.endsWith("]")
} ?: false
fun String?.htmlFormat(): String {
this ?: return ""
return this
.replace(removeHtmlRegex, "\n")
.replace(notImgHtmlRegex, "")
.replace("\\s*\\n+\\s*".toRegex(), "\n  ")
.replace("^[\\n\\s]+".toRegex(), "  ")
.replace("[\\n\\s]+$".toRegex(), "")
}
fun String.splitNotBlank(vararg delimiter: String): Array<String> = run {
this.split(*delimiter).map { it.trim() }.filterNot { it.isBlank() }.toTypedArray()
}

@ -809,5 +809,6 @@
<string name="crash_log">崩溃日志</string>
<string name="use_zh_layout">使用自定义中文分行</string>
<string name="image_style">图片样式</string>
<string name="system_tts">系统TTS</string>
</resources>

@ -811,5 +811,6 @@
<string name="reverse_content">反转内容</string>
<string name="debug">调试</string>
<string name="image_style">图片样式</string>
<string name="system_tts">系统TTS</string>
</resources>

@ -813,5 +813,6 @@
<string name="crash_log">崩溃日志</string>
<string name="use_zh_layout">使用自定义中文分行</string>
<string name="image_style">图片样式</string>
<string name="system_tts">系统TTS</string>
</resources>

@ -811,10 +811,11 @@
<string name="export_folder">导出文件夹</string>
<string name="export_charset">导出编码</string>
<string name="export_to_web_dav">导出到WebDav</string>
<string name="reverse_content">反转内容</string>
<string name="debug">调试</string>
<string name="crash_log">崩溃日志</string>
<string name="reverse_content">Reverse content</string>
<string name="debug">Debug</string>
<string name="crash_log">Crash log</string>
<string name="use_zh_layout">使用自定义中文分行</string>
<string name="image_style">图片样式</string>
<string name="image_style">Image style</string>
<string name="system_tts">System TTS</string>
</resources>

Loading…
Cancel
Save