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"
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 ;]