Merge remote-tracking branch 'origin/master' into eink

pull/241/head
Modificator 5 years ago
commit ebf499f27a
  1. 7
      .gitignore
  2. 4
      CHANGELOG.md
  3. 7
      README.md
  4. 78
      app/build.gradle
  5. 58
      app/google-services.json
  6. 13
      app/proguard-rules.pro
  7. 69
      app/src/debug/google-services.json
  8. 4
      app/src/debug/res/values/strings.xml
  9. 6
      app/src/google/res/values-zh-rHK/strings.xml
  10. 6
      app/src/google/res/values/strings.xml
  11. 237
      app/src/main/AndroidManifest.xml
  12. 2
      app/src/main/assets/18PlusList.txt
  13. 129
      app/src/main/assets/help.md
  14. 74
      app/src/main/assets/readConfig.json
  15. 38
      app/src/main/assets/txtChapterRule.json
  16. 121
      app/src/main/assets/txtTocRule.json
  17. 355
      app/src/main/assets/updateLog.md
  18. 53
      app/src/main/assets/web/bookshelf.html
  19. 70
      app/src/main/assets/web/bookshelf.js
  20. 640
      app/src/main/assets/web/index.html
  21. 202
      app/src/main/assets/web/index.js
  22. 176
      app/src/main/assets/web/new/bookshelf.css
  23. 46
      app/src/main/assets/web/new/bookshelf.html
  24. 203
      app/src/main/assets/web/new/bookshelf.js
  25. 1
      app/src/main/assets/web/new/css/about.f23c15cb.css
  26. 1
      app/src/main/assets/web/new/css/app.e1c0d2e4.css
  27. 1
      app/src/main/assets/web/new/css/chunk-vendors.ad4ff18f.css
  28. 1
      app/src/main/assets/web/new/css/detail.42c41bd6.css
  29. BIN
      app/src/main/assets/web/new/fonts/element-icons.535877f5.woff
  30. BIN
      app/src/main/assets/web/new/fonts/element-icons.732389de.ttf
  31. BIN
      app/src/main/assets/web/new/fonts/iconfont.f9a3fb0e.woff
  32. BIN
      app/src/main/assets/web/new/fonts/popfont.f39ecc1a.ttf
  33. BIN
      app/src/main/assets/web/new/fonts/shelffont.6c094b6d.ttf
  34. BIN
      app/src/main/assets/web/new/img/icons/android-chrome-192x192.png
  35. BIN
      app/src/main/assets/web/new/img/icons/android-chrome-512x512.png
  36. BIN
      app/src/main/assets/web/new/img/icons/apple-touch-icon-120x120.png
  37. BIN
      app/src/main/assets/web/new/img/icons/apple-touch-icon-152x152.png
  38. BIN
      app/src/main/assets/web/new/img/icons/apple-touch-icon-180x180.png
  39. BIN
      app/src/main/assets/web/new/img/icons/apple-touch-icon-60x60.png
  40. BIN
      app/src/main/assets/web/new/img/icons/apple-touch-icon-76x76.png
  41. BIN
      app/src/main/assets/web/new/img/icons/apple-touch-icon.png
  42. BIN
      app/src/main/assets/web/new/img/icons/favicon-16x16.png
  43. BIN
      app/src/main/assets/web/new/img/icons/favicon-32x32.png
  44. BIN
      app/src/main/assets/web/new/img/icons/msapplication-icon-144x144.png
  45. BIN
      app/src/main/assets/web/new/img/icons/mstile-150x150.png
  46. 149
      app/src/main/assets/web/new/img/icons/safari-pinned-tab.svg
  47. BIN
      app/src/main/assets/web/new/img/noCover.b5c48bc1.jpeg
  48. 1
      app/src/main/assets/web/new/js/about.2589b5fe.js
  49. 1
      app/src/main/assets/web/new/js/about~detail.08c372e6.js
  50. 1
      app/src/main/assets/web/new/js/app.b25f3cec.js
  51. 33
      app/src/main/assets/web/new/js/chunk-vendors.b3838a2d.js
  52. 1
      app/src/main/assets/web/new/js/detail.043d6e39.js
  53. 1
      app/src/main/assets/web/new/manifest.json
  54. 84
      app/src/main/java/io/legado/app/App.kt
  55. 12
      app/src/main/java/io/legado/app/README.md
  56. 9
      app/src/main/java/io/legado/app/base/BaseActivity.kt
  57. 45
      app/src/main/java/io/legado/app/base/BaseDialogFragment.kt
  58. 12
      app/src/main/java/io/legado/app/base/BaseFragment.kt
  59. 12
      app/src/main/java/io/legado/app/base/BaseService.kt
  60. 65
      app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt
  61. 11
      app/src/main/java/io/legado/app/base/adapter/ItemViewDelegate.kt
  62. 12
      app/src/main/java/io/legado/app/base/adapter/SimpleRecyclerAdapter.kt
  63. 41
      app/src/main/java/io/legado/app/constant/AppConst.kt
  64. 4
      app/src/main/java/io/legado/app/constant/AppPattern.kt
  65. 7
      app/src/main/java/io/legado/app/constant/EventBus.kt
  66. 3
      app/src/main/java/io/legado/app/constant/IntentAction.kt
  67. 27
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  68. 17
      app/src/main/java/io/legado/app/constant/RSSKeywords.kt
  69. 43
      app/src/main/java/io/legado/app/data/AppDatabase.kt
  70. 18
      app/src/main/java/io/legado/app/data/README.md
  71. 9
      app/src/main/java/io/legado/app/data/dao/BookChapterDao.kt
  72. 61
      app/src/main/java/io/legado/app/data/dao/BookDao.kt
  73. 8
      app/src/main/java/io/legado/app/data/dao/BookGroupDao.kt
  74. 36
      app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt
  75. 23
      app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt
  76. 19
      app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt
  77. 11
      app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt
  78. 3
      app/src/main/java/io/legado/app/data/dao/RssStarDao.kt
  79. 39
      app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt
  80. 33
      app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt
  81. 1
      app/src/main/java/io/legado/app/data/entities/BaseBook.kt
  82. 51
      app/src/main/java/io/legado/app/data/entities/Book.kt
  83. 13
      app/src/main/java/io/legado/app/data/entities/BookChapter.kt
  84. 8
      app/src/main/java/io/legado/app/data/entities/BookGroup.kt
  85. 9
      app/src/main/java/io/legado/app/data/entities/BookProgress.kt
  86. 136
      app/src/main/java/io/legado/app/data/entities/BookSource.kt
  87. 34
      app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt
  88. 4
      app/src/main/java/io/legado/app/data/entities/RssArticle.kt
  89. 7
      app/src/main/java/io/legado/app/data/entities/RssReadRecord.kt
  90. 27
      app/src/main/java/io/legado/app/data/entities/RssSource.kt
  91. 16
      app/src/main/java/io/legado/app/data/entities/RssStar.kt
  92. 19
      app/src/main/java/io/legado/app/data/entities/SearchBook.kt
  93. 15
      app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt
  94. 46
      app/src/main/java/io/legado/app/data/entities/rule/BookInfoRule.kt
  95. 33
      app/src/main/java/io/legado/app/data/entities/rule/ContentRule.kt
  96. 47
      app/src/main/java/io/legado/app/data/entities/rule/ExploreRule.kt
  97. 47
      app/src/main/java/io/legado/app/data/entities/rule/SearchRule.kt
  98. 37
      app/src/main/java/io/legado/app/data/entities/rule/TocRule.kt
  99. 35
      app/src/main/java/io/legado/app/help/ActivityHelp.kt
  100. 5
      app/src/main/java/io/legado/app/help/AdapterDataObserverHeader.kt
  101. Some files were not shown because too many files have changed in this diff Show More

7
.gitignore vendored

@ -7,4 +7,9 @@
/captures
.externalNativeBuild
/release
/tmp
/tmp
node_modules/
/app/app
/app/google
/app/gradle.properties
package-lock.json

@ -0,0 +1,4 @@
# 1.0.0 (2020-02-09)

