Phase.5 自動巡回エージェントで画像をダウンロードする(『スタパライフ』 Ver 1.10)

 Chapter.21 uniBrowserを改造してHTMLを表示する

巡回エージェントで画像を収集するようになったところで、収集された画像を従来の本文と一緒に閲覧できるようにならなければ意味がありません。unibrowserでは、ログデータベースに直接格納された本文しか表示できない(*1)ので、画像をあわせて表示できるカスタムブラウザを作成します。

  1. unibrowserからフォームをカスタマイズする
  2. UIWebBrowserコンポーネントでhtml表示
  3. ログからhtmlページへの展開(埋め込み)

基本的な方針として、画像と本文の表示は巡回先Webサイトのイメージに近づけるため、html形式にしてブラウザで表示するようにします。


1.unibrowserからフォームをカスタマイズする

カスタムブラウザといっても、一から作り始めるのでは大変です。ここでは、これまで使ってきたairWeb標準搭載のunibrowserを元にカスタマイズします。unirowserはPlugIn\browserフォルダに置かれていますので、これを作業ディレクトリ(PlugIn\stpl)へコピーします。コピーするファイルは、unibrowser.pasと unibrowser.dfmの2つです。

コピーすると、カスタムブラウザ用にリネーム(unibrowser → stplBrowser)して、Air Designerから開きます。

以下に、カスタムブラウザのデザイン構成を示します。


2.TUIWebBrowserコンポーネントでhtml表示

html表示をするからくりは、大雑把に言って以下のような構成になります。

 

  1. エージェント起動時に、雛型になるhtmlファイルを読み込みます。
  2. TitleViewはログデータベースを指定することで、ログへのアクセスが出来るようになります。
  3. タイトルツリーからアイテムが選ばれると、SelectItemイベントが発生します。
  4. エージェントのスクリプトからは、雛型になるhtml中で指定したID属性等の名前で表示されているノードにアクセスすることが出来ます。

 

ここで、まず雛型になるhtmlファイルを作成します。必要な情報だけに整理するなら自分の使いやすいレイアウトに構成を変えてしまうことすら可能なのですが、「スタパライフ」エージェントの場合はやはりサイトを訪れブラウザで表示したときのイメージに近い方がよいです。

そこで、大胆にも本家のページをブラウザで表示し、ソースを表示して取り込みます。(*2)


   1: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
   2: <html>
   3: <head>
   4: <meta http-equiv="Content-Type" content="text/html; charset=x-euc-jp">
   5: <title>ク?トセ、キ</title>
   6: </head>
   7: <body bgcolor=white style="LINE-HEIGHT: 140%; MARGIN: 40px 50px">
   8: <img src="stapalife.gif">
   9: <p><a href="ml-5-110.html">シ。、リ</a> | <a href="ml-5-108.html">チー、リ</a> | <a href="toc5-0.html">フワシ。</a> | <a href="/">・ネ・テ・ラ・レ。シ・ク</a></p>
  10: 
  11: <table cellpadding=0 cellspacing=0 bgcolor="#a0a0a0"><tr><td>
  12:  <table cellpadding=5 style="LINE-HEIGHT: 130%">
  13:   <tr bgcolor="azure">
  14:     <td nowrap width="33%">2000/11/12 02:53</td>
  15:     <td>ク?トセ、キ</td>
  16:   </tr>
  17:   <tr>
  18:     <td colspan=3 bgcolor="white" nowrap>
  19:      <p>
  20: <BR>
  21: <img src="D20001110c.gif" alt="D20001110c.gif"><BR clear=all>
  22: 2000ヌッ11キ・0ニ・。、ス、ホ3<BR>
  23: 。。、荀テ、ムハニ、ネ・ム・ユ・ァ、タ、ア、タ、ネ、ォ、ハ、・ュ・ト、、、ネサラ、・・・」、ハ、シ、ハ、鬢ミ。「・ム・ユ・ァ、ホ<BR>
  24: エナ、オ、ヒ、隍テ、ニハニ、ホフ」、ャエーチエ、ヒシコ、・・「、ト、゙、遙「テア、ヒエナ、、ハニ、ソゥ、テ、ニ、・ネ、、、ヲセ・BR>
  25: カキ、ヒ、ハ、・ォ、鬢タ。」<BR>
  26: 。。、ス、ウ、ヌ、ェク?トセ、キ、ヒ・ウ・・!。。SPAM!!。。SPAM SPAM SPAM SPAM!!。。ハニ、ネ・ム・ユ・ァ、ネ<BR>
  27: SPAM!!。。、ウ、・タ!!。。、ウ、・キ、ォ、ハ、、!!。。、ウ、・コョ、シ、ニソゥ、ヲ、ウ、ネ、チロチ・キ、ニ、筅ハ、ェ<BR>
  28: ソゥペ、ャ、・ア、ミ。「ヒワハェ、ホテヒ!!<BR>
  29: 。。、ニ、、、ヲ、ォ、ウ、ホトエサメ、ヌ、オ、鬢ヒツウ、ア、ソ、熙ケ、・ネ・ケ・ム・猗・ュ、ヒ、ハ、テ、ニ、キ、゙、、、゙、ケ、ォ<BR>
  30: 。ゥ。。、「、・・ホキ、ャ、鬢サ、ヌ、ケ、ォ。ゥ<BR>
  31: 
  32:      </p>
  33:     </td>
  34:   </tr>
  35:  </table>
  36:  </td></tr>
  37: </table>
  38: <p><a href="ml-5-110.html">シ。、リ</a> | <a href="ml-5-108.html">チー、リ</a> | <a href="toc5-0.html">フワシ。</a> | <a href="/">・ネ・テ・ラ・レ。シ・ク</a></p>
  39: 
  40: </body>
  41: </html>
