diff --git a/MyMcRealms/Controllers/ActivitiesController.cs b/MyMcRealms/Controllers/ActivitiesController.cs new file mode 100644 index 0000000..4cb55f0 --- /dev/null +++ b/MyMcRealms/Controllers/ActivitiesController.cs @@ -0,0 +1,60 @@ +using Microsoft.AspNetCore.Mvc; +using MyMcRealms.Attributes; +using MyMcRealms.Helpers; +using MyMcRealms.MyMcAPI; +using MyMcRealms.Responses; +using Newtonsoft.Json; + +namespace MyMcRealms.Controllers +{ + [Route("[controller]")] + [ApiController] + [RequireMinecraftCookie] + public class ActivitiesController : ControllerBase + { + [HttpGet("liveplayerlist")] + public async Task> GetLivePlayerList() + { + string cookie = Request.Headers.Cookie; + string playerUUID = cookie.Split(";")[0].Split(":")[2]; + + List lists = []; + + var allServers = await new Wrapper(Environment.GetEnvironmentVariable("MYMC_API_KEY")).GetAllServers(); + + foreach (var world in allServers.Servers) + { + var query = new MinecraftServerQuery().Query(world.Connect); + + if (query == null) continue; + + List players = []; + + if (query.Players.Sample == null) continue; + + foreach (var player in query.Players.Sample) + { + players.Add(new + { + playerId = player.Id.Replace("-", ""), + }); + } + + LivePlayerList list = new() + { + ServerId = allServers.Servers.IndexOf(world), + PlayerList = JsonConvert.SerializeObject(players), + }; + + lists.Add(list); + }; + + LivePlayerListsResponse response = new() + { + Lists = lists + }; + + return Ok(response); + } + } +} diff --git a/MyMcRealms/Helpers/MinercraftServerQuery.cs b/MyMcRealms/Helpers/MinercraftServerQuery.cs new file mode 100644 index 0000000..ca24336 --- /dev/null +++ b/MyMcRealms/Helpers/MinercraftServerQuery.cs @@ -0,0 +1,98 @@ +using MyMcRealms.Responses; +using Newtonsoft.Json; +using System.Net.Sockets; +using System.Text; + +namespace MyMcRealms.Helpers +{ + public class MinecraftServerQuery + { + public MinecraftServerQueryRepsonse? Query(string address) + { + string server = address.Split(':')[0]; + int port = address.Contains(':') ? int.Parse(address.Split(':')[1]) : 25565; + + try + { + using (TcpClient client = new()) + { + client.Connect(server, port); + + using (NetworkStream stream = client.GetStream()) + using (BinaryWriter writer = new(stream)) + using (BinaryReader reader = new(stream)) + { + // Send Handshake packet (https://wiki.vg/Protocol#Handshake) + byte[] handshakePacket = CreateHandshakePacket(server, port); + writer.Write((byte)handshakePacket.Length); // Packet length + writer.Write(handshakePacket); // Packet data + + // Send Status Request packet (https://wiki.vg/Protocol#Request) + writer.Write((byte)0x01); // Packet length + writer.Write((byte)0x00); // Packet ID (Request) + + // Read the response length + int length = ReadVarInt(reader); + // Read the response packet ID + int packetId = ReadVarInt(reader); + if (packetId != 0x00) throw new Exception("Invalid packet ID"); + + // Read the JSON length + int jsonLength = ReadVarInt(reader); + // Read the JSON response + byte[] jsonData = reader.ReadBytes(jsonLength); + string json = Encoding.UTF8.GetString(jsonData); + + return JsonConvert.DeserializeObject(json); + } + } + } + catch (Exception ex) + { + Console.WriteLine("Error: " + ex.Message); + return null; + } + } + + private static byte[] CreateHandshakePacket(string server, int port) + { + using (MemoryStream ms = new MemoryStream()) + { + using (BinaryWriter writer = new BinaryWriter(ms)) + { + WriteVarInt(writer, 0x00); // Packet ID (Handshake) + WriteVarInt(writer, 754); // Protocol version (754 for Minecraft 1.16.5) + WriteVarInt(writer, server.Length); // Server address length + writer.Write(Encoding.UTF8.GetBytes(server)); // Server address + writer.Write((ushort)port); // Server port + WriteVarInt(writer, 1); // Next state (1 for status) + + return ms.ToArray(); + } + } + } + + private static int ReadVarInt(BinaryReader reader) + { + int value = 0; + int size = 0; + int b; + while (((b = reader.ReadByte()) & 0x80) == 0x80) + { + value |= (b & 0x7F) << (size++ * 7); + if (size > 5) throw new Exception("VarInt is too big"); + } + return value | (b << (size * 7)); + } + + private static void WriteVarInt(BinaryWriter writer, int value) + { + while ((value & 0xFFFFFF80) != 0) + { + writer.Write((byte)((value & 0x7F) | 0x80)); + value >>= 7; + } + writer.Write((byte)(value & 0x7F)); + } + } +} diff --git a/MyMcRealms/Responses/LivePlayerListsResponse.cs b/MyMcRealms/Responses/LivePlayerListsResponse.cs new file mode 100644 index 0000000..1eef8f9 --- /dev/null +++ b/MyMcRealms/Responses/LivePlayerListsResponse.cs @@ -0,0 +1,13 @@ +namespace MyMcRealms.Responses +{ + public class LivePlayerListsResponse + { + public List Lists { get; set; } = []; + } + + public class LivePlayerList + { + public int ServerId { get; set; } + public string PlayerList { get; set; } = null!; + } +} diff --git a/MyMcRealms/Responses/MinecraftSeerverQueryResponse.cs b/MyMcRealms/Responses/MinecraftSeerverQueryResponse.cs new file mode 100644 index 0000000..3c4e383 --- /dev/null +++ b/MyMcRealms/Responses/MinecraftSeerverQueryResponse.cs @@ -0,0 +1,29 @@ +namespace MyMcRealms.Responses +{ + public class MinecraftServerQueryRepsonse + { + public MinecraftServerQueryVersionObject Version { get; set; } = null!; + public bool EnforcesSecureChat { get; set; } + public string Description { get; set; } = null!; + public MinecraftServerQueryPlayersObject Players { get; set; } = null!; + } + + public class MinecraftServerQueryVersionObject + { + public string Name { get; set; } = null!; + public string Protocol { get; set; } = null!; + } + + public class MinecraftServerQueryPlayersObject + { + public int Max { get; set; } + public int Online { get; set; } + public List Sample { get; set; } = null!; + } + + public class MinecraftServerQueryPlayersSampleObject + { + public string Id { get; set; } = null!; + public string Name { get; set; } = null!; + } +}