Skip to content

Commit

Permalink
evverything else
Browse files Browse the repository at this point in the history
  • Loading branch information
Wolfkid200444 committed Nov 30, 2023
1 parent 4c2a4ef commit 445b12e
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 35 deletions.
21 changes: 14 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Injector, common, webpack } from "replugged";
import { Injector, common, webpack, Logger } from "replugged";
import {
subscriptions as UserDecorationsStoreSubscriptions,
useUserDecorAvatarDecoration,
useUsersDecorationsStore,
} from "./lib/stores/UserDecorationsStore";
import { CDN_URL, RAW_SKU_ID, SKU_ID } from "./lib/constants";
Expand All @@ -10,26 +11,32 @@ const AvatarURL = webpack.getByProps("getUserAvatarURL");
const { isAnimatedAvatarDecoration } = webpack.getByProps("isAnimatedAvatarDecoration");
const inject = new Injector();

export const logger = Logger.plugin("UserDecorations");
export interface AvatarDecoration {
asset: string;
skuId: string;
}

UserDecorationsStoreSubscriptions;


export async function start(): Promise<void> {
inject.after(users, "getUser", (args, res) => {
useUserDecorAvatarDecoration;
UserDecorationsStoreSubscriptions;

inject.after(users, "getUser", (_, res) => {
const store = useUsersDecorationsStore.getState();
if (res && store.has(res.id)) {

if (res && store.has(res?.id)) {
const decor = store.get(res.id);

if (decor && res.avatarDecoration?.skuId !== SKU_ID) {
users.avatarDecoration = {
res.avatarDecoration = {
asset: decor,
skuId: SKU_ID,
};
} else if (!decor && res?.avatarDecoration?.skuId === SKU_ID) {
users.avatarDecoration = null;

} else if (!decor && res.avatarDecoration && res.avatarDecoration?.skuId === SKU_ID) {
//res.avatarDecoration = null;
}

res.avatarDecorationData = res?.avatarDecoration;
Expand Down
40 changes: 38 additions & 2 deletions src/lib/api.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,45 @@
import { API_URL } from "./constants";
import { useAuthorizationStore } from "./stores/AuthorizationStore";

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

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

export async function fetchApi(url: RequestInfo, options?: RequestInit) {
const res = await fetch(url, {
...options,
headers: {
...options?.headers,
Authorization: `Bearer ${useAuthorizationStore.getState().token}`,
},
});

if (res.ok) return res;
else throw new Error(await res.text());
}

export const getUsersDecorations = async (ids: string[] | undefined = undefined) => {
if (ids && ids.length === 0) return {}
if (ids && ids.length === 0) return {};
const url = new URL(API_URL + "/users");
if (ids && ids.length !== 0) url.searchParams.set("ids", JSON.stringify(ids));

return (await fetch(url).then(c => c.json())) as Record<string, string | null>;
return (await fetch(url).then((c) => c.json())) as Record<string, string | null>;
};

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

export const getUserDecoration = async (id: string = "@me"): Promise<Decoration | null> =>
fetchApi(API_URL + `/users/${id}/decoration`).then((c) => c.json());
1 change: 1 addition & 0 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export const SKU_ID = "100101099111114"; // decor in ascii numbers
export const RAW_SKU_ID = "11497119"; // raw in ascii numbers
export const GUILD_ID = "1096357702931841148";
export const INVITE_KEY = "dXp2SdxDcP";
export const DECORATION_FETCH_COOLDOWN = 1000 * 60 * 60 * 4; // 4 hours
59 changes: 59 additions & 0 deletions src/lib/stores/AuthorizationStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { StateStorage } from "zustand/middleware";
import { authorizationToken } from "../utils/settings";
import { persist, create } from "../zustand";
import { common } from "replugged";
import showAuthorizationModal from "../utils/showAuthorizationModal";

const { users } = common;

interface AuthorizationState {
token: string | null;
tokens: Record<string, string>;
init: () => void;
authorize: () => Promise<void>;
setToken: (token: string) => void;
remove: (id: string) => void;
isAuthorized: () => boolean;
}

const indexedDBStorage: StateStorage = {
async getItem(name: string): Promise<string | null> {
return (await authorizationToken).get(name).then((v) => v ?? null);
},
async setItem(name: string, value: string): Promise<void> {
await (await authorizationToken).set(name, value);
},
async removeItem(name: string): Promise<void> {
await authorizationToken.del(name);
},
};

export const useAuthorizationStore = create<AuthorizationState>(
persist(
(set, get) => ({
token: null,
tokens: {},
init: () => {
set({ token: get().tokens[users.getCurrentUser().id] ?? null });
},
setToken: (token: string) =>
set({ token, tokens: { ...get().tokens, [users.getCurrentUser().id]: token } }),
remove: (id: string) => {
const { tokens, init } = get();
const newTokens = { ...tokens };
delete newTokens[id];
set({ tokens: newTokens });

init();
},
authorize: () => void showAuthorizationModal(),
isAuthorized: () => !!get().token,
}),
{
name: "decor-auth",
getStorage: () => indexedDBStorage,
partialize: (state) => ({ tokens: state.tokens }),
onRehydrateStorage: () => (state) => state?.init(),
},
),
);
43 changes: 43 additions & 0 deletions src/lib/stores/CurrentUserDecorationsStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { common, lodash } from "replugged";
import { Decoration, NewDecoration, getUserDecoration, getUserDecorations } from "../api";
import discordifyDecoration from "../utils/discordifyDecoration";
import { useUsersDecorationsStore } from "./UserDecorationsStore";
import decorationToString from "../utils/decorationToString";
import { create } from "../zustand";

const { lodash, users, fluxDispatcher } = common;

interface CurrentUserDecorationsState {
decorations: Decoration[];
selectedDecoration: Decoration | null;
fetched: boolean;
fetch: () => Promise<void>;
delete: (decoration: Decoration | string) => Promise<void>;
create: (decoration: NewDecoration) => Promise<void>;
select: (decoration: Decoration | null) => Promise<void>;
clear: () => void;
}

function updateCurrentUserAvatarDecoration(decoration: Decoration | null) {
const user = users.getCurrentUser();
user.avatarDecoration = decoration ? discordifyDecoration(decoration) : null;
user.avatarDecorationData = user.avatarDecoration;

useUsersDecorationsStore
.getState()
.set(user.id, decoration ? decorationToString(decoration) : null);
fluxDispatcher.dispatch({ type: "CURRENT_USER_UPDATE", user });
fluxDispatcher.dispatch({ type: "USER_SETTINGS_ACCOUNT_SUBMIT_SUCCESS" });
}

export const useCurrentUserDecorationsStore = create<CurrentUserDecorationsState>((set, get) => ({
decorations: [],
selectedDecoration: null,
async fetch() {
const decorations = await getUserDecorations();
const selectedDecoration = await getUserDecoration();

set({ decorations, selectedDecoration });
},
clear: () => set({ decorations: [], selectedDecoration: null })
}));
60 changes: 37 additions & 23 deletions src/lib/stores/UserDecorationsStore.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,49 @@
import { common } from "replugged";
import { create } from "../zustand";
import { getUsersDecorations } from "../api";
import { SKU_ID } from "../constants";
import { DECORATION_FETCH_COOLDOWN, SKU_ID } from "../constants";
import { AvatarDecoration } from "../..";
import subscribeToFluxDispatcher from "../utils/subscribeToFluxDispatcher";
import { useCurrentUserDecorationsStore } from "./CurrentUserDecorationsStore";
import { useAuthorizationStore } from "./AuthorizationStore";

const { lodash, users, fluxDispatcher, React, channels } = common;

interface UserDecorationData {
asset: string | null;
fetchedAt: Date;
}

interface UsersDecorationsState {
usesDecorations: Map<string, string | null>;
usesDecorations: Map<string, UserDecorationData>;
fetchQueue: Set<string>;
bulkFetch: () => Promise<void>;
fetch: (userId: string, force?: boolean) => Promise<void>;
fetchMany: (userIds: string[]) => Promise<void>;
getAsset: (userId: string) => string | null | undefined;
get: (userId: string) => string | null | undefined;
get: (userId: string) => UserDecorationData | undefined;
has: (userId: string) => boolean;
set: (userId: string, decoration: string | null) => void;
}

export const useUsersDecorationsStore = create<UsersDecorationsState>((set, get) => ({
usersDecorations: new Map(),
usersDecorations: new Map<string, UserDecorationData>(),
fetchQueue: new Set(),
bulkFetch: lodash.debounce(async () => {
const { fetchQueue, usersDecorations } = get();
set({ fetchQueue: new Set() });
set({ fetchQueue: new Set() });

const fetchIds = Array.from(fetchQueue);
if (fetchIds.length === 0) return;
const fetchedUsersDecorations = await getUsersDecorations(fetchIds);

const newUsersDecorations = new Map(usersDecorations);
for (const [userId, decoration] of Object.entries(fetchedUsersDecorations)) {
newUsersDecorations.set(userId, decoration);
newUsersDecorations.set(userId, { asset: decoration, fetchedAt: new Date() });

const user = users.getUser(userId) as any;
if (user) {
console.log("Bulk: ",decoration)
user.avatarDecoration = decoration ? { asset: decoration, skuId: SKU_ID } : null;
user.avatarDecorationData = user.avatarDecoration;

Expand All @@ -54,8 +62,11 @@ export const useUsersDecorationsStore = create<UsersDecorationsState>((set, get)
async fetch(userId: string, force: boolean = false) {
const { usersDecorations, fetchQueue, bulkFetch } = get();

if (!force && usersDecorations.has(userId)) return;

if (usersDecorations.has(userId)) {
console.log("Fetch: ",usersDecorations.get(userId))
const { fetchedAt } = usersDecorations.get(userId)!;
if (!force && Date.now() - fetchedAt.getTime() < DECORATION_FETCH_COOLDOWN) return;
}
set({ fetchQueue: new Set(fetchQueue).add(userId) });
bulkFetch();
},
Expand All @@ -66,9 +77,15 @@ export const useUsersDecorationsStore = create<UsersDecorationsState>((set, get)
const { usersDecorations, fetchQueue, bulkFetch } = get();

const newFetchQueue = new Set(fetchQueue);

for (const userId of userIds) {
if (!usersDecorations.has(userId)) newFetchQueue.add(userId);
if (usersDecorations.has(userId)) {
const { fetchedAt } = usersDecorations.get(userId)!;
if (Date.now() - fetchedAt.getTime() < DECORATION_FETCH_COOLDOWN) continue;
}
newFetchQueue.add(userId);
}

set({ fetchQueue: newFetchQueue });
bulkFetch();
},
Expand All @@ -86,29 +103,24 @@ export const useUsersDecorationsStore = create<UsersDecorationsState>((set, get)
const { usersDecorations } = get();
const newUsersDecorations = new Map(usersDecorations);

newUsersDecorations.set(userId, decoration);
newUsersDecorations.set(userId, { asset: decoration, fetchedAt: new Date() });
set({ usersDecorations: newUsersDecorations });
},
}));

export const subscriptions = [
subscribeToFluxDispatcher("LOAD_MESSAGES_SUCCESS", ({ messages }) => {
useUsersDecorationsStore.getState().fetchMany(messages.map((m) => m.author.id));
subscribeToFluxDispatcher("USER_PROFILE_MODAL_OPEN", (data) => {
useUsersDecorationsStore.getState().fetch(data.userId, true);
}),

subscribeToFluxDispatcher("CONNECTION_OPEN", () => {
useAuthorizationStore.getState().init();
useCurrentUserDecorationsStore.getState().clear();
useUsersDecorationsStore.getState().fetch(users.getCurrentUser().id, true);
}),
subscribeToFluxDispatcher("MESSAGE_CREATE", (data) => {
const channelId = channels.getChannelId();
if (data.channelId === channelId) {
useUsersDecorationsStore.getState().fetch(data.message.author.id);
}
}),
subscribeToFluxDispatcher("TYPING_START", (data) => {
const channelId = channels.getChannelId();
if (data.channelId === channelId) {
useUsersDecorationsStore.getState().fetch(data.userId);
}

subscribeToFluxDispatcher("LOAD_MESSAGES_SUCCESS", ({ messages }) => {
useUsersDecorationsStore.getState().fetchMany(messages.map((m) => m.author.id));
}),
];

Expand All @@ -135,5 +147,7 @@ export function useUserDecorAvatarDecoration(user): AvatarDecoration | null | un
fetchUserDecorAvatarDecoration(user.id);
}, []);

console.log("Effects", decorAvatarDecoration)

return decorAvatarDecoration ? { asset: decorAvatarDecoration, skuId: SKU_ID } : null;
}
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) => `${decoration.animated ? 'a_' : ''}${decoration.hash}`;
5 changes: 5 additions & 0 deletions src/lib/utils/discordifyDecoration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Decoration } from "../api";
import { SKU_ID } from "../constants";
import decorationToString from "./decorationToString";

export default (d: Decoration) => ({ asset: decorationToString(d), skuId: SKU_ID });
4 changes: 4 additions & 0 deletions src/lib/utils/settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { settings } from "replugged"

export const defaultSettings = {}
export const authorizationToken = settings.init("decor.auth", defaultSettings)
39 changes: 39 additions & 0 deletions src/lib/utils/showAuthorizationModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { webpack, common } from "replugged"
import { useAuthorizationStore } from "../stores/AuthorizationStore";
import { AUTHORIZE_URL, CLIENT_ID } from "../constants";
import { logger } from "../..";

const { modal: { openModal } } = common
const OAuth = webpack.getByProps("OAuth2AuthorizeModal")

export default async () => new Promise(r => openModal(props =>
<OAuth.OAuth2AuthorizeModal
{...props}
scopes={["identify"]}
responseType="code"
redirectUri={AUTHORIZE_URL}
permissions={0}
clientId={CLIENT_ID}
cancelCompletesFlow={false}
callback={async (response: any) => {
try {
const url = new URL(response.location);
url.searchParams.append("client", "vencord");

const req = await fetch(url);

if (req?.ok) {
const token = await req.text();
useAuthorizationStore.getState().setToken(token);
} else {
throw new Error("Request not OK");
}
r(void 0);
} catch (e) {
logger("Decor").error("Failed to authorize", e);
}
}}
/>
))


Loading

0 comments on commit 445b12e

Please sign in to comment.