MSN Messenger em delphi

como trabalhar com o protocolo do MSN Messenger em delphi

Este artigo encontra-se fortemente desatualizado e será atualizado em breve

Esta é uma implementação do protocolo do MSN Messenger em delphi. Não está completo e para construí-lo, você precisará do pacote WSocket. A maior parte do que é apresentado aqui é uma parte da especificação (ainda não o suficiente para sequer fazer um clone do MSN Messenger). O trabalho que você vê aqui ainda tem aprimorações a serem feitas (muito devido ao fato de que eu sou novo em programação Socket), este artigo é baseado em obras de venkydude, artigos de  MSN e numa versão antiga do KMerlin (um clone do MSN Messenger para opensource linux). Este é o segundo artigo que escrevi sobre Instant Messaging (O primeiro foi um protocolo sobre o yahoo, algo que eu não tenha conseguido concluir devido às limitações de tempo (muito trabalho)) estou planejando atualizar este artigo assim que possível.

CÓDIGO <--------------------------------- --------------- ------------------------->

(A FAZER GLOBAL: IMPLEMENTAR AFAZERES LOCAIS, limpar, estender)

 unidade MSNMessenger;

 interface

 utiliza
 WSocket, MD5, Classes, SysUtils;

 tipo
 TUserState = (
 usOnline, / / você está online
 usBusy, / /  ocupado
 usBRB, / / Já volta
 usAway, / / Ausente
usOnPhone, / / No Telefone
 usLunch, / / Almoço
 usHidden, / / Invisível
 usOffline / / offline
 );

 TMSNMessenger = class (TComponent)

 privado

 FConnected: Boolean;
 FUserName: String;
 FPassword: String;
 FFriendlyUserName: String;
