返回提交历史
Modified
src/controllers/api/addFriendController.ts
+16
-1
Modified
src/controllers/api/addPendingFriendController.ts
+11
-0
Modified
src/controllers/api/removeFriendController.ts
+13
-9
Added
src/helpers/udp.ts
+28
-0
Modified
src/services/configService.ts
+11
-0
XFEstudio/XFESpaceNinjaServer
feat: notify pre-U40 clients about friendship changes from U40+ clients (#3597)
Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/3597 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
5a044998
代码差异
5 个文件
+79
-10
@@ -1,11 +1,14 @@
1
1
import type { Types } from "mongoose";
2
import { toOid } from "../../helpers/inventoryHelpers.ts";
2
import { fromOid, toOid, version_compare } from "../../helpers/inventoryHelpers.ts";
3
3
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
4
4
import { Friendship } from "../../models/friendModel.ts";
5
5
import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "../../services/friendService.ts";
6
6
import { getAccountForRequest, getAccountIdForRequest } from "../../services/loginService.ts";
7
7
import type { IFriendInfo } from "../../types/friendTypes.ts";
8
8
import type { RequestHandler } from "express";
9
import gameToBuildVersion from "../../constants/gameToBuildVersion.ts";
10
import { Account } from "../../models/loginModel.ts";
11
import { sendCustomMessageToNrs } from "../../helpers/udp.ts";
9
12
10
13
export const addFriendPostController: RequestHandler = async (req, res) => {
11
14
const account = await getAccountForRequest(req);
@@ -34,9 +37,21 @@ export const addFriendPostController: RequestHandler = async (req, res) => {
34
37
} else {
35
38
await acceptFriendRequest(account._id, payload.friend, newFriends);
36
39
}
40
const requesterUsesNrsNotifications =
41
account.BuildLabel && version_compare(account.BuildLabel, gameToBuildVersion["40.0.0"]) < 0;
37
42
for (const newFriend of newFriends) {
38
43
promises.push(addAccountDataToFriendInfo(newFriend, account.BuildLabel));
39
44
promises.push(addInventoryDataToFriendInfo(newFriend));
45
if (!requesterUsesNrsNotifications) {
46
void Account.findById(fromOid(newFriend._id), "BuildLabel").then(newFriendAcct => {
47
const requesteeUsesNrsNotifications =
48
newFriendAcct!.BuildLabel &&
49
version_compare(newFriendAcct!.BuildLabel, gameToBuildVersion["40.0.0"]) < 0;
50
if (requesteeUsesNrsNotifications) {
51
void sendCustomMessageToNrs(`addFriend,${account._id.toString()},${newFriendAcct!._id.toString()}`);
52
}
53
});
54
}
40
55
}
41
56
await Promise.all(promises);
42
57
res.json({
@@ -9,6 +9,7 @@ import type { IFriendInfo } from "../../types/friendTypes.ts";
9
9
import type { RequestHandler, Response } from "express";
10
10
import { logger } from "../../utils/logger.ts";
11
11
import gameToBuildVersion from "../../constants/gameToBuildVersion.ts";
12
import { sendCustomMessageToNrs } from "../../helpers/udp.ts";
12
13
13
14
export const addPendingFriendPostController: RequestHandler = async (req, res) => {
14
15
const account = await getAccountForRequest(req);
@@ -61,6 +62,16 @@ const sendFriendRequest = async (
61
62
return;
62
63
}
63
64
65
const requesterUsesNrsNotifications =
66
requesterAccount.BuildLabel && version_compare(requesterAccount.BuildLabel, gameToBuildVersion["40.0.0"]) < 0;
67
const requesteeUsesNrsNotifications =
68
requesteeAccount.BuildLabel && version_compare(requesteeAccount.BuildLabel, gameToBuildVersion["40.0.0"]) < 0;
69
if (requesteeUsesNrsNotifications && !requesterUsesNrsNotifications) {
70
void sendCustomMessageToNrs(
71
`addPendingFriend,${requesterAccount._id.toString()},${requesteeAccount._id.toString()}`
72
);
73
}
74
64
75
if (
65
76
!requesterAccount.BuildLabel ||
66
77
version_compare(requesterAccount.BuildLabel, gameToBuildVersion["13.0.0"]) >= 0
@@ -1,10 +1,11 @@
1
1
import gameToBuildVersion from "../../constants/gameToBuildVersion.ts";
2
2
import { fromOid, toOid2, version_compare } from "../../helpers/inventoryHelpers.ts";
3
3
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
4
import { sendCustomMessageToNrs } from "../../helpers/udp.ts";
4
5
import { Friendship } from "../../models/friendModel.ts";
5
6
import { Account } from "../../models/loginModel.ts";
6
7
import { getInventory } from "../../services/inventoryService.ts";
7
import { getAccountForRequest, getUnicodeName } from "../../services/loginService.ts";
8
import { getAccountForRequest, getUnicodeName, type TAccountDocument } from "../../services/loginService.ts";
8
9
import type { IOidWithLegacySupport } from "../../types/commonTypes.ts";
9
10
import { parallelForeach } from "../../utils/async-utils.ts";
10
11
import type { RequestHandler } from "express";
@@ -27,14 +28,14 @@ export const removeFriendGetController: RequestHandler = async (req, res) => {
27
28
}
28
29
}
29
30
await Promise.all(promises);
30
res.json(await toRemoveFriendsResponse(account.BuildLabel, friends));
31
res.json(await toRemoveFriendsResponse(account, friends));
31
32
} else {
32
33
const friendId = req.query.friendId as string;
33
34
await Promise.all([
34
35
Friendship.deleteOne({ owner: accountId, friend: friendId }),
35
36
Friendship.deleteOne({ owner: friendId, friend: accountId })
36
37
]);
37
res.json(await toRemoveFriendsResponse(account.BuildLabel, [{ $oid: friendId }]));
38
res.json(await toRemoveFriendsResponse(account, [{ $oid: friendId }]));
38
39
}
39
40
};
40
41
@@ -76,7 +77,7 @@ export const removeFriendPostController: RequestHandler = async (req, res) => {
76
77
}
77
78
}
78
79
await Promise.all(promises);
79
res.json(await toRemoveFriendsResponse(account.BuildLabel, removeFriendOids));
80
res.json(await toRemoveFriendsResponse(account, removeFriendOids));
80
81
};
81
82
82
83
// The friend ids format is a bit weird, e.g. when 6633b81e9dba0b714f28ff02 (A) is friends with 67cdac105ef1f4b49741c267 (B), A's friend id for B is 808000105ef1f40560ca079e and B's friend id for A is 8000b81e9dba0b06408a8075.
@@ -101,17 +102,20 @@ interface IRemoveFriendsResponseU39 {
101
102
}
102
103
103
104
const toRemoveFriendsResponse = async (
104
buildLabel: string | undefined,
105
account: TAccountDocument,
105
106
friends: IOidWithLegacySupport[]
106
107
): Promise<IRemoveFriendsResponseU39 | IRemoveFriendsResponseU40> => {
107
if (buildLabel && version_compare(buildLabel, gameToBuildVersion["40.0.0"]) < 0) {
108
if (account.BuildLabel && version_compare(account.BuildLabel, gameToBuildVersion["40.0.0"]) < 0) {
108
109
return { Friends: friends } satisfies IRemoveFriendsResponseU39;
109
110
} else {
110
111
const response: IRemoveFriendsResponseU40 = { FriendNames: [] };
111
112
for (const friend of friends) {
112
const acct = await Account.findById(fromOid(friend));
113
if (acct) {
114
response.FriendNames.push(getUnicodeName(acct, buildLabel));
113
const friendAcct = await Account.findById(fromOid(friend));
114
if (friendAcct) {
115
response.FriendNames.push(getUnicodeName(friendAcct, account.BuildLabel));
116
if (friendAcct.BuildLabel && version_compare(friendAcct.BuildLabel, gameToBuildVersion["40.0.0"]) < 0) {
117
void sendCustomMessageToNrs(`removeFriend,${account._id.toString()},${friendAcct._id.toString()}`);
118
}
115
119
}
116
120
}
117
121
return response;
@@ -0,0 +1,28 @@
1
import dgram from "node:dgram";
2
import { getNrsAddress } from "../services/configService.ts";
3
import { crc32 } from "node:zlib";
4
5
export const sendUdpMessage = (host: string, port: number, message: Buffer | string): Promise<void> => {
6
return new Promise<void>((resolve, reject) => {
7
const socket = dgram.createSocket("udp4");
8
socket.send(message, port, host, err => {
9
socket.close();
10
if (err) {
11
reject(err);
12
} else {
13
resolve();
14
}
15
});
16
});
17
};
18
19
export const sendCustomMessageToNrs = (message: string): Promise<void> => {
20
const [nrsAddr, nrsPort] = getNrsAddress();
21
const msgbuf = Buffer.from("\x00" + message, "utf8");
22
const crc = crc32(Buffer.from("b471e49539930dc9b5a131e6247c7387A", "utf8"), crc32(msgbuf));
23
return sendUdpMessage(
24
nrsAddr,
25
nrsPort,
26
Buffer.concat([Buffer.from([0x00, crc >> 24, crc >> 16, crc >> 8, crc]), msgbuf])
27
);
28
};
@@ -282,3 +282,14 @@ export const getWebServerParams = (): IWebServerParams => {
282
282
keyFile: config.httpsKeyFile || "static/cert/key.pem"
283
283
};
284
284
};
285
286
export const getNrsAddress = (): [string, number] => {
287
let nrsAddr = (config.nrsAddress || "%THIS_MACHINE%").split("%THIS_MACHINE%").join("127.0.0.1");
288
let nrsPort = 4950;
289
const colon = nrsAddr.indexOf(":");
290
if (colon != -1) {
291
nrsPort = parseInt(nrsAddr.substring(colon + 1));
292
nrsAddr = nrsAddr.substring(0, colon);
293
}
294
return [nrsAddr, nrsPort];
295
};