useBestFriends();

This commit is contained in:
2025-07-24 19:50:54 +03:00
parent 980b27bf84
commit c7d3cd85be
24 changed files with 925 additions and 249 deletions

View File

@@ -0,0 +1,67 @@
"use client";
import { useEffect, useState } from "react";
import { useCurrentAccount } from "./useCurrentAccount";
import { proxyFetch } from "@/lib/utils";
type AccountSettings = {
ChangeUsernameEnabled: boolean
/* determines if the account owner is a roblox admin */
IsAdmin: boolean,
PreviousUserNames: string,
/* censored out email */
UserEmail: string,
UserAbove13: boolean,
/* does the user have roblox premium */
IsPremium: boolean,
/* ingame chat */
IsGameChatSettingEnabled: boolean
}
export function useAccountSettings() {
const acct = useCurrentAccount();
const [accountSettings, setAccountSettings] = useState<AccountSettings | false | null>(null);
useEffect(() => {
if (!acct) return;
let cancelled = false;
const fetchSetttings = async () => {
if (!acct || cancelled) return;
try {
const res = await proxyFetch(
`https://www.roblox.com/my/settings/json`
);
const data = await res.json();
if (!cancelled) setAccountSettings(data);
} catch {
if (!cancelled) setAccountSettings(false);
}
};
fetchSetttings();
const handleTransaction = () => {
fetchSetttings();
};
window.addEventListener("settingTransactionCompletedEvent", handleTransaction);
return () => {
cancelled = true;
window.removeEventListener(
"settingTransactionCompletedEvent",
handleTransaction
);
};
}, [acct]);
return accountSettings;
}

View File

@@ -0,0 +1,63 @@
// https://avatar.roblox.com/v2/avatar/users/1083030325/outfits?isEditable=true&itemsPerPage=50&outfitType=Avatar
"use client";
import { useEffect, useState } from "react";
import { useCurrentAccount } from "./useCurrentAccount";
import { proxyFetch } from "@/lib/utils";
import { loadThumbnails } from "@/lib/thumbnailLoader";
type Outfit = {
name: string,
id: number
}
export function useAvatarOutfits() {
const acct = useCurrentAccount();
const [outfits, setOutfits] = useState<Outfit[] | false | null>(null);
useEffect(() => {
if (!acct) return;
let cancelled = false;
const fetchSetttings = async () => {
if (!acct || cancelled) return;
try {
const res = await proxyFetch(
`https://avatar.roblox.com/v2/avatar/users/${acct.id}/outfits?page=1&itemsPerPage=25&isEditable=true`
);
const data = await res.json() as {data: Outfit[]};
if (!cancelled) {
setOutfits(data.data);
loadThumbnails(data.data.map(a=>({
type: "Outfit",
targetId: a.id,
format: "webp",
size: "420x420"
}))).catch(a=>{})
}
} catch {
if (!cancelled) setOutfits(false);
}
};
fetchSetttings();
const handleTransaction = () => {
fetchSetttings();
};
window.addEventListener("avatarTransactionCompletedEvent", handleTransaction);
return () => {
cancelled = true;
window.removeEventListener(
"avatarTransactionCompletedEvent",
handleTransaction
);
};
}, [acct]);
return outfits;
}

View File

@@ -0,0 +1,89 @@
"use client";
// https://friends.roblox.com/v1/users/1083030325/friends/find?userSort=1
import { useEffect, useState } from "react";
import { useCurrentAccount } from "./useCurrentAccount";
import { proxyFetch } from "@/lib/utils";
import { loadThumbnails } from "@/lib/thumbnailLoader";
let isFetching = false;
let cachedData: any = null;
export function useBestFriends() {
const acct = useCurrentAccount();
const [friends, setFriends] = useState<
| {
hasVerifiedBadge: boolean;
id: number;
name: string;
displayName: string;
}[]
| null
| false
>(cachedData);
useEffect(() => {
let cancelled = false;
if (!acct) return;
if (isFetching) {
const IN = setInterval(() => {
if (cachedData !== null) {
if (!cancelled) setFriends(cachedData);
clearInterval(IN);
}
}, 50);
return () => {
clearInterval(IN);
cancelled = true;
};
}
isFetching = true;
(async () => {
const BestFriendIDs = JSON.parse(window.localStorage.getItem("BestFriendsStore") || "[]") as number[]
const friendsAPICall2 = await proxyFetch(
`https://users.roblox.com/v1/users`,
{
method: "POST",
body: JSON.stringify({
userIds: BestFriendIDs,
excludeBannedUsers: false
})
}
);
const J2 = (await friendsAPICall2.json()) as {
data: {
hasVerifiedBadge: boolean;
id: number;
name: string;
displayName: string;
}[];
};
loadThumbnails(
J2.data.map((a) => ({
type: "AvatarHeadShot",
size: "420x420",
targetId: a.id,
format: "webp"
}))
).catch(() => {});
const friendsList = BestFriendIDs.map((a) => {
const x = J2.data.find((b) => b.id === a);
return {
id: a,
hasVerifiedBadge: x?.hasVerifiedBadge || false,
name: x?.name || "?",
displayName: x?.displayName || "?"
};
});
if (!cancelled) setFriends(friendsList);
cachedData = friendsList;
isFetching = false;
})();
return () => {
cancelled = true;
};
}, [acct]);
return friends;
}

View File

@@ -41,17 +41,19 @@ export function useFriendsHome() {
isFetching = true;
(async () => {
const friendsAPICall = await proxyFetch(
`https://friends.roblox.com/v1/users/${acct.id}/friends/find?userSort=1`
`https://friends.roblox.com/v1/users/${acct.id}/friends` // /find?userSort=1
);
const J = (await friendsAPICall.json()) as {
PageItems: { id: number }[];
data: { id: number }[];
// PageItems: { id: number }[]; // /find
};
const friendsAPICall2 = await proxyFetch(
`https://users.roblox.com/v1/users`,
{
method: "POST",
body: JSON.stringify({
userIds: J.PageItems.map((a) => a.id),
userIds: J.data.map((a) => a.id),
// userIds: J.PageItems.map((a) => a.id),
excludeBannedUsers: false
})
}
@@ -72,7 +74,7 @@ export function useFriendsHome() {
format: "webp"
}))
).catch(() => {});
const friendsList = J.PageItems.map((a) => {
const friendsList = J.data.map((a) => { // J.PageItems /find
const x = J2.data.find((b) => b.id === a.id);
return {
id: a.id,

View File

@@ -57,7 +57,9 @@ async function fetchPresence(acctId: number) {
);
if (!res.ok) {
throw new Error(`API request failed with status ${res.status}`);
console.log(`[usePresence] API Error ${res.status} ${res.statusText}`)
return
// throw new Error(`API request failed with status ${res.status}`);
}
const json = await res.json();