XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFESpaceNinjaServer

feat: friends (#2004)

Closes #1288 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2004 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>

c56507e1
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

16 个文件 +343 -50
Added src/controllers/api/addFriendController.ts +60 -0
@@ -0,0 +1,60 @@
1 import { toOid } from "@/src/helpers/inventoryHelpers";
2 import { getJSONfromString } from "@/src/helpers/stringHelpers";
3 import { Friendship } from "@/src/models/friendModel";
4 import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "@/src/services/friendService";
5 import { getAccountIdForRequest } from "@/src/services/loginService";
6 import { IFriendInfo } from "@/src/types/friendTypes";
7 import { RequestHandler } from "express";
8
9 export const addFriendController: RequestHandler = async (req, res) => {
10 const accountId = await getAccountIdForRequest(req);
11 const payload = getJSONfromString<IAddFriendRequest>(String(req.body));
12 const promises: Promise<void>[] = [];
13 const newFriends: IFriendInfo[] = [];
14 if (payload.friend == "all") {
15 const [internalFriendships, externalFriendships] = await Promise.all([
16 Friendship.find({ owner: accountId }, "friend"),
17 Friendship.find({ friend: accountId }, "owner")
18 ]);
19 for (const externalFriendship of externalFriendships) {
20 if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) {
21 promises.push(
22 Friendship.insertOne({
23 owner: accountId,
24 friend: externalFriendship.owner,
25 Note: externalFriendship.Note // TOVERIFY: Should the note be copied when accepting a friend request?
26 }) as unknown as Promise<void>
27 );
28 newFriends.push({
29 _id: toOid(externalFriendship.owner)
30 });
31 }
32 }
33 } else {
34 const externalFriendship = await Friendship.findOne({ owner: payload.friend, friend: accountId }, "Note");
35 if (externalFriendship) {
36 promises.push(
37 Friendship.insertOne({
38 owner: accountId,
39 friend: payload.friend,
40 Note: externalFriendship.Note
41 }) as unknown as Promise<void>
42 );
43 newFriends.push({
44 _id: { $oid: payload.friend }
45 });
46 }
47 }
48 for (const newFriend of newFriends) {
49 promises.push(addAccountDataToFriendInfo(newFriend));
50 promises.push(addInventoryDataToFriendInfo(newFriend));
51 }
52 await Promise.all(promises);
53 res.json({
54 Friends: newFriends
55 });
56 };
57
58 interface IAddFriendRequest {
59 friend: string; // oid or "all" in which case all=1 is also a query parameter
60 }
Modified src/controllers/api/addIgnoredUserController.ts +1 -1
@@ -2,7 +2,7 @@ import { toOid } from "@/src/helpers/inventoryHelpers";
2 2 import { getJSONfromString } from "@/src/helpers/stringHelpers";
3 3 import { Account, Ignore } from "@/src/models/loginModel";
4 4 import { getAccountIdForRequest } from "@/src/services/loginService";
5 import { IFriendInfo } from "@/src/types/guildTypes";
5 import { IFriendInfo } from "@/src/types/friendTypes";
6 6 import { RequestHandler } from "express";
7 7
8 8 export const addIgnoredUserController: RequestHandler = async (req, res) => {
Added src/controllers/api/addPendingFriendController.ts +51 -0
@@ -0,0 +1,51 @@
1 import { toOid } from "@/src/helpers/inventoryHelpers";
2 import { getJSONfromString } from "@/src/helpers/stringHelpers";
3 import { Friendship } from "@/src/models/friendModel";
4 import { Account } from "@/src/models/loginModel";
5 import { addInventoryDataToFriendInfo, areFriendsOfFriends } from "@/src/services/friendService";
6 import { getInventory } from "@/src/services/inventoryService";
7 import { getAccountIdForRequest } from "@/src/services/loginService";
8 import { IFriendInfo } from "@/src/types/friendTypes";
9 import { RequestHandler } from "express";
10
11 export const addPendingFriendController: RequestHandler = async (req, res) => {
12 const payload = getJSONfromString<IAddPendingFriendRequest>(String(req.body));
13
14 const account = await Account.findOne({ DisplayName: payload.friend });
15 if (!account) {
16 res.status(400).end();
17 return;
18 }
19
20 const accountId = await getAccountIdForRequest(req);
21 const inventory = await getInventory(account._id.toString(), "Settings");
22 if (
23 inventory.Settings?.FriendInvRestriction == "GIFT_MODE_NONE" ||
24 (inventory.Settings?.FriendInvRestriction == "GIFT_MODE_FRIENDS" &&
25 !(await areFriendsOfFriends(account._id, accountId)))
26 ) {
27 res.status(400).send("Friend Invite Restriction");
28 return;
29 }
30
31 await Friendship.insertOne({
32 owner: accountId,
33 friend: account._id,
34 Note: payload.message
35 });
36
37 const friendInfo: IFriendInfo = {
38 _id: toOid(account._id),
39 DisplayName: account.DisplayName,
40 Note: payload.message
41 };
42 await addInventoryDataToFriendInfo(friendInfo);
43 res.json({
44 Friend: friendInfo
45 });
46 };
47
48 interface IAddPendingFriendRequest {
49 friend: string;
50 message: string;
51 }
Modified src/controllers/api/addToGuildController.ts +9 -5
@@ -1,6 +1,7 @@
1 1 import { Guild, GuildMember } from "@/src/models/guildModel";
2 2 import { Account } from "@/src/models/loginModel";
3 import { fillInInventoryDataForGuildMember, hasGuildPermission } from "@/src/services/guildService";
3 import { addInventoryDataToFriendInfo, areFriends } from "@/src/services/friendService";
4 import { hasGuildPermission } from "@/src/services/guildService";
4 5 import { createMessage } from "@/src/services/inboxService";
5 6 import { getInventory } from "@/src/services/inventoryService";
6 7 import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
@@ -22,15 +23,18 @@ export const addToGuildController: RequestHandler = async (req, res) => {
22 23 return;
23 24 }
24 25
26 const senderAccount = await getAccountForRequest(req);
25 27 const inventory = await getInventory(account._id.toString(), "Settings");
26 // TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
27 if (inventory.Settings?.GuildInvRestriction == "GIFT_MODE_NONE") {
28 if (
29 inventory.Settings?.GuildInvRestriction == "GIFT_MODE_NONE" ||
30 (inventory.Settings?.GuildInvRestriction == "GIFT_MODE_FRIENDS" &&
31 !(await areFriends(account._id, senderAccount._id)))
32 ) {
28 33 res.status(400).json("Invite restricted");
29 34 return;
30 35 }
31 36
32 37 const guild = (await Guild.findById(payload.GuildId.$oid, "Name Ranks"))!;
33 const senderAccount = await getAccountForRequest(req);
34 38 if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) {
35 39 res.status(400).json("Invalid permission");
36 40 }
@@ -74,7 +78,7 @@ export const addToGuildController: RequestHandler = async (req, res) => {
74 78 Rank: 7,
75 79 Status: 2
76 80 };
77 await fillInInventoryDataForGuildMember(member);
81 await addInventoryDataToFriendInfo(member);
78 82 res.json({ NewMember: member });
79 83 } else if ("RequestMsg" in payload) {
80 84 // Player applying to join a clan
Modified src/controllers/api/getFriendsController.ts +50 -11
@@ -1,15 +1,54 @@
1 import { Request, Response } from "express";
1 import { toOid } from "@/src/helpers/inventoryHelpers";
2 import { Friendship } from "@/src/models/friendModel";
3 import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "@/src/services/friendService";
4 import { getAccountIdForRequest } from "@/src/services/loginService";
5 import { IFriendInfo } from "@/src/types/friendTypes";
6 import { Request, RequestHandler, Response } from "express";
2 7
3 8 // POST with {} instead of GET as of 38.5.0
4 const getFriendsController = (_request: Request, response: Response): void => {
5 response.writeHead(200, {
6 //Connection: "keep-alive",
7 //"Content-Encoding": "gzip",
8 "Content-Type": "text/html",
9 // charset: "UTF - 8",
10 "Content-Length": "3"
11 });
12 response.end(Buffer.from([0x7b, 0x7d, 0x0a]));
9 export const getFriendsController: RequestHandler = async (req: Request, res: Response) => {
10 const accountId = await getAccountIdForRequest(req);
11 const response: IGetFriendsResponse = {
12 Current: [],
13 IncomingFriendRequests: [],
14 OutgoingFriendRequests: []
15 };
16 const [internalFriendships, externalFriendships] = await Promise.all([
17 Friendship.find({ owner: accountId }),
18 Friendship.find({ friend: accountId }, "owner Note")
19 ]);
20 for (const externalFriendship of externalFriendships) {
21 if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) {
22 response.IncomingFriendRequests.push({
23 _id: toOid(externalFriendship.owner),
24 Note: externalFriendship.Note
25 });
26 }
27 }
28 for (const internalFriendship of internalFriendships) {
29 const friendInfo: IFriendInfo = {
30 _id: toOid(internalFriendship.friend)
31 };
32 if (externalFriendships.find(x => x.owner.equals(internalFriendship.friend))) {
33 response.Current.push(friendInfo);
34 } else {
35 response.OutgoingFriendRequests.push(friendInfo);
36 }
37 }
38 const promises: Promise<void>[] = [];
39 for (const arr of Object.values(response)) {
40 for (const friendInfo of arr) {
41 promises.push(addAccountDataToFriendInfo(friendInfo));
42 promises.push(addInventoryDataToFriendInfo(friendInfo));
43 }
44 }
45 await Promise.all(promises);
46 res.json(response);
13 47 };
14 48
15 export { getFriendsController };
49 // interface IGetFriendsResponse {
50 // Current: IFriendInfo[];
51 // IncomingFriendRequests: IFriendInfo[];
52 // OutgoingFriendRequests: IFriendInfo[];
53 // }
54 type IGetFriendsResponse = Record<"Current" | "IncomingFriendRequests" | "OutgoingFriendRequests", IFriendInfo[]>;
Modified src/controllers/api/getIgnoredUsersController.ts +1 -1
@@ -1,7 +1,7 @@
1 1 import { toOid } from "@/src/helpers/inventoryHelpers";
2 2 import { Account, Ignore } from "@/src/models/loginModel";
3 3 import { getAccountIdForRequest } from "@/src/services/loginService";
4 import { IFriendInfo } from "@/src/types/guildTypes";
4 import { IFriendInfo } from "@/src/types/friendTypes";
5 5 import { parallelForeach } from "@/src/utils/async-utils";
6 6 import { RequestHandler } from "express";
7 7
Modified src/controllers/api/giftingController.ts +6 -3
@@ -1,5 +1,6 @@
1 1 import { getJSONfromString } from "@/src/helpers/stringHelpers";
2 2 import { Account } from "@/src/models/loginModel";
3 import { areFriends } from "@/src/services/friendService";
3 4 import { createMessage } from "@/src/services/inboxService";
4 5 import { getInventory, updateCurrency } from "@/src/services/inventoryService";
5 6 import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
@@ -30,8 +31,11 @@ export const giftingController: RequestHandler = async (req, res) => {
30 31 }
31 32
32 33 // Cannot gift to players who have gifting disabled.
33 // TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
34 if (inventory.Settings?.GiftMode == "GIFT_MODE_NONE") {
34 const senderAccount = await getAccountForRequest(req);
35 if (
36 inventory.Settings?.GiftMode == "GIFT_MODE_NONE" ||
37 (inventory.Settings?.GiftMode == "GIFT_MODE_FRIENDS" && !(await areFriends(account._id, senderAccount._id)))
38 ) {
35 39 res.status(400).send("17").end();
36 40 return;
37 41 }
@@ -40,7 +44,6 @@ export const giftingController: RequestHandler = async (req, res) => {
40 44 // TODO: Cannot gift archwing items to players that have not completed the archwing quest. (Code 7)
41 45 // TODO: Cannot gift necramechs to players that have not completed heart of deimos. (Code 20)
42 46
43 const senderAccount = await getAccountForRequest(req);
44 47 const senderInventory = await getInventory(
45 48 senderAccount._id.toString(),
46 49 "PremiumCredits PremiumCreditsFree ActiveAvatarImageType GiftsRemaining"
Added src/controllers/api/removeFriendController.ts +36 -0
@@ -0,0 +1,36 @@
1 import { toOid } from "@/src/helpers/inventoryHelpers";
2 import { Friendship } from "@/src/models/friendModel";
3 import { getAccountIdForRequest } from "@/src/services/loginService";
4 import { IOid } from "@/src/types/commonTypes";
5 import { RequestHandler } from "express";
6
7 export const removeFriendGetController: RequestHandler = async (req, res) => {
8 const accountId = await getAccountIdForRequest(req);
9 if (req.query.all) {
10 const [internalFriendships, externalFriendships] = await Promise.all([
11 Friendship.find({ owner: accountId }, "friend"),
12 Friendship.find({ friend: accountId }, "owner")
13 ]);
14 const promises: Promise<void>[] = [];
15 const friends: IOid[] = [];
16 for (const externalFriendship of externalFriendships) {
17 if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) {
18 promises.push(Friendship.deleteOne({ _id: externalFriendship._id }) as unknown as Promise<void>);
19 friends.push(toOid(externalFriendship.owner));
20 }
21 }
22 await Promise.all(promises);
23 res.json({
24 Friends: friends
25 });
26 } else {
27 const friendId = req.query.friendId as string;
28 await Promise.all([
29 Friendship.deleteOne({ owner: accountId, friend: friendId }),
30 Friendship.deleteOne({ owner: friendId, friend: accountId })
31 ]);
32 res.json({
33 Friends: [{ $oid: friendId } satisfies IOid]
34 });
35 }
36 };
Added src/controllers/api/setFriendNoteController.ts +30 -0
@@ -0,0 +1,30 @@
1 import { getJSONfromString } from "@/src/helpers/stringHelpers";
2 import { Friendship } from "@/src/models/friendModel";
3 import { getAccountIdForRequest } from "@/src/services/loginService";
4 import { RequestHandler } from "express";
5
6 export const setFriendNoteController: RequestHandler = async (req, res) => {
7 const accountId = await getAccountIdForRequest(req);
8 const payload = getJSONfromString<ISetFriendNoteRequest>(String(req.body));
9 const friendship = await Friendship.findOne({ owner: accountId, friend: payload.FriendId }, "Note Favorite");
10 if (friendship) {
11 if ("Note" in payload) {
12 friendship.Note = payload.Note;
13 } else {
14 friendship.Favorite = payload.Favorite;
15 }
16 await friendship.save();
17 }
18 res.json({
19 Id: payload.FriendId,
20 SetNote: "Note" in payload,
21 Note: friendship?.Note,
22 Favorite: friendship?.Favorite
23 });
24 };
25
26 interface ISetFriendNoteRequest {
27 FriendId: string;
28 Note?: string;
29 Favorite?: boolean;
30 }
Modified src/controllers/custom/deleteAccountController.ts +3 -0
@@ -10,6 +10,7 @@ import { Stats } from "@/src/models/statsModel";
10 10 import { GuildMember } from "@/src/models/guildModel";
11 11 import { Leaderboard } from "@/src/models/leaderboardModel";
12 12 import { deleteGuild } from "@/src/services/guildService";
13 import { Friendship } from "@/src/models/friendModel";
13 14
14 15 export const deleteAccountController: RequestHandler = async (req, res) => {
15 16 const accountId = await getAccountIdForRequest(req);
@@ -22,6 +23,8 @@ export const deleteAccountController: RequestHandler = async (req, res) => {
22 23
23 24 await Promise.all([
24 25 Account.deleteOne({ _id: accountId }),
26 Friendship.deleteMany({ owner: accountId }),
27 Friendship.deleteMany({ friend: accountId }),
25 28 GuildMember.deleteMany({ accountId: accountId }),
26 29 Ignore.deleteMany({ ignorer: accountId }),
27 30 Ignore.deleteMany({ ignoree: accountId }),
Added src/models/friendModel.ts +15 -0
@@ -0,0 +1,15 @@
1 import { IFriendship } from "@/src/types/friendTypes";
2 import { model, Schema } from "mongoose";
3
4 const friendshipSchema = new Schema<IFriendship>({
5 owner: { type: Schema.Types.ObjectId, required: true },
6 friend: { type: Schema.Types.ObjectId, required: true },
7 Note: String,
8 Favorite: Boolean
9 });
10
11 friendshipSchema.index({ owner: 1 });
12 friendshipSchema.index({ friend: 1 });
13 friendshipSchema.index({ owner: 1, friend: 1 }, { unique: true });
14
15 export const Friendship = model<IFriendship>("Friendship", friendshipSchema);
Modified src/routes/api.ts +9 -1
@@ -3,8 +3,10 @@ import { abandonLibraryDailyTaskController } from "@/src/controllers/api/abandon
3 3 import { abortDojoComponentController } from "@/src/controllers/api/abortDojoComponentController";
4 4 import { abortDojoComponentDestructionController } from "@/src/controllers/api/abortDojoComponentDestructionController";
5 5 import { activateRandomModController } from "@/src/controllers/api/activateRandomModController";
6 import { addFriendController } from "@/src/controllers/api/addFriendController";
6 7 import { addFriendImageController } from "@/src/controllers/api/addFriendImageController";
7 8 import { addIgnoredUserController } from "@/src/controllers/api/addIgnoredUserController";
9 import { addPendingFriendController } from "@/src/controllers/api/addPendingFriendController";
8 10 import { addToAllianceController } from "@/src/controllers/api/addToAllianceController";
9 11 import { addToGuildController } from "@/src/controllers/api/addToGuildController";
10 12 import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
@@ -101,6 +103,7 @@ import { questControlController } from "@/src/controllers/api/questControlContro
101 103 import { queueDojoComponentDestructionController } from "@/src/controllers/api/queueDojoComponentDestructionController";
102 104 import { redeemPromoCodeController } from "@/src/controllers/api/redeemPromoCodeController";
103 105 import { releasePetController } from "@/src/controllers/api/releasePetController";
106 import { removeFriendGetController } from "@/src/controllers/api/removeFriendController";
104 107 import { removeFromAllianceController } from "@/src/controllers/api/removeFromAllianceController";
105 108 import { removeFromGuildController } from "@/src/controllers/api/removeFromGuildController";
106 109 import { removeIgnoredUserController } from "@/src/controllers/api/removeIgnoredUserController";
@@ -120,6 +123,7 @@ import { setDojoComponentColorsController } from "@/src/controllers/api/setDojoC
120 123 import { setDojoComponentMessageController } from "@/src/controllers/api/setDojoComponentMessageController";
121 124 import { setDojoComponentSettingsController } from "@/src/controllers/api/setDojoComponentSettingsController";
122 125 import { setEquippedInstrumentController } from "@/src/controllers/api/setEquippedInstrumentController";
126 import { setFriendNoteController } from "@/src/controllers/api/setFriendNoteController";
123 127 import { setGuildMotdController } from "@/src/controllers/api/setGuildMotdController";
124 128 import { setHubNpcCustomizationsController } from "@/src/controllers/api/setHubNpcCustomizationsController";
125 129 import { setPlacedDecoInfoController } from "@/src/controllers/api/setPlacedDecoInfoController";
@@ -180,13 +184,13 @@ apiRouter.get("/getGuildContributions.php", getGuildContributionsController);
180 184 apiRouter.get("/getGuildDojo.php", getGuildDojoController);
181 185 apiRouter.get("/getGuildLog.php", getGuildLogController);
182 186 apiRouter.get("/getIgnoredUsers.php", getIgnoredUsersController);
187 apiRouter.get("/getMessages.php", inboxController); // unsure if this is correct, but needed for U17
183 188 apiRouter.get("/getNewRewardSeed.php", getNewRewardSeedController);
184 189 apiRouter.get("/getShip.php", getShipController);
185 190 apiRouter.get("/getVendorInfo.php", getVendorInfoController);
186 191 apiRouter.get("/hub", hubController);
187 192 apiRouter.get("/hubInstances", hubInstancesController);
188 193 apiRouter.get("/inbox.php", inboxController);
189 apiRouter.get("/getMessages.php", inboxController); // unsure if this is correct, but needed for U17
190 194 apiRouter.get("/inventory.php", inventoryController);
191 195 apiRouter.get("/loginRewards.php", loginRewardsController);
192 196 apiRouter.get("/logout.php", logoutController);
@@ -196,6 +200,7 @@ apiRouter.get("/modularWeaponSale.php", modularWeaponSaleController);
196 200 apiRouter.get("/playedParkourTutorial.php", playedParkourTutorialController);
197 201 apiRouter.get("/questControl.php", questControlController);
198 202 apiRouter.get("/queueDojoComponentDestruction.php", queueDojoComponentDestructionController);
203 apiRouter.get("/removeFriend.php", removeFriendGetController);
199 204 apiRouter.get("/removeFromAlliance.php", removeFromAllianceController);
200 205 apiRouter.get("/setActiveQuest.php", setActiveQuestController);
201 206 apiRouter.get("/setActiveShip.php", setActiveShipController);
@@ -213,8 +218,10 @@ apiRouter.get("/updateSession.php", updateSessionGetController);
213 218 // post
214 219 apiRouter.post("/abortDojoComponent.php", abortDojoComponentController);
215 220 apiRouter.post("/activateRandomMod.php", activateRandomModController);
221 apiRouter.post("/addFriend.php", addFriendController);
216 222 apiRouter.post("/addFriendImage.php", addFriendImageController);
217 223 apiRouter.post("/addIgnoredUser.php", addIgnoredUserController);
224 apiRouter.post("/addPendingFriend.php", addPendingFriendController);
218 225 apiRouter.post("/addToAlliance.php", addToAllianceController);
219 226 apiRouter.post("/addToGuild.php", addToGuildController);
220 227 apiRouter.post("/arcaneCommon.php", arcaneCommonController);
@@ -297,6 +304,7 @@ apiRouter.post("/setDojoComponentColors.php", setDojoComponentColorsController);
297 304 apiRouter.post("/setDojoComponentMessage.php", setDojoComponentMessageController);
298 305 apiRouter.post("/setDojoComponentSettings.php", setDojoComponentSettingsController);
299 306 apiRouter.post("/setEquippedInstrument.php", setEquippedInstrumentController);
307 apiRouter.post("/setFriendNote.php", setFriendNoteController);
300 308 apiRouter.post("/setGuildMotd.php", setGuildMotdController);
301 309 apiRouter.post("/setHubNpcCustomizations.php", setHubNpcCustomizationsController);
302 310 apiRouter.post("/setPlacedDecoInfo.php", setPlacedDecoInfoController);
Added src/services/friendService.ts +44 -0
Modified src/services/guildService.ts +3 -13
Added src/types/friendTypes.ts +24 -0
Modified src/types/guildTypes.ts +1 -15