Prosty klient GG

Deti

Tytułem wstępu - prosty kawałek kodu, który pozwoli na wysyłanie i odbieranie wiadomości poprzez GG. W dotychczasowej wersji (bardzo ubogiej, lecz co ważne - działającej) możliwe jest również ustawienie statusu klienta oraz opisu.

Wystarczy dodać do projektu następujący moduł:

using System;
using System.Text;
using System.Runtime.InteropServices;

/*  HAKGER sharpGG Engine, rev. 0.1 / 21.10.2007
 * 
 *  The sHGG class contains several methods that allow you to use
 *  Gadu-gadu protocol and simply imitate GG messenger.
 * 
 *  This unit is owned by HAKGERSoft, any modifications without 
 *  HAKGERSoft permission is prohibited!
 * 
 *  Author: Deti 
 *  
 */

namespace HAKGERSoft
{
    public sealed class sHGG : System.Net.Sockets.TcpClient
    {

        # region Constant declarations

        private const string DEFAULT_GG_HOST = "m1.gadu-gadu.pl";
        private const int DEFAULT_GG_PORT = 8074;
        private const int DEFAULT_GG_VERSION = 0x21; // 6.0 (build 133)
        private const UInt16 DEFAULT_LOCAL_PORT = 1550;
        private const string DEFAULT_ENCODING = "windows-1250";
        
        private const uint IN_WELCOME = 0x1;
        private const uint IN_LOGIN_OK = 0x3;
        private const uint IN_LOGIN_FAILED = 0x9;
        private const uint IN_DISCONNECTING = 0xb;
        private const uint IN_RECEIVE_MESSAGE = 0xa;

        private const uint OUT_LOGIN60 = 0x15;
        private const uint OUT_STATUS_CHANGE = 0x2;
        private const uint OUT_PING = 0x8;
        private const uint OUT_MESSAGE = 0xb;

        private const uint MESSAGE_CLASS_CHAT = 0x8;

        private const uint STATUS_NOT_AVAILABLE = 0x1; // niedostepny
        private const uint STATUS_NOT_AVAILABLE_DESC = 0x15; // niedostepny + opis
        private const uint STATUS_AVAILABLE = 0x2; // dostepny
        private const uint STATUS_AVAILABLE_DESC = 0x4; // dostepny + opis
        private const uint STATUS_BUSY = 0x3; // zajety
        private const uint STATUS_BUSY_DESC = 0x5; // zajety + opis
        private const uint STATUS_INVISIBLE = 0x14; // niewidoczny
        private const uint STATUS_INVISIBLE_DESC = 0x16; // niewidoczny + opis
        private const uint STATUS_BLOCKED = 0x6; // zablokowany
        private const uint FRIENDS_MASK = 0x8000; // maska (tylko dla przyjaciol)

        private const int MAX_DESCRIPTIONS_SIZE = 70;
        private const int MAX_MESSAGE_SIZE = 1989;

        private const int PING_INTERVAL = 120; // [sek] 

        # endregion

        # region Structures and types

        public enum GGStatusType { NotAvailable, Available, Busy, Invisible }

        [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
        private struct sggHeader
        {
            internal uint Type;
            internal uint Size;
        }

        # endregion

        # region Output structures

        [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
        private struct sggOutLogin60
        {
            internal sggHeader Header;
            internal uint Number;
            internal uint Hash;
            internal uint Status;
            internal uint Version;
            internal byte Unknown1;
            internal uint LocalIp;
            internal UInt16 LocalPort;
            internal uint ExternalIp;
            internal UInt16 ExternalPort;
            internal byte ImageSize;
            internal byte Unknown2;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
        private struct sggOutStatus
        {
            internal sggHeader Header;
            internal uint Status;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_DESCRIPTIONS_SIZE + 1)]
            internal String Desc;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
        private struct sggOutPing
        {
            internal sggHeader Header;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
        private struct sggOutMessage
        {
            internal sggHeader Header;
            internal uint Recipient;
            internal uint Seq;
            internal uint Class;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_MESSAGE_SIZE + 1)]
            internal string Message;
        }

        # endregion

        # region Input structures

        [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
        private struct sggInMessage
        {
            internal sggHeader Header;
            internal uint Sender;
            internal uint Seq;
            internal uint Time;
            internal uint Class;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_MESSAGE_SIZE + 1)]
            internal string Message;
        }

        # endregion

        # region sHGG Properties

        public string vGGServerAddress = DEFAULT_GG_HOST;
        public string GGServerAddress
        {
            get { return vGGServerAddress; }
            set { vGGServerAddress = value; }
        }