http://www.alt-r.com/di/ml-5-109.html

む! バケバケ。EUCで登録されているものをブラウザのソース表示で確認したものだから仕方がないです。「名前を付けて保存」のhtmlのみ・SJISを指定して保存する手もありますが、このくらいであれば化けていても構成はわかります。

大事そうなところを追っかけて見ましょう。

7行目 <body>タグで、背景や全体のテキストのスタイルを指定しています。そのまま残します。
8行目 「スタパライフ」のロゴが入っています。エージェントの外枠にでも表示するとカッコイイ感じがしますが、今回は見送り。
9行目 同じくナビゲーションバーがかかれています。エージェントのナビゲーションもココから行うとCoolなのですが、次のステップとしましょう。
11-13行 エッセイの見出し枠にあたるtableタグ、trタグがかかれています、そのまま残します。
14行目 日付の欄です。ココを動的に書き換えるため、tdタグの属性に、IDを追加します。
15行目 タイトルの欄です。同上。
18行目 本文の欄ですね。これも同じく。
19-32 グチャグチャですが、要はココに本文が入っているわけですね。この部分を挟むような形で<A NAME="xxx"></A>というタグにしておきます。
38行目 9行目と同じです。今回はゴメンナサイ。

と、そんなわけで、サクサク削除して雛型だけにし、後から動的に内容を書き換えるところには印をつけたものにします。印は、ID属性やNAME属性でつけておくとOKです。つけても書き換えられないタグがあるかもしれませんが、そこはCut&Try。DHTMLを極めた方がそばにいらっしゃる方は、活用しましょう♪

以下のようになりました。


   1:<html>
   2:<body bgcolor=white style="LINE-HEIGHT: 140%; MARGIN: 40px 50px">
   3:<table cellpadding=0 cellspacing=0 bgcolor="#a0a0a0"><tr><td>
   4: <table cellpadding=5 style="LINE-HEIGHT: 130%">
   5:  <tr bgcolor="azure">
   6:    <td nowrap width="33%" ID="POSTDATE"></td>
   7:    <td ID="SUBJ"></td>
   8:  </tr>
   9:  <tr>
  10:    <td colspan=3 bgcolor="white" nowrap><A ID="MSGBODY">Message Body</A></td>
  11:  </tr>
  12:  <tr style="display:none">
  13:    <td colspan=3 bgcolor="white" nowrap ID="SENDER">あ</td>
  14:  </tr>
  15: </table>
  16: </td></tr>
  17:</table>
  18:</body>
  19:</html>
sptl.htm

12-14行は、stplBrowserの元になった試作HTMLブラウザ「qBrowser」の名残です。エージェントから発言者を書き出していますが、「スタパライフ」には発言者の表示がありません。エージェントを直せばいいのですが、後から雛型を本家チックにした(Ver 1.22)ため非表示で残してあります。

