More fetch, add urlencode (#903)
* feat: add fetchText * minor: add urlencode
This commit is contained in:
Generated
+21
-1
@@ -54,7 +54,8 @@
|
||||
"react-native-tab-view": "^3.5.2",
|
||||
"react-native-vector-icons": "^9.0.0",
|
||||
"react-native-webview": "11.26.0",
|
||||
"sanitize-html": "^2.7.0"
|
||||
"sanitize-html": "^2.7.0",
|
||||
"urlencode": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
@@ -16603,6 +16604,25 @@
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
||||
"integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="
|
||||
},
|
||||
"node_modules/urlencode": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/urlencode/-/urlencode-2.0.0.tgz",
|
||||
"integrity": "sha512-K4+koEq4II9FqKKdLyMwfVFiWvTLJsdsIihXCprumjlOwpviO44E4hAhLYBLb6CEVTZh9hXXMTQHIT+Hwv5BPw==",
|
||||
"dependencies": {
|
||||
"iconv-lite": "~0.6.3"
|
||||
}
|
||||
},
|
||||
"node_modules/urlencode/node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-latest-callback": {
|
||||
"version": "0.1.9",
|
||||
"resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.1.9.tgz",
|
||||
|
||||
+2
-1
@@ -60,7 +60,8 @@
|
||||
"react-native-tab-view": "^3.5.2",
|
||||
"react-native-vector-icons": "^9.0.0",
|
||||
"react-native-webview": "11.26.0",
|
||||
"sanitize-html": "^2.7.0"
|
||||
"sanitize-html": "^2.7.0",
|
||||
"urlencode": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import { getUserAgent } from '@hooks/persisted/useUserAgent';
|
||||
|
||||
type FetchInit = {
|
||||
headers?: Record<string, string> | Headers;
|
||||
method?: string;
|
||||
body?: FormData | string;
|
||||
[x: string]: string | Record<string, string> | undefined | FormData | Headers;
|
||||
};
|
||||
|
||||
const makeInit = (init?: FetchInit) => {
|
||||
const defaultHeaders = {
|
||||
'User-Agent': getUserAgent(),
|
||||
};
|
||||
if (init?.headers) {
|
||||
if (init.headers instanceof Headers) {
|
||||
if (!init.headers.get('User-Agent') && defaultHeaders['User-Agent']) {
|
||||
init.headers.set('User-Agent', defaultHeaders['User-Agent']);
|
||||
}
|
||||
} else {
|
||||
init.headers = {
|
||||
...defaultHeaders,
|
||||
...init.headers,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
init = {
|
||||
...init,
|
||||
headers: defaultHeaders,
|
||||
};
|
||||
}
|
||||
return init;
|
||||
};
|
||||
|
||||
export const fetchApi = async (
|
||||
url: string,
|
||||
init?: FetchInit,
|
||||
): Promise<Response> => {
|
||||
init = makeInit(init);
|
||||
return await fetch(url, init);
|
||||
};
|
||||
|
||||
const FILE_READER_PREFIX_LENGTH = 'data:application/octet-stream;base64,'
|
||||
.length;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param url
|
||||
* @param init
|
||||
* @returns base64 of file
|
||||
*/
|
||||
export const fetchFile = async (
|
||||
url: string,
|
||||
init?: FetchInit,
|
||||
): Promise<string> => {
|
||||
init = makeInit(init);
|
||||
try {
|
||||
const res = await fetch(url, init);
|
||||
if (!res.ok) {
|
||||
throw new Error();
|
||||
}
|
||||
const blob = await res.blob();
|
||||
return await new Promise((resolve, reject) => {
|
||||
const fr = new FileReader();
|
||||
fr.onloadend = () => {
|
||||
resolve(fr.result.slice(FILE_READER_PREFIX_LENGTH) as string);
|
||||
};
|
||||
fr.onerror = () => reject();
|
||||
fr.onabort = () => reject();
|
||||
fr.readAsDataURL(blob);
|
||||
});
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param url
|
||||
* @param init
|
||||
* @param encoding link: https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder/encoding
|
||||
* @returns plain text
|
||||
*/
|
||||
export const fetchText = async (
|
||||
url: string,
|
||||
init?: FetchInit,
|
||||
encoding?: string,
|
||||
): Promise<string> => {
|
||||
init = makeInit(init);
|
||||
try {
|
||||
const res = await fetch(url, init);
|
||||
if (!res.ok) {
|
||||
throw new Error();
|
||||
}
|
||||
const blob = await res.blob();
|
||||
return await new Promise((resolve, reject) => {
|
||||
const fr = new FileReader();
|
||||
fr.onloadend = () => {
|
||||
resolve(fr.result as string);
|
||||
};
|
||||
fr.onerror = () => reject();
|
||||
fr.onabort = () => reject();
|
||||
fr.readAsText(blob, encoding);
|
||||
});
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
@@ -10,15 +10,17 @@ import dayjs from 'dayjs';
|
||||
import { NovelStatus, Plugin, PluginItem } from './types';
|
||||
import { FilterTypes } from './types/filterTypes';
|
||||
import { parseMadaraDate } from './helpers/parseDate';
|
||||
import { isUrlAbsolute } from '@utils/isAbsoluteUrl';
|
||||
import { fetchApi, fetchFile } from '@utils/fetch/fetch';
|
||||
import { isUrlAbsolute } from './helpers/isAbsoluteUrl';
|
||||
import { fetchApi, fetchFile, fetchText } from './helpers/fetch';
|
||||
import { defaultCover } from './helpers/constants';
|
||||
import { encode, decode } from 'urlencode';
|
||||
|
||||
const packages: Record<string, any> = {
|
||||
'cheerio': { load },
|
||||
'dayjs': dayjs,
|
||||
'urlencode': { encode, decode },
|
||||
'@libs/novelStatus': { NovelStatus },
|
||||
'@libs/fetch': { fetchApi, fetchFile },
|
||||
'@libs/fetch': { fetchApi, fetchFile, fetchText },
|
||||
'@libs/parseMadaraDate': { parseMadaraDate },
|
||||
'@libs/isAbsoluteUrl': { isUrlAbsolute },
|
||||
'@libs/filterInputs': { FilterTypes },
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react';
|
||||
import { Pressable, StyleSheet, View } from 'react-native';
|
||||
import { IconButton } from 'react-native-paper';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { isUrlAbsolute } from '../../../utils/isAbsoluteUrl';
|
||||
import * as WebBrowser from 'expo-web-browser';
|
||||
import color from 'color';
|
||||
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
|
||||
@@ -66,28 +65,24 @@ const ChapterFooter = ({
|
||||
iconColor={theme.onSurface}
|
||||
/>
|
||||
</Pressable>
|
||||
{isUrlAbsolute(chapterUrl) ? (
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={() => WebBrowser.openBrowserAsync(chapterUrl)}
|
||||
>
|
||||
<IconButton icon="earth" size={26} iconColor={theme.onSurface} />
|
||||
</Pressable>
|
||||
) : null}
|
||||
<>
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={() => scrollTo(0)}
|
||||
>
|
||||
<IconButton
|
||||
icon="format-vertical-align-top"
|
||||
size={26}
|
||||
iconColor={theme.onSurface}
|
||||
/>
|
||||
</Pressable>
|
||||
</>
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={() => WebBrowser.openBrowserAsync(chapterUrl)}
|
||||
>
|
||||
<IconButton icon="earth" size={26} iconColor={theme.onSurface} />
|
||||
</Pressable>
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={() => scrollTo(0)}
|
||||
>
|
||||
<IconButton
|
||||
icon="format-vertical-align-top"
|
||||
size={26}
|
||||
iconColor={theme.onSurface}
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
|
||||
@@ -1,67 +1,3 @@
|
||||
import { getUserAgent } from '@hooks/persisted/useUserAgent';
|
||||
|
||||
export const fetchApi = async (
|
||||
url: string,
|
||||
init?: {
|
||||
headers?: Record<string, string> | Headers;
|
||||
[x: string]:
|
||||
| string
|
||||
| Record<string, string>
|
||||
| undefined
|
||||
| FormData
|
||||
| Headers;
|
||||
},
|
||||
): Promise<Response> => {
|
||||
const defaultHeaders = {
|
||||
'User-Agent': getUserAgent(),
|
||||
};
|
||||
if (init?.headers) {
|
||||
if (init.headers instanceof Headers) {
|
||||
if (!init.headers.get('User-Agent') && defaultHeaders['User-Agent']) {
|
||||
init.headers.set('User-Agent', defaultHeaders['User-Agent']);
|
||||
}
|
||||
} else {
|
||||
init.headers = {
|
||||
...defaultHeaders,
|
||||
...init.headers,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
init = {
|
||||
...init,
|
||||
headers: defaultHeaders,
|
||||
};
|
||||
}
|
||||
return await fetch(url, init);
|
||||
};
|
||||
|
||||
const FILE_READER_PREFIX_LENGTH = 'data:application/octet-stream;base64,'
|
||||
.length;
|
||||
|
||||
export const fetchFile = async (url: string, init: any): Promise<string> => {
|
||||
if (!init) {
|
||||
init = {};
|
||||
}
|
||||
try {
|
||||
const res = await fetch(url, init);
|
||||
if (!res.ok) {
|
||||
throw new Error();
|
||||
}
|
||||
const blob = await res.blob();
|
||||
return await new Promise((resolve, reject) => {
|
||||
const fr = new FileReader();
|
||||
fr.onloadend = () => {
|
||||
resolve(fr.result.slice(FILE_READER_PREFIX_LENGTH) as string);
|
||||
};
|
||||
fr.onerror = () => reject();
|
||||
fr.onabort = () => reject();
|
||||
fr.readAsDataURL(blob);
|
||||
});
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchTimeout = async (
|
||||
url: string,
|
||||
init?: any,
|
||||
|
||||
Reference in New Issue
Block a user