        private string vGGNumber = "0";
        public string GGNumber
        {
            get { return vGGNumber; }
            set { vGGNumber = value; } // todo: nie jesli zalogowany ?
        }

        private string vGGPassword = "";
        public string GGPassword
        {
            get { return vGGPassword; }
            set { vGGPassword = value; } // todo: nie jesli zalogowany ?
        }

        private GGStatusType vGGStatus = GGStatusType.NotAvailable;
        public GGStatusType GGStatus
        {
            get { return vGGStatus; }
            set   // todo: zrobic
            {
                vGGStatus = value;
                if (this.IsGGLogged)
                {
                    sggOutStatus OutStatus = new sggOutStatus();
                    OutStatus.Header.Type = (uint)OUT_STATUS_CHANGE;
                    OutStatus.Status = StatusCode(value, GGDescription);
                    if (this.GGFriendsMask)
                        OutStatus.Status = OutStatus.Status | FRIENDS_MASK;
                    OutStatus.Desc = GGDescription;
                    OutStatus.Header.Size = 4 + (uint)GGDescription.Length;
                    if (GGDescription != "")
                        OutStatus.Header.Size++; 
                    byte[] bOutStatus = RawSerialize(OutStatus);
                    if (GGDescription == "")
                        OutData(bOutStatus, 12);
                    else
                        OutData(bOutStatus, 13 + GGDescription.Length); 
                    // todo: jesli niedostepny?
                }    
            }  
        }

        private string vGGDescription = "";
        public string GGDescription
        {
            get { return vGGDescription; }
            set  // todo: zrobic
            { 
                vGGDescription = value;
                if (vGGDescription.Length > MAX_DESCRIPTIONS_SIZE)
                    vGGDescription = vGGDescription.Substring(0, MAX_DESCRIPTIONS_SIZE); 
                if (this.IsGGLogged)
                {
                    GGStatus = vGGStatus;
                }
            }
        }

        private bool vGGFriendsMask = false;
        public bool GGFriendsMask
        {
            get { return vGGFriendsMask; }
            set 
            {
                vGGFriendsMask = value;
                if (this.IsGGLogged)
                {
                    GGStatus = vGGStatus;
                }
            }
        }

        private byte vGGImageSize = (byte)255;
        public byte GGImageSize
        {
            get { return vGGImageSize; }
            set { vGGImageSize = value; }
        }

        private bool vIsGGLogged = false;
        public bool IsGGLogged
        {
            get { return vIsGGLogged; }
            set { vIsGGLogged = value; }
        }

        # endregion

        # region Other members

        private System.Threading.Thread InIdle;
        private System.Net.Sockets.NetworkStream InStream;
        private System.Windows.Forms.Timer Timer; 

        # endregion

        # region Events and delegates

        //public delegate void EventHandler(object sender, EventArgs args);
        public event EventHandler GGLogged; // zalogowano
       
        public event EventHandler GGLogFailed; // nie zalogowano
        
        public event EventHandler GGDisconnected; // utrata polaczenia

        public delegate void MessageReceiveEventHandler(object sender, MessageReceiveEventArgs args);
        public event MessageReceiveEventHandler GGMessageReceive; // otrzymano wiadomosc
        public class MessageReceiveEventArgs : EventArgs
        {
            public int Number;
            public string Message;
            public DateTime Time;
        }

        # endregion

        // temporary
        //public string temp = "";
        //public static readonly object zamek = new object(); // todo: lock()


        public sHGG()
        {
            

        }

        # region Additional functions

        private static byte[] RawSerialize(object Anything)
        {
            int RawSize = Marshal.SizeOf(Anything);
            IntPtr Buffer = Marshal.AllocHGlobal(RawSize);
            Marshal.StructureToPtr(Anything, Buffer, false);
            byte[] RawDatas = new byte[RawSize];
            Marshal.Copy(Buffer, RawDatas, 0, RawSize);
            Marshal.FreeHGlobal(Buffer);
            return RawDatas;
        }

        private static object RawDeserialize(byte[] RawDatas, Type Anytype)
        {
            int RawSize = Marshal.SizeOf(Anytype);
            if (RawSize > RawDatas.Length)
                return null;
            IntPtr Buffer = Marshal.AllocHGlobal(RawSize);
            Marshal.Copy(RawDatas, 0, Buffer, RawSize);
            object RetObj = Marshal.PtrToStructure(Buffer, Anytype);
            Marshal.FreeHGlobal(Buffer);
            return RetObj;
        }

