Skip to content

Commit

Permalink
Most of the plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
FieryFlames committed Jul 28, 2023
1 parent 7ce57c3 commit feb6f77
Show file tree
Hide file tree
Showing 25 changed files with 931 additions and 89 deletions.
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Decor",
"description": "TODO",
"description": "Custom avatar decorations.",
"authors": [
{
"name": "Fiery",
Expand Down
14 changes: 10 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
{
"private": true,
"scripts": {
"build": "node build.mjs"
"build": "node build.mjs",
"format": "prettier --write \"src/\""
},
"dependencies": {
"@swc/helpers": "^0.4.14"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^24.0.0",
"@rollup/plugin-node-resolve": "^15.0.1",
"@sapphire/prettier-config": "^2.0.0",
"@swc/core": "^1.3.35",
"esbuild": "^0.16.14",
"prettier": "^3.0.0",
"react-native-image-picker": "^5.6.0",
"rollup": "^3.9.1",
"rollup-plugin-esbuild": "^5.0.0",
"vendetta-types": "latest"
"vendetta-types": "^2.4.20",
"zustand": "^4.3.9"
},
"pnpm": {
"peerDependencyRules": {
Expand All @@ -22,5 +27,6 @@
"react-native"
]
}
}
}
},
"prettier": "@sapphire/prettier-config"
}
81 changes: 71 additions & 10 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

83 changes: 54 additions & 29 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,60 @@
import { findByProps, findByStoreName } from "@vendetta/metro";
import { after } from "@vendetta/patcher";
import { BASE_URL } from "./lib/constants";
import { fetchUsers, users } from "./lib/users";
import Settings from "./ui/pages/Settings";
import { storage } from "@vendetta/plugin";
import { findByProps, findByStoreName } from '@vendetta/metro';
import { ReactNative } from '@vendetta/metro/common';
import { after } from '@vendetta/patcher';
import { getUsers, users } from './lib/api';
import { CDN_URL } from './lib/constants';
import Settings from './ui/pages/Settings';
import { unsubscribeFromUserDecorationsStore } from './ui/stores/UserDecorationsStore';
import { storage } from '@vendetta/plugin';

const UserStore = findByStoreName("UserStore");
const ImageResolver = findByProps("getAvatarDecorationURL", "default");
const UserStore = findByStoreName('UserStore');
const ImageResolver = findByProps('getAvatarDecorationURL', 'default');
const { CollectiblesExperiment } = findByProps('useCollectiblesExperiment');
const AvatarDecorationUtils = findByProps('isAnimatedAvatarDecoration');

let patches = [];

export default {
onLoad: async () => {
patches.push(after("getUser", UserStore, (_, ret) => {
if (ret && users?.has(ret.id)) ret.avatarDecoration = `decor_${users?.get(ret.id)}`
}));

patches.push(after("getAvatarDecorationURL", ImageResolver, ([{ avatarDecoration }], _) => {
if (avatarDecoration?.startsWith("decor")) {
const parts = avatarDecoration.split("_");
parts.shift();
return BASE_URL + `/${parts.join("_")}.png`
};
}));

storage.debug ??= false;

await fetchUsers();
},
onUnload: () => {
patches.forEach((unpatch) => unpatch());
},
settings: Settings
onLoad: async () => {
patches.push(unsubscribeFromUserDecorationsStore);
patches.push(
after('getUser', UserStore, (_, ret) => {
if (ret && !ret.avatarDecoration?.startsWith('decor_') && users?.has(ret.id)) ret.avatarDecoration = `decor_${users?.get(ret.id)}`;
})
);

patches.push(
after('getAvatarDecorationURL', ImageResolver, ([{ avatarDecoration, canAnimate }], _) => {
if (avatarDecoration?.startsWith('decor')) {
const parts = avatarDecoration.split('_').slice(1);
if (!canAnimate && parts[0] === 'a') parts.shift();
return CDN_URL + `/${parts.join('_')}.png`;
} else if (avatarDecoration?.startsWith('file://')) {
return avatarDecoration;
}
})
);

patches.push(
after('isAnimatedAvatarDecoration', AvatarDecorationUtils, ([avatarDecoration], _) => {
if (ReactNative.Platform.OS === 'ios' && avatarDecoration?.startsWith('file://')) return true;
})
);

patches.push(
(() => {
const oldVal = CollectiblesExperiment.getCurrentConfig().canUseAvatarDecorations;
CollectiblesExperiment.getCurrentConfig().canUseAvatarDecorations = true;
return () => (CollectiblesExperiment.getCurrentConfig().canUseAvatarDecorations = oldVal);
})()
);

storage.developerMode ??= false;

getUsers();
},
onUnload: () => {
patches.forEach((unpatch) => unpatch());
},
settings: Settings
};
73 changes: 73 additions & 0 deletions src/lib/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { storage } from '@vendetta/plugin';
import { BASE_URL } from './constants';