次は、この雛型ファイルを読み込む方法です。読み込み自体は、フォームが表示されるときに実行します。全てのソースはこちらをご覧下さい。


 148: // for HTML View   by quwaji
 149:   bNavigateComplete := '';
 150:   bWaitNavigate := '';
 151: //  htmfile := DataDir + '\' + ChangeFileExt(FFileName, '.htm');
 152:   htmfile := ExeDir + '\' + 'Plugin\stpl\stpl.htm';
 153:   if FileExists(htmfile) then
 154:   begin
 155:        UIWebBrowser1.Navigate(htmfile, 0, nil, nil, nil);
 156:   end
 157:   else
 158:   begin
 159:        UIWebBrowser1.Navigate('about:blank', 0, nil, nil, nil);
 160:   end;
 161: // end HTML View   
ssptlBrowser.r - TstplBrowser.FormCreate()

FormCreate()は、フォームが生成されるときのイベントに割り当てた関数です。この中で2つのフラグをクリア(149,150行)し、TUIWebBrowserコンポーネントの Navigateメソッドを呼び出して雛型ファイルを読み込んでいます。「スタパライフ」エージェントでは、固定ファイルを読み込んでいます。(152-156行)

Navigateメソッドによりナビゲーションが発生すると、いくつかのイベントが発生します。HTMLブラウザでは、ナビゲーションが完了した時点でもイベントを発生させます。このイベントを補足するのがNavigateComplete2メソッドです。unibrowserにはなかった処理ですので、Air Designerでイベント処理を追加します。


 434:procedure TstplBrowser.UIWebBrowser1NavigateComplete2(Sender: TObject; const pDisp: IDispatch; var URL: OleVariant);
 435:var
 436:   Item:TTitleItem;
 437:// modify-start shitara ▼▼▼初期表示メッセージのテキスト部編集・イメージ表示を追加▼▼▼
 438:   TmpLines: TStringList;
 439:   Line:String;
 440:   ImageFile:String;
 441:// modify-end   shitara ▲▲▲初期表示メッセージのテキスト部編集・イメージ表示を追加▲▲▲
 442:begin
 443:// modify-start shitara ▼▼▼イメージ表示を追加▼▼▼
 444:   ImageFile:='';
 445:// modify-end   shitara ▲▲▲イメージ表示を追加▲▲▲
 446:     bNavigateComplete := 'おっけ〜';
 447:     if bWaitNavigate <> '' then
 448:     begin
 449:          Item := TitleView1.Selected;
 450:// modify-start shitara ▼▼▼初期表示メッセージのテキスト部編集▼▼▼
 451:          TmpLines:=TStringList.Create;
 452:          TmpLines.Text:=Item.Text;
 453:          // 元のヘッダを削除する(mbox & NIFTY 用)
 454:          while TmpLines.Count>0 do begin
 455:            Line := TmpLines.Strings[0];
 456:            if StrLIComp(Line,'X-ImageFile: ',13)=0 then begin
 457:              ImageFile:=Copy(Line,14,Length(Line)-12);
 458:            end;
 459:            if Length(Line)=0 then begin
 460:              TmpLines.Delete(0);
 461:              Break;
 462:            end;
 463:            TmpLines.Delete(0);
 464:          end;
 465:// modify-end   shitara ▲▲▲初期表示メッセージのテキスト部編集▲▲▲
 466:          if Assigned(Item) then
 467:          begin
 468:             UIWebBrowser1.document.all('SUBJ').innerHTML := Item.Subject;
 469:             UIWebBrowser1.document.all('POSTDATE').innerHTML := FormatDateTime('yyyy/mm/dd HH:MM', Item.Date);
 470:             UIWebBrowser1.document.all('SENDER').innerHTML := Item.SenderName;
 471:// modify-start shitara ▼▼▼テキスト表示をプレーンテキストに変更・イメージ表示を追加▼▼▼
 472://             UIWebBrowser1.document.all('MSGBODY').innerHTML := TmpLines.Text;
 473:             if Length(ImageFile)>0 then begin
 474:               UIWebBrowser1.document.all('MSGBODY').innerHTML := '<img src="' + DataDir + '\stpl\' + ImageFile + '"><br><pre>'+TmpLines.Text+'</pre>';
 475:             end else begin
 476:               UIWebBrowser1.document.all('MSGBODY').innerHTML := '<pre>'+TmpLines.Text+'</pre>';
 477:             end;
 478:// modify-end   shitara ▲▲▲テキスト表示をプレーンテキストに変更・イメージ表示を追加▲▲▲
 479:          end;
 480:          bWaitNavigate := '';
 481:     end;
 482:
 483:end;
   
