Персональный блог Игоря Антонова aka "spider_net"

Программируем клиент для Mail.Agent


Рубрика: Статьи -> Журнал Хакер -> Программирование -> Delphi
Метки: |
Просмотров: 7618
Программируем клиент для Mail.Agent

У всеми любимой тети аси давно появился русский клон – «Mail.Агент». Не буду спорить, по популярности аська все еще впереди. Но уже сейчас понятно, что Mail.агента ждет большое будущее. А раз так, ты не должен терять времени зря – усаживайся поудобнее и приготовься постичь тонкости программирования мессенджера нового поколения.

План действий

Как ты понимаешь, Mail.Agent – это обычная сетевая программа, которая использует протокол поверх TCP/IP. Отсюда вывод: чтобы создать своего клиента, необходимо хорошенько раскурить этот протокол, выбрать способ реализации сетевой части и написать пару десятков строчек кода. По первому критерию все должно быть ясно, а вот на втором стоит остановиться. Ты уже, наверняка, в курсе, что на Delphi закодить любое приложение можно используя, минимум, две технологии: с помощью готовых классов и WinAPI-функций. Первый вариант зачастую проще, но зато второй – интересней. Вдобавок он позволяет понять принципы работы ОС. Для написания сетевых приложений в Windows есть целый набор сетевых функций – Winsock API. Про них я уже писал много раз и еще раз писать одно и то же просто не хочется. Поэтому мы рассмотрим готовый и, главное, универсальный компонент для работы с протоколом MMP, а исходник примера с реализацией на чистом WinSock API ты сможешь скачать со всем известного www.vr-online.ru.

TMrim – быстрый путь в нирвану

Delphi-программисты обычно на шаг впереди своих сишных коллег. Этот случай не исключение. Наш соотечественник Алексей Панов позаботился обо всех нас и закодил компонент для комфортной работы с протоколом MMP.

