Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b8cfebe3d | |||
| e760bd1a9b | |||
| d260eb1b5e | |||
| d59f95f13a | |||
| 0d9da26eff | |||
| a769110c70 | |||
| 71ea318503 | |||
| 59d5f08c83 | |||
| 36230cebea | |||
| cbee29ea85 | |||
| 9b5a44fe59 | |||
| 9f87edaa7c | |||
| e976c9793c |
@@ -45,7 +45,7 @@ body:
|
||||
label: LNReader version
|
||||
description: You can find your LNReader version in **More → About**.
|
||||
placeholder: |
|
||||
Example: "1.1.15"
|
||||
Example: "1.1.17"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -88,7 +88,7 @@ body:
|
||||
required: true
|
||||
- label: If this is an issue with a source, I should be opening an issue in the [sources repository](https://github.com/lnreader/lnreader-sources/issues/new/choose).
|
||||
required: true
|
||||
- label: I have updated the app to version **[1.1.15](https://github.com/lnreader/lnreader/releases/latest)**.
|
||||
- label: I have updated the app to version **[1.1.17](https://github.com/lnreader/lnreader/releases/latest)**.
|
||||
required: true
|
||||
- label: I will fill out all of the requested information in this form.
|
||||
required: true
|
||||
|
||||
@@ -8,7 +8,10 @@ jobs:
|
||||
name: Build App
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Clone Repository (Latest)
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
|
||||
@@ -133,7 +133,7 @@ def reactNativeArchitectures() {
|
||||
ext {
|
||||
versionMajor = 1
|
||||
versionMinor = 1
|
||||
versionPatch = 15
|
||||
versionPatch = 17
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
Generated
+144
-146
@@ -1,14 +1,13 @@
|
||||
{
|
||||
"name": "lnreader",
|
||||
"version": "1.1.15",
|
||||
"version": "1.1.17",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "lnreader",
|
||||
"version": "1.1.15",
|
||||
"version": "1.1.17",
|
||||
"dependencies": {
|
||||
"@cd-z/react-native-epub-creator": "^2.0.4",
|
||||
"@gorhom/bottom-sheet": "^4.4.5",
|
||||
"@react-native-async-storage/async-storage": "^1.18.1",
|
||||
"@react-native-community/clipboard": "^1.5.1",
|
||||
@@ -54,7 +53,6 @@
|
||||
"react-native-pager-view": "^6.2.0",
|
||||
"react-native-paper": "^5.7.2",
|
||||
"react-native-reanimated": "~2.9.1",
|
||||
"react-native-saf-x": "^2.2.1",
|
||||
"react-native-safe-area-context": "4.3.1",
|
||||
"react-native-screens": "~3.15.0",
|
||||
"react-native-shimmer-placeholder": "^2.0.9",
|
||||
@@ -62,7 +60,6 @@
|
||||
"react-native-tts": "^4.1.0",
|
||||
"react-native-vector-icons": "^9.0.0",
|
||||
"react-native-webview": "11.23.0",
|
||||
"react-native-zip-archive": "^6.0.9",
|
||||
"react-redux": "^7.2.6",
|
||||
"redux-persist": "^6.0.0",
|
||||
"rn-fetch-blob": "^0.12.0",
|
||||
@@ -2086,40 +2083,6 @@
|
||||
"react": ">=16.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cd-z/epub-constructor": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@cd-z/epub-constructor/-/epub-constructor-2.2.2.tgz",
|
||||
"integrity": "sha512-45/zouZcN7txT6spb8ddW3Wmo0MLdOhXqePOUVKODXSjnpYPEjSdkfIeu/Xt4p80q80lxGi52zeYAQ4XTVAAsw==",
|
||||
"dependencies": {
|
||||
"node-html-parser": "^4.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@cd-z/react-native-epub-creator": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@cd-z/react-native-epub-creator/-/react-native-epub-creator-2.0.4.tgz",
|
||||
"integrity": "sha512-bVCE0J9lNjT2JaEVqfjU4kI5Fq4cI4KObWnNfo0NBeCjU89s6+3oHme1yKI/Xm2cq7QDWIklRW/WPXGAO4IE1A==",
|
||||
"dependencies": {
|
||||
"@cd-z/epub-constructor": "^2.1.0",
|
||||
"expo-file-system": "^15.2.2",
|
||||
"react-native-saf-x": "^2.2.1",
|
||||
"react-native-zip-archive": "^6.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@cd-z/react-native-epub-creator/node_modules/expo-file-system": {
|
||||
"version": "15.4.2",
|
||||
"resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-15.4.2.tgz",
|
||||
"integrity": "sha512-WFaEWuFEuUpETiq85YlhKYJgedccWTjtCMnYGAgyNfCfvnIgfMCVH7dWudGuxhfAcTZqh36OcqtSckbtbhOtyg==",
|
||||
"dependencies": {
|
||||
"uuid": "^3.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"expo": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@egjs/hammerjs": {
|
||||
"version": "2.0.17",
|
||||
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
|
||||
@@ -10001,14 +9964,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||
"bin": {
|
||||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/hermes-engine": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/hermes-engine/-/hermes-engine-0.11.0.tgz",
|
||||
@@ -13247,78 +13202,6 @@
|
||||
"node": ">= 6.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-html-parser": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-4.1.5.tgz",
|
||||
"integrity": "sha512-NLgqUXtftqnBqIjlRjYSaApaqE7TTxfTiH4VqKCjdUJKFOtUzRwney83EHz2qYc0XoxXAkYdmLjENCuZHvsIFg==",
|
||||
"dependencies": {
|
||||
"css-select": "^4.1.3",
|
||||
"he": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-html-parser/node_modules/css-select": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz",
|
||||
"integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-what": "^6.0.1",
|
||||
"domhandler": "^4.3.1",
|
||||
"domutils": "^2.8.0",
|
||||
"nth-check": "^2.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/node-html-parser/node_modules/dom-serializer": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
|
||||
"integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.0.1",
|
||||
"domhandler": "^4.2.0",
|
||||
"entities": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/node-html-parser/node_modules/domhandler": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
|
||||
"integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/node-html-parser/node_modules/domutils": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
|
||||
"integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
|
||||
"dependencies": {
|
||||
"dom-serializer": "^1.0.1",
|
||||
"domelementtype": "^2.2.0",
|
||||
"domhandler": "^4.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/node-html-parser/node_modules/entities": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
|
||||
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/node-int64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
||||
@@ -14823,14 +14706,15 @@
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/react-native": {
|
||||
"version": "0.69.12",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.69.12.tgz",
|
||||
"integrity": "sha512-kHOj4V0wD/hvjyiiXPP96fMBSRNnx0MXGTTrL43KppsWvKnOtvnQ/qSTWR0d3s6Hi4n7mx3Un+NqCuBKsf+5yg==",
|
||||
"version": "0.69.9",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.69.9.tgz",
|
||||
"integrity": "sha512-I1xqIn47RWxBToO4E6yqyIPSaK9mZnMiscMfrFpWjQr3Gdkicr9y+twmtrRszxaLdQLjHzh/M3y4qOqc3hZnpg==",
|
||||
"deprecated": "Issues and pull requests filed against this version are not supported. See the React Native release support policy to learn more: https://github.com/reactwg/react-native-releases#releases-support-policy",
|
||||
"dependencies": {
|
||||
"@jest/create-cache-key-function": "^27.0.1",
|
||||
"@react-native-community/cli": "^8.0.7",
|
||||
"@react-native-community/cli-platform-android": "^8.0.5",
|
||||
"@react-native-community/cli-platform-ios": "^8.0.6",
|
||||
"@react-native-community/cli": "^8.0.4",
|
||||
"@react-native-community/cli-platform-android": "^8.0.4",
|
||||
"@react-native-community/cli-platform-ios": "^8.0.4",
|
||||
"@react-native/assets": "1.0.0",
|
||||
"@react-native/normalize-color": "2.0.0",
|
||||
"@react-native/polyfills": "2.0.0",
|
||||
@@ -14842,9 +14726,9 @@
|
||||
"invariant": "^2.2.4",
|
||||
"jsc-android": "^250230.2.1",
|
||||
"memoize-one": "^5.0.0",
|
||||
"metro-react-native-babel-transformer": "0.70.4",
|
||||
"metro-runtime": "0.70.4",
|
||||
"metro-source-map": "0.70.4",
|
||||
"metro-react-native-babel-transformer": "0.70.3",
|
||||
"metro-runtime": "0.70.3",
|
||||
"metro-source-map": "0.70.3",
|
||||
"mkdirp": "^0.5.1",
|
||||
"nullthrows": "^1.1.1",
|
||||
"pretty-format": "^26.5.2",
|
||||
@@ -15095,15 +14979,6 @@
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-saf-x": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-saf-x/-/react-native-saf-x-2.2.1.tgz",
|
||||
"integrity": "sha512-WL8EFpmjlTTVrHkIFmLzfux3ZpG+XM32VLjggEvGoz8rMq6bCFqH/SlqudHfnAUdFrWprQwypJ451xcQ5Tqwgw==",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-safe-area-context": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.3.1.tgz",
|
||||
@@ -15267,15 +15142,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-zip-archive": {
|
||||
"version": "6.0.9",
|
||||
"resolved": "https://registry.npmjs.org/react-native-zip-archive/-/react-native-zip-archive-6.0.9.tgz",
|
||||
"integrity": "sha512-IYf3yHJ7sHzj9ucO3Amx/TtsZcTgHdfj+Dar8p1e32E+wBsn7mv0PDp2X/oCLGci0nG9i2DBstrnCW74/7NLYQ==",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.6",
|
||||
"react-native": ">=0.60.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native/node_modules/@jest/types": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz",
|
||||
@@ -15357,6 +15223,130 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native/node_modules/metro-babel-transformer": {
|
||||
"version": "0.70.3",
|
||||
"resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.70.3.tgz",
|
||||
"integrity": "sha512-bWhZRMn+mIOR/s3BDpFevWScz9sV8FGktVfMlF1eJBLoX24itHDbXvTktKBYi38PWIKcHedh6THSFpJogfuwNA==",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.14.0",
|
||||
"hermes-parser": "0.6.0",
|
||||
"metro-source-map": "0.70.3",
|
||||
"nullthrows": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native/node_modules/metro-react-native-babel-preset": {
|
||||
"version": "0.70.3",
|
||||
"resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.70.3.tgz",
|
||||
"integrity": "sha512-4Nxc1zEiHEu+GTdEMEsHnRgfaBkg8f/Td3+FcQ8NTSvs+xL3LBrQy6N07idWSQZHIdGFf+tTHvRfSIWLD8u8Tg==",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.14.0",
|
||||
"@babel/plugin-proposal-async-generator-functions": "^7.0.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.0.0",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
|
||||
"@babel/plugin-proposal-optional-catch-binding": "^7.0.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/plugin-syntax-export-default-from": "^7.0.0",
|
||||
"@babel/plugin-syntax-flow": "^7.2.0",
|
||||
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0",
|
||||
"@babel/plugin-syntax-optional-chaining": "^7.0.0",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.0.0",
|
||||
"@babel/plugin-transform-async-to-generator": "^7.0.0",
|
||||
"@babel/plugin-transform-block-scoping": "^7.0.0",
|
||||
"@babel/plugin-transform-classes": "^7.0.0",
|
||||
"@babel/plugin-transform-computed-properties": "^7.0.0",
|
||||
"@babel/plugin-transform-destructuring": "^7.0.0",
|
||||
"@babel/plugin-transform-exponentiation-operator": "^7.0.0",
|
||||
"@babel/plugin-transform-flow-strip-types": "^7.0.0",
|
||||
"@babel/plugin-transform-function-name": "^7.0.0",
|
||||
"@babel/plugin-transform-literals": "^7.0.0",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.0.0",
|
||||
"@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0",
|
||||
"@babel/plugin-transform-parameters": "^7.0.0",
|
||||
"@babel/plugin-transform-react-display-name": "^7.0.0",
|
||||
"@babel/plugin-transform-react-jsx": "^7.0.0",
|
||||
"@babel/plugin-transform-react-jsx-self": "^7.0.0",
|
||||
"@babel/plugin-transform-react-jsx-source": "^7.0.0",
|
||||
"@babel/plugin-transform-runtime": "^7.0.0",
|
||||
"@babel/plugin-transform-shorthand-properties": "^7.0.0",
|
||||
"@babel/plugin-transform-spread": "^7.0.0",
|
||||
"@babel/plugin-transform-sticky-regex": "^7.0.0",
|
||||
"@babel/plugin-transform-template-literals": "^7.0.0",
|
||||
"@babel/plugin-transform-typescript": "^7.5.0",
|
||||
"@babel/plugin-transform-unicode-regex": "^7.0.0",
|
||||
"@babel/template": "^7.0.0",
|
||||
"react-refresh": "^0.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native/node_modules/metro-react-native-babel-transformer": {
|
||||
"version": "0.70.3",
|
||||
"resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.70.3.tgz",
|
||||
"integrity": "sha512-WKBU6S/G50j9cfmFM4k4oRYprd8u3qjleD4so1E2zbTNILg+gYla7ZFGCAvi2G0ZcqS2XuGCR375c2hF6VVvwg==",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.14.0",
|
||||
"babel-preset-fbjs": "^3.4.0",
|
||||
"hermes-parser": "0.6.0",
|
||||
"metro-babel-transformer": "0.70.3",
|
||||
"metro-react-native-babel-preset": "0.70.3",
|
||||
"metro-source-map": "0.70.3",
|
||||
"nullthrows": "^1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native/node_modules/metro-runtime": {
|
||||
"version": "0.70.3",
|
||||
"resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.70.3.tgz",
|
||||
"integrity": "sha512-22xU7UdXZacniTIDZgN2EYtmfau2pPyh97Dcs+cWrLcJYgfMKjWBtesnDcUAQy3PHekDYvBdJZkoQUeskYTM+w==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native/node_modules/metro-source-map": {
|
||||
"version": "0.70.3",
|
||||
"resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.70.3.tgz",
|
||||
"integrity": "sha512-zsYtZGrwRbbGEFHtmMqqeCH9K9aTGNVPsurMOWCUeQA3VGyVGXPGtLMC+CdAM9jLpUyg6jw2xh0esxi+tYH7Uw==",
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.14.0",
|
||||
"@babel/types": "^7.0.0",
|
||||
"invariant": "^2.2.4",
|
||||
"metro-symbolicate": "0.70.3",
|
||||
"nullthrows": "^1.1.1",
|
||||
"ob1": "0.70.3",
|
||||
"source-map": "^0.5.6",
|
||||
"vlq": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native/node_modules/metro-symbolicate": {
|
||||
"version": "0.70.3",
|
||||
"resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.70.3.tgz",
|
||||
"integrity": "sha512-JTYkF1dpeDUssQ84juE1ycnhHki2ylJBBdJE1JHtfu5oC+z1ElDbBdPHq90Uvt8HbRov/ZAnxvv7Zy6asS+WCA==",
|
||||
"dependencies": {
|
||||
"invariant": "^2.2.4",
|
||||
"metro-source-map": "0.70.3",
|
||||
"nullthrows": "^1.1.1",
|
||||
"source-map": "^0.5.6",
|
||||
"through2": "^2.0.1",
|
||||
"vlq": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"metro-symbolicate": "src/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native/node_modules/ob1": {
|
||||
"version": "0.70.3",
|
||||
"resolved": "https://registry.npmjs.org/ob1/-/ob1-0.70.3.tgz",
|
||||
"integrity": "sha512-Vy9GGhuXgDRY01QA6kdhToPd8AkLdLpX9GjH5kpqluVqTu70mgOm7tpGoJDZGaNbr9nJlJgnipqHJQRPORixIQ=="
|
||||
},
|
||||
"node_modules/react-native/node_modules/pretty-format": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz",
|
||||
@@ -15384,6 +15374,14 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"node_modules/react-native/node_modules/source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native/node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
|
||||
+1
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "lnreader",
|
||||
"version": "1.1.15",
|
||||
"version": "1.1.17",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"android": "react-native run-android",
|
||||
@@ -16,7 +16,6 @@
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cd-z/react-native-epub-creator": "^2.0.4",
|
||||
"@gorhom/bottom-sheet": "^4.4.5",
|
||||
"@react-native-async-storage/async-storage": "^1.18.1",
|
||||
"@react-native-community/clipboard": "^1.5.1",
|
||||
@@ -62,7 +61,6 @@
|
||||
"react-native-pager-view": "^6.2.0",
|
||||
"react-native-paper": "^5.7.2",
|
||||
"react-native-reanimated": "~2.9.1",
|
||||
"react-native-saf-x": "^2.2.1",
|
||||
"react-native-safe-area-context": "4.3.1",
|
||||
"react-native-screens": "~3.15.0",
|
||||
"react-native-shimmer-placeholder": "^2.0.9",
|
||||
@@ -70,7 +68,6 @@
|
||||
"react-native-tts": "^4.1.0",
|
||||
"react-native-vector-icons": "^9.0.0",
|
||||
"react-native-webview": "11.23.0",
|
||||
"react-native-zip-archive": "^6.0.9",
|
||||
"react-redux": "^7.2.6",
|
||||
"redux-persist": "^6.0.0",
|
||||
"rn-fetch-blob": "^0.12.0",
|
||||
|
||||
@@ -4,6 +4,9 @@ import { StyleSheet, View, Text, Pressable } from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import { coverPlaceholderColor } from '../theme/colors';
|
||||
|
||||
import { getSourceStorage } from '@hooks/useSourceStorage';
|
||||
import { defaultUserAgentString } from '@utils/fetch/fetch';
|
||||
|
||||
import color from 'color';
|
||||
|
||||
const ListView = ({
|
||||
@@ -16,6 +19,7 @@ const ListView = ({
|
||||
isSelected,
|
||||
onLongPress,
|
||||
}) => {
|
||||
const { cookies = '' } = getSourceStorage(item.sourceId);
|
||||
return (
|
||||
<Pressable
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
@@ -31,6 +35,7 @@ const ListView = ({
|
||||
<FastImage
|
||||
source={{
|
||||
uri: item.novelCover,
|
||||
headers: { Cookie: cookies, 'User-Agent': defaultUserAgentString },
|
||||
}}
|
||||
style={[styles.extensionIcon, inLibraryBadge && { opacity: 0.5 }]}
|
||||
/>
|
||||
|
||||
@@ -20,6 +20,9 @@ import { SourceNovelItem } from 'src/sources/types';
|
||||
import { ThemeColors } from '@theme/types';
|
||||
import SourceScreenSkeletonLoading from '@screens/browse/loadingAnimation/SourceScreenSkeletonLoading';
|
||||
|
||||
import { getSourceStorage } from '@hooks/useSourceStorage';
|
||||
import { defaultUserAgentString } from '@utils/fetch/fetch';
|
||||
|
||||
interface NovelCoverProps {
|
||||
item: LibraryNovelInfo;
|
||||
onPress: () => void;
|
||||
@@ -65,6 +68,8 @@ const NovelCover: React.FC<NovelCoverProps> = ({
|
||||
|
||||
const uri = item.novelCover;
|
||||
|
||||
const { cookies = '' } = getSourceStorage(item.sourceId);
|
||||
|
||||
return item.sourceId < 0 ? (
|
||||
<SourceScreenSkeletonLoading theme={theme} completeRow={item.sourceId} />
|
||||
) : displayMode !== DisplayModes.List ? (
|
||||
@@ -75,9 +80,9 @@ const NovelCover: React.FC<NovelCoverProps> = ({
|
||||
},
|
||||
styles.standardNovelCover,
|
||||
isSelected && {
|
||||
backgroundColor: theme.primary,
|
||||
} &&
|
||||
styles.selectedNovelCover,
|
||||
backgroundColor: theme.primary,
|
||||
opacity: 0.8,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Pressable
|
||||
@@ -108,7 +113,10 @@ const NovelCover: React.FC<NovelCoverProps> = ({
|
||||
)}
|
||||
</View>
|
||||
<FastImage
|
||||
source={{ uri }}
|
||||
source={{
|
||||
uri,
|
||||
headers: { Cookie: cookies, 'User-Agent': defaultUserAgentString },
|
||||
}}
|
||||
style={[
|
||||
{
|
||||
height: coverHeight,
|
||||
@@ -348,9 +356,6 @@ const styles = StyleSheet.create({
|
||||
overflow: 'hidden',
|
||||
margin: 2,
|
||||
},
|
||||
selectedNovelCover: {
|
||||
opacity: 0.8,
|
||||
},
|
||||
compactTitle: {
|
||||
color: 'rgba(255,255,255,1)',
|
||||
textShadowColor: 'rgba(0, 0, 0, 0.75)',
|
||||
|
||||
@@ -17,6 +17,7 @@ export type NovelListRenderItem = ListRenderItem<
|
||||
|
||||
interface NovelListProps
|
||||
extends FlatListProps<LibraryNovelInfo | NovelInfo | SourceNovelItem> {
|
||||
isFetchingNextPage?: boolean;
|
||||
inSource?: boolean;
|
||||
}
|
||||
|
||||
@@ -46,7 +47,7 @@ const NovelList: React.FC<NovelListProps> = props => {
|
||||
);
|
||||
var extendedNovelList: Array<SourceNovelItem | LibraryNovelInfo> =
|
||||
props?.data as Array<LibraryNovelInfo>;
|
||||
if (props.data?.length && props.inSource) {
|
||||
if (props.data?.length && props.inSource && props.isFetchingNextPage) {
|
||||
let remainder = numColumns - (props.data?.length % numColumns);
|
||||
let extension = [];
|
||||
if (remainder !== 0 && remainder !== numColumns) {
|
||||
|
||||
@@ -25,7 +25,7 @@ const SwitchSetting: React.FC<SwitchProps> = ({
|
||||
paddingHorizontal: 16,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
paddingVertical: 12,
|
||||
paddingVertical: description ? 16 : 12,
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
@@ -39,6 +39,7 @@ const SwitchSetting: React.FC<SwitchProps> = ({
|
||||
},
|
||||
text: {
|
||||
fontSize: 16,
|
||||
flex: 1,
|
||||
textAlignVertical: 'center',
|
||||
},
|
||||
switch: { marginLeft: 8 },
|
||||
|
||||
@@ -31,7 +31,7 @@ export const useGithubUpdateChecker = () => {
|
||||
const newSemVer = newVersion.split('.');
|
||||
const oldSemVer = currentVersion.split('.');
|
||||
|
||||
oldSemVer?.map((version, index) => {
|
||||
return oldSemVer?.some((version, index) => {
|
||||
if (newSemVer[index] > version) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -130,6 +130,7 @@ const BrowseSourceScreen: React.FC<BrowseSourceScreenProps> = ({ route }) => {
|
||||
) : (
|
||||
<NovelList
|
||||
data={novelList}
|
||||
isFetchingNextPage={hasNextPage && !searchText}
|
||||
inSource
|
||||
renderItem={({ item }) => {
|
||||
const inLibrary = novelInLibrary(item.novelUrl);
|
||||
|
||||
@@ -8,6 +8,9 @@ import { SourceNovelItem } from '../../../sources/types';
|
||||
import { ThemeColors } from '../../../theme/types';
|
||||
import { getString } from '@strings/translations';
|
||||
|
||||
import { getSourceStorage } from '@hooks/useSourceStorage';
|
||||
import { defaultUserAgentString } from '@utils/fetch/fetch';
|
||||
|
||||
interface Props {
|
||||
novel: SourceNovelItem;
|
||||
navigateToNovel: (novel: SourceNovelItem) => void;
|
||||
@@ -32,6 +35,8 @@ const GlobalSearchNovelItem: React.FC<Props> = ({
|
||||
[inLibrary],
|
||||
);
|
||||
|
||||
const { cookies = '' } = getSourceStorage(novel.sourceId);
|
||||
|
||||
return (
|
||||
<View style={styles.novelItem}>
|
||||
<Pressable
|
||||
@@ -41,7 +46,10 @@ const GlobalSearchNovelItem: React.FC<Props> = ({
|
||||
onLongPress={onLongPress}
|
||||
>
|
||||
<FastImage
|
||||
source={{ uri: novel.novelCover }}
|
||||
source={{
|
||||
uri: novel.novelCover,
|
||||
headers: { Cookie: cookies, 'User-Agent': defaultUserAgentString },
|
||||
}}
|
||||
style={[styles.novelCover, { ...novelItemDimensions }]}
|
||||
/>
|
||||
{inLibrary ? (
|
||||
|
||||
@@ -16,6 +16,9 @@ import {
|
||||
openNovelProps,
|
||||
} from '@utils/handleNavigateParams';
|
||||
|
||||
import { getSourceStorage } from '@hooks/useSourceStorage';
|
||||
import { defaultUserAgentString } from '@utils/fetch/fetch';
|
||||
|
||||
interface HistoryCardProps {
|
||||
history: History;
|
||||
handleNavigateToChapter: (
|
||||
@@ -56,6 +59,8 @@ const HistoryCard: React.FC<HistoryCardProps> = ({
|
||||
[chapterName, historyTimeRead],
|
||||
);
|
||||
|
||||
const { cookies = '' } = getSourceStorage(sourceId);
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={styles.container}
|
||||
@@ -85,7 +90,16 @@ const HistoryCard: React.FC<HistoryCardProps> = ({
|
||||
})
|
||||
}
|
||||
>
|
||||
<FastImage source={{ uri: novelCover }} style={styles.cover} />
|
||||
<FastImage
|
||||
source={{
|
||||
uri: novelCover,
|
||||
headers: {
|
||||
Cookie: cookies,
|
||||
'User-Agent': defaultUserAgentString,
|
||||
},
|
||||
}}
|
||||
style={styles.cover}
|
||||
/>
|
||||
</Pressable>
|
||||
<View style={styles.detailsContainer}>
|
||||
<Text
|
||||
|
||||
@@ -49,18 +49,17 @@ import NovelInfoHeader from './components/Info/NovelInfoHeader';
|
||||
import NovelBottomSheet from './components/NovelBottomSheet';
|
||||
import TrackSheet from './components/Tracker/TrackSheet';
|
||||
import { Row } from '../../components/Common';
|
||||
import JumpToChapterModal from './components/Modal/JumpToChapterModal';
|
||||
import JumpToChapterModal from './components/JumpToChapterModal';
|
||||
import { Actionbar } from '../../components/Actionbar/Actionbar';
|
||||
import EditInfoModal from './components/Modal/EditInfoModal';
|
||||
import EditInfoModal from './components/EditInfoModal';
|
||||
import { pickCustomNovelCover } from '../../database/queries/NovelQueries';
|
||||
import DownloadCustomChapterModal from './components/Modal/DownloadCustomChapterModal';
|
||||
import DownloadCustomChapterModal from './components/DownloadCustomChapterModal';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import useBoolean from '@hooks/useBoolean';
|
||||
import { useCategorySettings } from '@hooks/useSettings';
|
||||
import { openChapter } from '../../utils/handleNavigateParams';
|
||||
import NovelScreenLoading from './components/LoadingAnimation/NovelScreenLoading';
|
||||
import { useTrackerReducer } from '@redux/hooks';
|
||||
import EpubIconButton from './components/EpubIconButton';
|
||||
|
||||
const Novel = ({ route, navigation }) => {
|
||||
const item = route.params;
|
||||
@@ -536,11 +535,7 @@ const Novel = ({ route, navigation }) => {
|
||||
}
|
||||
/>
|
||||
</Menu>
|
||||
<EpubIconButton
|
||||
theme={theme}
|
||||
style={{ marginTop: StatusBar.currentHeight + 8 }}
|
||||
novel={novel}
|
||||
/>
|
||||
|
||||
<Menu
|
||||
visible={extraMenu}
|
||||
onDismiss={() => showExtraMenu(false)}
|
||||
|
||||
@@ -74,10 +74,10 @@ const ChapterItem: React.FC<ChapterItemProps> = ({
|
||||
onLongPress={() => onSelectLongPress?.(chapter)}
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
>
|
||||
{left && <View style={styles.flex}>{left}</View>}
|
||||
{left}
|
||||
<Row style={styles.row}>
|
||||
{!!bookmark && <ChapterBookmarkButton theme={theme} />}
|
||||
<View style={styles.textContainer}>
|
||||
<View>
|
||||
{isUpdateCard && (
|
||||
<Text
|
||||
style={[
|
||||
@@ -132,18 +132,16 @@ const ChapterItem: React.FC<ChapterItemProps> = ({
|
||||
</View>
|
||||
</View>
|
||||
</Row>
|
||||
<View style={styles.flex}>
|
||||
<DownloadButton
|
||||
downloadQueue={downloadQueue}
|
||||
chapter={chapter}
|
||||
theme={theme}
|
||||
deleteChapter={deleteChapter}
|
||||
downloadChapter={downloadChapter}
|
||||
hideDeleteChapterMenu={hideDeleteChapterMenu}
|
||||
showDeleteChapterMenu={showDeleteChapterMenu}
|
||||
deleteChapterMenuVisible={deleteChapterMenuVisible}
|
||||
/>
|
||||
</View>
|
||||
<DownloadButton
|
||||
downloadQueue={downloadQueue}
|
||||
chapter={chapter}
|
||||
theme={theme}
|
||||
deleteChapter={deleteChapter}
|
||||
downloadChapter={downloadChapter}
|
||||
hideDeleteChapterMenu={hideDeleteChapterMenu}
|
||||
showDeleteChapterMenu={showDeleteChapterMenu}
|
||||
deleteChapterMenuVisible={deleteChapterMenuVisible}
|
||||
/>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
@@ -158,28 +156,13 @@ const styles = StyleSheet.create({
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
columnGab: 8,
|
||||
},
|
||||
text: {
|
||||
fontSize: 12,
|
||||
},
|
||||
textRow: {
|
||||
flexDirection: 'row',
|
||||
marginTop: 5,
|
||||
},
|
||||
flex: {
|
||||
flexBasis: 'auto',
|
||||
flexShrink: 9,
|
||||
},
|
||||
textContainer: {
|
||||
flex: 1,
|
||||
marginVertical: '10%',
|
||||
height: '80%',
|
||||
justifyContent: 'space-evenly',
|
||||
},
|
||||
row: {
|
||||
flexBasis: 10,
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
height: '100%',
|
||||
},
|
||||
row: { flex: 1, overflow: 'hidden' },
|
||||
});
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
||||
import { StyleSheet, Text, View, TextInput } from 'react-native';
|
||||
|
||||
import { Button, IconButton, Modal, Portal } from 'react-native-paper';
|
||||
import { downloadAllChaptersAction } from '../../../../redux/novel/novel.actions';
|
||||
import { downloadAllChaptersAction } from '../../../redux/novel/novel.actions';
|
||||
|
||||
const DownloadCustomChapterModal = ({
|
||||
theme,
|
||||
+2
-2
@@ -10,8 +10,8 @@ import {
|
||||
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { Modal, Portal, TextInput } from 'react-native-paper';
|
||||
import { setNovel } from '../../../../redux/novel/novel.actions';
|
||||
import { updateNovelInfo } from '../../../../database/queries/NovelQueries';
|
||||
import { setNovel } from '../../../redux/novel/novel.actions';
|
||||
import { updateNovelInfo } from '../../../database/queries/NovelQueries';
|
||||
|
||||
import { getString } from '@strings/translations';
|
||||
import { Button } from '@components';
|
||||
@@ -1,145 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import useBoolean from '@hooks/useBoolean';
|
||||
import { IconButton, Portal } from 'react-native-paper';
|
||||
import ChooseEpubLocationModal from './Modal/ChooseEpubLocationModal';
|
||||
import { StatusBar, StyleProp } from 'react-native';
|
||||
import { IconProps } from 'react-native-paper/lib/typescript/src/components/MaterialCommunityIcon';
|
||||
import { ThemeColors } from '@theme/types';
|
||||
|
||||
import EpubBuilder from '@cd-z/react-native-epub-creator';
|
||||
import { ChapterItem, LibraryNovelInfo } from '@database/types';
|
||||
import { showToast } from '@hooks/showToast';
|
||||
import { getChapterFromDb } from '@database/queries/DownloadQueries';
|
||||
import { useReaderSettings } from '@redux/hooks';
|
||||
import { useSettings } from '@hooks/reduxHooks';
|
||||
|
||||
interface extendedNovelInfo extends LibraryNovelInfo {
|
||||
chapters: ChapterItem[];
|
||||
}
|
||||
interface EpubIconButtonProps {
|
||||
theme: ThemeColors;
|
||||
style: StyleProp<IconProps>;
|
||||
novel: extendedNovelInfo;
|
||||
}
|
||||
|
||||
const EpubIconButton: React.FC<EpubIconButtonProps> = ({
|
||||
theme,
|
||||
style,
|
||||
novel,
|
||||
}) => {
|
||||
const chooseEpubLocationModal = useBoolean(false);
|
||||
const readerSettings = useReaderSettings();
|
||||
const {
|
||||
epubUseAppTheme = false,
|
||||
epubUseCustomCSS = false,
|
||||
epubUseCustomJS = false,
|
||||
} = useSettings();
|
||||
|
||||
const epubStyle = `${
|
||||
epubUseAppTheme
|
||||
? `html {
|
||||
scroll-behavior: smooth;
|
||||
overflow-x: hidden;
|
||||
padding-top: ${StatusBar.currentHeight};
|
||||
word-wrap: break-word;
|
||||
}
|
||||
body {
|
||||
padding-left: ${readerSettings.padding}%;
|
||||
padding-right: ${readerSettings.padding}%;
|
||||
padding-bottom: 40px;
|
||||
font-size: ${readerSettings.textSize}px;
|
||||
color: ${readerSettings.textColor};
|
||||
text-align: ${readerSettings.textAlign};
|
||||
line-height: ${readerSettings.lineHeight};
|
||||
font-family: "${readerSettings.fontFamily}";
|
||||
background-color: "${readerSettings.theme}";
|
||||
}
|
||||
hr {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
a {
|
||||
color: ${theme.primary};
|
||||
}
|
||||
img {
|
||||
display: block;
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
}`
|
||||
: ''
|
||||
}
|
||||
${
|
||||
epubUseCustomCSS
|
||||
? readerSettings.customCSS
|
||||
.replace(RegExp(`\#sourceId-${novel.sourceId}\\s*\\{`, 'g'), 'body {')
|
||||
.replace(RegExp(`\#sourceId-${novel.sourceId}[^\.\#A-Z]*`, 'gi'), '')
|
||||
: ''
|
||||
}`;
|
||||
|
||||
const epubJS = `let novelName = "${novel.novelName}";
|
||||
let chapterName = "";
|
||||
let sourceId =${novel.sourceId};
|
||||
let chapterId ="";
|
||||
let novelId =${novel.novelId};
|
||||
let html = document.querySelector("chapter").innerHTML;
|
||||
${readerSettings.customJS}
|
||||
`;
|
||||
|
||||
const createEpub = async (uri: string) => {
|
||||
var epub = new EpubBuilder(
|
||||
{
|
||||
title: novel.novelName,
|
||||
fileName: novel.novelName.replace(/\s/g, ''),
|
||||
language: 'en',
|
||||
cover: novel.novelCover,
|
||||
description: novel.novelSummary,
|
||||
author: novel.author,
|
||||
bookId: novel.novelId.toString(),
|
||||
stylesheet: epubStyle || undefined,
|
||||
js: epubUseCustomJS ? epubJS : undefined,
|
||||
},
|
||||
uri,
|
||||
);
|
||||
try {
|
||||
// save and create the .epub file
|
||||
await epub.prepare();
|
||||
for (let i = 0; i < novel.chapters.length; i++) {
|
||||
const chapter = novel.chapters[i];
|
||||
if (chapter.downloaded) {
|
||||
const downloaded = await getChapterFromDb(chapter.chapterId);
|
||||
|
||||
await epub.addChapter({
|
||||
title: downloaded.chapterName?.trim() ?? 'Chapter ' + i,
|
||||
fileName: 'Chapter' + i,
|
||||
htmlBody: `<chapter data-novel-id='${chapter.novelId}' data-chapter-id='${chapter.chapterId}'>${downloaded.chapterText}</chapter>`,
|
||||
});
|
||||
}
|
||||
}
|
||||
var epubFilePath = await epub.save();
|
||||
showToast('Epub file saved at: ' + epubFilePath);
|
||||
} catch (error) {
|
||||
showToast('Cannot create because: ' + error);
|
||||
await epub.discardChanges();
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
icon="book-arrow-down-outline"
|
||||
iconColor={theme.onBackground}
|
||||
size={21}
|
||||
style={style}
|
||||
onPress={chooseEpubLocationModal.setTrue}
|
||||
/>
|
||||
<Portal>
|
||||
<ChooseEpubLocationModal
|
||||
modalVisible={chooseEpubLocationModal.value}
|
||||
hideModal={chooseEpubLocationModal.setFalse}
|
||||
onSubmit={createEpub}
|
||||
/>
|
||||
</Portal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default EpubIconButton;
|
||||
@@ -11,6 +11,9 @@ import { followNovelAction } from '../../../../redux/novel/novel.actions';
|
||||
import { useSettings } from '../../../../hooks/reduxHooks';
|
||||
import { showToast } from '../../../../hooks/showToast';
|
||||
|
||||
import { getSourceStorage } from '@hooks/useSourceStorage';
|
||||
import { defaultUserAgentString } from '@utils/fetch/fetch';
|
||||
|
||||
import {
|
||||
CoverImage,
|
||||
NovelAuthor,
|
||||
@@ -54,16 +57,30 @@ const NovelInfoHeader = ({
|
||||
[],
|
||||
);
|
||||
|
||||
const { cookies = '' } = getSourceStorage(novel.sourceId);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CoverImage
|
||||
source={{ uri: novel.novelCover }}
|
||||
source={{
|
||||
uri: novel.novelCover,
|
||||
headers: {
|
||||
Cookie: cookies,
|
||||
'User-Agent': defaultUserAgentString,
|
||||
},
|
||||
}}
|
||||
theme={theme}
|
||||
hideBackdrop={hideBackdrop}
|
||||
>
|
||||
<NovelInfoContainer>
|
||||
<NovelThumbnail
|
||||
source={{ uri: novel.novelCover }}
|
||||
source={{
|
||||
uri: novel.novelCover,
|
||||
headers: {
|
||||
Cookie: cookies,
|
||||
'User-Agent': defaultUserAgentString,
|
||||
},
|
||||
}}
|
||||
theme={theme}
|
||||
setCustomNovelCover={setCustomNovelCover}
|
||||
/>
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import { getString } from '@strings/translations';
|
||||
import { Button } from '@components';
|
||||
|
||||
import { Modal, TextInput, Text } from 'react-native-paper';
|
||||
import { useTheme } from '@hooks/useTheme';
|
||||
import { useSettings } from '@hooks/reduxHooks';
|
||||
import { openDocumentTree } from 'react-native-saf-x';
|
||||
import SwitchSetting from '@components/Switch/Switch';
|
||||
import useBoolean from '@hooks/useBoolean';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { setAppSettings } from '@redux/settings/settings.actions';
|
||||
|
||||
interface ChooseEpubLocationModalProps {
|
||||
hideModal: () => void;
|
||||
modalVisible: boolean;
|
||||
onSubmit?: (uri: string) => void;
|
||||
dispatchConfig?: boolean;
|
||||
}
|
||||
|
||||
const ChooseEpubLocationModal: React.FC<ChooseEpubLocationModalProps> = ({
|
||||
hideModal,
|
||||
modalVisible,
|
||||
onSubmit: onSubmitProp,
|
||||
dispatchConfig,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const {
|
||||
epubLocation = '',
|
||||
epubUseAppTheme = false,
|
||||
epubUseCustomCSS = false,
|
||||
epubUseCustomJS = false,
|
||||
} = useSettings();
|
||||
const saveConfig = dispatchConfig || epubLocation === '';
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [uri, setUri] = useState(epubLocation);
|
||||
const useAppTheme = useBoolean(epubUseAppTheme);
|
||||
const useCustomCSS = useBoolean(epubUseCustomCSS);
|
||||
const useCustomJS = useBoolean(epubUseCustomJS);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const onDismiss = () => {
|
||||
hideModal();
|
||||
setError('');
|
||||
setUri(epubLocation);
|
||||
};
|
||||
|
||||
const onSubmit = () => {
|
||||
if (saveConfig) {
|
||||
dispatch(setAppSettings('epubLocation', uri));
|
||||
dispatch(setAppSettings('epubUseAppTheme', useAppTheme.value));
|
||||
dispatch(setAppSettings('epubUseCustomCSS', useCustomCSS.value));
|
||||
dispatch(setAppSettings('epubUseCustomJS', useCustomJS.value));
|
||||
}
|
||||
onSubmitProp?.(uri);
|
||||
hideModal();
|
||||
};
|
||||
|
||||
const openFolderPicker = async () => {
|
||||
try {
|
||||
const resultUri = await openDocumentTree(true);
|
||||
if (resultUri) {
|
||||
setUri(resultUri.uri);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={modalVisible}
|
||||
onDismiss={onDismiss}
|
||||
contentContainerStyle={[
|
||||
styles.modalContainer,
|
||||
{ backgroundColor: theme.overlay3 },
|
||||
]}
|
||||
>
|
||||
<View style={styles.modalHeaderCtn}>
|
||||
<Text style={[styles.modalTitle, { color: theme.onSurface }]}>
|
||||
{getString('novelScreen.convertToEpubModal.chooseLocation')}
|
||||
</Text>
|
||||
<View style={styles.row}>
|
||||
<TextInput
|
||||
style={styles.textInput}
|
||||
onChangeText={setUri}
|
||||
value={uri}
|
||||
placeholder={getString(
|
||||
'novelScreen.convertToEpubModal.pathToFolder',
|
||||
)}
|
||||
onSubmitEditing={onSubmit}
|
||||
mode="outlined"
|
||||
theme={{ colors: { ...theme } }}
|
||||
underlineColor={theme.outline}
|
||||
dense
|
||||
error={error ? true : false}
|
||||
right={
|
||||
<TextInput.Icon
|
||||
icon="folder-edit-outline"
|
||||
onPress={openFolderPicker}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.settings}>
|
||||
<SwitchSetting
|
||||
label={getString('novelScreen.convertToEpubModal.useReaderTheme')}
|
||||
value={useAppTheme.value}
|
||||
onPress={useAppTheme.toggle}
|
||||
theme={theme}
|
||||
/>
|
||||
<SwitchSetting
|
||||
label={getString('novelScreen.convertToEpubModal.useCustomCSS')}
|
||||
value={useCustomCSS.value}
|
||||
onPress={useCustomCSS.toggle}
|
||||
theme={theme}
|
||||
/>
|
||||
<SwitchSetting
|
||||
label={getString('novelScreen.convertToEpubModal.useCustomJS')}
|
||||
description={getString(
|
||||
'novelScreen.convertToEpubModal.useCustomJSWarning',
|
||||
)}
|
||||
value={useCustomJS.value}
|
||||
onPress={useCustomJS.toggle}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.modalFooterCtn}>
|
||||
<Button title={getString('common.submit')} onPress={onSubmit} />
|
||||
<Button title={getString('common.cancel')} onPress={hideModal} />
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChooseEpubLocationModal;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
modalContainer: {
|
||||
margin: 30,
|
||||
borderRadius: 32,
|
||||
},
|
||||
settings: {
|
||||
marginTop: 12,
|
||||
paddingLeft: 5,
|
||||
},
|
||||
icon: { marginVertical: 0, marginRight: 0 },
|
||||
textInput: { flex: 1 },
|
||||
modalHeaderCtn: {
|
||||
padding: 20,
|
||||
paddingTop: 32,
|
||||
paddingBottom: 0,
|
||||
},
|
||||
modalFooterCtn: {
|
||||
flexDirection: 'row-reverse',
|
||||
paddingHorizontal: 20,
|
||||
paddingTop: 8,
|
||||
paddingBottom: 20,
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 24,
|
||||
marginBottom: 16,
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
@@ -1,4 +1,10 @@
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
useRef,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
import { Dimensions, NativeModules, NativeEventEmitter } from 'react-native';
|
||||
|
||||
import VolumeButtonListener from './../../utils/volumeButtonListener';
|
||||
@@ -195,15 +201,16 @@ const ChapterContent = ({ route, navigation }) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [chapter]);
|
||||
|
||||
const [ttsStatus, startTts] = useTextToSpeech(
|
||||
htmlToText(chapter?.chapterText).split('\n'),
|
||||
() => {
|
||||
if (!incognitoMode) {
|
||||
dispatch(markChapterReadAction(chapterId, novelId));
|
||||
updateTracker();
|
||||
}
|
||||
},
|
||||
const htmlText = useMemo(
|
||||
() => htmlToText(chapter?.chapterText).split('\n'),
|
||||
[chapter.chapterText],
|
||||
);
|
||||
const [ttsStatus, startTts] = useTextToSpeech(htmlText, () => {
|
||||
if (!incognitoMode) {
|
||||
dispatch(markChapterReadAction(chapterId, novelId));
|
||||
updateTracker();
|
||||
}
|
||||
});
|
||||
|
||||
const scrollTo = useCallback(
|
||||
offsetY => {
|
||||
@@ -268,13 +275,15 @@ const ChapterContent = ({ route, navigation }) => {
|
||||
);
|
||||
|
||||
const hideHeader = useCallback(() => {
|
||||
if (!hidden) {
|
||||
setImmersiveMode();
|
||||
} else {
|
||||
showStatusAndNavBar();
|
||||
}
|
||||
setHidden(!hidden);
|
||||
}, [hidden]);
|
||||
setHidden(h => {
|
||||
if (!h) {
|
||||
setImmersiveMode();
|
||||
} else {
|
||||
showStatusAndNavBar();
|
||||
}
|
||||
return !h;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const navigateToChapterBySwipe = useCallback(
|
||||
name => {
|
||||
@@ -313,18 +322,28 @@ const ChapterContent = ({ route, navigation }) => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const scrollToSavedProgress = () => scrollTo(position?.position);
|
||||
const scrollToSavedProgress = useCallback(
|
||||
() => scrollTo(position?.position),
|
||||
[],
|
||||
);
|
||||
|
||||
const chapterText = sanitizeChapterText(chapter.chapterText, {
|
||||
removeExtraParagraphSpacing,
|
||||
bionicReading,
|
||||
sourceId: sourceId,
|
||||
});
|
||||
const chapterText = useMemo(
|
||||
() =>
|
||||
sanitizeChapterText(chapter.chapterText, {
|
||||
removeExtraParagraphSpacing,
|
||||
bionicReading,
|
||||
sourceId,
|
||||
}),
|
||||
[chapter.chapterText, removeExtraParagraphSpacing, bionicReading, sourceId],
|
||||
);
|
||||
const openDrawer = () => {
|
||||
navigation.openDrawer();
|
||||
hideHeader();
|
||||
};
|
||||
|
||||
const bookmarkChapter = () =>
|
||||
setChapter(prevVal => ({ ...prevVal, bookmark: !prevVal?.bookmark }));
|
||||
|
||||
if (loading) {
|
||||
return <ChapterLoadingScreen />;
|
||||
}
|
||||
@@ -333,9 +352,6 @@ const ChapterContent = ({ route, navigation }) => {
|
||||
return <ErrorScreenV2 error={error} />;
|
||||
}
|
||||
|
||||
const bookmarkChapter = () =>
|
||||
setChapter(prevVal => ({ ...prevVal, bookmark: !prevVal?.bookmark }));
|
||||
|
||||
return (
|
||||
<>
|
||||
<WebViewReader
|
||||
|
||||
@@ -24,6 +24,7 @@ export const sanitizeChapterText = (
|
||||
'em',
|
||||
'b',
|
||||
'a',
|
||||
'center',
|
||||
]),
|
||||
allowedAttributes: {
|
||||
'img': ['src', 'class'],
|
||||
@@ -42,23 +43,22 @@ export const sanitizeChapterText = (
|
||||
text = textVide(text);
|
||||
}
|
||||
|
||||
const loadedCheerio = loadCheerio(text);
|
||||
if (
|
||||
options?.sourceId &&
|
||||
sourceManager(options.sourceId).headers &&
|
||||
loadedCheerio('input[offline]').length === 0
|
||||
) {
|
||||
loadedCheerio('img').each((i, element) => {
|
||||
const src = loadedCheerio(element).attr('src');
|
||||
if (src) {
|
||||
loadedCheerio(element).attr({
|
||||
'src': LoadingImageSrc,
|
||||
'class': 'load-icon',
|
||||
'delayed-src': src,
|
||||
});
|
||||
}
|
||||
});
|
||||
text = loadedCheerio('body').html() || text;
|
||||
if (options?.sourceId && sourceManager(options.sourceId).headers) {
|
||||
// Some documents might take a few seconds to be parsed, only do when necessary
|
||||
const loadedCheerio = loadCheerio(text);
|
||||
if (loadedCheerio('input[offline]').length === 0) {
|
||||
loadedCheerio('img').each((i, element) => {
|
||||
const src = loadedCheerio(element).attr('src');
|
||||
if (src) {
|
||||
loadedCheerio(element).attr({
|
||||
'src': LoadingImageSrc,
|
||||
'class': 'load-icon',
|
||||
'delayed-src': src,
|
||||
});
|
||||
}
|
||||
});
|
||||
text = loadedCheerio('body').html() || text;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
text =
|
||||
|
||||
@@ -25,7 +25,6 @@ import NovelBadgesModal from './modals/NovelBadgesModal';
|
||||
import { RootState } from '@redux/store';
|
||||
import { NavigationState } from '@react-navigation/native';
|
||||
import { getString } from '@strings/translations';
|
||||
import ChooseEpubLocationModal from '@screens/novel/components/Modal/ChooseEpubLocationModal';
|
||||
|
||||
interface GenralSettingsProps {
|
||||
navigation: NavigationState;
|
||||
@@ -105,10 +104,6 @@ const GenralSettings: React.FC<GenralSettingsProps> = ({ navigation }) => {
|
||||
* Chapter Sort Modal
|
||||
*/
|
||||
const defaultChapterSortModal = useBoolean();
|
||||
/**
|
||||
* Convert to EPUB Modal
|
||||
*/
|
||||
const convertToEpubModal = useBoolean();
|
||||
return (
|
||||
<>
|
||||
<Appbar
|
||||
@@ -263,21 +258,6 @@ const GenralSettings: React.FC<GenralSettingsProps> = ({ navigation }) => {
|
||||
theme={theme}
|
||||
/>
|
||||
<List.Divider theme={theme} />
|
||||
|
||||
<List.SubHeader theme={theme}>
|
||||
{getString('moreScreen.settingsScreen.generalSettingsScreen.epub')}
|
||||
</List.SubHeader>
|
||||
<List.Item
|
||||
title={getString(
|
||||
'moreScreen.settingsScreen.generalSettingsScreen.epubLocation',
|
||||
)}
|
||||
description={getString(
|
||||
'moreScreen.settingsScreen.generalSettingsScreen.epubLocationDescription',
|
||||
)}
|
||||
onPress={convertToEpubModal.setTrue}
|
||||
theme={theme}
|
||||
/>
|
||||
<List.Divider theme={theme} />
|
||||
<List.SubHeader theme={theme}>
|
||||
{getString(
|
||||
'moreScreen.settingsScreen.generalSettingsScreen.autoDownload',
|
||||
@@ -342,11 +322,6 @@ const GenralSettings: React.FC<GenralSettingsProps> = ({ navigation }) => {
|
||||
hideNovelSortModal={novelSortModalRef.setFalse}
|
||||
theme={theme}
|
||||
/>
|
||||
<ChooseEpubLocationModal
|
||||
hideModal={convertToEpubModal.setFalse}
|
||||
modalVisible={convertToEpubModal.value}
|
||||
dispatchConfig
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -23,16 +23,24 @@ import { useAppDispatch } from '@redux/hooks';
|
||||
import { useTheme } from '@hooks/useTheme';
|
||||
import { noop } from 'lodash-es';
|
||||
|
||||
import { getSourceStorage } from '@hooks/useSourceStorage';
|
||||
|
||||
const NovelCover = ({
|
||||
uri,
|
||||
navigateToNovel,
|
||||
sourceId,
|
||||
}: {
|
||||
uri: string;
|
||||
navigateToNovel: () => void;
|
||||
sourceId: number;
|
||||
}) => {
|
||||
const { cookies = '' } = getSourceStorage(sourceId);
|
||||
return (
|
||||
<Pressable onPress={navigateToNovel}>
|
||||
<FastImage source={{ uri }} style={styles.cover} />
|
||||
<FastImage
|
||||
source={{ uri, headers: { 'cookie': cookies } }}
|
||||
style={styles.cover}
|
||||
/>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
@@ -105,19 +113,23 @@ const UpdateNovelCard: React.FC<UpdateCardProps> = ({
|
||||
(state: RootState) => state.downloadsReducer,
|
||||
);
|
||||
|
||||
const renderNovelCover = useCallback(
|
||||
() => (
|
||||
<NovelCover
|
||||
navigateToNovel={navigateToNovel}
|
||||
uri={chapterList[0].novelCover}
|
||||
sourceId={chapterList[0].sourceId}
|
||||
/>
|
||||
),
|
||||
[JSON.stringify(chapterList[0])],
|
||||
);
|
||||
|
||||
if (chapterList.length > 1) {
|
||||
return (
|
||||
<List.Accordion
|
||||
title={chapterList[0].novelName}
|
||||
titleStyle={{ fontSize: 14, color: theme.onSurface }}
|
||||
left={() => (
|
||||
<View style={styles.listNovelCover}>
|
||||
<NovelCover
|
||||
navigateToNovel={navigateToNovel}
|
||||
uri={chapterList[0].novelCover}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
left={renderNovelCover}
|
||||
descriptionStyle={{ fontSize: 12 }}
|
||||
theme={{ colors: theme }}
|
||||
style={[styles.container, styles.padding]}
|
||||
@@ -145,6 +157,7 @@ const UpdateNovelCard: React.FC<UpdateCardProps> = ({
|
||||
<NovelCover
|
||||
navigateToNovel={navigateToNovel}
|
||||
uri={chapterList[0].novelCover}
|
||||
sourceId={chapterList[0].sourceId}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
@@ -172,6 +185,7 @@ const UpdateNovelCard: React.FC<UpdateCardProps> = ({
|
||||
<NovelCover
|
||||
navigateToNovel={navigateToNovel}
|
||||
uri={chapterList[0].novelCover}
|
||||
sourceId={chapterList[0].sourceId}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
@@ -198,11 +212,8 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 4,
|
||||
backgroundColor: coverPlaceholderColor,
|
||||
},
|
||||
listNovelCover: {
|
||||
justifyContent: 'center',
|
||||
},
|
||||
novelCover: {
|
||||
marginRight: 16,
|
||||
marginRight: 8,
|
||||
},
|
||||
imageContainer: {
|
||||
flex: 1,
|
||||
|
||||
@@ -4,6 +4,8 @@ import { fetchNovel } from '../Source/source';
|
||||
import { downloadChapter } from '../../database/queries/ChapterQueries';
|
||||
|
||||
import * as SQLite from 'expo-sqlite';
|
||||
import { getNovel } from '@database/queries/NovelQueries';
|
||||
|
||||
const db = SQLite.openDatabase('lnreader.db');
|
||||
|
||||
const updateNovelMetadata = async (novelId, novel) => {
|
||||
@@ -54,8 +56,16 @@ const updateNovel = async (sourceId, novelUrl, novelId, options) => {
|
||||
const { downloadNewChapters, refreshNovelMetadata } = options;
|
||||
|
||||
let novel = await fetchNovel(sourceId, novelUrl);
|
||||
let forceMetdataUpdate = false;
|
||||
|
||||
if (refreshNovelMetadata) {
|
||||
if (novelId === undefined) {
|
||||
// novelId will be undefined if novel is not saved in local library
|
||||
let dbNovel = await getNovel(sourceId, novelUrl);
|
||||
novelId = dbNovel.novelId;
|
||||
forceMetdataUpdate = true;
|
||||
}
|
||||
|
||||
if (refreshNovelMetadata || forceMetdataUpdate) {
|
||||
updateNovelMetadata(novelId, novel);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,368 @@
|
||||
import { fetchHtml } from '@utils/fetch/fetch';
|
||||
import * as cheerio from 'cheerio';
|
||||
|
||||
const sourceId = 165;
|
||||
const sourceName = 'Linovelib';
|
||||
|
||||
const baseUrl = 'https://w.linovelib.com';
|
||||
|
||||
const popularNovels = async page => {
|
||||
const url = `${baseUrl}/top/monthvisit/${page}.html`;
|
||||
const body = await fetchHtml({ url, sourceId });
|
||||
const loadedCheerio = cheerio.load(body);
|
||||
|
||||
const novels = [];
|
||||
|
||||
loadedCheerio('.module-rank-booklist .book-layout').each(function () {
|
||||
let novelUrl = loadedCheerio(this).attr('href');
|
||||
|
||||
if (novelUrl) {
|
||||
const novelName = loadedCheerio(this).find('.book-title').text();
|
||||
const novelCover = loadedCheerio(this)
|
||||
.find('img.book-cover')
|
||||
.attr('data-src');
|
||||
novelUrl = baseUrl + novelUrl;
|
||||
|
||||
const novel = {
|
||||
sourceId,
|
||||
novelUrl,
|
||||
novelName,
|
||||
novelCover,
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
}
|
||||
});
|
||||
|
||||
return { novels };
|
||||
};
|
||||
|
||||
const parseNovelAndChapters = async novelUrl => {
|
||||
const url = novelUrl;
|
||||
const body = await fetchHtml({ url, sourceId });
|
||||
const loadedCheerio = cheerio.load(body);
|
||||
|
||||
const novel = {
|
||||
sourceId,
|
||||
sourceName,
|
||||
url: novelUrl,
|
||||
novelUrl,
|
||||
};
|
||||
|
||||
novel.novelName = loadedCheerio('#bookDetailWrapper .book-title').text();
|
||||
|
||||
novel.novelCover = loadedCheerio('#bookDetailWrapper img.book-cover').attr(
|
||||
'src',
|
||||
);
|
||||
|
||||
novel.summary = loadedCheerio('#bookSummary content').text();
|
||||
|
||||
novel.author = loadedCheerio('#bookDetailWrapper .book-rand-a a').text();
|
||||
|
||||
novel.artist = null;
|
||||
|
||||
// TODO: Need some regex and dirty selector to get it
|
||||
// Need to look into how to translate that message
|
||||
novel.status = null;
|
||||
|
||||
let genres = [];
|
||||
loadedCheerio(
|
||||
'#bookDetailWrapper .book-cell .book-meta:last-child .tag-small.red a',
|
||||
).each(function () {
|
||||
genres.push(loadedCheerio(this).text());
|
||||
});
|
||||
|
||||
if (genres && genres.length > 0) {
|
||||
novel.genres = genres.join(', ');
|
||||
}
|
||||
|
||||
// Table of Content is on a different page than the summary page
|
||||
let chapters = [];
|
||||
|
||||
const idPattern = /\/(\d+)\.html/;
|
||||
const novelId = url.match(idPattern)[1];
|
||||
|
||||
const chaptersUrl = baseUrl + loadedCheerio('#btnReadBook').attr('href');
|
||||
const chaptersBody = await fetchHtml({ url: chaptersUrl, sourceId });
|
||||
|
||||
const chaptersLoadedCheerio = cheerio.load(chaptersBody);
|
||||
|
||||
let volumeName;
|
||||
|
||||
chaptersLoadedCheerio('#volumes .chapter-li').each(function () {
|
||||
if (chaptersLoadedCheerio(this).hasClass('chapter-bar')) {
|
||||
volumeName = chaptersLoadedCheerio(this).text();
|
||||
} else {
|
||||
const urlPart = chaptersLoadedCheerio(this)
|
||||
.find('.chapter-li-a')
|
||||
.attr('href');
|
||||
const chapterIdMatch = urlPart.match(idPattern);
|
||||
|
||||
// Sometimes the href attribute does not contain the url, but javascript:cid(0).
|
||||
// Increment the previous chapter ID should result in the right URL
|
||||
if (chapterIdMatch) {
|
||||
chapterId = chapterIdMatch[1];
|
||||
} else {
|
||||
chapterId++;
|
||||
}
|
||||
|
||||
const chapterUrl = `${baseUrl}/novel/${novelId}/${chapterId}.html`;
|
||||
|
||||
if (chapterUrl) {
|
||||
const chapterName =
|
||||
volumeName +
|
||||
' — ' +
|
||||
chaptersLoadedCheerio(this).find('.chapter-index').text().trim();
|
||||
const releaseDate = null;
|
||||
|
||||
chapters.push({
|
||||
chapterName,
|
||||
releaseDate,
|
||||
chapterUrl,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
novel.chapters = chapters;
|
||||
|
||||
return novel;
|
||||
};
|
||||
|
||||
const parseChapter = async (novelUrl, chapterUrl) => {
|
||||
let chapterName, chapterText, hasNextPage;
|
||||
let pageNumber = 1;
|
||||
|
||||
/*
|
||||
* TODO: Maybe there are other ways to get the translation table
|
||||
* It is embed and encrypted inside readtool.js
|
||||
*/
|
||||
const mapping_dict = {
|
||||
'“': '「',
|
||||
'’': '』',
|
||||
'': '是',
|
||||
'': '不',
|
||||
'': '他',
|
||||
'': '个',
|
||||
'': '来',
|
||||
'': '大',
|
||||
'': '子',
|
||||
'': '说',
|
||||
'': '年',
|
||||
'': '那',
|
||||
'': '她',
|
||||
'': '得',
|
||||
'': '自',
|
||||
'': '家',
|
||||
'': '而',
|
||||
'': '去',
|
||||
'': '小',
|
||||
'': '于',
|
||||
'': '么',
|
||||
'': '好',
|
||||
'': '发',
|
||||
'': '成',
|
||||
'': '事',
|
||||
'': '用',
|
||||
'': '道',
|
||||
'': '种',
|
||||
'': '乳',
|
||||
'': '茎',
|
||||
'': '肉',
|
||||
'': '胸',
|
||||
'': '淫',
|
||||
'': '射',
|
||||
'': '骚',
|
||||
'”': '」',
|
||||
'': '的',
|
||||
'': '了',
|
||||
'': '人',
|
||||
'': '有',
|
||||
'': '上',
|
||||
'': '到',
|
||||
'': '地',
|
||||
'': '中',
|
||||
'': '生',
|
||||
'': '着',
|
||||
'': '和',
|
||||
'': '出',
|
||||
'': '里',
|
||||
'': '以',
|
||||
'': '可',
|
||||
'': '过',
|
||||
'': '能',
|
||||
'': '多',
|
||||
'': '心',
|
||||
'': '之',
|
||||
'': '看',
|
||||
'': '当',
|
||||
'': '只',
|
||||
'': '把',
|
||||
'': '第',
|
||||
'': '想',
|
||||
'': '开',
|
||||
'': '阴',
|
||||
'': '欲',
|
||||
'': '交',
|
||||
'': '私',
|
||||
'': '臀',
|
||||
'': '脱',
|
||||
'': '唇',
|
||||
'‘': '『',
|
||||
'': '一',
|
||||
'': '我',
|
||||
'': '在',
|
||||
'': '这',
|
||||
'': '们',
|
||||
'': '时',
|
||||
'': '为',
|
||||
'': '你',
|
||||
'': '国',
|
||||
'': '就',
|
||||
'': '要',
|
||||
'': '也',
|
||||
'': '后',
|
||||
'': '会',
|
||||
'': '下',
|
||||
'': '天',
|
||||
'': '对',
|
||||
'': '然',
|
||||
'': '学',
|
||||
'': '都',
|
||||
'': '起',
|
||||
'': '没',
|
||||
'': '如',
|
||||
'': '还',
|
||||
'': '样',
|
||||
'': '作',
|
||||
'': '美',
|
||||
'': '液',
|
||||
'': '呻',
|
||||
'': '性',
|
||||
'': '穴',
|
||||
'': '舔',
|
||||
'': '裸',
|
||||
};
|
||||
|
||||
const addPage = async pageCheerio => {
|
||||
let pageText;
|
||||
|
||||
const formatPage = async () => {
|
||||
// Remove JS
|
||||
pageCheerio('#acontent .cgo').remove();
|
||||
|
||||
// Load lazyloaded images
|
||||
pageCheerio('#acontent img.imagecontent').each(function () {
|
||||
// Sometimes images are either in data-src or src
|
||||
const imgSrc =
|
||||
pageCheerio(this).attr('data-src') || pageCheerio(this).attr('src');
|
||||
if (imgSrc) {
|
||||
// The original CDN URL is locked behind a CF-like challenge, switch the URL to bypass that
|
||||
// There are no react-native-url-polyfill lib, can't use URL API
|
||||
const regex = /\/\/.+\.com\//;
|
||||
const imgUrl = imgSrc.replace(regex, '//img.linovelib.com/');
|
||||
// Clean up img element
|
||||
pageCheerio(this)
|
||||
.attr('src', imgUrl)
|
||||
.removeAttr('data-src')
|
||||
.removeClass('lazyload');
|
||||
}
|
||||
});
|
||||
|
||||
// Recover the original character
|
||||
pageText = pageCheerio('#acontent').html();
|
||||
pageText = pageText.replace(/./g, char => mapping_dict[char] || char);
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
await formatPage();
|
||||
chapterName ??=
|
||||
pageCheerio('#atitle + h3').text() +
|
||||
' — ' +
|
||||
pageCheerio('#atitle').text();
|
||||
chapterText += pageText;
|
||||
};
|
||||
|
||||
const loadPage = async url => {
|
||||
const body = await fetchHtml({ url, sourceId });
|
||||
const pageCheerio = cheerio.load(body);
|
||||
addPage(pageCheerio);
|
||||
pageHasNextPage =
|
||||
pageCheerio('#footlink > a[onclick$=ReadParams.url_next;]').text() ===
|
||||
'下一页'
|
||||
? true
|
||||
: false;
|
||||
return { pageCheerio, pageHasNextPage };
|
||||
};
|
||||
|
||||
let url = chapterUrl;
|
||||
do {
|
||||
const page = await loadPage(url);
|
||||
hasNextPage = page.pageHasNextPage;
|
||||
if (hasNextPage === true) {
|
||||
pageNumber++;
|
||||
url = chapterUrl.replace(/\.html/gi, `_${pageNumber}` + '.html');
|
||||
}
|
||||
} while (hasNextPage === true);
|
||||
|
||||
const chapter = {
|
||||
sourceId,
|
||||
novelUrl,
|
||||
chapterUrl,
|
||||
chapterName,
|
||||
chapterText,
|
||||
};
|
||||
|
||||
return chapter;
|
||||
};
|
||||
|
||||
const searchNovels = async searchTerm => {
|
||||
const url = `${baseUrl}/search.html?searchkey=` + encodeURI(searchTerm);
|
||||
const body = await fetchHtml({ url, sourceId });
|
||||
|
||||
const loadedCheerio = cheerio.load(body);
|
||||
|
||||
const novels = [];
|
||||
|
||||
const loadSearchResults = function () {
|
||||
loadedCheerio('.book-ol .book-layout').each(function () {
|
||||
let novelUrl = loadedCheerio(this).attr('href');
|
||||
|
||||
if (novelUrl) {
|
||||
const novelName = loadedCheerio(this).find('.book-title').text();
|
||||
const novelCover = loadedCheerio(this)
|
||||
.find('img.book-cover')
|
||||
.attr('data-src');
|
||||
novelUrl = baseUrl + novelUrl;
|
||||
|
||||
const novel = {
|
||||
url: novelUrl,
|
||||
name: novelName,
|
||||
cover: novelCover,
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const novelResults = loadedCheerio('.book-ol .book-layout');
|
||||
|
||||
if (novelResults.length === 0) {
|
||||
// console.log('Challenge');
|
||||
} else {
|
||||
loadSearchResults();
|
||||
}
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
const LinovelibScraper = {
|
||||
popularNovels,
|
||||
parseNovelAndChapters,
|
||||
parseChapter,
|
||||
searchNovels,
|
||||
};
|
||||
|
||||
export default LinovelibScraper;
|
||||
@@ -111,7 +111,6 @@ const parseNovelAndChapters = async (novelUrl: string) => {
|
||||
|
||||
return novelChapters;
|
||||
};
|
||||
console.log(novelId);
|
||||
|
||||
if (novelId) {
|
||||
novel.chapters = await getChapters(novelId);
|
||||
|
||||
@@ -69,7 +69,6 @@ const parseNovelAndChapters = async novelUrl => {
|
||||
)
|
||||
.text()
|
||||
.trim();
|
||||
console.log(novel.status);
|
||||
|
||||
novel.genre = loadedCheerio(
|
||||
'main > div.container > div > div.col-lg-3.col-md-4 > div:nth-child(4) > div.panel-body > ul',
|
||||
|
||||
@@ -6,7 +6,7 @@ import { fetchHtml } from '@utils/fetch/fetch';
|
||||
const sourceId = 51;
|
||||
const sourceName = 'Ranobes';
|
||||
|
||||
const baseUrl = 'https://ranobes.net';
|
||||
const baseUrl = 'https://ranobes.top';
|
||||
|
||||
const popularNovels = async page => {
|
||||
let url = `${baseUrl}/novels/page/${page}/`;
|
||||
|
||||
@@ -2,6 +2,10 @@ import * as cheerio from 'cheerio';
|
||||
const baseUrl = 'https://hasutl.wordpress.com/';
|
||||
|
||||
const popularNovels = async page => {
|
||||
if (page !== 1) {
|
||||
return { novels: [] };
|
||||
}
|
||||
|
||||
let url = baseUrl + 'light-novels-activas/';
|
||||
|
||||
const result = await fetch(url);
|
||||
@@ -125,8 +129,8 @@ const parseChapter = async (novelUrl, chapterUrl) => {
|
||||
return chapter;
|
||||
};
|
||||
|
||||
const searchNovels = async searchTerm => {
|
||||
const url = `${baseUrl}?s=${searchTerm}&post_type=wp-manga`;
|
||||
const searchNovels = async (searchTerm, page) => {
|
||||
const url = `${baseUrl}page/${page}/?s=${searchTerm}&post_type=wp-manga`;
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
import * as cheerio from 'cheerio';
|
||||
import { fetchHtml } from '@utils/fetch/fetch';
|
||||
import { bypassImages } from '../helpers/cloudflareImagesBypass';
|
||||
import { htmlToText } from '../helpers/htmlToText';
|
||||
|
||||
const baseUrl = 'https://novelasligeras.net';
|
||||
const searchQuery =
|
||||
'/wp-admin/admin-ajax.php?tags=1&sku=&limit=30&category_results=&order=DESC&category_limit=5&order_by=title&product_thumbnails=1&title=1&excerpt=1&content=&categories=1&attributes=1';
|
||||
|
||||
const sourceName = 'NOVA';
|
||||
const sourceId = 166;
|
||||
|
||||
const popularNovels = async page => {
|
||||
return { novels: await searchNovels('', page) };
|
||||
};
|
||||
|
||||
const parseNovelAndChapters = async novelUrl => {
|
||||
const url = baseUrl + novelUrl;
|
||||
|
||||
const body = await fetchHtml({ url });
|
||||
const loadedCheerio = cheerio.load(body);
|
||||
|
||||
let novel = {
|
||||
sourceId,
|
||||
sourceName,
|
||||
url,
|
||||
novelUrl,
|
||||
};
|
||||
|
||||
const novelCoverImg = loadedCheerio('.woocommerce-product-gallery').find(
|
||||
'img',
|
||||
);
|
||||
|
||||
novel.novelName = loadedCheerio('h1').text();
|
||||
novel.novelCover =
|
||||
novelCoverImg.attr('src') || novelCoverImg.attr('data-cfsrc');
|
||||
|
||||
novel.artist = loadedCheerio(
|
||||
'.woocommerce-product-attributes-item--attribute_pa_ilustrador td',
|
||||
).text();
|
||||
novel.author = loadedCheerio(
|
||||
'.woocommerce-product-attributes-item--attribute_pa_escritor td',
|
||||
).text();
|
||||
novel.status = loadedCheerio(
|
||||
'.woocommerce-product-attributes-item--attribute_pa_estado td',
|
||||
).text();
|
||||
|
||||
novel.summary = htmlToText(
|
||||
loadedCheerio('.woocommerce-product-details__short-description').html(),
|
||||
);
|
||||
|
||||
novel.chapters = [];
|
||||
|
||||
loadedCheerio('.vc_row div.vc_column-inner > div.wpb_wrapper').each(
|
||||
function () {
|
||||
const e = loadedCheerio(this);
|
||||
const volume = e.find('.dt-fancy-title').first().text();
|
||||
|
||||
if (!volume.startsWith('Volumen')) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.find('.wpb_tab a').map(function () {
|
||||
const chapterPartName = loadedCheerio(this).text();
|
||||
const chapterUrl = loadedCheerio(this)
|
||||
.attr('href')
|
||||
.replace(baseUrl, '');
|
||||
|
||||
const match = chapterPartName.match(/(Parte \d+) . (.+?): (.+)/);
|
||||
const part = match?.[1];
|
||||
const chapter = match?.[2];
|
||||
const name = match?.[3];
|
||||
|
||||
let chapterName;
|
||||
if (part && chapter) {
|
||||
chapterName = `${volume} - ${chapter} - ${part}: ${name}`;
|
||||
} else {
|
||||
chapterName = `${volume} - ${chapterPartName}`;
|
||||
}
|
||||
|
||||
novel.chapters.push({ chapterName, chapterUrl });
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
return novel;
|
||||
};
|
||||
|
||||
const parseChapter = async (novelUrl, chapterUrl) => {
|
||||
const url = baseUrl + chapterUrl;
|
||||
|
||||
const body = await fetchHtml({ url });
|
||||
const loadedCheerio = cheerio.load(body);
|
||||
|
||||
let chapterName = loadedCheerio('h2')
|
||||
.toArray()
|
||||
.map(e => loadedCheerio(e).text())
|
||||
.join(' ');
|
||||
|
||||
let chapterText;
|
||||
if (body.includes('Nadie entra sin permiso en la Gran Tumba de Nazarick')) {
|
||||
chapterText = loadedCheerio('#content');
|
||||
} else {
|
||||
chapterText = loadedCheerio(
|
||||
'.wpb_text_column.wpb_content_element > .wpb_wrapper',
|
||||
);
|
||||
}
|
||||
|
||||
// Remove ads
|
||||
chapterText.find('center').each(function () {
|
||||
loadedCheerio(this).remove();
|
||||
});
|
||||
|
||||
chapterText.find('*').each(function () {
|
||||
if (/text-align:.center/.test(loadedCheerio(this).attr('style'))) {
|
||||
loadedCheerio(this).replaceWith(
|
||||
`<center>${loadedCheerio(this).html()}</center>`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
chapterText = await bypassImages(loadedCheerio, chapterText, sourceId);
|
||||
|
||||
const chapter = {
|
||||
sourceId,
|
||||
novelUrl,
|
||||
chapterUrl,
|
||||
chapterName,
|
||||
chapterText,
|
||||
};
|
||||
|
||||
return chapter;
|
||||
};
|
||||
|
||||
const searchNovels = async (searchTerm, searchPage = 0) => {
|
||||
let novels = [];
|
||||
|
||||
if (searchPage > 0) {
|
||||
const url = `${baseUrl}/index.php/page/${searchPage}/?s=${searchTerm}&post_type=product&title=1&excerpt=1&content=0&categories=1&attributes=1&tags=1&sku=0&orderby=popularity&ixwps=1`;
|
||||
|
||||
const body = await fetchHtml({ url });
|
||||
const loadedCheerio = cheerio.load(body);
|
||||
|
||||
loadedCheerio('.dt-css-grid')
|
||||
.find('div.wf-cell')
|
||||
.each(function () {
|
||||
const img = loadedCheerio(this).find('img');
|
||||
novels.push({
|
||||
sourceId,
|
||||
novelName: loadedCheerio(this).find('h4.entry-title a').text(),
|
||||
novelCover: img.attr('data-src') || img.attr('data-cfsrc'),
|
||||
novelUrl: loadedCheerio(this)
|
||||
.find('h4.entry-title a')
|
||||
.attr('href')
|
||||
.replace(baseUrl, ''),
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const url = baseUrl + searchQuery;
|
||||
const form = new FormData();
|
||||
|
||||
form.append('action', 'product_search');
|
||||
form.append('product-search', searchPage ?? 1);
|
||||
form.append('product-query', searchTerm);
|
||||
|
||||
const data = await fetchHtml({
|
||||
url,
|
||||
init: {
|
||||
method: 'POST',
|
||||
body: form,
|
||||
},
|
||||
sourceId: sourceId.toString(),
|
||||
});
|
||||
|
||||
novels = JSON.parse(data).map(novel => ({
|
||||
sourceId,
|
||||
novelName: novel.title,
|
||||
novelCover: novel.thumbnail,
|
||||
novelUrl: novel.url.replace(baseUrl, ''),
|
||||
}));
|
||||
}
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
const NOVAScraper = {
|
||||
popularNovels,
|
||||
parseNovelAndChapters,
|
||||
parseChapter,
|
||||
searchNovels,
|
||||
};
|
||||
|
||||
export default NOVAScraper;
|
||||
@@ -1,9 +1,14 @@
|
||||
import * as cheerio from 'cheerio';
|
||||
import { htmlToText } from '../helpers/htmlToText';
|
||||
|
||||
const baseUrl = 'https://novelasligera.com/';
|
||||
const sourceName = 'Novelas Ligera';
|
||||
const sourceId = 26;
|
||||
|
||||
const popularNovels = async page => {
|
||||
if (page > 1) {
|
||||
return { novels: [] };
|
||||
}
|
||||
|
||||
let url = baseUrl;
|
||||
|
||||
const result = await fetch(url);
|
||||
@@ -18,14 +23,14 @@ const popularNovels = async page => {
|
||||
.find('.widget-image-caption.wp-caption-text')
|
||||
.text();
|
||||
if (novelName) {
|
||||
const novelCover = loadedCheerio(this).find('img').attr('src');
|
||||
const novelCover = loadedCheerio(this).find('img').attr('data-lazy-src');
|
||||
|
||||
let novelUrl = loadedCheerio(this).find('a').attr('href');
|
||||
novelUrl = novelUrl.replace(baseUrl, '');
|
||||
novelUrl = novelUrl.replace('novela/', '');
|
||||
|
||||
const novel = {
|
||||
sourceId: 26,
|
||||
sourceId,
|
||||
novelName,
|
||||
novelCover,
|
||||
novelUrl,
|
||||
@@ -41,30 +46,24 @@ const popularNovels = async page => {
|
||||
const parseNovelAndChapters = async novelUrl => {
|
||||
const url = baseUrl + 'novela/' + novelUrl;
|
||||
|
||||
// console.log(url);
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
|
||||
let loadedCheerio = cheerio.load(body);
|
||||
|
||||
let novel = {};
|
||||
|
||||
novel.sourceId = 26;
|
||||
|
||||
novel.sourceName = 'Novelas Ligera';
|
||||
|
||||
novel.url = url;
|
||||
|
||||
novel.novelUrl = novelUrl;
|
||||
let novel = {
|
||||
sourceId,
|
||||
sourceName,
|
||||
url,
|
||||
novelUrl,
|
||||
};
|
||||
|
||||
novel.novelName = loadedCheerio('h1').text();
|
||||
|
||||
novel.novelCover = loadedCheerio('.elementor-widget-container')
|
||||
.find('img')
|
||||
.attr('src');
|
||||
.attr('data-lazy-src');
|
||||
|
||||
loadedCheerio('.elementor-row')
|
||||
loadedCheerio('.elementor-widget-container')
|
||||
.find('p')
|
||||
.each(function () {
|
||||
if (loadedCheerio(this).text().includes('Autor:')) {
|
||||
@@ -83,11 +82,16 @@ const parseNovelAndChapters = async novelUrl => {
|
||||
}
|
||||
});
|
||||
|
||||
novel.artist = null;
|
||||
|
||||
novel.novelSummary = loadedCheerio(
|
||||
'.elementor-text-editor.elementor-clearfix',
|
||||
).text();
|
||||
const summary = loadedCheerio(
|
||||
'div[data-widget_type="text-editor.default"] .elementor-widget-container',
|
||||
).first();
|
||||
summary.find('div.yasr-visitor-votes').remove();
|
||||
novel.summary = summary
|
||||
.children()
|
||||
.toArray()
|
||||
.filter(e => e.childNodes[0]?.type === 'text')
|
||||
.map(e => e.childNodes[0].data)
|
||||
.join('\n\n');
|
||||
|
||||
let novelChapters = [];
|
||||
|
||||
@@ -115,7 +119,6 @@ const parseNovelAndChapters = async novelUrl => {
|
||||
|
||||
const parseChapter = async (novelUrl, chapterUrl) => {
|
||||
const url = baseUrl + 'novela/' + chapterUrl;
|
||||
// console.log(url);
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
@@ -131,7 +134,6 @@ const parseChapter = async (novelUrl, chapterUrl) => {
|
||||
loadedCheerio('.wp-post-navigation').remove();
|
||||
|
||||
let chapterText = loadedCheerio('.entry-content').html();
|
||||
chapterText = htmlToText(chapterText, { preserveNewlines: true });
|
||||
|
||||
const chapter = {
|
||||
sourceId: 26,
|
||||
@@ -146,7 +148,6 @@ const parseChapter = async (novelUrl, chapterUrl) => {
|
||||
|
||||
const searchNovels = async searchTerm => {
|
||||
const url = baseUrl + '?s=' + searchTerm + '&post_type=wp-manga';
|
||||
// console.log(url);
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
@@ -156,7 +157,7 @@ const searchNovels = async searchTerm => {
|
||||
let novels = [];
|
||||
|
||||
loadedCheerio('.inside-article').each(function () {
|
||||
const novelCover = loadedCheerio(this).find('img').attr('src');
|
||||
const novelCover = loadedCheerio(this).find('img').attr('data-lazy-src');
|
||||
let novelUrl = loadedCheerio(this).find('a').attr('href').split('/')[4];
|
||||
|
||||
let novelName;
|
||||
@@ -179,8 +180,6 @@ const searchNovels = async searchTerm => {
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
novels = [{ ...novels[1] }];
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ function getNovelName(y) {
|
||||
}
|
||||
|
||||
const popularNovels = async page => {
|
||||
if (page !== 1) {
|
||||
return { novels: [] };
|
||||
}
|
||||
|
||||
let url = baseUrl + 'p/todas-las-novelas.html';
|
||||
|
||||
let headers = new Headers({
|
||||
|
||||
@@ -2,6 +2,10 @@ import * as cheerio from 'cheerio';
|
||||
const baseUrl = 'https://oasistranslations.wordpress.com/';
|
||||
|
||||
const popularNovels = async page => {
|
||||
if (page !== 1) {
|
||||
return { novels: [] };
|
||||
}
|
||||
|
||||
let url = baseUrl;
|
||||
|
||||
const result = await fetch(url);
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
const baseUrl = 'https://www.skynovels.net/';
|
||||
|
||||
const popularNovels = async page => {
|
||||
if (page !== 1) {
|
||||
return { novels: [] };
|
||||
}
|
||||
|
||||
const url = 'https://api.skynovels.net/api/novels?&q';
|
||||
|
||||
const result = await fetch(url);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as cheerio from 'cheerio';
|
||||
import { defaultCoverUri, Status } from '../helpers/constants';
|
||||
import { fetchHtml } from '@utils/fetch/fetch';
|
||||
|
||||
const sourceId = 23;
|
||||
const sourceName = 'TuNovelaLigera';
|
||||
@@ -7,10 +8,9 @@ const sourceName = 'TuNovelaLigera';
|
||||
const baseUrl = 'https://tunovelaligera.com/';
|
||||
|
||||
const popularNovels = async page => {
|
||||
let url = baseUrl + 'novelas/page/' + page + '/?m_orderby=rating';
|
||||
let url = `${baseUrl}novelas/page/${page}/?m_orderby=rating`;
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
const body = await fetchHtml({ url });
|
||||
|
||||
let loadedCheerio = cheerio.load(body);
|
||||
|
||||
@@ -18,7 +18,9 @@ const popularNovels = async page => {
|
||||
|
||||
loadedCheerio('.page-item-detail').each(function () {
|
||||
const novelName = loadedCheerio(this).find('.h5 > a').text();
|
||||
const novelCover = loadedCheerio(this).find('img').attr('src');
|
||||
const novelCoverImg = loadedCheerio(this).find('img');
|
||||
const novelCover =
|
||||
novelCoverImg.attr('src') || novelCoverImg.attr('data-cfsrc');
|
||||
|
||||
let novelUrl = loadedCheerio(this)
|
||||
.find('.h5 > a')
|
||||
@@ -42,8 +44,7 @@ const popularNovels = async page => {
|
||||
const parseNovelAndChapters = async novelUrl => {
|
||||
const url = `${baseUrl}novelas/${novelUrl}`;
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
const body = await fetchHtml({ url });
|
||||
|
||||
let loadedCheerio = cheerio.load(body);
|
||||
|
||||
@@ -58,10 +59,13 @@ const parseNovelAndChapters = async novelUrl => {
|
||||
|
||||
novel.novelName = loadedCheerio('.post-title > h1').text().trim();
|
||||
|
||||
let novelCover = loadedCheerio('.summary_image > a > img');
|
||||
let novelCover = loadedCheerio('.summary_image img');
|
||||
|
||||
novel.novelCover =
|
||||
novelCover.attr('data-src') || novelCover.attr('src') || defaultCoverUri;
|
||||
novelCover.attr('data-src') ||
|
||||
novelCover.attr('src') ||
|
||||
novelCover.attr('data-cfsrc') ||
|
||||
defaultCoverUri;
|
||||
|
||||
loadedCheerio('.post-content_item').each(function () {
|
||||
const detailName = loadedCheerio(this)
|
||||
@@ -98,14 +102,13 @@ const parseNovelAndChapters = async novelUrl => {
|
||||
formData.append('action', 'manga_get_chapters');
|
||||
formData.append('manga', novelId);
|
||||
|
||||
const data = await fetch(
|
||||
'https://tunovelaligera.com/wp-admin/admin-ajax.php',
|
||||
{
|
||||
const text = await fetchHtml({
|
||||
url: 'https://tunovelaligera.com/wp-admin/admin-ajax.php',
|
||||
init: {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
},
|
||||
);
|
||||
const text = await data.text();
|
||||
});
|
||||
|
||||
loadedCheerio = cheerio.load(text);
|
||||
|
||||
@@ -135,8 +138,7 @@ const parseNovelAndChapters = async novelUrl => {
|
||||
const parseChapter = async (novelUrl, chapterUrl) => {
|
||||
const url = `${baseUrl}novelas/${novelUrl}/${chapterUrl}`;
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
const body = await fetchHtml({ url });
|
||||
|
||||
let loadedCheerio = cheerio.load(body);
|
||||
|
||||
@@ -159,8 +161,7 @@ const parseChapter = async (novelUrl, chapterUrl) => {
|
||||
const searchNovels = async searchTerm => {
|
||||
const url = `${baseUrl}?s=${searchTerm}&post_type=wp-manga`;
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
const body = await fetchHtml({ url });
|
||||
|
||||
let loadedCheerio = cheerio.load(body);
|
||||
|
||||
@@ -168,7 +169,9 @@ const searchNovels = async searchTerm => {
|
||||
|
||||
loadedCheerio('.c-tabs-item__content').each(function () {
|
||||
const novelName = loadedCheerio(this).find('.h4 > a').text();
|
||||
const novelCover = loadedCheerio(this).find('img').attr('src');
|
||||
const novelCoverImg = loadedCheerio(this).find('img');
|
||||
const novelCover =
|
||||
novelCoverImg.attr('src') ?? novelCoverImg.attr('data-cfsrc');
|
||||
|
||||
let novelUrl = loadedCheerio(this).find('.h4 > a').attr('href');
|
||||
novelUrl = novelUrl.replace(`${baseUrl}novelas/`, '');
|
||||
|
||||
+20
-16
@@ -2,6 +2,10 @@ import * as cheerio from 'cheerio';
|
||||
const baseUrl = 'https://yuukitls.com/';
|
||||
|
||||
const popularNovels = async page => {
|
||||
if (page !== 1) {
|
||||
return { novels: [] };
|
||||
}
|
||||
|
||||
let url = baseUrl;
|
||||
|
||||
const result = await fetch(url);
|
||||
@@ -11,25 +15,25 @@ const popularNovels = async page => {
|
||||
|
||||
let novels = [];
|
||||
|
||||
loadedCheerio('.menu-item-2869')
|
||||
.find('.menu-item.menu-item-type-post_type.menu-item-object-post')
|
||||
.each(function () {
|
||||
const novelName = loadedCheerio(this).text();
|
||||
const novelCover = loadedCheerio(this).find('img').attr('src');
|
||||
loadedCheerio(
|
||||
'li.quadmenu-item.quadmenu-item-object-post.quadmenu-item-level-2',
|
||||
).each(function () {
|
||||
const novelName = loadedCheerio(this).find('span.quadmenu-text').text();
|
||||
const novelCover = loadedCheerio(this).find('img').attr('src');
|
||||
|
||||
let novelUrl = loadedCheerio(this).find('a').attr('href');
|
||||
novelUrl = novelUrl.split('/');
|
||||
novelUrl = novelUrl[novelUrl.length - 2] + '/';
|
||||
let novelUrl = loadedCheerio(this).find('a').attr('href');
|
||||
novelUrl = novelUrl.split('/');
|
||||
novelUrl = novelUrl[novelUrl.length - 2] + '/';
|
||||
|
||||
const novel = {
|
||||
sourceId: 28,
|
||||
novelName,
|
||||
novelCover,
|
||||
novelUrl,
|
||||
};
|
||||
const novel = {
|
||||
sourceId: 28,
|
||||
novelName,
|
||||
novelCover,
|
||||
novelUrl,
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return { novels };
|
||||
};
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { getSourceStorage } from '@hooks/useSourceStorage';
|
||||
import { defaultUserAgentString, fetchApi } from '@utils/fetch/fetch';
|
||||
import { Cheerio, CheerioAPI, Element } from 'cheerio';
|
||||
|
||||
export const bypassImages = async (
|
||||
loadedCheerio: CheerioAPI,
|
||||
element: Cheerio<Element>,
|
||||
sourceId: number,
|
||||
): Promise<string> => {
|
||||
const { cookies = '' } = getSourceStorage(sourceId);
|
||||
let promises: Promise<number>[] = [];
|
||||
|
||||
element.find('noscript').each(function () {
|
||||
loadedCheerio(this).remove();
|
||||
});
|
||||
|
||||
element.find('img').each(function () {
|
||||
const attr: any = this.attributes.find(
|
||||
a => a.name.includes('src') && !a.value.includes(', '),
|
||||
);
|
||||
|
||||
if (!attr) {
|
||||
loadedCheerio(this).remove();
|
||||
return;
|
||||
}
|
||||
|
||||
const promise: Promise<number> = new Promise(async r => {
|
||||
const response = await fetchApi({
|
||||
url: attr.value,
|
||||
init: {
|
||||
headers: { Cookie: cookies, 'User-Agent': defaultUserAgentString },
|
||||
},
|
||||
});
|
||||
|
||||
const blob = await response.blob();
|
||||
const encodedImage: string = await new Promise((onSuccess, onError) => {
|
||||
try {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
onSuccess(this.result as string);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
} catch (e) {
|
||||
onError(e);
|
||||
}
|
||||
});
|
||||
|
||||
loadedCheerio(this).replaceWith(`<img src="${encodedImage}"/>`);
|
||||
r(0);
|
||||
});
|
||||
|
||||
promises.push(promise);
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
return element.html()!!;
|
||||
};
|
||||
@@ -18,6 +18,7 @@ import EinharjarProjectScraper from './es/einherjarproject';
|
||||
import TuNovelaLigeraScraper from './es/tunovelaligera';
|
||||
import SkyNovelsScraper from './es/skynovels';
|
||||
import NovelasLigeraScraper from './es/novelasligera';
|
||||
import NOVAScraper from './es/nova';
|
||||
import YuukiTlsScraper from './es/yuukitls';
|
||||
import NovelaWuxiaScraper from './es/novelawuxia';
|
||||
import OasisTranslationsScraper from './es/oasistranslations';
|
||||
@@ -78,6 +79,7 @@ import NovelUpdatesScraper from './en/novelupdates';
|
||||
import RanobesScraper from './en/ranobes';
|
||||
import RanobesruScraper from './ru/ranobes';
|
||||
import YushuboScraper from './ch/yushubo';
|
||||
import LinovelibScraper from './ch/linovelib';
|
||||
import {
|
||||
LiebeSchneeHiverNovelScraper,
|
||||
LightNovelsBrasilScraper,
|
||||
@@ -326,6 +328,8 @@ export const sourceManager = (sourceId: number): Scraper => {
|
||||
162: KodScraper, // @ts-ignore
|
||||
163: NovelsOnlineScraper, // @ts-ignore
|
||||
164: BookRiverScraper, // @ts-ignore
|
||||
165: LinovelibScraper, // @ts-ignore
|
||||
166: NOVAScraper, // @ts-ignore
|
||||
};
|
||||
|
||||
return scrapers[sourceId];
|
||||
|
||||
@@ -281,7 +281,7 @@
|
||||
},
|
||||
{
|
||||
"sourceId": 51,
|
||||
"url": "https://ranobes.net/",
|
||||
"url": "https://ranobes.top/",
|
||||
"sourceName": "Ranobes",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/icons/src/en/ranobes/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
@@ -999,5 +999,19 @@
|
||||
"url": "https://bookriver.ru/",
|
||||
"lang": "Russian",
|
||||
"icon": "https://bookriver.ru/android-chrome-512x512.png"
|
||||
},
|
||||
{
|
||||
"sourceId": 165,
|
||||
"sourceName": "Linovelib",
|
||||
"url": "https://w.linovelib.com",
|
||||
"lang": "Chinese",
|
||||
"icon": "https://w.linovelib.com/images/logo.png"
|
||||
},
|
||||
{
|
||||
"sourceId": 166,
|
||||
"sourceName": "NOVA",
|
||||
"url": "https://novelasligeras.net",
|
||||
"lang": "Spanish",
|
||||
"icon": "https://pbs.twimg.com/profile_images/1659040611507351552/DeLBdFep_400x400.jpg"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export const appVersion = '1.1.15';
|
||||
export const releaseDate = '06/04/23 09:30 PM';
|
||||
export const appVersion = '1.1.17';
|
||||
export const releaseDate = '25/07/23 11:00 AM';
|
||||
|
||||
@@ -131,9 +131,6 @@ export interface StringMap {
|
||||
'moreScreen.settingsScreen.generalSettingsScreen.refreshMetadataDescription': 'string';
|
||||
'moreScreen.settingsScreen.generalSettingsScreen.updateTime': 'string';
|
||||
'moreScreen.settingsScreen.generalSettingsScreen.autoDownload': 'string';
|
||||
'moreScreen.settingsScreen.generalSettingsScreen.epub': 'string';
|
||||
'moreScreen.settingsScreen.generalSettingsScreen.epubLocation': 'string';
|
||||
'moreScreen.settingsScreen.generalSettingsScreen.epubLocationDescription': 'string';
|
||||
'moreScreen.settingsScreen.generalSettingsScreen.downloadNewChapters': 'string';
|
||||
'moreScreen.settingsScreen.generalSettingsScreen.disableHapticFeedback': 'string';
|
||||
'moreScreen.settingsScreen.readerSettings.title': 'string';
|
||||
|
||||
Reference in New Issue
Block a user