Merge branch 'main' into dev

This commit is contained in:
amsyarasyiq 2024-05-24 19:25:54 +08:00
parent 9f5a41c3e9
commit 28b02ae60a
16 changed files with 386 additions and 245 deletions

View file

@ -26,13 +26,13 @@ jobs:
with:
node-version: 20
- name: Install dependencies
- name: Install pnpm and dependencies
run: |
npm i -g pnpm
pnpm i
pnpm i\
- name: Build
run: pnpm build
run: pnpm build --release-branch=${{ github.ref_name }} --build-minify
# TODO: Check for linting?
@ -49,5 +49,5 @@ jobs:
- name: Purge CDN cache
run: |
curl https://purge.jsdelivr.net/gh/pyoncord/bunny-builds@${{ github.ref_name }}/bunny.js
curl https://purge.jsdelivr.net/gh/pyoncord/bunny-build-infos@${{ github.ref_name }}/bunny.js

168
build.mjs
View file

@ -1,168 +0,0 @@
/* eslint-disable no-restricted-syntax */
// eslint-disable-next-line simple-import-sort/imports
// todo: rewrite aaaaaaaaa
import swc from "@swc/core";
import { execSync } from "child_process";
import esbuild from "esbuild";
import { readFile } from "fs/promises";
import { createServer } from "http";
import { argv } from "process";
// @ts-ignore
const isFlag = (s, l) => argv.slice(2).some(c => c === `-${s}` || c === `--${l}`);
const isRelease = isFlag("r", "release");
const shouldServe = isFlag("s", "serve");
const shouldWatch = isFlag("w", "watch");
const commitHash = execSync("git rev-parse --short HEAD").toString().trim();
try {
const ctx = await esbuild.context({
entryPoints: ["./src/entry.js"],
bundle: true,
minify: isRelease,
format: "iife",
target: "esnext",
supported: {
// Hermes does not actually supports const and let, even though it syntactically
// accepts it, but it's treated just like 'var' and causes issues
"const-and-let": false
},
outfile: "dist/bunny.js",
keepNames: true,
footer: {
js: "//# sourceURL=bunny"
},
define: {
__DEV__: String(!isRelease)
},
loader: { ".png": "dataurl" },
legalComments: "none",
plugins: [
{
name: "runtimeGlobalAlias",
setup: async build => {
const globalMap = {
"react": "globalThis.React",
"react-native": "globalThis.ReactNative"
};
Object.keys(globalMap).forEach(key => {
const filter = new RegExp(`^${key}$`);
build.onResolve({ filter }, args => ({
namespace: "glob-" + key, path: args.path
}));
build.onLoad({ filter, namespace: "glob-" + key }, () => ({
// @ts-ignore
contents: `Object.defineProperty(module, 'exports', { get: () => ${globalMap[key]} })`,
resolveDir: "src",
}));
});
}
},
{
name: "buildLog",
setup: async build => {
build.onStart(() => {
console.clear();
console.log(`Building with commit hash "${commitHash}", isRelease="${isRelease}"`);
});
build.onEnd(result => {
console.log(`Built with ${result.errors?.length} errors!`);
});
}
},
{
name: "swc",
setup(build) {
let timeString = Number(new Date).toString(36);
build.onStart(() => {
timeString = Number(new Date).toString(36);
console.log(`swc plugin: time-string="${timeString}"`);
});
build.onLoad({ filter: /\.[jt]sx?$/ }, async args => {
const result = await swc.transformFile(args.path, {
jsc: {
externalHelpers: true,
transform: {
constModules: {
globals: {
"bunny-build": {
version: `"${isRelease ? commitHash : timeString}"`
}
}
},
react: {
pragma: "__bunny_createElement"
}
},
},
// https://github.com/facebook/hermes/blob/3815fec63d1a6667ca3195160d6e12fee6a0d8d5/doc/Features.md
// https://github.com/facebook/hermes/issues/696#issuecomment-1396235791
env: {
targets: "fully supports es6",
include: [
"transform-arrow-functions",
"transform-block-scoping",
"transform-classes"
],
exclude: [
"transform-parameters",
"transform-async-to-generator",
"transform-exponentiation-operator",
"transform-named-capturing-groups-regex",
"transform-nullish-coalescing-operator",
"transform-object-rest-spread",
"transform-optional-chaining"
]
},
});
return { contents: result.code };
});
}
}
],
external: []
});
if (shouldWatch) {
await ctx.watch();
console.log("Watching...");
}
if (shouldServe) {
await ctx.rebuild();
const server = createServer(async (req, res) => {
try {
if (req.url === "/vendetta.js" || req.url === "/pyoncord.js" || req.url === "/bunny.js") {
await ctx.rebuild();
res.writeHead(200);
res.end(await readFile("./dist/bunny.js"), "utf-8");
} else {
res.writeHead(404);
res.end();
}
} catch (error) {
res.writeHead(500);
res.end();
}
}).listen(4040);
// @ts-ignore
console.log(`Serving on port ${server.address()?.port}, CTRL+C to stop`);
}
if (!shouldServe && !shouldWatch) {
ctx.rebuild();
ctx.dispose();
}
} catch (e) {
console.error("Build failed...", e);
process.exit(1);
}