        private static long Hash(string Password, uint Seed)
        {
            uint x, y, z;
            y = Seed;
            x = 0;
            for (int i = 0; i < Password.Length; i++)
            {
                x = (x & 0xffffff00) | Password[i];
                y ^= x;
                y += x;
                x <<= 8;
                y ^= x;
                x <<= 8;
                y -= x;
                x <<= 8;
                y ^= x;
                z = y & 0x1f;
                y = (y << Convert.ToInt32(z) | (y >> Convert.ToInt32(32 - z)));
            }
            return Convert.ToUInt32(y);
        }

        private static uint StatusCode(GGStatusType Status, string Desc)
        {
            uint Result = STATUS_NOT_AVAILABLE;
            if (Desc.Length == 0)
            {
                switch (Status)
                {
                    case GGStatusType.NotAvailable:
                        Result = STATUS_NOT_AVAILABLE;
                        break;
                    case GGStatusType.Available:
                        Result = STATUS_AVAILABLE;
                        break;
                    case GGStatusType.Busy:
                        Result = STATUS_BUSY;
                        break;
                    case GGStatusType.Invisible:
                        Result = STATUS_INVISIBLE;
                        break;
                }
            }
            else
            {
                switch (Status)
                {
                    case GGStatusType.NotAvailable:
                        Result = STATUS_NOT_AVAILABLE_DESC;
                        break;
                    case GGStatusType.Available:
                        Result = STATUS_AVAILABLE_DESC;
                        break;
                    case GGStatusType.Busy:
                        Result = STATUS_BUSY_DESC;
                        break;
                    case GGStatusType.Invisible:
                        Result = STATUS_INVISIBLE_DESC;
                        break;
                }
            }
            return Result;
        }

        # endregion

        public void GGLogin()
        {
            try
            {
                this.Connect(GGServerAddress, DEFAULT_GG_PORT);
                InIdle = new System.Threading.Thread(new System.Threading.ThreadStart(WaitForData));
                InIdle.Start(); 
            }
            catch
            {

            }             
        }

        public void GGLogout()
        {
            if (this.IsGGLogged)
            {
                InIdle.Abort();
                IsGGLogged = false;
                this.Close();
           }
        }

        private void WaitForData()
        {
            while (true)
            {
                InStream = this.GetStream();
                if (InStream.CanRead)
                {
                    uint PacketType = (uint)(InStream.ReadByte() | InStream.ReadByte() | InStream.ReadByte() | InStream.ReadByte());
                    uint PacketSize = (uint)(InStream.ReadByte() | InStream.ReadByte() | InStream.ReadByte() | InStream.ReadByte());
                    byte[] Bytes = new byte[PacketSize];
                    if (PacketSize > 0)
                        InStream.Read(Bytes, 0, (int)PacketSize);

                    switch (PacketType)
                    {
                        case IN_WELCOME: // done
                            uint Seed = BitConverter.ToUInt32(Bytes, 0);
                            OutLogin60(Seed);
                            break;
                        case IN_LOGIN_OK:
                            IsGGLogged = true;
                            Timer = new System.Windows.Forms.Timer();
                            Timer.Interval = PING_INTERVAL * 1000;
                            Timer.Tick += new EventHandler(DoPing);
                            Timer.Enabled = true;
                            // TODO: out notify - lista kontaktow 
                            if (GGStatus == GGStatusType.NotAvailable)
                                GGStatus = GGStatusType.Invisible;
                            else
                                GGStatus = vGGStatus;
                            if (GGLogged != null)
                                GGLogged(this, EventArgs.Empty); // todo: invoke
                            break;
                        case IN_LOGIN_FAILED: // done
                            IsGGLogged = false;
                            if (GGLogFailed != null)
                                GGLogFailed(this, EventArgs.Empty); // todo: invoke
                            break;
                        case IN_DISCONNECTING:
                            IsGGLogged = false;
                            if (GGDisconnected != null)
                                GGDisconnected(this, EventArgs.Empty); // todo: invoke
                            break;
                        case IN_RECEIVE_MESSAGE:
                            MessageReceiveEventArgs args = new MessageReceiveEventArgs();
                            args.Number =  BitConverter.ToInt32(Bytes, 0);
                            int time1 = BitConverter.ToInt32(Bytes, 8);
                            //args.Time = new DateTime(1970, 1, 1).AddMilliseconds(time1);
                            args.Time = DateTime.Now;
                             // todo: - datetime - skonwertowac z UTC ?
                            args.Message = Encoding.GetEncoding(DEFAULT_ENCODING).GetString(Bytes, 16, (int)PacketSize - 16);
                            if (GGMessageReceive != null)
                                GGMessageReceive(this, args); // todo: invoke
                            break;
                        default:
                            break;
                    }
                }
            }
        }

