Offline-редактор NoNaMe. Часть 2.

Offline-редактор NoNaMe. Часть 2.

Наконец-то появилось немного времени, чтобы выложить вторую часть статьи... Прошу не ругать, но вторая часть — это скорее теория, чем практика. Сначала хотел сделать полноценную простую программу для заливки новостей на сайт, но когда понял, что это получится вторая версия NoNaMe Post Editor-а, да еще и кастрированная, решил, что делать этого не стоит.

Поэтому предлагаю Вам самим сделать для себя такой редактор, какой Вам удобен и необходим. А я попробую Вам помочь. Итак, продолжаем...

----------------------<cut>----------------------

Ну что же, продолжим... Так как API для NoNaMe так и нет, придется отправлять все данные на сайт через web-страницу. А для этого необходимо знать куда, что и как отправлять. В этой статье я как раз и попробую объяснить, как производить разбор страниц, как отправлять и т.д. Начинаем…

А начнем мы вот с чего. Открываем в браузере страницу добавления новости и смотрим исходный код. Нам необходимо найти форму добавления новости. Вот она:

<form method="post" action="/addnew" name="addnew">
    <div class="np-block" id="np-edit">
      <div id="np-panel">
        <input type="text" id="np-name" name="np-name" value="Название новости" maxlength="70" />
        <ul class="bbtag">
          <li class="start"></li>
          <li><a href="javascript:void(0);" onclick="javascript:insert('cut',2, this);"><img src="http://www.nnm.ru/img/default/bb/cut.gif" width="21" height="20" /></a></li>
          <li><a href="javascript:void(0);" onclick="javascript:insert('doc',1, this);"><img src="http://www.nnm.ru/img/default/bb/doc.gif" width="21" height="20" /></a></li>
          <li class="end"></li>
        </ul>
        <ul class="bbtag">
          <li class="start"></li>
          <li><a href="javascript:void(0);" onclick="javascript:insert('url',3, this);"><img title="Вставить ссылку" src="http://www.nnm.ru/img/default/bb/link.gif" width="21" height="20" /></a></li>
          <li><a href="javascript:void(0);" onclick="javascript:insert('mail',3, this);"><img title="Вставить ссылку на Email" src="http://www.nnm.ru/img/default/bb/mail.gif" width="21" height="20" /></a></li>
          <li><a href="javascript:void(0);" onclick="window.open('http://www.sharing.ru/upload/partner.php?id=1', 'uploadfile', 'width=500, height=275, top=200, left=200');"><img title="Вставить файл" src="http://www.nnm.ru/img/default/bb/file.gif" width="21" height="20" /></a></li>
          <li><a href="javascript:void(0);" onclick="javascript:insert('image',3, this);"><img title="Вставить картинку" src="http://www.nnm.ru/img/default/bb/image.gif" width="21" height="20" /></a></li>
          <li><a href="javascript:void(0);" onclick="javascript:insert('video',3, this);"><img title="Вставить видео" src="http://www.nnm.ru/img/default/bb/video.gif" width="21" height="20" /></a></li>
          <li class="end"></li>
        </ul>
        <ul class="bbtag">
          <li class="start"></li>
          <li><a href="javascript:void(0);" onclick="javascript:insert('b',1, this);"><img title="Выделить текст жирным" src="http://www.nnm.ru/img/default/bb/bold.gif" width="21" height="20" /></a></li>
          <li><a href="javascript:void(0);" onclick="javascript:insert('i',1, this);"><img title="Выделить текст курсивом" src="http://www.nnm.ru/img/default/bb/italic.gif" width="21" height="20" /></a></li>
          <li><a href="javascript:void(0);" onclick="javascript:insert('u',1, this);"><img title="Подчеркнуть текст" src="http://www.nnm.ru/img/default/bb/underline.gif" width="21" height="20" /></a></li>
          <li><a href="javascript:void(0);" onclick="javascript:insert('s',1, this);"><img title="Зачеркнуть текст" src="http://www.nnm.ru/img/default/bb/strike.gif" width="21" height="20" /></a></li>
          <li class="end"></li>
        </ul>
      </div>
      <textarea name="np-text" id="np-text">Текст новости</textarea>
      <br /><br />Теги [через запятую, пример: <font color="blue">зима, фото, лыжи, я</font>]<input class="np-ist" type="text" name="tags" value="" id="np-tags" style="margin-bottom:2px;" />
      <input class="np-ist" type="text" name="np-ist" value="http://Источник" id="np-ist" />
      Отправить в раздел <input type="checkbox" name="n_dalee" value="1" onclick="if(!document.forms['addnew']['n_dalee'].checked){document.forms['addnew']['nra'].setAttribute('disabled', 'disabled');}else{document.forms['addnew']['nra'].removeAttribute('disabled');}" /> <select name="nra" disabled="disabled"><option value="0">[ выберите раздел ]</option><option value="1">newz</option><option value="2">filez</option><option value="3">mobilz</option><option value="9">muzic</option><option value="10">palmz</option><option value="12">gamez</option><option value="13">ironz</option><option value="14">webz</option><option value="15">creative</option><option value="16">video</option><option value="17">humor</option><option value="18">other</option></select><br /><br />
      <b>Добавить голосовалку</b> <input type="checkbox" name="golos_check" onclick="showgolos();">
      <table cellspacing="0" cellpadding="1" id="golos_inv"style="display:none;">
      	<tr><td width="100" align="right"><small><b>Вопрос</b>: </td><td align="right"><input name="vopr" type="text" value=""></td></tr>
      	<tr><td align="right"><small>Ответ #1: </td><td align="right"><input type="text" name="otv1" value=""></td></tr>
      	<tr><td align="right"><small>Ответ #2: </td><td align="right"><input type="text" name="otv2" value=""></td></tr>
      	<tr><td align="right"><small>Ответ #3: </td><td align="right"><input type="text" name="otv3" value=""></td></tr>
      	<tr><td align="right"><small>Ответ #4: </td><td align="right"><input type="text" name="otv4" value=""></td></tr>
      	<tr><td align="right"><small>Ответ #5: </td><td align="right"><input type="text" name="otv5" value=""></td></tr>
      	<tr><td align="right"><small>Ответ #6: </td><td align="right"><input type="text" name="otv6" value=""></td></tr>
      	<tr><td align="right"><small>Ответ #7: </td><td align="right"><input type="text" name="otv7" value=""></td></tr>
      	<tr><td align="right"><small>Ответ #8: </td><td align="right"><input type="text" name="otv8" value=""></td></tr>
      	<tr><td align="right"><small>Ответ #9: </td><td align="right"><input type="text" name="otv9" value=""></td></tr>
      </table>
      <br /><br />
      <center><input type="submit" name="pre" value="Предпросмотр новости" /> </center>
    </div>
    <input type="hidden" name="doc" value="18641" />
    </form>