View file

@ -1,23 +1,16 @@
{
"name": "bunny",
"version": "1.0.0",
"description": "A mod for Discord's mobile apps, fork of Vendetta.",
"private": true,
"author": {
"name": "Pyoncord Team",
"url": "https://github.com/pyoncord"
},
"scripts": {
"dev": "node build.mjs",
"build": "node build.mjs --release",
"serve": "node build.mjs --serve",
"watch": "node build.mjs --watch",
"build": "node scripts/build.mjs",
"serve": "node scripts/serve.mjs",
"serve:adb": "pnpm run serve --adb",
"lint": "eslint ./src --ext .js,.jsx,.ts,.tsx"
},
"keywords": [
"discord",
"android",
"ios",
"react native",
"vendetta",
"bunny"
],
"author": "pylixonly, Team Vendetta",
"license": "BSD-3-Clause",
"devDependencies": {
"@react-native-clipboard/clipboard": "1.10.0",
@ -29,7 +22,7 @@
"@types/node": "^20.11.25",
"@types/react": "~18.2.0",
"@types/react-native": "~0.72.0",
"@types/spark-md5": "^3.0.4",
"@types/yargs-parser": "^21.0.3",
"@typescript-eslint/eslint-plugin": "^7.1.1",
"@typescript-eslint/parser": "^7.1.1",
"@typescript-eslint/typescript-estree": "^7.1.1",
@ -43,9 +36,10 @@
"moment": "2.22.2",
"type-fest": "^4.12.0",
"typescript": "^5.4.5",
"typescript-transform-paths": "^3.4.7"
"yargs-parser": "^21.1.1"
},
"dependencies": {
"chalk": "^5.3.0",
"fuzzysort": "^2.0.4",
"spitroast": "^1.4.4"
},
@ -57,4 +51,4 @@
]
}
}
}
}

38
pnpm-lock.yaml generated
View file

@ -5,6 +5,9 @@ settings:
excludeLinksFromLockfile: false
dependencies:
chalk:
specifier: ^5.3.0
version: 5.3.0
fuzzysort:
specifier: ^2.0.4
version: 2.0.4
@ -40,9 +43,9 @@ devDependencies:
'@types/react-native':
specifier: ~0.72.0
version: 0.72.3
'@types/spark-md5':
specifier: ^3.0.4
version: 3.0.4
'@types/yargs-parser':
specifier: ^21.0.3
version: 21.0.3
'@typescript-eslint/eslint-plugin':
specifier: ^7.1.1
version: 7.1.1(@typescript-eslint/parser@7.1.1)(eslint@8.57.0)(typescript@5.4.5)
@ -82,9 +85,9 @@ devDependencies:
typescript:
specifier: ^5.4.5
version: 5.4.5
typescript-transform-paths:
specifier: ^3.4.7
version: 3.4.7(typescript@5.4.5)
yargs-parser:
specifier: ^21.1.1
version: 21.1.1
packages:
@ -632,8 +635,8 @@ packages:
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
dev: true
/@types/spark-md5@3.0.4:
resolution: {integrity: sha512-qtOaDz+IXiNndPgYb6t1YoutnGvFRtWSNzpVjkAPCfB2UzTyybuD4Tjgs7VgRawum3JnJNRwNQd4N//SvrHg1Q==}
/@types/yargs-parser@21.0.3:
resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==}
dev: true
/@typescript-eslint/eslint-plugin@7.1.1(@typescript-eslint/parser@7.1.1)(eslint@8.57.0)(typescript@5.4.5):
@ -970,6 +973,11 @@ packages:
supports-color: 7.2.0
dev: true
/chalk@5.3.0:
resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
dev: false
/color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@ -2369,15 +2377,6 @@ packages:
possible-typed-array-names: 1.0.0
dev: true
/typescript-transform-paths@3.4.7(typescript@5.4.5):
resolution: {integrity: sha512-1Us1kdkdfKd2unbkBAOV2HHRmbRBYpSuk9nJ7cLD2hP4QmfToiM/VpxNlhJc1eezVwVqSKSBjNSzZsK/fWR/9A==}
peerDependencies:
typescript: '>=3.6.5'
dependencies:
minimatch: 3.1.2
typescript: 5.4.5
dev: true
/typescript@5.4.5:
resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==}
engines: {node: '>=14.17'}
@ -2468,6 +2467,11 @@ packages:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
dev: true
/yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
dev: true
/yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}

