Skip to content

Commit

Permalink
feat(Splasher): initial port from enmity
Browse files Browse the repository at this point in the history
  • Loading branch information
janisslsm committed Feb 12, 2024
1 parent 21961f8 commit 352bf46
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 0 deletions.
18 changes: 18 additions & 0 deletions plugins/Splasher/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "Splasher",
"description": "Lets you use imgur pictures as profile banners! Ported from enmity",
"authors": [
{
"name": "John",
"id": "780819226839220265"
},
{
"name": "Niko",
"id": "341377368075796483"
}
],
"main": "src/index.ts",
"vendetta": {
"icon": "ic_call_ended"
}
}
152 changes: 152 additions & 0 deletions plugins/Splasher/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { findByName, findByProps, findByStoreName } from "@vendetta/metro"
import { after, instead, before } from "@vendetta/patcher"
import { getAssetIDByName } from "@vendetta/ui/assets";
import { showToast } from "@vendetta/ui/toasts";

let patches = []

const bannerRegex = /\u{e0042}\u{e004E}\u{e0052}\u{e003C}([\u{e0061}-\u{e007A}\u{e0041}-\u{e005a}\u{e0030}-\u{e0039}]+?)\u{e003E}/u;

const encode = (text) => {
const codePoints = [...text].map((c) => c.codePointAt(0));

const output: any[] = [];
for (const char of codePoints) {
output.push(
String.fromCodePoint(
char + (0x00 < char && char < 0x7f ? 0xe0000 : 0)
).toString()
);
}

return output.join("");
};

export default {
onLoad: () => {
let pendingID: string | null;
const ProfileBanner = findByName('ProfileBanner', false);
const EditUserProfileBanner = findByName('EditUserProfileBanner', false);
const UserProfileStore = findByProps('getUserProfile');
const UserSettingsAccountStore = findByProps('saveProfileChanges');
const Clipboard = findByProps('setString');
const ChangeBannerActionSheet = findByName('ChangeBannerActionSheet', false);
const Dialog = findByProps('show', 'openLazy', 'close');
const Users = findByStoreName("UserStore")

// Inital profile patch
patches.push(after('getUserProfile', UserProfileStore, (_, res) => {
if (res === undefined) { return res; }

const bannerString = res?.bio.match(bannerRegex);

if (bannerString) {
res.banner = bannerString[0];
res.bio = res.bio.replace(bannerRegex, '')
}

return res;
}));

// Actual banner patch
patches.push(before('default', ProfileBanner, (args) => {
if (!args[0].bannerSource) { return }
const bannerURI = args[0].bannerSource['uri']

if (!bannerURI) { return }

const id = bannerURI.match(bannerRegex);

if (id) {
const parsedID = [...id[0]]
.map(x => String.fromCodePoint(x.codePointAt(0)! - 0xe0000))
.join('');

args[0].bannerSource['uri'] = `https://i.imgur.com/${parsedID.slice(4, -1)}.png`;
}
}));

// Jump in-between [Save] and upload our banner to imgur
patches.push(instead('saveProfileChanges', UserSettingsAccountStore, (args, res) => {
const currentProfile = UserProfileStore.getUserProfile(Users.getCurrentUser().id)
const currentBio = args[0].bio !== undefined ? args[0].bio : currentProfile['bio']

// Remove banner if null by setting the bio to the current one, which is always stripped of the splash.
if (args[0].banner === null) { args[0].bio = currentBio; return res.apply(this, args); }
if (!(args[0].banner || currentProfile.banner.match(bannerRegex))) { return res.apply(this, args); }

// If uploading hasn't completed, block the save from occuring.
if (args[0].banner && !pendingID) {
showToast(
`Slow down and try again!`,
getAssetIDByName("ic_clock_timeout_16px")
);
return;
}

const encodedInfo = pendingID ? encode(`BNR<${pendingID}>`) : currentProfile.banner;
const insertedBio = currentBio + encodedInfo

// 190 is the maximum length, can't go over that one!
if (insertedBio.length > 190) {
Clipboard.setString(encodedInfo)
Dialog.show({
title: 'Woah there!',
body: `There's not enough space in your bio to insert your banner. You need to clear ${encodedInfo.length} characters before you can continue. Your banner has been copied to the clipboard.`,
});
}

args[0].bio = insertedBio;
pendingID = null;

return res.apply(this, args);
}));

// The banner must be uploaded before the save button, otherwise a race condition is forced.
patches.push(before('setPendingBanner', UserSettingsAccountStore, (args) => {
if (args[0] === null) { return; }

const formData = new FormData();
formData.append('image', args[0].split(',')[1])

fetch('https://api.imgur.com/3/image', {
method: "POST",
body: formData,
headers: {
"Authorization": "Client-ID 8218830746fcf7d",
}
}).then(response => {
response.json().then(output => {
showToast(
`Banner uploaded!`,
getAssetIDByName("ic_add_tier_40px"),
);

pendingID = output.data.id;
})
});
}));

// Nitro Spoofing

// This is jank. It enables the "Preview profile" button without nitro, but because I can't just change that prop without it replicating, I have to quickly do the old switcheroo.
patches.push(instead('default', EditUserProfileBanner, (args, res) => {
const premiumType = args[0].user['premiumType'];
args[0].user['premiumType'] = 2;

let result = res.apply(self, args);

args[0].user['premiumType'] = premiumType;
return result;
}));

// Switch the banner changing menu from the limited one to the nitro one.
patches.push(before('default', ChangeBannerActionSheet, (args) => {
args[0].isTryItOut = true;
}));
},
onUnload: () => {
for (const unpatch of patches) unpatch()
},
//settings: Settings
}
62 changes: 62 additions & 0 deletions plugins/Splasher/src/settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { ReactNative } from "@vendetta/metro/common";
import { Forms } from "@vendetta/ui/components";
import { getAssetIDByName } from "@vendetta/ui/assets";
import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";

const { FormSection, FormDivider, FormIcon, FormSwitchRow } = Forms;

export default () => {
useProxy(storage);

return (
<ReactNative.ScrollView>
<FormSection title="User Profile" titleStyleType="no_border">
<FormSwitchRow
label="Hide call button"
leading={<FormIcon source={getAssetIDByName("ic_audio")} />}
onValueChange={(v) => {
storage.upHideVoiceButton = v;
}}
value={storage.upHideVoiceButton}
/>
<FormDivider />
<FormSwitchRow
label="Hide video button"
leading={<FormIcon source={getAssetIDByName("ic_video")} />}
onValueChange={(v) => {
storage.upHideVideoButton = v;
}}
value={storage.upHideVideoButton}
/>
</FormSection>
<FormSection title="DMs" titleStyleType="no_border">
<FormSwitchRow
label="Hide call button"
leading={<FormIcon source={getAssetIDByName("ic_audio")} />}
onValueChange={(v) => {
storage.dmHideCallButton = v;
}}
value={storage.dmHideCallButton}
/>
<FormDivider />
<FormSwitchRow
label="Hide video button"
leading={<FormIcon source={getAssetIDByName("ic_video")} />}
onValueChange={(v) => {
storage.dmHideVideoButton = v;
}}
value={storage.dmHideVideoButton}
/>
</FormSection>
<FormSection title="Other" titleStyleType="no_border">
<FormSwitchRow
label="Hide video button in VC"
leading={<FormIcon source={getAssetIDByName("video")} />}
onValueChange={(v) => (storage.hideVCVideoButton = v)}
value={storage.hideVCVideoButton}
/>
</FormSection>
</ReactNative.ScrollView>
);
};

0 comments on commit 352bf46

Please sign in to comment.