@ -1,2 +1,9 @@
# legado
[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
## 阅读3.0
书源规则 https://celeter.github.io
## 免责声明
https://gedoor.github.io/MyBookshelf/disclaimer.html

@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: "de.timfreiheit.resourceplaceholders"
apply plugin: 'de.timfreiheit.resourceplaceholders'
apply plugin: 'io.fabric'
androidExtensions {
@ -19,6 +19,7 @@ def gitCommits = Integer.parseInt('git rev-list --count HEAD'.execute([], projec
android {
compileSdkVersion 29
flavorDimensions("version")
signingConfigs {
if (project.hasProperty("RELEASE_STORE_FILE")) {
myConfig {
@ -54,7 +55,8 @@ android {
if (project.hasProperty("RELEASE_STORE_FILE")) {
signingConfig signingConfigs.myConfig
}
minifyEnabled false
applicationIdSuffix '.release'
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
@ -72,10 +74,23 @@ android {
}
}
}
productFlavors {
app {
dimension "version"
manifestPlaceholders = [APP_CHANNEL_VALUE: "app"]
}
google {
dimension "version"
applicationId "io.legado.play"
manifestPlaceholders = [APP_CHANNEL_VALUE: "google"]
}
}
compileOptions {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
// Flag to enable support for the new language APIs
//coreLibraryDesugaringEnabled true
// Sets Java compatibility to Java 8
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
@ -95,39 +110,40 @@ kapt {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.12'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
//kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
//fireBase
implementation 'com.google.firebase:firebase-core:17.2.2'
implementation 'com.google.firebase:firebase-core:17.4.1'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
//androidX
implementation 'androidx.core:core-ktx:1.2.0-rc01'
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.media:media:1.1.0'
implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
implementation 'androidx.viewpager2:viewpager2:1.0.0'
implementation 'com.google.android.material:material:1.2.0-alpha04'
implementation 'com.google.android.material:material:1.1.0'
implementation 'com.google.android:flexbox:1.1.0'
implementation 'com.google.code.gson:gson:2.8.6'
//lifecycle
def lifecycle_version = '2.1.0'
def lifecycle_version = '2.2.0'
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
//room
def room_version = '2.2.3'
def room_version = '2.2.5'
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
//paging
implementation 'androidx.paging:paging-runtime:2.1.1'
implementation 'androidx.paging:paging-runtime:2.1.2'
//anko
def anko_version = '0.10.8'
@ -135,46 +151,54 @@ dependencies {
implementation "org.jetbrains.anko:anko-sdk27-listeners:$anko_version"
//liveEventBus
implementation 'com.jeremyliao:live-event-bus-x:1.4.5'
implementation 'com.jeremyliao:live-event-bus-x:1.5.7'
//
def coroutines_version = '1.2.2'
def coroutines_version = '1.3.3'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
//
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'org.jsoup:jsoup:1.12.1'
implementation 'org.jsoup:jsoup:1.13.1'
implementation 'cn.wanghaomiao:JsoupXpath:2.3.2'
implementation 'com.jayway.jsonpath:json-path:2.4.0'
//JS
implementation 'com.github.gedoor:rhino-android:1.3'
//JS rhino
implementation 'com.github.gedoor:rhino-android:1.4'
//Retrofit
implementation 'com.squareup.okhttp3:logging-interceptor:4.1.0'
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
//
//noinspection GradleDependency
implementation 'com.squareup.retrofit2:retrofit:2.7.2'
//Glide
implementation 'com.github.bumptech.glide:glide:4.9.0'
implementation 'com.github.bumptech.glide:glide:4.11.0'
//webServer
implementation 'org.nanohttpd:nanohttpd:2.3.1'
implementation 'org.nanohttpd:nanohttpd-websocket:2.3.1'
//
implementation 'cn.bingoogolapple:bga-qrcode-zxing:1.3.6'
implementation 'cn.bingoogolapple:bga-qrcode-zxing:1.3.7'
//
implementation 'com.jaredrummler:colorpicker:1.1.0'
//apache
implementation 'org.apache.commons:commons-lang3:3.9'
implementation 'org.apache.commons:commons-lang3:3.10'
implementation 'org.apache.commons:commons-text:1.8'
//MarkDown
implementation 'ru.noties.markwon:core:3.0.2'
implementation 'ru.noties.markwon:core:3.1.0'
//
implementation 'com.hankcs:hanlp:portable-1.7.7'
}
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.gms.google-services'
afterEvaluate {
for (Task task : project.tasks.matching { it.name.startsWith('crashlyticsUploadDeobs') }) {
task.enabled = false
}
}

@ -63,6 +63,64 @@
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:453392274790:android:c1481c1c3d3f51eff624a7",
"android_client_info": {
"package_name": "io.legado.app.release"
}
},
"oauth_client": [
{
"client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyD90mfNLhA7cAzzI9SonpSz5mrF5BnmyJA"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:453392274790:android:b891abd2331577dff624a7",
"android_client_info": {
"package_name": "io.legado.play.release"
}
},
"oauth_client": [
{
"client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyD90mfNLhA7cAzzI9SonpSz5mrF5BnmyJA"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"

@ -154,11 +154,12 @@
-keep class **.analyzeRule.**{*;}
# 保持web类
-keep class **.web.**{*;}
#数据类
-keep class **.data.**{*;}
-dontwarn rx.**
-dontwarn okio.**
-dontwarn retrofit2.**
-dontwarn javax.annotation.**
-dontwarn org.apache.log4j.lf5.viewer.**
-dontnote org.apache.log4j.lf5.viewer.**
@ -166,8 +167,10 @@
-dontnote org.python.core.**
-dontwarn com.hwangjr.rxbus.**
-dontwarn okhttp3.**
-dontwarn org.conscrypt.**
-dontwarn com.jeremyliao.liveeventbus.**
-keep class retrofit2.**{*;}
-keep class com.jeremyliao.liveeventbus.** { *; }
-keep class okhttp3.**{*;}
-keep class okio.**{*;}
-keep class com.hwangjr.rxbus.**{*;}
@ -183,6 +186,7 @@
-keep class com.gyf.barlibrary.* {*;}
##JSOUP
-keep class org.jsoup.**{*;}
-keep class **.xpath.**{*;}
-keep class org.slf4j.**{*;}
-dontwarn org.slf4j.**
@ -206,8 +210,6 @@
-keep class javax.script.** { *; }
-keep class com.sun.script.javascript.** { *; }
-keep class org.mozilla.javascript.** { *; }
-dontwarn org.mozilla.javascript.**
-dontwarn sun.**
###EPUB
-dontwarn nl.siegmann.epublib.**
@ -222,9 +224,6 @@
-keepclassmembers class * {
public <init> (org.json.JSONObject);
}
-keep public class com.kunfei.bookshelf.R$*{
public static final int *;
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);

@ -1,69 +0,0 @@
{
"project_info": {
"project_number": "453392274790",
"firebase_url": "https://legado-fca69.firebaseio.com",
"project_id": "legado-fca69",
"storage_bucket": "legado-fca69.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:453392274790:android:1d2b1eefbe0e78cff624a7",
"android_client_info": {
"package_name": "io.legado.app"
}
},
"oauth_client": [
{
"client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyD90mfNLhA7cAzzI9SonpSz5mrF5BnmyJA"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:453392274790:android:c4eac14b1410eec5f624a7",
"android_client_info": {
"package_name": "io.legado.app.debug"
}
},
"oauth_client": [
{
"client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyD90mfNLhA7cAzzI9SonpSz5mrF5BnmyJA"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}

@ -1,4 +1,4 @@
<resources>
<string name="app_name">阅读.debug</string>
<string name="receiving_shared_label">阅读.debug·搜索</string>
<string name="app_name" translatable="false">阅读·D</string>
<string name="receiving_shared_label" translatable="false">阅读·D·搜索</string>
</resources>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">閱讀Pro</string>
</resources>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">阅读Pro</string>
</resources>

@ -3,6 +3,9 @@
xmlns:tools="http://schemas.android.com/tools"
package="io.legado.app">
<uses-permission
android:name="android.permission.MANAGE_DOCUMENTS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
@ -22,14 +25,82 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:requestLegacyExternalStorage="true"
android:theme="@style/AppTheme.Light"
android:requestLegacyExternalStorage="false"
tools:ignore="AllowBackup,GoogleAppIndexingWarning,UnusedAttribute">
<!--主入口-->
<activity android:name=".ui.welcome.WelcomeActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts"
android:launchMode="singleTask" />
</activity>
<!--图标1-->
<activity
android:name=".ui.welcome.Launcher1"
android:icon="@mipmap/launcher1"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts"
android:launchMode="singleTask" />
</activity>
<!--图标2-->
<activity
android:name=".ui.welcome.Launcher2"
android:icon="@mipmap/launcher2"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts"
android:launchMode="singleTask" />
</activity>
<!--图标3-->
<activity
android:name=".ui.welcome.Launcher3"
android:icon="@mipmap/launcher3"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts"
android:launchMode="singleTask" />
</activity>
<!--图标4-->
<activity
android:name=".ui.welcome.Launcher4"
android:icon="@mipmap/launcher4"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts"
android:launchMode="singleTask" />
</activity>
<!--图标5-->
<activity
android:name=".ui.welcome.WelcomeActivity"
android:label="@string/app_name">
android:name=".ui.welcome.Launcher5"
android:icon="@mipmap/launcher5"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@ -39,49 +110,82 @@
android:resource="@xml/shortcuts"
android:launchMode="singleTask" />
</activity>
<!--图标6-->
<activity
android:name=".ui.welcome.Launcher6"
android:icon="@mipmap/launcher6"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts"
android:launchMode="singleTask" />
</activity>
<!--主界面-->
<activity
android:name=".ui.main.MainActivity"
android:launchMode="singleTask"
android:alwaysRetainTaskState="true" />
<!--阅读界面-->
<activity
android:name=".ui.book.read.ReadBookActivity"
android:configChanges="locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
android:launchMode="singleTask" />
<!--书籍详情页-->
<activity
android:name=".ui.book.info.BookInfoActivity"
android:launchMode="singleTop" />
<!--书籍信息编辑-->
<activity
android:name="io.legado.app.ui.book.info.edit.BookInfoEditActivity"
android:launchMode="singleTask" />
<!--音频播放界面-->
<activity
android:name=".ui.audio.AudioPlayActivity"
android:launchMode="singleTask" />
<!--授权界面-->
<activity
android:name=".help.permission.PermissionActivity"
android:theme="@style/Activity.Permission" />
<!--二维码扫描-->
<activity
android:name=".ui.qrcode.QrCodeActivity"
android:launchMode="singleTask" />
<!--书源编辑-->
<activity
android:name=".ui.book.source.edit.BookSourceEditActivity"
android:configChanges="locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize|stateHidden" />
<!--订阅源编辑-->
<activity
android:name=".ui.rss.source.edit.RssSourceEditActivity"
android:configChanges="locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize|stateHidden" />
<!--配置界面-->
<activity
android:name=".ui.config.ConfigActivity"
android:launchMode="singleTask" />
<activity
android:name=".ui.replacerule.ReplaceRuleActivity"
android:launchMode="singleTask" />
<!--搜索界面-->
<activity
android:name="io.legado.app.ui.book.search.SearchActivity"
android:launchMode="singleTask" />
<!--关于界面-->
<activity
android:name=".ui.about.AboutActivity"
android:launchMode="singleTask" />
<activity
android:name=".ui.qrcode.QrCodeActivity"
android:launchMode="singleTask" />
<!--捐赠界面-->
<activity
android:name=".ui.about.DonateActivity"
android:launchMode="singleTask" />
<activity android:name=".ui.book.info.BookInfoActivity" />
<activity android:name="io.legado.app.ui.book.info.edit.BookInfoEditActivity" />
<activity android:name=".ui.book.source.debug.BookSourceDebugActivity" />
<activity android:name=".ui.book.source.manage.BookSourceActivity">
<!--书源管理-->
<activity
android:name=".ui.book.source.manage.BookSourceActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@ -93,17 +197,85 @@
android:scheme="yuedu" />
</intent-filter>
</activity>
<activity android:name=".ui.chapterlist.ChapterListActivity" />
<activity android:name=".ui.rss.read.ReadRssActivity" />
<!--订阅源管理-->
<activity
android:name=".ui.audio.AudioPlayActivity"
android:launchMode="singleTask" />
<activity android:name=".ui.importbook.ImportBookActivity" />
<activity android:name=".ui.explore.ExploreShowActivity" />
<activity android:name=".ui.rss.source.manage.RssSourceActivity" />
<activity android:name=".ui.rss.source.debug.RssSourceDebugActivity" />
<activity android:name=".ui.rss.article.RssArticlesActivity" />
<activity android:name=".ui.download.DownloadActivity" />
android:name=".ui.rss.source.manage.RssSourceActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="rsssource"
android:scheme="yuedu" />
</intent-filter>
</activity>
<!--替换规则界面-->
<activity
android:name=".ui.replacerule.ReplaceRuleActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="replace"
android:scheme="yuedu" />
</intent-filter>
</activity>
<!--书籍管理-->
<activity
android:name=".ui.book.arrange.ArrangeBookActivity"
android:launchMode="singleTop" />
<!--书源调试-->
<activity
android:name=".ui.book.source.debug.BookSourceDebugActivity"
android:launchMode="singleTop" />
<!--目录-->
<activity
android:name="io.legado.app.ui.book.chapterlist.ChapterListActivity"
android:screenOrientation="behind"
android:launchMode="singleTop" />
<!--RSS阅读-->
<activity
android:name=".ui.rss.read.ReadRssActivity"
android:configChanges="orientation|screenSize"
android:hardwareAccelerated="true"
android:launchMode="singleTop" />
<!--导入书籍-->
<activity
android:name="io.legado.app.ui.book.local.ImportBookActivity"
android:launchMode="singleTop" />
<!--发现界面-->
<activity
android:name="io.legado.app.ui.book.explore.ExploreShowActivity"
android:launchMode="singleTop" />
<!--订阅源调试-->
<activity
android:name=".ui.rss.source.debug.RssSourceDebugActivity"
android:launchMode="singleTop" />
<!--订阅条目-->
<activity
android:name=".ui.rss.article.RssSortActivity"
android:launchMode="singleTop" />
<!--Rss收藏-->
<activity
android:name=".ui.rss.favorites.RssFavoritesActivity"
android:launchMode="singleTop" />
<!--下载界面-->
<activity
android:name="io.legado.app.ui.book.download.DownloadActivity"
android:launchMode="singleTop" />
<!--书源登录-->
<activity
android:name=".ui.login.SourceLogin"
android:configChanges="orientation|screenSize"
android:hardwareAccelerated="true" />
<!--文字处理-->
<activity
android:name=".receiver.SharedReceiverActivity"
android:label="@string/receiving_shared_label">
@ -118,6 +290,19 @@
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<activity
android:name=".ui.config.FileAssociationActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file"/>
<data android:scheme="content"/>
<data android:mimeType="text/plain" />
<data android:mimeType="application/json" />
</intent-filter>
</activity>
<service android:name=".service.CheckSourceService" />
<service android:name=".service.DownloadService" />
@ -141,6 +326,10 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<meta-data
android:name="channel"
android:value="${APP_CHANNEL_VALUE}" />
</application>
</manifest>

@ -0,0 +1,2 @@
OGN5dS5jb20=
c2cwMC54eXo=

@ -0,0 +1,129 @@
## 常见问题
1.为什么第一次安装好之后什么东西都没有?
* 因为阅读只是一个转码工具,不提供内容,第一次安装app,需要自己手动导入书源,可以从QQ群、公众号“开源阅读软件”、酷安评论里获取由书友制作分享的书源。
2.如何导入本地书源文件?
* 下载群文件里的书源文件(书源格式后缀有txt、json,其中json文件某些情况下无法导入,需要修改后缀为txt格式才可导入);
* 打开阅读软件;
* 我的 - 点击“书源管理”;
* 点击右上角选择“本地导入”;
* 左下角选择书源文件所在的路径;
* 点击书源文件导入;
* 导入后返回书源管理界面;
* 新版qq下载路径:Android/data/com.tencent.mobileqq/Tencent/QQfile_recv/
3.如何新建大佬发的单独书源?
* 复制书源代码;
* 打开阅读软件;
* 我的 - 点击“书源管理”;
* 右上角选择“新建书源”;
* 进入新建书源后点击右上角“粘贴源”;
* 粘贴书源完成后点击上方保存;
* 本次新建单独书源操作完成。
* 注:如果书源有错误或者复制不全会显示格式错误,请重新复制。
4.为什么导入2.0书源后看不了书?
* 2.0部分书源并不适用3.0,建议导入后进行筛选。
5.阅读2.0数据如何导入阅读3.0?
* 先对阅读2.0的数据进行备份,然后进入阅读3.0,点击“我的”,选择“备份与恢复”,再点击“导入旧版本数据”。
6.如何给朋友分享我的书源?
* 打开阅读软件;
* 点击备份;
* 打开手机自带的文件管理;
* 手机自带内存根目录找到YueDu3.0文件夹;
* 找到myBookSource.json长按选择分享;
* 选择微信分享或者QQ分享;
* 选择你要分享的好友点击发送;
* 好友接收后在手机自带内存根目录找到myBookSource.json文件(QQ在tencent--QQfile_recv微信在Tencent--MicroMsg--Download);
* 复制该文件到手机自带内存根目录找到YueDu3.0文件夹(如已有该文件请先删除该文件或者备份到其他地方再复制到文件夹);
* 打开阅读软件点击恢复。
* 注:备份路径如已修改过请在修改后的路径下查找书源文件。
7.目前阅读支持哪些格式的本地书籍?
* 目前支持TXT、EPUB格式(只支持显示EPUB里的文本内容,还不支持显示图片)。
8.如何刷新书架?
* 在书架界面下拉即可刷新。
9.书架界面书籍右上角的红色或者灰色背景小数字代表什么?
* 红色代表书籍有更新,灰色代表无更新,数字代表未读章节。
10.如何查看书籍详情?
* 长按书籍。
11.如何对书架上的书进行删除、切换书架的操作?
* 书籍详情页操作即可。
12.如何禁止或允许某本书更新?
* 书籍详情页,点击右上角 - “允许更新”。
13.如何更换小说封面、名字、作者或简介?
* 书籍详情页,点击右上角修改按钮。
14.怎么使用自定义字体?
* 阅读界面 - 字体-点击右上角选择字体文件路径。
15.目前支持哪些格式的字体文件?
* 目前支持ttf、otf格式。
16.书籍经常“正在加载中”怎么办?
* 在线书籍出现这个问题通常是由于源质量不好或不兼容引起的,可以换其它源多试试;本地书籍出现这个问题大概率是目录规则问题,手动切换规则可以解决。
17.书籍内容只有标题,正文内容是路径怎么办?
* 通常是缓存路径引起的,更换缓存路径即可。
18.效验书源显示失效就说明书源不能用了吗?
* 效验书源只是测试书源,可以做个参考,失效了不代表书源不能用了。
19.发现和正版书源能不能使用?
* 发现和正版书源只能用来找书,看排行榜,不能用来看书,如需看书请切换书源。
20.替换净化是什么?
* 替换净化可以去除书籍内容里的广告、错别字、屏蔽词等。
21.如何自己填写净化替换规则?
* 第一行:替换规则名称 - 根据自己需求对替换净化规则进行命名;
* 第二行:分组 - 净化规则的分组组别;
* 第三行:替换规则 - 填写需要被替换的内容;
* 第四行:替换为 - 填写想替换成的内容(如不填则默认表示删除第二行里填写的内容);
* 第五行:替换范围,选填书名或者源名 - 填写此替换净化规则需要对哪本书籍或者哪个书源生效(如不填则对所有书籍和书源生效)。
* 注:如常规去除方法去除不掉,则需要勾选“使用正则表达式”,同时第二行里的替换规则也需要按照正则表达式来填写(正则表达式填写方法可自行百度学习)。
22.如何听书?
* 可以使用手机自带的朗读引擎,也可使用第三方如谷歌、小米等朗读引擎。
* 【具体操作:安装-系统设置-其他高级设置-辅助功能-TTS输出-选择安装的朗读引擎(不同品牌手机的操作方法及步骤也不同,视情况而定)。】
23.如何设置屏幕方向、屏幕显示时长、显示/隐藏状态栏、显示/隐藏导航栏、音量键翻页、长按选择文本、点击总是翻下一页、自定义翻页案件?
* 阅读界面,设置(可上划,下面还有其他设置)。
24.搜索的时候感觉手机卡顿,如何解决?
* 我的 - 其他设置 - “更新和搜索线程数”调低。
25.更新前有什么注意事项?
* 要做好备份。
26.看书时如遇到“目录为空”、“加载失败”和长串英文等情况怎么办?
* 一般是书源问题,切换书源即可。
27.为什么书源这么多,发现里却只有一点点?
* 书源想要在发现界面里显示需要在书源里添加发现规则,并不是所有书源都有发现规则。
28.云备份在哪?
* 我的 - 备份与恢复 - WebDav设置。
29.如何操作进行云备份?
* 侧栏设置,WebDav设置;
* 正确填写WebDAV 服务器地址、WebDAV 账号、WebDAV 密码;(要获得这三项的信息,需要注册一个坚果云账号,如果直接在手机上注册,坚果云会让你下载app,过程比较麻烦,为了一步到位,最好是在电脑上打开这个注册链接:https://www.jianguoyun.com/d/signup;注册后,进入坚果云;点击右上角账户名处选择 “账户信息”,然后选择“安全选项”;在“安全选项” 中找到“第三方应用管理”,并选择“添加应用”,输入名称如“阅读”后,会生成密码,选择完成;其中https://dav.jianguoyun.com/dav/就是填入“WebDAV 服务器地址”的内容,“使用情况”后面的邮箱地址就是你的“WebDAV 账号”,点击显示密码后得到的密码就是你的“WebDAV 密码”。)
* 无需操作,APP默认每天自动云备份一次。
30.关于云备份的相关说明
* 在正确设置好云备份的情况下,APP默认每天自动云备份一次,当日多次手动云备份会对当日的旧云备份文件进行覆盖,并不会覆盖之前及之后不同日期的备份文件,每天所自动云备份的文件会按照日期进行命名。
31.本地备份和云备份都能备份哪些东西?
* 书架、看书进度、搜索记录、书源、替换、APP设置等都会备份,基本涵盖所有内容。
32.出现某些未知bug怎么办?
* 清除软件数据试试看,不行再进行反馈。

@ -1,72 +1,62 @@
[
{
"bgStr": "羊皮纸2.jpg",
"bgType": 1,
"bgStr": "#EBD9BB",
"bgStrNight": "#1E2021",
"textColor": "#63543C",
"textColorNight": "#DCDFE1",
"bgType": 0,
"bgTypeNight": 0,
"darkStatusIcon": true,
"textColor": "#5E432E",
"textSize": 24,
"letterSpacing": 0,
"lineSpacingExtra": 10,
"lineSpacingMultiplier": 1.2,
"paddingLeft": 16,
"paddingRight": 16,
"paddingTop": 0,
"paddingBottom": 0
"lineSpacingExtra": 10
},
{
"bgStr": "#C6BAA1",
"bgStr": "#DDC090",
"bgStrNight": "#3C3F43",
"textColor": "#3E3422",
"textColorNight": "#DCDFE1",
"bgType": 0,
"bgTypeNight": 0,
"darkStatusIcon": true,
"textColor": "#5E432E",
"textSize": 24,
"letterSpacing": 0,
"lineSpacingExtra": 10,
"lineSpacingMultiplier": 1.2,
"paddingLeft": 16,
"paddingRight": 16,
"paddingTop": 0,
"paddingBottom": 0
"lineSpacingExtra": 10
},
{
"bgStr": "#015A86",
"bgStr": "#C2D8AA",
"bgStrNight": "#3C3F43",
"textColor": "#596C44",
"textColorNight": "#88C16F",
"bgType": 0,
"bgTypeNight": 0,
"darkStatusIcon": false,
"textColor": "#FFFFFF",
"textSize": 24,
"letterSpacing": 0,
"lineSpacingExtra": 10,
"lineSpacingMultiplier": 1.2,
"paddingLeft": 16,
"paddingRight": 16,
"paddingTop": 0,
"paddingBottom": 0
"lineSpacingExtra": 10
},
{
"bgStr": "宁静夜色",
"bgType": 1,
"bgStr": "#DBB8E2",
"bgStrNight": "#3C3F43",
"textColor": "#68516C",
"textColorNight": "#F6AEAE",
"bgType": 0,
"bgTypeNight": 0,
"darkStatusIcon": false,
"textColor": "#adadad",
"textSize": 24,
"letterSpacing": 0,
"lineSpacingExtra": 10,
"lineSpacingMultiplier": 1.2,
"paddingLeft": 16,
"paddingRight": 16,
"paddingTop": 0,
"paddingBottom": 0
"lineSpacingExtra": 10
},
{
"bgStr": "#000000",
"bgStr": "#ABCEE0",
"bgStrNight": "#3C3F43",
"textColor": "#3D4C54",
"textColorNight": "#90BFF5",
"bgType": 0,
"bgTypeNight": 0,
"darkStatusIcon": false,
"textColor": "#adadad",
"textSize": 24,
"letterSpacing": 0,
"lineSpacingExtra": 10,
"lineSpacingMultiplier": 1.2,
"paddingLeft": 16,
"paddingRight": 16,
"paddingTop": 0,
"paddingBottom": 0
"lineSpacingExtra": 10
}
]

@ -1,38 +0,0 @@
[
{
"enable": true,
"name": "默认正则1",
"rule": "^(.{0,8})(第)([0-9零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10})([章节卷集部篇回场])(.{0,30})$",
"serialNumber": 0
},
{
"enable": true,
"name": "默认正则2",
"rule": "^([0-9]{1,5})([\\,\\.,-])(.{1,20})$",
"serialNumber": 1
},
{
"enable": true,
"name": "默认正则3",
"rule": "^(\\s{0,4})([\\(【《]?(卷)?)([0-9零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10})([\\.:: \f\t])(.{0,30})$",
"serialNumber": 2
},
{
"enable": true,
"name": "默认正则4",
"rule": "^(\\s{0,4})([\\((【《])(.{0,30})([\\))】》])(\\s{0,2})$",
"serialNumber": 3
},
{
"enable": true,
"name": "默认正则5",
"rule": "^(\\s{0,4})(正文)(.{0,20})$",
"serialNumber": 4
},
{
"enable": true,
"name": "默认正则6",
"rule": "^(.{0,4})(Chapter|chapter)(\\s{0,4})([0-9]{1,4})(.{0,30})$",
"serialNumber": 5
}
]

@ -0,0 +1,121 @@
[
{
"id": -1,
"enable": true,
"name": "目录",
"rule": "^[  \\t]{0,4}(?:序章|楔子|正文(?!完|结)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$",
"serialNumber": -17
},
{
"id": -2,
"enable": false,
"name": "目录(去空白)",
"rule": "(?<=[ \\s])(?:序章|楔子|正文(?!完|结)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$",
"serialNumber": -16
},
{
"id": -3,
"enable": false,
"name": "目录(匹配简介)",
"rule": "(?<=[ \\s])(?:(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完|结)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|回(?![合来事去])|场(?![和合比电是])|篇(?!张))).{0,30}$",
"serialNumber": -15
},
{
"id": -4,
"enable": false,
"name": "目录(古典、轻小说备用)",
"rule": "^[  \\t]{0,4}(?:序章|楔子|正文(?!完|结)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|回(?![合来事去])|场(?![和合比电是])|篇(?!张))).{0,30}$",
"serialNumber": -14
},
{
"id": -5,
"enable": false,
"name": "数字(纯数字标题)",
"rule": "(?<=[ \\s])\\d+[  \\t]{0,4}$",
"serialNumber": -13
},
{
"id": -6,
"enable": true,
"name": "数字 分隔符 标题名称",
"rule": "^[  \\t]{0,4}\\d{1,5}[,., 、_—\\-].{1,30}$",
"serialNumber": -12
},
{
"id": -7,
"enable": true,
"name": "大写数字 分隔符 标题名称",
"rule": "^[  \\t]{0,4}[零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8}[ 、_—\\-].{1,30}$",
"serialNumber": -11
},
{
"id": -8,
"enable": true,
"name": "正文 标题/序号",
"rule": "^[  \\t]{0,4}正文[  ]{1,4}.{0,20}$",
"serialNumber": -10
},
{
"id": -9,
"enable": true,
"name": "Chapter/Section/Part/Episode 序号 标题",
"rule": "^[  \\t]{0,4}(?:[Cc]hapter|[Ss]ection|[Pp]art|PART|[Ee]pisode|(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完|结)|终章|后记|尾声|番外)\\s{0,4}\\d{1,4}.{0,30}$",
"serialNumber": -9
},
{
"id": -10,
"enable": false,
"name": "Chapter(去简介)",
"rule": "^[  \\t]{0,4}(?:[Cc]hapter|[Ss]ection|[Pp]art|PART|[Ee]pisode)\\s{0,4}\\d{1,4}.{0,30}$",
"serialNumber": -8
},
{
"id": -11,
"enable": true,
"name": "特殊符号 序号 标题",
"rule": "(?<=[\\s ])[【〔〖「『〈[\\[](?:第|[Cc]hapter)[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10}[章节].{0,20}$",
"serialNumber": -7
},
{
"id": -12,
"enable": false,
"name": "特殊符号 标题(成对)",
"rule": "(?<=[\\s ]{0,4})(?:[\\[〈「『〖〔《(【\\(].{1,30}[\\)】)》〕〗』」〉\\]]?|(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完|结)|终章|后记|尾声|番外)[  ]{0,4}$",
"serialNumber": -6
},
{
"id": -13,
"enable":true,
"name": "特殊符号 标题(单个)",
"rule": "(?<=[\\s ]{0,4})(?:[☆★✦✧].{1,30}|(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完|结)|终章|后记|尾声|番外)[  ]{0,4}$",
"serialNumber": -5
},
{
"id": -14,
"enable": true,
"name": "章/卷 序号 标题",
"rule": "^[ \\t ]{0,4}(?:(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完|结)|终章|后记|尾声|番外|[卷章][\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8})[  ]{0,4}.{0,30}$",
"serialNumber": -4
},
{
"id": -15,
"enable":false,
"name": "顶格标题",
"rule": "^\\S.{1,20}$",
"serialNumber": -3
},
{
"id": -16,
"enable":false,
"name": "双标题(前向)",
"rule": "(?m)(?<=[ \\t ]{0,4})第[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8}章.{0,30}$(?=[\\s ]{0,8}第[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8}章)",
"serialNumber": -2
},
{
"id": -17,
"enable":false,
"name": "双标题(后向)",
"rule": "(?m)(?<=[ \\t ]{0,4}第[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8}章.{0,30}$[\\s ]{0,8})第[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8}章.{0,30}$",
"serialNumber": -1
}
]

@ -1,7 +1,341 @@
## 更新日志
* 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
* 请关注公众号[开源阅读]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。
* 新公众号[开源阅读]()已启用,[开源阅读软件]()备用
* 旧版数据导入教程:
* 先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
**2020/05/20**
* Rss列表增加一种显示样式
**2020/05/18**
* 修复http://alanskycn.gitee.io/书源导入失败问题,被屏蔽UA了
* Rss列表添加样式切换
**2020/05/17**
* 自动翻页功能完成
* 替换规则输入时弹出辅助输入条
**2020/05/10**
* 添加识别rss分组中的频道信息,在菜单中可以切换频道 from yangyxd
* 源管理添加置底,批量置顶,批量置地
* 封面选择本地图片完成
**2020/05/04**
* 优化txt文件目录解析
**2020/05/03**
* 优化一些界面显示问题
* 订阅源添加style
* 修复一些重复目录的bug
**2020/05/02**
* 修复不停换源的bug
* 修复本地书籍自动换源
* 修复书源校验的一些问题
**2020/05/01**
* 尝试修复朗读时可能错位的bug
* 添加自动换源配置
* 换源添加禁用菜单
**2020/04/29**
* 修复bug
* 订阅界面添加长按菜单
**2020/04/26**
* 添加导入旧的书源转换
* 修复不自动朗读下一章的bug
**2020/04/25**
* 修复翻页按键设置为空时崩溃的bug
* 翻页按键优先自定义按键,可覆盖音量按键
* 写书源时的辅助键盘添加※
* 更改了书源格式,不再需要转义符
**2020/04/24**
* 坚果云最近调整了策略,必须使用应用密码才能备份,用户信息,安全,第三方应用
* text目录规则添加id字段,负值为系统自带规则
* 其它一些优化
**2020/04/20**
* 优化阅读界面信息显示
**2020/04/19**
* 添加阅读界面各种信息设置
**2020/04/18**
* feat: 中文简繁处理库换成 HanLP, 中文增加 zh-rHK 翻译, hingbong
* 修复更新时间不对的bug
**2020/04/13**
* 去除rss朗读时的引号
**2020/04/13**
* 修复调用webView返回结果多了引号的bug
**2020/04/12**
* 解决无法取消加粗的bug
* 修复换源自动加入书架的bug
**2020/04/09**
* 修复书架刷新闪烁
**2020/04/08**
* 可以隐藏书架未分组
**2020/04/07**
* 书架添加未分组,有未分组书籍时自动显示
* 其它一些优化
**2020/04/04**
* 优化备份逻辑
* 修复订阅分类太多显示不全的bug
* 修复一些分类要手动刷新的问题
**2020/04/02**
* 书架书名和作者作为唯一值
* 添加订阅分类,分类规则和发现一样,分类一::url1 && 分类2::url2
**2020/03/29**
* 添加退出软件后是否响应耳机按键的开关
* 优化书源校验
**2020/03/26**
* 修复txt目录bug
* 最近工作比较忙,只有晚上有时间写软件,bug之类的不要催,白天不回消息
**2020/03/25**
* 修复7.1.1的网络问题,是retrofit2库最新版本的bug,暂时退回上版本
* 去除下载路径的配置,减少错误
* 添加隐藏状态栏是否扩展到刘海
**2020/03/24**
* txt文件第一章之前的文字不再放到简介里
* 优化txt目录识别,章节超过3万字判断为目录识别错误重新识别
* 修复文件关联 by wqfantexi
**2020/03/22**
* 添加文件关联 by wqfantexi
* 手动排序可以了,在书架整理里面拖动排序
* 删除分组时同时删除书籍里的分组信息,下次添加新分组时不会自动出现在分组内
* 修复换源丢失分组信息的bug
* 修复部分朗读引擎不自动朗读下一章的bug
**2020/03/21**
* 详情页点击书名搜索
**2020/03/20**
* 自动备份文件和手动备份文件分开
* 修复一些rss收藏取消不了的bug
* 修复rss请求头无效bug
**2020/03/19**
* 美化界面我的 by yangyxd
* 优化搜索
**2020/03/18**
* 尝试修复搜索时崩溃
* 解决看过书籍的移到顶部需要向上滚动才能看到的bug
* 只有再书源被删除找不到书源时才会自动换源
* 美化界面 by yangyxd
* 订阅后台播放
**2020/03/16**
* 修复滚动模式切换章节位置不归0的bug
* 修复文字选择更多菜单在部分手机上报错的bug
* 修复文字选择菜单问题
**2020/03/15**
* 加载正文无书源时自动换源
**2020/03/14**
* 修改导航栏图标
**2020/03/13**
* 更改书架控件,ViewPager2替换回2.0使用的ViewPager,解决下拉不流畅问题
* 修复点击作者搜索后,打开的详情页还是原来的书籍的bug
* 修改朗读菜单
* 优化rss朗读
**2020/03/12**
* 导入本地添加需要权限模式
**2020/03/11**
* 修复调节上边距时下边距一起动的bug
* 适配沚水的web阅读 by 六月
* 分组管理页面调整 by yangyxd
**2020/03/10**
* 优化文字选择菜单弹出位置
* 添加屏幕方向控制
* 添加点击作者搜索
**2020/03/09**
* 底部文字对齐
* 主题添加阴影调节 by yangyxd
**2020/03/08**
* 订阅长按保存图片
* 订阅全屏播放
* 书架全部分组可以隐藏了
* 内置web书架基本能用了 by 六月
* 书架整理加入未分组
* 显示总进度
* 隐藏状态栏时,标题显示在上方
**2020/03/07**
* 添加标题上下间距调整
* 添加标题大小调整
* 书籍整理添加批量启用禁用更新
* 换源禁用书源不显示
* 修复搜索界面简介最下面显示半行文字
* 搜索历史改为多行
**2020/03/06**
* 添加隐藏标题
* 行距段距改成倍距,根据字体大小变化
* 修复翻页时右下角页数闪烁
* 修复朗读错行
* 添加底部分隔线,开关在边距设置里
**2020/03/05**
* 修复翻页动画
* 修复主题模式跟随
* 修复滚动翻页切换章节时跳动
* 适配阅读3.0的web做源
* 本地目录规则网络导入
**2020/03/04**
* 修复仿真翻页动画
* 添加阅读记录同步,正常退出进入软件时同步阅读记录
**2020/03/03**
* 修复bug
* 优化排版,确保段距为0时每行在相同的位置
* 修复底部遮挡
**2020/03/02**
* 添加书源登录
* 替换规则实时生效
* 页面最后一行计算是否能放下时不计算行距
* 优化翻页动画
* 优化书源校验
* 按键翻页有动画了
**2020/03/01**
* 修复书源解析的一个bug
* 添加底部操作栏颜色配置
* 修复滚动点击翻页,修复滚动最后一页显示加载中
* 去除备份恢复默认路径
* 尝试修复部分手机一键导入书源报错
* 翻页还有些bug不用反馈了,我已经知道,会修复的
**2020/02/29**
* 添加书源一键导入
* 修复主题模式跟随系统
* 修复书源校验
* 添加书架排序
* 添加点击翻页开关
* 修复共用布局没有记住配置的bug
**2020/02/28**
* 解决阅读界面部分字体超出范围的问题
* 修复背景切换有时空白的bug
* 修复滚动翻页问题
**2020/02/27**
* 修复bug,边距调节,换源等一些bug,记不清了
* 修复默认字体问题
* 改了下包名,好上架应用市场
**2020/02/26**
* 修复仿真翻页
* 功能添加: 选择默认字体时, 可选择字体默认字体(非衬线), 系统衬线字体, 系统等宽字体by hingbong
**2020/02/25**
* 优化文本选择和滚动,感觉很完美了
**2020/02/24**
* 滚动暂时可以滚了,先这样吧,头大
* 紧急修复朗读报错的bug
**2020/02/23**
* 修复BUG
* 本地目录正则自定义完成
* 选择文本修复框选不全的问题,增加操作按钮
**2020/02/22**
* 长按选择完成
**2020/02/21**
* 重写了阅读界面,实现了段距调整,两端对齐,页眉页脚调整
* 选择文本暂不可用,滚动暂不可用,仿真翻页还有问题
**2020/02/19**
* 导出功能完成
* 其它一些优化,仿真翻页有点问题,还没找到问题所在
**2020/02/15**
* 修复bug
* 添加一个图标
* 阅读界面文本选择开关
* 书源管理发现开启关闭标志
**2020/02/14**
* 书籍分组支持一本书籍在多个分组,既可以在追更,又可以在玄幻
* 搜索界面限制刷新频率,每秒刷新一次
* 添加一种图标,2.0的老图标
**2020/02/13**
* 修复BUG
* 优化已下载检测,解决目录卡顿
* 添加切换图标
**2020/02/12**
* 修复bug
* 优化,网页编码优先使用书源配置的编码
* 其它一些优化
* 添加简繁转换
**2020/02/10**
* 多页目录并行获取解析
* 优化详情页
* 优化换源页面,添加换源是否加载目录配置
* 换源顺序按书源顺序排列
**2020/02/09**
* 优化书源管理,备份恢复
* 主题色修改,底部操作栏更明显
**2020/02/08**
* 书架分组调整顺序后,书架及时变动
**2020/02/07**
* 优化
* 书源校验
* 书架整理
**2020/02/05**
* 修复bug
* Rss收藏功能完成
* Rss已读标记不会再丢失
**2020/02/04**
* 主界面切换时自动隐藏键盘
* 添加本地书籍完成,解析txt文件完成,本地txt可以看了
* 封面换源,书籍信息界面点击封面弹出封面换源界面
* 默认封面绘制书名和作者
* 修复在线朗读遇到单独标点,停止朗读的问题
**2020/02/02**
* merged commit e584606, rss修复BaseURL模式下部分图片无法加载, 修复可能出现的乱码
* 菜单添加网址功能完成
**2020/01/31**
* 修复搜索闪退,因为默认线程为0了
**2020/01/30**
* 优化缓存文件夹选择,不再需要存储权限
* 修复替换净化导入报错的bug
**2020/01/27**
* 添加根据系统主题切换夜间模式
@ -49,14 +383,7 @@
- 调试正文页>>输入正文页URL,如:`--https://www.zhaishuyuan.com/chapter/30394/20940996`
* 修改订阅中自动添加style的情景
订阅源的内容规则中存在`<style>``style=`,
```
<style>
img{max-width:100% !important; width:auto; height:auto;}
video{object-fit:fill; max-width:100% !important; width:auto; height:auto;}
body{word-wrap:break-word; height:auto;max-width: 100%; width:auto;}
</style>
```
订阅源的内容规则中存在`<style>``style=`
**2019/12/28**
* 添加下载界面
@ -87,13 +414,13 @@
* 最近感冒了,发热咳嗽还没好,继续咸鱼
**2019/12/12**
* [fix]web服务停止问题
* web服务停止问题
* 默认显示沉浸式状态栏
**2019/12/09**
* [add]其他设置->清理缓存
* [mod]调整深色模式配色,预适配Android10
* [mod]启用web服务
* 其他设置->清理缓存
* 调整深色模式配色,预适配Android10
* 启用web服务
**2019/12/03**
* from Celeter:

@ -1,32 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>阅读书架</title>
<link href="bookshelf.css" rel="stylesheet"/>
<meta charset="utf-8" />
<title>阅读3.0书架</title>
<link rel="icon" href="favicon.ico">
<link href="bookshelf.css" rel="stylesheet" />
</head>
<body>
<button id="top" class="top"></button>
<button id="showchapter" class="showchapter"></button>
<button id="hidebooks" class="hidebooks"></button>
<div class="nav">
<button id="back">返回</button>
<button id="type">所有书籍 ▼</button>
<button id="sort">手动排序 ▼</button>
<button id="setting">阅读设置</button>
<input type="text" class="address" id="address" title="阅读APP地址或IP" value=""/>
<button id="refresh">重新加载</button>
</div>
<button id="top" class="top"></button>
<button id="showchapter" class="showchapter"></button>
<button id="hidebooks" class="hidebooks"></button>
<div class="nav">
<button id="back">返回</button>
<button id="type">所有书籍 ▼</button>
<button id="sort">手动排序 ▼</button>
<button id="setting">阅读设置</button>
<input type="text" class="address" id="address" title="阅读APP地址或IP" value="" />
<button id="refresh">重新加载</button>
</div>
<div class="allcontent" id="allcontent">
<div id="books" class="books"></div>
<div id="more" class="more">
<div id="info" class="info"></div>
<div class="clear"></div>
<div id="chapter" class="chapter"></div>
<div id="content" class="content"></div>
<div class="allcontent" id="allcontent">
<div id="books" class="books"></div>
<div id="more" class="more">
<div id="info" class="info"></div>
<div class="clear"></div>
<div id="chapter" class="chapter"></div>
<div id="content" class="content"></div>
<div id="page" class="button">
<center><button id='up'>上一章</button><button id='down'>下一章</button></center>
</div>
</div>
</div>
</div>
<script src="bookshelf.js"></script>
<script src="bookshelf.js"></script>
</body>
</html>

@ -1,11 +1,14 @@
var $ = document.querySelector.bind(document)
, $$ = document.querySelectorAll.bind(document)
, $c = document.createElement.bind(document)
, randomImg = "http://acg.bakayun.cn/randbg.php?t=dfzh"
, randomImg = "http://api.mtyqx.cn/api/random.php"
, randomImg2 = "http://img.xjh.me/random_img.php"
, books
;
var now_chapter = -1;
var sum_chapter = 0;
var formatTime = value => {
return new Date(value).toLocaleString('zh-CN', {
hour12: false, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"
@ -13,13 +16,13 @@ var formatTime = value => {
};
var apiMap = {
getBookshelf: "/getBookshelf",
getChapterList: "/getChapterList",
getBookContent: "/getBookContent",
saveBook: "/saveBook"
"getBookshelf": "/getBookshelf",
"getChapterList": "/getChapterList",
"getBookContent": "/getBookContent",
"saveBook": "/saveBook"
};
var apiAddress = (apiName, url) => {
var apiAddress = (apiName, url, index) => {
let address = $('#address').value || window.location.host;
if (!(/^http|^\/\//).test(address)) {
address = "//" + address;
@ -28,6 +31,9 @@ var apiAddress = (apiName, url) => {
address += ":1122";
}
localStorage.setItem('address', address);
if (apiName == "getBookContent") {
return address + apiMap[apiName] + (url ? "?url=" + encodeURIComponent(url) : "") + "&index=" + index;
}
return address + apiMap[apiName] + (url ? "?url=" + encodeURIComponent(url) : "");
};
@ -41,12 +47,12 @@ var init = () => {
alert(getBookshelf.errorMsg);
return;
}
books = data.data.sort((book1, book2) => book1.serialNumber - book2.serialNumber);
books.forEach(book => {
books = data.data;
books.forEach((book, i) => {
let bookDiv = $c("div");
let img = $c("img");
img.src = book.coverUrl || randomImg;
img.setAttribute("data-series-num", book.serialNumber);
img.setAttribute("data-series-num", i);
bookDiv.appendChild(img);
bookDiv.innerHTML += `<table><tbody>
<tr><td>书名</td><td>${book.name}</td></tr>
@ -59,6 +65,8 @@ var init = () => {
});
$$('#books img').forEach(bookImg =>
bookImg.addEventListener("click", () => {
now_chapter = -1;
sum_chapter = 0;
$('#allcontent').classList.add("read");
var book = books[bookImg.getAttribute("data-series-num")];
$("#info").innerHTML = `<img src="${bookImg.src}">
@ -85,11 +93,13 @@ var init = () => {
data.data.forEach(chapter => {
let ch = $c("button");
ch.setAttribute("data-url", chapter.durChapterUrl);
ch.setAttribute("title", chapter.durChapterName);
ch.innerHTML = chapter.durChapterName.length > 15 ? chapter.durChapterName.substring(0, 14) + "..." : chapter.durChapterName;
ch.setAttribute("data-url", chapter.bookUrl);
ch.setAttribute("data-index", chapter.index);
ch.setAttribute("title", chapter.title);
ch.innerHTML = chapter.title.length > 15 ? chapter.title.substring(0, 14) + "..." : chapter.title;
$("#chapter").appendChild(ch);
});
sum_chapter = data.data.length;
$('#chapter').scrollTop = 0;
$("#content").innerHTML = "章节列表加载完成!";
});
@ -126,15 +136,47 @@ $('#showchapter').addEventListener("click", () => {
window.location.hash = "#chapter";
});
$('#up').addEventListener('click', e => {
if (now_chapter > 0) {
now_chapter--;
let clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent("click", true, false);
$('[data-index="' + now_chapter + '"]').dispatchEvent(clickEvent);
} else if (now_chapter == 0) {
alert("已经是第一章了^_^!")
} else {
}
});
$('#down').addEventListener('click', e => {
if (now_chapter > -1) {
if (now_chapter < sum_chapter - 1) {
now_chapter++;
let clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent("click", true, false);
$('[data-index="' + now_chapter + '"]').dispatchEvent(clickEvent);
} else {
alert("已经是最后一章了^_^!")
}
}
});
$('#chapter').addEventListener("click", (e) => {
if (e.target.tagName === "BUTTON") {
var url = e.target.getAttribute("data-url");
var index = e.target.getAttribute("data-index");
var name = e.target.getAttribute("title");
if (!url) {
alert("未取得章节地址");
alert("未取得书籍地址");
}
if (!index && (0 != index)) {
alert("未取得章节索引");
}
now_chapter = parseInt(index);
$("#content").innerHTML = "<p>" + name + " 加载中...</p>";
fetch(apiAddress("getBookContent", url), { mode: "cors" })
fetch(apiAddress("getBookContent", url, index), { mode: "cors" })
.then(res => res.json())
.then(data => {
if (!data.isSuccess) {

@ -3,305 +3,371 @@
<head>
<meta charset="UTF-8">
<title>书源编辑器v3.8</title>
<link rel="stylesheet" type="text/css" href="/index.css"/>
<title>阅读3.0书源编辑器_V4.0</title>
<link rel="icon" href="favicon.ico">
<link rel="stylesheet" type="text/css" href="index.css" />
</head>
<body>
<div class="editor">
<div class="setbox">
<div class="rules">
<div><b>书源基础信息</b></div>
<div>
<div>书源名称:</div>
<textarea rows="1" id="bookSourceName" placeholder="书源名称(bookSourceName) | 会显示在书源列表"></textarea>
</div>
<div>
<div>书源分组:</div>
<textarea rows="1" id="bookSourceGroup" placeholder="书源分组(bookSourceGroup) | 描述书源的特征信息"></textarea>
</div>
<div>
<div>书源域名:</div>
<textarea rows="1" id="bookSourceUrl"
placeholder="书源URL(bookSourceUrl) | 通常填写网站主页(标头不可省略),例: https://www.qidian.com"></textarea>
</div>
<div>
<div>登录网页:</div>
<textarea rows="1" id="loginUrl" placeholder="登录URL(loginUrl) | 填写网站登录网址,仅在需要登录的书源有用"></textarea>
</div>
<div><b>书籍发现规则</b></div>
<div>
<div>发现菜单:</div>
<textarea rows="5" id="ruleFindUrl"
placeholder="发现分类菜单规则(ruleFindUrl),将显示在发现菜单&#10;每行一条发现分类(网址域名可省略):&#10;名称1::网址(Url)1&#10;名称2::网址(Url)2&#10;..."></textarea>
</div>
<div>
<div>结果列表:</div>
<textarea rows="1" id="ruleFindList"
placeholder="发现页列表规则(ruleFindList) | 选择书籍节点 (规则结果为List&lt;Element&gt;)"></textarea>
</div>
<div>
<div>书籍名称:</div>
<textarea rows="1" id="ruleFindName"
placeholder="发现页书名规则(ruleFindName) | 选择节点书名 (规则结果为String)"></textarea>
</div>
<div>
<div>书籍作者:</div>
<textarea rows="1" id="ruleFindAuthor"
placeholder="发现页作者规则(ruleFindAuthor) | 选择节点作者 (规则结果为String)"></textarea>
</div>
<div>
<div>书籍分类:</div>
<textarea rows="1" id="ruleFindKind"
placeholder="发现页分类规则(ruleFindKind) | 选择节点分类信息 (规则结果为List&lt;String&gt;)"></textarea>
</div>
<div>
<div>最新章节:</div>
<textarea rows="1" id="ruleFindLastChapter"
placeholder="发现页最新章节规则(ruleFindLastChapter) | 选择节点最新章节 (规则结果为String)"></textarea>
</div>
<div>
<div>简介内容:</div>
<textarea rows="1" id="ruleFindIntroduce"
placeholder="发现页简介规则(ruleFindIntroduce) | 选择节点书籍简介 (规则结果为String)"></textarea>
</div>
<div>
<div>封面链接:</div>
<textarea rows="1" id="ruleFindCoverUrl"
placeholder="发现页封面规则(ruleFindCoverUrl) | 选择节点书籍封面 (规则结果为Url)"></textarea>
</div>
<div>
<div>详情链接:</div>
<textarea rows="1" id="ruleFindNoteUrl"
placeholder="发现页详情规则(ruleFindNoteUrl) | 选择书籍详情页网址 (规则结果为Url)"></textarea>
</div>
<div><b>书籍搜索规则</b></div>
<div>
<div>搜索网址:</div>
<textarea rows="1" id="ruleSearchUrl"
placeholder="搜索网址(ruleSearchUrl) | [域名可省略]/search.php@kw=searchKey|char=utf-8"></textarea>
</div>
<div>
<div>结果验证:</div>
<textarea rows="1" id="ruleBookUrlPattern"
placeholder="搜索页URL验证(ruleBookUrlPattern) | 正则验证URL是否为详情页,成功则跳过搜索页解析"></textarea>
</div>
<div>
<div>结果列表:</div>
<textarea rows="1" id="ruleSearchList"
placeholder="搜索页列表规则(ruleSearchList) | 选择书籍节点 (规则结果为List&lt;Element&gt;)"></textarea>
</div>
<div>
<div>书籍名称:</div>
<textarea rows="1" id="ruleSearchName"
placeholder="搜索页书名规则(ruleSearchName) | 选择节点书名 (规则结果为String)"></textarea>
</div>
<div>
<div>书籍作者:</div>
<textarea rows="1" id="ruleSearchAuthor"
placeholder="搜索页作者规则(ruleSearchAuthor) | 选择节点作者 (规则结果为String)"></textarea>
</div>
<div>
<div>书籍分类:</div>
<textarea rows="1" id="ruleSearchKind"
placeholder="搜索页分类规则(ruleSearchKind) | 选择节点分类信息 (规则结果为List&lt;String&gt;)"></textarea>
</div>
<div>
<div>最新章节:</div>
<textarea rows="1" id="ruleSearchLastChapter"
placeholder="搜索页最新章节规则(ruleSearchLastChapter) | 选择节点最新章节 (规则结果为String)"></textarea>
</div>
<div>
<div>简介内容:</div>
<textarea rows="1" id="ruleSearchIntroduce"
placeholder="搜索页简介规则(ruleSearchIntroduce) | 选择节点书籍简介 (规则结果为String)"></textarea>
</div>
<div>
<div>封面链接:</div>
<textarea rows="1" id="ruleSearchCoverUrl"
placeholder="搜索页封面规则(ruleSearchCoverUrl) | 选择节点书籍封面 (规则结果为Url)"></textarea>
</div>
<div>
<div>详情链接:</div>
<textarea rows="1" id="ruleSearchNoteUrl"
placeholder="搜索页详情规则(ruleSearchNoteUrl) | 选择书籍详情页网址 (规则结果为Url)"></textarea>
</div>
<div><b>书籍详情规则</b></div>
<div>
<div>页面处理:</div>
<textarea rows="1" id="ruleBookInfoInit"
placeholder="详情页信息预处理(ruleBookInfoInit) | 用于加速详情信息检索"></textarea>
</div>
<div>
<div>书籍名称:</div>
<textarea rows="1" id="ruleBookName"
placeholder="书名规则(ruleBookName) | 选择详情页书名 (规则结果为String)"></textarea>
</div>
<div>
<div>书籍作者:</div>
<textarea rows="1" id="ruleBookAuthor"
placeholder="作者规则(ruleBookAuthor) | 选择详情页作者 (规则结果为String)"></textarea>
</div>
<div>
<div>书籍分类:</div>
<textarea rows="1" id="ruleBookKind"
placeholder="分类规则(ruleBookKind) | 选择详情页分类信息 (规则结果为List&lt;String&gt;)"></textarea>
</div>
<div>
<div>最新章节:</div>
<textarea rows="1" id="ruleBookLastChapter"
placeholder="最新章节规则(ruleBookLastChapter) | 选择详情页最新章节 (规则结果为String)"></textarea>
</div>
<div>
<div>简介内容:</div>
<textarea rows="1" id="ruleIntroduce"
placeholder="简介规则(ruleIntroduce) | 选择详情页书籍简介 (规则结果为String)"></textarea>
</div>
<div>
<div>封面链接:</div>
<textarea rows="1" id="ruleCoverUrl" placeholder="封面规则(ruleCoverUrl) | 选择详情页书籍封面 (规则结果为Url)"></textarea>
</div>
<div>
<div>目录链接:</div>
<textarea rows="1" id="ruleChapterUrl"
placeholder="目录URL规则(ruleChapterUrl) | 选择目录页网址 (规则结果为Url, 与详情页相同时可省略)"></textarea>
</div>
<div><b>目录列表规则</b></div>
<div>
<div>目录翻页:</div>
<textarea rows="1" id="ruleChapterUrlNext"
placeholder="目录下一页规则(ruleChapterUrlNext) | 选择目录下一页链接 (规则结果为List&lt;Url&gt;)"></textarea>
</div>
<div>
<div>目录列表:</div>
<textarea rows="1" id="ruleChapterList"
placeholder="目录列表规则(ruleChapterList) | 选择目录列表的章节节点 (规则结果为List&lt;Element&gt;)"></textarea>
</div>
<div>
<div>章节名称:</div>
<textarea rows="1" id="ruleChapterName"
placeholder="章节名称规则(ruleChapterName) | 选择章节名称 (规则结果为String)"></textarea>
</div>
<div>
<div>章节链接:</div>
<textarea rows="1" id="ruleContentUrl"
placeholder="章节URL规则(ruleContentUrl) | 选择章节链接 (规则结果为Url)"></textarea>
</div>
<div><b>正文阅读规则</b></div>
<div>
<div>章节正文:</div>
<textarea rows="1" id="ruleBookContent"
placeholder="正文规则(ruleBookContent) | 选择正文内容 (规则结果为String)"></textarea>
</div>
<div>
<div>正文翻页:</div>
<textarea rows="1" id="ruleContentUrlNext"
placeholder="正文翻页URL规则(ruleContentUrlNext) | 选择下一分页(不是下一章)链接 (规则结果为Url)"></textarea>
</div>
<div><b>其它规则</b></div>
<div>
<div>浏览标识:</div>
<textarea rows="1" id="httpUserAgent"
placeholder="浏览器UA(HttpUserAgent) | 浏览器标识:User-Agent (可选)"></textarea>
</div>
<div>
<div>排序编号:</div>
<textarea rows="1" id="serialNumber" placeholder="整数: 0~N (可选,默认0) | 数字越小越靠前"></textarea>
</div>
<div>
<div>搜索权重:</div>
<textarea rows="1" id="weight" placeholder="整数: 0~N (可选,默认0) | 数字越大越靠前"></textarea>
</div>
<div>
<div>是否启用:</div>
<textarea rows="1" id="enable" placeholder="默认启用=true,手动启用=false (可选,默认true)"></textarea>
<div class="editor">
<div class="setbox">
<div class="rules">
<div><b>基本</b></div>
<div>
<div>源URL :</div>
<textarea rows="1" id="bookSourceUrl" class="base" title="bookSourceUrl"
placeholder="<必填>通常填写网站主页,例: https://www.qidian.com"></textarea>
</div>
<div>
<div>源类型 :</div>
<textarea rows="1" id="bookSourceType" class="base" title="bookSourceType"
placeholder="&lt;必填&gt;0:文本 1:音频"></textarea>
</div>
<div>
<div>源名称 :</div>
<textarea rows="1" id="bookSourceName" class="base" title="bookSourceName"
placeholder="&lt;必填&gt;会显示在书源列表"></textarea>
</div>
<div>
<div>源分组 :</div>
<textarea rows="1" id="bookSourceGroup" class="base" title="bookSourceGroup"
placeholder="&lt;选填&gt;描述书源的特征信息"></textarea>
</div>
<div>
<div>登录地址:</div>
<textarea rows="1" id="loginUrl" class="base" title="loginUrl"
placeholder="&lt;选填&gt;填写网站登录网址,仅在需要登录的书源有用"></textarea>
</div>
<div>
<div>链接验证:</div>
<textarea rows="1" id="bookUrlPattern" class="base" title="bookUrlPattern"
placeholder="&lt;选填&gt;当详情页URL与源URL的域名不一致时有效,用于添加网址"></textarea>
</div>
<div>
<div>请求头 :</div>
<textarea rows="3" id="header" class="base" title="header" placeholder="&lt;选填&gt;客户端标识"></textarea>
</div>
<p></p>
<div><b>搜索</b></div>
<div>
<div>搜索地址:</div>
<textarea rows="1" id="searchUrl" class="base" title="searchUrl"
placeholder="[域名可省略]/search.php@kw={{key}}"></textarea>
</div>
<div>
<div>列表规则:</div>
<textarea rows="3" id="ruleSearch_bookList" class="ruleSearch" title="bookList"
placeholder="选择书籍节点 (规则结果为List&lt;Element&gt;)"></textarea>
</div>
<div>
<div>书名规则:</div>
<textarea rows="1" id="ruleSearch_name" class="ruleSearch" title="name"
placeholder="选择节点书名 (规则结果为String)"></textarea>
</div>
<div>
<div>作者规则:</div>
<textarea rows="1" id="ruleSearch_author" class="ruleSearch" title="author"
placeholder="选择节点作者 (规则结果为String)"></textarea>
</div>
<div>
<div>分类规则:</div>
<textarea rows="1" id="ruleSearch_kind" class="ruleSearch" title="kind"
placeholder="选择节点分类信息 (规则结果为String)"></textarea>
</div>
<div>
<div>字数规则:</div>
<textarea rows="1" id="ruleSearch_wordCount" class="ruleSearch" title="wordCount"
placeholder="选择节点字数信息 (规则结果为String)"></textarea>
</div>
<div>
<div>最新章节:</div>
<textarea rows="1" id="ruleSearch_lastChapter" class="ruleSearch" title="lastChapter"
placeholder="选择节点最新章节 (规则结果为String)"></textarea>
</div>
<div>
<div>简介规则:</div>
<textarea rows="1" id="ruleSearch_intro" class="ruleSearch" title="intro"
placeholder="选择节点书籍简介 (规则结果为String)"></textarea>
</div>
<div>
<div>封面规则:</div>
<textarea rows="1" id="ruleSearch_coverUrl" class="ruleSearch" title="coverUrl"
placeholder="选择节点书籍封面 (规则结果为String类型的url)"></textarea>
</div>
<div>
<div>详情地址:</div>
<textarea rows="1" id="ruleSearch_bookUrl" class="ruleSearch" title="bookUrl"
placeholder="选择书籍详情页网址 (规则结果为String类型的url)"></textarea>
</div>
<p></p>
<div><b>发现</b></div>
<div>
<div>发现地址:</div>
<textarea rows="6" id="exploreUrl" class="base" title="exploreUrl"
placeholder="内容能显示在发现菜单&#10;每行一条发现分类(网址域名可省略),例:&#10;名称1::网址(Url)1&#10;名称2::网址(Url)2&#10;..."></textarea>
</div>
<div>
<div>列表规则:</div>
<textarea rows="1" id="ruleExplore_bookList" class="ruleExplore" title="bookList"
placeholder="选择书籍节点 (规则结果为List&lt;Element&gt;)"></textarea>
</div>
<div>
<div>书名规则:</div>
<textarea rows="1" id="ruleExplore_name" class="ruleExplore" title="name"
placeholder="选择节点书名 (规则结果为String)"></textarea>
</div>
<div>
<div>作者规则:</div>
<textarea rows="1" id="ruleExplore_author" class="ruleExplore" title="author"
placeholder="选择节点作者 (规则结果为String)"></textarea>
</div>
<div>
<div>分类规则:</div>
<textarea rows="1" id="ruleExplore_kind" class="ruleExplore" title="kind"
placeholder="选择节点分类信息 (规则结果为String)"></textarea>
</div>
<div>
<div>字数规则:</div>
<textarea rows="1" id="ruleExplore_wordCount" class="ruleExplore" title="wordCount"
placeholder="选择节点字数信息 (规则结果为String)"></textarea>
</div>
<div>
<div>最新章节:</div>
<textarea rows="1" id="ruleExplore_lastChapter" class="ruleExplore" title="lastChapter"
placeholder="选择节点最新章节 (规则结果为String)"></textarea>
</div>
<div>
<div>简介规则:</div>
<textarea rows="1" id="ruleExplore_intro" class="ruleExplore" title="intro"
placeholder="选择节点书籍简介 (规则结果为String)"></textarea>
</div>
<div>
<div>封面规则:</div>
<textarea rows="1" id="ruleExplore_coverUrl" class="ruleExplore" title="coverUrl"
placeholder="选择节点书籍封面 (规则结果为String类型的url)"></textarea>
</div>
<div>
<div>详情地址:</div>
<textarea rows="1" id="ruleExplore_bookUrl" class="ruleExplore" title="bookUrl"
placeholder="选择书籍详情页网址 (规则结果为String类型的url)"></textarea>
</div>
<p></p>
<div><b>详情</b></div>
<div>
<div>预处理 :</div>
<textarea rows="3" id="ruleBookInfo_init" class="ruleBookInfo" title="init"
placeholder="用于加速详情信息检索,只支持AllInOne规则"></textarea>
</div>
<div>
<div>书名规则:</div>
<textarea rows="1" id="ruleBookInfo_name" class="ruleBookInfo" title="name"
placeholder="选择节点书名 (规则结果为String)"></textarea>
</div>
<div>
<div>作者规则:</div>
<textarea rows="1" id="ruleBookInfo_author" class="ruleBookInfo" title="author"
placeholder="选择节点作者 (规则结果为String)"></textarea>
</div>
<div>
<div>分类规则:</div>
<textarea rows="1" id="ruleBookInfo_kind" class="ruleBookInfo" title="kind"
placeholder="选择节点分类信息 (规则结果为String)"></textarea>
</div>
<div>
<div>字数规则:</div>
<textarea rows="1" id="ruleBookInfo_wordCount" class="ruleBookInfo" title="wordCount"
placeholder="选择节点字数信息 (规则结果为String)"></textarea>
</div>
<div>
<div>最新章节:</div>
<textarea rows="1" id="ruleBookInfo_lastChapter" class="ruleBookInfo" title="lastChapter"
placeholder="选择节点最新章节 (规则结果为String)"></textarea>
</div>
<div>
<div>简介规则:</div>
<textarea rows="1" id="ruleBookInfo_intro" class="ruleBookInfo" title="intro"
placeholder="选择节点书籍简介 (规则结果为String)"></textarea>
</div>
<div>
<div>封面规则:</div>
<textarea rows="1" id="ruleBookInfo_coverUrl" class="ruleBookInfo" title="coverUrl"
placeholder="选择节点书籍封面 (规则结果为String类型的url)"></textarea>
</div>
<div>
<div>目录地址:</div>
<textarea rows="1" id="ruleBookInfo_tocUrl" class="ruleBookInfo" title="tocUrl"
placeholder="选择书籍详情页网址 (规则结果为String类型的url, 与详情页相同时可省略)"></textarea>
</div>
<p></p>
<div><b>目录</b></div>
<div>
<div>列表规则:</div>
<textarea rows="3" id="ruleToc_chapterList" class="ruleToc" title="chapterList"
placeholder="选择目录列表的章节节点 (规则结果为List&lt;Element&gt;)"></textarea>
</div>
<div>
<div>章节名称:</div>
<textarea rows="1" id="ruleToc_chapterName" class="ruleToc" title="chapterName"
placeholder="选择章节名称 (规则结果为String)"></textarea>
</div>
<div>
<div>章节地址:</div>
<textarea rows="1" id="ruleToc_chapterUrl" class="ruleToc" title="chapterUrl"
placeholder="选择章节链接 (规则结果为String类型的Url)"></textarea>
</div>
<div>
<div>收费标识:</div>
<textarea rows="1" id="ruleToc_isVip" class="ruleToc" title="isVip"
placeholder="章节是否为VIP章节 (规则结果为Bool)"></textarea>
</div>
<div>
<div>章节信息:</div>
<textarea rows="1" id="ruleToc_updateTime" class="ruleToc" title="updateTime"
placeholder="选择章节信息 (规则结果为String)"></textarea>
</div>
<div>
<div>翻页规则:</div>
<textarea rows="1" id="ruleToc_nextTocUrl" class="ruleToc" title="nextTocUrl"
placeholder="选择目录下一页链接 (规则结果为List&lt;String&gt;或String)"></textarea>
</div>
<p></p>
<div><b>正文</b></div>
<div>
<div>正文规则:</div>
<textarea rows="1" id="ruleContent_content" class="ruleContent" title="content"
placeholder="选择正文内容 (规则结果为String)"></textarea>
</div>
<div>
<div>翻页规则:</div>
<textarea rows="1" id="ruleContent_nextContentUrl" class="ruleContent" title="nextContentUrl"
placeholder="选择下一分页(不是下一章)链接 (规则结果为String类型的Url)"></textarea>
</div>
<div>
<div>脚本注入:</div>
<textarea rows="3" id="ruleContent_webJs" class="ruleContent" title="webJs"
placeholder="注入javascript,用于模拟鼠标点击等,无返回结果"></textarea>
</div>
<div>
<div>资源正则:</div>
<textarea rows="1" id="ruleContent_sourceRegex" class="ruleContent" title="sourceRegex"
placeholder="匹配资源的url特征,用于嗅探"></textarea>
</div>
<p></p>
<div><b>其它规则</b></div>
<div>
<div>启用搜索:</div>
<textarea rows="1" id="enabled" class="base" title="enabled"
placeholder="启用: true 关闭: false (可选,默认true)"></textarea>
</div>
<div>
<div>启用发现:</div>
<textarea rows="1" id="enabledExplore" class="base" title="enabledExplore"
placeholder="启用: true 关闭: false (可选,默认true)"></textarea>
</div>
<div>
<div>搜索权重:</div>
<textarea rows="1" id="weight" class="base" title="weight"
placeholder="整数: 0~N (可选,默认0) | 数字越大越靠前"></textarea>
</div>
<div>
<div>排序编号:</div>
<textarea rows="1" id="customOrder" class="base" title="customOrder"
placeholder="整数: 0~N (可选,默认0) | 数字越小越靠前"></textarea>
</div>
<div>
<div>更新时间:</div>
<textarea rows="1" id="lastUpdateTime" class="base" title="lastUpdateTime"
placeholder="整数: 0~N (可选,默认0) | 暂未使用"></textarea>
</div>
</div>
</div>
</div>
<div class="menu">
<svg class="button">
<text x="50%" y="55%">⇈推送书源</text>
<rect id="push"></rect>
</svg>
<svg class="button">
<text x="50%" y="55%">⇊拉取书源</text>
<rect id="pull"></rect>
</svg>
<svg class="button">
<text x="50%" y="55%">⋘编辑书源</text>
<rect id="editor"></rect>
</svg>
<svg class="button">
<text x="50%" y="55%">⋙生成书源</text>
<rect id="conver"></rect>
</svg>
<svg class="button">
<text x="50%" y="55%">✗清空表单</text>
<rect id="initial"></rect>
</svg>
<svg class="button">
<text x="50%" y="55%">↶撤销操作</text>
<rect id="undo"></rect>
</svg>
<svg class="button">
<text x="50%" y="55%">↷重做操作</text>
<rect id="redo"></rect>
</svg>
<svg class="button">
<text x="50%" y="55%">⇏调试书源</text>
<rect id="debug"></rect>
</svg>
<svg class="button">
<text x="50%" y="55%">✓保存书源</text>
<rect id="accept"></rect>
</svg>
</div>
<div class="outbox">
<div class="tabbox">
<div class="tabtitle">
<div name="编辑书源" class="tab1 this">编辑书源</div>
<div name="调试书源" class="tab2">调试书源</div>
<div name="书源列表" class="tab3">书源列表</div>
<div name="帮助信息" class="tab4">帮助信息</div>
</div>
<div class="tabbody">
<div class="tab1 this">
<textarea class="context" id="RuleJsonString" placeholder="这里输出序列化的JSON数据,可直接导入'阅读'APP"></textarea>
</div>
<div class="tab2">
<input type="text" class="inputbox" id="DebugKey" placeholder="我的">
<textarea class="context" id="DebugConsole" placeholder="这里用于输出调试信息"></textarea>
</div>
<div class="tab3">
<div class="titlebar">
<button id="Import">导入书源文件</button>
<button id="Export">导出书源文件</button>
<button id="Delete">删除选中书源</button>
<button id="ClrAll">清空当前列表</button>
<div class="menu">
<svg class="button">
<text x="50%" y="55%">⇈推送书源</text>
<rect id="push"></rect>
</svg>
<svg class="button">
<text x="50%" y="55%">⇊拉取书源</text>
<rect id="pull"></rect>
</svg>
<svg class="button">
<text x="50%" y="55%">⋘编辑书源</text>
<rect id="editor"></rect>
</svg>
<svg class="button">
<text x="50%" y="55%">⋙生成书源</text>
<rect id="conver"></rect>
</svg>
<svg class="button">
<text x="50%" y="55%">✗清空表单</text>
<rect id="initial"></rect>
</svg>
<svg class="button">
<text x="50%" y="55%">↶撤销操作</text>
<rect id="undo"></rect>
</svg>
<svg class="button">
<text x="50%" y="55%">↷重做操作</text>
<rect id="redo"></rect>
</svg>
<svg class="button">
<text x="50%" y="55%">⇏调试书源</text>
<rect id="debug"></rect>
</svg>
<svg class="button">
<text x="50%" y="55%">✓保存书源</text>
<rect id="accept"></rect>
</svg>
</div>
<div class="outbox">
<div class="tabbox">
<div class="tabtitle">
<div name="编辑书源" class="tab1 this">编辑书源</div>
<div name="调试书源" class="tab2">调试书源</div>
<div name="书源列表" class="tab3">书源列表</div>
<div name="帮助信息" class="tab4">帮助信息</div>
</div>
<div class="tabbody">
<div class="tab1 this">
<textarea class="context" id="RuleJsonString"
placeholder="这里输出序列化的JSON数据,可直接导入'阅读'APP"></textarea>
</div>
<div class="tab2">
<input type="text" class="inputbox" id="DebugKey" placeholder="输入搜索关键字,默认搜「我的」">
<textarea class="context" id="DebugConsole" placeholder="这里用于输出调试信息"></textarea>
</div>
<div class="tab3">
<input type="text" class="inputbox" id="Filter" placeholder="输入筛选关键词(源名称、源URL或源分组)后按回车筛选源">
<div class="titlebar">
<button id="Import">导入书源文件</button>
<button id="Export">导出书源文件</button>
<button id="Delete">删除选中书源</button>
<button id="ClrAll">清空当前列表</button>
</div>
<div class="context" id="RuleList"></div>
</div>
<div class="context" id="RuleList"></div>
</div>
<div class="tab4">
<div class="context link">
<a target="_blank" href="https://gedoor.github.io/MyBookshelf/sourcerule.html">官方书源教程</a>
<a target="_blank" href="https://zhuanlan.zhihu.com/p/29436838">Xpath基础教程</a>
<a target="_blank" href="https://zhuanlan.zhihu.com/p/32187820">Xpath高级教程</a>
<a target="_blank" href="https://www.w3cschool.cn/regex_rmjc/?">正则表达式教程</a>
<a target="_blank" href="https://regexr.com/">正则表达式在线验证工具</a>
<div>^$()[]{}.?+*| 这些是Java正则特殊符号,匹配需转义
<br>(?s) 前缀表示跨行解析
<br>(?m) 前缀表示逐行匹配
<br>(?i) 前缀表示忽略大小写
<div class="tab4">
<div class="context link">
<a target="_blank" href="https://celeter.github.io">源制作教程</a>
<a target="_blank" href="https://zhuanlan.zhihu.com/p/29436838">Xpath基础教程</a>
<a target="_blank" href="https://zhuanlan.zhihu.com/p/32187820">Xpath高级教程</a>
<a target="_blank" href="https://www.w3cschool.cn/regex_rmjc/?">正则表达式教程</a>
<a target="_blank" href="https://regexr.com/">正则表达式在线验证工具</a>
<div>^$()[]{}.?+*| 这些是Java正则特殊符号,匹配需转义
<br>(?s) 前缀表示跨行解析
<br>(?m) 前缀表示逐行匹配
<br>(?i) 前缀表示忽略大小写
</div>
<a target="_blank" href="https://www.beta.browxy.com/">代码在线运行工具</a>
<a target="_blank" href="bookshelf.html">阅读书架(经典)</a>
<a target="_blank" href="new/bookshelf.html">阅读书架(新潮)</a>
</div>
<a target="_blank" href="https://www.beta.browxy.com/">代码在线运行工具</a>
<a target="_blank" href="/bookshelf.html">阅读书架(测试)</a>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="/index.js"></script>
<script type="text/javascript" src="index.js"></script>
</body>
</html>

@ -20,11 +20,45 @@ function hashParam(key, val) {
// 创建书源规则容器对象
const RuleJSON = (() => {
let ruleJson = {};
$$('.rules textarea').forEach(item => ruleJson[item.id] = '');
// for (let item of $$('.rules textarea')) ruleJson[item.id] = '';
ruleJson.serialNumber = 0;
let searchJson = {};
let exploreJson = {};
let bookInfoJson = {};
let tocJson = {};
let contentJson = {};
// 基本以及其他
$$('.rules .base').forEach(item => ruleJson[item.title] = '');
ruleJson.lastUpdateTime = 0;
ruleJson.customOrder = 0;
ruleJson.weight = 0;
ruleJson.enable = true;
ruleJson.enabled = true;
ruleJson.enabledExplore = true;
// 搜索规则
$$('.rules .ruleSearch').forEach(item => searchJson[item.title] = '');
//ruleJson.ruleSearch = JSON.stringify(searchJson);
ruleJson.ruleSearch = searchJson;
// 发现规则
$$('.rules .ruleExplore').forEach(item => exploreJson[item.title] = '');
//ruleJson.ruleExplore = JSON.stringify(exploreJson);
ruleJson.ruleExplore = exploreJson;
// 详情页规则
$$('.rules .ruleBookInfo').forEach(item => bookInfoJson[item.title] = '');
//ruleJson.ruleBookInfo = JSON.stringify(bookInfoJson);
ruleJson.ruleBookInfo = bookInfoJson;
// 目录规则
$$('.rules .ruleToc').forEach(item => tocJson[item.title] = '');
//ruleJson.ruleToc = JSON.stringify(tocJson);
ruleJson.ruleToc = tocJson;
// 正文规则
$$('.rules .ruleContent').forEach(item => contentJson[item.title] = '');
//ruleJson.ruleContent = JSON.stringify(contentJson);
ruleJson.ruleContent = contentJson;
return ruleJson;
})();
// 选项卡Tab切换事件处理
@ -72,15 +106,133 @@ function HttpPost(url, data) {
}
// 将书源表单转化为书源对象
function rule2json() {
Object.keys(RuleJSON).forEach((key) => RuleJSON[key] = $('#' + key).value);
RuleJSON.serialNumber = RuleJSON.serialNumber == '' ? 0 : parseInt(RuleJSON.serialNumber);
// 转换base
Object.keys(RuleJSON).forEach(key => {
if (!key.startsWith("rule")) {
RuleJSON[key] = $('#' + key).value;
}
});
// 转换搜索规则
let searchJson = {};
//Object.keys(JSON.parse(RuleJSON.ruleSearch)).forEach(key => {
Object.keys(RuleJSON.ruleSearch).forEach(key => {
searchJson[key] = $('#' + 'ruleSearch_' + key).value;
});
//RuleJSON.ruleSearch = JSON.stringify(searchJson);
RuleJSON.ruleSearch = searchJson;
// 转换发现规则
let exploreJson = {};
//Object.keys(JSON.parse(RuleJSON.ruleExplore)).forEach(key => {
Object.keys(RuleJSON.ruleExplore).forEach(key => {
exploreJson[key] = $('#' + 'ruleExplore_' + key).value;
});
//RuleJSON.ruleExplore = JSON.stringify(exploreJson);
RuleJSON.ruleExplore = exploreJson;
// 转换详情页规则
let bookInfoJson = {};
//Object.keys(JSON.parse(RuleJSON.ruleBookInfo)).forEach(key => {
Object.keys(RuleJSON.ruleBookInfo).forEach(key => {
bookInfoJson[key] = $('#' + 'ruleBookInfo_' + key).value;
});
//RuleJSON.ruleBookInfo = JSON.stringify(bookInfoJson);
RuleJSON.ruleBookInfo = bookInfoJson;
// 转换目录规则
let tocJson = {};
//Object.keys(JSON.parse(RuleJSON.ruleToc)).forEach(key => {
Object.keys(RuleJSON.ruleToc).forEach(key => {
tocJson[key] = $('#' + 'ruleToc_' + key).value;
});
//RuleJSON.ruleToc = JSON.stringify(tocJson);
RuleJSON.ruleToc = tocJson;
// 转换正文规则
let contentJson = {};
//Object.keys(JSON.parse(RuleJSON.ruleContent)).forEach(key => {
Object.keys(RuleJSON.ruleContent).forEach(key => {
contentJson[key] = $('#' + 'ruleContent_' + key).value;
});
//RuleJSON.ruleContent = JSON.stringify(contentJson);
RuleJSON.ruleContent = contentJson;
RuleJSON.lastUpdateTime = RuleJSON.lastUpdateTime == '' ? 0 : parseInt(RuleJSON.lastUpdateTime);
RuleJSON.customOrder = RuleJSON.customOrder == '' ? 0 : parseInt(RuleJSON.customOrder);
RuleJSON.weight = RuleJSON.weight == '' ? 0 : parseInt(RuleJSON.weight);
RuleJSON.enable = RuleJSON.enable == '' || RuleJSON.enable.toLocaleLowerCase().replace(/^\s*|\s*$/g, '') == 'true';
RuleJSON.bookSourceType == RuleJSON.bookSourceType == '' ? 0 : parseInt(RuleJSON.weight);
RuleJSON.enabled = RuleJSON.enabled == '' || String(RuleJSON.enabled).toLocaleLowerCase().replace(/^\s*|\s*$/g, '') == 'true';
RuleJSON.enabledExplore = RuleJSON.enabledExplore == '' || String(RuleJSON.enabledExplore).toLocaleLowerCase().replace(/^\s*|\s*$/g, '') == 'true';
return RuleJSON;
}
// 将书源对象填充到书源表单
function json2rule(RuleEditor) {
Object.keys(RuleJSON).forEach((key) => $("#" + key).value = RuleEditor[key] ? RuleEditor[key] : '');
// 转换base
Object.keys(RuleJSON).forEach(key => {
if (!key.startsWith("rule")) {
let val = RuleEditor[key];
if (typeof val == "number") {
$("#" + key).value = val ? String(val) : '0';
}
else if (typeof val == "boolean") {
$("#" + key).value = val ? String(val) : 'false';
}
else {
$("#" + key).value = val ? String(val) : '';
}
}
});
// 转换搜索规则
if (RuleEditor.ruleSearch) {
//let searchJson = JSON.parse(RuleEditor.ruleSearch);
let searchJson = RuleEditor.ruleSearch;
//Object.keys(JSON.parse(RuleJSON.ruleSearch)).forEach(key => {
Object.keys(RuleJSON.ruleSearch).forEach(key => {
$('#' + 'ruleSearch_' + key).value = searchJson[key] ? searchJson[key] : '';
});
}
// 转换发现规则
if (RuleEditor.ruleExplore) {
//let exploreJson = JSON.parse(RuleEditor.ruleExplore);
//Object.keys(JSON.parse(RuleJSON.ruleExplore)).forEach(key => {
let exploreJson = RuleEditor.ruleExplore;
Object.keys(RuleJSON.ruleExplore).forEach(key => {
$('#' + 'ruleExplore_' + key).value = exploreJson[key] ? exploreJson[key] : '';
});
}
// 转换详情页规则
if (RuleEditor.ruleBookInfo) {
//let bookInfoJson = JSON.parse(RuleEditor.ruleBookInfo);
//Object.keys(JSON.parse(RuleJSON.ruleBookInfo)).forEach(key => {
let bookInfoJson = RuleEditor.ruleBookInfo;
Object.keys(RuleJSON.ruleBookInfo).forEach(key => {
$('#' + 'ruleBookInfo_' + key).value = bookInfoJson[key] ? bookInfoJson[key] : '';
});
}
// 转换目录规则
if (RuleEditor.ruleToc) {
//let tocJson = JSON.parse(RuleEditor.ruleToc);
//Object.keys(JSON.parse(RuleJSON.ruleToc)).forEach(key => {
let tocJson = RuleEditor.ruleToc;
Object.keys(RuleJSON.ruleToc).forEach(key => {
$('#' + 'ruleToc_' + key).value = tocJson[key] ? tocJson[key] : '';
});
}
// 转换正文规则
if (RuleEditor.ruleContent) {
//let contentJson = JSON.parse(RuleEditor.ruleContent);
//Object.keys(JSON.parse(RuleJSON.ruleContent)).forEach(key => {
let contentJson = RuleEditor.ruleContent;
Object.keys(RuleJSON.ruleContent).forEach(key => {
$('#' + 'ruleContent_' + key).value = contentJson[key] ? contentJson[key] : '';
});
}
}
// 记录操作过程
var course = { "old": [], "now": {}, "new": [] };
@ -153,16 +305,16 @@ $('.menu').addEventListener('click', e => {
});
failMsg = '\n推送失败的书源将用红色字体标注!';
}
alert(`批量推送书源到「阅读APP」\n共计: ${RuleSources.length}\n成功: ${okData.length}\n失败: ${RuleSources.length - okData.length}${failMsg}`);
alert(`批量推送书源到「阅读3.0APP」\n共计: ${RuleSources.length}\n成功: ${okData.length}\n失败: ${RuleSources.length - okData.length}${failMsg}`);
}
else {
alert(`批量推送书源到「阅读APP」成功!\n共计: ${RuleSources.length}`);
alert(`批量推送书源到「阅读3.0APP」成功!\n共计: ${RuleSources.length}`);
}
}
else {
alert(`批量推送书源失败!\nErrorMsg: ${json.errorMsg}`);
}
}).catch(err => { alert(`批量推送书源失败,无法连接到「阅读APP」!\n${err}`); });
}).catch(err => { alert(`批量推送书源失败,无法连接到「阅读3.0APP」!\n${err}`); });
thisNode.setAttribute('class', '');
})();
return;
@ -181,7 +333,7 @@ $('.menu').addEventListener('click', e => {
else {
alert(`批量拉取书源失败!\nErrorMsg: ${json.errorMsg}`);
}
}).catch(err => { alert(`批量拉取书源失败,无法连接到「阅读APP」!\n${err}`); });
}).catch(err => { alert(`批量拉取书源失败,无法连接到「阅读3.0APP」!\n${err}`); });
thisNode.setAttribute('class', '');
})();
return;
@ -244,9 +396,9 @@ $('.menu').addEventListener('click', e => {
(async () => {
let saveRule = [rule2json()];
await HttpPost(`/saveSources`, saveRule).then(json => {
alert(json.isSuccess ? `书源《${saveRule[0].bookSourceName}》已成功保存到「阅读APP」` : `书源《${saveRule[0].bookSourceName}》保存失败!\nErrorMsg: ${json.errorMsg}`);
alert(json.isSuccess ? `书源《${saveRule[0].bookSourceName}》已成功保存到「阅读3.0APP」` : `书源《${saveRule[0].bookSourceName}》保存失败!\nErrorMsg: ${json.errorMsg}`);
setRule(saveRule[0]);
}).catch(err => { alert(`保存书源失败,无法连接到「阅读APP」!\n${err}`); });
}).catch(err => { alert(`保存书源失败,无法连接到「阅读3.0APP」!\n${err}`); });
thisNode.setAttribute('class', '');
})();
return;
@ -261,6 +413,26 @@ $('#DebugKey').addEventListener('keydown', e => {
$('#debug').dispatchEvent(clickEvent);
}
});
$('#Filter').addEventListener('keydown', e => {
if (e.keyCode == 13) {
let cashList = [];
$('#RuleList').innerHTML = "";
let sKey = Filter.value ? Filter.value : '';
if (sKey == '') {
cashList = RuleSources;
} else {
let patt = new RegExp(sKey);
RuleSources.forEach(source => {
if (patt.test(source.bookSourceUrl) || patt.test(source.bookSourceName) || patt.test(source.bookSourceGroup)) {
cashList.push(source);
}
})
}
cashList.forEach(source => {
$('#RuleList').innerHTML += newRule(source);
})
}
});
// 列表规则更改事件
$('#RuleList').addEventListener('click', e => {
@ -348,7 +520,7 @@ $('.tab3>.titlebar').addEventListener('click', e => {
console.log(deleteSources);
console.log(`以上书源已删除!`)
}
}).catch(err => { alert(`删除书源失败,无法连接到「阅读APP」!\n${err}`); });
}).catch(err => { alert(`删除书源失败,无法连接到「阅读3.0APP」!\n${err}`); });
}
break;
case 'ClrAll':

@ -0,0 +1,176 @@
html, body {
height: 100%;
margin: 0;
}
.hide {
display: none;
}
.top, .showchapter, .hidebooks {
width: 60px;
height: 50px;
position: absolute;
right: 30px;
bottom: 30px;
color: black;
font-size: 28px;
background-color: #ddd;
opacity: 0.85;
}
.top {
bottom: 150px;
}
.showchapter {
bottom: 90px;
bottom: 90px;
}
.address {
width: 270px;
}
.nav {
border-bottom: solid 1px #ccc;
}
input, button {
width: 110px;
line-height: 34px;
background-color: #eee;
color: #555;
border: none;
margin: 10px 5px;
font-weight: 500;
border-radius: 2px;
outline: none;
cursor: pointer;
}
input {
padding: 0 10px;
cursor: text;
}
input:hover, button:hover {
border-color: #aaa;
background-color: #efefef;
color: #222;
outline: solid 1px #ccc;
}
.allcontent {
height: calc(100% - 60px);
}
.allscreen {
height: 100%
}
.books > div {
display: inline-block;
margin: 10px;
vertical-align: top;
border: solid 1px #ddd;
}
.read > .books {
width: 420px;
float: left;
height: 100%;
overflow: auto;
border-right: solid 1px #ccc;
}
.read > .books > div {
margin-right: 0;
border-right: none;
}
.more {
overflow-y: auto;
height: 100%;
display: none;
}
.read .more {
display: block;
}
.books > div > img {
width: 120px;
height: 180px;
float: left;
margin-right: 10px;
cursor: pointer;
}
.info {
padding: 10px 20px 0 20px;
width: 600px;
margin: 0 auto;
}
.info > img {
width: 600px;
height: 900px;
}
.info p {
line-height: 1.5;
text-align: justify;
margin: 0;
}
.books tr:nth-child(n+2) td {
border-top: solid 1px #999;
}
.books td:nth-child(1) {
vertical-align: top;
width: 50px;
}
.books td:nth-child(2) {
vertical-align: top;
width: 200px;
}
.clear {
clear: both;
}
.chapter {
margin: 10px;
max-height: 500px;
overflow-y: auto;
border-top: solid 1px #333;
border-bottom: solid 1px #333;
}
.chapter button {
width: 230px;
text-align: left;
text-indent: 14px;
margin: 10px 4px;
}
.content {
padding: 20px;
text-align: justify;
min-height: 1000px;
padding-bottom: 200px;
}
.content h2 {
font-family: "Microsoft YaHei",微软雅黑,"MicrosoftJhengHei",华文细黑,STHeiti,MingLiu;
font-weight: 500;
text-align: center;
line-height: 100px;
font-size: 40px;
margin: 0;
}

@ -0,0 +1,46 @@
<!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>yd-web-tool</title>
<link href=css/about.f23c15cb.css rel=prefetch>
<link href=css/detail.42c41bd6.css rel=prefetch>
<link href=js/about.2589b5fe.js rel=prefetch>
<link href=js/about~detail.08c372e6.js rel=prefetch>
<link href=js/detail.043d6e39.js rel=prefetch>
<link href=css/app.e1c0d2e4.css rel=preload as=style>
<link href=css/chunk-vendors.ad4ff18f.css rel=preload as=style>
<link href=js/app.b25f3cec.js rel=preload as=script>
<link href=js/chunk-vendors.b3838a2d.js rel=preload as=script>
<link href=css/chunk-vendors.ad4ff18f.css rel=stylesheet>
<link href=css/app.e1c0d2e4.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.b3838a2d.js></script>
<script src=js/app.b25f3cec.js></script>
</body>
</html>

@ -0,0 +1,203 @@
var $ = document.querySelector.bind(document)
, $$ = document.querySelectorAll.bind(document)
, $c = document.createElement.bind(document)
, randomImg = "http://api.mtyqx.cn/api/random.php"
, randomImg2 = "http://img.xjh.me/random_img.php"
, books
;
var now_chapter = -1;
var sum_chapter = 0;
var formatTime = value => {
return new Date(value).toLocaleString('zh-CN', {
hour12: false, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"
}).replace(/\//g, "-");
};
var apiMap = {
"getBookshelf": "/getBookshelf",
"getChapterList": "/getChapterList",
"getBookContent": "/getBookContent",
"saveBook": "/saveBook"
};
var apiAddress = (apiName, url, index) => {
let address = $('#address').value || window.location.host;
if (!(/^http|^\/\//).test(address)) {
address = "//" + address;
}
if (!(/:\d{4,}/).test(address.split("//")[1].split("/")[0])) {
address += ":1122";
}
localStorage.setItem('address', address);
if (apiName == "getBookContent") {
return address + apiMap[apiName] + (url ? "?url=" + encodeURIComponent(url) : "") + "&index=" + index;
}
return address + apiMap[apiName] + (url ? "?url=" + encodeURIComponent(url) : "");
};
var init = () => {
$('#allcontent').classList.remove("read");
$('#books').innerHTML = "";
fetch(apiAddress("getBookshelf"), { mode: "cors" })
.then(res => res.json())
.then(data => {
if (!data.isSuccess) {
alert(getBookshelf.errorMsg);
return;
}
books = data.data;
books.forEach((book, i) => {
let bookDiv = $c("div");
let img = $c("img");
img.src = book.coverUrl || randomImg;
img.setAttribute("data-series-num", i);
bookDiv.appendChild(img);
bookDiv.innerHTML += `<table><tbody>
<tr><td>书名</td><td>${book.name}</td></tr>
<tr><td>作者</td><td>${book.author}</td></tr>
<tr><td>阅读</td><td>${book.durChapterTitle}<br>${formatTime(book.durChapterTime)}</td></tr>
<tr><td>更新</td><td>${book.latestChapterTitle}<br>${formatTime(book.latestChapterTime)}</td></tr>
<tr><td>来源</td><td>${book.origin}</td></tr>
</tbody></table>`;
$('#books').appendChild(bookDiv);
});
$$('#books img').forEach(bookImg =>
bookImg.addEventListener("click", () => {
now_chapter = -1;
sum_chapter = 0;
$('#allcontent').classList.add("read");
var book = books[bookImg.getAttribute("data-series-num")];
$("#info").innerHTML = `<img src="${bookImg.src}">
<p>  来源${book.origin}</p>
<p>  书名${book.name}</p>
<p>  作者${book.author}</p>
<p>阅读章节${book.durChapterName}</p>
<p>阅读时间${formatTime(book.durChapterTime)}</p>
<p>最新章节${book.latestChapterTitle}</p>
<p>检查时间${formatTime(book.lastCheckTime)}</p>
<p>  简介${book.intro.trim().replace(/\n/g, "<br>")}</p>`;
window.location.hash = "";
window.location.hash = "#info";
$("#content").innerHTML = "章节列表加载中...";
$("#chapter").innerHTML = "";
fetch(apiAddress("getChapterList", book.bookUrl), { mode: "cors" })
.then(res => res.json())
.then(data => {
if (!data.isSuccess) {
alert(data.errorMsg);
$("#content").innerHTML = "章节列表加载失败!";
return;
}
data.data.forEach(chapter => {
let ch = $c("button");
ch.setAttribute("data-url", chapter.bookUrl);
ch.setAttribute("data-index", chapter.index);
ch.setAttribute("title", chapter.title);
ch.innerHTML = chapter.title.length > 15 ? chapter.title.substring(0, 14) + "..." : chapter.title;
$("#chapter").appendChild(ch);
});
sum_chapter = data.data.length;
$('#chapter').scrollTop = 0;
$("#content").innerHTML = "章节列表加载完成!";
});
}));
});
};
$("#back").addEventListener("click", () => {
if (window.location.hash === "#content") {
window.location.hash = "#chapter";
} else if (window.location.hash === "#chapter") {
window.location.hash = "#info";
} else {
$('#allcontent').classList.remove("read");
}
});
$("#refresh").addEventListener("click", init);
$('#hidebooks').addEventListener("click", () => {
$("#books").classList.toggle("hide");
$(".nav").classList.toggle("hide");
$("#allcontent").classList.toggle("allscreen");
});
$('#top').addEventListener("click", () => {
window.location.hash = "";
window.location.hash = "#info";
});
$('#showchapter').addEventListener("click", () => {
window.location.hash = "";
window.location.hash = "#chapter";
});
$('#up').addEventListener('click', e => {
if (now_chapter > 0) {
now_chapter--;
let clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent("click", true, false);
$('[data-index="' + now_chapter + '"]').dispatchEvent(clickEvent);
} else if (now_chapter == 0) {
alert("已经是第一章了^_^!")
} else {
}
});
$('#down').addEventListener('click', e => {
if (now_chapter > -1) {
if (now_chapter < sum_chapter - 1) {
now_chapter++;
let clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent("click", true, false);
$('[data-index="' + now_chapter + '"]').dispatchEvent(clickEvent);
} else {
alert("已经是最后一章了^_^!")
}
}
});
$('#chapter').addEventListener("click", (e) => {
if (e.target.tagName === "BUTTON") {
var url = e.target.getAttribute("data-url");
var index = e.target.getAttribute("data-index");
var name = e.target.getAttribute("title");
if (!url) {
alert("未取得书籍地址");
}
if (!index && (0 != index)) {
alert("未取得章节索引");
}
now_chapter = parseInt(index);
$("#content").innerHTML = "<p>" + name + " 加载中...</p>";
fetch(apiAddress("getBookContent", url, index), { mode: "cors" })
.then(res => res.json())
.then(data => {
if (!data.isSuccess) {
alert(data.errorMsg);
$("#content").innerHTML = "<p>" + name + " 加载失败!</p>";
return;
}
var content = data.data.trim().split("\n\n");
if (content.length === 2) {
$("#content").innerHTML = `<h2>${content[0]}</h2>  (全文 ${content[1].length} 字)<br><br>  ` + content[1].trim().replace(/\n/g, "<br><br>");
} else {
$("#content").innerHTML = `<h2>${name || e.target.innerHTML}</h2>  (全文 ${data.data.length} 字)<br><br>  ` + data.data.trim().replace(/\n/g, "<br><br>");
}
window.location.hash = "";
window.location.hash = "#content";
});
}
});
$('#address').setAttribute("placeholder", "阅读APP地址或IP:" + window.location.host);
if (!$('#address').value && typeof localStorage && localStorage.getItem('address')) {
$('#address').value = localStorage.getItem('address');
}
init();

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#2c3e50;margin:0;height:100%}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -0,0 +1,149 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="16.000000pt" height="16.000000pt" viewBox="0 0 16.000000 16.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,16.000000) scale(0.000320,-0.000320)"
fill="#000000" stroke="none">
<path d="M18 46618 c45 -75 122 -207 122 -211 0 -2 25 -45 55 -95 30 -50 55
-96 55 -102 0 -5 5 -10 10 -10 6 0 10 -4 10 -9 0 -5 73 -135 161 -288 89 -153
173 -298 187 -323 14 -25 32 -57 41 -72 88 -149 187 -324 189 -335 2 -7 8 -13
13 -13 5 0 9 -4 9 -10 0 -5 46 -89 103 -187 175 -302 490 -846 507 -876 8 -16
20 -36 25 -45 28 -46 290 -498 339 -585 13 -23 74 -129 136 -236 61 -107 123
-215 137 -240 14 -25 29 -50 33 -56 5 -5 23 -37 40 -70 18 -33 38 -67 44 -75
11 -16 21 -33 63 -109 14 -25 29 -50 33 -56 4 -5 21 -35 38 -65 55 -100 261
-455 269 -465 4 -5 14 -21 20 -35 15 -29 41 -75 103 -180 24 -41 52 -88 60
-105 9 -16 57 -100 107 -185 112 -193 362 -626 380 -660 8 -14 23 -38 33 -55
11 -16 23 -37 27 -45 4 -8 26 -46 48 -85 23 -38 53 -90 67 -115 46 -81 64
-113 178 -310 62 -107 121 -210 132 -227 37 -67 56 -99 85 -148 16 -27 32 -57
36 -65 4 -8 15 -27 25 -42 9 -15 53 -89 96 -165 44 -76 177 -307 296 -513 120
-206 268 -463 330 -570 131 -227 117 -203 200 -348 36 -62 73 -125 82 -140 10
-15 21 -34 25 -42 4 -8 20 -37 36 -65 17 -27 38 -65 48 -82 49 -85 64 -111 87
-153 13 -25 28 -49 32 -55 4 -5 78 -134 165 -285 87 -151 166 -288 176 -305
10 -16 26 -43 35 -59 9 -17 125 -217 257 -445 132 -229 253 -441 270 -471 17
-30 45 -79 64 -108 18 -29 33 -54 33 -57 0 -2 20 -37 44 -77 24 -40 123 -212
221 -383 97 -170 190 -330 205 -355 16 -25 39 -65 53 -90 13 -25 81 -144 152
-265 70 -121 137 -238 150 -260 12 -22 37 -65 55 -95 18 -30 43 -73 55 -95 12
-22 48 -85 80 -140 77 -132 163 -280 190 -330 13 -22 71 -123 130 -225 59
-102 116 -199 126 -217 10 -17 29 -50 43 -72 15 -22 26 -43 26 -45 0 -2 27
-50 60 -106 33 -56 60 -103 60 -105 0 -2 55 -98 90 -155 8 -14 182 -316 239
-414 13 -22 45 -79 72 -124 27 -46 49 -86 49 -89 0 -2 14 -24 30 -48 16 -24
30 -46 30 -49 0 -5 74 -135 100 -176 5 -8 24 -42 43 -75 50 -88 58 -101 262
-455 104 -179 199 -345 213 -370 14 -25 28 -49 32 -55 4 -5 17 -26 28 -45 10
-19 62 -109 114 -200 114 -197 133 -230 170 -295 16 -27 33 -57 38 -65 17 -28
96 -165 103 -180 4 -8 16 -28 26 -45 10 -16 77 -131 148 -255 72 -124 181
-313 243 -420 62 -107 121 -209 131 -227 35 -62 323 -560 392 -678 38 -66 83
-145 100 -175 16 -30 33 -59 37 -65 4 -5 17 -27 29 -47 34 -61 56 -100 90
-156 17 -29 31 -55 31 -57 0 -2 17 -32 39 -67 21 -35 134 -229 251 -433 117
-203 235 -407 261 -451 27 -45 49 -85 49 -88 0 -4 8 -19 19 -34 15 -21 200
-341 309 -533 10 -19 33 -58 51 -87 17 -29 31 -54 31 -56 0 -2 25 -44 55 -94
30 -50 55 -95 55 -98 0 -4 6 -15 14 -23 7 -9 27 -41 43 -71 17 -30 170 -297
342 -594 171 -296 311 -542 311 -547 0 -5 5 -9 10 -9 6 0 10 -4 10 -10 0 -5
22 -47 49 -92 27 -46 58 -99 68 -118 24 -43 81 -140 93 -160 5 -8 66 -114 135
-235 69 -121 130 -227 135 -235 12 -21 259 -447 283 -490 10 -19 28 -47 38
-62 11 -14 19 -29 19 -32 0 -3 37 -69 83 -148 99 -170 305 -526 337 -583 13
-22 31 -53 41 -70 11 -16 22 -37 26 -45 7 -14 82 -146 103 -180 14 -24 181
-311 205 -355 13 -22 46 -80 75 -130 29 -49 64 -110 78 -135 14 -25 51 -88 82
-140 31 -52 59 -102 63 -110 4 -8 18 -33 31 -55 205 -353 284 -489 309 -535
17 -30 45 -78 62 -106 18 -28 36 -60 39 -72 4 -12 12 -22 17 -22 5 0 9 -4 9
-10 0 -5 109 -197 241 -427 133 -230 250 -431 259 -448 51 -90 222 -385 280
-485 37 -63 78 -135 92 -160 14 -25 67 -117 118 -205 51 -88 101 -175 111
-193 34 -58 55 -95 149 -257 51 -88 101 -173 110 -190 9 -16 76 -131 147 -255
72 -124 140 -241 151 -260 61 -108 281 -489 355 -615 38 -66 77 -133 87 -150
35 -63 91 -161 100 -175 14 -23 99 -169 128 -220 54 -97 135 -235 142 -245 4
-5 20 -32 35 -60 26 -48 238 -416 276 -480 10 -16 26 -46 37 -65 30 -53 382
-661 403 -695 10 -16 22 -37 26 -45 4 -8 26 -48 50 -88 24 -41 43 -75 43 -77
0 -2 22 -40 50 -85 27 -45 50 -84 50 -86 0 -3 38 -69 83 -147 84 -142 302
-520 340 -587 10 -19 34 -60 52 -90 18 -30 44 -75 57 -100 14 -25 45 -79 70
-120 25 -41 56 -96 70 -121 14 -25 77 -133 138 -240 62 -107 122 -210 132
-229 25 -43 310 -535 337 -581 11 -19 26 -45 34 -59 17 -32 238 -414 266 -460
11 -19 24 -41 28 -49 3 -7 75 -133 160 -278 84 -146 153 -269 153 -274 0 -5 5
-9 10 -9 6 0 10 -4 10 -10 0 -5 82 -150 181 -322 182 -314 201 -346 240 -415
12 -21 80 -139 152 -263 71 -124 141 -245 155 -270 14 -25 28 -49 32 -55 6 -8
145 -248 220 -380 37 -66 209 -362 229 -395 11 -19 24 -42 28 -49 4 -8 67
-118 140 -243 73 -125 133 -230 133 -233 0 -2 15 -28 33 -57 19 -29 47 -78 64
-108 17 -30 53 -93 79 -139 53 -90 82 -141 157 -272 82 -142 115 -199 381
-659 142 -245 268 -463 281 -485 12 -22 71 -125 132 -230 60 -104 172 -298
248 -430 76 -132 146 -253 156 -270 11 -16 22 -36 26 -44 3 -8 30 -54 60 -103
29 -49 53 -91 53 -93 0 -3 18 -34 40 -70 22 -36 40 -67 40 -69 0 -2 37 -66 81
-142 45 -77 98 -168 119 -204 20 -36 47 -81 58 -100 12 -19 27 -47 33 -62 6
-16 15 -28 20 -28 5 0 9 -4 9 -9 0 -6 63 -118 140 -251 77 -133 140 -243 140
-245 0 -2 18 -33 41 -70 22 -37 49 -83 60 -101 10 -19 29 -51 40 -71 25 -45
109 -189 126 -218 7 -11 17 -29 22 -40 6 -11 22 -38 35 -60 14 -22 37 -62 52
-90 14 -27 35 -62 45 -77 11 -14 19 -29 19 -32 0 -3 18 -35 40 -71 22 -36 40
-67 40 -69 0 -2 19 -35 42 -72 23 -38 55 -94 72 -124 26 -47 139 -244 171
-298 6 -9 21 -36 34 -60 28 -48 37 -51 51 -19 6 12 19 36 29 52 10 17 27 46
38 65 11 19 104 181 208 360 103 179 199 345 213 370 14 25 42 74 64 109 21
34 38 65 38 67 0 2 18 33 40 69 22 36 40 67 40 69 0 3 177 310 199 346 16 26
136 234 140 244 2 5 25 44 52 88 27 44 49 81 49 84 0 2 18 34 40 70 22 36 40
67 40 69 0 2 20 36 43 77 35 58 169 289 297 513 9 17 50 86 90 155 40 69 86
150 103 180 16 30 35 62 41 70 6 8 16 24 22 35 35 64 72 129 167 293 59 100
116 199 127 220 11 20 30 53 41 72 43 72 1070 1850 1121 1940 14 25 65 113
113 195 48 83 96 166 107 185 10 19 28 50 38 68 11 18 73 124 137 235 64 111
175 303 246 427 71 124 173 299 225 390 52 91 116 202 143 248 27 45 49 85 49
89 0 4 6 14 14 22 7 9 28 43 46 76 26 47 251 436 378 655 11 19 29 51 40 70
11 19 101 176 201 348 99 172 181 317 181 323 0 5 5 9 10 9 6 0 10 5 10 11 0
6 8 23 18 37 11 15 32 52 49 82 16 30 130 228 253 440 122 212 234 405 248
430 13 25 39 70 57 100 39 65 69 117 130 225 25 44 50 87 55 95 12 19 78 134
220 380 61 107 129 224 150 260 161 277 222 382 246 425 15 28 47 83 71 123
24 41 43 78 43 83 0 5 4 9 8 9 4 0 13 12 19 28 7 15 23 45 36 67 66 110 277
478 277 483 0 3 6 13 14 21 7 9 27 41 43 71 17 30 45 80 63 110 34 57 375 649
394 685 6 11 16 27 22 35 6 8 26 42 44 75 18 33 41 74 51 90 10 17 24 41 32
55 54 97 72 128 88 152 11 14 19 28 19 30 0 3 79 141 175 308 96 167 175 305
175 308 0 3 6 13 14 21 7 9 26 39 41 66 33 60 276 483 338 587 24 40 46 80 50
88 4 8 13 24 20 35 14 23 95 163 125 215 11 19 52 91 92 160 40 69 80 139 90
155 9 17 103 179 207 360 105 182 200 346 211 365 103 181 463 802 489 845 7
11 15 27 19 35 4 8 29 51 55 95 64 110 828 1433 848 1470 9 17 24 41 33 55 9
14 29 48 45 77 15 28 52 93 82 145 30 51 62 107 71 123 17 30 231 398 400 690
51 88 103 179 115 202 12 23 26 48 32 55 6 7 24 38 40 68 17 30 61 107 98 170
37 63 84 144 103 180 19 36 41 72 48 81 8 8 14 18 14 21 0 4 27 51 59 106 32
55 72 124 89 154 16 29 71 125 122 213 51 88 104 180 118 205 13 25 28 50 32
55 4 6 17 26 28 45 11 19 45 80 77 135 31 55 66 116 77 135 11 19 88 152 171
295 401 694 620 1072 650 1125 11 19 87 152 170 295 83 143 158 273 166 288 9
16 21 36 26 45 6 9 31 52 55 96 25 43 54 94 66 115 11 20 95 164 186 321 91
157 173 299 182 315 9 17 26 46 37 65 12 19 66 114 121 210 56 96 108 186 117
200 8 14 24 40 34 59 24 45 383 664 412 713 5 9 17 29 26 45 15 28 120 210
241 419 36 61 68 117 72 125 4 8 12 23 19 34 35 57 245 420 262 453 11 20 35
61 53 90 17 29 32 54 32 56 0 3 28 51 62 108 33 57 70 119 80 138 10 19 23 42
28 50 5 8 32 53 59 100 27 47 149 258 271 470 122 212 234 405 248 430 30 53
62 108 80 135 6 11 15 27 19 35 4 8 85 150 181 315 96 165 187 323 202 350 31
56 116 202 130 225 5 8 25 42 43 75 19 33 92 159 162 280 149 257 157 271 202
350 19 33 38 67 43 75 9 14 228 392 275 475 12 22 55 96 95 165 40 69 80 139
90 155 24 42 202 350 221 383 9 15 27 47 41 72 14 25 75 131 136 236 61 106
121 210 134 232 99 172 271 470 279 482 5 8 23 40 40 70 18 30 81 141 142 245
60 105 121 210 135 235 14 25 71 124 127 220 56 96 143 247 194 335 51 88 96
167 102 175 14 24 180 311 204 355 23 43 340 590 356 615 5 8 50 87 101 175
171 301 517 898 582 1008 25 43 46 81 46 83 0 2 12 23 27 47 14 23 40 67 56
97 16 30 35 62 42 70 7 8 15 22 18 30 4 8 20 38 37 65 16 28 33 57 37 65 6 12
111 196 143 250 5 8 55 95 112 193 57 98 113 195 126 215 12 20 27 46 32 57 6
11 14 27 20 35 5 8 76 130 156 270 80 140 165 287 187 325 23 39 52 90 66 115
13 25 30 52 37 61 8 8 14 18 14 21 0 4 41 77 92 165 50 87 175 302 276 478
101 176 208 360 236 408 28 49 67 117 86 152 19 35 41 70 48 77 6 6 12 15 12
19 0 7 124 224 167 291 12 21 23 40 23 42 0 2 21 40 46 83 26 43 55 92 64 109
54 95 327 568 354 614 19 30 45 75 59 100 71 128 82 145 89 148 4 2 8 8 8 13
0 5 42 82 94 172 311 538 496 858 518 897 14 25 40 70 58 100 18 30 42 71 53
90 10 19 79 139 152 265 73 127 142 246 153 265 10 19 43 76 72 125 29 50 63
108 75 130 65 116 80 140 87 143 4 2 8 8 8 12 0 8 114 212 140 250 6 8 14 24
20 35 5 11 54 97 108 190 l100 170 -9611 3 c-5286 1 -9614 -1 -9618 -5 -5 -6
-419 -719 -619 -1068 -89 -155 -267 -463 -323 -560 -38 -66 -81 -140 -95 -165
-31 -56 -263 -457 -526 -910 -110 -190 -224 -388 -254 -440 -29 -52 -61 -109
-71 -125 -23 -39 -243 -420 -268 -465 -11 -19 -204 -352 -428 -740 -224 -388
-477 -826 -563 -975 -85 -148 -185 -322 -222 -385 -37 -63 -120 -207 -185
-320 -65 -113 -177 -306 -248 -430 -72 -124 -172 -297 -222 -385 -51 -88 -142
-245 -202 -350 -131 -226 -247 -427 -408 -705 -65 -113 -249 -432 -410 -710
-160 -278 -388 -673 -506 -877 -118 -205 -216 -373 -219 -373 -3 0 -52 82
-109 183 -58 100 -144 250 -192 332 -95 164 -402 696 -647 1120 -85 149 -228
396 -317 550 -212 365 -982 1700 -1008 1745 -10 19 -43 76 -72 125 -29 50 -64
110 -77 135 -14 25 -63 110 -110 190 -47 80 -96 165 -110 190 -14 25 -99 171
-188 325 -89 154 -174 300 -188 325 -13 25 -64 113 -112 195 -48 83 -140 242
-205 355 -65 113 -183 317 -263 454 -79 137 -152 264 -163 282 -50 89 -335
583 -354 614 -12 19 -34 58 -50 85 -15 28 -129 226 -253 440 -124 215 -235
408 -247 430 -12 22 -69 121 -127 220 -58 99 -226 389 -373 645 -148 256 -324
561 -392 678 -67 117 -134 232 -147 255 -13 23 -33 59 -46 80 l-22 37 -9615 0
-9615 0 20 -32z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
{"name":"yd-web-tool","short_name":"yd-web-tool","theme_color":"#4DBA87","icons":[{"src":"./img/icons/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"./img/icons/android-chrome-512x512.png","sizes":"512x512","type":"image/png"},{"src":"./img/icons/android-chrome-maskable-192x192.png","sizes":"192x192","type":"image/png","purpose":"maskable"},{"src":"./img/icons/android-chrome-maskable-512x512.png","sizes":"512x512","type":"image/png","purpose":"maskable"}],"start_url":".","display":"standalone","background_color":"#000000"}

@ -1,12 +1,11 @@
package io.legado.app
import android.app.Activity
import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatDelegate
import com.jeremyliao.liveeventbus.LiveEventBus
@ -19,7 +18,6 @@ import io.legado.app.help.AppConfig
import io.legado.app.help.CrashHandler
import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.theme.ThemeStore
import io.legado.app.ui.book.read.page.ChapterProvider
import io.legado.app.utils.getCompatColor
import io.legado.app.utils.getPrefInt
@ -48,19 +46,22 @@ class App : Application() {
versionCode = it.versionCode
versionName = it.versionName
}
if (!ThemeStore.isConfigured(this, versionCode)) applyTheme()
initNightMode()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) createChannelId()
LiveEventBus.get()
applyDayNight()
LiveEventBus
.config()
.supportBroadcast(this)
.lifecycleObserverAlwaysActive(true)
.autoClear(false)
registerActivityLife()
registerActivityLifecycleCallbacks(ActivityHelp)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
when (newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
Configuration.UI_MODE_NIGHT_YES, Configuration.UI_MODE_NIGHT_NO -> applyDayNight()
}
}
/**
@ -70,29 +71,26 @@ class App : Application() {
if (AppConfig.isNightTheme) {
ThemeStore.editTheme(this)
.primaryColor(
getPrefInt("colorPrimaryNight", getCompatColor(R.color.shine_color))
)
.accentColor(
getPrefInt("colorAccentNight", getCompatColor(R.color.lightBlue_color))
)
.backgroundColor(
getPrefInt("colorPrimaryNight", getCompatColor(R.color.md_blue_grey_600))
).accentColor(
getPrefInt("colorAccentNight", getCompatColor(R.color.md_deep_orange_800))
).backgroundColor(
getPrefInt("colorBackgroundNight", getCompatColor(R.color.shine_color))
)
.apply()
).bottomBackground(
getPrefInt("colorBottomBackgroundNight", getCompatColor(R.color.md_grey_850))
).apply()
} else {
ThemeStore.editTheme(this)
.primaryColor(
getPrefInt("colorPrimary", getCompatColor(R.color.md_grey_100))
)
.accentColor(
getPrefInt("colorAccent", getCompatColor(R.color.lightBlue_color))
)
.backgroundColor(
getPrefInt("colorPrimary", getCompatColor(R.color.md_indigo_800))
).accentColor(
getPrefInt("colorAccent", getCompatColor(R.color.md_red_600))
).backgroundColor(
getPrefInt("colorBackground", getCompatColor(R.color.md_grey_100))
)
.apply()
).bottomBackground(
getPrefInt("colorBottomBackground", getCompatColor(R.color.md_grey_200))
).apply()
}
ChapterProvider.upReadAloudSpan()
}
fun applyDayNight() {
@ -116,9 +114,7 @@ class App : Application() {
*/
@RequiresApi(Build.VERSION_CODES.O)
private fun createChannelId() {
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
notificationManager?.let {
(getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager)?.let {
//用唯一的ID创建渠道对象
val downloadChannel = NotificationChannel(
channelIdDownload,
@ -157,32 +153,4 @@ class App : Application() {
}
}
private fun registerActivityLife() {
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityPaused(activity: Activity) {
}
override fun onActivityResumed(activity: Activity) {
}
override fun onActivityStarted(activity: Activity) {
}
override fun onActivityDestroyed(activity: Activity) {
ActivityHelp.remove(activity)
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) {
}
override fun onActivityStopped(activity: Activity) {
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
ActivityHelp.add(activity)
}
})
}
}

@ -0,0 +1,12 @@
## 文件结构介绍
* base 基类
* constant 常量
* data 数据
* help 帮助
* lib 库
* model 解析
* receiver 广播侦听
* service 服务
* ui 界面
* web web服务

@ -52,9 +52,12 @@ abstract class BaseActivity(
} ?: super.onCreateOptionsMenu(menu)
}
override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {
menu.applyOpenTint(this)
return super.onMenuOpened(featureId, menu)
override fun onMenuOpened(featureId: Int, menu: Menu?): Boolean {
menu?.let {
menu.applyOpenTint(this)
return super.onMenuOpened(featureId, menu)
}
return true
}
open fun onCompatCreateOptionsMenu(menu: Menu): Boolean {

@ -0,0 +1,45 @@
package io.legado.app.base
import android.os.Bundle
import android.view.View
import androidx.fragment.app.DialogFragment
import io.legado.app.help.coroutine.Coroutine
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlin.coroutines.CoroutineContext
abstract class BaseDialogFragment : DialogFragment(), CoroutineScope {
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private lateinit var job: Job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = Job()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
onFragmentCreated(view, savedInstanceState)
observeLiveBus()
}
abstract fun onFragmentCreated(view: View, savedInstanceState: Bundle?)
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
fun <T> execute(
scope: CoroutineScope = this,
context: CoroutineContext = Dispatchers.IO,
block: suspend CoroutineScope.() -> T
): Coroutine<T> {
return Coroutine.async(scope, context) { block() }
}
open fun observeLiveBus() {
}
}

@ -1,5 +1,6 @@
package io.legado.app.base
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.*
import androidx.appcompat.view.SupportMenuInflater
@ -18,6 +19,7 @@ abstract class BaseFragment(layoutID: Int) : Fragment(layoutID),
private set
val menuInflater: MenuInflater
@SuppressLint("RestrictedApi")
get() = SupportMenuInflater(requireContext())
override val coroutineContext: CoroutineContext
@ -32,6 +34,14 @@ abstract class BaseFragment(layoutID: Int) : Fragment(layoutID),
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
onFragmentCreated(view, savedInstanceState)
observeLiveBus()
}
abstract fun onFragmentCreated(view: View, savedInstanceState: Bundle?)
override fun onDestroy() {
super.onDestroy()
job.cancel()
@ -52,6 +62,8 @@ abstract class BaseFragment(layoutID: Int) : Fragment(layoutID),
}
}
open fun observeLiveBus() {
}
open fun onCompatCreateOptionsMenu(menu: Menu) {
}

@ -3,17 +3,27 @@ package io.legado.app.base
import android.app.Service
import android.content.Intent
import android.os.IBinder
import io.legado.app.help.coroutine.Coroutine
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import kotlin.coroutines.CoroutineContext
abstract class BaseService : Service(), CoroutineScope by MainScope() {
fun <T> execute(
scope: CoroutineScope = this,
context: CoroutineContext = Dispatchers.IO,
block: suspend CoroutineScope.() -> T
): Coroutine<T> {
return Coroutine.async(scope, context) { block() }
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onDestroy() {
super.onDestroy()
cancel()

@ -5,6 +5,7 @@ import android.util.SparseArray
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import java.util.*
@ -15,13 +16,17 @@ import java.util.*
*
* 通用的adapter 可添加headerfooter以及不同类型item
*/
abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : RecyclerView.Adapter<ItemViewHolder>() {
abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) :
RecyclerView.Adapter<ItemViewHolder>() {
constructor(context: Context, vararg delegates: Pair<Int, ItemViewDelegate<ITEM>>) : this(context) {
constructor(context: Context, vararg delegates: ItemViewDelegate<ITEM>) : this(context) {
addItemViewDelegates(*delegates)
}
constructor(context: Context, vararg delegates: ItemViewDelegate<ITEM>) : this(context) {
constructor(
context: Context,
vararg delegates: Pair<Int, ItemViewDelegate<ITEM>>
) : this(context) {
addItemViewDelegates(*delegates)
}
@ -122,7 +127,7 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
}
}
fun setItems(items: List<ITEM>?, notify: Boolean = true) {
fun setItems(items: List<ITEM>?) {
synchronized(lock) {
if (this.items.isNotEmpty()) {
this.items.clear()
@ -130,9 +135,19 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
if (items != null) {
this.items.addAll(items)
}
if (notify) {
notifyDataSetChanged()
notifyDataSetChanged()
}
}
fun setItems(items: List<ITEM>?, diffResult: DiffUtil.DiffResult) {
synchronized(lock) {
if (this.items.isNotEmpty()) {
this.items.clear()
}
if (items != null) {
this.items.addAll(items)
}
diffResult.dispatchUpdatesTo(this)
}
}
@ -236,7 +251,11 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
synchronized(lock) {
val size = getActualItemCount()
if (fromPosition in 0 until size && toPosition in 0 until size) {
notifyItemRangeChanged(fromPosition + getHeaderCount(), toPosition - fromPosition + 1, payloads)
notifyItemRangeChanged(
fromPosition + getHeaderCount(),
toPosition - fromPosition + 1,
payloads
)
}
}
}
@ -271,7 +290,12 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
return footerItems?.size() ?: 0
}
fun getItem(position: Int): ITEM? = if (position in 0 until items.size) items[position] else null
fun getItem(position: Int): ITEM? = items.getOrNull(position)
fun getItemByLayoutPosition(position: Int): ITEM? {
val pos = position - getHeaderCount()
return items.getOrNull(pos)
}
fun getItems(): List<ITEM> = items
@ -294,7 +318,9 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
return when {
isHeader(position) -> TYPE_HEADER_VIEW + position
isFooter(position) -> TYPE_FOOTER_VIEW + position - getActualItemCount() - getHeaderCount()
else -> getItem(getActualPosition(position))?.let { getItemViewType(it, getActualPosition(position)) } ?: 0
else -> getItem(getActualPosition(position))?.let {
getItemViewType(it, getActualPosition(position))
} ?: 0
}
}
@ -309,7 +335,16 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
}
else -> {
val holder = ItemViewHolder(inflater.inflate(itemDelegates.getValue(viewType).layoutId, parent, false))
val holder = ItemViewHolder(
inflater.inflate(
itemDelegates.getValue(viewType).layoutId,
parent,
false
)
)
itemDelegates.getValue(viewType)
.registerListener(holder)
if (itemClickListener != null) {
holder.itemView.setOnClickListener {
@ -336,7 +371,11 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
final override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
}
final override fun onBindViewHolder(holder: ItemViewHolder, position: Int, payloads: MutableList<Any>) {
final override fun onBindViewHolder(
holder: ItemViewHolder,
position: Int,
payloads: MutableList<Any>
) {
if (!isHeader(holder.layoutPosition) && !isFooter(holder.layoutPosition)) {
getItem(holder.layoutPosition - getHeaderCount())?.let {
itemDelegates.getValue(getItemViewType(holder.layoutPosition))
@ -385,10 +424,6 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
}
private fun addAnimation(holder: ItemViewHolder) {
if (itemAnimation == null) {
itemAnimation = ItemAnimation.create().enabled(true)
}
itemAnimation?.let {
if (it.itemAnimEnabled) {
if (!it.itemAnimFirstOnly || holder.layoutPosition > it.itemAnimStartPosition) {

@ -9,6 +9,17 @@ import android.content.Context
*/
abstract class ItemViewDelegate<ITEM>(protected val context: Context, val layoutId: Int) {
/**
* 如果使用了事件回调,回调里不要直接使用item,会出现不更新的问题,
* 使用getItem(holder.layoutPosition)来获取item,
* 或者使用registerListener(holder: ItemViewHolder, position: Int)
*/
abstract fun convert(holder: ItemViewHolder, item: ITEM, payloads: MutableList<Any>)
/**
* 注册事件
*/
abstract fun registerListener(holder: ItemViewHolder)
}

@ -15,8 +15,20 @@ abstract class SimpleRecyclerAdapter<ITEM>(context: Context, private val layoutI
this@SimpleRecyclerAdapter.convert(holder, item, payloads)
}
override fun registerListener(holder: ItemViewHolder) {
this@SimpleRecyclerAdapter.registerListener(holder)
}
})
}
/**
* 如果使用了事件回调,回调里不要直接使用item,会出现不更新的问题,
* 使用getItem(holder.layoutPosition)来获取item
*/
abstract fun convert(holder: ItemViewHolder, item: ITEM, payloads: MutableList<Any>)
/**
* 注册事件
*/
abstract fun registerListener(holder: ItemViewHolder)
}

@ -4,8 +4,6 @@ import android.annotation.SuppressLint
import io.legado.app.App
import io.legado.app.R
import io.legado.app.data.entities.BookGroup
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.putPrefBoolean
import java.text.SimpleDateFormat
import javax.script.ScriptEngine
import javax.script.ScriptEngineManager
@ -22,46 +20,51 @@ object AppConst {
const val UA_NAME = "User-Agent"
val userAgent: String by lazy {
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36"
}
val SCRIPT_ENGINE: ScriptEngine by lazy {
ScriptEngineManager().getEngineByName("rhino")
}
val TIME_FORMAT: SimpleDateFormat by lazy {
val timeFormat: SimpleDateFormat by lazy {
SimpleDateFormat("HH:mm")
}
val DATE_FORMAT: SimpleDateFormat by lazy {
val dateFormat: SimpleDateFormat by lazy {
SimpleDateFormat("yyyy/MM/dd HH:mm")
}
val fileNameFormat: SimpleDateFormat by lazy {
SimpleDateFormat("yy-MM-dd-HH-mm-ss")
}
val keyboardToolChars: List<String> by lazy {
arrayListOf(
"@", "&", "|", "%", "/", ":", "[", "]", "{", "}", "<", ">", "\\", "$", "#", "!", ".",
"href", "src", "textNodes", "xpath", "json", "css", "id", "class", "tag"
"", "@", "&", "|", "%", "/", ":", "[", "]", "{", "}", "<", ">", "\\",
"$", "#", "!", ".", "href", "src", "textNodes", "xpath", "json", "css",
"id", "class", "tag"
)
}
val bookGroupAll = BookGroup(-1, App.INSTANCE.getString(R.string.all))
val bookGroupLocal = BookGroup(-2, App.INSTANCE.getString(R.string.local))
val bookGroupAudio = BookGroup(-3, App.INSTANCE.getString(R.string.audio))
var bookGroupLocalShow: Boolean
get() = App.INSTANCE.getPrefBoolean("bookGroupLocal", false)
set(value) {
App.INSTANCE.putPrefBoolean("bookGroupLocal", value)
}
var bookGroupAudioShow: Boolean
get() = App.INSTANCE.getPrefBoolean("bookGroupAudio", false)
set(value) {
App.INSTANCE.putPrefBoolean("bookGroupAudio", value)
}
val bookGroupNone = BookGroup(-4, App.INSTANCE.getString(R.string.no_group))
const val notificationIdRead = 1144771
const val notificationIdAudio = 1144772
const val notificationIdWeb = 1144773
const val notificationIdDownload = 1144774
val urlOption: String by lazy {
"""
,{
"charset": "",
"method": "POST",
"body": "",
"headers": {"User-Agent": ""}
}
""".trimIndent()
}
}

@ -2,7 +2,9 @@ package io.legado.app.constant
import java.util.regex.Pattern
object Pattern {
object AppPattern {
val JS_PATTERN: Pattern = Pattern.compile("(<js>[\\w\\W]*?</js>|@js:[\\w\\W]*$)", Pattern.CASE_INSENSITIVE)
val EXP_PATTERN: Pattern = Pattern.compile("\\{\\{([\\w\\W]*?)\\}\\}")
val authorRegex = "\\s*者\\s*[::]".toRegex()
}

@ -1,17 +1,16 @@
package io.legado.app.constant
object Bus {
object EventBus {
const val MEDIA_BUTTON = "mediaButton"
const val RECREATE = "RECREATE"
const val UP_BOOK = "sourceDebugLog"
const val ALOUD_STATE = "aloud_state"
const val TTS_START = "ttsStart"
const val TTS_PROGRESS = "ttsStart"
const val TTS_DS = "ttsDs"
const val BATTERY_CHANGED = "batteryChanged"
const val TIME_CHANGED = "timeChanged"
const val UP_CONFIG = "upConfig"
const val OPEN_CHAPTER = "openChapter"
const val REPLACE = "replace"
const val AUDIO_SUB_TITLE = "audioSubTitle"
const val AUDIO_STATE = "audioState"
const val AUDIO_PROGRESS = "audioProgress"
@ -20,5 +19,5 @@ object Bus {
const val SHOW_RSS = "showRss"
const val WEB_SERVICE_STOP = "webServiceStop"
const val UP_DOWNLOAD = "upDownload"
const val UP_TABS = "upTabs"
const val SAVE_CONTENT = "saveContent"
}

@ -1,6 +1,6 @@
package io.legado.app.constant
object Action {
object IntentAction {
const val start = "start"
const val play = "play"
const val stop = "stop"
@ -17,4 +17,5 @@ object Action {
const val next = "next"
const val moveTo = "moveTo"
const val init = "init"
const val remove = "remove"
}

@ -1,16 +1,22 @@
package io.legado.app.constant
object PreferKey {
const val downloadPath = "downloadPath"
const val versionCode = "versionCode"
const val themeMode = "themeMode"
const val hideStatusBar = "hideStatusBar"
const val clickTurnPage = "clickTurnPage"
const val clickAllNext = "clickAllNext"
const val hideNavigationBar = "hideNavigationBar"
const val precisionSearch = "precisionSearch"
const val readAloudOnLine = "readAloudOnLine"
const val readAloudByPage = "readAloudByPage"
const val ttsSpeechRate = "ttsSpeechRate"
const val ttsSpeechPer = "ttsSpeechPer"
const val prevKey = "prevKeyCode"
const val nextKey = "nextKeyCode"
const val showRss = "showRss"
const val bookshelfLayout = "bookshelfLayout"
const val bookshelfSort = "bookshelfSort"
const val recordLog = "recordLog"
const val processText = "process_text"
const val cleanCache = "cleanCache"
@ -20,6 +26,21 @@ object PreferKey {
const val fontFolder = "fontFolder"
const val backupPath = "backupUri"
const val threadCount = "threadCount"
const val webPort = "webPort"
const val keepLight = "keep_light"
const val autoDarkMode = "autoDarkMode"
const val webService = "webService"
const val webDavUrl = "web_dav_url"
const val webDavAccount = "web_dav_account"
const val webDavPassword = "web_dav_password"
const val webDavCreateDir = "webDavCreateDir"
const val changeSourceLoadToc = "changeSourceLoadToc"
const val chineseConverterType = "chineseConverterType"
const val launcherIcon = "launcherIcon"
const val textSelectAble = "selectText"
const val lastBackup = "lastBackup"
const val bodyIndent = "textIndent"
const val shareLayout = "shareLayout"
const val readStyleSelect = "readStyleSelect"
const val systemTypefaces = "system_typefaces"
const val readBodyToLh = "readBodyToLh"
}

@ -1,17 +0,0 @@
package io.legado.app.constant
object RSSKeywords {
const val RSS_ITEM = "item"
const val RSS_ITEM_TITLE = "title"
const val RSS_ITEM_LINK = "link"
const val RSS_ITEM_CATEGORY = "category"
const val RSS_ITEM_THUMBNAIL = "media:thumbnail"
const val RSS_ITEM_ENCLOSURE = "enclosure"
const val RSS_ITEM_DESCRIPTION = "description"
const val RSS_ITEM_CONTENT = "content:encoded"
const val RSS_ITEM_PUB_DATE = "pubDate"
const val RSS_ITEM_TIME = "time"
const val RSS_ITEM_URL = "url"
const val RSS_ITEM_TYPE = "type"
}

@ -4,6 +4,7 @@ import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import io.legado.app.data.dao.*
import io.legado.app.data.entities.*
@ -16,8 +17,9 @@ import kotlinx.coroutines.launch
@Database(
entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class,
ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,
RssSource::class, Bookmark::class, RssArticle::class, RssStar::class],
version = 6,
RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::class,
RssStar::class, TxtTocRule::class],
version = 13,
exportSchema = true
)
abstract class AppDatabase : RoomDatabase() {
@ -29,13 +31,47 @@ abstract class AppDatabase : RoomDatabase() {
fun createDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
.fallbackToDestructiveMigration()
.addMigrations(migration_10_11, migration_11_12, migration_12_13)
.addCallback(object : Callback() {
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
GlobalScope.launch { Restore.restore(Backup.backupPath) }
GlobalScope.launch { Restore.restoreDatabase(Backup.backupPath) }
}
})
.build()
}
private val migration_10_11 = object : Migration(10, 11) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE txtTocRules")
database.execSQL(
"""
CREATE TABLE txtTocRules(id INTEGER NOT NULL,
name TEXT NOT NULL, rule TEXT NOT NULL, serialNumber INTEGER NOT NULL,
enable INTEGER NOT NULL, PRIMARY KEY (id))
"""
)
}
}
private val migration_11_12 = object : Migration(11, 12) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
ALTER TABLE rssSources ADD style TEXT
"""
)
}
}
private val migration_12_13 = object : Migration(12, 13) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
ALTER TABLE rssSources ADD articleStyle INTEGER NOT NULL DEFAULT 0
"""
)
}
}
}
abstract fun bookDao(): BookDao
@ -50,4 +86,5 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun rssArticleDao(): RssArticleDao
abstract fun rssStarDao(): RssStarDao
abstract fun cookieDao(): CookieDao
abstract fun txtTocRule(): TxtTocRuleDao
}

@ -1 +1,17 @@
## 存储数据用
## 存储数据用
* dao 数据操作
* entities 数据模型
* \Book 书籍信息
* \BookChapter 目录信息
* \BookGroup 书籍分组
* \Bookmark 书签
* \BookSource 书源
* \Cookie http cookie
* \ReplaceRule 替换规则
* \RssArticle rss条目
* \RssReadRecord rss阅读记录
* \RssSource rss源
* \RssStar rss收藏
* \SearchBook 搜索结果
* \SearchKeyword 搜索关键字
* \TxtTocRule txt文件目录规则

@ -10,15 +10,18 @@ import io.legado.app.data.entities.BookChapter
@Dao
interface BookChapterDao {
@Query("select * from chapters where bookUrl = :bookUrl")
@Query("select * from chapters where bookUrl = :bookUrl order by `index`")
fun observeByBook(bookUrl: String): LiveData<List<BookChapter>>
@Query("SELECT * FROM chapters where bookUrl = :bookUrl and title like '%'||:key||'%'")
@Query("SELECT * FROM chapters where bookUrl = :bookUrl and title like '%'||:key||'%' order by `index`")
fun liveDataSearch(bookUrl: String, key: String): LiveData<List<BookChapter>>
@Query("select * from chapters where bookUrl = :bookUrl")
@Query("select * from chapters where bookUrl = :bookUrl order by `index`")
fun getChapterList(bookUrl: String): List<BookChapter>
@Query("select * from chapters where bookUrl = :bookUrl and `index` >= :start and `index` <= :end order by `index`")
fun getChapterList(bookUrl: String, start: Int, end: Int): List<BookChapter>
@Query("select * from chapters where bookUrl = :bookUrl and `index` = :index")
fun getChapter(bookUrl: String, index: Int): BookChapter?

@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData
import androidx.room.*
import io.legado.app.constant.BookType
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookProgress
@Dao
interface BookDao {
@ -11,30 +12,45 @@ interface BookDao {
@Query("SELECT * FROM books order by durChapterTime desc")
fun observeAll(): LiveData<List<Book>>
@Query("SELECT * FROM books WHERE type = ${BookType.audio} order by durChapterTime desc")
@Query("SELECT * FROM books WHERE type = ${BookType.audio}")
fun observeAudio(): LiveData<List<Book>>
@Query("SELECT * FROM books WHERE origin = '${BookType.local}' order by durChapterTime desc")
@Query("SELECT * FROM books WHERE origin = '${BookType.local}'")
fun observeLocal(): LiveData<List<Book>>
@Query("SELECT * FROM books WHERE origin <> '${BookType.local}' and type = 0 order by durChapterTime desc")
@Query("SELECT bookUrl FROM books WHERE origin = '${BookType.local}'")
fun observeLocalUri(): LiveData<List<String>>
@Query("SELECT * FROM books WHERE origin <> '${BookType.local}' and type = 0")
fun observeDownload(): LiveData<List<Book>>
@Query("SELECT * FROM books WHERE `group` = :group")
@Query("SELECT * FROM books WHERE (`group` & :group) > 0")
fun observeByGroup(group: Int): LiveData<List<Book>>
@Query("SELECT bookUrl FROM books WHERE `group` = :group")
fun observeUrlsByGroup(group: Int): LiveData<List<String>>
@Query("select * from books where (SELECT sum(groupId) FROM book_groups) & `group` = 0")
fun observeNoGroup(): LiveData<List<Book>>
@Query("select count(bookUrl) from books where (SELECT sum(groupId) FROM book_groups) & `group` = 0")
fun observeNoGroupSize(): LiveData<Int>
@Query("SELECT * FROM books WHERE name like '%'||:key||'%' or author like '%'||:key||'%'")
fun liveDataSearch(key: String): LiveData<List<Book>>
@Query("SELECT * FROM books WHERE (`group` & :group) > 0")
fun getBooksByGroup(group: Int): List<Book>
@Query("SELECT * FROM books WHERE `name` in (:names)")
fun findByName(vararg names: String): List<Book>
@Query("SELECT * FROM books WHERE bookUrl = :bookUrl")
fun getBook(bookUrl: String): Book?
@Query("SELECT * FROM books WHERE name = :name and author = :author")
fun getBook(name: String, author: String): Book?
@get:Query("select count(bookUrl) from books where (SELECT sum(groupId) FROM book_groups) & `group` = 0")
val noGroupSize: Int
@get:Query("SELECT * FROM books where origin <> '${BookType.local}' and type = 0")
val webBooks: List<Book>
@ -42,7 +58,7 @@ interface BookDao {
val hasUpdateBooks: List<Book>
@get:Query("SELECT * FROM books")
val allBooks: List<Book>
val all: List<Book>
@get:Query("SELECT * FROM books where type = 0 ORDER BY durChapterTime DESC limit 1")
val lastReadBook: Book?
@ -53,15 +69,40 @@ interface BookDao {
@get:Query("SELECT COUNT(*) FROM books")
val allBookCount: Int
@get:Query("select max(`order`) from books")
val maxOrder: Int
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg book: Book)
@Update
fun update(vararg books: Book)
fun update(vararg book: Book)
@Query("delete from books where bookUrl = :bookUrl")
fun delete(bookUrl: String)
@Delete
fun delete(vararg book: Book)
@Query("update books set durChapterPos = :pos where bookUrl = :bookUrl")
fun upProgress(bookUrl: String, pos: Int)
@Query("update books set `group` = :newGroupId where `group` = :oldGroupId")
fun upGroup(oldGroupId: Int, newGroupId: Int)
@get:Query("select bookUrl, durChapterIndex, durChapterPos, durChapterTime, durChapterTitle from books")
val allBookProgress: List<BookProgress>
@Query(
"""
update books set
durChapterIndex = :durChapterIndex, durChapterPos = :durChapterPos,
durChapterTime = :durChapterTime, durChapterTitle = :durChapterTitle
where bookUrl = :bookUrl and durChapterTime < :durChapterTime
"""
)
fun upBookProgress(
bookUrl: String,
durChapterIndex: Int,
durChapterPos: Int,
durChapterTime: Long,
durChapterTitle: String?
)
}

@ -13,14 +13,14 @@ interface BookGroupDao {
@Query("SELECT * FROM book_groups ORDER BY `order`")
fun liveDataAll(): LiveData<List<BookGroup>>
@get:Query("SELECT MAX(groupId) FROM book_groups")
val maxId: Int
@get:Query("SELECT sum(groupId) FROM book_groups")
val idsSum: Int
@get:Query("SELECT MAX(`order`) FROM book_groups")
val maxOrder: Int
@Query("SELECT * FROM book_groups ORDER BY `order`")
fun all(): List<BookGroup>
@get:Query("SELECT * FROM book_groups ORDER BY `order`")
val all: List<BookGroup>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg bookGroup: BookGroup)

@ -14,36 +14,24 @@ interface BookSourceDao {
@Query("select * from book_sources where bookSourceName like :searchKey or bookSourceGroup like :searchKey or bookSourceUrl like :searchKey order by customOrder asc")
fun liveDataSearch(searchKey: String = ""): LiveData<List<BookSource>>
@Query("select * from book_sources where enabledExplore = 1 and exploreUrl is not null and exploreUrl <> '' order by customOrder asc")
@Query("select * from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' order by customOrder asc")
fun liveExplore(): LiveData<List<BookSource>>
@Query("select * from book_sources where enabledExplore = 1 and exploreUrl is not null and exploreUrl <> '' and (bookSourceGroup like :key or bookSourceName like :key) order by customOrder asc")
@Query("select * from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' and (bookSourceGroup like :key or bookSourceName like :key) order by customOrder asc")
fun liveExplore(key: String): LiveData<List<BookSource>>
@Query("select bookSourceGroup from book_sources where bookSourceGroup is not null and bookSourceGroup <> ''")
@Query("select bookSourceGroup from book_sources where trim(bookSourceGroup) <> ''")
fun liveGroup(): LiveData<List<String>>
@Query("select bookSourceGroup from book_sources where enabled = 1 and bookSourceGroup is not null and bookSourceGroup <> ''")
@Query("select bookSourceGroup from book_sources where enabled = 1 and trim(bookSourceGroup) <> ''")
fun liveGroupEnabled(): LiveData<List<String>>
@Query("select bookSourceGroup from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' and trim(bookSourceGroup) <> ''")
fun liveGroupExplore(): LiveData<List<String>>
@Query("select distinct enabled from book_sources where bookSourceName like :searchKey or bookSourceGroup like :searchKey or bookSourceUrl like :searchKey")
fun searchIsEnable(searchKey: String = ""): List<Boolean>
@Query("update book_sources set enabled = 1 where bookSourceUrl = :sourceUrl")
fun enableSection(sourceUrl: String)
@Query("update book_sources set enabled = 0 where bookSourceUrl = :sourceUrl")
fun disableSection(sourceUrl: String)
@Query("update book_sources set enabledExplore = 1 where bookSourceUrl = :sourceUrl")
fun enableSectionExplore(sourceUrl: String)
@Query("update book_sources set enabledExplore = 0 where bookSourceUrl = :sourceUrl")
fun disableSectionExplore(sourceUrl: String)
@Query("delete from book_sources where bookSourceUrl = :sourceUrl")
fun delSection(sourceUrl: String)
@Query("select * from book_sources where enabledExplore = 1 order by customOrder asc")
fun observeFind(): DataSource.Factory<Int, BookSource>
@ -53,15 +41,21 @@ interface BookSourceDao {
@Query("select * from book_sources where enabled = 1 and bookSourceGroup like '%' || :group || '%'")
fun getEnabledByGroup(group: String): List<BookSource>
@get:Query("select * from book_sources where trim(bookUrlPattern) <> ''")
val hasBookUrlPattern: List<BookSource>
@get:Query("select * from book_sources where bookSourceGroup is null or bookSourceGroup = ''")
val noGroup: List<BookSource>
@get:Query("select * from book_sources order by customOrder asc")
val all: List<BookSource>
@get:Query("select * from book_sources where enabled = 1 order by customOrder asc")
@get:Query("select * from book_sources where enabled = 1 order by customOrder")
val allEnabled: List<BookSource>
@get:Query("select * from book_sources where enabled = 1 and bookSourceType = 0 order by customOrder")
val allTextEnabled: List<BookSource>
@Query("select * from book_sources where bookSourceUrl = :key")
fun getBookSource(key: String): BookSource?
@ -75,7 +69,7 @@ interface BookSourceDao {
fun update(vararg bookSource: BookSource)
@Delete
fun delete(bookSource: BookSource)
fun delete(vararg bookSource: BookSource)
@Query("delete from book_sources where bookSourceUrl = :key")
fun delete(key: String)

@ -32,24 +32,21 @@ interface ReplaceRuleDao {
@Query("SELECT * FROM replace_rules WHERE id in (:ids)")
fun findByIds(vararg ids: Long): List<ReplaceRule>
@Query("update replace_rules set isEnabled = 1 where id in (:ids)")
fun enableSection(vararg ids: Long)
@Query("update replace_rules set isEnabled = 0 where id in (:ids)")
fun disableSection(vararg ids: Long)
@Query("delete from replace_rules where id in (:ids)")
fun delSection(vararg ids: Long)
@Query(
"""SELECT * FROM replace_rules WHERE isEnabled = 1
AND (scope LIKE '%' || :scope || '%' or scope = null or scope = '')"""
"""
SELECT * FROM replace_rules WHERE isEnabled = 1
AND (scope LIKE '%' || :scope || '%' or scope is null or scope = '')
order by sortOrder
"""
)
fun findEnabledByScope(scope: String): List<ReplaceRule>
@Query(
"""SELECT * FROM replace_rules WHERE isEnabled = 1
AND (scope LIKE '%' || :name || '%' or scope LIKE '%' || :origin || '%' or scope = null or scope = '')"""
"""
SELECT * FROM replace_rules WHERE isEnabled = 1
AND (scope LIKE '%' || :name || '%' or scope LIKE '%' || :origin || '%' or scope is null or scope = '')
order by sortOrder
"""
)
fun findEnabledByScope(name: String, origin: String): List<ReplaceRule>

@ -3,6 +3,7 @@ package io.legado.app.data.dao
import androidx.lifecycle.LiveData
import androidx.room.*
import io.legado.app.data.entities.RssArticle
import io.legado.app.data.entities.RssReadRecord
@Dao
interface RssArticleDao {
@ -10,18 +11,28 @@ interface RssArticleDao {
@Query("select * from rssArticles where origin = :origin and link = :link")
fun get(origin: String, link: String): RssArticle?
@Query("select * from rssArticles where origin = :origin order by `order` desc")
fun liveByOrigin(origin: String): LiveData<List<RssArticle>>
@Query(
"""select t1.link, t1.sort, t1.origin, t1.`order`, t1.title, t1.content, t1.description, t1.image, t1.pubDate, ifNull(t2.read, 0) as read
from rssArticles as t1 left join rssReadRecords as t2
on t1.link = t2.record where origin = :origin and sort = :sort
order by `order` desc"""
)
fun liveByOriginSort(origin: String, sort: String): LiveData<List<RssArticle>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg rssArticle: RssArticle)
@Query("delete from rssArticles where origin = :origin and `order` < :order")
fun clearOld(origin: String, order: Long)
@Query("delete from rssArticles where origin = :origin and sort = :sort and `order` < :order")
fun clearOld(origin: String, sort: String, order: Long)
@Update
fun update(vararg rssArticle: RssArticle)
@Query("delete from rssArticles where origin = :origin")
fun delete(origin: String)
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insertRecord(vararg rssReadRecord: RssReadRecord)
}

@ -28,24 +28,15 @@ interface RssSourceDao {
@Query("SELECT * FROM rssSources where enabled = 1 order by customOrder")
fun liveEnabled(): LiveData<List<RssSource>>
@Query("select sourceGroup from rssSources where sourceGroup is not null and sourceGroup <> ''")
@Query("select sourceGroup from rssSources where trim(sourceGroup) <> ''")
fun liveGroup(): LiveData<List<String>>
@Query("update rssSources set enabled = 1 where sourceUrl in (:sourceUrls)")
fun enableSection(vararg sourceUrls: String)
@Query("update rssSources set enabled = 0 where sourceUrl in (:sourceUrls)")
fun disableSection(vararg sourceUrls: String)
@get:Query("select min(customOrder) from rssSources")
val minOrder: Int
@get:Query("select max(customOrder) from rssSources")
val maxOrder: Int
@Query("delete from rssSources where sourceUrl in (:sourceUrls)")
fun delSection(vararg sourceUrls: String)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg rssSource: RssSource)

@ -7,6 +7,9 @@ import io.legado.app.data.entities.RssStar
@Dao
interface RssStarDao {
@get:Query("select * from rssStars order by starTime desc")
val all: List<RssStar>
@Query("select * from rssStars where origin = :origin and link = :link")
fun get(origin: String, link: String): RssStar?

@ -19,16 +19,45 @@ interface SearchBookDao {
@Query("select * from searchBooks where bookUrl = :bookUrl")
fun getSearchBook(bookUrl: String): SearchBook?
@Query("select * from searchBooks where name = :name and author = :author order by originOrder limit 1")
@Query("select * from searchBooks where name = :name and author = :author and origin in (select bookSourceUrl from book_sources where enabled = 1) order by originOrder limit 1")
fun getFirstByNameAuthor(name: String, author: String): SearchBook?
@Query("select * from searchBooks where name = :name and author = :author order by originOrder")
fun getByNameAuthor(name: String, author: String): List<SearchBook>
@Query("select * from searchBooks where name = :name and author = :author and origin in (select bookSourceUrl from book_sources where enabled = 1) order by originOrder")
@Query(
"""
select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl, t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable, t1.wordCount, t2.customOrder as originOrder
from searchBooks as t1 inner join book_sources as t2
on t1.origin = t2.bookSourceUrl
where t1.name = :name and t1.author = :author and t2.enabled = 1
order by t2.customOrder
"""
)
fun getByNameAuthorEnable(name: String, author: String): List<SearchBook>
@Query(
"""
select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl, t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable, t1.wordCount, t2.customOrder as originOrder
from searchBooks as t1 inner join book_sources as t2
on t1.origin = t2.bookSourceUrl
where t1.name = :name and t1.author = :author and originName like '%'||:key||'%' and t2.enabled = 1
order by t2.customOrder
"""
)
fun getChangeSourceSearch(name: String, author: String, key: String): List<SearchBook>
@Query(
"""
select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl, t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable, t1.wordCount, t2.customOrder as originOrder
from searchBooks as t1 inner join book_sources as t2
on t1.origin = t2.bookSourceUrl
where t1.name = :name and t1.author = :author and t1.coverUrl is not null and t1.coverUrl <> '' and t2.enabled = 1
order by t2.customOrder
"""
)
fun getEnableHasCover(name: String, author: String): List<SearchBook>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg searchBook: SearchBook): List<Long>
@Query("delete from searchBooks where time < :time")
fun clearExpired(time: Long)
}

@ -0,0 +1,33 @@
package io.legado.app.data.dao
import androidx.lifecycle.LiveData
import androidx.room.*
import io.legado.app.data.entities.TxtTocRule
@Dao
interface TxtTocRuleDao {
@Query("select * from txtTocRules order by serialNumber")
fun observeAll(): LiveData<List<TxtTocRule>>
@get:Query("select * from txtTocRules order by serialNumber")
val all: List<TxtTocRule>
@get:Query("select * from txtTocRules where enable = 1 order by serialNumber")
val enabled: List<TxtTocRule>
@get:Query("select ifNull(max(serialNumber), 0) from txtTocRules")
val lastOrderNum: Int
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg rule: TxtTocRule)
@Update(onConflict = OnConflictStrategy.REPLACE)
fun update(vararg rule: TxtTocRule)
@Delete
fun delete(vararg rule: TxtTocRule)
@Query("delete from txtTocRules where id < 0")
fun deleteDefault()
}

@ -3,6 +3,7 @@ package io.legado.app.data.entities
import io.legado.app.utils.splitNotBlank
interface BaseBook {
var bookUrl: String
var variableMap: HashMap<String, String>?
var kind: String?
var wordCount: String?

@ -4,32 +4,36 @@ import android.os.Parcelable
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
import io.legado.app.constant.AppPattern
import io.legado.app.constant.BookType
import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonObject
import kotlinx.android.parcel.IgnoredOnParcel
import kotlinx.android.parcel.Parcelize
import java.nio.charset.Charset
import kotlin.math.max
@Parcelize
@Entity(tableName = "books", indices = [(Index(value = ["bookUrl"], unique = true))])
@Entity(
tableName = "books",
primaryKeys = ["name", "author"],
indices = [(Index(value = ["bookUrl"], unique = true))]
)
data class Book(
@PrimaryKey
var bookUrl: String = "", // 详情页Url(本地书源存储完整文件路径)
override var bookUrl: String = "", // 详情页Url(本地书源存储完整文件路径)
var tocUrl: String = "", // 目录页Url (toc=table of Contents)
var origin: String = BookType.local, // 书源URL(默认BookType.local)
var originName: String = "", //书源名称
var name: String = "", // 书籍名称(书源获取)
var author: String = "", // 作者名称(书源获取)
override var kind: String? = null, // 分类信息(书源获取)
var originName: String = "", //书源名称 or 本地书籍文件名
var name: String = "", // 书籍名称(书源获取)
var author: String = "", // 作者名称(书源获取)
override var kind: String? = null, // 分类信息(书源获取)
var customTag: String? = null, // 分类信息(用户修改)
var coverUrl: String? = null, // 封面Url(书源获取)
var customCoverUrl: String? = null, // 封面Url(用户修改)
var intro: String? = null, // 简介内容(书源获取)
var customIntro: String? = null, // 简介内容(用户修改)
var intro: String? = null, // 简介内容(书源获取)
var customIntro: String? = null, // 简介内容(用户修改)
var charset: String? = null, // 自定义字符集名称(仅适用于本地书籍)
var type: Int = 0, // @BookType
var type: Int = 0, // 0:text 1:audio
var group: Int = 0, // 自定义分组索引号
var latestChapterTitle: String? = null, // 最新章节标题
var latestChapterTime: Long = System.currentTimeMillis(), // 最新章节标题更新时间
@ -48,6 +52,25 @@ data class Book(
var variable: String? = null // 自定义书籍变量信息(用于书源规则检索书籍信息)
) : Parcelable, BaseBook {
fun isLocalBook(): Boolean {
return origin == BookType.local
}
fun isTxt(): Boolean {
return isLocalBook() && originName.endsWith(".txt", true)
}
override fun equals(other: Any?): Boolean {
if (other is Book) {
return other.bookUrl == bookUrl
}
return false
}
override fun hashCode(): Int {
return bookUrl.hashCode()
}
@Ignore
@IgnoredOnParcel
override var variableMap: HashMap<String, String>? = null
@ -66,6 +89,8 @@ data class Book(
@IgnoredOnParcel
override var tocHtml: String? = null
fun getRealAuthor() = author.replace(AppPattern.authorRegex, "")
fun getUnreadChapterNum() = max(totalChapterNum - durChapterIndex - 1, 0)
fun getDisplayCover() = if (customCoverUrl.isNullOrEmpty()) coverUrl else customCoverUrl
@ -77,6 +102,10 @@ data class Book(
variable = GSON.toJson(variableMap)
}
fun fileCharset(): Charset {
return charset(charset ?: "UTF-8")
}
fun toSearchBook(): SearchBook {
return SearchBook(
name = name,

@ -33,7 +33,7 @@ data class BookChapter(
var tag: String? = null, //
var start: Long? = null, // 章节起始位置
var end: Long? = null, // 章节终止位置
var variable: String? = null
var variable: String? = null //变量
) : Parcelable {
@Ignore
@ -52,5 +52,16 @@ data class BookChapter(
variable = GSON.toJson(variableMap)
}
override fun hashCode(): Int {
return url.hashCode()
}
override fun equals(other: Any?): Boolean {
if (other is BookChapter) {
return other.url == url
}
return false
}
}

@ -8,8 +8,8 @@ import kotlinx.android.parcel.Parcelize
@Parcelize
@Entity(tableName = "book_groups")
data class BookGroup(
@PrimaryKey
var groupId: Int = 0,
var groupName: String,
var order: Int = 0
@PrimaryKey
val groupId: Int = 0b1,
var groupName: String,
var order: Int = 0
) : Parcelable

@ -0,0 +1,9 @@
package io.legado.app.data.entities
data class BookProgress(
val bookUrl: String,
val durChapterIndex: Int,
val durChapterPos: Int,
val durChapterTime: Long,
val durChapterTitle: String?
)

@ -1,10 +1,7 @@
package io.legado.app.data.entities
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
import androidx.room.*
import io.legado.app.App
import io.legado.app.constant.AppConst
import io.legado.app.constant.AppConst.userAgent
@ -15,12 +12,12 @@ import io.legado.app.utils.ACache
import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.getPrefString
import kotlinx.android.parcel.IgnoredOnParcel
import kotlinx.android.parcel.Parcelize
import java.util.*
import javax.script.SimpleBindings
@Parcelize
@TypeConverters(BookSource.Converters::class)
@Entity(
tableName = "book_sources",
indices = [(Index(value = ["bookSourceUrl"], unique = false))]
@ -40,32 +37,24 @@ data class BookSource(
var lastUpdateTime: Long = 0, // 最后更新时间,用于排序
var weight: Int = 0, // 智能排序的权重
var exploreUrl: String? = null, // 发现url
var ruleExplore: String? = null, // 发现规则
var ruleExplore: ExploreRule? = null, // 发现规则
var searchUrl: String? = null, // 搜索url
var ruleSearch: String? = null, // 搜索规则
var ruleBookInfo: String? = null, // 书籍信息页规则
var ruleToc: String? = null, // 目录页规则
var ruleContent: String? = null // 正文页规则
var ruleSearch: SearchRule? = null, // 搜索规则
var ruleBookInfo: BookInfoRule? = null, // 书籍信息页规则
var ruleToc: TocRule? = null, // 目录页规则
var ruleContent: ContentRule? = null // 正文页规则
) : Parcelable, JsExtensions {
@Ignore
@IgnoredOnParcel
private var searchRuleV: SearchRule? = null
@Ignore
@IgnoredOnParcel
private var exploreRuleV: ExploreRule? = null
@Ignore
@IgnoredOnParcel
private var bookInfoRuleV: BookInfoRule? = null
@Ignore
@IgnoredOnParcel
private var tocRuleV: TocRule? = null
override fun hashCode(): Int {
return bookSourceUrl.hashCode()
}
@Ignore
@IgnoredOnParcel
private var contentRuleV: ContentRule? = null
override fun equals(other: Any?): Boolean {
if (other is BookSource) {
return other.bookSourceUrl == bookSourceUrl
}
return false
}
@Throws(Exception::class)
fun getHeaderMap(): Map<String, String> {
@ -87,43 +76,33 @@ data class BookSource(
}
fun getSearchRule(): SearchRule {
searchRuleV ?: let {
searchRuleV = GSON.fromJsonObject<SearchRule>(ruleSearch)
searchRuleV ?: let { searchRuleV = SearchRule() }
}
return searchRuleV!!
return ruleSearch ?: SearchRule()
}
fun getExploreRule(): ExploreRule {
exploreRuleV ?: let {
exploreRuleV = GSON.fromJsonObject<ExploreRule>(ruleExplore)
exploreRuleV ?: let { exploreRuleV = ExploreRule() }
}
return exploreRuleV!!
return ruleExplore ?: ExploreRule()
}
fun getBookInfoRule(): BookInfoRule {
bookInfoRuleV ?: let {
bookInfoRuleV = GSON.fromJsonObject<BookInfoRule>(ruleBookInfo)
bookInfoRuleV ?: let { bookInfoRuleV = BookInfoRule() }
}
return bookInfoRuleV!!
return ruleBookInfo ?: BookInfoRule()
}
fun getTocRule(): TocRule {
tocRuleV ?: let {
tocRuleV = GSON.fromJsonObject<TocRule>(ruleToc)
tocRuleV ?: let { tocRuleV = TocRule() }
}
return tocRuleV!!
return ruleToc ?: TocRule()
}
fun getContentRule(): ContentRule {
contentRuleV ?: let {
contentRuleV = GSON.fromJsonObject<ContentRule>(ruleContent)
contentRuleV ?: let { contentRuleV = ContentRule() }
return ruleContent ?: ContentRule()
}
fun addGroup(group: String) {
bookSourceGroup?.let {
if (!it.contains(group)) {
bookSourceGroup = "$it;$group"
}
} ?: let {
bookSourceGroup = group
}
return contentRuleV!!
}
fun getExploreKinds(): ArrayList<ExploreKind>? {
@ -147,7 +126,7 @@ data class BookSource(
}
}
val b = a.split("(&&|\n)+".toRegex())
b.map { c ->
b.forEach { c ->
val d = c.split("::")
if (d.size > 1)
exploreKinds.add(ExploreKind(d[0], d[1]))
@ -197,4 +176,57 @@ data class BookSource(
var title: String,
var url: String? = null
)
class Converters {
@TypeConverter
fun exploreRuleToString(exploreRule: ExploreRule?): String {
return GSON.toJson(exploreRule)
}
@TypeConverter
fun stringToExploreRule(json: String?): ExploreRule? {
return GSON.fromJsonObject<ExploreRule>(json)
}
@TypeConverter
fun searchRuleToString(searchRule: SearchRule): String {
return GSON.toJson(searchRule)
}
@TypeConverter
fun stringToSearchRule(json: String?): SearchRule? {
return GSON.fromJsonObject<SearchRule>(json)
}
@TypeConverter
fun bookInfoRuleToString(bookInfoRule: BookInfoRule): String {
return GSON.toJson(bookInfoRule)
}
@TypeConverter
fun stringToBookInfoRule(json: String?): BookInfoRule? {
return GSON.fromJsonObject<BookInfoRule>(json)
}
@TypeConverter
fun tocRuleToString(tocRule: TocRule): String {
return GSON.toJson(tocRule)
}
@TypeConverter
fun stringToTocRule(json: String?): TocRule? {
return GSON.fromJsonObject<TocRule>(json)
}
@TypeConverter
fun contentRuleToString(contentRule: ContentRule): String {
return GSON.toJson(contentRule)
}
@TypeConverter
fun stringToContentRule(json: String?): ContentRule? {
return GSON.fromJsonObject<ContentRule>(json)
}
}
}

@ -1,11 +1,14 @@
package io.legado.app.data.entities
import android.os.Parcelable
import android.text.TextUtils
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import kotlinx.android.parcel.Parcelize
import java.util.regex.Pattern
import java.util.regex.PatternSyntaxException
@Parcelize
@Entity(
@ -24,4 +27,33 @@ data class ReplaceRule(
var isRegex: Boolean = true,
@ColumnInfo(name = "sortOrder")
var order: Int = 0
) : Parcelable
) : Parcelable {
override fun equals(other: Any?): Boolean {
if (other is ReplaceRule) {
return other.id == id
}
return super.equals(other)
}
override fun hashCode(): Int {
return id.hashCode()
}
fun isValid(): Boolean{
if (TextUtils.isEmpty(pattern)){
return false;
}
//判断正则表达式是否正确
if (isRegex){
try {
Pattern.compile(pattern);
}
catch (ex: PatternSyntaxException){
return false;
}
}
return true;
}
}

@ -9,6 +9,7 @@ import androidx.room.Entity
)
data class RssArticle(
var origin: String = "",
var sort: String = "",
var title: String = "",
var order: Long = 0,
var link: String = "",
@ -20,7 +21,7 @@ data class RssArticle(
) {
override fun hashCode(): Int {
return super.hashCode()
return link.hashCode()
}
override fun equals(other: Any?): Boolean {
@ -36,6 +37,7 @@ data class RssArticle(
fun toStar(): RssStar {
return RssStar(
origin = origin,
sort = sort,
title = title,
starTime = System.currentTimeMillis(),
link = link,

@ -0,0 +1,7 @@
package io.legado.app.data.entities
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "rssReadRecords")
data class RssReadRecord(@PrimaryKey val record: String, val read: Boolean = true)

@ -23,6 +23,8 @@ data class RssSource(
var sourceIcon: String = "",
var sourceGroup: String? = null,
var enabled: Boolean = true,
var sortUrl: String? = null,
var articleStyle: Int = 0,
//列表规则
var ruleArticles: String? = null,
var ruleNextPage: String? = null,
@ -33,12 +35,25 @@ data class RssSource(
var ruleImage: String? = null,
var ruleLink: String? = null,
var ruleContent: String? = null,
var style: String? = null,
var header: String? = null,
var enableJs: Boolean = false,
var loadWithBaseUrl: Boolean = false,
var customOrder: Int = 0
) : Parcelable, JsExtensions {
override fun equals(other: Any?): Boolean {
if (other is RssSource) {
return other.sourceUrl == sourceUrl
}
return false
}
override fun hashCode(): Int {
return sourceUrl.hashCode()
}
@Throws(Exception::class)
fun getHeaderMap(): Map<String, String> {
val headerMap = HashMap<String, String>()
@ -88,4 +103,16 @@ data class RssSource(
return a == b || (a.isNullOrEmpty() && b.isNullOrEmpty())
}
fun sortUrls(): LinkedHashMap<String, String> {
val sortMap = linkedMapOf<String, String>()
sortUrl?.split("(&&|\n)+".toRegex())?.forEach { c ->
val d = c.split("::")
if (d.size > 1)
sortMap[d[0]] = d[1]
}
if (sortMap.isEmpty()) {
sortMap[""] = sourceUrl
}
return sortMap
}
}

@ -9,6 +9,7 @@ import androidx.room.Entity
)
data class RssStar(
var origin: String = "",
var sort: String = "",
var title: String = "",
var starTime: Long = 0,
var link: String = "",
@ -16,4 +17,17 @@ data class RssStar(
var description: String? = null,
var content: String? = null,
var image: String? = null
)
) {
fun toRssArticle(): RssArticle {
return RssArticle(
origin = origin,
sort = sort,
title = title,
link = link,
pubDate = pubDate,
description = description,
content = content,
image = image
)
}
}

@ -21,7 +21,7 @@ import kotlinx.android.parcel.Parcelize
)
data class SearchBook(
@PrimaryKey
var bookUrl: String = "",
override var bookUrl: String = "",
var origin: String = "", // 书源规则
var originName: String = "",
var type: Int = 0, // @BookType
@ -78,16 +78,21 @@ data class SearchBook(
variable = GSON.toJson(variableMap)
}
@Ignore
@delegate:Ignore
@IgnoredOnParcel
var origins: LinkedHashSet<String>? = null
private set
val origins: LinkedHashSet<String> by lazy { linkedSetOf(origin) }
fun addOrigin(origin: String) {
if (origins == null) {
origins = linkedSetOf(this.origin)
origins.add(origin)
}
fun getDisplayLastChapterTitle(): String {
latestChapterTitle?.let {
if (it.isNotEmpty()) {
return it
}
}
origins?.add(origin)
return "无最新章节"
}
fun toBook(): Book {

@ -0,0 +1,15 @@
package io.legado.app.data.entities
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "txtTocRules")
data class TxtTocRule(
@PrimaryKey
var id: Long = System.currentTimeMillis(),
var name: String = "",
var rule: String = "",
var serialNumber: Int = -1,
var enable: Boolean = true
)

@ -1,5 +1,8 @@
package io.legado.app.data.entities.rule
import android.os.Parcel
import android.os.Parcelable
data class BookInfoRule(
var init: String? = null,
var name: String? = null,
@ -11,4 +14,45 @@ data class BookInfoRule(
var coverUrl: String? = null,
var tocUrl: String? = null,
var wordCount: String? = null
)
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString()
)
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(init)
dest.writeString(name)
dest.writeString(author)
dest.writeString(intro)
dest.writeString(kind)
dest.writeString(lastChapter)
dest.writeString(updateTime)
dest.writeString(coverUrl)
dest.writeString(tocUrl)
dest.writeString(wordCount)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<BookInfoRule> {
override fun createFromParcel(parcel: Parcel): BookInfoRule {
return BookInfoRule(parcel)
}
override fun newArray(size: Int): Array<BookInfoRule?> {
return arrayOfNulls(size)
}
}
}

@ -1,8 +1,39 @@
package io.legado.app.data.entities.rule
import android.os.Parcel
import android.os.Parcelable
data class ContentRule(
var content: String? = null,
var nextContentUrl: String? = null,
var webJs: String? = null,
var sourceRegex: String? = null
)
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString()
)
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(content)
dest.writeString(nextContentUrl)
dest.writeString(webJs)
dest.writeString(sourceRegex)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<ContentRule> {
override fun createFromParcel(parcel: Parcel): ContentRule {
return ContentRule(parcel)
}
override fun newArray(size: Int): Array<ContentRule?> {
return arrayOfNulls(size)
}
}
}

@ -1,5 +1,8 @@
package io.legado.app.data.entities.rule
import android.os.Parcel
import android.os.Parcelable
data class ExploreRule(
override var bookList: String? = null,
override var name: String? = null,
@ -11,4 +14,46 @@ data class ExploreRule(
override var bookUrl: String? = null,
override var coverUrl: String? = null,
override var wordCount: String? = null
) : BookListRule
) : BookListRule, Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString()
)
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(bookList)
dest.writeString(name)
dest.writeString(author)
dest.writeString(intro)
dest.writeString(kind)
dest.writeString(lastChapter)
dest.writeString(updateTime)
dest.writeString(bookUrl)
dest.writeString(coverUrl)
dest.writeString(wordCount)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<ExploreRule> {
override fun createFromParcel(parcel: Parcel): ExploreRule {
return ExploreRule(parcel)
}
override fun newArray(size: Int): Array<ExploreRule?> {
return arrayOfNulls(size)
}
}
}

@ -1,5 +1,8 @@
package io.legado.app.data.entities.rule
import android.os.Parcel
import android.os.Parcelable
data class SearchRule(
override var bookList: String? = null,
override var name: String? = null,
@ -11,4 +14,46 @@ data class SearchRule(
override var bookUrl: String? = null,
override var coverUrl: String? = null,
override var wordCount: String? = null
) : BookListRule
) : BookListRule, Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString()
)
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(bookList)
dest.writeString(name)
dest.writeString(author)
dest.writeString(intro)
dest.writeString(kind)
dest.writeString(lastChapter)
dest.writeString(updateTime)
dest.writeString(bookUrl)
dest.writeString(coverUrl)
dest.writeString(wordCount)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<SearchRule> {
override fun createFromParcel(parcel: Parcel): SearchRule {
return SearchRule(parcel)
}
override fun newArray(size: Int): Array<SearchRule?> {
return arrayOfNulls(size)
}
}
}

@ -1,5 +1,8 @@
package io.legado.app.data.entities.rule
import android.os.Parcel
import android.os.Parcelable
data class TocRule(
var chapterList: String? = null,
var chapterName: String? = null,
@ -7,4 +10,36 @@ data class TocRule(
var isVip: String? = null,
var updateTime: String? = null,
var nextTocUrl: String? = null
)
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString()
)
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(chapterList)
dest.writeString(chapterName)
dest.writeString(chapterUrl)
dest.writeString(isVip)
dest.writeString(updateTime)
dest.writeString(nextTocUrl)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<TocRule> {
override fun createFromParcel(parcel: Parcel): TocRule {
return TocRule(parcel)
}
override fun newArray(size: Int): Array<TocRule?> {
return arrayOfNulls(size)
}
}
}

@ -1,13 +1,15 @@
package io.legado.app.help
import android.app.Activity
import android.app.Application
import android.os.Bundle
import java.lang.ref.WeakReference
import java.util.*
/**
* Activity管理器,管理项目中Activity的状态
*/
object ActivityHelp {
object ActivityHelp : Application.ActivityLifecycleCallbacks {
private val activities: MutableList<WeakReference<Activity>> = arrayListOf()
@ -19,7 +21,7 @@ object ActivityHelp {
* 判断指定Activity是否存在
*/
fun isExist(activityClass: Class<*>): Boolean {
for (item in activities) {
activities.forEach { item ->
if (item.get()?.javaClass == activityClass) {
return true
}
@ -63,7 +65,7 @@ object ActivityHelp {
* 关闭指定 activity
*/
fun finishActivity(vararg activities: Activity) {
for (activity in activities) {
activities.forEach { activity ->
activity.finish()
}
}
@ -81,9 +83,32 @@ object ActivityHelp {
}
}
}
for (activityWeakReference in waitFinish) {
activityWeakReference.get()?.finish()
waitFinish.forEach {
it.get()?.finish()
}
}
override fun onActivityPaused(activity: Activity) {
}
override fun onActivityResumed(activity: Activity) {
}
override fun onActivityStarted(activity: Activity) {
}
override fun onActivityDestroyed(activity: Activity) {
remove(activity)
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) {
}
override fun onActivityStopped(activity: Activity) {
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
add(activity)
}
}

@ -2,7 +2,10 @@ package io.legado.app.help
import androidx.recyclerview.widget.RecyclerView
internal class AdapterDataObserverProxy(var adapterDataObserver: RecyclerView.AdapterDataObserver, var headerCount: Int) : RecyclerView.AdapterDataObserver() {
internal class AdapterDataObserverHeader(
var adapterDataObserver: RecyclerView.AdapterDataObserver,
var headerCount: Int
) : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
adapterDataObserver.onChanged()
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save