29
scripts/adb.mjs Normal file
View file

@ -0,0 +1,29 @@
// @ts-nocheck
import { execSync } from "child_process";
const packageName = process.env.DISCORD_PACKAGE_NAME ?? "com.discord";
export function getPackageName() {
return packageName;
}
export function isADBAvailableAndAppInstalled() {
try {
const out = execSync(`adb shell pm list packages ${packageName}`);
return out.toString().trimEnd() === `package:${packageName}`;
} catch {
return false;
}
}
export async function restartAppFromADB(reversePort) {
if (typeof reversePort === "number") {
await execSync(`adb reverse tcp:${reversePort} tcp:${reversePort}`);
}
await forceStopAppFromADB();
await execSync(`adb shell am start ${packageName}/com.discord.main.MainActivity`);
}
export async function forceStopAppFromADB() {
await execSync(`adb shell am force-stop ${packageName}`);
}

159
scripts/build.mjs Normal file
View file

@ -0,0 +1,159 @@
// @ts-nocheck
/* eslint-disable no-restricted-syntax */
import swc from "@swc/core";
import crypto from "crypto";
import { build } from "esbuild";
import { readFile, writeFile } from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";
import yargs from "yargs-parser";
import { printBuildSuccess } from "./util.mjs";
const args = yargs(process.argv.slice(2));
const {
"release-branch": releaseBranch,
"build-minify": buildMinify
} = args;
let context = null;
const config = {
entryPoints: ["src/entry.ts"],
bundle: true,
outfile: "dist/bunny.js",
format: "iife",
splitting: false,
minify: false,
external: [],
supported: {
// Hermes does not actually supports const and let, even though it syntactically
// accepts it, but it's treated just like 'var' and causes issues
"const-and-let": false
},
footer: {
js: "//# sourceURL=bunny"
},
loader: {
".png": "dataurl"
},
define: {
__DEV__: JSON.stringify(releaseBranch !== "main")
},
legalComments: "none",
plugins: [
{
name: "runtimeGlobalAlias",
setup: async build => {
const globalMap = {
"react": "globalThis.React",
"react-native": "globalThis.ReactNative"
};
Object.keys(globalMap).forEach(key => {
const filter = new RegExp(`^${key}$`);
build.onResolve({ filter }, args => ({
namespace: "glob-" + key, path: args.path
}));
build.onLoad({ filter, namespace: "glob-" + key }, () => ({
// @ts-ignore
contents: `Object.defineProperty(module, 'exports', { get: () => ${globalMap[key]} })`,
resolveDir: "src",
}));
});
}
},
{
name: "swc",
setup(build) {
build.onLoad({ filter: /\.[jt]sx?$/ }, async args => {
const result = await swc.transformFile(args.path, {
jsc: {
externalHelpers: true,
transform: {
constModules: {
globals: {
"bunny-build-info": {
version: `"${context.hash}-${releaseBranch ?? "local"}"`
}
}
},
react: {
pragma: "__bunny_createElement"
}
},
},
// https://github.com/facebook/hermes/blob/3815fec63d1a6667ca3195160d6e12fee6a0d8d5/doc/Features.md
// https://github.com/facebook/hermes/issues/696#issuecomment-1396235791
env: {
targets: "fully supports es6",
include: [
// Pretend that arrow functions are unsupported, since hermes does not support async arrow functions for some reason
"transform-arrow-functions",
"transform-block-scoping",
"transform-classes"
],
exclude: [
"transform-parameters",
"transform-template-literals",
"transform-async-to-generator",
"transform-exponentiation-operator",
"transform-named-capturing-groups-regex",
"transform-nullish-coalescing-operator",
"transform-object-rest-spread",
"transform-optional-chaining"
]
},
});
return { contents: result.code };
});
}
}
]
};
export async function buildBundle() {
context = {
hash: crypto.randomBytes(8).toString("hex").slice(0, 7)
};
const initialStartTime = performance.now();
await build(config);
return {
config,
context,
timeTook: performance.now() - initialStartTime
};
}
const pathToThisFile = path.resolve(fileURLToPath(import.meta.url));
const pathPassedToNode = path.resolve(process.argv[1]);
const isThisFileBeingRunViaCLI = pathToThisFile.includes(pathPassedToNode);
if (isThisFileBeingRunViaCLI) {
const { timeTook } = await buildBundle();
printBuildSuccess(
context.hash,
releaseBranch,
timeTook
);
if (buildMinify) {
const bundleBuffer = await readFile(config.outfile);
let { code } = await swc.minify(
bundleBuffer.toString(),
{
compress: true,
mangle: true,
}
);
code += config.footer.js;
const minFilePath = config.outfile.replace(/\.js$/, ".min.js");
await writeFile(minFilePath, code);
}
}

