C# SAMPQuery — Библиотека для отправки запросов на сервера SAMP

Mavi_

Известный
Автор темы
8
16
SAMPQuery

SAMPQuery — это библиотека, которая позволяет запрашивать информацию о серверах SAMP и выполнять RCON команды. Включает в себя коррекцию кодировки, поддержка подключении по имен хоста, асинхронные запросы и многое другое.

Установка

Менеджер пакетов
Код:
Install-Package SAMPQuery -Version 1.1.0

Терминал
Bash:
$ dotnet add package SAMPQuery --version 1.1.0

Package Reference
XML:
<PackageReference Include="SAMPQuery" Version="1.1.0" />

Клонировать репозиторию
Bash:
$ git clone https://github.com/justmavi/sampquery.git

Использование

C#:
var server = new SampQuery("localhost", 7777);

ServerInfo serverInfo = await server.GetServerInfoAsync();
ServerRules serverRules = await server.GetServerRulesAsync();
IEnumerable<ServerPlayer> serverPlayers = await server.GetServerPlayersAsync();

Console.WriteLine($"Welcome to ${serverInfo.HostName}! Mapname: ${serverRules.MapName}");
Console.WriteLine("Players online:");
serverPlayers.ToList().ForEach(player => Console.WriteLine(player.PlayerName));

Документация

Конструктор
C#:
var server = new SampQuery("127.0.0.1", 7777);

Конструктор имеет перегрузки:
C#:
SampQuery(IPAddress ip, ushort port)
SampQuery(string ip, ushort port, string password)
SampQuery(IPAddress ip, ushort port, string password)
SampQuery(IPAddress ip) // будет использоваться дефолтный 7777 порт
SampQuery(string ip) // будет использоваться дефолтный 7777 порт. Также здесь поддерживается строка подключения формата "HOST:PORT".

Имя хоста также поддерживается:
C#:
var server = new SampQuery("localhost", 7777);

GetServerInfo
Запрашивает основную информацию о сервере

C#:
var server = new SampQuery("127.0.0.1", 7777);
ServerInfo data =  server.GetServerInfo();

Console.WriteLine($"Server {data.HostName}. Online: {data.Players}/{data.MaxPlayers}");

GetServerInfoAsync
Асинхронно запрашивает основную информацию о сервере

C#:
var server = new SampQuery("127.0.0.1", 7777);
ServerInfo data = await server.GetServerInfoAsync();

Console.WriteLine($"Server {data.HostName}. Online: {data.Players}/{data.MaxPlayers}");

GetServerRules
Запрашивает правила, назначенные сервером

C#:
var server = new SampQuery("127.0.0.1", 7777);
ServerInfo data = server.GetServerRules();

Console.WriteLine($"Lagcomp {(data.Lagcomp ? "On" : "Off")}. Map: {data.MapName}. SAMPCAC: {data.SAMPCAC_Version ?? "Isn't required"}");

GetServerRulesAsync
Асинхронно запрашивает правила, назначенные сервером

C#:
var server = new SampQuery("127.0.0.1", 7777);
ServerInfo data = await server.GetServerRulesAsync();

Console.WriteLine($"Lagcomp {(data.Lagcomp ? "On" : "Off")}. Map: {data.MapName}. SAMPCAC: {data.SAMPCAC_Version ?? "Isn't required"}");

GetServerPlayers
Запрашивает игроков в онлайне с детальной информацией (работает до 100 игроков, лимит SA-MP)

C#:
 var server = new SampQuery("127.0.0.1", 7777);
 IEnumerable<ServerPlayer> players = server.GetServerPlayers();

 Console.WriteLine("ID | Nick | Score | Ping\n");

 foreach(ServerPlayer player in players)
 {
     Console.WriteLine($"{player.PlayerId} | {player.PlayerName} | {player.PlayerScore} | {player.PlayerPing}");
 }
Максимальное значение ID игрока 255. Двухбайтовые идентификаторы не поддерживаются (ограничение SA-MP).

GetServerPlayersAsync

Асинхронно запрашивает игроков в онлайне с детальной информацией (работает до 100 игроков, лимит SA-MP)

C#:
 var server = new SampQuery("127.0.0.1", 7777);
 IEnumerable<ServerPlayer> players = await server.GetServerPlayersAsync();

 Console.WriteLine("ID | Nick | Score | Ping\n");

 foreach(ServerPlayer player in players)
 {
     Console.WriteLine($"{player.PlayerId} | {player.PlayerName} | {player.PlayerScore} | {player.PlayerPing}");
 }
Максимальное значение ID игрока 255. Двухбайтовые идентификаторы не поддерживаются (ограничение SA-MP).

SendRconCommand

Исполняет команду RCON

C#:
 var server = new SampQuery("127.0.0.1", 7777, "helloworld");
string answer = sampQuery.SendRconCommand("varlist");

Console.WriteLine($"Server says: {answer}");

SendRconCommandAsync
Асинхронно исполняет команду RCON

C#:
var server = new SampQuery("127.0.0.1", 7777, "helloworld");
string answer = await sampQuery.SendRconCommandAsync("varlist");

Console.WriteLine($"Server says: {answer}");

ServerInfo
Класс, представляющий информацию о сервере. Свойства:

  • HostName
  • GameMode
  • Language
  • Players
  • MaxPlayers
  • Password
  • ServerPing

ServerRules
Класс, представляющий правила сервера. Свойства:

  • Lagcomp
  • MapName
  • Version
  • SAMPCAC_Version
  • Weather
  • Weburl
  • WorldTime
  • Gravity

ServerPlayer
Класс, представляющий информацию об игроке. Свойства:

  • PlayerId
  • PlayerName
  • PlayerScore
  • PlayerPing

Благодарность

  • Отдельная благодарность @continue98 за помощь.

Оставайся на связи

 
Последнее редактирование:

MrCreepTon

Неизвестный
Всефорумный модератор
2,375
5,443
Тоже неплохой вариант
 
  • Нравится
Реакции: routefleeder

Mavi_

Известный
Автор темы
8
16
Тоже неплохой вариант
Да, видел уже. Этот вариант тоже унаследован от вики, но баг с чтением данных там опять же, не исправлен (можете сами посмотреть). Да и разницы между моим и этим вариантом достаточно много, как минимум, проще в использовании (изначальная цель была только это).

Реализацию IDisposable и асинхронные методы добавлю в следующей версии, как руки дойдут ))
 
