From 8c0ff5d150bc8b7298159007428860812f6cebb5 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 7 May 2025 10:59:22 +0200 Subject: [PATCH 1/2] feat: friends --- src/controllers/api/addFriendController.ts | 60 ++++++++++++++++++ .../api/addIgnoredUserController.ts | 2 +- .../api/addPendingFriendController.ts | 51 ++++++++++++++++ src/controllers/api/addToGuildController.ts | 14 +++-- src/controllers/api/getFriendsController.ts | 61 +++++++++++++++---- .../api/getIgnoredUsersController.ts | 2 +- src/controllers/api/giftingController.ts | 9 ++- src/controllers/api/removeFriendController.ts | 36 +++++++++++ .../api/setFriendNoteController.ts | 30 +++++++++ .../custom/deleteAccountController.ts | 3 + src/models/friendModel.ts | 15 +++++ src/routes/api.ts | 10 ++- src/services/friendService.ts | 44 +++++++++++++ src/services/guildService.ts | 16 +---- src/types/friendTypes.ts | 24 ++++++++ src/types/guildTypes.ts | 16 +---- 16 files changed, 343 insertions(+), 50 deletions(-) create mode 100644 src/controllers/api/addFriendController.ts create mode 100644 src/controllers/api/addPendingFriendController.ts create mode 100644 src/controllers/api/removeFriendController.ts create mode 100644 src/controllers/api/setFriendNoteController.ts create mode 100644 src/models/friendModel.ts create mode 100644 src/services/friendService.ts create mode 100644 src/types/friendTypes.ts diff --git a/src/controllers/api/addFriendController.ts b/src/controllers/api/addFriendController.ts new file mode 100644 index 00000000..f246f444 --- /dev/null +++ b/src/controllers/api/addFriendController.ts @@ -0,0 +1,60 @@ +import { toOid } from "@/src/helpers/inventoryHelpers"; +import { getJSONfromString } from "@/src/helpers/stringHelpers"; +import { Friendship } from "@/src/models/friendModel"; +import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "@/src/services/friendService"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { IFriendInfo } from "@/src/types/friendTypes"; +import { RequestHandler } from "express"; + +export const addFriendController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const payload = getJSONfromString(String(req.body)); + const promises: Promise[] = []; + const newFriends: IFriendInfo[] = []; + if (payload.friend == "all") { + const [internalFriendships, externalFriendships] = await Promise.all([ + Friendship.find({ owner: accountId }, "friend"), + Friendship.find({ friend: accountId }, "owner") + ]); + for (const externalFriendship of externalFriendships) { + if (!internalFriendships.find(x => x.friend == externalFriendship.owner)) { + promises.push( + Friendship.insertOne({ + owner: accountId, + friend: externalFriendship.owner, + Note: externalFriendship.Note // TOVERIFY: Should the note be copied when accepting a friend request? + }) as unknown as Promise + ); + newFriends.push({ + _id: toOid(externalFriendship.owner) + }); + } + } + } else { + const externalFriendship = await Friendship.findOne({ owner: payload.friend, friend: accountId }, "Note"); + if (externalFriendship) { + promises.push( + Friendship.insertOne({ + owner: accountId, + friend: payload.friend, + Note: externalFriendship.Note + }) as unknown as Promise + ); + newFriends.push({ + _id: { $oid: payload.friend } + }); + } + } + for (const newFriend of newFriends) { + promises.push(addAccountDataToFriendInfo(newFriend)); + promises.push(addInventoryDataToFriendInfo(newFriend)); + } + await Promise.all(promises); + res.json({ + Friends: newFriends + }); +}; + +interface IAddFriendRequest { + friend: string; // oid or "all" in which case all=1 is also a query parameter +} diff --git a/src/controllers/api/addIgnoredUserController.ts b/src/controllers/api/addIgnoredUserController.ts index 99b38972..8e8d4441 100644 --- a/src/controllers/api/addIgnoredUserController.ts +++ b/src/controllers/api/addIgnoredUserController.ts @@ -2,7 +2,7 @@ import { toOid } from "@/src/helpers/inventoryHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { Account, Ignore } from "@/src/models/loginModel"; import { getAccountIdForRequest } from "@/src/services/loginService"; -import { IFriendInfo } from "@/src/types/guildTypes"; +import { IFriendInfo } from "@/src/types/friendTypes"; import { RequestHandler } from "express"; export const addIgnoredUserController: RequestHandler = async (req, res) => { diff --git a/src/controllers/api/addPendingFriendController.ts b/src/controllers/api/addPendingFriendController.ts new file mode 100644 index 00000000..c045d044 --- /dev/null +++ b/src/controllers/api/addPendingFriendController.ts @@ -0,0 +1,51 @@ +import { toOid } from "@/src/helpers/inventoryHelpers"; +import { getJSONfromString } from "@/src/helpers/stringHelpers"; +import { Friendship } from "@/src/models/friendModel"; +import { Account } from "@/src/models/loginModel"; +import { addInventoryDataToFriendInfo, areFriendsOfFriends } from "@/src/services/friendService"; +import { getInventory } from "@/src/services/inventoryService"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { IFriendInfo } from "@/src/types/friendTypes"; +import { RequestHandler } from "express"; + +export const addPendingFriendController: RequestHandler = async (req, res) => { + const payload = getJSONfromString(String(req.body)); + + const account = await Account.findOne({ DisplayName: payload.friend }); + if (!account) { + res.status(400).end(); + return; + } + + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(account._id.toString(), "Settings"); + if ( + inventory.Settings?.FriendInvRestriction == "GIFT_MODE_NONE" || + (inventory.Settings?.FriendInvRestriction == "GIFT_MODE_FRIENDS" && + !(await areFriendsOfFriends(account._id, accountId))) + ) { + res.status(400).send("Friend Invite Restriction"); + return; + } + + await Friendship.insertOne({ + owner: accountId, + friend: account._id, + Note: payload.message + }); + + const friendInfo: IFriendInfo = { + _id: toOid(account._id), + DisplayName: account.DisplayName, + Note: payload.message + }; + await addInventoryDataToFriendInfo(friendInfo); + res.json({ + Friend: friendInfo + }); +}; + +interface IAddPendingFriendRequest { + friend: string; + message: string; +} diff --git a/src/controllers/api/addToGuildController.ts b/src/controllers/api/addToGuildController.ts index c67b8d1a..4ae01b7b 100644 --- a/src/controllers/api/addToGuildController.ts +++ b/src/controllers/api/addToGuildController.ts @@ -1,6 +1,7 @@ import { Guild, GuildMember } from "@/src/models/guildModel"; import { Account } from "@/src/models/loginModel"; -import { fillInInventoryDataForGuildMember, hasGuildPermission } from "@/src/services/guildService"; +import { addInventoryDataToFriendInfo, areFriends } from "@/src/services/friendService"; +import { hasGuildPermission } from "@/src/services/guildService"; import { createMessage } from "@/src/services/inboxService"; import { getInventory } from "@/src/services/inventoryService"; import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService"; @@ -22,15 +23,18 @@ export const addToGuildController: RequestHandler = async (req, res) => { return; } + const senderAccount = await getAccountForRequest(req); const inventory = await getInventory(account._id.toString(), "Settings"); - // TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented - if (inventory.Settings?.GuildInvRestriction == "GIFT_MODE_NONE") { + if ( + inventory.Settings?.GuildInvRestriction == "GIFT_MODE_NONE" || + (inventory.Settings?.GuildInvRestriction == "GIFT_MODE_FRIENDS" && + !(await areFriends(account._id, senderAccount._id))) + ) { res.status(400).json("Invite restricted"); return; } const guild = (await Guild.findById(payload.GuildId.$oid, "Name Ranks"))!; - const senderAccount = await getAccountForRequest(req); if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) { res.status(400).json("Invalid permission"); } @@ -74,7 +78,7 @@ export const addToGuildController: RequestHandler = async (req, res) => { Rank: 7, Status: 2 }; - await fillInInventoryDataForGuildMember(member); + await addInventoryDataToFriendInfo(member); res.json({ NewMember: member }); } else if ("RequestMsg" in payload) { // Player applying to join a clan diff --git a/src/controllers/api/getFriendsController.ts b/src/controllers/api/getFriendsController.ts index 1227f84d..83d0c8a1 100644 --- a/src/controllers/api/getFriendsController.ts +++ b/src/controllers/api/getFriendsController.ts @@ -1,15 +1,54 @@ -import { Request, Response } from "express"; +import { toOid } from "@/src/helpers/inventoryHelpers"; +import { Friendship } from "@/src/models/friendModel"; +import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "@/src/services/friendService"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { IFriendInfo } from "@/src/types/friendTypes"; +import { Request, RequestHandler, Response } from "express"; // POST with {} instead of GET as of 38.5.0 -const getFriendsController = (_request: Request, response: Response): void => { - response.writeHead(200, { - //Connection: "keep-alive", - //"Content-Encoding": "gzip", - "Content-Type": "text/html", - // charset: "UTF - 8", - "Content-Length": "3" - }); - response.end(Buffer.from([0x7b, 0x7d, 0x0a])); +export const getFriendsController: RequestHandler = async (req: Request, res: Response) => { + const accountId = await getAccountIdForRequest(req); + const response: IGetFriendsResponse = { + Current: [], + IncomingFriendRequests: [], + OutgoingFriendRequests: [] + }; + const [internalFriendships, externalFriendships] = await Promise.all([ + Friendship.find({ owner: accountId }), + Friendship.find({ friend: accountId }, "owner Note") + ]); + for (const externalFriendship of externalFriendships) { + if (!internalFriendships.find(x => x.friend == externalFriendship.owner)) { + response.IncomingFriendRequests.push({ + _id: toOid(externalFriendship.owner), + Note: externalFriendship.Note + }); + } + } + for (const internalFriendship of internalFriendships) { + const friendInfo: IFriendInfo = { + _id: toOid(internalFriendship.friend) + }; + if (externalFriendships.find(x => x.owner == internalFriendship.friend)) { + response.Current.push(friendInfo); + } else { + response.OutgoingFriendRequests.push(friendInfo); + } + } + const promises: Promise[] = []; + for (const arr of Object.values(response)) { + for (const friendInfo of arr) { + promises.push(addAccountDataToFriendInfo(friendInfo)); + promises.push(addInventoryDataToFriendInfo(friendInfo)); + } + } + await Promise.all(promises); + res.json(response); }; -export { getFriendsController }; +// interface IGetFriendsResponse { +// Current: IFriendInfo[]; +// IncomingFriendRequests: IFriendInfo[]; +// OutgoingFriendRequests: IFriendInfo[]; +// } +type IGetFriendsResponse = Record<"Current" | "IncomingFriendRequests" | "OutgoingFriendRequests", IFriendInfo[]>; diff --git a/src/controllers/api/getIgnoredUsersController.ts b/src/controllers/api/getIgnoredUsersController.ts index abadb91c..b3a6cf22 100644 --- a/src/controllers/api/getIgnoredUsersController.ts +++ b/src/controllers/api/getIgnoredUsersController.ts @@ -1,7 +1,7 @@ import { toOid } from "@/src/helpers/inventoryHelpers"; import { Account, Ignore } from "@/src/models/loginModel"; import { getAccountIdForRequest } from "@/src/services/loginService"; -import { IFriendInfo } from "@/src/types/guildTypes"; +import { IFriendInfo } from "@/src/types/friendTypes"; import { parallelForeach } from "@/src/utils/async-utils"; import { RequestHandler } from "express"; diff --git a/src/controllers/api/giftingController.ts b/src/controllers/api/giftingController.ts index f965e60c..0d5c4723 100644 --- a/src/controllers/api/giftingController.ts +++ b/src/controllers/api/giftingController.ts @@ -1,5 +1,6 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { Account } from "@/src/models/loginModel"; +import { areFriends } from "@/src/services/friendService"; import { createMessage } from "@/src/services/inboxService"; import { getInventory, updateCurrency } from "@/src/services/inventoryService"; import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService"; @@ -30,8 +31,11 @@ export const giftingController: RequestHandler = async (req, res) => { } // Cannot gift to players who have gifting disabled. - // TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented - if (inventory.Settings?.GiftMode == "GIFT_MODE_NONE") { + const senderAccount = await getAccountForRequest(req); + if ( + inventory.Settings?.GiftMode == "GIFT_MODE_NONE" || + (inventory.Settings?.GiftMode == "GIFT_MODE_FRIENDS" && !(await areFriends(account._id, senderAccount._id))) + ) { res.status(400).send("17").end(); return; } @@ -40,7 +44,6 @@ export const giftingController: RequestHandler = async (req, res) => { // TODO: Cannot gift archwing items to players that have not completed the archwing quest. (Code 7) // TODO: Cannot gift necramechs to players that have not completed heart of deimos. (Code 20) - const senderAccount = await getAccountForRequest(req); const senderInventory = await getInventory( senderAccount._id.toString(), "PremiumCredits PremiumCreditsFree ActiveAvatarImageType GiftsRemaining" diff --git a/src/controllers/api/removeFriendController.ts b/src/controllers/api/removeFriendController.ts new file mode 100644 index 00000000..c2f3d27c --- /dev/null +++ b/src/controllers/api/removeFriendController.ts @@ -0,0 +1,36 @@ +import { toOid } from "@/src/helpers/inventoryHelpers"; +import { Friendship } from "@/src/models/friendModel"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { IOid } from "@/src/types/commonTypes"; +import { RequestHandler } from "express"; + +export const removeFriendGetController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + if (req.query.all) { + const [internalFriendships, externalFriendships] = await Promise.all([ + Friendship.find({ owner: accountId }, "friend"), + Friendship.find({ friend: accountId }, "owner") + ]); + const promises: Promise[] = []; + const friends: IOid[] = []; + for (const externalFriendship of externalFriendships) { + if (!internalFriendships.find(x => x.friend == externalFriendship.owner)) { + promises.push(Friendship.deleteOne({ _id: externalFriendship._id }) as unknown as Promise); + friends.push(toOid(externalFriendship.owner)); + } + } + await Promise.all(promises); + res.json({ + Friends: friends + }); + } else { + const friendId = req.query.friendId as string; + await Promise.all([ + Friendship.deleteOne({ owner: accountId, friend: friendId }), + Friendship.deleteOne({ owner: friendId, friend: accountId }) + ]); + res.json({ + Friends: [{ $oid: friendId } satisfies IOid] + }); + } +}; diff --git a/src/controllers/api/setFriendNoteController.ts b/src/controllers/api/setFriendNoteController.ts new file mode 100644 index 00000000..c12543da --- /dev/null +++ b/src/controllers/api/setFriendNoteController.ts @@ -0,0 +1,30 @@ +import { getJSONfromString } from "@/src/helpers/stringHelpers"; +import { Friendship } from "@/src/models/friendModel"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { RequestHandler } from "express"; + +export const setFriendNoteController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const payload = getJSONfromString(String(req.body)); + const friendship = await Friendship.findOne({ owner: accountId, friend: payload.FriendId }, "Note Favorite"); + if (friendship) { + if ("Note" in payload) { + friendship.Note = payload.Note; + } else { + friendship.Favorite = payload.Favorite; + } + await friendship.save(); + } + res.json({ + Id: payload.FriendId, + SetNote: "Note" in payload, + Note: friendship?.Note, + Favorite: friendship?.Favorite + }); +}; + +interface ISetFriendNoteRequest { + FriendId: string; + Note?: string; + Favorite?: boolean; +} diff --git a/src/controllers/custom/deleteAccountController.ts b/src/controllers/custom/deleteAccountController.ts index 32fe4f19..449a44c4 100644 --- a/src/controllers/custom/deleteAccountController.ts +++ b/src/controllers/custom/deleteAccountController.ts @@ -10,6 +10,7 @@ import { Stats } from "@/src/models/statsModel"; import { GuildMember } from "@/src/models/guildModel"; import { Leaderboard } from "@/src/models/leaderboardModel"; import { deleteGuild } from "@/src/services/guildService"; +import { Friendship } from "@/src/models/friendModel"; export const deleteAccountController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -22,6 +23,8 @@ export const deleteAccountController: RequestHandler = async (req, res) => { await Promise.all([ Account.deleteOne({ _id: accountId }), + Friendship.deleteMany({ owner: accountId }), + Friendship.deleteMany({ friend: accountId }), GuildMember.deleteMany({ accountId: accountId }), Ignore.deleteMany({ ignorer: accountId }), Ignore.deleteMany({ ignoree: accountId }), diff --git a/src/models/friendModel.ts b/src/models/friendModel.ts new file mode 100644 index 00000000..f253101a --- /dev/null +++ b/src/models/friendModel.ts @@ -0,0 +1,15 @@ +import { IFriendship } from "@/src/types/friendTypes"; +import { model, Schema } from "mongoose"; + +const friendshipSchema = new Schema({ + owner: { type: Schema.Types.ObjectId, required: true }, + friend: { type: Schema.Types.ObjectId, required: true }, + Note: String, + Favorite: Boolean +}); + +friendshipSchema.index({ owner: 1 }); +friendshipSchema.index({ friend: 1 }); +friendshipSchema.index({ owner: 1, friend: 1 }, { unique: true }); + +export const Friendship = model("Friendship", friendshipSchema); diff --git a/src/routes/api.ts b/src/routes/api.ts index 787164d9..8fdd94df 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -3,8 +3,10 @@ import { abandonLibraryDailyTaskController } from "@/src/controllers/api/abandon import { abortDojoComponentController } from "@/src/controllers/api/abortDojoComponentController"; import { abortDojoComponentDestructionController } from "@/src/controllers/api/abortDojoComponentDestructionController"; import { activateRandomModController } from "@/src/controllers/api/activateRandomModController"; +import { addFriendController } from "@/src/controllers/api/addFriendController"; import { addFriendImageController } from "@/src/controllers/api/addFriendImageController"; import { addIgnoredUserController } from "@/src/controllers/api/addIgnoredUserController"; +import { addPendingFriendController } from "@/src/controllers/api/addPendingFriendController"; import { addToAllianceController } from "@/src/controllers/api/addToAllianceController"; import { addToGuildController } from "@/src/controllers/api/addToGuildController"; import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController"; @@ -101,6 +103,7 @@ import { questControlController } from "@/src/controllers/api/questControlContro import { queueDojoComponentDestructionController } from "@/src/controllers/api/queueDojoComponentDestructionController"; import { redeemPromoCodeController } from "@/src/controllers/api/redeemPromoCodeController"; import { releasePetController } from "@/src/controllers/api/releasePetController"; +import { removeFriendGetController } from "@/src/controllers/api/removeFriendController"; import { removeFromAllianceController } from "@/src/controllers/api/removeFromAllianceController"; import { removeFromGuildController } from "@/src/controllers/api/removeFromGuildController"; import { removeIgnoredUserController } from "@/src/controllers/api/removeIgnoredUserController"; @@ -120,6 +123,7 @@ import { setDojoComponentColorsController } from "@/src/controllers/api/setDojoC import { setDojoComponentMessageController } from "@/src/controllers/api/setDojoComponentMessageController"; import { setDojoComponentSettingsController } from "@/src/controllers/api/setDojoComponentSettingsController"; import { setEquippedInstrumentController } from "@/src/controllers/api/setEquippedInstrumentController"; +import { setFriendNoteController } from "@/src/controllers/api/setFriendNoteController"; import { setGuildMotdController } from "@/src/controllers/api/setGuildMotdController"; import { setHubNpcCustomizationsController } from "@/src/controllers/api/setHubNpcCustomizationsController"; import { setPlacedDecoInfoController } from "@/src/controllers/api/setPlacedDecoInfoController"; @@ -180,13 +184,13 @@ apiRouter.get("/getGuildContributions.php", getGuildContributionsController); apiRouter.get("/getGuildDojo.php", getGuildDojoController); apiRouter.get("/getGuildLog.php", getGuildLogController); apiRouter.get("/getIgnoredUsers.php", getIgnoredUsersController); +apiRouter.get("/getMessages.php", inboxController); // unsure if this is correct, but needed for U17 apiRouter.get("/getNewRewardSeed.php", getNewRewardSeedController); apiRouter.get("/getShip.php", getShipController); apiRouter.get("/getVendorInfo.php", getVendorInfoController); apiRouter.get("/hub", hubController); apiRouter.get("/hubInstances", hubInstancesController); apiRouter.get("/inbox.php", inboxController); -apiRouter.get("/getMessages.php", inboxController); // unsure if this is correct, but needed for U17 apiRouter.get("/inventory.php", inventoryController); apiRouter.get("/loginRewards.php", loginRewardsController); apiRouter.get("/logout.php", logoutController); @@ -196,6 +200,7 @@ apiRouter.get("/modularWeaponSale.php", modularWeaponSaleController); apiRouter.get("/playedParkourTutorial.php", playedParkourTutorialController); apiRouter.get("/questControl.php", questControlController); apiRouter.get("/queueDojoComponentDestruction.php", queueDojoComponentDestructionController); +apiRouter.get("/removeFriend.php", removeFriendGetController); apiRouter.get("/removeFromAlliance.php", removeFromAllianceController); apiRouter.get("/setActiveQuest.php", setActiveQuestController); apiRouter.get("/setActiveShip.php", setActiveShipController); @@ -213,8 +218,10 @@ apiRouter.get("/updateSession.php", updateSessionGetController); // post apiRouter.post("/abortDojoComponent.php", abortDojoComponentController); apiRouter.post("/activateRandomMod.php", activateRandomModController); +apiRouter.post("/addFriend.php", addFriendController); apiRouter.post("/addFriendImage.php", addFriendImageController); apiRouter.post("/addIgnoredUser.php", addIgnoredUserController); +apiRouter.post("/addPendingFriend.php", addPendingFriendController); apiRouter.post("/addToAlliance.php", addToAllianceController); apiRouter.post("/addToGuild.php", addToGuildController); apiRouter.post("/arcaneCommon.php", arcaneCommonController); @@ -297,6 +304,7 @@ apiRouter.post("/setDojoComponentColors.php", setDojoComponentColorsController); apiRouter.post("/setDojoComponentMessage.php", setDojoComponentMessageController); apiRouter.post("/setDojoComponentSettings.php", setDojoComponentSettingsController); apiRouter.post("/setEquippedInstrument.php", setEquippedInstrumentController); +apiRouter.post("/setFriendNote.php", setFriendNoteController); apiRouter.post("/setGuildMotd.php", setGuildMotdController); apiRouter.post("/setHubNpcCustomizations.php", setHubNpcCustomizationsController); apiRouter.post("/setPlacedDecoInfo.php", setPlacedDecoInfoController); diff --git a/src/services/friendService.ts b/src/services/friendService.ts new file mode 100644 index 00000000..a1e1b67c --- /dev/null +++ b/src/services/friendService.ts @@ -0,0 +1,44 @@ +import { IFriendInfo } from "../types/friendTypes"; +import { getInventory } from "./inventoryService"; +import { config } from "./configService"; +import { Account } from "../models/loginModel"; +import { Types } from "mongoose"; +import { Friendship } from "../models/friendModel"; + +export const addAccountDataToFriendInfo = async (info: IFriendInfo): Promise => { + info.DisplayName = (await Account.findById(info._id.$oid, "DisplayName"))!.DisplayName; +}; + +export const addInventoryDataToFriendInfo = async (info: IFriendInfo): Promise => { + const inventory = await getInventory(info._id.$oid, "PlayerLevel ActiveAvatarImageType"); + info.PlayerLevel = config.spoofMasteryRank == -1 ? inventory.PlayerLevel : config.spoofMasteryRank; + info.ActiveAvatarImageType = inventory.ActiveAvatarImageType; +}; + +export const areFriends = async (a: Types.ObjectId | string, b: Types.ObjectId | string): Promise => { + const [aAddedB, bAddedA] = await Promise.all([ + Friendship.exists({ owner: a, friend: b }), + Friendship.exists({ owner: b, friend: a }) + ]); + return Boolean(aAddedB && bAddedA); +}; + +export const areFriendsOfFriends = async (a: Types.ObjectId | string, b: Types.ObjectId | string): Promise => { + const [aInternalFriends, bInternalFriends] = await Promise.all([ + Friendship.find({ owner: a }), + Friendship.find({ owner: b }) + ]); + for (const aInternalFriend of aInternalFriends) { + if (bInternalFriends.find(x => x.friend == aInternalFriend.friend)) { + const c = aInternalFriend.friend; + const [cAcceptedA, cAcceptedB] = await Promise.all([ + Friendship.exists({ owner: c, friend: a }), + Friendship.exists({ owner: c, friend: b }) + ]); + if (cAcceptedA && cAcceptedB) { + return true; + } + } + } + return false; +}; diff --git a/src/services/guildService.ts b/src/services/guildService.ts index db6f1525..f1bb5ff9 100644 --- a/src/services/guildService.ts +++ b/src/services/guildService.ts @@ -24,7 +24,6 @@ import { Types } from "mongoose"; import { ExportDojoRecipes, ExportResources, IDojoBuild, IDojoResearch } from "warframe-public-export-plus"; import { logger } from "../utils/logger"; import { config } from "./configService"; -import { Account } from "../models/loginModel"; import { getRandomInt } from "./rngService"; import { Inbox } from "../models/inboxModel"; import { IFusionTreasure, ITypeCount } from "../types/inventoryTypes/inventoryTypes"; @@ -32,6 +31,7 @@ import { IInventoryChanges } from "../types/purchaseTypes"; import { parallelForeach } from "../utils/async-utils"; import allDecoRecipes from "@/static/fixed_responses/allDecoRecipes.json"; import { createMessage } from "./inboxService"; +import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "./friendService"; export const getGuildForRequest = async (req: Request): Promise => { const accountId = await getAccountIdForRequest(req); @@ -71,12 +71,8 @@ export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: s if (guildMember.accountId.equals(accountId)) { missingEntry = false; } else { - dataFillInPromises.push( - (async (): Promise => { - member.DisplayName = (await Account.findById(guildMember.accountId, "DisplayName"))!.DisplayName; - })() - ); - dataFillInPromises.push(fillInInventoryDataForGuildMember(member)); + dataFillInPromises.push(addAccountDataToFriendInfo(member)); + dataFillInPromises.push(addInventoryDataToFriendInfo(member)); } members.push(member); } @@ -466,12 +462,6 @@ export const setDojoRoomLogFunded = (guild: TGuildDatabaseDocument, component: I } }; -export const fillInInventoryDataForGuildMember = async (member: IGuildMemberClient): Promise => { - const inventory = await getInventory(member._id.$oid, "PlayerLevel ActiveAvatarImageType"); - member.PlayerLevel = config.spoofMasteryRank == -1 ? inventory.PlayerLevel : config.spoofMasteryRank; - member.ActiveAvatarImageType = inventory.ActiveAvatarImageType; -}; - export const createUniqueClanName = async (name: string): Promise => { const initialDiscriminator = getRandomInt(0, 999); let discriminator = initialDiscriminator; diff --git a/src/types/friendTypes.ts b/src/types/friendTypes.ts new file mode 100644 index 00000000..24c2f9d7 --- /dev/null +++ b/src/types/friendTypes.ts @@ -0,0 +1,24 @@ +import { Types } from "mongoose"; +import { IMongoDate, IOid } from "./commonTypes"; + +export interface IFriendInfo { + _id: IOid; + DisplayName?: string; + PlatformNames?: string[]; + PlatformAccountId?: string; + Status?: number; + ActiveAvatarImageType?: string; + LastLogin?: IMongoDate; + PlayerLevel?: number; + Suffix?: number; + Note?: string; + Favorite?: boolean; + NewRequest?: boolean; +} + +export interface IFriendship { + owner: Types.ObjectId; + friend: Types.ObjectId; + Note?: string; + Favorite?: boolean; +} diff --git a/src/types/guildTypes.ts b/src/types/guildTypes.ts index 9dbb0d08..5f5ba8f8 100644 --- a/src/types/guildTypes.ts +++ b/src/types/guildTypes.ts @@ -2,6 +2,7 @@ import { Types } from "mongoose"; import { IOid, IMongoDate } from "@/src/types/commonTypes"; import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes"; import { IPictureFrameInfo } from "./shipTypes"; +import { IFriendInfo } from "./friendTypes"; export interface IGuildClient { _id: IOid; @@ -104,21 +105,6 @@ export interface IGuildMemberDatabase { ShipDecorationsContributed?: ITypeCount[]; } -export interface IFriendInfo { - _id: IOid; - DisplayName?: string; - PlatformNames?: string[]; - PlatformAccountId?: string; - Status?: number; - ActiveAvatarImageType?: string; - LastLogin?: IMongoDate; - PlayerLevel?: number; - Suffix?: number; - Note?: string; - Favorite?: boolean; - NewRequest?: boolean; -} - // GuildMemberInfo export interface IGuildMemberClient extends IFriendInfo { Rank: number; -- 2.47.2 From d1762c3426f8032da34d0fa84640966b1916851c Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 7 May 2025 11:10:30 +0200 Subject: [PATCH 2/2] == operator seems kinda unreliable for Types.ObjectId --- src/controllers/api/addFriendController.ts | 2 +- src/controllers/api/getFriendsController.ts | 4 ++-- src/controllers/api/removeFriendController.ts | 2 +- src/services/friendService.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/controllers/api/addFriendController.ts b/src/controllers/api/addFriendController.ts index f246f444..bbb629d9 100644 --- a/src/controllers/api/addFriendController.ts +++ b/src/controllers/api/addFriendController.ts @@ -17,7 +17,7 @@ export const addFriendController: RequestHandler = async (req, res) => { Friendship.find({ friend: accountId }, "owner") ]); for (const externalFriendship of externalFriendships) { - if (!internalFriendships.find(x => x.friend == externalFriendship.owner)) { + if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) { promises.push( Friendship.insertOne({ owner: accountId, diff --git a/src/controllers/api/getFriendsController.ts b/src/controllers/api/getFriendsController.ts index 83d0c8a1..684e9bcc 100644 --- a/src/controllers/api/getFriendsController.ts +++ b/src/controllers/api/getFriendsController.ts @@ -18,7 +18,7 @@ export const getFriendsController: RequestHandler = async (req: Request, res: Re Friendship.find({ friend: accountId }, "owner Note") ]); for (const externalFriendship of externalFriendships) { - if (!internalFriendships.find(x => x.friend == externalFriendship.owner)) { + if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) { response.IncomingFriendRequests.push({ _id: toOid(externalFriendship.owner), Note: externalFriendship.Note @@ -29,7 +29,7 @@ export const getFriendsController: RequestHandler = async (req: Request, res: Re const friendInfo: IFriendInfo = { _id: toOid(internalFriendship.friend) }; - if (externalFriendships.find(x => x.owner == internalFriendship.friend)) { + if (externalFriendships.find(x => x.owner.equals(internalFriendship.friend))) { response.Current.push(friendInfo); } else { response.OutgoingFriendRequests.push(friendInfo); diff --git a/src/controllers/api/removeFriendController.ts b/src/controllers/api/removeFriendController.ts index c2f3d27c..20243753 100644 --- a/src/controllers/api/removeFriendController.ts +++ b/src/controllers/api/removeFriendController.ts @@ -14,7 +14,7 @@ export const removeFriendGetController: RequestHandler = async (req, res) => { const promises: Promise[] = []; const friends: IOid[] = []; for (const externalFriendship of externalFriendships) { - if (!internalFriendships.find(x => x.friend == externalFriendship.owner)) { + if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) { promises.push(Friendship.deleteOne({ _id: externalFriendship._id }) as unknown as Promise); friends.push(toOid(externalFriendship.owner)); } diff --git a/src/services/friendService.ts b/src/services/friendService.ts index a1e1b67c..a17c9750 100644 --- a/src/services/friendService.ts +++ b/src/services/friendService.ts @@ -29,7 +29,7 @@ export const areFriendsOfFriends = async (a: Types.ObjectId | string, b: Types.O Friendship.find({ owner: b }) ]); for (const aInternalFriend of aInternalFriends) { - if (bInternalFriends.find(x => x.friend == aInternalFriend.friend)) { + if (bInternalFriends.find(x => x.friend.equals(aInternalFriend.friend))) { const c = aInternalFriend.friend; const [cAcceptedA, cAcceptedB] = await Promise.all([ Friendship.exists({ owner: c, friend: a }), -- 2.47.2