        void DoPing(object sender, EventArgs e)
        {
            if (IsGGLogged)
            {
                sggOutPing OutPing = new sggOutPing();
                OutPing.Header.Type = OUT_PING;
                OutPing.Header.Size = 0;
                byte[] bOutPing = RawSerialize(OutPing);
                OutData(bOutPing, 0);
            }
        }

        private void OutData(byte[] bStruct, int ByteCount) // todo: lock()
        {
                System.IO.BinaryWriter bWriter = new System.IO.BinaryWriter(InStream, Encoding.ASCII);
                if (ByteCount == 0)
                    bWriter.Write(bStruct);
                else
                    bWriter.Write(bStruct, 0, ByteCount);
        }

        private void OutLogin60(uint Seed)
        {
            sggOutLogin60 OutLogin60 = new sggOutLogin60();
            OutLogin60.Header.Type = OUT_LOGIN60;
            OutLogin60.Header.Size = 31;
            OutLogin60.Number = Convert.ToUInt32(GGNumber);
            OutLogin60.Hash = (uint)Hash(GGPassword, Seed);
            OutLogin60.Status = STATUS_INVISIBLE;
            OutLogin60.Version = DEFAULT_GG_VERSION;
            OutLogin60.Unknown1 = (byte)0x0;
            OutLogin60.LocalIp = 0;
            OutLogin60.LocalPort = DEFAULT_LOCAL_PORT;
            OutLogin60.ExternalIp = 0;
            OutLogin60.ExternalPort = (UInt16)0;
            OutLogin60.ImageSize = GGImageSize;
            OutLogin60.Unknown2 = (byte)0xbe;
            byte[] bOutLogin60 = RawSerialize(OutLogin60);
            OutData(bOutLogin60, 0);
        }

        # region Public methods

        public void GGSendMessage(int Recipient, string Message) // todo: zrobic overload na formaty
        {
            if ((Recipient <= 0) | (Message == "") | (!IsGGLogged))
            //if ((Recipient <= 0) | (Message == ""))
                return;
            sggOutMessage OutMessage = new sggOutMessage();
            OutMessage.Header.Type = OUT_MESSAGE;
            OutMessage.Header.Size = 13 + (uint)Message.Length;
            OutMessage.Seq = 0;
            OutMessage.Class = MESSAGE_CLASS_CHAT;
            if (Message.Length > MAX_MESSAGE_SIZE)
                Message = Message.Substring(0, MAX_MESSAGE_SIZE);
            OutMessage.Recipient = (uint)Recipient;
            OutMessage.Message = Message;
            byte[] bOutMessage = RawSerialize(OutMessage);
            OutData(bOutMessage, 21 + Message.Length);
        }

        # endregion

    }
}

Po dodaniu modułu możemy już komunikować się z GG w naszej aplikacji. Pierwszą rzeczą, którą należy zrobić jest dodanie poniższej linijki do kodu (kodu Twojego programu, nie powyższego modułu oczywiście):

using HAKGERSoft;

A tutaj przykład kodu na szybkie połączenie:

sharpGG = new sHGG();
            sharpGG.GGNumber = "123456"; // twój numer GG
            sharpGG.GGPassword = "abcdefg"; // hasło GG
            sharpGG.GGStatus = sHGG.GGStatusType.Available; // status na dzień dobry
            sharpGG.GGLogin(); // metoda odpowiedzialna za logowanie

Jeśli wszystko pójdzie dobrze, powinniśmy się zalogować i ustawić status jak wyżej.

Aby się wylogować:

sharpGG.GGLogout();

Aby zmienić opis:

sharpGG.GGDescription = "opis"; // wstawia opis

Aby zmienić status:

sharpGG.GGStatus = sHGG.GGStatusType.Available; // przykład dla dostępnego

Inne propercje oraz zdarzenia zaczynają się od frazy GG (np. odbieranie wiadomości: sharpGG.GGMessageReceive) - nie będę tu jednak uczył jak się używa zdarzeń - bo nie o to chodzi.

Aby wysłać wiadomość:

sharpGG.GGSendMessage(123456, "abc"); // wiadomosc pod numer 123456 o treści "abc"

3 komentarzy

Zdaje się chodziło o "właściwości". "Properties" (ang. właściwości) Deti spolszczył na propercje :)

Co to są "propercje"? Jakaś wyjątkowo niefortunna kalka językowa. Nie spotkałem się z nią nigdzie.

Tak tak, zapewne o to chodzi, ale zaskoczyła mnie taka wersja ;]