sptlBrowser.r - TstplBrowser.UIWebBrowser1NavigateComplete2()

「スタパライフ」エージェントの場合、期待するNavigateComplete2()メソッドの呼び出しは、エージェントが起動したときの1回だけです。(*3)

446行目 ナビゲーションが完了したことを示す変数に値をセットしています。この値が設定されるまで、ブラウザにロードする雛型中のアイテムを書き換えることは出来ません。
447行目 もしナビゲーション完了待ちで情報を表示しなければならない状況であれば、Itemから選択されている情報を読み出し、ブラウザ上に埋め込んでいきます。埋め込みの処理自体は、次に解説するものと同じです。

エージェントの起動時は、実は先にAWTitleViewコンポーネントのSelectItemイベントが発生するようで、待ち合わせる必要があります。


3.ログからhtmlページへの展開(埋め込み)

Chapter (X-1). で手を加えた巡回エージェントによって、以下のような形式のログが取得されるようになります。


   0: From foo@bar Sun Nov 12 02:53:00 2000
   1: From: スタパ齋藤
   2: X-Number: 109
   3: Date: Sun Nov 12 02:53:00 2000
   4: Subject: 口直し
   5: X-ImageFile: D20001110c.gif
   6: 
   7: 
   8: 2000年11月10日 その3
   9:  やっぱ米とパフェだけだとかなりキツいと思われる。なぜならば、パフェの
  10: 甘さによって米の味が完全に失われ、つまり、単に甘い米を食ってるという状
  11: 況になるからだ。
  12:  そこでお口直しにコレ!! SPAM!! SPAM SPAM SPAM SPAM!! 米とパフェと
  13: SPAM!! これだ!! これしかない!! これを混ぜて食うことを想像してもなお
  14: 食欲がわけば、本物の男!!
  15:  ていうかこの調子でさらに続けたりするとスパム日記になってしまいますか
  16: ? ある種の嫌がらせですか?
stpl.mbxより抜粋

5行目に確認できる「X-ImageFile: 」ヘッダが今回追加されたもので、取得された画像ファイルの所在を示しています。