Последнее редактирование:

MrCreepTon

Неизвестный
Всефорумный модератор
2,375
5,443

odelyaZ

Известный
270
166
эм
Они отличаются, в этих не так много функций как хотелось бы. Я говорил конкретно про аналог для JS
 

Mavi_

Известный
Автор темы
8
16
v1.0.1 is live

- Добавлены асинхронные аналоги для всего ранее доступного функционала
- Добавлена возможность подключиться к серверу по hostname
- Добавлена поддержка .net5, убрана поддержка .net2
- Добавлена документация
- Изменились названия классов и пространства имён (посмотрите readme в github)
- Общий рефакторинг кода. Исправлено большое количество ошибок

Прошлая версия несовместима с текущей!

Доступно на NuGet для скачивания.
Для более подробного изучения - GitHub.

#Kudos некому kolya112 за то, что заставил меня взяться за дело, а то сам я бы вряд ли.

v1.0.2
- Исправлена ошибка при подключении к локальному серверу по hostname в дистрибютивах Linux. (#4)
 
Последнее редактирование:

Mavi_

Известный
Автор темы
8
16
v1.1.0

- Исправлена критическая ошибка, из-за которой библиотека не работала на старых версиях фреймворка.
Протестил под .net7.0, .net6.0, .net5.0 и .netcoreapp3.1 - работает как надо.
- Добавлено 2 новых перегрузок конструктора
C#:
var sampquery = new SampQuery("localhost"); // будет использоваться дефолтный порт 7777. Такая же перегрузка и для типа IPAddress
var sampquery = new SampQuery("localhost:6666"); // Такой вариант так же теперь будет работать
var sampquery = new SampQuery("localhost:7777", 6666); // такой вариант работать НЕ будет. Либо то, либо то, иначе никак.

- Добавлена поддержка .NET 7.0.

Доступно на NuGet для скачивания.
Для более подробного изучения - GitHub.
 
Последнее редактирование:

4elovek550

Известный
21
2
Салам.
Возможно кто-то сталкивался с проблемой, что сервер на базе OpenMP не отдаёт список игроков. Соединение рвётся по тайм-ауту. При этом информацию о названии, кол-ву игроков и т.д. спокойно отдаёт.
 

Mavi_

Известный
Автор темы
8
16
Салам.
Возможно кто-то сталкивался с проблемой, что сервер на базе OpenMP не отдаёт список игроков. Соединение рвётся по тайм-ауту. При этом информацию о названии, кол-ву игроков и т.д. спокойно отдаёт.
У них нет такой возможности. Подробнее: https://github.com/openmultiplayer/open.mp/issues/1147#issuecomment-3567695563
 

4elovek550

Известный
21
2
Здрасте. Я почему-то вспомнил про эту тему и вот что я нарыл.
Рассказываю сразу: Получаю информацию с сервера Advance RP который сообщает мне что стоит на версии omp 1.5.8.
Если честно, то я уже и не помню откуда я это взял вообще, так это было ещё под новый год.
Вдруг кому-то пригодится.

Типо библиотека:
class Query
{
    IPAddress serverIp;
    IPEndPoint serverEndPoint;
    Socket svrConnect;

    string szIP;
    ushort iPort;

    DateTime TransmitMS = new DateTime();
    DateTime ReceiveMS = new DateTime();

    Dictionary<string, string> dData = new Dictionary<string, string>();

    public Query(string ip, ushort port, char packet_type)
    {
        MemoryStream stream = new MemoryStream();
        BinaryWriter writer = new BinaryWriter(stream);

        try
        {
            serverIp = new IPAddress(IPAddress.Parse(ip).GetAddressBytes());
            serverEndPoint = new IPEndPoint(serverIp, port);

            svrConnect = new Socket(serverEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);

            svrConnect.SendTimeout = 5000;
            svrConnect.ReceiveTimeout = 5000;
            szIP = ip;
            iPort = port;

            try
            {
                using (stream)
                {
                    using (writer)
                    {
                        string[] szSplitIP = szIP.ToString().Split('.');

                        writer.Write("SAMP".ToCharArray());

                        writer.Write(Convert.ToByte(Convert.ToInt16(szSplitIP[0])));
                        writer.Write(Convert.ToByte(Convert.ToInt16(szSplitIP[1])));
                        writer.Write(Convert.ToByte(Convert.ToInt16(szSplitIP[2])));
                        writer.Write(Convert.ToByte(Convert.ToInt16(szSplitIP[3])));

                        writer.Write(iPort);
                        writer.Write(packet_type);

                        TransmitMS = DateTime.Now;
                    }
                }
                svrConnect.SendTo(stream.ToArray(), serverEndPoint);
            }
            catch (Exception e)
            {
                Console.Write("Failed to receive packet:", e);
            }
        }
        catch (Exception e)
        {
            Console.Write("Failed to connect to IP:", e);
        }
    }

    public Dictionary<string, string> read(bool flushdata = true)
    {
        try
        {
            serverIp = new IPAddress(IPAddress.Parse(szIP).GetAddressBytes());
            serverEndPoint = new IPEndPoint(serverIp, iPort);

            EndPoint rawPoint = (EndPoint)serverEndPoint;

            byte[] szReceive = new byte[2048];
            svrConnect.ReceiveFrom(szReceive, ref rawPoint);

            svrConnect.Close();

            ReceiveMS = DateTime.Now;

            if (flushdata)
                dData.Clear();

            string ping = ReceiveMS.Subtract(TransmitMS).Milliseconds.ToString();

            MemoryStream stream = new MemoryStream(szReceive);
            BinaryReader read = new BinaryReader(stream);

            using (stream)
            {
                using (read)
                {
                    read.ReadBytes(10);

                    switch (read.ReadChar())
                    {
                        case 'i':
                            dData.Add("password", Convert.ToString(read.ReadByte()));
                            dData.Add("players", Convert.ToString(read.ReadInt16()));
                            dData.Add("maxplayers", Convert.ToString(read.ReadInt16()));
                            dData.Add("hostname", new string(read.ReadChars(read.ReadInt32())));
                            dData.Add("gamemode", new string(read.ReadChars(read.ReadInt32())));
                            dData.Add("mapname", new string(read.ReadChars(read.ReadInt32())));
                            break;

                        case 'r':
                            for (int i = 0, iRules = read.ReadInt16(); i < iRules; i++)
                                dData.Add(new string(read.ReadChars(read.ReadByte())), new string(read.ReadChars(read.ReadByte())));
                            break;

                        case 'c':
                            for (int i = 0, iPlayers = read.ReadInt16(); i < iPlayers; i++)
                                dData.Add(new string(read.ReadChars(read.ReadByte())), Convert.ToString(read.ReadInt32()));
                            break;

                        case 'd':
                            for (int i = 0, iTotalPlayers = read.ReadInt16(); i < iTotalPlayers; i++)
                            {
                                string id = Convert.ToString(read.ReadByte());
                                dData.Add(id + ".name", new string(read.ReadChars(read.ReadByte())));
                                dData.Add(id + ".score", Convert.ToString(read.ReadInt32()));
                                dData.Add(id + ".ping", Convert.ToString(read.ReadInt32()));
                            }
                            break;

                        case 'p':
                            dData.Add("ping", ping.ToString());
                            break;

                    }
                }
            }

        }
        catch (Exception e)
        {
            Console.Write("There's been a problem reading the data", e);
        }
        return dData;
    }
}

Реализация:

Выдаёт список игроков:
Query api = new Query("185.169.134.156", 7777, 'c');

List<string> players = new List<string>();

foreach (KeyValuePair<string, string> kvp in api.read(true))
{   
        players.Add(FormatText(kvp.Key.ToLower()));
}