返回提交历史
Added
src/controllers/api/addFriendController.ts
+60
-0
Modified
src/controllers/api/addIgnoredUserController.ts
+1
-1
Added
src/controllers/api/addPendingFriendController.ts
+51
-0
Modified
src/controllers/api/addToGuildController.ts
+9
-5
Modified
src/controllers/api/getFriendsController.ts
+50
-11
Modified
src/controllers/api/getIgnoredUsersController.ts
+1
-1
Modified
src/controllers/api/giftingController.ts
+6
-3
Added
src/controllers/api/removeFriendController.ts
+36
-0
Added
src/controllers/api/setFriendNoteController.ts
+30
-0
Modified
src/controllers/custom/deleteAccountController.ts
+3
-0
Added
src/models/friendModel.ts
+15
-0
Modified
src/routes/api.ts
+9
-1
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
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
代码差异
16 个文件
+343
-50
@@ -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
}
@@ -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) => {
@@ -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
}
@@ -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
@@ -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[]>;
@@ -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
@@ -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"
@@ -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
};
@@ -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
}
@@ -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 }),
@@ -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);
@@ -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);