Что нам это дает? А это дает нам все, что необходимо на данный момент. Построчно объяснять не буду =)… Слишком много получится. Покажу строки которые необходимы в данный момент (скажем так – это необходимый минимум…):

<form method="post" action="/addnew" name="addnew">
<input type="text" id="np-name" name="np-name" value="Название новости" maxlength="70" />
<textarea name="np-text" id="np-text">Текст новости</textarea>

Пока хватит =))). Вот это уже можно разобрать построчно. Давайте попробую:

<form method="post" action="/addnew" name="addnew"> — из этой строки нам необходимо получить два параметра method, который отвечает за способ отправки новости на сайт, и action, который показывает куда отправляется новость. Больше здесь ничего интересного для нас нету…
<input type="text" id="np-name" name="np-name" value="Название новости" maxlength="70" /> — из этой строки нам необходимо название поля (name="np-name"), ну и как бонус – получаем максимальную длину заголовка (70 символов ) =).
<textarea name="np-text" id="np-text">Текст новости</textarea> — здесь нам также необходимо получить название поля (name="np-text")…

Так-с… С формой добавления новости разобрались. Теперь займемся авторизацией, т.к. без этого добавить новость не получится. Открываем страницу http://nnm.ruhttp://nnm.ru (если вы авторизованы – нажмите Выкл). Теперь снова смотрим исходный код страницы и выдираем необходимую форму:

<form method="post" name="auth" action="/">
      <ul class="ulMenu" id="m-auth">
        <li class="title">account</li>
        <li><input type="text" name="login" value="Введите логин" onblur="if(this.value==''){this.value='Введите логин';}" onfocus="if(this.value=='Введите логин'){this.value='';}" /></li>
        <li><input type="password" name="passwd" value="" /></li>
        <li><a href="#" onclick="document.forms['auth'].submit();">вкл.</a>/выкл.</li>
        <li class="right"><a href="http://www.nnm.ru/register">регистрация</a></li>
        <li class="right"><a href="http://www.nnm.ru/retrive">забыл пароль?</a></li>
      </ul>
      </form>

Думаю здесь Вы уже разберетесь сами =). Если нет – спрашиваем в комментах, объясню.

По секрету скажу, что нам необходимо будет еще кое-что, но не сейчас. Поэтому я пока не буду грузить Вас лишним…

Ну что же, все необходимые данные для отправки новости у нас есть.

Вот тут необходимо остановиться и подумать, что будем использовать для работы с инетом. Тут есть множество вариантов: WinAPI, Indy, ICS, SwinHTTP и т.д. Когда-то, я перепробовал большинство из этих вариантов и остановился на 3-х.

1. Indy – использую когда необходимо быстро сделать какое-либо приложение, не задумываясь о куках, заголовках, формированиях запросов. Хорошая библиотека, но не лишена своих недостатков. Среди них – небольшие ошибки и, относительно, большой размер. Если с первым бороться еще можно (ошибок мало и они довольно специфичные, т.ч. мало кто их заметит), то со вторым трудновато. Размер библиотеки – это, в данном случае, показатель возможностей этой библиотеки. А возможности у нее большие…
2. ICS – практически тоже самое, что и Indy. Чуть меньше возможностей, меньший размер и другой подход. Если Indy использует синхронный подход, то ICS – асинхронный. Что это значит. Синхронный – это когда Вы даете запрос и получаете ответ на него. При этом приложение приостанавливается, пока не выполниться запрос (если не использовать AntiFreeze), а асинхронный – это когда вы посылаете запрос, а ответ получаете в определенном событии. И тот и другой подход имеют свои плюсы и минусы…
3. SwinHTTP – это также асинхронный компонент. В этом компоненте реализованы только минимальные функции. Именно его я и использую в последнее время. Из минусов – в нем кроме транспортных функций нет ничего. Ни работы с куками, никаких дополнительных функций. Из плюсов – размер и простота работы.

Долго думал, на каком компоненте показывать, но решил остановиться на Indy, хотя SwinHTTP мне более приятен. Что-ж, поехали (далее будут приведены фрагменты из NoNaMe Post Creator, поэтому не стоит просто копировать их =))):

function TInetThread.DoLogin(const ALogin, APassword: string; AIsProxy: Boolean;
	const AProxyAdress: string; AProxyPort: Integer; const AProxyLogin, AProxyPassword: string): Integer;
var
  Data: TStringList;
  ResStr: string;
begin
  Result := -1;
  with FHTTP do
    if AIsProxy then
      begin
        ProxyParams.ProxyServer := AProxyAdress;
        ProxyParams.ProxyPort := AProxyPort;
        ProxyParams.ProxyUsername := AProxyLogin;
        ProxyParams.ProxyPassword := AProxyPassword;
      end else
        begin
          ProxyParams.ProxyServer := '';
          ProxyParams.ProxyPort := 80;
          ProxyParams.ProxyUsername := '';
          ProxyParams.ProxyPassword := '';
        end;
  if ATimeOut <= 0 then ATimeOut := 30;
  Data := TStringList.Create;
  try
    Data.Text := C_Login + '=' + ALogin + '&' + C_Password + '=' + APassword;
   try
      ResStr := FHTTP.Post(pg_NNM, Data);
      if (ResStr = '') or (Pos(C_UnLogin, ResStr) > 0) then Result := 1;
    except
      Result := 6;
    end;
  finally
    Data.Free;
  end;
end;

