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

@ -3,6 +3,11 @@
* 关注合作公众号 **[小说拾遗]** 获取好看的小说。 * 关注合作公众号 **[小说拾遗]** 获取好看的小说。
* 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 * 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
**2021/03/15**
* 优化图片TEXT样式显示
* 图片url在解析正文时就拼接成绝对url
* 修复一些bug
**2021/03/08** **2021/03/08**
* 阅读页面停留10分钟之后自动备份进度 * 阅读页面停留10分钟之后自动备份进度
* 添加了针对中文的断行排版处理-by hoodie13, 需要再阅读界面设置里手动开启 * 添加了针对中文的断行排版处理-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; 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([ self.__precacheManifest = (self.__precacheManifest || []).concat([
{ {
"revision": "12386c7dce7d463af4ed", "revision": "a4809f9e102ca093c5e0",
"url": "css/about.dbe575e1.css" "url": "css/about.dbe575e1.css"
}, },
{ {
"revision": "d761b0dee902b1105e91", "revision": "92b120ee6d7003919840",
"url": "css/app.e4c919b7.css" "url": "css/app.e4c919b7.css"
}, },
{ {
"revision": "16ab934214be8df372e3", "revision": "d14a57da901d79a81631",
"url": "css/chunk-vendors.ad4ff18f.css" "url": "css/chunk-vendors.ad4ff18f.css"
}, },
{ {
"revision": "4083fbb292940cf804ca", "revision": "329d93821b9c998393c1",
"url": "css/detail.b4324411.css" "url": "css/detail.79c91c30.css"
}, },
{ {
"revision": "535877f50039c0cb49a6196a5b7517cd", "revision": "535877f50039c0cb49a6196a5b7517cd",
@ -40,28 +40,28 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "img/noCover.b5c48bc1.jpeg" "url": "img/noCover.b5c48bc1.jpeg"
}, },
{ {
"revision": "bc7efb33b2dbae4d4578e3657e73de4b", "revision": "6b1059341e07bf6fefd98e31613ba6da",
"url": "index.html" "url": "index.html"
}, },
{ {
"revision": "12386c7dce7d463af4ed", "revision": "a4809f9e102ca093c5e0",
"url": "js/about.4300b5ad.js" "url": "js/about.32f651f9.js"
}, },
{ {
"revision": "2828d7869f38c2483b33", "revision": "2828d7869f38c2483b33",
"url": "js/about~detail.c1b29cbc.js" "url": "js/about~detail.c1b29cbc.js"
}, },
{ {
"revision": "d761b0dee902b1105e91", "revision": "92b120ee6d7003919840",
"url": "js/app.acf50eba.js" "url": "js/app.3f3311a5.js"
}, },
{ {
"revision": "16ab934214be8df372e3", "revision": "d14a57da901d79a81631",
"url": "js/chunk-vendors.1042cf7f.js" "url": "js/chunk-vendors.305d5536.js"
}, },
{ {
"revision": "4083fbb292940cf804ca", "revision": "329d93821b9c998393c1",
"url": "js/detail.4d0609e7.js" "url": "js/detail.0cc184f2.js"
}, },
{ {
"revision": "b46d04eb43bc31ca0f9f95121646440d", "revision": "b46d04eb43bc31ca0f9f95121646440d",

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

@ -23,7 +23,7 @@ val appDb by lazy {
RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::class, RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::class,
RssStar::class, TxtTocRule::class, ReadRecord::class, HttpTTS::class, Cache::class, RssStar::class, TxtTocRule::class, ReadRecord::class, HttpTTS::class, Cache::class,
RuleSub::class, EpubChapter::class], RuleSub::class, EpubChapter::class],
version = 30, version = 31,
exportSchema = true exportSchema = true
) )
abstract class AppDatabase : RoomDatabase() { 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_14_15, migration_15_17, migration_17_18, migration_18_19,
migration_19_20, migration_20_21, migration_21_22, migration_22_23, 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_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() .allowMainThreadQueries()
.addCallback(dbCallback) .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`)") 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") @Query("select sum(readTime) from readRecord where bookName = :bookName")
fun getReadTime(bookName: String): Long? 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? fun getReadTime(androidId: String, bookName: String): Long?
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)

@ -2,9 +2,9 @@ package io.legado.app.data.entities
import androidx.room.Entity import androidx.room.Entity
@Entity(tableName = "readRecord", primaryKeys = ["androidId", "bookName"]) @Entity(tableName = "readRecord", primaryKeys = ["deviceId", "bookName"])
data class ReadRecord( data class ReadRecord(
var androidId: String = "", var deviceId: String = "",
var bookName: String = "", var bookName: String = "",
var readTime: Long = 0L 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.Book
import io.legado.app.data.entities.ReplaceRule import io.legado.app.data.entities.ReplaceRule
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import splitties.init.appCtx import splitties.init.appCtx
class ContentProcessor(private val bookName: String, private val bookOrigin: String) { 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)) replaceRules.addAll(appDb.replaceRuleDao.findEnabledByScope(bookName, bookOrigin))
} }
suspend fun getContent( @Synchronized
fun getContent(
book: Book, book: Book,
title: String, //已经经过简繁转换 title: String, //已经经过简繁转换
content: String, content: String,
@ -41,9 +40,7 @@ class ContentProcessor(private val bookName: String, private val bookOrigin: Str
content1.replace(item.pattern, item.replacement) content1.replace(item.pattern, item.replacement)
} }
} catch (e: Exception) { } 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) 2 -> content1 = HanLP.convertToTraditionalChinese(content1)
} }
} catch (e: Exception) { } catch (e: Exception) {
withContext(Dispatchers.Main) { appCtx.toastOnUi("简繁转换出错")
appCtx.toastOnUi("简繁转换出错")
}
} }
} }
val contents = arrayListOf<String>() val contents = arrayListOf<String>()

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

@ -149,11 +149,11 @@ object Restore {
fileToListT<ReadRecord>(path, "readRecord.json")?.let { fileToListT<ReadRecord>(path, "readRecord.json")?.let {
it.forEach { readRecord -> it.forEach { readRecord ->
//判断是不是本机记录 //判断是不是本机记录
if (readRecord.androidId != androidId) { if (readRecord.deviceId != androidId) {
appDb.readRecordDao.insert(readRecord) appDb.readRecordDao.insert(readRecord)
} else { } else {
val time = appDb.readRecordDao val time = appDb.readRecordDao
.getReadTime(readRecord.androidId, readRecord.bookName) .getReadTime(readRecord.deviceId, readRecord.bookName)
if (time == null || time < readRecord.readTime) { if (time == null || time < readRecord.readTime) {
appDb.readRecordDao.insert(readRecord) 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.help.coroutine.CompositeCoroutine
import io.legado.app.model.rss.Rss import io.legado.app.model.rss.Rss
import io.legado.app.model.webBook.WebBook 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.isAbsUrl
import io.legado.app.utils.msg import io.legado.app.utils.msg
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -36,7 +36,7 @@ object Debug {
if (debugSource != sourceUrl || callback == null || !print) return if (debugSource != sourceUrl || callback == null || !print) return
var printMsg = msg ?: "" var printMsg = msg ?: ""
if (isHtml) { if (isHtml) {
printMsg = printMsg.htmlFormat() printMsg = HtmlFormatter.format(msg)
} }
if (showTime) { if (showTime) {
printMsg = printMsg =

@ -67,11 +67,11 @@ class AnalyzeRule(val ruleData: RuleDataInterface) : JsExtensions {
return this return this
} }
fun setRedirectUrl(url: String): AnalyzeRule { fun setRedirectUrl(url: String): URL? {
kotlin.runCatching { kotlin.runCatching {
redirectUrl = URL(url.split(AnalyzeUrl.splitUrlRegex, 1)[0]) 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)) { if (book.getDelTag(tag)) {
html = html.replace("<ruby>\\s?([\\u4e00-\\u9fa5])\\s?.*?</ruby>".toRegex(), "$1") html = html.replace("<ruby>\\s?([\\u4e00-\\u9fa5])\\s?.*?</ruby>".toRegex(), "$1")
} }
return html.htmlFormat() return HtmlFormatter.formatKeepImg(html)
} }
return null return null
} }

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

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

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

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

@ -63,7 +63,7 @@ class ReadAloudConfigDialog : DialogFragment() {
get() { get() {
val eid = appCtx.getPrefLong(PreferKey.speakEngine) val eid = appCtx.getPrefLong(PreferKey.speakEngine)
val ht = appDb.httpTTSDao.get(eid) 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?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {

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

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

@ -69,8 +69,7 @@ object ChapterProvider {
@JvmStatic @JvmStatic
lateinit var contentPaint: TextPaint lateinit var contentPaint: TextPaint
/*🖼比汉字宽,跟字一起效果的效果不是很好,用卍取代*/ private const val srcReplaceChar = ""
private const val srcReplaceChar = ""
init { init {
upStyle() upStyle()
@ -91,13 +90,12 @@ object ChapterProvider {
textPages.add(TextPage()) textPages.add(TextPage())
contents.forEachIndexed { index, content -> contents.forEachIndexed { index, content ->
if (book.getImageStyle() == Book.imgStyleText) { if (book.getImageStyle() == Book.imgStyleText) {
var text = content.replace(srcReplaceChar, "") var text = content.replace(srcReplaceChar, "")
val srcList = LinkedList<String>() val srcList = LinkedList<String>()
val sb = StringBuffer() val sb = StringBuffer()
val matcher = AppPattern.imgPattern.matcher(text) val matcher = AppPattern.imgPattern.matcher(text)
while (matcher.find()) { while (matcher.find()) {
matcher.group(1)?.let { it -> matcher.group(1)?.let { src ->
val src = NetworkUtils.getAbsoluteURL(bookChapter.url, it)
srcList.add(src) srcList.add(src)
ImageProvider.getImage(book, bookChapter.index, src) ImageProvider.getImage(book, bookChapter.index, src)
matcher.appendReplacement(sb, srcReplaceChar) matcher.appendReplacement(sb, srcReplaceChar)
@ -119,9 +117,8 @@ object ChapterProvider {
if (text.isNotBlank()) { if (text.isNotBlank()) {
val matcher = AppPattern.imgPattern.matcher(text) val matcher = AppPattern.imgPattern.matcher(text)
if (matcher.find()) { if (matcher.find()) {
matcher.group(1)?.let { it -> matcher.group(1)?.let { src ->
if (!book.isEpub()) { if (!book.isEpub()) {
val src = NetworkUtils.getAbsoluteURL(bookChapter.url, it)
durY = setTypeImage( durY = setTypeImage(
book, bookChapter, src, book, bookChapter, src,
durY, textPages, book.getImageStyle() 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.io.File
import java.util.* 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?.safeTrim() = if (this.isNullOrBlank()) null else this.trim()
fun String?.isContentScheme(): Boolean = this?.startsWith("content://") == true fun String?.isContentScheme(): Boolean = this?.startsWith("content://") == true
@ -50,16 +47,6 @@ fun String?.isJsonArray(): Boolean =
str.startsWith("[") && str.endsWith("]") str.startsWith("[") && str.endsWith("]")
} ?: false } ?: 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 { fun String.splitNotBlank(vararg delimiter: String): Array<String> = run {
this.split(*delimiter).map { it.trim() }.filterNot { it.isBlank() }.toTypedArray() this.split(*delimiter).map { it.trim() }.filterNot { it.isBlank() }.toTypedArray()
} }

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

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

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

@ -811,10 +811,11 @@
<string name="export_folder">导出文件夹</string> <string name="export_folder">导出文件夹</string>
<string name="export_charset">导出编码</string> <string name="export_charset">导出编码</string>
<string name="export_to_web_dav">导出到WebDav</string> <string name="export_to_web_dav">导出到WebDav</string>
<string name="reverse_content">反转内容</string> <string name="reverse_content">Reverse content</string>
<string name="debug">调试</string> <string name="debug">Debug</string>
<string name="crash_log">崩溃日志</string> <string name="crash_log">Crash log</string>
<string name="use_zh_layout">使用自定义中文分行</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> </resources>

Loading…
Cancel
Save