import { Client, PermissionCalculator } from "revolt.js"; import { Channels, Servers, Users } from "revolt.js/dist/api/objects"; import Collection from "revolt.js/dist/maps/Collection"; import { useCallback, useContext, useEffect, useState } from "preact/hooks"; import { AppContext } from "./RevoltClient"; export interface HookContext { client: Client; forceUpdate: () => void; } export function useForceUpdate(context?: HookContext): HookContext { const client = useContext(AppContext); if (context) return context; const H = useState(0); var updateState: (_: number) => void; if (Array.isArray(H)) { let [, u] = H; updateState = u; } else { console.warn("Failed to construct using useState."); updateState = () => {}; } return { client, forceUpdate: () => updateState(Math.random()) }; } // TODO: utils.d.ts maybe? type PickProperties<T, U> = Pick< T, { [K in keyof T]: T[K] extends U ? K : never; }[keyof T] >; // The keys in Client that are an object // for some reason undefined keeps appearing despite there being no reason to so it's filtered out type ClientCollectionKey = Exclude< keyof PickProperties<Client, Collection<any>>, undefined >; function useObject( type: ClientCollectionKey, id?: string | string[], context?: HookContext, ) { const ctx = useForceUpdate(context); function update(target: any) { if ( typeof id === "string" ? target === id : Array.isArray(id) ? id.includes(target) : true ) { ctx.forceUpdate(); } } const map = ctx.client[type]; useEffect(() => { map.addListener("update", update); return () => map.removeListener("update", update); }, [id]); return typeof id === "string" ? map.get(id) : Array.isArray(id) ? id.map((x) => map.get(x)) : map.toArray(); } export function useUser(id?: string, context?: HookContext) { if (typeof id === "undefined") return; return useObject("users", id, context) as Readonly<Users.User> | undefined; } export function useSelf(context?: HookContext) { const ctx = useForceUpdate(context); return useUser(ctx.client.user!._id, ctx); } export function useUsers(ids?: string[], context?: HookContext) { return useObject("users", ids, context) as ( | Readonly<Users.User> | undefined )[]; } export function useChannel(id?: string, context?: HookContext) { if (typeof id === "undefined") return; return useObject("channels", id, context) as | Readonly<Channels.Channel> | undefined; } export function useChannels(ids?: string[], context?: HookContext) { return useObject("channels", ids, context) as ( | Readonly<Channels.Channel> | undefined )[]; } export function useServer(id?: string, context?: HookContext) { if (typeof id === "undefined") return; return useObject("servers", id, context) as | Readonly<Servers.Server> | undefined; } export function useServers(ids?: string[], context?: HookContext) { return useObject("servers", ids, context) as ( | Readonly<Servers.Server> | undefined )[]; } export function useDMs(context?: HookContext) { const ctx = useForceUpdate(context); function mutation(target: string) { let channel = ctx.client.channels.get(target); if (channel) { if ( channel.channel_type === "DirectMessage" || channel.channel_type === "Group" ) { ctx.forceUpdate(); } } } const map = ctx.client.channels; useEffect(() => { map.addListener("update", mutation); return () => map.removeListener("update", mutation); }, []); return map .toArray() .filter( (x) => x.channel_type === "DirectMessage" || x.channel_type === "Group" || x.channel_type === "SavedMessages", ) as ( | Channels.GroupChannel | Channels.DirectMessageChannel | Channels.SavedMessagesChannel )[]; } export function useUserPermission(id: string, context?: HookContext) { const ctx = useForceUpdate(context); const mutation = (target: string) => target === id && ctx.forceUpdate(); useEffect(() => { ctx.client.users.addListener("update", mutation); return () => ctx.client.users.removeListener("update", mutation); }, [id]); let calculator = new PermissionCalculator(ctx.client); return calculator.forUser(id); } export function useChannelPermission(id: string, context?: HookContext) { const ctx = useForceUpdate(context); const channel = ctx.client.channels.get(id); const server = channel && (channel.channel_type === "TextChannel" || channel.channel_type === "VoiceChannel") ? channel.server : undefined; const mutation = (target: string) => target === id && ctx.forceUpdate(); const mutationServer = (target: string) => target === server && ctx.forceUpdate(); const mutationMember = (target: string) => target.substr(26) === ctx.client.user!._id && ctx.forceUpdate(); useEffect(() => { ctx.client.channels.addListener("update", mutation); if (server) { ctx.client.servers.addListener("update", mutationServer); ctx.client.servers.members.addListener("update", mutationMember); } return () => { ctx.client.channels.removeListener("update", mutation); if (server) { ctx.client.servers.removeListener("update", mutationServer); ctx.client.servers.members.removeListener( "update", mutationMember, ); } }; }, [id]); let calculator = new PermissionCalculator(ctx.client); return calculator.forChannel(id); } export function useServerPermission(id: string, context?: HookContext) { const ctx = useForceUpdate(context); const mutation = (target: string) => target === id && ctx.forceUpdate(); const mutationMember = (target: string) => target.substr(26) === ctx.client.user!._id && ctx.forceUpdate(); useEffect(() => { ctx.client.servers.addListener("update", mutation); ctx.client.servers.members.addListener("update", mutationMember); return () => { ctx.client.servers.removeListener("update", mutation); ctx.client.servers.members.removeListener("update", mutationMember); }; }, [id]); let calculator = new PermissionCalculator(ctx.client); return calculator.forServer(id); }