Свойства протокола MMP

  • ActiveAntiSpam (boolean) – активация антиспамовой системы;
  • AntiSpamWords (string) – в этом свойстве записываются слова, которые будут идентифицироваться как спам. Внеси сюда: «порно, секс, недорого, только у нас» и сможешь временно забыть о проблеме нежелательной корреспонденции, которой так много летает по протоколу MMP;
  • login (string) – логин в системе mail.ru, то есть твой ящик в любом из доменов, принадлежащих серверу mail.ru (bk.ru, list.ru, inbox.ru);
  • loginStatus (integer) – статус пользователя. В свойстве устанавливается один из возможных статусов (онлайн, невидимый и т.д.). Некоторые из доступных вариантов:
  • - STATUS_ONLINE – онлайн

    - STATUS_AWAY – отошел (нет на месте)

    - STATUS_FLAG_INVISIBLE – флаг невидимости. Этот флаг нужно использовать, как правило, со STATUS_ONLINE

  • login_s__desc (string) – дополнительное описание текущего состояния. Как правило, сюда пишут «готов поболтать» или что-то в этом роде;
  • password (string) – пароль на указанный в свойстве login аккаунт.
  • События

  • OnAddNewContact – событие возникает при добавлении нового контакта;
  • OnAuthAck – возникает при получении ответа об авторизации;
  • OnAuthReq – событие срабатывает при запросе авторизации. Во время реакции на это событие хорошо бы показывать форму с информацией о пользователе, запросившем авторизацию. В качестве информации принято показывать: ник пользователя, e-mail и текст запроса на авторизацию;
  • OnComposeEvent – событие происходит при начале какого-либо действия со стороны удаленного пользователя. Например, при генерации этого события ты можешь узнать, что удаленный пользователь начал печатать тебе сообщение;
  • OnConnectProgress – событие срабатывает при попытке соединения с сервером Mail.Agent. С помощью этого события ты сможешь информировать пользователя о текущем состоянии подключения. Например, можно установить на форме TProgressBar и отображать на нем прогресс подключения. Кстати, именно этой простой фишки лишен Mail.Agent, так что – наматывай на ус («А нафига это надо?» - Прим. ред.);
  • OnDisconnect – дисконнект он и в Африке дисконнект;
  • OnGetFile – информирует о приеме файла от контакта;
  • OnList – возникает при получении списка контактов;
  • OnListUpdate – происходит при обновлении контактов;
  • OnLoginInfo – срабатывает во время подключения к серверу. Например, если не получилось подключиться из-за неправильно введенных данных (логин, пароль), то тут это можно обработать;
  • OnMeInfo – событие возникает при получении информации о твоем аккаунте;
  • OnMessage – событие генерируется во время получения очередного сообщения;
  • OnMyServicesMessage – при получении сервисных сообщений будет генерировать это событие;
  • OnNewMail – событие возникает в момент прихода в твой почтовый ящик новой корреспонденции;
  • OnRecvNormalAvatar – событие возникает во время приема аватары контакта. Здесь можешь написать код для обновления аватары у нужного контакта;
  • OnRecvSmallAvatar – по сути, то же самое, что и предыдущее, за исключением типа аватары. Событие возникает при приеме маленькой аватары;
  • OnSecondLogin – событие сигнализирует о неприятном известии: под твоим логином кто-то вошел. Во время возникновения этого события нужно поднимать тревогу и убеждать пользователя скорее сменить пароль;
  • OnStatus – событие возникает при изменении каким-нибудь контактом своего статуса;
  • OnUserSearchResult – событие генерируется при получении результатов поиска.
  • Mail.Agent на Delphi

    Практика

    Толку от неподкрепленной практикой теории мало. Поэтому запусти Delphi и приготовься кодить. Как обычно, сразу после запуска Delphi создаст новый пустой проект. Закрой его и загляни на наш DVD. Там тебя ждет компонент TMRim. Скопируй его на винт и заинсталь к своему Dlephi. Компонент устанавливается стандартным способом через Component -> Install Component. Установив компонент, создай новый проект и накидай на форму следующие компоненты:

  • 1 компонент TMainMenu
  • 1 TImageList
  • 1 TMrim
  • 1 TListView
  • 1 TProgressBar
  • По всей форме я растянул компонент TListView. В нем будет отображаться контакт-лист. Чтобы контакты не смотрелись серо и убого, я подобрал в TImageList картинки, соответствующие возможным статусам пользователей (Online, Away, Offline и т.д.). С помощью компонента TMainMenu я создал основное меню из следующих пунктов: подключить, отключить, выбор статуса (онлайн, отошел, невидимый). Готовый вид моей формы ты можешь увидеть на рисунке.

    Mail.Agent на Delphi

    На этом о форме можно забыть и приступить к кодингу. Первым делом научимся устанавливать и разрывать соединение с сервером. Для этого создай обработчик события OnClick для кнопки c именем «Подключить» и напиши в нем:

    If Form2.ShowModal = mrCancel Then
     Exit;
    ConnectBar.Visible := true;
    MailAgent.Login := Form2.LoginEdit.Text;
    MailAgent.Password := Form2.PassEdit.Text;
    MailAgent.LoginStatus := STATUS_ONLINE;
    MailAgent.login_s__desc := ‘Я в сети!!!';
    MailAgent.Connect2(MailAgent.Login, MailAgent.Password);

    Перед тем, как начать попытку подключения, я модально показываю Form2 (смотри рисунок). На этой форме расположены два поля ввода, в которые нужно ввести имя пользователя и пароль. Если пароль и имя пользователя введены успешно, то можно начинать первую попытку соединения. Для этого я заполняю все необходимые свойства компонента TMrim (у меня он носит имя MailAgent). После того, как все поля заполнены, можно выполнить метод Connect2.

    Mail.Agent на Delphi

    Контекстно-зависимый скриншот

    Процесс подключения запущен. Было бы неплохо иметь возможность отследить его текущее состояние на ProgressBar. Создай для компонента TMrim обработчик события OnConnectProgress и напиши в нем:

    ConnectBar.Position := State;
    If State = 100 Then
    begin
     Sleep(100);
     ConnectBar.Visible := false;
    end;

    Думаю, пояснять этот код нет смысла. Сложного в нем абсолютно ничего. Двигаемся дальше. С подключением разобрались, теперь нужно позаботиться о заполнении контакт-листа. Тебе уже известно, что получение списка контактов происходит при возникновении события OnList компонента TMrim, а раз так – создай обработчик события OnList и напиши в нем всего лишь две строчки кода:

    _ContactList := List;
    GetUserList ();

    В первой строчке кода я получаю в переменную _ContactList (объявлена как глобальная переменная типа AContact_List) ссылку на весь список контактов (List). После того, как ссылка получена, я обращаюсь к самописной процедуре GetUserList(), код которой ты можешь увидеть на соответствующей врезке.

    Что может быть проще, чем получить список контактов?

    procedure TForm1.GetUserList;
    var
     i:integer;
     _User : AUser;
    begin
     ListView1.Items.Clear;
     for i:=0 to _ContactList.users_num-1 do
     begin
      _User := _ContactList.users_id[i];
      with ListView1.Items.Add do
      begin
       Caption := _User.user_nick;
       ImageIndex := GetStatusImage(_User.user_status, _user.user_status_id);
      end;
     end;
    end;

    Основная начинка процедуры GetUserList () – пробежка по структуре типа AContact_List, содержащей список контактов, и добавление всех найденных контактов в ListView. Обрати внимание, что в примере из структуры я извлекаю лишь ники пользователей. При программировании реального приложения этим можно не ограничиваться. Например, можно получить дату рождения пользователя, знак зодиака, e-mail и т.д. (полное описание структуры смотри во врезке). Добавляя очередной контакт в ListView, я определяю его статус и уже на основании его устанавливаю соответствующую картинку. Чтобы облегчить этот процесс, я написал функцию GetStatusImage(). В качестве параметров ей нужно передать статус и идентификатор статуса пользователя. Результатом будет число, соответствующее определенному индексу картинки в TImageList. Исходный код функции GetStatusImage() приведен во врезке.

    Чатимся

    Получить список контактов в красивом виде – лишь полдела. Главная функция любого IM – предоставление возможности комфортного общения, для чего программисты обычно создают окно чата с закладками. Каждая такая закладка – это чат с определенным собеседником. Очень удобно и позволяет избавиться от проблемы «завала» всего рабочего стола «окнами». Я решил не отступать от этой идеи и реализовать в нашем примере подобный интерфейс. Сейчас я подробно расскажу, как это делается.

    Создай новую форму и брось на нее один компонент TPageControl (вкладки не создавай) и одну кнопку. У компонента TPageControl установи свойство Align в alTop (растянуть по верху). Теперь растяни этот компонент по всей оставшейся части формы, но не забудь оставить немного места под кнопку. По ее нажатию мы будем отправлять сообщения активному в данный момент собеседнику. Если ты запутался с расположением компонент, то не парься, а просто взгляни на рисунок и по нему подгони свою форму.

    Mail.Agent на Delphi

    Как закончишь с дизайном, возвращайся к модулю главной формы и объяви в ней новую процедуру: CreateNewTab (user_email:string; text:string) , где user_email – e-mail пользователя, от которого пришло (или которому хотим отправить) сообщение. Этот мыльник у нас будет отображаться в качестве заголовка закладки, тем самым, идентифицируя собеседника. Смотрится не очень красиво (в реальном приложении лучше отображать ник пользователя). Весь код этой незатейливой процедуры ты можешь увидеть на соответствующей врезке. Быстренько начинай его переписывать, не забывая при этом иногда возвращаться к тексту статьи за разъяснениями.

    Изначально мы не можем знать точного количества собеседников, для которых в окне чата будем создавать закладки. Поэтому новые вкладки логичней всего создавать динамически – по мере необходимости. Скорее всего, после прочтения последнего предложения тебе стало смешно, – мол, как еще в такой ситуации можно поступить. Оказывается, можно. Однажды мне попался исходник довольно симпатичного асечного клиента. Когда я принялся его изучать и добрался до формы обмена сообщениями, – я ужаснулся.

    Автор программы создал почти сотню (!) вкладок и по мере необходимости игрался со свойством Visible. Но что-то я немного отвлекся. Поскольку все элементы формы сообщений мы будем создавать динамически, то на первом этапе нам нужно определить, какие потребуется создавать элементы управления. Обычно в подобных окнах размещают пару компонент типа TMemo (TRichEdit и всевозможные клоны). Один используется для отражения всей истории переписки, а второй – для ввода текста отправляемого сообщения.

    Помимо элементов ввода нам придется создавать новую закладку в компоненте PageControl. Итак, подведем итог. Для создания новой вкладки нам нужно создать три компонента: два TMemo и один TTabSheet. По этой причине в разделе var моей процедуры я объявил три соответствующие переменные: _NewTab типа TTabSheet (новая закладка для PageControl), _NewTextMemo типа TMemo (ввод текста для отправки) и _NewChatMemo (для отображения всей переписки) аналогичного типа. По моему замыслу, эта процедура должна вызываться каждый раз при получении нового сообщения. Следовательно, перед созданием новой вкладки нужно удостовериться, что для автора полученного сообщения еще не была создана закладка. Для проверки я запускаю цикл (for i:=0 to form3.PageControl1.PageCount-1), которой пробегает по всем существующим вкладкам и сравнивает их заголовок с e-mail отправителя.

    Если совпадение найдено, то нет смысла создавать еще одну закладку. Нужно просто добавить в компонент ChatMemoX полученный текст. Поскольку полного имени компонента (вместе с индексом) мы не знаем, то нам придется его найти с помощью метода FindComponent() элемента, на котором мы собственно, и ищем компонент. В нашем случае компонент ChatMemo будет располагаться на компоненте TPageControl, именно поэтому метод я вызываю следующим образом:

    Form3.PageControl.Pages[i].FindComponent(‘ChatMemo’+ IntToStr(i+1)).

    В качестве одного единственного параметра для метода FindComponent() я передаю имя искомого компонента, плюс его индекс, который будет равен индексу текущей закладки + 1 (так как первая вкладка имеет индекс 0). Компонент найден, – теперь надо добавить в него полученный текст. Чтобы это сделать, я привожу полученный в результате поиска объект к типу TMemo, а затем вызываю метод свойства Lines – Add(). Дальше все просто – добавив полученный текст, вызываю метод Show формы сообщений и выхожу из процедуры. Мы рассмотрели вариант с найденной вкладкой. А если она еще не была создана? Конечно же, нужно ее создать! Порядок очереди создания новых элементов будет таким:

  • Новая вкладка (TNewTabSheet);
  • Новый элемент хранения истории переписки (TNewChatMemo);
  • Новый элемент для ввода текста для отправки (TNewTextMemo).
  • Каждый визуальный компонент в Delphi – это обычный объект, который первым делом нужно инициализировать (выделить память). После можно заполнять все свойства, прикручивать обработчики событий и т.д. Единственный нюанс при создании визуальных компонент – необходимость «вставки» (указание родителя) этого компонента на любой другой. Иначе, как можно догадаться, – компонент не отобразится. Чтобы вставить вновь созданный компонент в форму, необходимо лишь вызвать у формы метод InsertControl(). В качестве единственного параметра нужно указать ссылку на проинициализированный элемент управления.

    function TForm1.GetStatusImage(status: integer; advStatusId:string): integer;
    begin
     Result := 1;
     Case Status Of
      STATUS_ONLINE: Result := 0;
      STATUS_OFFLINE: Result := 1;
      STATUS_AWAY: Result := 2;
      STATUS_FLAG_INVISIBLE : Result := 5;
      STATUS_USER_DEFINED:
    begin
     if advStatusId = 'status_chat' then Result := 3;
     if advStatusId = 'status_dnd' then Result := 4;
    end;
    end;
    end;
    procedure TForm1.CreateNewTab(user_email: string; text:string);
    var
    _NewTab : TTabSheet;
    _NewTextMemo, _NewChatMemo:TMemo;
    i:integer;
    begin
    for i:=0 to form3.PageControl1.PageCount-1 Do
     if form3.PageControl1.Pages[i].Caption = user_nick then
    begin
     form3.PageControl1.ActivePageIndex := i;
     if Text <> '' then
      TMemo(Form3.PageControl1.Pages[i].
     
     FindComponent('ChatMemo'+IntToStr(i+1))).
     
     Lines.Add(user_nick+': '+text);
     form3.Show;
    Exit;
    end;
    _NewTab := TTabSheet.Create(form3.PageControl1);
    _NewTab.Caption := user_nick;
    _NewTab.Name := 'Tab'+IntToStr(form3.PageControl1.PageCount);
    _NewTab.PageControl := form3.PageControl1;
    _NewChatMemo := TMemo.Create(_NewTab);
    _NewChatMemo.Name := 'ChatMemo'+IntToStr(Form3.PageControl1.PageCount);
    _NewChatMemo.Align := alTop;
    _NewTab.InsertControl(_NewChatMemo);
    _NewTextMemo := TMemo.Create(_NewTab);
    _NewTextMemo.Name := 'TextMemo'+IntToStr(form3.PageControl1.PageCount);
    _NewTextMemo.Align := alTop;
    _NewTab.InsertControl(_NewTextMemo);
    _NewChatMemo.Lines.Clear;
    _NewTextMemo.Lines.Clear;
    If Text <> '' Then
     _NewChatMemo.Lines.Add(user_nick+': '+text);
    Form3.PageControl1.ActivePage := _NewTab;
    Form3.Show;
    end;

    AContact_List

    AContact_List = record
    //Количество групп
     group_num: integer;
     //Массив структур типа AGroup
     groups_id: array[0..20] of AGroup;
     //Массив структур типа AUser
     users_id: array[0..1023] of AUser;
     //Общие количество контактов
     users_num: integer;
    AUser = record
     user_flags: integer;
     user_gnum: integer;
     //Email пользователя
     user_mail: string;
     //Nick пользователя
     user_nick: string;
     //Имя пользователя
     user_name: string;
     //Фамилия пользователя
     user_lastname: string;
     //Дата рождения
     user_bday: string;
     //Место жительства
     user_location: string;
     //Флаги
     server_flags: integer;
     //Статус пользователя
     user_status: integer;
     //Телефон
     user_phone: string;
     //Идентификатор пользователя
     user_id: integer;
     //Идентификатор статуса
     user_status_id,
     //статус пользователя
     user_status_name,
     //Оисание статуса
     user_status_desc: string;
     //Идентификатор клиента пользоваля
     user_client_id: string;
    end;

    Тестируем клиента

    Клиент готов, теперь самое время его хорошенько протестировать. Я специально завел пару аккаунтов на mail.ru: один для только что испеченного нами клиента (user_test_2008@mail.ru), а второй – для Mail.Agentа (user_test_2009@mail.ru). Оба клиента успешно соединились с сервером и обменялись несколькими сообщениями. Результат ты можешь увидеть на рисунках (с соответствующими подписями).

    Mail.Agent на Delphi

    Mail.Agent на Delphi

    Happy End

    Лекцию рассмотрения внутренностей протокола MMP можно считать оконченной. Тебе остается переварить знания и воспользоваться ими в своих хацкерских целях. Полученной инфы вполне хватит для написания полноценного Mail.Agent’a или спамбота. К сожалению, спецификация протокола уже давно не обновлялась, а значит, шагать в ногу с Mail.Agent’ом, увы, не получится. Будем надеяться, что девелоперы MA расщедрятся и полностью откроют протокол со всеми вкусностями (передача видеоданных и т.д.). Пока нам остается лишь пользоваться тем, что есть. На этой немного грустной ноте я хочу попрощаться и пожелать тебе больше позитива и поменьше Access Violition. Пока!

    Исходники примера

    Статья опубликована в журнале "Хакер" (http://xakep.ru). Август 2008 г.

    Ссылка на журнал: http://goo.gl/YTLMNp

    P.S. Друзья, перед тем как писать комментарии в стиле "Автор, на дворе 2014 год, на фиг нам Mail.Agent на Delphi", обрати внимание на дату статьи - август 2008. На блоге я собираю все свои статьи, которые писал для журналов. Увы, некоторые из них утратили актуальность.

    Оставьте комментарий!
    comments powered by HyperComments