97
scripts/serve.mjs Normal file
View file

@ -0,0 +1,97 @@
// @ts-nocheck
import chalk from "chalk";
import { readFile } from "fs/promises";
import http from "http";
import os from "os";
import readline from "readline";
import url from "url";
import yargs from "yargs-parser";
import { forceStopAppFromADB, getPackageName, isADBAvailableAndAppInstalled, restartAppFromADB } from "./adb.mjs";
import { buildBundle } from "./build.mjs";
import { printBuildSuccess } from "./util.mjs";
const args = yargs(process.argv.slice(2));
export function serve(options) {
// @ts-ignore
const server = http.createServer(async (req, res) => {
const { pathname } = url.parse(req.url || "", true);
if (pathname?.endsWith(".js")) {
try {
const { config, context, timeTook } = await buildBundle();
printBuildSuccess(
context.hash,
args.production,
timeTook
);
res.writeHead(200, { "Content-Type": "application/javascript" });
res.end(await readFile(config.outfile, "utf-8"));
} catch {
res.writeHead(500);
res.end();
}
} else {
res.writeHead(404);
res.end();
}
}, options);
server.listen(args.port ?? 4040);
console.info(chalk.bold.yellowBright("Serving Bunny bundle, available on:"));
const netInterfaces = os.networkInterfaces();
for (const netinterfaces of Object.values(netInterfaces)) {
for (const details of netinterfaces || []) {
if (details.family !== "IPv4") continue;
const port = chalk.green(server.address()?.port.toString());
console.info(` http://${details.address}:${port}/bundle.js`);
}
}
return server;
}
const server = serve();
console.log("\nPress Q key or Ctrl+C to exit.");
if (args.adb && isADBAvailableAndAppInstalled()) {
const packageName = getPackageName();
console.log(`Press R key to reload Discord ${chalk.bold.blue(`(${packageName})`)}.`);
console.log(`Press S key to force stop Discord ${chalk.bold.blue(`(${packageName})`)}.`);
readline.emitKeypressEvents(process.stdin);
if (process.stdin.isTTY) {
process.stdin.setRawMode(true);
}
process.stdin.on("keypress", (ch, key) => {
if (!key) return;
if (key.name === "q" || key.ctrl && key.name === "c") {
process.exit(0);
}
if (key.name === "r") {
console.info(chalk.yellow(`${chalk.bold("↻ Reloading")} ${packageName}`));
restartAppFromADB(server.port)
.then(() => console.info(chalk.greenBright(`${chalk.bold("✔ Executed")} reload command`)))
.catch(e => console.error(e));
}
if (key.name === "s") {
console.info(chalk.yellow(`${chalk.bold("⎊ Force stopping")} ${packageName}`));
forceStopAppFromADB()
.then(() => console.info(chalk.greenBright(`${chalk.bold("✔ Executed")} force stop command`)))
.catch(e => console.error(e));
}
});
} else if (args.adb) {
console.warn("ADB option enabled but failed to connect to device!");
}

