OSDN Git Service

・EnumThread をプログラム本体に任せるようになった。
[gikonavigoeson/gikonavi.git] / res / ExternalBoardPlugIn / MachiBBSPlugIn.dpr
1 library MachiBBSPlugIn;
2
3 {
4         MachiBBSBoardPlugIn
5         \82Ü\82¿BBS\8f\88\97\9d\83\86\83j\83b\83g
6         \95\8e\9a\83R\81[\83h\82Ì\95Ï\8a·\82É PzConv.pas [ http://plaza11.mbn.or.jp/~pz/ ] \82ð\8eg\97p\82µ\82Ä\82¢\82Ü\82·\81B
7         \83R\83\93\83p\83C\83\8b\91O\82É\81A\93¯\82\83f\83B\83\8c\83N\83g\83\8a\82É\92u\82¢\82Ä\82­\82¾\82³\82¢\81B
8 }
9
10 uses
11         Windows,
12         SysUtils,
13         Classes,
14         Math,
15         IdURI,
16         YofUtils in '..\..\YofUtils.pas',
17         PlugInMain in 'PlugInMain.pas',
18         ThreadItem in 'ThreadItem.pas',
19         BoardItem in 'BoardItem.pas',
20         FilePath in 'FilePath.pas';
21
22 {$R *.res}
23
24 type
25         // =========================================================================
26         // TMachiBBSThreadItem
27         // =========================================================================
28         TMachiBBSThreadItem = class(TThreadItem)
29         private
30                 FIsTemporary    : Boolean;
31                 FDat                                    : TStringList;
32
33         public
34                 constructor     Create( inInstance : DWORD );
35                 destructor      Destroy; override;
36
37         private
38                 function        Download : TDownloadState;
39                 function        GetRes( inNo : Integer ) : string;
40                 function        GetHeader( inOptionalHeader : string ) : string;
41                 function        GetFooter( inOptionalFooter : string ) : string;
42
43                 procedure       To2chDat( ioHTML : TStringList );
44         end;
45
46         // =========================================================================
47         // TMachiBBSBoardItem
48         // =========================================================================
49         TMachiBBSBoardItem = class(TBoardItem)
50         private
51                 FIsTemporary    : Boolean;
52                 FDat                                    : TStringList;
53
54         public
55                 constructor     Create( inInstance : DWORD );
56                 destructor      Destroy; override;
57
58         private
59                 function        Download : TDownloadState;
60                 function        ToThreadURL( inFileName : string ) : string;
61                 procedure       EnumThread( inCallBack : TBoardItemEnumThreadCallBack );
62
63         end;
64
65         // =========================================================================
66         // \83T\83u\83W\83F\83N\83g\83\8c\83R\81[\83h
67         // =========================================================================
68         TSubjectRec = record
69                 FFileName: string;
70                 FTitle: string;
71                 FCount: Integer;
72         end;
73
74 const
75         LOG_DIR                                         = 'MachiBBS\';
76
77         PLUGIN_NAME                             = 'MachiBBSPlugIn';
78         MAJOR_VERSION                   = 1;
79         MINOR_VERSION                   = 0;
80         RELEASE_VERSION         = 'alpha';
81         REVISION_VERSION        = 1;
82
83 // =========================================================================
84 // \8eG\97p\8aÖ\90\94
85 // =========================================================================
86
87 // *************************************************************************
88 // \83e\83\93\83|\83\89\83\8a\82È\83p\83X\82Ì\8eæ\93¾
89 // *************************************************************************
90 function TemporaryFile : string;
91 var
92         tempPath : array [0..MAX_PATH] of       char;
93 begin
94
95         GetTempPath( SizeOf(tempPath), tempPath );
96         repeat
97                 Result := tempPath + IntToStr( Random( $7fffffff ) );
98         until not FileExists( Result );
99
100 end;
101
102 // *************************************************************************
103 // \82Ü\82¿BBS\97p\83\8d\83O\83t\83H\83\8b\83_\8eæ\93¾
104 // *************************************************************************
105 function MyLogFolder : string;
106 var
107         folder : string;
108 begin
109
110         folder := LogFolder;
111         if Length( folder ) = 0 then
112                 Result := ''
113         else
114                 Result := folder + LOG_DIR;
115
116 end;
117
118 (*************************************************************************
119  *\83f\83B\83\8c\83N\83g\83\8a\82ª\91\8dÝ\82·\82é\82©\83`\83F\83b\83N
120  *************************************************************************)
121 function DirectoryExistsEx(const Name: string): Boolean;
122 var
123         Code: Integer;
124 begin
125         Code := GetFileAttributes(PChar(Name));
126         Result := (Code <> -1) and (FILE_ATTRIBUTE_DIRECTORY and Code <> 0);
127 end;
128
129 (*************************************************************************
130  *\83f\83B\83\8c\83N\83g\83\8a\8dì\90¬\81i\95¡\90\94\8aK\91w\91Î\89\9e\81j
131  *************************************************************************)
132 function ForceDirectoriesEx(Dir: string): Boolean;
133 begin
134         Result := True;
135         if Length(Dir) = 0 then
136                 raise Exception.Create('\83t\83H\83\8b\83_\82ª\8dì\90¬\8fo\97\88\82Ü\82¹\82ñ');
137         Dir := ExcludeTrailingPathDelimiter(Dir);
138         if (Length(Dir) < 3) or DirectoryExistsEx(Dir)
139                 or (ExtractFilePath(Dir) = Dir) then Exit; // avoid 'xyz:\' problem.
140         Result := ForceDirectoriesEx(ExtractFilePath(Dir)) and CreateDir(Dir);
141 end;
142
143
144
145 // =========================================================================
146 // PlugIn
147 // =========================================================================
148
149 // *************************************************************************
150 // \83v\83\89\83O\83C\83\93\82Ì\83o\81[\83W\83\87\83\93\82ð\97v\8b\81\82³\82ê\82½
151 // *************************************************************************
152 procedure OnVersionInfo(
153         var outAgent            : PChar;        // \83o\81[\83W\83\87\83\93\82ð\88ê\90Ø\8aÜ\82Ü\82È\82¢\8f\83\90\88\82È\96¼\8fÌ
154         var outMajor            : DWORD;        // \83\81\83W\83\83\81[\83o\81[\83W\83\87\83\93
155         var outMinor            : DWORD;        // \83}\83C\83i\81[\83o\81[\83W\83\87\83\93
156         var outRelease  : PChar;        // \83\8a\83\8a\81[\83X\92i\8aK\96¼
157         var outRevision : DWORD         // \83\8a\83r\83W\83\87\83\93\83i\83\93\83o\81[
158 ); stdcall;
159 begin
160
161         try
162                 outAgent                := CreateResultString( PChar( PLUGIN_NAME ) );
163                 outMajor                := MAJOR_VERSION;
164                 outMinor                := MINOR_VERSION;
165                 outRelease      := CreateResultString( PChar( RELEASE_VERSION ) );
166                 outRevision     := REVISION_VERSION;
167         except
168                 outAgent                := nil;
169                 outMajor                := 0;
170                 outMinor                := 0;
171                 outRelease      := nil;
172                 outRevision     := 0;
173         end;
174
175 end;
176
177 // *************************************************************************
178 // \8ew\92è\82µ\82½ URL \82ð\82±\82Ì\83v\83\89\83O\83C\83\93\82Å\8eó\82¯\95t\82¯\82é\82©\82Ç\82¤\82©
179 // *************************************************************************
180 function OnAcceptURL(
181         inURL : PChar                                           // \94»\92f\82ð\8bÂ\82¢\82Å\82¢\82é URL
182 ): Boolean; stdcall;                    // \8eó\82¯\95t\82¯\82é\82È\82ç True
183 var
184         URI : TIdURI;
185         foundPos : Integer;
186 const
187         BBS_HOST = 'machi.to';
188 begin
189
190         try
191                 // \97á\82Æ\82µ\82Ä\83z\83X\83g\96¼\82ª machi.to \82Å\8fI\82í\82é\8fê\8d\87\82Í\8eó\82¯\95t\82¯\82é\82æ\82¤\82É\82µ\82Ä\82¢\82é
192                 URI := TIdURI.Create( inURL );
193                 try
194                         foundPos := Pos( BBS_HOST, URI.Host );
195                         Result := (foundPos > 0) and (Length( URI.Host ) - foundPos + 1 = Length( BBS_HOST ))
196                 finally
197                         URI.Free;
198                 end;
199         except
200                 Result := False;
201         end;
202
203 end;
204
205
206
207 // =========================================================================
208 // TMachiBBSThreadItem
209 // =========================================================================
210
211 // *************************************************************************
212 // \83R\83\93\83X\83g\83\89\83N\83^
213 // *************************************************************************
214 constructor TMachiBBSThreadItem.Create(
215         inInstance      : DWORD
216 );
217 var
218         uri                                     : TIdURI;
219         uriList                 : TStringList;
220 begin
221
222         inherited;
223
224         OnDownload              := Download;
225         OnGetRes                        := GetRes;
226         OnGetHeader             := GetHeader;
227         OnGetFooter             := GetFooter;
228
229         FilePath                        := '';
230         FIsTemporary    := False;
231         FDat                                    := nil;
232
233         uri                     := TIdURI.Create( URL );
234         uriList := TStringList.Create;
235         try
236                 // http://hokkaido.machi.to/bbs/read.pl?BBS=hokkaidou&KEY=1061764446&LAST=50
237                 YofUtils.ExtractHttpFields(
238                         ['&'], [],
239                         Copy( uri.Params, Pos( '?', uri.Params ) + 1, Length( uri.Params ) ), uriList );
240                 FileName        := uriList.Values[ 'KEY' ] + '.dat';
241                 FilePath        := MyLogFolder + uriList.Values[ 'BBS' ] + '\' + uriList.Values[ 'KEY' ] + '.dat';
242                 IsLogFile       := FileExists( FilePath );
243         finally
244                 uri.Free;
245                 uriList.Free;
246         end;
247
248 end;
249
250 // *************************************************************************
251 // \83f\83X\83g\83\89\83N\83^
252 // *************************************************************************
253 destructor TMachiBBSThreadItem.Destroy;
254 begin
255
256         if FDat <> nil then begin
257                 try
258                         FDat.Free;
259                         FDat := nil;
260                 except
261                 end;
262         end;
263
264         // \88ê\8e\9e\83t\83@\83C\83\8b\82Ì\8fê\8d\87\82Í\8dí\8f\9c\82·\82é
265         if FIsTemporary then
266                 DeleteFile( FilePath );
267
268         inherited;
269
270 end;
271
272 // *************************************************************************
273 // \8ew\92è\82µ\82½ URL \82Ì\83X\83\8c\83b\83h\82Ì\83_\83E\83\93\83\8d\81[\83h\82ð\8ew\8e¦\82³\82ê\82½
274 // *************************************************************************
275 function TMachiBBSThreadItem.Download : TDownloadState;
276 var
277         modified                        : Double;
278         tmp                                             : PChar;
279         downResult              : TStringList;
280         responseCode    : Longint;
281         logStream                       : TFileStream;
282         uri                                             : TIdURI;
283         uriList                         : TStringList;
284         datURL                          : string;
285         foundPos                        : Integer;
286 begin
287
288         Result := dsError;
289
290         uri                     := TIdURI.Create( URL );
291         uriList := TStringList.Create;
292         try
293                 YofUtils.ExtractHttpFields(
294                         ['&'], [],
295                         Copy( uri.Params, Pos( '?', uri.Params ) + 1, Length( uri.Params ) ), uriList );
296                 if MyLogFolder = '' then begin
297                         // \82Ç\82±\82É\95Û\91\82µ\82Ä\82¢\82¢\82Ì\82©\95ª\82©\82ç\82È\82¢\82Ì\82Å\88ê\8e\9e\83t\83@\83C\83\8b\82É\95Û\91
298                         FilePath                        := TemporaryFile;
299                         FIsTemporary    := True;
300                 end else begin
301                         FilePath        := MyLogFolder + uriList.Values[ 'BBS' ] + '\' + uriList.Values[ 'KEY' ] + '.dat';
302                         FIsTemporary    := False;
303                 end;
304
305                 // \95Û\91\97p\82Ì\83f\83B\83\8c\83N\83g\83\8a\82ð\8c@\82é
306                 ForceDirectoriesEx( Copy( FilePath, 1, LastDelimiter( '\', FilePath ) ) );
307
308                 if FileExists( FilePath ) then
309                         logStream := TFileStream.Create( FilePath, fmOpenReadWrite or fmShareDenyWrite )
310                 else
311                         logStream := TFileStream.Create( FilePath, fmCreate or fmShareDenyWrite );
312                 try
313                         // \93Æ\8e©\82É\83_\83E\83\93\83\8d\81[\83h\82â\83t\83B\83\8b\83^\83\8a\83\93\83O\82ð\8ds\82í\82È\82¢\8fê\8d\87\82Í
314                         // InternalDownload \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
315                         modified        := LastModified;
316                         // http://hokkaido.machi.to/bbs/read.pl?BBS=hokkaidou&KEY=1061764446
317                         // 1 + \90V\92\85
318                         datURL          :=
319                                 uri.Protocol + '://' + uri.Host + '/bbs/read.pl?' +
320                                 'BBS=' + uriList.Values[ 'BBS' ] + '&KEY=' + uriList.Values[ 'KEY' ] +
321                                 '&START=' + IntToStr( Count + 1 );
322                         responseCode := InternalDownload( PChar( datURL ), modified, tmp, 0 );
323
324                         try
325                                 if responseCode = 200 then begin
326                                         downResult := TStringList.Create;
327                                         try
328                                                 downResult.Text := string( tmp );
329
330                                                 // \83\8c\83X\82Ì\8aJ\8en\88Ê\92u
331                                                 foundPos                                := Pos( '<dt', downResult.Text );
332                                                 downResult.Text := Copy( downResult.Text, foundPos, Length( downResult.Text ) );
333                                                 if foundPos > 0 then begin
334                                                         // \83\8c\83X\82Ì\8fI\97¹\88Ê\92u
335                                                         foundPos := Pos( '<table', downResult.Text ) - 1;
336                                                         if foundPos > 0 then
337                                                                 downResult.Text := Copy( downResult.Text, 1, foundPos );
338
339                                                         // \82Ü\82¿BBS\82Í dat \92¼\93Ç\82Ý\82ª\8fo\97\88\82È\82¢\82µ\81Acgi \88È\8aO\82É\8d·\95ª\93Ç\82Ý\8d\9e\82Ý\82Ì\95û\96@\82ª\82 \82é\82í\82¯\82Å\82à\96³\82¢\82Ì\82Å
340                                                         // \91f\82Ì\82Ü\82Ü\82ð\96³\97\9d\82É\95Û\82Æ\82¤\82Æ\82Í\82¹\82¸\82É 2ch \82Ì dat \8c`\8e®\82É\95Ï\8a·\82µ\82½\82à\82Ì\82ð\95Û\91\82µ\82Ä\82µ\82Ü\82¤
341                                                         To2chDat( downResult );
342
343                                                         if Count = 0 then begin
344                                                                 Result := dsComplete
345                                                         end else begin
346                                                                 if downResult.Count > 0 then
347                                                                         // \8dÅ\8f\89\82Í\95K\82¸ 1 \82Ì\83\8c\83X
348                                                                         downResult.Delete( 0 );
349                                                                 Result := dsDiffComplete;
350                                                         end;
351                                                         if downResult.Count > 0 then begin
352                                                                 logStream.Position      := logStream.Size;
353                                                                 logStream.Write( PChar( downResult.Text )^, Length( downResult.Text ) );
354
355                                                                 LastModified                            := modified;
356                                                                 IsLogFile                                               := True;
357                                                                 NewReceive                                      := Count + 1;
358                                                                 Count                                                           := Count + downResult.Count;
359                                                                 NewResCount                                     := downResult.Count;
360                                                         end else begin
361                                                                 Result                                                  := dsNotModify;
362                                                         end;
363                                                 end;
364                                         finally
365                                                 downResult.Free;
366                                         end;
367                                 end;
368                         finally
369                                 DisposeResultString( tmp );
370                         end;
371                 finally
372                         logStream.Free;
373                 end;
374         finally
375                 uri.Free;
376                 uriList.Free;
377         end;
378
379 end;
380
381 // *************************************************************************
382 // \83\8c\83X\94Ô\8d\86 inNo \82É\91Î\82·\82é html \82ð\97v\8b\81\82³\82ê\82½
383 // *************************************************************************
384 function TMachiBBSThreadItem.GetRes(
385         inNo            : Integer               // \97v\8b\81\82³\82ê\82½\83\8c\83X\94Ô\8d\86
386 ) : string;                                             // \91Î\89\9e\82·\82é HTML
387 var
388         res                     : string;
389         tmp                     : PChar;
390 begin
391
392         // \93Æ\8e©\82É\83t\83B\83\8b\83^\83\8a\83\93\83O\82ð\8ds\82í\82È\82¢\8fê\8d\87\82Í
393         // InternalAbon \82¨\82æ\82Ñ Dat2HTML \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
394         if FDat = nil then begin
395                 if IsLogFile then begin
396                         // dat \82Ì\93Ç\82Ý\8d\9e\82Ý
397                         FDat                    := TStringList.Create;
398                         FDat.LoadFromFile( FilePath );
399                 end else begin
400                         // \83\8d\83O\82É\91\8dÝ\82µ\82È\82¢\82Ì\82Å\82±\82Ì\82Ü\82Ü\8fI\97¹
401                         Result := '';
402                         Exit;
403                 end;
404         end;
405         res                     := FDat[ inNo - 1 ];
406         tmp                     := InternalAbon( PChar( res ) );
407         try
408                 Result  := Dat2HTML( string( tmp ), inNo );
409         finally
410                 DisposeResultString( tmp );
411         end;
412
413 end;
414
415 // *************************************************************************
416 // \83X\83\8c\83b\83h\82Ì\83w\83b\83_ html \82ð\97v\8b\81\82³\82ê\82½
417 // *************************************************************************
418 function TMachiBBSThreadItem.GetHeader(
419         inOptionalHeader        : string
420 ) : string;
421 begin
422
423         // \93Æ\8e©\82É\83t\83B\83\8b\83^\83\8a\83\93\83O\82ð\8ds\82í\82È\82¢\8fê\8d\87\82Í
424         // InternalHeader \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
425         Result := InternalHeader(
426                 '<meta http-equiv="Content-type" content="text/html; charset=Shift_JIS">' +
427                 inOptionalHeader );
428
429
430         // GetRes \82ð\8cÄ\82Î\82ê\82é\82±\82Æ\82ª\97\\91z\82³\82ê\82é\82Ì\82Å FDat \82ð\90\90¬\82µ\82Ä\82¨\82­
431         if FDat <> nil then begin
432                 try
433                         FDat.Free;
434                         FDat := nil;
435                 except
436                 end;
437         end;
438         if IsLogFile then begin
439                 // dat \82Ì\93Ç\82Ý\8d\9e\82Ý
440                 FDat                    := TStringList.Create;
441                 FDat.LoadFromFile( FilePath );
442         end;
443
444 end;
445
446 // *************************************************************************
447 // \83X\83\8c\83b\83h\82Ì\83t\83b\83^ html \82ð\97v\8b\81\82³\82ê\82½
448 // *************************************************************************
449 function TMachiBBSThreadItem.GetFooter(
450         inOptionalFooter : string
451 ) : string;
452 begin
453
454         // \93Æ\8e©\82É\83t\83B\83\8b\83^\83\8a\83\93\83O\82ð\8ds\82í\82È\82¢\8fê\8d\87\82Í
455         // InternalFooter \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
456         Result := InternalFooter( inOptionalFooter );
457
458         // \82à\82¤ GetRes \82Í\8cÄ\82Î\82ê\82È\82¢\82Æ\8ev\82¤\82Ì\82Å FDat \82ð\8aJ\95ú\82µ\82Ä\82¨\82­
459         try
460                 if FDat <> nil then begin
461                         FDat.Free;
462                         FDat := nil;
463                 end;
464         except
465         end;
466
467 end;
468
469 // *************************************************************************
470 // \82Ü\82¿BBS\82Ì HTML \82ð 2ch \82Ì dat \8c`\8e®\82É
471 // *************************************************************************
472 procedure       TMachiBBSThreadItem.To2chDat(
473         ioHTML                  : TStringList
474 );
475 var
476         i, bound                : Integer;
477         foundPos                : Integer;
478         strTmp                  : string;
479         res                                     : TStringList;
480 const
481         MAIL_TAG                = '<a href="mailto:';
482 begin
483
484         //===== 2ch \82Ì dat \8c`\8e®\82É\95Ï\8a·
485         // \83z\83X\83g\96¼\82Ì\8cã\82Å\89ü\8ds\82³\82ê\82Ä\82¢\82½\82è\82·\82é\82Ì\82Å\89ü\8ds\82ð\82·\82×\82Ä\8eæ\82è\8f\9c\82­
486         ioHTML.Text     := StringReplace( ioHTML.Text, #13#10, '', [rfReplaceAll] );
487         // \91ã\82í\82è\82É <dt> \82ð\8ds\82Ì\8bæ\90Ø\82è\82É\82·\82é
488         ioHTML.Text     := StringReplace( ioHTML.Text, '<dt>', #10, [rfReplaceAll] );
489         // <dt> \82©\82ç\8en\82Ü\82Á\82Ä\82¢\82é\82Ì\82Å\8dÅ\8f\89\82Í\8bó\82Ì\82Í\82¸
490         if Length( ioHTML[ 0 ] ) = 0 then
491                 ioHTML.Delete( 0 );
492         // \83g\83\8a\83b\83v\82Ì\8cã\82Ì '<b> </b>' \82ð\8bó\82É
493         ioHTML.Text     := StringReplace( ioHTML.Text, '<b> </b>', '', [rfReplaceAll, rfIgnoreCase] );
494         // '<b>' \82Í\83\81\81[\83\8b\82Æ\96¼\91O\82Ì\8bæ\90Ø\82è
495         ioHTML.Text     := StringReplace( ioHTML.Text, '<b>', '<>', [rfReplaceAll, rfIgnoreCase] );
496         // \83\81\81[\83\8b\82Æ\96¼\91O\82É\82Â\82¢\82Ä\82­\82é\95Â\82\83^\83O\82ð\93\8a\8de\93ú\82Æ\82Ì\8bæ\90Ø\82è\82É
497         ioHTML.Text     := StringReplace( ioHTML.Text, '</b></a>', '<>', [rfReplaceAll, rfIgnoreCase] );
498         ioHTML.Text     := StringReplace( ioHTML.Text, '</b>', '<>', [rfReplaceAll, rfIgnoreCase] );
499         // '<dd>' \82ð\96{\95\82Æ\82Ì\8bæ\90Ø\82è\82É
500         ioHTML.Text     := StringReplace( ioHTML.Text, '<dd>', '<>', [rfReplaceAll, rfIgnoreCase] );
501
502         res := TStringList.Create;
503         try
504                 bound := ioHTML.Count - 1;
505                 for i := 0 to bound do begin
506                         res.Text := StringReplace( ioHTML[ i ], '<>', #10, [rfReplaceAll] );
507                         if res.Count >= 3 then begin    // 3 \96¢\96\9e\82Í\82 \82è\82¦\82È\82¢\82Æ\8ev\82¤\82¯\82Ç\88À\91S\82Ì\82½\82ß
508                                 foundPos := Pos( MAIL_TAG, res[ 0 ] );
509                                 if foundPos > 0 then begin
510                                         // \83\81\81[\83\8b\83A\83h\83\8c\83X\82ð\94²\82«\8fo\82·
511                                         foundPos        := foundPos + Length( MAIL_TAG );
512                                         res[ 0 ]        := Copy( res[ 0 ], foundPos, Length( res[ 0 ] ) );
513                                         strTmp          := Copy( res[ 0 ], 1, Pos( '">', res[ 0 ] ) - 1 );
514                                         // \83\81\81[\83\8b\82Æ\96¼\91O\82ª\8bt\82È\82Ì\82Å\82Ð\82Á\82­\82è\95Ô\82µ\82Ä\96ß\82·
515                                         res[ 0 ]        := res[ 1 ];
516                                         res[ 1 ]        := strTmp;
517                                 end else begin
518                                         // \83\81\81[\83\8b\82Æ\96¼\91O\82ª\8bt\82È\82Ì\82Å\82Ð\82Á\82­\82è\95Ô\82·
519                                         res[ 0 ]        := res[ 1 ];
520                                         res[ 1 ]        := '';
521                                 end;
522                                 res[ 2 ] := StringReplace( res[ 2 ], '[', 'ID:', [] );
523                                 res[ 2 ] := StringReplace( res[ 2 ], ']', '', [] );
524                         end;
525                         ioHTML[ i ] := StringReplace( res.Text, #13#10, '<>', [rfReplaceAll] );
526                 end;
527         finally
528                 res.Free;
529         end;
530
531 end;
532
533 // *************************************************************************
534 // TThreadItem \82ª\90\90¬\82³\82ê\82½\8fê\8d\87\82Ì\8f\88\92u(TMachiBBSThreadItem \82ð\90\90¬\82·\82é)
535 // *************************************************************************
536 procedure ThreadItemOnCreateOfTMachiBBSThreadItem(
537         inInstance : DWORD
538 );
539 var
540         threadItem : TMachiBBSThreadItem;
541 begin
542
543         threadItem := TMachiBBSThreadItem.Create( inInstance );
544         ThreadItemSetLong( inInstance, tipContext, DWORD( threadItem ) );
545
546 end;
547
548 // *************************************************************************
549 // TThreadItem \82ª\94j\8aü\82³\82ê\82½\8fê\8d\87\82Ì\8f\88\92u(TMachiBBSThreadItem \82ð\94j\8aü\82·\82é)
550 // *************************************************************************
551 procedure ThreadItemOnDisposeOfTMachiBBSThreadItem(
552         inInstance : DWORD
553 );
554 var
555         threadItem : TMachiBBSThreadItem;
556 begin
557
558         threadItem := TMachiBBSThreadItem( ThreadItemGetLong( inInstance, tipContext ) );
559         threadItem.Free;
560
561 end;
562
563 // =========================================================================
564 // TMachiBBSBoardItem
565 // =========================================================================
566
567 // *************************************************************************
568 // \83R\83\93\83X\83g\83\89\83N\83^
569 // *************************************************************************
570 constructor TMachiBBSBoardItem.Create(
571         inInstance      : DWORD
572 );
573 var
574         uri                                     : TIdURI;
575         uriList                 : TStringList;
576 begin
577
578         inherited;
579
580         OnDownload                                              := Download;
581         OnEnumThread                                    := EnumThread;
582         OnFileName2ThreadURL    := ToThreadURL;
583
584         FilePath                        := '';
585         FIsTemporary    := False;
586         FDat                                    := nil;
587
588         if Copy( URL, Length( URL ), 1 ) = '/' then
589                 uri := TIdURI.Create( URL )
590         else
591                 uri := TIdURI.Create( URL + '/' );
592         uriList := TStringList.Create;
593         try
594                 YofUtils.ExtractHttpFields( ['/', '?'], [], uri.Path, uriList );
595                 // http://hokkaido.machi.to/hokkaidou/subject.txt
596                 FilePath        := MyLogFolder + uriList[ 1 ] + '\' + uri.Document;
597                 IsLogFile       := FileExists( FilePath );
598         finally
599                 uri.Free;
600                 uriList.Free;
601         end;
602
603 end;
604  
605 // *************************************************************************
606 // \83f\83X\83g\83\89\83N\83^
607 // *************************************************************************
608 destructor TMachiBBSBoardItem.Destroy;
609 begin
610
611         if FDat <> nil then begin
612                 try
613                         FDat.Free;
614                         FDat := nil;
615                 except
616                 end;
617         end;
618
619         // \88ê\8e\9e\83t\83@\83C\83\8b\82Ì\8fê\8d\87\82Í\8dí\8f\9c\82·\82é
620         if FIsTemporary then
621                 DeleteFile( FilePath );
622
623         inherited;
624
625 end;
626
627 // *************************************************************************
628 // \8ew\92è\82µ\82½\83X\83\8c\88ê\97\97\82Ì\83_\83E\83\93\83\8d\81[\83h\82ð\97v\8b\81\82³\82ê\82½
629 // *************************************************************************
630 function TMachiBBSBoardItem.Download : TDownloadState;
631 var
632         modified                        : Double;
633         downResult              : PChar;
634         responseCode    : Longint;
635         uri                                             : TIdURI;
636         uriList                         : TStringList;
637 const
638         SUBJECT_NAME    = 'subject.txt';
639 begin
640
641         Result := dsError;
642
643         if Copy( URL, Length( URL ), 1 ) = '/' then
644                 uri := TIdURI.Create( URL + SUBJECT_NAME )
645         else
646                 uri := TIdURI.Create( URL );
647         uriList := TStringList.Create;
648         if FDat <> nil then begin
649                 try
650                         FDat.Free;
651                         FDat := nil;
652                 except
653                 end;
654         end;
655         FDat := TStringList.Create;
656         // \93Æ\8e©\82É\83_\83E\83\93\83\8d\81[\83h\82â\83t\83B\83\8b\83^\83\8a\83\93\83O\82ð\8ds\82í\82È\82¢\8fê\8d\87\82Í
657         // InternalDownload \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
658         modified := LastModified;
659         responseCode := InternalDownload( PChar( uri.URI ), modified, downResult );
660         try
661                 if responseCode = 200 then begin
662                         try
663                                 // \83p\83X\82ð\8eZ\8fo
664                                 YofUtils.ExtractHttpFields( ['/', '?'], [], uri.Path, uriList );
665                                 if MyLogFolder = '' then begin
666                                         // \82Ç\82±\82É\95Û\91\82µ\82Ä\82¢\82¢\82Ì\82©\95ª\82©\82ç\82È\82¢\82Ì\82Å\88ê\8e\9e\83t\83@\83C\83\8b\82É\95Û\91
667                                         FilePath                        := TemporaryFile;
668                                         FIsTemporary    := True;
669                                 end else begin
670                                         FilePath                        := MyLogFolder + uriList[ 1 ] + '\' + uri.Document;
671                                         FIsTemporary    := False
672                                 end;
673
674                                 // \95Û\91\97p\82Ì\83f\83B\83\8c\83N\83g\83\8a\82ð\8c@\82é
675                                 ForceDirectoriesEx( Copy( FilePath, 1, LastDelimiter( '\', FilePath ) ) );
676
677                                 FDat.Text := string( downResult );
678                                 // \95Û\91
679                                 FDat.SaveToFile( FilePath );
680                         finally
681                                 uri.Free;
682                                 uriList.Free;
683                         end;
684                         Result := dsComplete;
685                 end;
686         finally
687                 DisposeResultString( downResult );
688         end;
689
690 end;
691
692 // *************************************************************************
693 // \83X\83\8c\88ê\97\97\82Ì URL \82©\82ç\83X\83\8c\83b\83h\82Ì URL \82ð\93±\82«\8fo\82·
694 // *************************************************************************
695 function TMachiBBSBoardItem.ToThreadURL(
696         inFileName      : string        // \83X\83\8c\83b\83h\83t\83@\83C\83\8b\96¼
697 ) : string;                                                     // \83X\83\8c\83b\83h\82Ì URL
698 var
699         threadURL               : string;
700         uri                                     : TIdURI;
701         uriList                 : TStringList;
702         found                           : Integer;
703 begin
704
705         found := Pos( '.', inFileName );
706         if found > 0 then
707                 inFileName := Copy( inFileName, 1, found - 1 );
708         if Copy( URL, Length( URL ), 1 ) = '/' then
709                 uri := TIdURI.Create( URL )
710         else
711                 uri := TIdURI.Create( URL + '/' );
712         uriList := TStringList.Create;
713
714         try
715                 try
716                         // http://hokkaido.machi.to/hokkaidou/
717                         // http://hokkaido.machi.to/bbs/read.pl?BBS=hokkaidou&KEY=1061764446&LAST=50
718                         YofUtils.ExtractHttpFields( ['/', '?'], [], uri.Path, uriList );
719                         threadURL       := uri.Protocol + '://' + uri.Host + '/bbs/read.pl?' +
720                                 'BBS=' + uriList[ 1 ] + '&KEY=' + inFileName + '&LAST=50';
721                         Result          := threadURL;
722                 finally
723                         uri.Free;
724                         uriList.Free;
725                 end;
726         except
727                 Result := '';
728         end;
729
730 end;
731
732 // *************************************************************************
733 // \82±\82Ì\94Â\82É\82¢\82­\82Â\82Ì\83X\83\8c\82ª\82 \82é\82©\97v\8b\81\82³\82ê\82½
734 // *************************************************************************
735 procedure       TMachiBBSBoardItem.EnumThread(
736         inCallBack      : TBoardItemEnumThreadCallBack
737 );
738 var
739         uri                                     : TIdURI;
740         uriList                 : TStringList;
741 const
742         SUBJECT_NAME    = 'subject.txt';
743 begin
744
745         try
746                 if FDat = nil then begin
747                         FDat := TStringList.Create;
748
749                         if Copy( URL, Length( URL ), 1 ) = '/' then
750                                 uri := TIdURI.Create( URL + SUBJECT_NAME )
751                         else
752                                 uri := TIdURI.Create( URL );
753                         uriList := TStringList.Create;
754                         try
755                                 // \83p\83X\82ð\8eZ\8fo
756                                 YofUtils.ExtractHttpFields( ['/', '?'], [], uri.Path, uriList );
757                                 // http://hokkaido.machi.to/hokkaidou/subject.txt
758                                 FilePath        := MyLogFolder + uriList[ 1 ] + '\' + uri.Document;
759                                 if FileExists( FilePath ) then
760                                         // \93Ç\82Ý\8d\9e\82Ý
761                                         FDat.LoadFromFile( FilePath );
762                         finally
763                                 uri.Free;
764                                 uriList.Free;
765                         end;
766                 end;
767
768                 // \93Æ\8e©\82É\83t\83B\83\8b\83^\83\8a\83\93\83O\82ð\8ds\82í\82È\82¢\8fê\8d\87\82Í EnumThread \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
769                 inherited EnumThread( inCallBack, FDat.Text );
770         except
771         end;
772
773 end;
774
775 // *************************************************************************
776 // TBoardItem \82ª\90\90¬\82³\82ê\82½\8fê\8d\87\82Ì\8f\88\92u(TMachiBBSBoardItem \82ð\90\90¬\82·\82é)
777 // *************************************************************************
778 procedure BoardItemOnCreateOfTMachiBBSBoardItem(
779         inInstance : DWORD
780 );
781 var
782         boardItem : TMachiBBSBoardItem;
783 begin
784
785         boardItem := TMachiBBSBoardItem.Create( inInstance );
786         BoardItemSetLong( inInstance, bipContext, DWORD( boardItem ) );
787
788 end;
789
790 // *************************************************************************
791 // TBoardItem \82ª\94j\8aü\82³\82ê\82½\8fê\8d\87\82Ì\8f\88\92u(TMachiBBSBoardItem \82ð\94j\8aü\82·\82é)
792 // *************************************************************************
793 procedure BoardItemOnDisposeOfTMachiBBSBoardItem(
794         inInstance : DWORD
795 );
796 var
797         boardItem : TMachiBBSBoardItem;
798 begin
799
800         boardItem := TMachiBBSBoardItem( BoardItemGetLong( inInstance, bipContext ) );
801         boardItem.Free;
802
803 end;
804
805
806
807 // =========================================================================
808 // \83G\83\93\83g\83\8a\83|\83C\83\93\83g
809 // =========================================================================
810 procedure DLLEntry(
811         ul_reason_for_call : DWORD
812 );
813 var
814         module : HMODULE;
815 begin
816
817         case ul_reason_for_call of
818                 DLL_PROCESS_ATTACH:
819                 begin
820                         Randomize;
821
822                         module := GetModuleHandle( nil );
823
824                         LoadInternalAPI( module );
825                         LoadInternalFilePathAPI( module );
826                         LoadInternalThreadItemAPI( module );
827                         LoadInternalBoardItemAPI( module );
828
829                         // ===== \83C\83\93\83X\83^\83\93\83X\82Ì\8eæ\82è\88µ\82¢\82ð TThreadItem \82©\82ç TMachiBBSThreadItem \82É\95Ï\8dX\82·\82é
830                         ThreadItemOnCreate      := ThreadItemOnCreateOfTMachiBBSThreadItem;
831                         ThreadItemOnDispose     := ThreadItemOnDisposeOfTMachiBBSThreadItem;
832                         // ===== \83C\83\93\83X\83^\83\93\83X\82Ì\8eæ\82è\88µ\82¢\82ð TBoardItem \82©\82ç TMachiBBSBoardItem \82É\95Ï\8dX\82·\82é
833                         BoardItemOnCreate               := BoardItemOnCreateOfTMachiBBSBoardItem;
834                         BoardItemOnDispose      := BoardItemOnDisposeOfTMachiBBSBoardItem;
835                 end;
836                 DLL_PROCESS_DETACH:
837                         ;
838                 DLL_THREAD_ATTACH:
839                         ;
840                 DLL_THREAD_DETACH:
841                         ;
842         end;
843
844 end;
845
846 exports
847         OnVersionInfo,
848         OnAcceptURL;
849
850 begin
851
852         try
853                 DllProc := @DLLEntry;
854                 DLLEntry( DLL_PROCESS_ATTACH );
855         except end;
856
857 end.