Авторские статьи ООП на службе зла [vk.com dumper]

Discussion in 'Статьи' started by BlackIce, 9 Feb 2013.

  1. BlackIce

    BlackIce Elder - Старейшина

    Joined:
    10 Jan 2013
    Messages:
    100
    Likes Received:
    31
    Reputations:
    27
    Интро

    Сейчас актуально писать мелкий говнософт для разных социальных сетей. Обычно начинающие быдлокодеры пишут подобное с помощью процедурного подхода и если проект более-менее сложный начинают захлебываться в глобальных переменных и значениях процедур и функций. Я хочу продемонстрировать ООП подход и его приимущества. В статье будет ценен не код программы (скорее он то только для устрашения), а способ мышления при использовании ООП подхода.

    Этап 1

    ТЗ: Написать дампер сообщений социальной сети ВКонтакте с возможностью дампить переписку как по SID так и по логину/паролю.

    Оговорюсь заранее, что при написании софта, я встретил много подводных камней, которые значительно усиливали запутанность программы. Я не стал вдаваться в безупречную реализацию, тк, пишу для демонстрации объектно ориентированного метода, но все-же я пометил комментариями участки кода, требующие доработки.

    Что нам понадобится?
    1) Язык поддерживающий ООП (Я пишу на Дэльфи)
    2) Сниффер сетевого трафика (работать будем в основном с http поэтому httpAnalyzer вполне устроит).

    Этап 2

    По ООП есть куча разной литературы. Я попробую максимально доступно объяснить всю суть на конкретном примере. Итак, исходя из ТЗ мне нужно создать класс объекта, который смог бы проделывать определенные операции на сайте. Для начала происнифаем сайт и разберемся в алгоритмах запросов. Об этом я вообще писать не буду. По окончании у нас будет алгоритм примерно такой:
    1 - Авторизуемся
    2 - парсим корешей
    3 - парсим месаги

    Создаем новый класс объекта . Коротко немного теории : каждый класс что-то знает и что-то умеет делать. То что класс знает называется полями класса (если угодно переменные), а то что он умеет - называется методами класса (если угодно процедуры и функции)... В самомо начале проэктируется внешний вид класса (что знает умеет), а потом реализация (как он умеет). Чтож, давайте попробуем определиться, что необходимо знать и уметь нашему классу...

    Знать: авторизационные данные (логин пароль), SID пользователя, количество друзей пользователя, список ID друзей, и он должен взаимодействовать с сетью по протоколу http, поэтому будет разумно запихнуть в него объект типа TidHTTP. Можно продолжать, но в учебных целях притормозим :cool:
    Уметь: Авторизоваться, Парсить ID друзей, Парсить сообщения.

    Также, каждый объект класса будет инициализирован под средством метода конструктора Create () . В нем удобно создавать объекту все условия для комфортной работы (создавать файлы/ соединяться с БД/ задавать настройки по умолчанию других объектов.)


    Code:
    //---------------Class for work with Vk.com--------- 
    type
        TVKDump = Class
                    Login, Password : String; // Данные для авторизации
                    Link : String;            // Нужная шняга
                    SID : String;             // Значение remixsid в кукисах
                    HTTP : TidHTTP;           // Для работы с нетом
                    FriendList : TStringList; // Список ИДшников друзей
                    FriendCount : Integer;    //Количество друзей
                    function ParseMessage (FriendID : String) : Boolean;   //Вытаскиваем сообщения со скритов
                    function Authorize () : Boolean;                      //Авторизация
                    function ParseFriends () : Integer;                   //Парсим ИД корешей
                    constructor Create ();                                // Чето создаем и настраиваем
                  end;
    //-------------------------------------------------
    Этап 3

    Вот собственно и все! Осталось всего ничего описать реализацию каждой функции :D. Здесь в подробности я вдаваться не буду, просто выложу свой быдлокод .... Там в основном парсинг и танци с бубном вокруг запросов к ВК.

    Code:
    //------------function parse messages with a script--------------------------
    function TVKDump.ParseMessage (FriendID : String) : Boolean;
      var Data, MsgList : TStringList;
          Name, Mess, tmp : String;
          i, j : Integer;
      begin
        Result := True;
    try
     try
        HTTP.AllowCookies := False;
        HTTP.Request.CustomHeaders.Clear;
        HTTP.Request.CustomHeaders.Add('Cookie:remixsid=' + SID);
        MsgList := TStringList.Create;
        Data := TStringList.Create;
    
    
          for j := 0 to 5 do
            begin
              Data.Clear;
              Data.Add('act=a_history');
              Data.Add('al=1');
              // offset - параметр смещения сообщений, можно пробовать подставлять
              Data.Add('offset=' + IntTostr(j*50));
              Data.Add('peer=' + FriendID);
              Data.Add('whole=0');
    
              tmp := HTTP.Post('http://vk.com/al_im.php', Data);
              HTTP.AllowCookies := True;
              Sleep(200);
                for i := 1 to 100 do
                  begin
                    Delete(tmp, 1, Pos('class="mem_link" target="_blank">', tmp) + 32);
                    Name := Copy(tmp, 1, Pos('</a>', tmp) - 1);
                    Delete(tmp, 1, Pos('</div>', tmp) + 5);
                    Mess := Copy(tmp,1, Pos('</div>', tmp) - 1);
                    MsgList.Add(Name + ':' +Mess);
                  end;
           end;
           MsgList.SaveToFile(FriendID + '_damp.txt');
     except
      Result := False;
     end;
    finally
        MsgList.Free;
        Data.Free;
    end;
      end;
    
    //----------------------------------------------------------
    
    
    
    
    
    
    
    //-----------------authorization-----------------------------
    function TVKDump.Authorize () : Boolean;    
    
    
    //---------------parse link necessary to request-------------
    function ParseLink ( Page : String) : Boolean;
      begin
        Result := false;
        Delete(page,1,Pos('action=',page)+15);
        link := Copy(page,1,pos('>',page)-2);
          if link <> ''
            then
              begin
                Link := 'http://' + Link;
                Result := true;
              end;
      end;
    //--------------------------------------------------------------
    
    
    
    var Data:TStringList;
        tmp: String;
    
      begin
        Result := False;
          try
            try
    				  Data := TStringList.Create;
    				  Data.Add('email='+Login);
    				  Data.Add('pass='+Password);
    
    
    
    //-----------  реализация быдлятская, но нет времени делать норм -----------------------
              ParseLink(HTTP.Get('http://m.vk.com/login'));
              HTTP.HandleRedirects := False;
              HTTP.AllowCookies := False;
              try
    				    HTTP.Post(Link, Data);
              except
                try
                  HTTP.Get(HTTP.Response.Location);
                except
                  tmp := HTTP.Response.RawHeaders.Text;
                  Delete(tmp, 1, Pos('remixsid=',tmp) + 8);
                  SID :=Copy( tmp,1 ,Pos('expires=',tmp)-3);
                end;
    
              end;
    //--------------------------------------------------------------------------------------
              HTTP.HandleRedirects := True;
              HTTP.AllowCookies := True;
              tmp := HTTP.Get(HTTP.Response.Location);
    
              
    				    if Pos('logout',tmp) <> 0
    					    then Result := True;
    			except
    				Result := False;
    			end;
    			finally
    				Data.Free;
    			end;
    
    		end;
    
    
    
    //-------------parsing function friends ID's -----------------------
    // парсит вроде первых 20 чтобы парсило всех нужно допиливать
    
    function TVKDump.ParseFriends () : Integer;
    var tmp: String;
        i:integer;
      begin
        HTTP.AllowCookies := False;
        HTTP.Request.CustomHeaders.Add('Cookie:remixsid=' + SID);
        tmp := HTTP.Get('http://m.vk.com/friends');
        HTTP.AllowCookies := True;
        Delete(tmp,1,Pos(' <em>',tmp)+4);
        FriendCount :=StrToInt(Copy(tmp,1,Pos('</em>',tmp)-1));
          for i:=1 to 18  do
    
            begin
              Delete(tmp,1,Pos('<a href="/write',tmp)+14);
              FriendList.Add(copy(tmp,1,Pos('">',tmp)-1));
            end;
        FriendList.SaveToFile('friends.txt');
      end;
    //-----------------------------------------------------------
    
    
    
    //-------------- Созаём нужные объекты и настраиваем HTTP ----------------
    constructor TVKDump.Create ();
    
      begin
        FriendList := TStringList.Create;
        HTTP := TIdHTTP.Create(nil);
        HTTP.HandleRedirects := True;
          with HTTP.Request do
            begin
              UserAgent := 'Mozilla/5.0 (Windows NT 5.1; rv:16.0) Gecko/20100101 Firefox/16.0';
              ContentType := 'application/x-www-form-urlencoded';
              AcceptLanguage := 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3';
            end;
    
      end;
    Этап 4

    На 4 этапе необходимо создать объект нашего класса. Для этого его нужно инициализировать методом Create (). Также описывается поведение объекта и взаимодействие с другими компонентами программы. Дабы немного поизвращаться я не стал делать GUI. Программа получает необходимые данные ввиде параметров командной строки, а результаты пишет в файлы.

    Code:
     Var
          VK : TVKDump;
          i : Integer;
    begin
      VK := TVKDump.Create ();
    
    
    
    
    //-----------если без запущена параметров, тогда выходим -----------------
       if ((Paramstr(1) = '') and (Paramstr(2) = '')) then
        begin
          Exit;
        end;
    
    //----------возможность указать SID---------------------------------------
     if ((Paramstr(1) = '0')  and (Paramstr(2)  = '0'))
      then
        begin
          VK.SID := Paramstr(3);
            VK.ParseFriends();
              for i := 1 to 3 do  // можно ставить VK.FriendsCount но нужно допилить функцию парсинга друзей
                VK.ParseMessage(VK.FriendList[i]);
        end
    
       else  //-----------иначе указываем логин и пароль------------------------
    
        begin
          VK.Login := Paramstr(1);
          VK.Password := Paramstr(2);
          VK.Authorize;
          VK.ParseFriends();
            for i := 1 to 5 do   // можно ставить VK.FriendsCount но нужно допилить функцию парсинга друзей
              VK.ParseMessage(VK.FriendList[i]);
        end;
    
    
    end.
    Чтобы автоизоваться с помощью логина и пароля нужно в консольке набрать
    А если по SID:
    Оутро

    Моя программа является еще сырым приложением и используется в качестве наглядности, но если есть необходимость и руки = тру, то и интерфейс можно прикрутить и потоки и функции поправить...
    Подобным образом создаются проекты побольше со сложной структурой. Мир ООП очень богат и при разумном использовании очень помогает упростить жизнь разработчику как професионального уровня так и мелкому фрилансеру. Надеюсь кому-то пригодиться. Спасибо за внимание. BlackIce.

    Привожу код всего приложения:
    Code:
    program VK_Dumper;
    Uses
    IdHttp,IdCookieManager,Dialogs, IdHTTPHeaderInfo, Classes, SysUtils;
    
    //---------------Class for work with Vk.com---------
    type
        TVKDump = Class
                    Login, Password : String; // Данные для авторизации
                    Link : String;            // Нужная шняга
                    SID : String;             // Значение remixsid в кукисах
                    HTTP : TidHTTP;           // Для работы с нетом
                    FriendList : TStringList; // Список ИДшников друзей
                    FriendCount : Integer;    //Количество друзей
                    function ParseMessage (FriendID : String) : Boolean;   //Вытаскиваем сообщения со скритов
                    function Authorize () : Boolean;                      //Авторизация
                    function ParseFriends () : Integer;                   //Парсим ИД корешей
                    constructor Create ();                                // Чето создаем и настраиваем
                  end;
    //-------------------------------------------------
    
    
    
    
    
    //------------function parse messages with a script--------------------------
    function TVKDump.ParseMessage (FriendID : String) : Boolean;
      var Data, MsgList : TStringList;
          Name, Mess, tmp : String;
          i, j : Integer;
      begin
        Result := True;
    try
     try
        HTTP.AllowCookies := False;
        HTTP.Request.CustomHeaders.Clear;
        HTTP.Request.CustomHeaders.Add('Cookie:remixsid=' + SID);
        MsgList := TStringList.Create;
        Data := TStringList.Create;
    
    
          for j := 0 to 5 do
            begin
              Data.Clear;
              Data.Add('act=a_history');
              Data.Add('al=1');
              // offset - параметр смещения сообщений, можно пробовать подставлять
              Data.Add('offset=' + IntTostr(j*50));
              Data.Add('peer=' + FriendID);
              Data.Add('whole=0');
    
              tmp := HTTP.Post('http://vk.com/al_im.php', Data);
              HTTP.AllowCookies := True;
              Sleep(200);
                for i := 1 to 100 do
                  begin
                    Delete(tmp, 1, Pos('class="mem_link" target="_blank">', tmp) + 32);
                    Name := Copy(tmp, 1, Pos('</a>', tmp) - 1);
                    Delete(tmp, 1, Pos('</div>', tmp) + 5);
                    Mess := Copy(tmp,1, Pos('</div>', tmp) - 1);
                    MsgList.Add(Name + ':' +Mess);
                  end;
           end;
           MsgList.SaveToFile(FriendID + '_damp.txt');
     except
      Result := False;
     end;
    finally
        MsgList.Free;
        Data.Free;
    end;
      end;
    
    //----------------------------------------------------------
    
    
    
    
    
    
    
    //-----------------authorization-----------------------------
    function TVKDump.Authorize () : Boolean;    
    
    
    //---------------parse link necessary to request-------------
    function ParseLink ( Page : String) : Boolean;
      begin
        Result := false;
        Delete(page,1,Pos('action=',page)+15);
        link := Copy(page,1,pos('>',page)-2);
          if link <> ''
            then
              begin
                Link := 'http://' + Link;
                Result := true;
              end;
      end;
    //--------------------------------------------------------------
    
    
    
    var Data:TStringList;
        tmp: String;
    
      begin
        Result := False;
          try
            try
    				  Data := TStringList.Create;
    				  Data.Add('email='+Login);
    				  Data.Add('pass='+Password);
    
    
    
    //-----------  реализация быдлятская, но нет времени делать норм -----------------------
              ParseLink(HTTP.Get('http://m.vk.com/login'));
              HTTP.HandleRedirects := False;
              HTTP.AllowCookies := False;
              try
    				    HTTP.Post(Link, Data);
              except
                try
                  HTTP.Get(HTTP.Response.Location);
                except
                  tmp := HTTP.Response.RawHeaders.Text;
                  Delete(tmp, 1, Pos('remixsid=',tmp) + 8);
                  SID :=Copy( tmp,1 ,Pos('expires=',tmp)-3);
                end;
    
              end;
    //--------------------------------------------------------------------------------------
              HTTP.HandleRedirects := True;
              HTTP.AllowCookies := True;
              tmp := HTTP.Get(HTTP.Response.Location);
    
              
    				    if Pos('logout',tmp) <> 0
    					    then Result := True;
    			except
    				Result := False;
    			end;
    			finally
    				Data.Free;
    			end;
    
    		end;
    
    
    
    //-------------parsing function friends ID's -----------------------
    // парсит вроде первых 20 чтобы парсило всех нужно допиливать
    
    function TVKDump.ParseFriends () : Integer;
    var tmp: String;
        i:integer;
      begin
        HTTP.AllowCookies := False;
        HTTP.Request.CustomHeaders.Add('Cookie:remixsid=' + SID);
        tmp := HTTP.Get('http://m.vk.com/friends');
        HTTP.AllowCookies := True;
        Delete(tmp,1,Pos(' <em>',tmp)+4);
        FriendCount :=StrToInt(Copy(tmp,1,Pos('</em>',tmp)-1));
          for i:=1 to 18  do
    
            begin
              Delete(tmp,1,Pos('<a href="/write',tmp)+14);
              FriendList.Add(copy(tmp,1,Pos('">',tmp)-1));
            end;
        FriendList.SaveToFile('friends.txt');
      end;
    //-----------------------------------------------------------
    
    
    
    //-------------- Созаём нужные объекты и настраиваем HTTP ----------------
    constructor TVKDump.Create ();
    
      begin
        FriendList := TStringList.Create;
        HTTP := TIdHTTP.Create(nil);
        HTTP.HandleRedirects := True;
          with HTTP.Request do
            begin
              UserAgent := 'Mozilla/5.0 (Windows NT 5.1; rv:16.0) Gecko/20100101 Firefox/16.0';
              ContentType := 'application/x-www-form-urlencoded';
              AcceptLanguage := 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3';
            end;
    
      end;
    
    
    
    
    
    
    
    
     Var
          VK : TVKDump;
          i : Integer;
    begin
      VK := TVKDump.Create ();
    
    
    
    
    //-----------если без запущена параметров, тогда выходим -----------------
       if ((Paramstr(1) = '') and (Paramstr(2) = '')) then
        begin
          Exit;
        end;
    
    //----------возможность указать SID---------------------------------------
     if ((Paramstr(1) = '0')  and (Paramstr(2)  = '0'))
      then
        begin
          VK.SID := Paramstr(3);
            VK.ParseFriends();
              for i := 1 to 3 do  // можно ставить VK.FriendsCount но нужно допилить функцию парсинга друзей
                VK.ParseMessage(VK.FriendList[i]);
        end
    
       else  //-----------иначе указываем логин и пароль------------------------
    
        begin
          VK.Login := Paramstr(1);
          VK.Password := Paramstr(2);
          VK.Authorize;
          VK.ParseFriends();
            for i := 1 to 5 do   // можно ставить VK.FriendsCount но нужно допилить функцию парсинга друзей
              VK.ParseMessage(VK.FriendList[i]);
        end;
    
    
    end.
    
     
    #1 BlackIce, 9 Feb 2013
    Last edited: 9 Feb 2013
    3 people like this.
  2. Kaimi

    Kaimi Well-Known Member

    Joined:
    23 Aug 2007
    Messages:
    1,732
    Likes Received:
    811
    Reputations:
    231
    Т.е. берем класс, фигачим в нем всё то же разрозненное нечто, что в теории существовало бы в виде отдельных процедур и пары глобальных переменных и называем это ООП подходом, который обладает "приимуществами" в виде упрощенной поддержки ?
     
    _________________________
  3. DooD

    DooD Elder - Старейшина

    Joined:
    30 Sep 2010
    Messages:
    1,168
    Likes Received:
    450
    Reputations:
    288
    конечно это лишь просто пример работы с классами,и тут можно было бы спокойно обойтись процедурами(мне и самому ближе процедурно-функциональное программирование,ну не люблю я классы ёкрмн,писать по меньшей мере,юзать готовые-да :) ).Вот банальный пример, когда лучше применить класс:http://mrkto.com/oop_php_class_function/
    а так ТС молодец,и идея прикольна ;)
     
  4. B1t.exe

    B1t.exe Elder - Старейшина

    Joined:
    6 Nov 2006
    Messages:
    1,020
    Likes Received:
    128
    Reputations:
    23
    Кстати, давно искал такой софт )) чтоб дампить переписку из акков, и в офф-лайне спокойно читать/разобрать.

    Можете дать рабочую прогу?
     
  5. Spot

    Spot Elder - Старейшина

    Joined:
    1 Mar 2007
    Messages:
    461
    Likes Received:
    38
    Reputations:
    1
    Поправте меня, но на статью и близко не тянет. Что бы представить модель ООП не хватит написать только один класс-обертку. Суть ООП всё таки это взаимодействие между различными обьектами, а не работа с одним единственным классом-обьектом.

    Как пример обьекта - годится, но не как представление модели ООП.
     
  6. BlackIce

    BlackIce Elder - Старейшина

    Joined:
    10 Jan 2013
    Messages:
    100
    Likes Received:
    31
    Reputations:
    27
    2 Kaimi, DX вы же делфи не знаете? :)
    На статью мало тянет, согласен. Можно было показать примеры наследования и взаимодействия с классами. Но так как сам только практикую, то и писал для себеподобных. Вс-еже, даже такой подход упрощает разработку и разбивает создание на 2 этапа: проэктирование класса, где кодер на абстрактном уровне разрабатывает структуру будущей программы и реализацию. За критику благодарю, буду стараться больше работать в данной сфере и писать более годные статьи.
    Линк на файл : http://www.sendspace.com/file/dykm4i
     
    1 person likes this.
  7. Eich3

    Eich3 Member

    Joined:
    27 Jan 2013
    Messages:
    22
    Likes Received:
    7
    Reputations:
    5
    В статье хорошо продемонстрирован антипаттерн "God object". Слишком много функционала возложено на один объект. Не обязательно знать делфи, чтобы не заметить такой ошибки.

    Главная задача ООП - закрывать для изменения существующий и отлаженный код, и предоставить возможность добавлять к нему новую функциональность. За счет этого обеспечивается повторное использование.

    В данном случае нет смысла в классе TVKDump; с тем же успехом можно было бы сделать все это модулем (delphi unit), а поля - просто переменными этого модуля.

    Получение страницы с веб-сервера vk.com - в принципе задача для отдельного класса. Ведь можно грузить страницы как напрямую, так и через прокси или цепочки прокси. Если все это запихнуть в один класс, никакой выгоды от ООП не будет, т.к. такой код сложнее выдрать для использования где-либо еще. Страница может загружаться не полностью, а частями (для уменьшения трафика) + списки друзей и прочее могут быть реализованы на сайте постранично (следовательно для каждой страницы требуется отдельный запрос).

    Аутентификацию тоже следует вынести в отдельный класс, зависящий от соединения с vk (а не просто от сферического TidHTTP, поскольку это внешняя зависимость). Предположим, что вконтакте спалил наш "многопоточный сканер" и решил показать каптчу, на абсолютно произвольном запросе. Теперь придется в код класса внедрять еще и каптчу. Это значит, что надо интегрировать все это дело с antigate и как альтернативу предоставить возможность вбивать их вручную.

    А если делаем многопоточность, там может возникнуть задача равномерно размыть запросы по всем проксям, с учетом числа запросов и переданного трафика. Тогда потребуется вести учет по трафику и числу запросов каждой прокси - еще один класс, который будет решать, через какой прокси должен быть отправлен запрос в этот раз. Плюс обход всяких сбоев при работе с прокси, необходимость повторения запроса в случае падения коннекта - вообщем, еще много различных проблем можно найти.

    Особого проектирования в этой статье не усматривается. Обычный код, который пишут на скорую руку. Все, что здесь есть ООПшного - только использование дельфийских классов и компонентов путем композиции.

    P.S. Для программы уровня "написал и забыл" данный код хорошо годится, главное, чтобы работало. А поддерживать такое не пожелаю никому :)
     
  8. Chrome~

    Chrome~ Elder - Старейшина

    Joined:
    13 Dec 2008
    Messages:
    936
    Likes Received:
    162
    Reputations:
    27
    То есть ты хочешь сказать, что знаешь Delphi? Или даже ООП?
    ТС, прочитай про атрибуты видимости методов, переменных, про свойства. Так как ты написал пишут только начинающие быдлокодеры. Ко всему этому ты еще создаешь объекты в конструкторе, но нигде их не освобождаешь. Зачем писать статью с такими ошибками? Чтобы сделать свой членский взнос в литературу для быдлокодеров?