16
scripts/util.mjs Normal file
View file

@ -0,0 +1,16 @@
// @ts-nocheck
import chalk from "chalk";
export function printBuildSuccess(
hash,
branch,
timeTook,
minified
) {
console.info([
chalk.bold.greenBright("✔ Built bundle" + (minified ? " (minified)" : "")),
hash && chalk.bold.blueBright(`(${hash})`),
!branch && chalk.bold.cyanBright("(local)"),
timeTook && chalk.gray(`in ${timeTook.toFixed(3)}ms`)
].filter(Boolean).join(" "));
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -1,16 +1,13 @@
import PyoncordIcon from "@assets/icons/pyoncord.png";
import { Strings } from "@core/i18n";
import { requireAssetIndex } from "@lib/api/assets";
import { isFontSupported, isThemeSupported } from "@lib/api/native/loader";
import { useProxy } from "@lib/api/storage";
import { settings } from "@lib/settings";
import { registerSection } from "@ui/settings";
import { version } from "bunny-build";
import { version } from "bunny-build-info";
// @ts-ignore
// eslint-disable-next-line import-alias/import-alias
import _PyoncordIcon from "../../../assets/icons/pyon64.png";
export const PyoncordIcon = _PyoncordIcon as string;
export { PyoncordIcon };
export default function initSettings() {
registerSection({

View file

@ -1,5 +1,5 @@
import { getFactoryOfProxy } from "@lib/utils/lazy";
import { version } from "bunny-build";
import { version } from "bunny-build-info";
import { instead } from "spitroast";
// @ts-ignore - shut up fr

14
src/global.d.ts vendored
View file

@ -1,12 +1,12 @@
declare global {
var React: typeof import("react");
var SkiaApi: typeof import("@shopify/react-native-skia").Skia;
var React: typeof import("react");
var SkiaApi: typeof import("@shopify/react-native-skia").Skia;
interface Window {
[key: string]: any;
vendetta: any;
bunny: typeof import("@lib");
}
interface Window {
[key: string]: any;
vendetta: any;
bunny: typeof import("@lib");
}
}
export { };

View file

@ -6,7 +6,7 @@ import { getThemeFromLoader, selectTheme, themes } from "@lib/managers/themes";
import { settings } from "@lib/settings";
import { logger } from "@lib/utils/logger";
import { showToast } from "@ui/toasts";
import { version } from "bunny-build";
import { version } from "bunny-build-info";
import { Platform, type PlatformConstants } from "react-native";
export let socket: WebSocket;

7
src/modules.d.ts vendored
View file

@ -1,5 +1,8 @@
declare module "bunny-build" {
declare module "bunny-build-info" {
const version: string;
}
export * from "bunny-build";
declare module "*.png" {
const str: string;
export default str;
}

View file

@ -1,35 +1,45 @@
{
"exclude": ["dist"],
"exclude": [
"dist"
],
"compilerOptions": {
"strict": true,
"paths": {
"@core/*": ["./src/core/*"],
"@lib": ["./src/lib"],
"@lib/*": ["./src/lib/*"],
"@metro": ["./src/metro"],
"@metro/*": ["./src/metro/*"],
"@ui": ["./src/lib/ui"],
"@ui/*": ["./src/lib/ui/*"],
"@types": ["./src/lib/utils/types.ts"],
"bunny-build": ["./src/modules.d.ts"]
"@core/*": [
"./src/core/*"
],
"@lib": [
"./src/lib"
],
"@lib/*": [
"./src/lib/*"
],
"@metro": [
"./src/metro"
],
"@metro/*": [
"./src/metro/*"
],
"@ui": [
"./src/lib/ui"
],
"@ui/*": [
"./src/lib/ui/*"
],
"@types": [
"./src/lib/utils/types.ts"
],
"@assets/*": [
"./src/assets/*"
]
},
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"noEmit": true,
"allowJs": true,
"checkJs": true,
"plugins": [
{ "transform": "typescript-transform-paths", "afterDeclarations": true }
],
"jsx": "react",
"target": "esnext"
}
}
}