エージェントが本文を表示する契機は、タイトルツリー(TitleView1)でアイテムが選択されたときになります。エージェントが起動したときも同じイベントが発生します。このイベントで選択されたアイテムの情報を取得し、エージェントのUIWebBrowserコンポーネントに読み込んだsptl.htmに埋め込んでいきます。


 372:procedure TstplBrowser.TitleView1SelectItem(Sender: TObject; Item: TListItem; Selected: Boolean);
 373:var
 374:   Item:TTitleItem;
 375:   TmpLines: TStringList;
 376:   Line:string;
 377:// modify-start shitara ▼▼▼イメージ表示を追加▼▼▼
 378:   ImageFile:String;
 379:// modify-end   shitara ▲▲▲イメージ表示を追加▲▲▲
 380:begin
 381:// modify-start shitara ▼▼▼イメージ表示を追加▼▼▼
 382:   ImageFile:='';
 383:// modify-end   shitara ▲▲▲イメージ表示を追加▲▲▲
 384:     Item := TitleView1.Selected;
 385:     if Assigned(Item) then
 386:     begin
 387:// for HTML View   by quwaji
 388:        if bNavigateComplete <> '' then
 389:        begin
 390:            TmpLines:=TStringList.Create;
 391:            TmpLines.Text:=Item.Text;
 392:            // 元のヘッダを削除する(mbox & NIFTY 用)
 393:            while TmpLines.Count>0 do begin
 394:              Line := TmpLines.Strings[0];
 395:// modify-start shitara ▼▼▼イメージ表示を追加▼▼▼
 396:              if StrLIComp(Line,'X-ImageFile: ',13)=0 then begin
 397:                ImageFile:=Copy(Line,14,Length(Line)-12);
 398:              end;
 399:// modify-end   shitara ▲▲▲イメージ表示を追加▲▲▲
 400:              if Length(Line)=0 then begin
 401:                TmpLines.Delete(0);
 402:                Break;
 403:              end;
 404:              TmpLines.Delete(0);
 405:            end;
 406:             UIWebBrowser1.document.all('SUBJ').innerHTML := Item.Subject;
 407:             UIWebBrowser1.document.all('POSTDATE').innerHTML := FormatDateTime('yyyy/mm/dd HH:MM', Item.Date);
 408:             UIWebBrowser1.document.all('SENDER').innerHTML := Item.SenderName;
 409:// modify-start shitara ▼▼▼テキスト表示をプレーンテキストに変更・イメージ表示を追加▼▼▼
 410://             UIWebBrowser1.document.all('MSGBODY').innerHTML := TmpLines.Text;
 411:             if Length(ImageFile)>0 then begin
 412:               UIWebBrowser1.document.all('MSGBODY').innerHTML := '<img src="' + DataDir + '\stpl\' + ImageFile + '"><br><pre>'+TmpLines.Text+'</pre>';
 413:             end else begin
 414:               UIWebBrowser1.document.all('MSGBODY').innerHTML := '<pre>'+TmpLines.Text+'</pre>';
 415:             end;
 416:// modify-end   shitara ▲▲▲テキスト表示をプレーンテキストに変更・イメージ表示を追加▲▲▲
 417:             bWaitNavigate := '';
 418:            TmpLines.Free;
 419:        end
 420:        else
 421:        begin
 422:             bWaitNavigate := 'たのむよ';
 423:        end;
 424:// end HTML View
 425:     end;
 426:     Unread1.Text := Format('%d/%d', [TitleView1.UnreadCount, TitleView1.Items.Count]);
 427:
 428:end;
stplBrowser.pas

ポイントを見ていきましょう。

378,382,395-398 ImageFileという変数を追加して、選択されたItemにX-ImageFileヘッダが含まれている場合、ファイル名を取得するようにしています。
388,420-423 bNavigateCompleteとbWaitNavigateを組み合わせて、htmlドキュメントのロード、更新が完了するのを待ち合わせています。ブラウザコンポーネントがレンダリング中にdocumentプロパティ(オブジェクト)にアクセスすると不幸が起きるためです。
406-415 雛型のロードが完了している/前回の表示更新が完了している確認が取れれば、ブラウザコンポーネントのdocumentプロパティから辿って、雛型で埋めた属性を頼りに情報をセットします。ImageFileにはX-ImageFileヘッダから取得したファイル名が入っていますので、その長さが0かどうかでヘッダが有ったか無かったかを判断しています。イメージファイルが有った場合には、テキスト本文を表示する前にimgタグを追加し、srcでImageFileを参照するように指定します。これで、htmlが表示されるときには画像が表示されるようになるはずです。

以下が最終的に巡回データを表示したカスタムブラウザエージェントです。

 

さて、ここでまた新たなアイデアが浮かんでいれば、嬉しい限りです。是非挑戦してみてください。


*1 AWHyperViewという、ログ本文の形式(あるいは、Content-Type:ヘッダ?)によって、ブラウザコンポーネントでhtml表示をしたりメーラ風の添付ファイルを別枠に表示するハイパーなViewerコンポーネントの提供が予定されています。

*2 サイトのソースを加工して配布するのは、本来あまりよくありません。『スパタライフ』エージェントは、サイトの許可をいただいております。また、ここで示したような加工の手順をエージェントで行うようにすれば、より汎用性が高まり、再配布の問題もなくなります。オルトアール・エッセイ汎用エージェントへと進化する際には、雛形ファイルをサイトのページから生成するようになるでしょう。あるいは、サイト運営者がエージェントや雛型ファイルを作成・提供するようになるかもしれません。AirWebの可能性の一つです。

*3 実は、v1.22では「最新の状態に更新」を実行したり、他のブラウザからリンクをドラッグアンドドロップすると発生してしまいます。特に後者は、その後TitleViewでItemを選びなおすと、ブラウザコンポーネントが保持しているhtmlデータが想定外のものになっているためエラーになってしまいます。要修正!