Это функция авторизации на nnm.ru. Надеюсь тут ничего объяснять не стоит. Идем дальше…

function TInetThread.GetDoc(const AUrl: string; out ADoc: Integer): Integer;
var
   sVal, ResStr: string;
  Idx: Integer;
  lUrl: string;
  Data: TStringList;
begin
  Result := -1;
  Data := TStringList.Create;
  try
    ADoc := 0;
    lUrl := AUrl;
    if lUrl[Length(lUrl)] <> '/' then lUrl := lUrl + '/';
    lUrl := lUrl + C_AddNew;
    ResStr := FHTTP.Get(lUrl);
    if (ResStr = '') or (Pos(C_Doc, ResStr) <= 0) or (Pos(C_AddForm, ResStr) <= 0) then Result := 2;
    if Result < 0 then
      begin
        Idx := Pos(C_Doc, ResStr);
        if Idx > 0 then
          begin
            Inc(Idx, Length(C_Doc));
            sVal := '';
            while (Idx < Length(ResStr)) and (ResStr[Idx] <>'"') do
              begin
                sVal := sVal + ResStr[Idx];
                Inc(Idx);
              end;
            ADoc := StrToIntDef(sVal, 0);
          end;
      end;
  finally
    Data.Free;
  end;
end;

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

Долго думал, надо ли приводить следующую функцию или нет, но решил, что надо… Хотя на данный момент, мне так кажеться, она лишняя. Итак, следующая функция – отправка изображения и получение урл-а этого изображения на сайте:

function TInetThread.PostHDDImage(const ADoc, APath: string; out AUrl: string): Integer;
var
  lData: TIdMultiPartFormDataStream;
  lResStr, lRes: string;
  lUrl, lPref: string;
  Idx: Integer;
begin
  Result := -1;
  AUrl := '';
  lData := TIdMultiPartFormDataStream.Create;
  try
    lData.AddFile(C_HDDImage, APath, GetMIMETypeFromFile(APath));
    lUrl := ADoc;
    if lUrl[Length(lUrl)] <> '/' then lUrl := lUrl + '/';
    lUrl := lUrl + C_AddImage;
    lResStr := FHTTP.Post(lUrl, lData);
    if (lResStr <> '') and (Pos(C_SParseImg, LowerCase(lResStr)) > 0) then
    	begin
       	lRes := '';
      	Idx := Pos(C_SParseImg, LowerCase(lResStr)) + Length(C_SParseImg) + 1;
        while lResStr[Idx] <> '''' do
        	begin
            lRes := lRes + lResStr[Idx];
            Inc(Idx);
          end;
      	lPref := '';
        Inc(Idx, 4);
        while lResStr[Idx] <> '''' do
        	begin
            lPref := lPref + lResStr[Idx];
            Inc(Idx);
          end;
        if (lRes = '') or (lPref = '')
        	then Result := 4
          else AUrl := lPref + ':' + lRes;
      end else Result := 3;
  finally
    lData.Free;
  end;
end;

Ну и на последок – функция, которая отправляет саму новость на сайт (включая изображения в новости). Использовать ее напрямую у Вас не выйдет, придется немного подумать самим =):

procedure TInetThread.Execute;
var
  lDocID: Integer;
  lTxt: string;
  lData: TStream;
  lUrl: string;
  lCat: Integer;
  lResStr: string;
  lStr: string;