Flog: TStrings;
 FFriendlyNameChange: TNotifyEvent;
 FState: TUserState;
 função GetHost: String;
 procedimento SetHost (const Valor: String);
 função GetPort: String;
 procedimento SetPort (const Valor: String);
 procedimento SetUserName (const Valor: String);
 procedimento SetPassword (const Valor: String);
 função GetFriendlyUserName: String;
 procedimento SetFriendlyUserName (const Valor: String);
 procedimento SetState (const Valor: TUserState);

 protegido
 FSocket: TWSocket;
 FTrialID: Integer;

 procedimento SendVER;
 procedimento ReceiveSYN;

 procedimento SocketWrite (const AString: String);
 procedimento LogWrite (const Dados: Seqüência);
 procedimento ProcessCommand (const ACommand: String);
 procedimento SocketDisconnect (Sender: TObject; Erro: Word);
 procedimento SocketDataAvailable (Sender: TObject; Erro: Word);
 procedimento SocketConnect (Sender: TObject; Erro: Word);

 procedimento TriggerFriendlyNameChange; dinâmico;

 público

 construtor Criar (AOwner: TComponent); substituir;

 destruidor Destroy; substituir;

 procedimento Login;
 procedimento Logoff;
 publicado
 propriedade Host: String lê GetHost escreve SetHost;
 propriedade Porta: String lê GetPort escreve SetPort;
 propriedade Nome_de_Usuário: String lê FUserName escreve SetUserName;
 propriedade Senha: String lê FPassword escreve SetPassword;
 propriedade FriendlyUserName: String lê GetFriendlyUserName escreve SetFriendlyUserName;
 propriedade Conectado: Boolean lê FConnected;
 propriedade Log: TStrings lê flog escreve flog;
 propriedade FriendlyNameChange: TNotifyEvent lê FFriendlyNameChange escreve FFriendlyNameChange;
 propriedade Status: TUserState lê FState escreve SetState;
 fiml;

 implementação

 usa windows;

 constRealState: arrumar [TUserState] do String =
 ( "CHG NLN% d ',' CHG BSY% d ',' CHG BRB% d ',' CHG AWY% d ',' CHG NPH% d ',' CHG LUN% d ',
 «CHG HDN% d ',' CHG FLN% d ');

 tipo
 CharSet = Conjunto de char;

 função UTF8ToAnsi (x: string): ansistring;
 (Função que recebe sequencia UTF8 e converte a ansi string)

 var
 i: integer;
 B1, B2: byte;

 começo
 Resultado: = x;
 i: = 1;
 enquanto i <= Comprimento (Resultado) começa
 se (ord (Resultado [i]) e US $ 80) <> 0, então, começar
 b1: = ord (Resultado [i]);
 b2: = ord (Resultado [i + 1]);
 if (b1 e US $ F0) <> $ C0 então
 Resultado [i]: = # 128

 se não, começar
 Resultado [i]: = Chr ((b1 SHL 6) ou (b2 e US $ 3F));
 Apagar (Resultado, i + 1,1);
 fim;
 fim;
 inc (i);
 fim;
 fim;

 função AnsiToUtf8 (x: ansistring): string;
 (Função que recebe ansi string e converte para a sequência UTF8)

 var
 i: integer;
 B1, B2: byte;

 começo
 Resultado: = x;
 para i: = Comprimento (Resultado) para 1
 se Resultado [i]> = # 127, então, começar
 b1: = $ C0 ou (ord (Resultado [i]) shr 6);
 b2: = $ 80 ou (ord (Resultado [i]) e US $ 3F);
 Resultado [i]: = chr (b1);
 Inserir (chr (b2), Resultado, i + 1);
 fim;
 fim;

 FunçãoExtractWord (N: Integer; S: String; WordDelims: CharSet): String;
 Var
 I, J: Word;
 Count: Integer;
 SLen: Integer;

 começo
 Contador: = 0;
 I: = 1;
 Resultado: ='';
 SLen: = Length (S);
 Enquanto I <= sLen Começar

 (preskoc oddelovace)

 Enquanto (I <= sLen) E (S [I] Em WordDelims) São Inc (I);
 (neni-li na konci retezce, bude nalezen zacatek slova)

 Se I <= sLen Então,  Inc (Count);
 J: = I;

 (um je zde konec slova)

 Enquanto (J <= sLen) e não (S [J] Em WordDelims) são Inc (J);
 (je-li-te toto n slovo, vloz ho nd vystup)
 
 Se Count = N, então, começe
 Resultado: = Copy (S, I, JI);
 Sair
 Fim;

 I: = J;
 Fim; (enquanto)
 Fim;


 função WordAt (const Texto: string; Posição: Integer): string;
 começo
 Resultado: = ExtractWord (Posição, Texto, [ '']);
 Fim;

 TMSNMessenger ()

 construtorTMSNMessenger.Criar (AOwner: TComponent);
 começar
 herdada Criar (AOwner);
 FSocket: = TWSocket.Create (Self);
 FSocket.Addr: = 'messenger.hotmail.com';
 FSocket.Port: ='1863 ';
 FSocket.Proto: = 'tcp';

 FSocket.OnSessionConnected: = SocketConnect;
 FSocket.OnSessionClosed: = SocketDisconnect;
 FSocket.OnDataAvailable: = SocketDataAvailable;
 FConnected: = False;
 Fim;

 destruidor TMSNMessenger.Destroy;

 começo
 FSocket.Free;
 FSocket: = nil;
 herdou Destroy;
 Fim;

 função TMSNMessenger.GetFriendlyUserName: String;
 começo
 se não FConnected então
 Resultado: = FFriendlyUserName;
 Fim;

 função TMSNMessenger.GetHost: String;
 começo
 Resultado: = FSocket.Addr;
 Fim;

 função TMSNMessenger.GetPort: String;
 começo
 Resultado: = FSocket.Port;
 Fim;

 procedimento TMSNMessenger.Login;
 começo
 FSocket.Connect;
 Fim;

 procedimento TMSNMessenger.Logoff;
 começo
 Fim;

 procedimento TMSNMessenger.LogWrite (const Dados: Seqüência);
 começo
 se Assigned (flog) então
 FLog.Add (Dados);
 Fim;

 (Processcommand aqui é semelhante a um processo windowproc, aqui processamos todo o tipo de informações enviadas do servidor a partir de agora, é IFFull (repleto de se's) talvez se eu tiver algum tempo livre vai transformar isso em um caso)

 A FAZER: Limpar este procedimento bagunçado
 A FAZER: Adicionar mais comandos

 TMSNMessenger.ProcessCommand procedimento;
 var
 Tmp: String;
 Hash: String;
 começo
Tmp: = WordAt (ACommand, 1);

 se Tmp = 'VER', então,
 SocketWrite ( 'INF% d');

 se Tmp = 'INF', então, 
 SocketWrite ( 'USR% d MD5 Eu + FUserName);

 se Tmp = 'USR', então,

 começo
 se WordAt (ACommand, 4) ='S', então,

 começo
 Hash: = WordAt (ACommand, 5);
 Apagar (Hash, pos (# 13 # 10, hash), Comprimento (hash));
 Hash: = StrMD5 (hash + senha);
 SocketWrite ( 'USR% d MD5 S' + Minúsculas (hash));
 outro fim

 começo
 FFriendlyUserName: = WordAt (ACommand, 5);
 SocketWrite («SYN 1% d ');
 ReceiveSYN;
 fim;
 fim;
 
 (Quando você receber um XFR e você não estiver conectado ao servidor msn, significa redirecionar para um outro servidor)

 se (TMP = 'XFR') e não Conectado então

 começo
 TMP: = WordAt (ACommand, 4);
 FSocket.Fechar;
 Apagar (Tmp, pos ( ':', Tmp), Comprimento (Tmp));
 FSocket.Addr: = Tmp;
 TMP: = WordAt (ACommand, 4);
 Apagar (Tmp, 1, pos ( ':', Tmp));
 FSocket.Port: = Tmp;
 FSocket.Connect;
 Sair;
 fim;

Rename Friendly name

 se (TMP = "REA") então
 começo
 FFriendlyUserName: = WordAt (ACommand,5);
 FFriendlyUserName: = StringReplace (FFriendlyUserName, «20%», « ', [rfReplaceall]);
 TriggerFriendlyNameChange;
 fim;
 
 (O comando out é recebido antes do servidor nos  desconecta, se é porque nós nos loggamos em outra máquina, vamos receber a mensagem OUT OTH (OUTRA MÁQUINA)

A Fazer: escrever algum evento ou algo para recuperar essa notificação)

 se (TMP = "OUT") então
 começo
 se pos ( "OTH", ACommand)> 1, então
 LogWrite ( 'entrou em um outro computador desligando');
 fim;

 fim;

(SYN é sem dúvida um dos Comandos MSN Messenger mais informativos que o SYN nos informa sobre:
 e-mail disponível
 Lista de Amigos
 Lista de Bloqueados
 Lista invertida (as pessoas que têm você em suas listas)
 Os números de telefone (Principal, móveis, etc)
 configurações MSN Messenger
 etc

 No entanto, isto tem um preço, já que há tanta informação WSocket que pode não obter todas as informações corretamente (uma qualidade do não bloqueio de sockets) assim, para obtê-lo, vamos congelar esta thread durante 5 segundos  (significando que seus formulários não irão receber qualquer mensagem e parecem não responder por um tempo), Eu sei que deve ter um jeito melhor, se alguém souber, me mande um email.


 A FAZER: Analisar o conteúdo recebido
 A FAZER: procurar um caminho que não te obriga a congelar a thread
 )

 procedimento TMSNMessenger.ReceiveSYN;
 var
 Tmp: String;

 começo
FSocket.OnDataAvailable: = nil;

 Sleep (5000);
 Tmp: = FSocket.ReceiveStr;

 FSocket.OnDataAvailable: = SocketDataAvailable;
 Tmp: = UTF8ToAnsi (Tmp);
 LogWrite ( 'RECV:' + Tmp);
 SocketWrite ( 'CHG NLN% d');
 fim;

 procedimento TMSNMessenger.SendVER;
 começo
 SocketWrite ( 'VER% d CVR0 MSNP5 MSNP6 MSNP7')
 fim;

 procedimento TMSNMessenger.SetFriendlyUserName (const Valor: String);
 var
 tmp: String;
 começo
 se FConnected e (FUserName <> Value), então,
 começo
 tmp: = StringReplace (Valor, '', '% 20', [rfReplaceAll]);
 tmp: = AnsiToUtf8 (Tmp);
 SocketWrite ( "REA% d '+ + FUsername'' + Tmp);
 fim;
 fim;

 procedimento TMSNMessenger.SetHost (const Valor: String);
 começo
se não Conectado, então,
 se FSocket.Addr <> Value, então,
 FSocket.Addr: = Valor;
 fim;

 procedimento TMSNMessenger.SetPassWord (const Valor: String);
 começo
 se não Conectado, então,
 se (FPassword <> Value), então,
 FPassword: = Valor;
 fim;

 procedimento TMSNMessenger.SetPort (const Valor: String);
 começo
 se não Conectado, então,
 se FSocket.Port <> Value, então,
 FSocket.Port: = Valor;
 fim;

 procedimento TMSNMessenger.SetState (const Valor: TUserState);
 começo
 se FConnected então
 se (FState <> Value), então
SocketWrite (RealState [Valor]);
 fim;

 procedimento TMSNMessenger.SetUserName (const Valor: String);
 começo
 se não FConnected então
 se FUsername <> Value, então,
 FUserName: = Valor;
 fim;

 procedimento TMSNMessenger.SocketConnect (Sender: TObject; Erro: Word);
 começo
 FTrialID: = 1;
 SendVER;
 fim;

 procedimentoTMSNMessenger.SocketDataAvailable (Sender: TObject; Erro: Word);
 var
 Tmp: String;
 começo
 Tmp: = FSocket.ReceiveStr;
 Tmp: = UTF8ToAnsi (Tmp);
 LogWrite ( 'RECV:' + Tmp);
 ProcessCommand (Tmp);
 fim;

 procedimento TMSNMessenger.SocketDisconnect (Sender: TObject; Erro: Word);
 começo
 FConnected: = False;
 LogWrite ( 'Desligado');
 fim;

 procedimento TMSNMessenger.SocketWrite (const AString: String);
 começo
 FSocket.SendStr (Format (AString, [FTrialID]) + # 13 + # 10);
 LogWrite ( «ENVIO: '+ Format (AString, [FTrialID]));
 Inc (FTrialID);
 fim;

 procedimento TMSNMessenger.TriggerFriendlyNameChange;


 começo
 se Assigned (FFriendlyNameChange), então,
FFriendlyNameChange (Self);
 fim;

 fim.

CÓDIGO <---------------------------------/ -------------- -------------------------> Uma amostra seria: amsn: = TMSNMessenger.Create (Self); / / amsn é uma variável do tipo TMSNMessenger AMSN.UserName: =''; / / Isto indica que o nome de usuário deve ser de forma * @ hotmail.com AMSN.PassWord: ='';// Isso indica a senha AMSN.Log: = MEmo1.Lines; / / Log indica um destino para despejar as informações recebidas e enviadas, vou usá-lo para obter protocolo de informações e tal, mas não é obrigatória a sua utilização por AMSN.Login; / / procedimento que indica que devemos iniciar o processo de entrada.