export interface Preset {
id: string;
name: string;
description: string | null;
decorations: Decoration[];
}

export interface Decoration {
hash: string;
animated: boolean;
alt: string | null;
authorId: string | null;
reviewed: boolean | null;
presetId: string | null;
}

export interface NewDecoration {
uri: string;
fileType: string;
fileName: string;
alt: string;
}

export const API_URL = BASE_URL + '/api';

export let users: Map<string, string>;

export const isAuthorized = () => !!storage.token;

export const cFetch = (url: RequestInfo, options?: RequestInit) =>
fetch(url, { ...options, headers: { ...options?.headers, Authorization: `Bearer ${storage.token}` } }).then((c) =>
c.ok ? c : Promise.reject(c)
);

export const getUsers = async (cache: RequestCache = 'default') =>
(users = new Map(Object.entries(await fetch(API_URL + '/users', { cache }).then((c) => c.json()))));

export const getUserDecorations = async (id: string = '@me'): Promise<Decoration[]> =>
cFetch(API_URL + `/users/${id}/decorations`).then((c) => c.json());

export const getUserDecoration = async (id: string = '@me'): Promise<Decoration | null> =>
cFetch(API_URL + `/users/${id}/decoration`).then((c) => c.json());

export const setUserDecoration = async (decoration: Decoration | NewDecoration | null, id: string = '@me'): Promise<string | Decoration> => {
const formData = new FormData();

if (decoration && Object.hasOwn(decoration, 'hash')) {
decoration = decoration as Decoration;
formData.append('hash', decoration.hash);
} else if (!decoration) {
formData.append('hash', null);
} else if (decoration && Object.hasOwn(decoration, 'uri')) {
decoration = decoration as NewDecoration;
//@ts-expect-error
formData.append('image', { uri: decoration.uri, type: decoration.fileType, name: decoration.fileName });
formData.append('alt', decoration.alt);
}

return cFetch(API_URL + `/users/${id}/decoration`, { method: 'PUT', body: formData }).then((c) =>
decoration && Object.hasOwn(decoration, 'uri') ? c.json() : c.text()
);
};

export const getDecoration = async (hash: string): Promise<Decoration> => fetch(API_URL + `/decorations/${hash}`).then((c) => c.json());

export const deleteDecoration = async (hash: string): Promise<void> => {
await cFetch(API_URL + `/decorations/${hash}`, { method: 'DELETE' });
};

export const getPresets = async (): Promise<Preset[]> => fetch(API_URL + '/decorations/presets').then((c) => c.json());
10 changes: 8 additions & 2 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
export const BASE_URL = "https://decor.fieryflames.dev";
export const DISCORD_SERVER_INVITE = "https://discord.gg/dXp2SdxDcP";
import { storage } from '@vendetta/plugin';

const maybeCustom = (customValue: string, defaultValue: string) => (storage.useCustomConstants && customValue ? customValue : defaultValue);

export const CLIENT_ID = maybeCustom(storage.clientId, '1096966363416899624');
export const BASE_URL = maybeCustom(storage.baseUrl, 'https://decor.fieryflames.dev');
export const CDN_URL = maybeCustom(storage.cdnUrl, 'https://decorcdn.fieryflames.dev');
export const DISCORD_SERVER_INVITE = 'https://discord.gg/dXp2SdxDcP';
6 changes: 0 additions & 6 deletions src/lib/users.ts

This file was deleted.

3 changes: 3 additions & 0 deletions src/lib/utils/decorationToString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Decoration } from '../api';

export default (decoration: Decoration) => `decor_${decoration.animated ? 'a_' : ''}${decoration.hash}`;
8 changes: 8 additions & 0 deletions src/lib/utils/readFileAsBase64.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ReactNative } from '@vendetta/metro/common';

const FileManager = ReactNative.NativeModules.DCDFileManager ?? ReactNative.NativeModules.RTNFileManager;

export default async function readFileAsBase64(file: string) {
if (file.startsWith('file://')) file = file.slice(7);
return FileManager.readFile(file, 'base64');
}
Loading

0 comments on commit feb6f77

Please sign in to comment.