begin
  if (FHandle > 0) and (WM_StartPost > 0) then PostMessage(FHandle, WM_StartPost, 0, 0);
  try
    FHTTP.ReadTimeout := 1000 * ATimeOut;
    FHTTP.Request.Referer := pg_NNM;
    FHTTP.AllowCookies := True;
    FHTTP.CookieManager.CookieCollection.Clear;
    PostMessage(FHandle, WM_StatusText, Integer(PChar(str_Auths)), Length(str_Auths));
    FResult := DoLogin(ALogin, APassword, AIsProxy, AProxyAdress, AProxyPort, AProxyLogin, AProxyPassword);
    if FResult >= 0 then Exit;
    PostMessage(FHandle, WM_StatusText, Integer(PChar(str_GetDocs)), Length(str_GetDocs));
    FResult := GetDoc(ADoc, lDocID);
    if FResult >= 0 then Exit;
    lTxt := AText;
    PostMessage(FHandle, WM_StatusText, Integer(PChar(str_StartImages)), Length(str_StartImages));
    FResult := DoImages(ADoc, lTxt);
    if FResult >= 0 then Exit;
    PostMessage(FHandle, WM_StatusText, Integer(PChar(str_DoText)), Length(str_DoText));
    lTxt := StringReplace(lTxt, delsym, newsym, [rfReplaceAll]);
    ACaption := StringReplace(ACaption, delsym, newsym, [rfReplaceAll]);
    AInLink := StringReplace(AInLink, delsym, newsym, [rfReplaceAll]);
    FHTTP.Host := 'www.nnm.ru';
    lUrl := ADoc;
    if lUrl[Length(lUrl)] <> '/' then lUrl := lUrl + '/';
    lUrl := lUrl + C_AddNew;
    FHTTP.Request.Referer := lUrl;
    FHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
    FHTTP.Request.AcceptCharSet := 'windows-1251,utf-8;q=0.7,*;q=0.7';
    //FHTTP.Request.AcceptEncoding := 'gzip,deflate';
    FHTTP.Request.AcceptLanguage := 'ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3';
    FHTTP.Request.Accept := 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5';
    lStr := C_Caption + '=' + ACaption + '&' + C_Text + '=' + lTxt + '&' + C_Link + '=' + AInLink;
    if ACategory <> '' then
      begin
        lCat := -1;
        if ACategory = 'Newz' then lCat := 1 else
        if ACategory = 'Filez' then lCat := 2 else
        if ACategory = 'Mobilz' then lCat := 3 else
        if ACategory = 'Music' then lCat := 9 else
        if ACategory = 'Palmz' then lCat := 10 else
        if ACategory = 'Gamez' then lCat := 12 else
        if ACategory = 'Ironz' then lCat := 13 else
        if ACategory = 'Webz' then lCat := 14 else
        if ACategory = 'Creative' then lCat := 15 else
        if ACategory = 'Video' then lCat := 16 else
        if ACategory = 'Humor' then lCat := 17 else
        if ACategory = 'Other' then lCat := 18;
        if lCat > 0 then lStr := lStr + '&' + C_Category + '=' + IntToStr(lCat) +
                                 '&' + C_IsCategory + '=1';
      end;
    lStr := lStr + '&vopr=&otv1=&otv2=&otv3=&otv4=&otv5=&otv6=&otv7=&otv8=&otv9=';
    lStr := lStr + '&' + C_InDoc + '=' + IntToStr(lDocID);
    lStr := lStr + '&' + C_Login + '=' + ALogin;
    lStr := lStr + '&' + C_Password + '=' + APassword;
    lStr := lStr + '&' + C_NoSex + '=1';
    lData := TStringStream.Create(lStr);
    try
      FHTTP.HTTPOptions := [hoForceEncodeParams];
      lResStr := FHTTP.Post(C_PostNews, lData);
      FHTTP.HTTPOptions := [];
      if lResStr = '' then FResult := 5;
    finally
      lData.Free;
    end;
  finally
    if (FHandle > 0) and (WM_StartPost > 0) then
      begin
         if FResult < 0 then FResult := 0;
        PostMessage(FHandle, WM_EndPost, FResult, 0);
      end;
  end;
end;

Ну вот пожалуй и все… Итак, практически половину программы привел =). Есть вопросы, просьбы, предложения – в коменты или в личку…

[cut]

P.S.: наверное некоторые будут удивлены, почему не представлен собранный демонстрационный код… На это есть несколько причин. Во-первых, такую программу я уже выкладывал =))) – NoNaMe Post Editor. Во-вторых, мне кажеться, что для того, чтобы научиться – надо делать самому. Не важно, получается или нет сразу. Не важно, насколько красивый и чистый код будет в начале. Важно – делать до тех пор, пока не будет работать.

С уважением, aktuba.

Тэги: codegear, delphi

Комментарии

    Нет комментариев
комментарии
^ Наверх