@brayjamin/cursed-network
Version:
Protocols for cross-server communication under the CursedMC proxy.
769 lines (767 loc) • 29.2 kB
text/typescript
import { type, command, fetch, manager, event, task } from '@grakkit/stdlib-paper';
import { Client, Server } from '@grakkit/socket';
import * as _ from '@brayjamin/underscore';
import { obcCommandSender, obePlayer } from '@grakkit/types-paper';
function decode (content: Uint8Array) {
let index = 0;
let string = '';
let extra1: number, extra2: number;
while (index < content.length) {
let char = content[index++];
switch (char >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
string += String.fromCharCode(char);
break;
case 12:
case 13:
extra1 = content[index++];
string += String.fromCharCode(((char & 0x1f) << 6) | (extra1 & 0x3f));
break;
case 14:
extra1 = content[index++];
extra2 = content[index++];
string += String.fromCharCode(((char & 0x0f) << 12) | ((extra1 & 0x3f) << 6) | ((extra2 & 0x3f) << 0));
break;
}
}
return string;
}
function encode (content: string) {
let index = 0;
const array = new Uint8Array(content.length);
while (index < content.length) {
array[index] = content.charCodeAt(index++);
}
return array;
}
let Proxy;
let Port;
let NodeType;
let NodeName;
let Driver;
let sql;
type ResponseType =
| 'USER_INVALID'
| 'ALREADY_FRIEND'
| 'ALREADY_SENT'
| 'FRIENDER_ID INVALID'
| 'FRIENDEE_ID INVALID'
| 'NOT_FRIENDS'
| 'ALREADY_EXISTS'
| 'REQUEST_NOT_ACTIVE'
| boolean;
const insertAt = (str: string, sub: string, pos: number): string => `${str.slice(0, pos)}${sub}${str.slice(pos)}`;
setImmediate(() => {
if (manager.getPlugin('MySQL').isEnabled()) {
//@ts-expect-error
Driver = type('com.mysql.cj.jdbc.Driver');
new Driver();
//@ts-expect-error
sql = type('me.vagdedes.mysql.database.MySQL');
try {
sql.disconnect();
} catch (e) {}
sql.connect();
console.info(`[grakkit] §dCursed Network §f« » §3MySQL §aSUCCESS`);
} else console.info(`[grakkit] §dCursed Network §f« » §3MySQL §cFAILURE§r! Is MySQL plugin present and enabled?`);
});
const UUID = type('java.util.UUID');
const thisClient = new Client();
type NodeType = 'server' | 'client';
function register (type: NodeType, port: number, name: string) {
Port = port;
NodeType = type;
NodeName = name;
switch (type) {
case 'server': {
Proxy = new Server();
Proxy.start(port);
try {
thisClient.connect(port);
} catch (e) {
console.error(e);
}
break;
}
case 'client': {
try {
thisClient.connect(port);
} catch (e) {
console.error(e);
}
Proxy = thisClient.server;
break;
}
}
console.info('[grakkit] §dCursed Network §rinit §aSUCCESS');
console.info(`§fNode: §3${NodeType}§r §fPort: §3${Port}§r`);
/* Register Proxy Listeners */
}
function serializeRow (response, columns) {
const row = {};
for (let i = 0; typeof columns[i] != 'undefined'; i++) {
row[columns[i]] = response.getString(columns[i]);
}
return row;
}
function serializeResponse (response: any, columns: any) {
const arrResponse = [];
let i = 0;
if (response == null) return [];
if (!response.next()) return [];
for (let result = true; result; result = response.next()) {
arrResponse[i++] = serializeRow(response, columns);
}
return arrResponse;
}
function justify (...array: string[]) {
return array.join(' ');
}
const fn = {
getPendingRequestsSent (friender_id: string): object[] {
return serializeResponse(
sql.query(
justify(
`SELECT * FROM users AS \`friend\``,
`JOIN friendships ON friender_id = friend.id OR friendee_id = friend.id`,
`WHERE '${friender_id}' IN (friender_id) AND friend.id <> '${friender_id}'`,
`AND accepted_at IS null AND blocked_at IS null;`
)
),
[ 'friendee_id', 'created_at' ]
);
},
getPendingRequestsReceived (friendee_id: string): object[] {
return serializeResponse(
sql.query(
justify(
`SELECT * FROM users AS \`friend\``,
`JOIN friendships ON friender_id = friend.id`,
`WHERE friendee_id = '${friendee_id}'`,
`AND accepted_at IS null and blocked_at IS null`
)
),
[ 'friender_id', 'created_at' ]
);
},
sendMessage (id, message) {
Protocol('SEND_MESSAGE', { uuid: id, message: message });
},
getFriends (id: string): object[] {
return serializeResponse(
sql.query(
justify(
`SELECT * FROM users AS \`friend\``,
`JOIN friendships ON friender_id = friend.id OR friendee_id = friend.id`,
`WHERE '${id}' IN (friendee_id, friender_id) AND friend.id <> '${id}'`,
`AND accepted_at IS NOT null AND blocked_at IS null;`
)
),
[ 'id', 'name', 'created_at', 'accepted_at' ]
);
},
removeFriend (friender_id, friendee_id): boolean | 'FRIENDEE_ID_INVALID' | 'NOT_FRIENDS' {
if (!fn.userExists(friendee_id)) return 'FRIENDEE_ID_INVALID';
// if friends
const q = sql.query(
justify(
`SELECT * FROM users AS \`friend\``,
`JOIN friendships ON friender_id = friend.id OR friendee_id = friend.id`,
`WHERE`,
`('${friender_id}' in (friender_id) OR`,
`'${friendee_id}' in (friender_id))`,
`AND ('${friendee_id}' in (friendee_id)`,
`OR '${friender_id}' in (friendee_id))`,
`AND accepted_at IS NOT null`
)
);
if (q != null && q.next() && q.getString('accepted_at') != null) {
// remove friend
return sql.update(
justify(
`DELETE FROM friendships`,
`WHERE friender_id = '${friender_id}' AND friendee_id = '${friendee_id}' OR friender_id = '${friendee_id}' AND friendee_id = '${friender_id}'`
)
);
} else return 'NOT_FRIENDS';
},
userExists (id): boolean {
const q = sql.query(`SELECT * FROM users WHERE id = '${id}'`);
if (q != null && q.next() && q.getString('id') != null) return true;
return false;
},
declineRequest (friender_id, friendee_id): boolean | 'FRIENDEE_ID_INVALID' {
if (!fn.userExists(friendee_id)) return 'FRIENDEE_ID_INVALID';
const q = sql.query(
justify(
`SELECT * FROM users AS \`friend\``,
`JOIN friendships ON friender_id = friend.id OR friendee_id = friend.id`,
`WHERE`,
`('${friender_id}' in (friender_id) OR`,
`'${friendee_id}' in (friender_id))`,
`AND ('${friendee_id}' in (friendee_id)`,
`OR '${friender_id}' in (friendee_id))`,
`AND accepted_at IS null AND blocked_at IS null;`
)
);
if (q != null && q.next())
return sql.update(
justify(
`DELETE FROM friendships`,
`WHERE`,
`('${friender_id}' in (friender_id) OR`,
`'${friendee_id}' in (friender_id))`,
`AND ('${friendee_id}' in (friendee_id)`,
`OR '${friender_id}' in (friendee_id))`,
`AND accepted_at IS null;`
)
);
else return false;
},
addFriend (
friender_id,
friendee_id
): boolean | 'REQUEST_NOT_ACTIVE' | 'ALREADY_FRIEND' | 'FRIENDER_ID_INVALID' | 'FRIENDEE_ID_INVALID' {
/* If a friend request exists */
if (!fn.userExists(friender_id)) return 'FRIENDER_ID_INVALID';
if (!fn.userExists(friendee_id)) return 'FRIENDEE_ID_INVALID';
const q = sql.query(
`SELECT * FROM friendships WHERE friendee_id = '${friendee_id}' AND friender_id = '${friender_id}' AND accepted_at IS null;`
);
if (q != null && q.next())
if (
!sql.update(
justify(
'UPDATE friendships',
'SET accepted_at = now()',
`WHERE friendee_id = '${friendee_id}' AND friender_id = '${friender_id}'`,
`AND accepted_at IS null`
)
)
)
/* add as a friend */
return sql.update(
justify(
'UPDATE friendships',
'SET accepted_at = now()',
`WHERE friendee_id = '${friender_id}' AND friender_id = '${friendee_id}'`,
`AND accepted_at IS null`
)
);
else return true;
else {
const q2 = sql.query(
`SELECT * FROM friendships WHERE friendee_id = '${friendee_id}' AND friender_id = '${friender_id}' AND accepted_at IS NOT null;`
);
if (q2 != null && q2.next()) return 'ALREADY_FRIEND';
else return 'REQUEST_NOT_ACTIVE';
}
},
test (friender_id, friendee_id) {
return sql.query(
justify(
`SELECT * FROM users AS \`friend\``,
`JOIN friendships ON friender_id = friend.id OR friendee_id = friend.id`,
`WHERE`,
`('${friender_id}' in (friender_id) OR`,
`'${friendee_id}' in (friender_id))`,
`AND ('${friendee_id}' in (friendee_id)`,
`OR '${friender_id}' in (friendee_id))`,
`AND accepted_at IS null AND blocked_at IS null`
)
);
},
// send friend request from friender_id to friendee_id
// use sql
// if user doesn't exist return 'FRIENDEE_ID_INVALID'
// if friend request already exists return 'REQUEST_ALREADY_EXISTS'
// if friendee_id already sent a friend request to friender_id, automatically accept the request
// if friendee_id is already a friend with friender_id, return 'ALREADY_FRIEND'
// if friendee_id is blocked by friender_id, return 'FRIENDEE_BLOCKED'
sendFriendRequest (
friender_id,
friendee_id
):
| 'FRIENDEE_ID_INVALID'
| 'REQUEST_ALREADY_EXISTS'
| 'ALREADY_FRIEND'
| 'FRIENDEE_BLOCKED'
| 'AUTO_ACCEPTED'
| 'REQUEST_SENT' {
if (!fn.userExists(friendee_id)) return 'FRIENDEE_ID_INVALID';
const q = sql.query(
justify(
`SELECT * FROM friendships`,
`WHERE friendee_id = '${friendee_id}' AND friender_id = '${friender_id}' AND accepted_at IS null;`
)
);
if (q != null && q.next()) return 'REQUEST_ALREADY_EXISTS';
const q2 = sql.query(
justify(
`SELECT * FROM friendships`,
`WHERE friendee_id = '${friender_id}' AND friender_id = '${friendee_id}' AND accepted_at IS null;`
)
);
if (q2 != null && q2.next()) {
fn.addFriend(friendee_id, friender_id);
return 'AUTO_ACCEPTED';
}
const q3 = sql.query(
justify(
`SELECT * FROM friendships`,
`WHERE`,
`('${friender_id}' in (friender_id) OR`,
`'${friendee_id}' in (friender_id))`,
`AND ('${friendee_id}' in (friendee_id)`,
`OR '${friender_id}' in (friendee_id))`,
`AND accepted_at IS NOT null;`
)
);
if (q3 != null && q3.next()) return 'ALREADY_FRIEND';
const q4 = sql.query(
justify(
`SELECT * FROM friendships`,
`WHERE`,
`('${friender_id}' in (friender_id) OR`,
`'${friendee_id}' in (friender_id))`,
`AND ('${friendee_id}' in (friendee_id)`,
`OR '${friender_id}' in (friendee_id))`,
`AND blocked_at IS NOT null;`
)
);
if (q4 != null && q4.next()) return 'FRIENDEE_BLOCKED';
sql.update(
justify(
`INSERT INTO friendships`,
`(friender_id, friendee_id, created_at)`,
`VALUES`,
`('${friender_id}', '${friendee_id}', now())`
)
);
return 'REQUEST_SENT';
// if the friendee_id sent a friend request to friender_id, automatically accept the request
},
sendFriendRequestBackup (
friender_id,
friendee_id
): boolean | 'AUTO_ACCEPTED' | 'ALREADY_SENT' | 'ALREADY_FRIEND' | 'FRIENDEE_ID_INVALID' {
/* If a friend request exists */
if (!fn.userExists(friendee_id)) return 'FRIENDEE_ID_INVALID';
const q = sql.query(
justify(
`SELECT * FROM users AS \`friend\``,
`JOIN friendships ON friender_id = friend.id OR friendee_id = friend.id`,
`WHERE`,
`('${friender_id}' in (friender_id) OR`,
`'${friendee_id}' in (friender_id))`,
`AND ('${friendee_id}' in (friendee_id)`,
`OR '${friender_id}' in (friendee_id))`,
`AND accepted_at IS null AND blocked_at IS null`
)
);
if (q != null && q.next()) {
switch (q.getString('friender_id')) {
case friender_id:
return 'ALREADY_SENT';
case friendee_id: {
fn.addFriend(friendee_id, friender_id);
return 'AUTO_ACCEPTED';
}
}
} else {
const q2 = sql.query(
`SELECT * FROM friendships WHERE friendee_id = '${friendee_id}' OR friendee_id = '${friender_id}' AND friender_id = '${friender_id}' OR friender_id = '${friendee_id}' AND accepted_at IS NOT null;`
);
if (q2 != null && q2.next()) return 'ALREADY_FRIEND';
else
return sql.update(
justify(
`INSERT INTO friendships (friender_id, friendee_id) VALUES ('${friender_id}', '${friendee_id}');`
)
);
}
},
getNameFromUuid (id: string): string {
const q = sql.query(`SELECT * FROM users WHERE id = '${id}';`);
const empty = !q.next();
if (q != null && !empty && q.getString('name') != null) return q.getString('name');
const name = fetch(`https://sessionserver.mojang.com/session/minecraft/profile/${id}`).json().name;
if (empty && q.getString('id') == null)
sql.update(`INSERT INTO users (id, name, server) VALUES ('${id}', '${name}', '${NodeName}')`);
else sql.update(`UPDATE users SET name = '${name}' WHERE id = '${id}';`);
return name;
},
getUuidFromName (name: string) {
const request = fetch(`https://api.mojang.com/users/profiles/minecraft/${name}`);
try {
let id = request.json().id;
id = insertAt(id, '-', 8);
id = insertAt(id, '-', 13);
id = insertAt(id, '-', 18);
id = insertAt(id, '-', 23);
return id;
} catch (e) {
return null;
}
},
hasFriends (id: string): boolean {
const q = sql.query(
justify(
`SELECT * FROM friendships WHERE friendee_id = '${id}' OR friender_id = '${id}' AND accepted_at IS NOT null;`
)
);
if (q != null && q.next()) return true;
else return false;
},
addUser (id: string): ResponseType {
const q = sql.query(`SELECT * FROM users WHERE id = '${id}'`);
if (q != null && q.next() && q.getString('id') != null) return 'ALREADY_EXISTS';
return sql.update(
`INSERT INTO users (id, name, server) VALUES ('${id}', '${
fetch(`https://sessionserver.mojang.com/session/minecraft/profile/` + id).json().name
}', '${NodeName}');`
);
},
getServer (id: string): string | null {
const q = sql.query(`SELECT * FROM users WHERE id = '${id}'`);
if (q != null && q.next() && q.getString('server') != null) return q.getString('server');
return null;
},
setServer (id: string, server: string): boolean {
return sql.update(`UPDATE users SET server = '${server}' WHERE id = '${id}'`);
}
};
function Protocol (protocol: string, params: {}) {
thisClient.send(encode(JSON.stringify({ protocol: protocol, params })));
}
function filter (suggestions: string[], key) {
return suggestions.filter(word => {
return word.toLowerCase().includes(key.toLowerCase());
});
}
export function getFriendList (player) {
const friends = fn.getFriends(player.getUniqueId().toString());
if (friends.length === 0) return [];
const names = [];
let i = names.length;
friends.forEach(friend => {
//@ts-expect-error
if (friend.name != null) names[i] = friend.name;
else {
//@ts-expect-error
const q = sql.query(`SELECT * FROM users WHERE id = '${friend.id}';`);
if (q != null && q.next() && q.getString('name') != null) names[i] = q.getString('name');
else {
names[i] = fetch(
`https://sessionserver.mojang.com/session/minecraft/profile/${q.getString('id')}`
).json().name;
//@ts-expect-error
sql.update(`UPDATE users SET name = '${names[i]}' WHERE id = '${friend.id}';`);
}
}
i++;
});
return names;
}
export const feedback = {
friends: {
// return an an array of messages showing the user all the pending requests they have (make it pretty)
getPending: (name: string, Requests: string[]) => {
let requests = [];
Requests.forEach(request => {
requests[requests.length] = fn.getNameFromUuid(request);
});
if (requests.length === 1)
return [
`§f${requests[0]} §bhas requested to be your friend!`,
'§5§m ',
`To accept, type /friend accept [name]`,
`To deny, type /friend deny [name]`
];
if (requests.length > 4) {
return [
`§bYou have §f${requests.length} §bpending §bfriend requests!`,
'§5§m ',
`§f${requests.slice(0, 4).join('§b,§f ')}`,
` §r §r §d§o+ ${requests.length - 4} more`,
'§5§m ',
`§7To §aaccept§7, type §e/friend accept §d[name]`,
`§7To §cdeny§7, type §e/friend deny §d[name]`,
`§7To view all requests, type §e/friend pending`
];
}
if (requests.length > 1) {
return [
`§bYou have §f${requests.length} §bpending friend requests!`,
'§5§m ',
`§f${requests.join('§b,§f ')}`,
'§5§m ',
`§7Respond with §3/friend §8<§3accept§7 | §3deny§8> §d[name]`
];
}
return null;
},
friendee_blocked: '§cYou are blocked by this player.',
help: {
header: [ '§fHelp for §b/friend', '§5§m ' ],
body: [
'§3/f add §d[name]\n§8-> §7Send a friend request.',
'§3/f remove §d[name]\n§8-> §7Unfriend a player.',
'§3/f list\n§8-> §7View all your friends',
'§3/f accept §d[name]\n§8-> §7Accept a friend request',
'§3/f deny §d[name]\n§8-> §7Decline a friend request',
'§3/f help §d[page]\n§8-> §7Show this dialogue'
]
},
list: {
header (player) {
const friends = getFriendList(player).length;
if (friends === 0) return [ '§5§m ', '§fYou have no friends' ];
return [ '§5§m ', `§fFriends (${friends}):` ];
}
},
already_friend: 'You are already friends with this player!',
friendee_id_invalid: 'This user does not exist!',
already_sent: "You've already sent a friend request to this player!",
not_friends: "You're already not friends with this player!",
request_not_active: "You haven't received a friend request from this player!",
declined_request: 'Friend request declined!',
auto_success: name => {
return `${name} already had an active friend request so they were automatically friended.`;
},
friended_success: name => {
return `You're now friends with ${name}!`;
},
request_sent: name => {
return 'A friend request has been sent to ' + name + '!';
},
remove_success: name => {
return `${name} has been removed from your friends list.`;
}
}
};
// a function where plugging in an array and a page number will return at most 10 items a page, plugging in a page number will return those
// if array.slice(start, end) is empty, return array.slice(start)
export function paginate (array: string[], page: number, max: number = 10) {
const start = (page - 1) * max;
const end = page * max;
if (array.slice(start, end).length === 0) return array.slice(start);
return array.slice(start, end);
}
// a function that returns the help header, an array of friends plugged into paginate, and a footer
// if page is less than 1, return the first page
export function returnFormatted (
options: {
player?: obePlayer | obcCommandSender;
args?: string[] | [];
header?: string[] | [];
body?: string[] | [];
topfooter?: string[] | [];
page?: number;
max?: number;
enable_pages?: boolean;
} = {
player: null,
args: null,
header: [],
body: [],
topfooter: [],
page: 1,
max: 10,
enable_pages: false
}
) {
const { player, args, header, body, topfooter, page, max, enable_pages } = options;
if (enable_pages) {
let Page = page;
if (Page < 1) Page = 1;
if (page > Math.ceil(body.length / max)) Page = Math.ceil(body.length / max);
let footer = [];
footer = [ ' \n§7Page ' + Page + ' of ' + Math.ceil(body.length / max) ];
return [ header, paginate(body, Page, max), topfooter, footer ];
}
return [ header, body, topfooter ];
}
export function sendFormatted (
options: {
player?: obePlayer | obcCommandSender;
args?: string[] | [];
header?: string[] | [];
body?: string[] | [];
topfooter?: string[] | [];
page?: number;
max?: number;
enable_pages?: boolean;
} = {
player: null,
args: null,
header: [],
body: [],
topfooter: [],
page: 1,
max: 10,
enable_pages: false
}
) {
const { player, args, header, body, topfooter, page, max, enable_pages } = options;
let Page = 1;
if (args && args[1]) Page = parseInt(args[1]);
returnFormatted({
player: player,
args: args,
header: header,
body: body,
topfooter: topfooter,
page: Page,
max: max,
enable_pages: enable_pages
}).forEach(section => {
if (section)
section.forEach(line => {
player.sendMessage(line);
});
});
}
command({
name: 'friend',
aliases: [ 'f', 'friends' ],
execute (player, ...args) {
//@ts-expect-error
const uuid = player.getUniqueId().toString();
switch (args[0]) {
case 'help': {
let page = 1;
if (args[1]) page = parseInt(args[1]);
sendFormatted({
player: player,
args: args,
header: feedback.friends.help.header,
body: feedback.friends.help.body,
page: page,
max: 3,
enable_pages: true
});
break;
}
case 'list': {
let page = 1;
if (args[1]) page = parseInt(args[1]);
sendFormatted({
player: player,
header: feedback.friends.list.header(player),
body: getFriendList(player),
topfooter: [ '§5§m ' ],
page: page,
max: 10,
enable_pages: fn.hasFriends(uuid)
});
break;
}
case 'add': {
if (typeof args[1] == 'undefined') return;
switch (fn.sendFriendRequest(uuid, fn.getUuidFromName(args[1]))) {
case 'ALREADY_FRIEND':
return player.sendMessage(feedback.friends.already_friend);
case 'FRIENDEE_ID_INVALID':
return player.sendMessage(feedback.friends.friendee_id_invalid);
case 'REQUEST_ALREADY_EXISTS':
return player.sendMessage(feedback.friends.already_sent);
case 'AUTO_ACCEPTED':
return player.sendMessage(feedback.friends.auto_success(args[1]));
case 'FRIENDEE_BLOCKED':
return player.sendMessage(feedback.friends.friendee_blocked);
case 'REQUEST_SENT':
return player.sendMessage(feedback.friends.request_sent(args[1]));
default:
return player.sendMessage('fuck');
}
break;
}
case 'remove': {
if (typeof args[1] == 'undefined') return;
switch (fn.removeFriend(uuid, fn.getUuidFromName(args[1]))) {
case 'FRIENDEE_ID_INVALID':
return player.sendMessage(feedback.friends.friendee_id_invalid);
case 'NOT_FRIENDS':
return player.sendMessage(feedback.friends.not_friends);
case true:
return player.sendMessage(feedback.friends.remove_success(args[1]));
default:
return player.sendMessage('fuck');
}
}
case 'deny': {
if (typeof args[1] == 'undefined') return;
switch (fn.declineRequest(uuid, fn.getUuidFromName(args[1]))) {
case 'FRIENDEE_ID_INVALID':
return player.sendMessage(feedback.friends.friendee_id_invalid);
case true:
return player.sendMessage(feedback.friends.declined_request);
}
}
case 'accept': {
if (typeof args[1] == 'undefined') return;
switch (fn.addFriend(fn.getUuidFromName(args[1]), uuid)) {
case 'ALREADY_FRIEND':
return player.sendMessage(feedback.friends.already_friend);
case 'FRIENDEE_ID_INVALID':
return player.sendMessage(feedback.friends.friendee_id_invalid);
case 'REQUEST_NOT_ACTIVE':
return player.sendMessage(feedback.friends.request_not_active);
case true:
return player.sendMessage(feedback.friends.friended_success(args[1]));
}
}
default: {
let page = 1;
if (args[1]) page = parseInt(args[1]);
sendFormatted({
player: player,
args: args,
header: feedback.friends.help.header,
body: feedback.friends.help.body,
page: page,
max: 3,
enable_pages: true
});
break;
}
}
},
tabComplete: (player, ...args) => {
switch (args.length) {
case 1:
return filter([ 'list', 'add', 'remove', 'accept', 'deny', 'help', 'toggle' ], args[0]);
}
}
});
event('org.bukkit.event.player.PlayerJoinEvent', {
priority: 'MONITOR',
script: event => {
const player = event.getPlayer();
const id = event.getPlayer().getUniqueId().toString();
if (!fn.userExists(id)) return fn.addUser(id);
const requests = fn.getPendingRequestsReceived(id);
if (requests.length === 0) return;
core.task.timeout(() => {
//@ts-expect-error
player.sendMessage(' ');
sendFormatted({
player: player,
header: feedback.friends.getPending(
player.getName(),
//@ts-expect-error
requests.map(request => request.friender_id)
)
});
}, 2);
}
});
export { thisClient, register, decode, encode, Proxy, Port, Protocol, Client, Server, fn, sql, serializeResponse };