diff --git a/package.json b/package.json index 3bfbe20..770e7c2 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@swc/core": "^1.4.2", "@types/chroma-js": "^2.4.0", "@types/lodash": "^4.14.194", + "@types/node": "^20.11.20", "@types/react": "18.0.35", "@types/react-native": "0.70.6", "esbuild": "^0.17.16", diff --git a/packages/bunny-types/def.tt b/packages/bunny-types/def.tt index c2e8a11..432973b 100644 --- a/packages/bunny-types/def.tt +++ b/packages/bunny-types/def.tt @@ -15,6 +15,7 @@ alerts assets } + native plugins themes commands diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6149bed..c1d75e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,6 +25,9 @@ devDependencies: '@types/lodash': specifier: ^4.14.194 version: 4.14.194 + '@types/node': + specifier: ^20.11.20 + version: 20.11.20 '@types/react': specifier: 18.0.35 version: 18.0.35 @@ -396,6 +399,12 @@ packages: resolution: {integrity: sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g==} dev: true + /@types/node@20.11.20: + resolution: {integrity: sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==} + dependencies: + undici-types: 5.26.5 + dev: true + /@types/prop-types@15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} dev: true @@ -502,3 +511,7 @@ packages: engines: {node: '>=14.17'} hasBin: true dev: true + + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + dev: true diff --git a/src/entry.ts b/src/entry.ts index 3eccf77..0340b4d 100644 --- a/src/entry.ts +++ b/src/entry.ts @@ -14,7 +14,7 @@ const init = (async () => { await import(".").then((m) => m.default()); } catch (e) { - const { ClientInfoManager } = require("@lib/native"); + const { ClientInfoManager } = require("@lib/native/modules"); const stack = e instanceof Error ? e.stack : undefined; console.log(stack ?? e?.toString?.() ?? e); diff --git a/src/lib/debug.ts b/src/lib/debug.ts index dbab4f1..2de46a7 100644 --- a/src/lib/debug.ts +++ b/src/lib/debug.ts @@ -1,7 +1,7 @@ import { ReactNative as RN } from "@metro/common"; import { after } from "@lib/patcher"; import { getThemeFromLoader, selectTheme } from "@lib/themes"; -import { ClientInfoManager, DeviceManager, BundleUpdaterManager } from "@lib/native"; +import { ClientInfoManager, DeviceManager, BundleUpdaterManager } from "@lib/native/modules"; import { getAssetIDByName } from "@ui/assets"; import { showToast } from "@ui/toasts"; import settings from "@lib/settings"; diff --git a/src/lib/native/fs.ts b/src/lib/native/fs.ts new file mode 100644 index 0000000..6fa1d5b --- /dev/null +++ b/src/lib/native/fs.ts @@ -0,0 +1,73 @@ +import { FileManager } from "@lib/native/modules"; + +/** + * Remove file from given path, currently no check for any failure + * @param path Path to the file + */ +export async function removeFile(path: string, prefix = "pyoncord/") { + return void await FileManager.removeFile("documents", `${prefix}${path}`); +} + +/** + * Check if the file or directory given by the path exists + * @param path Path to the file + */ +export async function fileExists(path: string, prefix = "pyoncord/") { + return await FileManager.fileExists(`${FileManager.getConstants().DocumentsDirPath}/${prefix}${path}`); +} + +/** + * A wrapper to write to a file to the documents directory + * @param path Path to the file + * @param data String data to write to the file + */ +export async function writeFile(path: string, data: string, prefix = "pyoncord/"): Promise { + if (typeof data !== "string") throw new Error("Argument 'data' must be a string"); + return void await FileManager.writeFile("documents", `${prefix}${path}`, data, "utf8"); +} + +/** + * A wrapper to read a file from the documents directory + * @param path Path to the file + * @param fallback Fallback data to return if the file doesn't exist, and will be written to the file + */ +export async function readFile(path: string, fallback?: string | null, prefix = "pyoncord/"): Promise { + try { + return await FileManager.readFile(`${FileManager.getConstants().DocumentsDirPath}/${prefix}${path}`, "utf8"); + } catch { + if (fallback == null) { + throw new Error(`Errored while reading ${path} doesn't exist`); + } + + await writeFile(path, fallback); + return fallback; + } +} + +export async function downloadFile(url: string, path: string, prefix = "pyoncord/") { + const blob = await fetch(url).then(r => r.blob()); + const dataURL: string | null = await new Promise(r => { + const reader = new FileReader(); + reader.onload = () => r(reader.result as unknown as string); + reader.readAsDataURL(blob); + }); + + let data: string; + if (dataURL == null) { + throw new Error("Failed to convert blob to data URL"); + } else { + const index = dataURL.indexOf("base64,"); + if (index === -1) throw new Error("dataURL does not contain base64"); + data = dataURL.slice(index + 7); + } + + return void await FileManager.writeFile("documents", `${prefix}${path}`, data, "base64"); +} + +// // thanks Rosie, doesnt work though +// export async function downloadFile(url: string, path: string, prefix = "pyoncord/") { +// const arrayBuffer = await fetch(url).then(r => r.arrayBuffer()); +// const data = Buffer.from(arrayBuffer).toString("base64"); + +// return void await FileManager.writeFile("documents", `${prefix}${path}`, data, "base64"); +// } \ No newline at end of file diff --git a/src/lib/native/index.ts b/src/lib/native/index.ts new file mode 100644 index 0000000..e168b1e --- /dev/null +++ b/src/lib/native/index.ts @@ -0,0 +1,2 @@ +export * as fs from "./fs"; +export * as modules from "./modules"; \ No newline at end of file diff --git a/src/lib/native.ts b/src/lib/native/modules.ts similarity index 100% rename from src/lib/native.ts rename to src/lib/native/modules.ts diff --git a/src/lib/storage/backends.ts b/src/lib/storage/backends.ts index ba50cc8..d428ba2 100644 --- a/src/lib/storage/backends.ts +++ b/src/lib/storage/backends.ts @@ -1,4 +1,4 @@ -import { MMKVManager, FileManager } from "@lib/native"; +import { MMKVManager, FileManager } from "@lib/native/modules"; import { ReactNative as RN } from "@metro/common"; export interface StorageBackend { diff --git a/src/lib/themes.ts b/src/lib/themes.ts index 250d56b..287511c 100644 --- a/src/lib/themes.ts +++ b/src/lib/themes.ts @@ -4,7 +4,7 @@ import { find, findByName, findByProps, findByStoreName } from "@metro/filters"; import { after, before, instead } from "@lib/patcher"; import { createFileBackend, createMMKVBackend, createStorage, wrapSync, awaitSyncWrapper } from "@lib/storage"; import logger from "@lib/logger"; -import { ThemeManager } from "./native"; +import { ThemeManager } from "@lib/native/modules"; import { getStoredTheme, getThemeFilePath } from "./loader"; import { Author } from "./types"; diff --git a/src/lib/windowObject.ts b/src/lib/windowObject.ts index 746c9e8..195e70e 100644 --- a/src/lib/windowObject.ts +++ b/src/lib/windowObject.ts @@ -1,6 +1,7 @@ import { createVendettaObject } from "./vendettaObject"; import * as constants from "@lib/constants"; import * as debug from "@lib/debug"; +import * as native from "@lib/native"; import * as plugins from "@lib/plugins"; import * as themes from "@lib/themes"; import * as commands from "@lib/commands"; @@ -36,6 +37,7 @@ function createBunnyObject(unloads: any[]) { assets: { ...assets }, ...color, }, + native: { ...native }, plugins: utils.without({ ...plugins }, "initPlugins", "evalPlugin"), themes: utils.without({ ...themes }, "initThemes"), commands: utils.without({ ...commands }, "patchCommands"), diff --git a/src/ui/safeMode.tsx b/src/ui/safeMode.tsx index 21e34a9..deaa68d 100644 --- a/src/ui/safeMode.tsx +++ b/src/ui/safeMode.tsx @@ -2,7 +2,7 @@ import { ReactNative as RN, stylesheet } from "@metro/common"; import { findByName, findByProps, findByStoreName } from "@metro/filters"; import { after } from "@lib/patcher"; import { toggleSafeMode } from "@lib/debug"; -import { DeviceManager } from "@lib/native"; +import { DeviceManager } from "@lib/native/modules"; import { semanticColors } from "@ui/color"; import { Button, Codeblock, ErrorBoundary as _ErrorBoundary, SafeAreaView } from "@ui/components"; import settings from "@lib/settings"; diff --git a/src/ui/settings/components/PluginCard.tsx b/src/ui/settings/components/PluginCard.tsx index f8d5735..a3dac31 100644 --- a/src/ui/settings/components/PluginCard.tsx +++ b/src/ui/settings/components/PluginCard.tsx @@ -1,6 +1,6 @@ import { NavigationNative, clipboard } from "@metro/common"; import { removePlugin, startPlugin, stopPlugin, getSettings, fetchPlugin, BunnyPlugin } from "@lib/plugins"; -import { MMKVManager } from "@lib/native"; +import { MMKVManager } from "@lib/native/modules"; import { getAssetIDByName } from "@ui/assets"; import { showToast } from "@ui/toasts"; import { showConfirmationAlert } from "@ui/alerts"; diff --git a/src/ui/settings/components/ThemeCard.tsx b/src/ui/settings/components/ThemeCard.tsx index acc0b96..1ab9502 100644 --- a/src/ui/settings/components/ThemeCard.tsx +++ b/src/ui/settings/components/ThemeCard.tsx @@ -1,7 +1,7 @@ import { clipboard } from "@metro/common"; import { Theme, applyTheme, fetchTheme, removeTheme, selectTheme, themes } from "@lib/themes"; import { useProxy } from "@lib/storage"; -import { BundleUpdaterManager } from "@lib/native"; +import { BundleUpdaterManager } from "@lib/native/modules"; import { getAssetIDByName } from "@ui/assets"; import { showConfirmationAlert } from "@ui/alerts"; import { showToast } from "@ui/toasts";